Compare commits

..

149 Commits

Author SHA1 Message Date
Matthias Clasen 95a3d3c26f Add a demo for path ops 2022-04-06 23:59:17 -04:00
Matthias Clasen df1bca3027 Add some tests for pathops 2022-04-06 23:59:17 -04:00
Matthias Clasen acf81a0377 path: Implement path ops
Implement boolean operations on paths.
2022-04-06 23:59:17 -04:00
Matthias Clasen bf8fca391a path: Add gsk_path_transform
This is an obvious operation to want for paths.
2022-04-06 23:57:28 -04:00
Matthias Clasen aa722dcfac path: Add gsk_path_is_convex
Add a function to compute whether a path is convex.
2022-04-06 23:52:03 -04:00
Matthias Clasen f5ccf63550 path: Add gsk_path_get_flags
The flags contain some useful information.
2022-04-06 23:52:03 -04:00
Matthias Clasen 61f9b1d9e0 path: Add gsk_path_reverse
This is a natural operation, and useful for debugging things.
2022-04-06 23:52:03 -04:00
Matthias Clasen aa8ba37a0f curve: Cosmetics 2022-04-06 23:47:35 -04:00
Matthias Clasen 86c1493b4b contour: Fix winding for circle contour
Test included.
2022-04-06 23:47:35 -04:00
Matthias Clasen f58513e4dd path: Fix gsk_path_measure_in_fill
This was getting some simple polygon cases wrong :(

The problem here is a misunderstanding in line_get_crossing.
It was returning 'on edge' when a point of the polygon is
on the ray, which is not what 'on edge' is about.

Add some test cases involving horizontal edges that coincide
with the test point.
2022-04-06 23:47:35 -04:00
Matthias Clasen c816eee4d1 curve: Add more line-curve intersection tests 2022-04-06 23:47:35 -04:00
Matthias Clasen 436fe18084 curve: Add another intersection test 2022-04-06 23:47:35 -04:00
Matthias Clasen 3ba2659860 curve: Specify tolerance for intersections
We had 0.1 hardcoded in a bunch of places.
2022-04-06 23:47:35 -04:00
Matthias Clasen ffc5fdb9de Add a debug key for paths
Not used yet.
2022-04-06 23:47:35 -04:00
Matthias Clasen e88cf34642 curve: Add gsk_curve_print for debugging 2022-04-06 23:47:35 -04:00
Matthias Clasen 5eb79b3b01 curve: Move some curve apis to gskcurve.c
Some of this will be reused elsewhere, so move
it out of gskpathstroke.c.
2022-04-06 23:47:35 -04:00
Matthias Clasen cf793f94ac curve: Use GskBoundingBox 2022-04-06 23:47:35 -04:00
Matthias Clasen ba405bfbf1 Add a bounding box type
graphene_rect_t does not quite work for this purpose.
2022-04-06 23:47:35 -04:00
Matthias Clasen 4b6223e07d curve: Handle self-intersection 2022-04-06 23:47:35 -04:00
Matthias Clasen 8637ea8fb8 curve: Small improvement 2022-04-06 23:47:35 -04:00
Matthias Clasen ab8a32bc76 curve: Refine line-line intersection
For collinear line segments, return up to 2 intersections
for the endpoints of the intersection (which is also a
line segment in this case).

Tests included.
2022-04-06 23:47:35 -04:00
Matthias Clasen 6aa38e6b39 curve: Fix line-curve intersections 2022-04-06 23:47:35 -04:00
Matthias Clasen 46bef9b667 curve test: Fix the split test
We were misinterpreting the return value of
gsk_path_measure_get_closest_point.
2022-04-06 23:47:35 -04:00
Matthias Clasen 86862d56bd curve: Skip a failing test
We don't have a good error bound for approximating
conics. Until that changes, skip this test.
2022-04-06 23:47:35 -04:00
Matthias Clasen 1e5c025a1e curve: Add another intersection test 2022-04-06 23:47:35 -04:00
Matthias Clasen 60f87f2a7d path: Fix a typo 2022-04-06 23:47:35 -04:00
Matthias Clasen 001903aeef contour: Add some docs 2022-04-06 23:47:35 -04:00
Matthias Clasen 3b04f5a963 Allow showing points in curve2 test
This is useful for debugging intersections.
2022-04-06 23:47:35 -04:00
Matthias Clasen de29a01682 gsk: Use stroke bounds in the stroke node
We can use gsk_path_get_stroke_bounds to get a
better estimate for the bounds of the stroke node.
2022-04-06 23:47:35 -04:00
Matthias Clasen bd3b88b276 gtk-demo: Use gsk_path_builder_add_layout
We have an api now to hide the cairo use.
2022-04-06 23:47:35 -04:00
Matthias Clasen e7a9030fb5 gsk: Add gsk_path_builder_add_layout
This api makes it easy to turn text into a path that
can be further manipulated. The implementation currently
goes via cairo.
2022-04-06 23:47:35 -04:00
Matthias Clasen 98aca9e2cd curve2: Show osculating circles 2022-04-06 23:47:35 -04:00
Matthias Clasen 6e8b23d485 Add gsk_path_measure_get_curvature 2022-04-06 23:47:35 -04:00
Matthias Clasen 05678d44ca Add an interactive path test
This one is for interactive exploring of svg paths.

You can enter an SVG path in the entry and hit Enter
to see how GSK renders it. If you click the button
in the headerbar, you can see what GTK thinks the
closest point, tangent and distance are wrt. to the
mouse position, and the bounding box of the path.

There's also stroke parameters to play with.
2022-04-06 23:47:35 -04:00
Matthias Clasen 7b4bc08254 stroke: Make gsk_stroke_to_cairo public
It comes in handy, and does no harm.
2022-04-06 23:47:35 -04:00
Matthias Clasen 3b81a9e529 gtk-demo: Add a curve editor demo 2022-04-06 23:47:35 -04:00
Matthias Clasen 6a0901c40e Implement offsetting
Implement offsetting of paths by reusing the
infrastructure of the stroker.
2022-04-06 23:47:33 -04:00
Matthias Clasen a7b3a0c472 Add gsk_path_offset
Add a function that takes a path, and offsets it
by some distance, applying line-join parameters
as needed.

This commit just adds the api, the implementation
will be in the following commit.
2022-04-06 23:47:08 -04:00
Matthias Clasen 645eddf838 stroker: Implement arcs
Implement arced joins as specified in SVG2.
2022-04-06 23:47:08 -04:00
Matthias Clasen ada1cc7dc5 Add GSK_LINE_JOIN_ARCS
Implementation will follow.
2022-04-06 23:47:08 -04:00
Matthias Clasen 042a3a1344 Add another stroker test
Check that the outlines of random paths look as
expected. We currently have to exclude paths where
points get too close to each other.
2022-04-06 23:47:08 -04:00
Matthias Clasen 2542d913bc Add basic tests for strokes
Add tests to check that any point on a path is
always at most half the line-width away from the
stroke path with that line-width.
2022-04-06 23:47:08 -04:00
Matthias Clasen fc619b9783 Implement stroking
Implement gsk_contour_default_add_stroke, which takes a contour
and stroke parameters, and adds contours to a path builder for
the outline that would be produced by stroking the path with these
parameters.

The current implementation does not try to handle short segments
in the vicinity of sharp joins in any special way, so there can
be some artifacts in that situation.
2022-04-06 23:47:06 -04:00
Matthias Clasen 3c56f326fc Special-case circles for strokes
The outline of a circle is just two circles.
2021-12-04 22:58:23 -05:00
Matthias Clasen 4c81600fc9 Special-case rects for strokes
In many cases, the outline of a rectangle is just
two rectangles.
2021-12-04 22:58:23 -05:00
Matthias Clasen 68661b7291 Add gsk_path_stroke
Add the plumbing that will let us do special-case stroking
for rectangles, circles and other special contours. There
is no implementation yet.
2021-12-04 22:58:23 -05:00
Matthias Clasen b6a2a8b0fc Add gsk_curve_get_curvature
This will be used in the stroker.
2021-12-04 22:58:23 -05:00
Matthias Clasen d08802a2c3 Add gsk_curve_get_normal
Its easy but thats no reason not to have this api.
2021-12-04 22:58:23 -05:00
Matthias Clasen 759bf152d3 Add a test for gsk_curve_offset
The stroker relies on offsetting.
This only tests a few very simple cases.
2021-12-04 22:58:23 -05:00
Matthias Clasen 021bca4629 Add a test for gsk_curve_reverse
The stroker relies on this working.
2021-12-04 22:58:23 -05:00
Matthias Clasen f91995db06 Add a test for tangents of degenerate curves
The stroker relies on these to work.
2021-12-04 22:58:23 -05:00
Matthias Clasen ad145e201b curve: Handle degenerate cases
Nothing prevents control points from being identical,
and if that happens, some of our constructions involving
tangents and normals break down. Handle these cases in
get_{start,end}_tangent and offset, for the case of
cubics.
2021-12-04 22:58:23 -05:00
Matthias Clasen 0bef169b32 Add gsk_curve_reverse
This will be used in stroking.
2021-12-04 22:58:23 -05:00
Matthias Clasen 5f40db5043 Add gsk_curve_offset
This method creates an offset curve from an existing
curve by just moving the control points laterally.

This will be used in stroking.
2021-12-04 22:58:23 -05:00
Matthias Clasen afd398112b Add conic decomposition tests
We don't have good error bounds here, unfortunately.
2021-12-04 22:58:23 -05:00
Matthias Clasen 98591b383b path: support conic->curve in foreach 2021-12-04 22:58:23 -05:00
Matthias Clasen 319e75c73f Add gsk_curve_decompose_curve
This is mainly useful for decomposing a conic into
cubics. The criterion here for terminating the
subdivision is very improvised.
2021-12-04 22:58:23 -05:00
Matthias Clasen c80d5f89d8 Add a performance test for curve eval
All curve types are equally fast here.
2021-12-04 22:58:23 -05:00
Matthias Clasen 8dd4f022c1 Add a performance test for curve intersection
This shows how much more expensive curve
intersections are.
2021-12-04 22:58:23 -05:00
Matthias Clasen ee42889790 Add curve split tests 2021-12-04 22:58:23 -05:00
Matthias Clasen bd0f92fa8b Add another intersection testcase
This tests horizontal line/conic intersection,
which failed before the fix in the previous commit.
2021-12-04 22:58:23 -05:00
Matthias Clasen c438527e49 xxx: work around bounding box problems
graphene_rect_intersect returns FALSE when you
intersect the bounding boxes of axis-aligned
lines, so we never find intersections with those.

Make things better by making the bounding boxes worse.
2021-12-04 22:58:23 -05:00
Matthias Clasen dad4e1da22 Add curve intersection tests
These tests check that gsk_curve_intersect finds
the intersections we want.
2021-12-04 22:58:23 -05:00
Matthias Clasen 52394b1520 Add gsk_curve_intersect
Add a way to find the intersections of two curves.
This will be used in stroking.
2021-12-04 22:58:23 -05:00
Matthias Clasen 3efd6c9a12 Add gsk_curve_get_bounds
Add getters for bounding boxes of curves.
2021-12-04 22:58:23 -05:00
Matthias Clasen 6d04869c85 Only test conic weights between 1/20 and 20
The rest just give us no end of numeric trouble.
2021-12-04 22:58:23 -05:00
Matthias Clasen 5781a8c66a xxx: Link ottie tests against libottie 2021-12-04 22:58:23 -05:00
Matthias Clasen 65813c3f19 xxx: Link ottie tools against libottie 2021-12-04 22:58:23 -05:00
Benjamin Otte 33d5acf1db Ottie: Add ottie-editor 2021-12-04 22:58:23 -05:00
Benjamin Otte d0d62ee0d6 ottie: Add a snapshot testsuite test
The test takes a lottie file and a timestamp in seconds and produces a
rendernode at that timestamp.

It then serializes that node and compares it via diff(1) with a file
containing the expected output.

This is a lot stricter than it needs to be (because different node files
can generate the same output and updates to the rendering pipeline can
break everything) but I chose this method on purpose because it does a
good job at guarding against accidental changes in other parts of the
code.

It's also better than comparing image output because it avoids
antialiasing artifacts when using curves and things like that.
2021-12-04 22:58:23 -05:00
Benjamin Otte e359e0b146 ottie: Add a command-line tool
Supports:

 * Taking a screenie:
   ottie image file.lottie image.png

 * Recording a rendernode:
   ottie node file.lottie render.node

 * Encoding an image:
   ottie video file.lottie video.webm
2021-12-04 22:58:23 -05:00
Benjamin Otte 4a633e4476 Ottie: Add 2021-12-04 22:58:23 -05:00
Benjamin Otte 78c2fca48e path: Add gsk_path_builder_add_ellipse() 2021-12-04 22:58:23 -05:00
Benjamin Otte 9b93dcbbd8 path: Change semantics of gtk_path_builder_add_segment()
Allow start >= end to mean that the path continues at the beginning
after reaching the end until it reaches the point at @end.
2021-12-04 22:58:23 -05:00
Benjamin Otte 5acdc8358e path: Add gsk_path_measure_is_closed () 2021-12-04 22:58:23 -05:00
Benjamin Otte 9ecd508b86 path: Add gsk_path_measure_restrict_to_contour() 2021-12-04 22:58:23 -05:00
Matthias Clasen 452eb951d7 Add gsk_path_measure_get_{path,tolerance}
These are just nice apis to have and avoid having to carry
these around as extra arguments in many places.

This was showing up as inconvenience in writing tests
for the measure apis.
2021-12-04 22:58:23 -05:00
Benjamin Otte a6e665c6f3 gtk-demo: Use dashes in path-fill demo 2021-12-04 22:58:23 -05:00
Matthias Clasen 19f2eddf46 Add gsk_path_get_stroke_bounds
A relatively cheap way to get bounds for the area
that would be affected by stroking a path.
2021-12-04 22:58:23 -05:00
Benjamin Otte a79d427a4b testsuite: Add tests for the dasher 2021-12-04 22:58:23 -05:00
Benjamin Otte f9d794d315 path: Add a foreach function that dashes a path 2021-12-04 22:58:23 -05:00
Benjamin Otte 75b96fa7cf path: Deal with non-uniformness of progress parameter
The progress is non-uniform, so simple translation of progress doesn't work.
So check if larger and smaller values inch closer towards minimal distance.
2021-12-04 22:58:23 -05:00
Benjamin Otte a5763a3de1 path: Always decompose conics into at least 2 segments
Conics are evil in that their parameter skews towards the center, and if
it's a very flat conic (weight almost equal to 0), then we'd approximate
it with a single segment and not subdivide, which would cause the
parameter to be wildly off around 0.25 or 0.75.

And that would cause offset calculations to fail.
2021-12-04 22:58:23 -05:00
Matthias Clasen 6c9206d46a testsuite Add curve tangent tests 2021-12-04 22:58:23 -05:00
Benjamin Otte 918b07967c testsuite: Add a test for the conic that got us segment() 2021-12-04 22:58:23 -05:00
Benjamin Otte 2f3aae002d path: Add gsk_curve_segment()
Using split() twice with scaled t values does not work with conics.
2021-12-04 22:58:23 -05:00
Benjamin Otte a1f9a45f17 testsuite: Add a test for gsk_curve_decompose() 2021-12-04 22:58:23 -05:00
Benjamin Otte 57cf7e6fc2 testuite: Add tests for gsk_curve_get_tangent() 2021-12-04 22:58:23 -05:00
Matthias Clasen 5c817aef62 testuite: Add tests for gsk_curve_get_point()
Add a few tests for gsk_curve_get_point().

Since GskCurve is not public api, we add gskcurve.c
as source to the test binary.
2021-12-04 22:58:23 -05:00
Benjamin Otte b7c83ce45c curve: Split eval() into get_point() and get_tangent()
That's more in line with the get_start/end_point/tangent() functions.

Plus, those calls are independent and we usually want one or the other.
2021-12-04 22:58:23 -05:00
Matthias Clasen 8b13ea22be Add gsk_curve_get_{start,end}_tangent
Add a way to get the tangents at the start and end of the curve.
This will be used in stroking.
2021-12-04 22:58:23 -05:00
Benjamin Otte a21b3c7337 testsuite: Add conics to the random paths 2021-12-04 22:58:23 -05:00
Benjamin Otte eb04db32e0 path: Add GskCurve
GskCurve is an abstraction for path operations. It's essentially a
collection of vfuncs per GskPathOperation.

GskStandardContour has been ported to use it where appropriate.
2021-12-04 22:58:23 -05:00
Benjamin Otte faf0c8d1f9 path: Introduce gskpathop
A gskpathop is a pointer to a graphene_point_t* with the low bits used
to encode the GskPathOperation. It's an easy way to introduce API for
operations.

So far it's just used to replace GskStandardOperation.
2021-12-04 22:43:32 -05:00
Benjamin Otte 2f91089b71 WIP: css: Replace border rendering code with GskPath
The weight is wrong still, I need to compute the correct one to get real
45deg circle corners and not just roughly correct ones.
2021-12-04 22:43:32 -05:00
Benjamin Otte b0a1b4f672 WIP: pathbuilder: Add gsk_path_builder_add_rounded_rect()
It works, but does not use a custom contour yet.
2021-12-04 22:43:32 -05:00
Benjamin Otte 599baee1c4 path: Add conic curves
So far this just adds the API, if you use it, you'll get lots of
g_warnings().

This will be fixed in future commits.
2021-12-04 22:43:32 -05:00
Benjamin Otte 51d645168f path: Rename to gtk_path_builder_add_segment()
It's about bulding paths, not about measuring them.
2021-12-04 22:43:32 -05:00
Benjamin Otte 5991f4b6c9 path: Split contours into their own file
I'm not sure I want to keep all contours in one file, but for now that's
how it is.
2021-12-04 22:43:32 -05:00
Benjamin Otte a41b9ebec7 path: Make all private contour APIs take a GskContour
... instead of a path, index tuple.
2021-12-04 22:43:32 -05:00
Matthias Clasen 50098ba75b Add a nodeparser tests for fill and stroke nodes 2021-12-04 22:43:32 -05:00
Benjamin Otte c804a3863e stroke: Add support for dashes
... and hook it up in the node parser and for Cairo rendering.
2021-12-04 22:43:32 -05:00
Matthias Clasen a94a8857cc gsk: Implement parsing fill and stroke nodes
Make serialization and deserialization work for stroke and
fill nodes.
2021-12-04 22:43:31 -05:00
Benjamin Otte 4b5ee6e45d path: Add flags to gsk_path_foreach()
This way we can default to the siplest possible foreach() output - like
cairo_copy_path_flat() decomposing everything into lines - and add flags
to get more and more fancy.

This will be useful to have conics automatically decomposed for Cairo
drawing or if we want to add more line types in the future.
2021-12-04 22:43:31 -05:00
Benjamin Otte e30ef1989d testsuite: Add an in_fill() test 2021-12-04 22:43:31 -05:00
Matthias Clasen 288e8a46cf Implement gsk_path_measure_in_fill
Implement this in the obvious way, using the decomposed form
of standard contours. Since the decomposed form is part of the
measure object, this api moves from gsk_path_in_fill to
gsk_path_measure_in_fill.
2021-12-04 22:43:31 -05:00
Benjamin Otte db8d6e3d99 testsuite: Add a parsing test
This test includes an implementation of a gsk_path_equal() func with
a tolerance that is necessary because parsing does not always work
100% exactly due to floating point rounding, so we can't just
compare the to_string() output.
2021-12-04 22:43:31 -05:00
Matthias Clasen 9d313b3264 path: Special-case rects and circles
Write out the commands for rects and circles in a special
way, and add code in the parser to recognize this, so we
can successfully round-trip these through the SVG path format.

The special way - for people who want to use it for debugging -
for now is that we use uppercase "Z" to close standard paths, but
lowercase "z" to close our special paths.

A test is included, but the random path serializations should take care
of it, too.
2021-12-04 22:43:31 -05:00
Matthias Clasen a28311883f path: Fix serialization for circles
The svg A can not do a full circle, since it is a two point
parametrization - if the start and end point are the same,
it draws nothing. So, use two arcs.
2021-12-04 22:43:31 -05:00
Benjamin Otte aa695ae1c2 testsuite: Add librsvg path tests 2021-12-04 22:43:31 -05:00
Matthias Clasen 42b017d073 path: Implement gsk_path_parse
Implement the SVG path syntax to read back the strings
that we generate when serializing paths. The tests for
this code are taken from librsvg.

This includes an elliptical arc implementation according
to the SVG spec. The code is mostly taken from librsvg,
but pretty directly follows the SVG spec implementation
notes. We don't export this, since the parametrization
is inconvenient. We do want an arc_to API, but
these are not the arcs we are looking for.
2021-12-04 22:43:31 -05:00
Matthias Clasen 0414c15537 path: Implement SVG arcs
This is elliptical arc implementation according to the SVG spec.
The code is mostly taken from librsvg, but pretty directly
follows the SVG spec implementation notes.

We don't export this, since the parametrization is inconvenient.
We do want an arc_to API, but these are not the arcs we are
looking for.

It will be used in parsing SVG path syntax.
2021-12-04 22:43:31 -05:00
Matthias Clasen 98435e15dd stroke: Add miter limit
Add a miter limit to GskStroke. This will be needed to
fully implement line joins.

Also introduce the GSK_LINE_JOIN_MITER_CLIP value,
following SVG 2.0. cairo does not have it, so translate
it to plain miter when using cairo.
2021-12-04 22:43:31 -05:00
Benjamin Otte 7f63e426cc testsuite: Add relative path functions
They're making the paths slightly weirder, but they test public API, so
woohoo!
2021-12-04 22:43:31 -05:00
Benjamin Otte 9e450370e8 pathbuilder: Add relative path commands
And gsk_path_builder_get_current_point().

They will be needed by the string parser.
2021-12-04 22:43:31 -05:00
Benjamin Otte 5fe35cb0fd path: Add GSK_CIRCLE_POINT_INIT() to initialize points on the circle
This is just splitting out a commonly done operation into a macro.
2021-12-04 22:43:31 -05:00
Benjamin Otte 70f097ebd7 pathbuilder: Redo semantics for starting curves
We now always have a "current point" which is either the last point an
operation was made to, or (0, 0) if no drawing operation has
been made yet.

Adding a contour of any kind to the builder will always update the
current point to that contour's end point.
2021-12-04 22:43:31 -05:00
Benjamin Otte 1f1641fdfc gtk-demo: Show closest point in text-on-path demo 2021-12-04 22:43:31 -05:00
Benjamin Otte dce77dc2a5 path: Split GskPathBuilder into its own file
... and add missing API docs.
2021-12-04 22:42:38 -05:00
Benjamin Otte 1500250116 testsuite: Add a test using get_point() and get_closest_point() 2021-12-04 22:42:38 -05:00
Benjamin Otte 8a5c2e771a testsuite: Add a test for get_point() 2021-12-04 22:42:38 -05:00
Benjamin Otte 419f4f87a4 testsuite: Update create_random_path()
1. Allow specifying the max number of contours
2. Be smarter about creating the paths:
   With 10% chance, create a "weird" path like the empty one or only
   points or things like that.
   Otherwise create a bunch of contours, with 2/3 a standard contour,
   with 1/3 a predetermined one.
2021-12-04 22:42:38 -05:00
Benjamin Otte 0f848aace9 gtk-demo: Add cute maze demo 2021-12-04 22:42:38 -05:00
Benjamin Otte c1111d9c4d testsuite: Add tests for gsk_path_measure_get_closest_point() 2021-12-04 22:42:14 -05:00
Benjamin Otte 614e043c94 path: Add gsk_path_measure_get_closest_point()
... and gsk_path_measure_get_closest_point_full().

Those 2 functions allow finding the closest point on a path to a given
point.
2021-12-04 22:42:14 -05:00
Benjamin Otte ee8ad62bb5 spline: Use Skia's tolerance checks
This avoids measuring being too far off (it's still off, but it's less
than a percent now.
2021-12-04 22:42:14 -05:00
Benjamin Otte ec6d102dc5 testsuite: Add tests for gsk_path_measure_add_segment() 2021-12-04 22:42:14 -05:00
Benjamin Otte c82cfa8b0c gtk-demo: Add a text-on-path demo 2021-12-04 22:42:14 -05:00
Benjamin Otte ab41ef43d8 gtk-demo: Add a path-fill demo 2021-12-04 22:42:14 -05:00
Benjamin Otte aa913100ba path: Add gsk_path_measure_get_point()
Allows querying the coordinates and direction of any specific point on a
path.
2021-12-04 22:40:12 -05:00
Matthias Clasen 0e256a6935 path: Add gsk_path_add_circle()
Adds a circle contour, too.
2021-12-04 22:40:12 -05:00
Benjamin Otte 2559885415 pathmeasure: Implement support for beziers
Instead of treating bezier curves as lines, we properly decompose them
into line segments now so that we can treat those as lines.
2021-12-04 22:40:12 -05:00
Benjamin Otte d9a79c0a2f path: Implement gsk_path_to_cairo() using foreach() 2021-12-04 22:40:12 -05:00
Benjamin Otte 268efbd5a6 path: Add gsk_path_foreach() 2021-12-04 22:40:12 -05:00
Benjamin Otte a14770a47d path: Collect flags
We don't need them yet, but maybe later.
2021-12-04 22:40:12 -05:00
Benjamin Otte d42f45bc5a testsuite: Add path tests 2021-12-04 22:40:12 -05:00
Benjamin Otte 87b108209e pathmeasure: Add gsk_path_measure_add_segment()
This allows chunking paths, weeee.
2021-12-04 22:40:12 -05:00
Benjamin Otte 5ac2abc6d9 path: Add gsk_path_builder_add_path() 2021-12-04 22:40:12 -05:00
Benjamin Otte 9c721bff1b gsk: Add GskPathMeasure
An object to do measuring operations on paths - determining their
length, cutting off subpaths, things like that.
2021-12-04 22:40:12 -05:00
Benjamin Otte 78cae537a9 path: Change data structure for standard path
Instead of the Cairo method and imitating cairo_path_data_t, use the
Skia method and keep points and operations separate.

That way we get a points array that includes the starting point -
because it's always the end point of the previous operation.
2021-12-04 22:40:12 -05:00
Benjamin Otte 69cdeb6037 popover: Use fill and stroke nodes instead of Cairo
... to render the arrow.

The arrow should really be turned into a real thing - maybe an icon?
2021-12-04 22:40:12 -05:00
Benjamin Otte 8d1f354d01 snapshot: Add gtk_snapshot_push_stroke() 2021-12-04 22:40:12 -05:00
Benjamin Otte c8928bf064 gsk: Add GskStrokeNode 2021-12-04 22:40:12 -05:00
Benjamin Otte 2b029af78f gsk: Add GskStroke
It's unused in this commit. This just prepares the new object.
2021-12-04 22:40:12 -05:00
Benjamin Otte 592bab384f demos: Add a simple demo filling a path 2021-12-04 22:40:12 -05:00
Benjamin Otte ffebe0c340 snapshot: Add gtk_snapshot_push_fill() 2021-12-04 22:40:12 -05:00
Benjamin Otte 16ca8f75fd gsk: Add GskFillNode
Take a rendernode as source and a GskPath and fill the region in the
path just like cairo_fill() would.
2021-12-04 22:40:12 -05:00
Benjamin Otte 955762c5e8 gsk: Add GskPath 2021-12-04 22:40:12 -05:00
Matthias Clasen c49205c1e6 gtk-demo: Small fixup to the cursors demo 2021-12-04 22:40:12 -05:00
395 changed files with 62574 additions and 21101 deletions
+1 -1
View File
@@ -167,7 +167,7 @@ macos:
needs: []
before_script:
- bash .gitlab-ci/show-info-osx.sh
- pip3 install --user meson==0.59
- pip3 install --user meson==0.56
- pip3 install --user ninja
- export PATH=/Users/gitlabrunner/Library/Python/3.7/bin:$PATH
- export MESON_FORCE_BACKTRACE=1
+1 -1
View File
@@ -5,7 +5,7 @@ call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliar
@echo on
:: FIXME: make warnings fatal
pip3 install --upgrade --user meson==0.59 || goto :error
pip3 install --upgrade --user meson==0.56.2 || goto :error
meson -Dmedia-gstreamer=disabled _build || goto :error
ninja -C _build || goto :error
+1 -2
View File
@@ -31,8 +31,7 @@ pacman --noconfirm -S --needed \
mingw-w64-$MSYS2_ARCH-pango \
mingw-w64-$MSYS2_ARCH-fribidi \
mingw-w64-$MSYS2_ARCH-gst-plugins-bad \
mingw-w64-$MSYS2_ARCH-shared-mime-info \
mingw-w64-$MSYS2_ARCH-python-gobject
mingw-w64-$MSYS2_ARCH-shared-mime-info
mkdir -p _ccache
export CCACHE_BASEDIR="$(pwd)"
-119
View File
@@ -1,127 +1,8 @@
Overview of Changes
===================
* GtkProgressBar:
- Fix handling of "inverted"
* GtkLabel:
- Add a "natural wrap mode" property to influence how
natural width is determined
* GtkTextView
- Scroll insertion on-screen after undo / redo
* gsk:
- Abort region diffing when changes are too complex
* gdk:
- Avoid compressing discrete scroll events
- Fix problems with hiding windows
- Improve GL and GLES version checks
* Wayland:
- Support new high-contrast setting
* Inspector:
- Add DND inspection support
* build:
- Avoid deprecated meson apis
* Translation updates
Galician
Portuguese
Ukrainian
Overview of Changes in 4.5.1, 16-12-2021
========================================
* GtkWidget sizing has been rewritten to implement
width-for-height more properly. This had some fallout,
and some widgets may still not react kindly to the
new way of doing things.
See https://blog.gtk.org/2021/12/03/sizable-news/
for details, and please file issues if you notice fallout.
* Rename git `master` branch to `main`
* Css:
- Fully support font-variant-caps
- Fix a crash with gradients
* Make various widgets activatable:
- GtkComboBox
- GtkDropDown
* GtkPopover:
- Make focus indicators not disappear
* GtkTextView:
- Don't leave embedded children stranded when scrolling
- Don't insert Emoji into non-editable textviews
- Fix Emoji chooser positioning
- Fix problems with pasting text
- Improve scroll-to-mark behavior
- Support right-aligned, centered and decimal tabs
- Make child anchor replacement character settable
- Provide more context to input methods
* GtkDragIcon:
- Provide default icons for paintables and files
* GtkBuilder:
- Speed up template precompilation
* Actions:
- Reduce allocations during signal emissions
- Avoid duplication and unnecessary recursion
* Inspector:
- Show the selected im-module in the General tab
- Add a clipboard viewer
- Make the recorder record events too
- Add a graph visualizing gtk_widget_measure()
* Gsk:
- Fix hexbox rendering
- Fix transformed linear gradient rendering
* Printing:
- Fix dialog-less printing
* Windows:
- Use the common EGL setup code
- Respect GDK_DEBUG=gl-egl
- Fix AeroSnap indicator and positioning
* X11:
- Improve behavior of windows drags on headerbar controls
- Trap errors for RANDR changes
- Fix problems with drag icons
* Wayland:
- Ensure we prefer the Wayland im-module over others
* Translation updates
Basque
Catalan
Croatian
Friulian
Galician
Hebrew
Icelandic
Italian
Latvian
Lithuanian
Occitan
Persian
Portuguese
Spanish
Swedish
Ukrainian
Overview of Changes in 4.5.0
============================
+1 -1
View File
@@ -17,7 +17,7 @@ executable('gtk4-constraint-editor',
c_args: common_cflags,
dependencies: libgtk_dep,
include_directories: confinc,
win_subsystem: 'windows',
gui_app: true,
link_args: extra_demo_ldflags,
install: false,
)
+127 -316
View File
@@ -1,10 +1,12 @@
/* Clipboard
*
* GdkClipboard is used for clipboard handling. This demo shows how to
* copy and paste text, images, colors or files to and from the clipboard.
* copy and paste text to and from the clipboard.
*
* You can also use Drag-And-Drop to copy the data from the source to the
* target.
* It also shows how to transfer images via the clipboard or via
* drag-and-drop, and how to make clipboard contents persist after
* the application exits. Clipboard persistence requires a clipboard
* manager to run.
*/
#include <glib/gi18n.h>
@@ -12,103 +14,22 @@
#include <string.h>
#include "demoimage.h"
static GtkWidget *window = NULL;
static void
copy_button_clicked (GtkStack *source_stack,
gpointer user_data)
copy_button_clicked (GtkWidget *button,
gpointer user_data)
{
GtkWidget *entry;
GdkClipboard *clipboard;
const char *visible_child_name;
GtkWidget *visible_child;
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (source_stack));
entry = GTK_WIDGET (user_data);
visible_child = gtk_stack_get_visible_child (source_stack);
visible_child_name = gtk_stack_get_visible_child_name (source_stack);
/* Get the clipboard object */
clipboard = gtk_widget_get_clipboard (entry);
if (strcmp (visible_child_name, "Text") == 0)
{
gdk_clipboard_set_text (clipboard, gtk_editable_get_text (GTK_EDITABLE (visible_child)));
}
else if (strcmp (visible_child_name, "Image") == 0)
{
GtkWidget *child;
for (child = gtk_widget_get_first_child (visible_child); child; child = gtk_widget_get_next_sibling (child))
{
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (child)))
{
GtkWidget *image = gtk_widget_get_first_child (child);
GdkPaintable *paintable = gtk_image_get_paintable (GTK_IMAGE (image));
if (GDK_IS_TEXTURE (paintable))
gdk_clipboard_set (clipboard, GDK_TYPE_TEXTURE, paintable);
else
gdk_clipboard_set (clipboard, GDK_TYPE_PAINTABLE, paintable);
break;
}
}
}
else if (strcmp (visible_child_name, "Color") == 0)
{
GdkRGBA color;
gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (visible_child), &color);
gdk_clipboard_set (clipboard, GDK_TYPE_RGBA, &color);
}
else if (strcmp (visible_child_name, "File") == 0)
{
gdk_clipboard_set (clipboard, G_TYPE_FILE, g_object_get_data (G_OBJECT (visible_child), "file"), NULL);
}
else
{
g_print ("TODO");
}
}
static void
present_value (GtkStack *dest_stack,
const GValue *value)
{
GtkWidget *child;
if (G_VALUE_HOLDS (value, G_TYPE_FILE))
{
GFile *file;
gtk_stack_set_visible_child_name (dest_stack, "File");
child = gtk_stack_get_visible_child (dest_stack);
file = g_value_get_object (value);
g_object_set (child, "label", g_file_peek_path (file), NULL);
}
else if (G_VALUE_HOLDS (value, GDK_TYPE_RGBA))
{
GdkRGBA *color;
gtk_stack_set_visible_child_name (dest_stack, "Color");
child = gtk_widget_get_first_child (gtk_stack_get_visible_child (dest_stack));
color = g_value_get_boxed (value);
g_object_set (child, "rgba", color, NULL);
}
else if (G_VALUE_HOLDS (value, GDK_TYPE_TEXTURE) ||
G_VALUE_HOLDS (value, GDK_TYPE_PAINTABLE))
{
GdkPaintable *paintable;
gtk_stack_set_visible_child_name (dest_stack, "Image");
child = gtk_stack_get_visible_child (dest_stack);
paintable = g_value_get_object (value);
g_object_set (child, "paintable", paintable, NULL);
}
else if (G_VALUE_HOLDS (value, G_TYPE_STRING))
{
gtk_stack_set_visible_child_name (dest_stack, "Text");
child = gtk_stack_get_visible_child (dest_stack);
gtk_label_set_label (GTK_LABEL (child), g_value_get_string (value));
}
/* Set clipboard text */
gdk_clipboard_set_text (clipboard, gtk_editable_get_text (GTK_EDITABLE (entry)));
}
static void
@@ -116,259 +37,149 @@ paste_received (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GtkStack *dest_stack = user_data;
GdkClipboard *clipboard;
const GValue *value;
GtkWidget *entry;
char *text;
GError *error = NULL;
clipboard = GDK_CLIPBOARD (source_object);
entry = GTK_WIDGET (user_data);
value = gdk_clipboard_read_value_finish (clipboard, result, &error);
if (value)
/* Get the resulting text of the read operation */
text = gdk_clipboard_read_text_finish (clipboard, result, &error);
if (text)
{
present_value (dest_stack, value);
/* Set the entry text */
gtk_editable_set_text (GTK_EDITABLE (entry), text);
g_free (text);
}
else
{
g_print ("%s\n", error->message);
GtkWidget *dialog;
/* Show an error about why pasting failed.
* Usually you probably want to ignore such failures,
* but for demonstration purposes, we show the error.
*/
dialog = gtk_message_dialog_new (GTK_WINDOW (window),
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
"Could not paste text: %s",
error->message);
g_signal_connect (dialog, "response",
G_CALLBACK (gtk_window_destroy), NULL);
gtk_widget_show (dialog);
g_error_free (error);
}
}
static void
paste_button_clicked (GtkStack *dest_stack,
gpointer user_data)
paste_button_clicked (GtkWidget *button,
gpointer user_data)
{
GtkWidget *entry;
GdkClipboard *clipboard;
GdkContentFormats *formats;
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (dest_stack));
formats = gdk_clipboard_get_formats (clipboard);
entry = GTK_WIDGET (user_data);
if (gdk_content_formats_contain_gtype (formats, GDK_TYPE_TEXTURE))
gdk_clipboard_read_value_async (clipboard, GDK_TYPE_TEXTURE, 0, NULL, paste_received, dest_stack);
else if (gdk_content_formats_contain_gtype (formats, GDK_TYPE_PAINTABLE))
gdk_clipboard_read_value_async (clipboard, GDK_TYPE_PAINTABLE, 0, NULL, paste_received, dest_stack);
else if (gdk_content_formats_contain_gtype (formats, GDK_TYPE_RGBA))
gdk_clipboard_read_value_async (clipboard, GDK_TYPE_RGBA, 0, NULL, paste_received, dest_stack);
else if (gdk_content_formats_contain_gtype (formats, G_TYPE_FILE))
gdk_clipboard_read_value_async (clipboard, G_TYPE_FILE, 0, NULL, paste_received, dest_stack);
else if (gdk_content_formats_contain_gtype (formats, G_TYPE_STRING))
gdk_clipboard_read_value_async (clipboard, G_TYPE_STRING, 0, NULL, paste_received, dest_stack);
}
/* Get the clipboard object */
clipboard = gtk_widget_get_clipboard (entry);
static void
update_copy_button_sensitivity (GtkWidget *source_stack)
{
GtkButton *copy_button;
const char *visible_child_name;
GtkWidget *visible_child;
gboolean sensitive;
copy_button = GTK_BUTTON (g_object_get_data (G_OBJECT (source_stack), "copy-button"));
visible_child = gtk_stack_get_visible_child (GTK_STACK (source_stack));
visible_child_name = gtk_stack_get_visible_child_name (GTK_STACK (source_stack));
if (strcmp (visible_child_name, "Text") == 0)
{
sensitive = strlen (gtk_editable_get_text (GTK_EDITABLE (visible_child))) > 0;
}
else if (strcmp (visible_child_name, "Color") == 0 ||
strcmp (visible_child_name, "Image") == 0)
{
sensitive = TRUE;
}
else if (strcmp (visible_child_name, "File") == 0)
{
sensitive = g_object_get_data (G_OBJECT (visible_child), "file") != NULL;
}
else
{
sensitive = FALSE;
}
gtk_widget_set_sensitive (GTK_WIDGET (copy_button), sensitive);
}
static void
source_changed_cb (GtkButton *copy_button,
GParamSpec *pspec,
GtkWidget *source_stack)
{
update_copy_button_sensitivity (source_stack);
}
static void
text_changed_cb (GtkButton *copy_button,
GParamSpec *pspec,
GtkWidget *entry)
{
update_copy_button_sensitivity (gtk_widget_get_ancestor (entry, GTK_TYPE_STACK));
}
static void
file_button_set_file (GtkButton *button,
GFile *file)
{
gtk_label_set_label (GTK_LABEL (gtk_button_get_child (button)), g_file_peek_path (file));
g_object_set_data_full (G_OBJECT (button), "file", g_object_ref (file), g_object_unref);
}
static void
file_chooser_response (GtkNativeDialog *dialog,
int response,
GtkButton *button)
{
gtk_native_dialog_hide (dialog);
if (response == GTK_RESPONSE_ACCEPT)
{
GFile *file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
file_button_set_file (button, file);
g_object_unref (file);
update_copy_button_sensitivity (gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_STACK));
}
gtk_native_dialog_destroy (dialog);
}
static void
open_file_cb (GtkWidget *button)
{
GtkFileChooserNative *chooser;
chooser = gtk_file_chooser_native_new ("Choose a file",
GTK_WINDOW (gtk_widget_get_ancestor (button, GTK_TYPE_WINDOW)),
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Open",
"_Cancel");
g_signal_connect (chooser, "response", G_CALLBACK (file_chooser_response), button);
gtk_native_dialog_show (GTK_NATIVE_DIALOG (chooser));
}
static void
update_paste_button_sensitivity (GdkClipboard *clipboard,
GtkWidget *paste_button)
{
GdkContentFormats *formats;
gboolean sensitive = FALSE;
formats = gdk_clipboard_get_formats (clipboard);
if (gdk_content_formats_contain_gtype (formats, G_TYPE_FILE) ||
gdk_content_formats_contain_gtype (formats, GDK_TYPE_RGBA) ||
gdk_content_formats_contain_gtype (formats, GDK_TYPE_TEXTURE) ||
gdk_content_formats_contain_gtype (formats, GDK_TYPE_PAINTABLE) ||
gdk_content_formats_contain_gtype (formats, G_TYPE_STRING))
sensitive = TRUE;
gtk_widget_set_sensitive (paste_button, sensitive);
}
static void
unset_clipboard_handler (gpointer data)
{
GdkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET (data));
g_signal_handlers_disconnect_by_func (clipboard, update_paste_button_sensitivity, data);
}
static gboolean
on_drop (GtkStack *dest_stack,
const GValue *value,
double x,
double y,
gpointer data)
{
present_value (dest_stack, value);
return TRUE;
}
static GdkContentProvider *
drag_prepare (GtkDragSource *drag_source,
double x,
double y,
gpointer data)
{
GtkWidget *button;
GValue value = G_VALUE_INIT;
button = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (drag_source));
if (GTK_IS_TOGGLE_BUTTON (button))
{
GtkWidget *image = gtk_widget_get_first_child (button);
GdkPaintable *paintable = gtk_image_get_paintable (GTK_IMAGE (image));
if (GDK_IS_TEXTURE (paintable))
{
g_value_init (&value, GDK_TYPE_TEXTURE);
g_value_set_object (&value, paintable);
}
else
{
g_value_init (&value, GDK_TYPE_PAINTABLE);
g_value_set_object (&value, paintable);
}
}
else
{
GFile *file = g_object_get_data (G_OBJECT (button), "file");
if (file)
{
g_value_init (&value, G_TYPE_FILE);
g_value_set_object (&value, file);
}
else
return NULL;
}
return gdk_content_provider_new_for_value (&value);
/* Request the contents of the clipboard, contents_received will be
called when we do get the contents.
*/
gdk_clipboard_read_text_async (clipboard, NULL, paste_received, entry);
}
GtkWidget *
do_clipboard (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkBuilderScope *scope;
GtkBuilder *builder;
GtkWidget *button;
GtkWidget *vbox, *hbox;
GtkWidget *label;
GtkWidget *entry, *button;
GtkWidget *image;
scope = gtk_builder_cscope_new ();
gtk_builder_cscope_add_callback_symbols (GTK_BUILDER_CSCOPE (scope),
"copy_button_clicked", G_CALLBACK (copy_button_clicked),
"paste_button_clicked", G_CALLBACK (paste_button_clicked),
"source_changed_cb", G_CALLBACK (source_changed_cb),
"text_changed_cb", G_CALLBACK (text_changed_cb),
"open_file_cb", G_CALLBACK (open_file_cb),
"on_drop", G_CALLBACK (on_drop),
"drag_prepare", G_CALLBACK (drag_prepare),
NULL);
builder = gtk_builder_new ();
gtk_builder_set_scope (builder, scope);
gtk_builder_add_from_resource (builder, "/clipboard/clipboard.ui", NULL);
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
window = gtk_window_new ();
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "Clipboard");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
gtk_window_set_display (GTK_WINDOW (window), gtk_widget_get_display (do_widget));
button = GTK_WIDGET (gtk_builder_get_object (builder, "copy_button"));
g_object_set_data (gtk_builder_get_object (builder, "source_stack"), "copy-button", button);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_margin_start (vbox, 8);
gtk_widget_set_margin_end (vbox, 8);
gtk_widget_set_margin_top (vbox, 8);
gtk_widget_set_margin_bottom (vbox, 8);
button = GTK_WIDGET (gtk_builder_get_object (builder, "paste_button"));
g_signal_connect (gtk_widget_get_clipboard (button), "changed",
G_CALLBACK (update_paste_button_sensitivity), button);
g_object_set_data_full (G_OBJECT (button), "clipboard-handler", button, unset_clipboard_handler);
gtk_window_set_child (GTK_WINDOW (window), vbox);
g_object_unref (builder);
g_object_unref (scope);
label = gtk_label_new ("\"Copy\" will copy the text\nin the entry to the clipboard");
gtk_box_append (GTK_BOX (vbox), label);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
gtk_widget_set_margin_start (hbox, 8);
gtk_widget_set_margin_end (hbox, 8);
gtk_widget_set_margin_top (hbox, 8);
gtk_widget_set_margin_bottom (hbox, 8);
gtk_box_append (GTK_BOX (vbox), hbox);
/* Create the first entry */
entry = gtk_entry_new ();
gtk_box_append (GTK_BOX (hbox), entry);
/* Create the button */
button = gtk_button_new_with_mnemonic (_("_Copy"));
gtk_box_append (GTK_BOX (hbox), button);
g_signal_connect (button, "clicked",
G_CALLBACK (copy_button_clicked), entry);
label = gtk_label_new ("\"Paste\" will paste the text from the clipboard to the entry");
gtk_box_append (GTK_BOX (vbox), label);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
gtk_widget_set_margin_start (hbox, 8);
gtk_widget_set_margin_end (hbox, 8);
gtk_widget_set_margin_top (hbox, 8);
gtk_widget_set_margin_bottom (hbox, 8);
gtk_box_append (GTK_BOX (vbox), hbox);
/* Create the second entry */
entry = gtk_entry_new ();
gtk_box_append (GTK_BOX (hbox), entry);
/* Create the button */
button = gtk_button_new_with_mnemonic (_("_Paste"));
gtk_box_append (GTK_BOX (hbox), button);
g_signal_connect (button, "clicked",
G_CALLBACK (paste_button_clicked), entry);
label = gtk_label_new ("Images can be transferred via the clipboard, too");
gtk_box_append (GTK_BOX (vbox), label);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
gtk_widget_set_margin_start (hbox, 8);
gtk_widget_set_margin_end (hbox, 8);
gtk_widget_set_margin_top (hbox, 8);
gtk_widget_set_margin_bottom (hbox, 8);
gtk_box_append (GTK_BOX (vbox), hbox);
/* Create the first image */
image = demo_image_new ("dialog-warning");
gtk_box_append (GTK_BOX (hbox), image);
/* Create the second image */
image = demo_image_new ("process-stop");
gtk_box_append (GTK_BOX (hbox), image);
/* Create the third image */
image = demo_image_new ("weather-clear");
gtk_box_append (GTK_BOX (hbox), image);
}
if (!gtk_widget_get_visible (window))
-288
View File
@@ -1,288 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkWindow" id="window">
<property name="resizable">1</property>
<property name="title">Clipboard</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel">
<property name="label">“Copy” will copy the selected data the clipboard, “Paste” will show the current clipboard contents. You can also drag the data to the bottom.</property>
<property name="wrap">1</property>
<property name="max-width-chars">40</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="spacing">12</property>
<child>
<object class="GtkDropDown" id="source_chooser">
<property name="valign">center</property>
<property name="model">
<object class="GtkStringList">
<items>
<item>Text</item>
<item>Color</item>
<item>Image</item>
<item>File</item>
</items>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStack" id="source_stack">
<signal name="notify::visible-child" handler="source_changed_cb" object="copy_button"/>
<property name="vexpand">1</property>
<binding name="visible-child-name">
<lookup name="string" type="GtkStringObject">
<lookup name="selected-item">
source_chooser
</lookup>
</lookup>
</binding>
<child>
<object class="GtkStackPage">
<property name="name">Text</property>
<property name="child">
<object class="GtkEntry" id="source_text">
<property name="valign">center</property>
<signal name="notify::text" handler="text_changed_cb" object="copy_button"/>
<property name="text">Copy this!</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">Color</property>
<property name="child">
<object class="GtkColorButton" id="source_color">
<property name="valign">center</property>
<property name="rgba">purple</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">Image</property>
<property name="child">
<object class="GtkBox">
<property name="valign">center</property>
<style>
<class name="linked"/>
</style>
<child>
<object class="GtkToggleButton" id="image_rose">
<property name="active">1</property>
<child>
<object class="GtkDragSource">
<signal name="prepare" handler="drag_prepare"/>
</object>
</child>
<child>
<object class="GtkImage">
<style>
<class name="large-icons"/>
</style>
<property name="paintable">resource:///transparent/portland-rose.jpg</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkToggleButton" id="image_floppy">
<property name="group">image_rose</property>
<child>
<object class="GtkDragSource">
<signal name="prepare" handler="drag_prepare"/>
</object>
</child>
<child>
<object class="GtkImage">
<style>
<class name="large-icons"/>
</style>
<property name="paintable">resource:///images/floppybuddy.gif</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkToggleButton" id="image_logo">
<property name="group">image_floppy</property>
<child>
<object class="GtkDragSource">
<signal name="prepare" handler="drag_prepare"/>
</object>
</child>
<child>
<object class="GtkImage">
<style>
<class name="large-icons"/>
</style>
<property name="paintable">resource:///images/org.gtk.Demo4.svg</property>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">File</property>
<property name="child">
<object class="GtkButton" id="source_file">
<child>
<object class="GtkDragSource">
<property name="propagation-phase">capture</property>
<signal name="prepare" handler="drag_prepare"/>
</object>
</child>
<property name="valign">center</property>
<property name="child">
<object class="GtkLabel">
<property name="label">—</property>
<property name="xalign">0</property>
<property name="ellipsize">start</property>
</object>
</property>
<signal name="clicked" handler="open_file_cb"/>
</object>
</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="copy_button">
<property name="valign">center</property>
<property name="label" translatable="yes">_Copy</property>
<signal name="clicked" handler="copy_button_clicked" object="source_stack"/>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkSeparator">
</object>
</child>
<child>
<object class="GtkBox">
<property name="spacing">12</property>
<child>
<object class="GtkDropTarget">
<property name="actions">copy</property>
<property name="formats">GdkTexture GdkPaintable GFile GdkRGBA gchararray</property>
<signal name="drop" handler="on_drop" object="dest_stack"/>
</object>
</child>
<child>
<object class="GtkButton" id="paste_button">
<property name="label" translatable="yes">_Paste</property>
<signal name="clicked" handler="paste_button_clicked" object="dest_stack"/>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="visible-child-name" type="GtkStack">
dest_stack
</lookup>
</binding>
</object>
</child>
<child>
<object class="GtkStack" id="dest_stack">
<property name="halign">end</property>
<property name="valign">center</property>
<child>
<object class="GtkStackPage">
<property name="name"></property>
<property name="child">
<object class="GtkLabel">
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">Text</property>
<property name="child">
<object class="GtkLabel">
<property name="halign">end</property>
<property name="valign">center</property>
<property name="xalign">0</property>
<property name="ellipsize">end</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">Image</property>
<property name="child">
<object class="GtkImage">
<property name="halign">end</property>
<property name="valign">center</property>
<style>
<class name="large-icons"/>
</style>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">Color</property>
<property name="child">
<object class="GtkBox">
<property name="halign">end</property>
<property name="valign">center</property>
<child>
<object class="GtkColorSwatch">
<property name="accessible-role">img</property>
<property name="can-focus">0</property>
<property name="selectable">0</property>
<property name="has-menu">0</property>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">File</property>
<property name="child">
<object class="GtkLabel">
<property name="halign">end</property>
<property name="valign">center</property>
<property name="xalign">0</property>
<property name="hexpand">1</property>
<property name="ellipsize">start</property>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
+2 -3
View File
@@ -24,6 +24,7 @@ do_cursors (GtkWidget *do_widget)
builder = gtk_builder_new_from_resource ("/cursors/cursors.ui");
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
g_signal_connect (window, "destroy",
@@ -34,9 +35,7 @@ do_cursors (GtkWidget *do_widget)
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
{
gtk_window_destroy (GTK_WINDOW (window));
}
gtk_window_destroy (GTK_WINDOW (window));
return window;
}
File diff suppressed because it is too large Load Diff
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define CURVE_TYPE_EDITOR (curve_editor_get_type ())
G_DECLARE_FINAL_TYPE (CurveEditor, curve_editor, CURVE, EDITOR, GtkWidget)
GtkWidget * curve_editor_new (void);
void curve_editor_set_edit (CurveEditor *self,
gboolean edit);
void curve_editor_set_path (CurveEditor *self,
GskPath *path);
GskPath * curve_editor_get_path (CurveEditor *self);
void curve_editor_set_stroke (CurveEditor *self,
GskStroke *stroke);
const GskStroke * curve_editor_get_stroke (CurveEditor *self);
void curve_editor_set_color (CurveEditor *self,
GdkRGBA *color);
const GdkRGBA * curve_editor_get_color (CurveEditor *self);
gboolean curve_editor_get_show_outline (CurveEditor *self);
void curve_editor_set_show_outline (CurveEditor *self,
gboolean show_outline);
G_END_DECLS
+272
View File
@@ -0,0 +1,272 @@
/* Path/Curve Editor
*
* This demo shows an elaborate curve editor that you would expect to find
* in a vector graphics editor. It is built on top of GTK's path APIs.
*/
#include <gtk/gtk.h>
#include "curve-editor.h"
static GskPath *
make_circle_path (void)
{
float w = 310;
float h = 310;
float cx = w / 2;
float cy = h / 2;
float pad = 20;
float r = (w - 2 * pad) / 2;
float k = 0.55228;
float kr = k * r;
GskPathBuilder *builder;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, cx, pad);
gsk_path_builder_curve_to (builder, cx + kr, pad,
w - pad, cy - kr,
w - pad, cy);
gsk_path_builder_curve_to (builder, w - pad, cy + kr,
cx + kr, h - pad,
cx, h - pad);
gsk_path_builder_curve_to (builder, cx - kr, h - pad,
pad, cy + kr,
pad, cy);
gsk_path_builder_curve_to (builder, pad, cy - kr,
cx - kr, pad,
cx, pad);
gsk_path_builder_close (builder);
return gsk_path_builder_free_to_path (builder);
}
static void
edit_changed (GtkToggleButton *button,
GParamSpec *pspec,
CurveEditor *editor)
{
curve_editor_set_edit (editor, gtk_toggle_button_get_active (button));
}
static void
reset (GtkButton *button,
CurveEditor *editor)
{
GskPath *path;
path = make_circle_path ();
curve_editor_set_path (editor, path);
gsk_path_unref (path);
}
static void
line_width_changed (GtkSpinButton *spin,
CurveEditor *editor)
{
GskStroke *stroke;
stroke = gsk_stroke_copy (curve_editor_get_stroke (editor));
gsk_stroke_set_line_width (stroke, gtk_spin_button_get_value (spin));
curve_editor_set_stroke (editor, stroke);
gsk_stroke_free (stroke);
}
static void
cap_changed (GtkDropDown *combo,
GParamSpec *pspec,
CurveEditor *editor)
{
GskStroke *stroke;
stroke = gsk_stroke_copy (curve_editor_get_stroke (editor));
gsk_stroke_set_line_cap (stroke, (GskLineCap)gtk_drop_down_get_selected (combo));
curve_editor_set_stroke (editor, stroke);
gsk_stroke_free (stroke);
}
static void
join_changed (GtkDropDown *combo,
GParamSpec *pspec,
CurveEditor *editor)
{
GskStroke *stroke;
stroke = gsk_stroke_copy (curve_editor_get_stroke (editor));
gsk_stroke_set_line_join (stroke, (GskLineJoin)gtk_drop_down_get_selected (combo));
curve_editor_set_stroke (editor, stroke);
gsk_stroke_free (stroke);
}
static void
color_changed (GtkColorChooser *chooser,
GParamSpec *pspec,
CurveEditor *editor)
{
GdkRGBA color;
gtk_color_chooser_get_rgba (chooser, &color);
curve_editor_set_color (editor, &color);
}
static void
stroke_toggled (GtkCheckButton *button,
CurveEditor *editor)
{
curve_editor_set_show_outline (editor, gtk_check_button_get_active (button));
gtk_widget_queue_draw (GTK_WIDGET (editor));
}
static void
limit_changed (GtkSpinButton *spin,
CurveEditor *editor)
{
GskStroke *stroke;
stroke = gsk_stroke_copy (curve_editor_get_stroke (editor));
gsk_stroke_set_miter_limit (stroke, gtk_spin_button_get_value (spin));
curve_editor_set_stroke (editor, stroke);
gsk_stroke_free (stroke);
}
static void
dashes_changed (GtkEntry *entry,
GParamSpec *spec,
CurveEditor *editor)
{
const char *text;
char **split;
GArray *dash;
GskStroke *stroke;
text = gtk_editable_get_text (GTK_EDITABLE (entry));
split = g_strsplit (text, " ", 0);
dash = g_array_new (FALSE, FALSE, sizeof (float));
for (int i = 0; split[i] != NULL; i++)
{
double d;
char *endp = 0;
d = g_strtod (split[i], &endp);
if (*endp == '\0')
g_array_append_vals (dash, (float[1]) { d }, 1);
}
g_strfreev (split);
stroke = gsk_stroke_copy (curve_editor_get_stroke (editor));
gsk_stroke_set_dash (stroke, (const float *)dash->data, dash->len);
curve_editor_set_stroke (editor, stroke);
gsk_stroke_free (stroke);
g_array_free (dash, TRUE);
}
GtkWidget *
do_curve (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
GtkWidget *demo;
GtkWidget *edit_toggle;
GtkWidget *reset_button;
GtkWidget *titlebar;
GtkWidget *stroke_toggle;
GtkWidget *line_width_spin;
GtkWidget *stroke_button;
GtkWidget *popover;
GtkWidget *grid;
GtkWidget *cap_combo;
GtkWidget *join_combo;
GtkWidget *color_button;
GtkWidget *limit_spin;
GtkWidget *dash_entry;
if (!window)
{
window = gtk_window_new ();
gtk_window_set_title (GTK_WINDOW (window), "Curve Editor");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
gtk_window_set_default_size (GTK_WINDOW (window), 310, 350);
edit_toggle = gtk_toggle_button_new ();
gtk_button_set_icon_name (GTK_BUTTON (edit_toggle), "document-edit-symbolic");
reset_button = gtk_button_new_from_icon_name ("edit-undo-symbolic");
stroke_button = gtk_menu_button_new ();
gtk_menu_button_set_icon_name (GTK_MENU_BUTTON (stroke_button), "open-menu-symbolic");
popover = gtk_popover_new ();
gtk_menu_button_set_popover (GTK_MENU_BUTTON (stroke_button), popover);
grid = gtk_grid_new ();
gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
gtk_popover_set_child (GTK_POPOVER (popover), grid);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Color:"), 0, 0, 1, 1);
color_button = gtk_color_button_new_with_rgba (&(GdkRGBA){ 0., 0., 0., 1.});
gtk_grid_attach (GTK_GRID (grid), color_button, 1, 0, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Line width:"), 0, 1, 1, 1);
line_width_spin = gtk_spin_button_new_with_range (1, 20, 1);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (line_width_spin), 1);
gtk_grid_attach (GTK_GRID (grid), line_width_spin, 1, 1, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Line cap:"), 0, 2, 1, 1);
cap_combo = gtk_drop_down_new_from_strings ((const char *[]){"Butt", "Round", "Square", NULL});
gtk_grid_attach (GTK_GRID (grid), cap_combo, 1, 2, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Line join:"), 0, 3, 1, 1);
join_combo = gtk_drop_down_new_from_strings ((const char *[]){"Miter", "Miter-clip", "Round", "Bevel", "Arcs", NULL});
gtk_grid_attach (GTK_GRID (grid), join_combo, 1, 3, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Miter limit:"), 0, 4, 1, 1);
limit_spin = gtk_spin_button_new_with_range (0, 10, 1);
gtk_spin_button_set_digits (GTK_SPIN_BUTTON (limit_spin), 1);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (limit_spin), 4);
gtk_grid_attach (GTK_GRID (grid), limit_spin, 1, 4, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Dashes:"), 0, 5, 1, 1);
dash_entry = gtk_entry_new ();
gtk_grid_attach (GTK_GRID (grid), dash_entry, 1, 5, 1, 1);
stroke_toggle = gtk_check_button_new_with_label ("Show outline");
gtk_grid_attach (GTK_GRID (grid), stroke_toggle, 1, 6, 1, 1);
titlebar = gtk_header_bar_new ();
gtk_header_bar_pack_start (GTK_HEADER_BAR (titlebar), edit_toggle);
gtk_header_bar_pack_start (GTK_HEADER_BAR (titlebar), reset_button);
gtk_header_bar_pack_start (GTK_HEADER_BAR (titlebar), stroke_button);
gtk_window_set_titlebar (GTK_WINDOW (window), titlebar);
demo = curve_editor_new ();
g_signal_connect (stroke_toggle, "toggled", G_CALLBACK (stroke_toggled), demo);
g_signal_connect (edit_toggle, "notify::active", G_CALLBACK (edit_changed), demo);
g_signal_connect (reset_button, "clicked", G_CALLBACK (reset), demo);
g_signal_connect (cap_combo, "notify::selected", G_CALLBACK (cap_changed), demo);
g_signal_connect (join_combo, "notify::selected", G_CALLBACK (join_changed), demo);
g_signal_connect (color_button, "notify::rgba", G_CALLBACK (color_changed), demo);
g_signal_connect (line_width_spin, "value-changed", G_CALLBACK (line_width_changed), demo);
g_signal_connect (limit_spin, "value-changed", G_CALLBACK (limit_changed), demo);
g_signal_connect (dash_entry, "notify::text", G_CALLBACK (dashes_changed), demo);
reset (NULL, CURVE_EDITOR (demo));
gtk_spin_button_set_value (GTK_SPIN_BUTTON (line_width_spin), 6);
gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (color_button), &(GdkRGBA) { 1, 0, 0, 1 });
gtk_drop_down_set_selected (GTK_DROP_DOWN (cap_combo), GSK_LINE_CAP_ROUND);
gtk_editable_set_text (GTK_EDITABLE (dash_entry), "0 8");
gtk_window_set_child (GTK_WINDOW (window), demo);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}
+12 -1
View File
@@ -15,7 +15,6 @@
<file>demo.ui</file>
</gresource>
<gresource prefix="/clipboard">
<file>clipboard.ui</file>
<file>demoimage.c</file>
<file>demoimage.h</file>
</gresource>
@@ -250,6 +249,10 @@
<gresource prefix="/video-player">
<file>bbb.png</file>
</gresource>
<gresource prefix="/curve">
<file>curve-editor.c</file>
<file>curve-editor.h</file>
</gresource>
<gresource prefix="/sources">
<file>application_demo.c</file>
<file>assistant.c</file>
@@ -267,6 +270,7 @@
<file>css_pixbufs.c</file>
<file>css_shadows.c</file>
<file>cursors.c</file>
<file>curve.c</file>
<file>dialog.c</file>
<file>drawingarea.c</file>
<file>dropdown.c</file>
@@ -287,6 +291,7 @@
<file>gears.c</file>
<file>gestures.c</file>
<file>glarea.c</file>
<file>glyphs.c</file>
<file>gltransition.c</file>
<file>headerbar.c</file>
<file>hypertext.c</file>
@@ -325,6 +330,9 @@
<file>paintable_symbolic.c</file>
<file>panes.c</file>
<file>password_entry.c</file>
<file>path_fill.c</file>
<file>path_maze.c</file>
<file>path_text.c</file>
<file>peg_solitaire.c</file>
<file>pickers.c</file>
<file>printing.c</file>
@@ -409,6 +417,9 @@
<gresource prefix="/fontrendering">
<file>fontrendering.ui</file>
</gresource>
<gresource prefix="/path_text">
<file>path_text.ui</file>
</gresource>
<gresource prefix="/org/gtk/Demo4">
<file>icons/16x16/actions/application-exit.png</file>
<file>icons/16x16/actions/document-new.png</file>
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -21,7 +21,7 @@
<item>
<attribute name="label" translatable="yes">Save _As...</attribute>
<attribute name="action">app.save-as</attribute>
<attribute name="accel">&lt;Control&gt;&lt;Shift&gt;s</attribute>
<attribute name="accel">&lt;Control&gt;s</attribute>
</item>
</section>
<section>
+8 -2
View File
@@ -17,6 +17,7 @@ demos = files([
'css_pixbufs.c',
'css_shadows.c',
'cursors.c',
'curve.c',
'dialog.c',
'drawingarea.c',
'dnd.c',
@@ -34,6 +35,7 @@ demos = files([
'gestures.c',
'glarea.c',
'gltransition.c',
'glyphs.c',
'headerbar.c',
'hypertext.c',
'iconscroll.c',
@@ -70,6 +72,9 @@ demos = files([
'paintable_symbolic.c',
'panes.c',
'password_entry.c',
'path_fill.c',
'path_maze.c',
'path_text.c',
'peg_solitaire.c',
'pickers.c',
'printing.c',
@@ -128,6 +133,7 @@ extra_demo_sources = files([
'script-names.c',
'unicode-names.c',
'suggestionentry.c',
'curve-editor.c',
])
if harfbuzz_dep.found() and pangoft_dep.found()
@@ -242,7 +248,7 @@ executable('gtk4-demo',
c_args: gtkdemo_args + demo_cflags,
dependencies: gtkdemo_deps,
include_directories: confinc,
win_subsystem: 'windows',
gui_app: true,
link_args: extra_demo_ldflags,
install: true,
)
@@ -252,7 +258,7 @@ executable('gtk4-demo-application',
c_args: gtkdemo_args + common_cflags,
dependencies: gtkdemo_deps,
include_directories: confinc,
win_subsystem: 'windows',
gui_app: true,
link_args: extra_demo_ldflags,
install: true,
)
+348
View File
@@ -0,0 +1,348 @@
/* Path/Text Fill
*
* This demo shows how to use PangoCairo to draw text with more than
* just a single color.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "paintable.h"
#include "gsk/gskpathdashprivate.h"
#define GTK_TYPE_PATH_PAINTABLE (gtk_path_paintable_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathPaintable, gtk_path_paintable, GTK, PATH_PAINTABLE, GObject)
struct _GtkPathPaintable
{
GObject parent_instance;
int width;
int height;
GskPath *path;
GdkPaintable *background;
};
struct _GtkPathPaintableClass
{
GObjectClass parent_class;
};
static int
gtk_path_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
if (self->background)
return MAX (gdk_paintable_get_intrinsic_width (self->background), self->width);
else
return self->width;
}
static int
gtk_path_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
if (self->background)
return MAX (gdk_paintable_get_intrinsic_height (self->background), self->height);
else
return self->height;
}
static void
gtk_path_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
#if 0
gtk_snapshot_push_fill (snapshot, self->path, GSK_FILL_RULE_WINDING);
#else
GskStroke *stroke = gsk_stroke_new (2.0);
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
gsk_stroke_free (stroke);
#endif
if (self->background)
{
gdk_paintable_snapshot (self->background, snapshot, width, height);
}
else
{
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, 0, width, height),
&GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (width, height),
(GskColorStop[8]) {
{ 0.0, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.2, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.3, { 1.0, 1.0, 0.0, 1.0 } },
{ 0.4, { 0.0, 1.0, 0.0, 1.0 } },
{ 0.6, { 0.0, 1.0, 1.0, 1.0 } },
{ 0.7, { 0.0, 0.0, 1.0, 1.0 } },
{ 0.8, { 1.0, 0.0, 1.0, 1.0 } },
{ 1.0, { 1.0, 0.0, 1.0, 1.0 } }
},
8);
}
gtk_snapshot_pop (snapshot);
}
static GdkPaintableFlags
gtk_path_paintable_get_flags (GdkPaintable *paintable)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
if (self->background)
return gdk_paintable_get_flags (self->background);
else
return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE;
}
static void
gtk_path_paintable_paintable_init (GdkPaintableInterface *iface)
{
iface->get_intrinsic_width = gtk_path_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gtk_path_paintable_get_intrinsic_height;
iface->snapshot = gtk_path_paintable_snapshot;
iface->get_flags = gtk_path_paintable_get_flags;
}
/* When defining the GType, we need to implement the GdkPaintable interface */
G_DEFINE_TYPE_WITH_CODE (GtkPathPaintable, gtk_path_paintable, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gtk_path_paintable_paintable_init))
/* Here's the boilerplate for the GObject declaration.
* We don't need to do anything special here, because we keep no
* data of our own.
*/
static void
gtk_path_paintable_class_init (GtkPathPaintableClass *klass)
{
}
static void
gtk_path_paintable_init (GtkPathPaintable *self)
{
}
/* And finally, we add a simple constructor.
* It is declared in the header so that the other examples
* can use it.
*/
GdkPaintable *
gtk_path_paintable_new (GskPath *path,
GdkPaintable *background,
int width,
int height)
{
GtkPathPaintable *self;
self = g_object_new (GTK_TYPE_PATH_PAINTABLE, NULL);
self->path = path;
self->background = background;
if (self->background)
{
g_signal_connect_swapped (self->background, "invalidate-contents", G_CALLBACK (gdk_paintable_invalidate_contents), self);
g_signal_connect_swapped (self->background, "invalidate-size", G_CALLBACK (gdk_paintable_invalidate_size), self);
}
self->width = width;
self->height = height;
return GDK_PAINTABLE (self);
}
void
gtk_path_paintable_set_path (GtkPathPaintable *self,
GskPath *path)
{
g_clear_pointer (&self->path, gsk_path_unref);
self->path = gsk_path_ref (path);
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
}
static GskPath *
create_hexagon (GtkWidget *widget)
{
GskPathBuilder *builder;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, 120, 0);
gsk_path_builder_line_to (builder, 360, 0);
gsk_path_builder_line_to (builder, 480, 208);
gsk_path_builder_line_to (builder, 360, 416);
gsk_path_builder_line_to (builder, 120, 416);
gsk_path_builder_line_to (builder, 0, 208);
gsk_path_builder_close (builder);
return gsk_path_builder_free_to_path (builder);
}
static GskPath *
create_path_from_text (GtkWidget *widget)
{
PangoLayout *layout;
PangoFontDescription *desc;
GskPathBuilder *builder;
layout = gtk_widget_create_pango_layout (widget, "Pango power!\nPango power!\nPango power!");
desc = pango_font_description_from_string ("sans bold 36");
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
builder = gsk_path_builder_new ();
gsk_path_builder_add_layout (builder, layout);
return gsk_path_builder_free_to_path (builder);
}
static gboolean
build_path (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
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_CURVE:
gsk_path_builder_curve_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
break;
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
static gboolean
update_path (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer measure)
{
float progress = gdk_frame_clock_get_frame_time (frame_clock) % (60 * G_USEC_PER_SEC) / (float) (30 * G_USEC_PER_SEC);
GskPathBuilder *builder;
GskPath *path;
graphene_point_t pos;
graphene_vec2_t tangent;
GskStroke *stroke;
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder,
measure,
#if 1
0.0, gsk_path_measure_get_length (measure));
#else
progress > 1 ? (progress - 1) * gsk_path_measure_get_length (measure) : 0.0,
(progress < 1 ? progress : 1.0) * gsk_path_measure_get_length (measure));
#endif
path = gsk_path_builder_free_to_path (builder);
stroke = gsk_stroke_new (1);
gsk_stroke_set_dash (stroke, (float[2]) { 10, 5 }, 2);
gsk_stroke_set_dash_offset (stroke, - (gdk_frame_clock_get_frame_time (frame_clock) % G_USEC_PER_SEC) * 15. / G_USEC_PER_SEC);
builder = gsk_path_builder_new ();
gsk_path_dash (path, stroke, 0.2, build_path, builder);
gsk_path_unref (path);
gsk_path_measure_get_point (measure,
(progress > 1 ? (progress - 1) : progress) * gsk_path_measure_get_length (measure),
&pos,
&tangent);
gsk_path_builder_move_to (builder, pos.x + 5 * graphene_vec2_get_x (&tangent), pos.y + 5 * graphene_vec2_get_y (&tangent));
gsk_path_builder_line_to (builder, pos.x + 3 * graphene_vec2_get_y (&tangent), pos.y + 3 * graphene_vec2_get_x (&tangent));
gsk_path_builder_line_to (builder, pos.x - 3 * graphene_vec2_get_y (&tangent), pos.y - 3 * graphene_vec2_get_x (&tangent));
gsk_path_builder_close (builder);
path = gsk_path_builder_free_to_path (builder);
gtk_path_paintable_set_path (GTK_PATH_PAINTABLE (gtk_picture_get_paintable (GTK_PICTURE (widget))),
path);
gsk_path_unref (path);
return G_SOURCE_CONTINUE;
}
GtkWidget *
do_path_fill (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *picture;
GdkPaintable *paintable;
GtkMediaStream *stream;
GskPath *path;
graphene_rect_t bounds;
GskPathMeasure *measure;
window = gtk_window_new ();
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_window_set_title (GTK_WINDOW (window), "Path Fill");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
#if 0
stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm");
#else
stream = gtk_nuclear_media_stream_new ();
#endif
gtk_media_stream_play (stream);
gtk_media_stream_set_loop (stream, TRUE);
path = create_hexagon (window);
path = create_path_from_text (window);
gsk_path_get_bounds (path, &bounds);
paintable = gtk_path_paintable_new (path,
GDK_PAINTABLE (stream),
bounds.origin.x + bounds.size.width,
bounds.origin.y + bounds.size.height);
picture = gtk_picture_new_for_paintable (paintable);
measure = gsk_path_measure_new (path);
gtk_widget_add_tick_callback (picture, update_path, measure, (GDestroyNotify) gsk_path_measure_unref);
gtk_picture_set_keep_aspect_ratio (GTK_PICTURE (picture), FALSE);
gtk_picture_set_can_shrink (GTK_PICTURE (picture), FALSE);
g_object_unref (paintable);
gtk_window_set_child (GTK_WINDOW (window), picture);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}
+339
View File
@@ -0,0 +1,339 @@
/* Path/Maze
* #Keywords: game, mouse
*
* This demo shows how to use a GskPath to create a maze and use
* gsk_path_measure_get_closest_point() to check the mouse stays
* on the path.
*
* It also shows off the performance of GskPath (or not) as this
* is a rather complex path.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "paintable.h"
#define MAZE_GRID_SIZE 20
#define MAZE_STROKE_SIZE_ACTIVE (MAZE_GRID_SIZE - 4)
#define MAZE_STROKE_SIZE_INACTIVE (MAZE_GRID_SIZE - 12)
#define MAZE_WIDTH 31
#define MAZE_HEIGHT 21
#define GTK_TYPE_MAZE (gtk_maze_get_type ())
G_DECLARE_FINAL_TYPE (GtkMaze, gtk_maze, GTK, MAZE, GtkWidget)
struct _GtkMaze
{
GtkWidget parent_instance;
int width;
int height;
GskPath *path;
GskPathMeasure *measure;
GdkPaintable *background;
gboolean active;
};
struct _GtkMazeClass
{
GtkWidgetClass parent_class;
};
G_DEFINE_TYPE (GtkMaze, gtk_maze, GTK_TYPE_WIDGET)
static void
gtk_maze_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkMaze *self = GTK_MAZE (widget);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
*minimum = *natural = self->width;
else
*minimum = *natural = self->height;
}
static void
gtk_maze_snapshot (GtkWidget *widget,
GdkSnapshot *snapshot)
{
GtkMaze *self = GTK_MAZE (widget);
double width = gtk_widget_get_width (widget);
double height = gtk_widget_get_height (widget);
GskStroke *stroke;
stroke = gsk_stroke_new (MAZE_STROKE_SIZE_INACTIVE);
if (self->active)
gsk_stroke_set_line_width (stroke, MAZE_STROKE_SIZE_ACTIVE);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_ROUND);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_ROUND);
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
gsk_stroke_free (stroke);
if (self->background)
{
gdk_paintable_snapshot (self->background, snapshot, width, height);
}
else
{
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, 0, width, height),
&GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (width, height),
(GskColorStop[8]) {
{ 0.0, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.2, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.3, { 1.0, 1.0, 0.0, 1.0 } },
{ 0.4, { 0.0, 1.0, 0.0, 1.0 } },
{ 0.6, { 0.0, 1.0, 1.0, 1.0 } },
{ 0.7, { 0.0, 0.0, 1.0, 1.0 } },
{ 0.8, { 1.0, 0.0, 1.0, 1.0 } },
{ 1.0, { 1.0, 0.0, 1.0, 1.0 } }
},
8);
}
gtk_snapshot_pop (snapshot);
}
static void
gtk_maze_dispose (GObject *object)
{
GtkMaze *self = GTK_MAZE (object);
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
if (self->background)
{
g_signal_handlers_disconnect_matched (self->background, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
g_clear_object (&self->background);
}
G_OBJECT_CLASS (gtk_maze_parent_class)->dispose (object);
}
static void
gtk_maze_class_init (GtkMazeClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_maze_dispose;
widget_class->measure = gtk_maze_measure;
widget_class->snapshot = gtk_maze_snapshot;
}
static void
pointer_motion (GtkEventControllerMotion *controller,
double x,
double y,
GtkMaze *self)
{
if (!self->active)
return;
if (gsk_path_measure_get_closest_point (self->measure, &GRAPHENE_POINT_INIT (x, y), NULL) <= MAZE_STROKE_SIZE_ACTIVE / 2.0f)
return;
self->active = FALSE;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
pointer_leave (GtkEventControllerMotion *controller,
GtkMaze *self)
{
if (!self->active)
{
self->active = TRUE;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
}
static void
gtk_maze_init (GtkMaze *self)
{
GtkEventController *controller;
controller = GTK_EVENT_CONTROLLER (gtk_event_controller_motion_new ());
g_signal_connect (controller, "motion", G_CALLBACK (pointer_motion), self);
g_signal_connect (controller, "leave", G_CALLBACK (pointer_leave), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
self->active = TRUE;
}
static void
gtk_maze_set_path (GtkMaze *self,
GskPath *path)
{
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
self->path = gsk_path_ref (path);
self->measure = gsk_path_measure_new (path);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
GtkWidget *
gtk_maze_new (GskPath *path,
GdkPaintable *background,
int width,
int height)
{
GtkMaze *self;
self = g_object_new (GTK_TYPE_MAZE, NULL);
gtk_maze_set_path (self, path);
gsk_path_unref (path);
self->background = background;
if (self->background)
{
g_signal_connect_swapped (self->background, "invalidate-contents", G_CALLBACK (gtk_widget_queue_draw), self);
g_signal_connect_swapped (self->background, "invalidate-size", G_CALLBACK (gtk_widget_queue_resize), self);
}
self->width = width;
self->height = height;
return GTK_WIDGET (self);
}
static void
add_point_to_maze (GtkBitset *maze,
GskPathBuilder *builder,
guint x,
guint y)
{
gboolean set[4] = { };
guint dir;
gtk_bitset_add (maze, y * MAZE_WIDTH + x);
while (TRUE)
{
set[0] = set[0] || x == 0 || gtk_bitset_contains (maze, y * MAZE_WIDTH + x - 1);
set[1] = set[1] || y == 0 || gtk_bitset_contains (maze, (y - 1) * MAZE_WIDTH + x);
set[2] = set[2] || x + 1 == MAZE_WIDTH || gtk_bitset_contains (maze, y * MAZE_WIDTH + x + 1);
set[3] = set[3] || y + 1 == MAZE_HEIGHT || gtk_bitset_contains (maze, (y + 1) * MAZE_WIDTH + x);
if (set[0] && set[1] && set[2] && set[3])
return;
do
{
dir = g_random_int_range (0, 4);
}
while (set[dir]);
switch (dir)
{
case 0:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x - 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x - 1, y);
break;
case 1:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y - 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x, y - 1);
break;
case 2:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 1.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x + 1, y);
break;
case 3:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 1.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x, y + 1);
break;
default:
g_assert_not_reached ();
break;
}
}
}
static GskPath *
create_path_for_maze (GtkWidget *widget)
{
GskPathBuilder *builder;
GtkBitset *maze;
builder = gsk_path_builder_new ();
maze = gtk_bitset_new_empty ();
/* make sure the outer lines are unreachable:
* Set the full range, then remove the center again. */
gtk_bitset_add_range (maze, 0, MAZE_WIDTH * MAZE_HEIGHT);
gtk_bitset_remove_rectangle (maze, MAZE_WIDTH + 1, MAZE_WIDTH - 2, MAZE_HEIGHT - 2, MAZE_WIDTH);
/* Fill the maze */
add_point_to_maze (maze, builder, MAZE_WIDTH / 2, MAZE_HEIGHT / 2);
/* Add start and stop lines */
gsk_path_builder_move_to (builder, 1.5 * MAZE_GRID_SIZE, -0.5 * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, 1.5 * MAZE_GRID_SIZE, 1.5 * MAZE_GRID_SIZE);
gsk_path_builder_move_to (builder, (MAZE_WIDTH - 1.5) * MAZE_GRID_SIZE, (MAZE_HEIGHT - 1.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (MAZE_WIDTH - 1.5) * MAZE_GRID_SIZE, (MAZE_HEIGHT + 0.5) * MAZE_GRID_SIZE);
gtk_bitset_unref (maze);
return gsk_path_builder_free_to_path (builder);
}
GtkWidget *
do_path_maze (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *maze;
GtkMediaStream *stream;
GskPath *path;
window = gtk_window_new ();
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_window_set_title (GTK_WINDOW (window), "Follow the maze with the mouse");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
#if 0
stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm");
#else
stream = gtk_nuclear_media_stream_new ();
#endif
gtk_media_stream_play (stream);
gtk_media_stream_set_loop (stream, TRUE);
path = create_path_for_maze (window);
maze = gtk_maze_new (path,
GDK_PAINTABLE (stream),
MAZE_WIDTH * MAZE_GRID_SIZE,
MAZE_HEIGHT * MAZE_GRID_SIZE);
gtk_window_set_child (GTK_WINDOW (window), maze);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}
+594
View File
@@ -0,0 +1,594 @@
/* Path/Curved Text
*
* This demo shows how to use GskPath to animate a path along another path.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#define GTK_TYPE_PATH_WIDGET (gtk_path_widget_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathWidget, gtk_path_widget, GTK, PATH_WIDGET, GtkWidget)
#define POINT_SIZE 8
enum {
PROP_0,
PROP_TEXT,
PROP_EDITABLE,
N_PROPS
};
struct _GtkPathWidget
{
GtkWidget parent_instance;
char *text;
gboolean editable;
graphene_point_t points[4];
guint active_point;
float line_closest;
GskPath *line_path;
GskPathMeasure *line_measure;
GskPath *text_path;
GdkPaintable *background;
};
struct _GtkPathWidgetClass
{
GtkWidgetClass parent_class;
};
static GParamSpec *properties[N_PROPS] = { NULL, };
G_DEFINE_TYPE (GtkPathWidget, gtk_path_widget, GTK_TYPE_WIDGET)
static GskPath *
create_path_from_text (GtkWidget *widget,
const char *text)
{
cairo_surface_t *surface;
cairo_t *cr;
cairo_path_t *path;
PangoLayout *layout;
PangoFontDescription *desc;
GskPath *result;
surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
cr = cairo_create (surface);
layout = gtk_widget_create_pango_layout (widget, text);
desc = pango_font_description_from_string ("sans bold 36");
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
cairo_move_to (cr, 0, - pango_layout_get_baseline (layout) / (double) PANGO_SCALE);
pango_cairo_layout_path (cr, layout);
path = cairo_copy_path_flat (cr);
result = gsk_path_new_from_cairo (path);
cairo_path_destroy (path);
g_object_unref (layout);
cairo_destroy (cr);
cairo_surface_destroy (surface);
return result;
}
typedef struct
{
GskPathMeasure *measure;
GskPathBuilder *builder;
double scale;
} GtkPathTransform;
static void
gtk_path_transform_point (GskPathMeasure *measure,
const graphene_point_t *pt,
float scale,
graphene_point_t *res)
{
graphene_vec2_t tangent;
gsk_path_measure_get_point (measure, pt->x * scale, res, &tangent);
res->x -= pt->y * scale * graphene_vec2_get_y (&tangent);
res->y += pt->y * scale * graphene_vec2_get_x (&tangent);
}
static gboolean
gtk_path_transform_op (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GtkPathTransform *transform = data;
switch (op)
{
case GSK_PATH_MOVE:
{
graphene_point_t res;
gtk_path_transform_point (transform->measure, &pts[0], transform->scale, &res);
gsk_path_builder_move_to (transform->builder, res.x, res.y);
}
break;
case GSK_PATH_LINE:
{
graphene_point_t res;
gtk_path_transform_point (transform->measure, &pts[1], transform->scale, &res);
gsk_path_builder_line_to (transform->builder, res.x, res.y);
}
break;
case GSK_PATH_CURVE:
{
graphene_point_t res[3];
gtk_path_transform_point (transform->measure, &pts[1], transform->scale, &res[0]);
gtk_path_transform_point (transform->measure, &pts[2], transform->scale, &res[1]);
gtk_path_transform_point (transform->measure, &pts[3], transform->scale, &res[2]);
gsk_path_builder_curve_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y, res[2].x, res[2].y);
}
break;
case GSK_PATH_CONIC:
{
graphene_point_t res[2];
gtk_path_transform_point (transform->measure, &pts[1], transform->scale, &res[0]);
gtk_path_transform_point (transform->measure, &pts[2], transform->scale, &res[1]);
gsk_path_builder_conic_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y, weight);
}
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (transform->builder);
break;
default:
g_assert_not_reached();
return FALSE;
}
return TRUE;
}
static GskPath *
gtk_path_transform (GskPathMeasure *measure,
GskPath *path)
{
GtkPathTransform transform = { measure, gsk_path_builder_new () };
graphene_rect_t bounds;
gsk_path_get_bounds (path, &bounds);
if (bounds.origin.x + bounds.size.width > 0)
transform.scale = gsk_path_measure_get_length (measure) / (bounds.origin.x + bounds.size.width);
else
transform.scale = 1.0f;
gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_CURVE, gtk_path_transform_op, &transform);
return gsk_path_builder_free_to_path (transform.builder);
}
static void
gtk_path_widget_clear_text_path (GtkPathWidget *self)
{
g_clear_pointer (&self->text_path, gsk_path_unref);
}
static void
gtk_path_widget_clear_paths (GtkPathWidget *self)
{
gtk_path_widget_clear_text_path (self);
g_clear_pointer (&self->line_path, gsk_path_unref);
g_clear_pointer (&self->line_measure, gsk_path_measure_unref);
}
static void
gtk_path_widget_create_text_path (GtkPathWidget *self)
{
GskPath *path;
gtk_path_widget_clear_text_path (self);
if (self->line_measure == NULL)
return;
path = create_path_from_text (GTK_WIDGET (self), self->text);
self->text_path = gtk_path_transform (self->line_measure, path);
gsk_path_unref (path);
}
static void
gtk_path_widget_create_paths (GtkPathWidget *self)
{
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
GskPathBuilder *builder;
gtk_path_widget_clear_paths (self);
if (width <= 0 || height <= 0)
return;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder,
self->points[0].x * width, self->points[0].y * height);
gsk_path_builder_curve_to (builder,
self->points[1].x * width, self->points[1].y * height,
self->points[2].x * width, self->points[2].y * height,
self->points[3].x * width, self->points[3].y * height);
self->line_path = gsk_path_builder_free_to_path (builder);
self->line_measure = gsk_path_measure_new (self->line_path);
gtk_path_widget_create_text_path (self);
}
static void
gtk_path_widget_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkPathWidget *self = GTK_PATH_WIDGET (widget);
GTK_WIDGET_CLASS (gtk_path_widget_parent_class)->size_allocate (widget, width, height, baseline);
gtk_path_widget_create_paths (self);
}
static void
gtk_path_widget_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkPathWidget *self = GTK_PATH_WIDGET (widget);
double width = gtk_widget_get_width (widget);
double height = gtk_widget_get_height (widget);
GskPath *path;
GskStroke *stroke;
gsize i;
/* frosted glass the background */
gtk_snapshot_push_blur (snapshot, 100);
gdk_paintable_snapshot (self->background, snapshot, width, height);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 1, 1, 0.5 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
/* draw the text */
if (self->text_path)
{
gtk_snapshot_push_fill (snapshot, self->text_path, GSK_FILL_RULE_WINDING);
gdk_paintable_snapshot (self->background, snapshot, width, height);
/* ... with an emboss effect */
stroke = gsk_stroke_new (2.0);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT(1, 1));
gtk_snapshot_push_stroke (snapshot, self->text_path, stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 0.2 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gsk_stroke_free (stroke);
gtk_snapshot_pop (snapshot);
gtk_snapshot_pop (snapshot);
}
if (self->editable && self->line_path)
{
/* draw the control line */
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, self->line_path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
}
if (self->line_closest >= 0)
{
GskPathBuilder *builder;
graphene_point_t closest;
builder = gsk_path_builder_new ();
gsk_path_measure_get_point (self->line_measure, self->line_closest, &closest, NULL);
gsk_path_builder_add_circle (builder, &closest, POINT_SIZE);
path = gsk_path_builder_free_to_path (builder);
gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 1, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
}
if (self->editable && self->line_path)
{
GskPathBuilder *builder;
/* draw the points */
builder = gsk_path_builder_new ();
for (i = 0; i < 4; i++)
{
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (self->points[i].x * width, self->points[i].y * height), POINT_SIZE);
}
path = gsk_path_builder_free_to_path (builder);
gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 1, 1, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
}
}
static void
gtk_path_widget_set_text (GtkPathWidget *self,
const char *text)
{
if (g_strcmp0 (self->text, text) == 0)
return;
g_free (self->text);
self->text = g_strdup (text);
gtk_path_widget_create_paths (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEXT]);
}
static void
gtk_path_widget_set_editable (GtkPathWidget *self,
gboolean editable)
{
if (self->editable == editable)
return;
self->editable = editable;
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EDITABLE]);
}
static void
gtk_path_widget_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
switch (prop_id)
{
case PROP_TEXT:
gtk_path_widget_set_text (self, g_value_get_string (value));
break;
case PROP_EDITABLE:
gtk_path_widget_set_editable (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_widget_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
switch (prop_id)
{
case PROP_TEXT:
g_value_set_string (value, self->text);
break;
case PROP_EDITABLE:
g_value_set_boolean (value, self->editable);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_widget_dispose (GObject *object)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
gtk_path_widget_clear_paths (self);
G_OBJECT_CLASS (gtk_path_widget_parent_class)->dispose (object);
}
static void
gtk_path_widget_class_init (GtkPathWidgetClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_path_widget_dispose;
object_class->set_property = gtk_path_widget_set_property;
object_class->get_property = gtk_path_widget_get_property;
widget_class->size_allocate = gtk_path_widget_allocate;
widget_class->snapshot = gtk_path_widget_snapshot;
properties[PROP_TEXT] =
g_param_spec_string ("text",
"text",
"Text transformed along a path",
NULL,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
properties[PROP_EDITABLE] =
g_param_spec_boolean ("editable",
"editable",
"If the path can be edited by the user",
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
drag_begin (GtkGestureDrag *gesture,
double x,
double y,
GtkPathWidget *self)
{
graphene_point_t mouse = GRAPHENE_POINT_INIT (x, y);
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
gsize i;
for (i = 0; i < 4; i++)
{
if (graphene_point_distance (&GRAPHENE_POINT_INIT (self->points[i].x * width, self->points[i].y * height), &mouse, NULL, NULL) <= POINT_SIZE)
{
self->active_point = i;
break;
}
}
if (i == 4)
{
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
return;
}
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
drag_update (GtkGestureDrag *drag,
double offset_x,
double offset_y,
GtkPathWidget *self)
{
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
double start_x, start_y;
gtk_gesture_drag_get_start_point (drag, &start_x, &start_y);
self->points[self->active_point] = GRAPHENE_POINT_INIT ((start_x + offset_x) / width,
(start_y + offset_y) / height);
self->points[self->active_point].x = CLAMP (self->points[self->active_point].x, 0, 1);
self->points[self->active_point].y = CLAMP (self->points[self->active_point].y, 0, 1);
gtk_path_widget_create_paths (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
pointer_motion (GtkEventControllerMotion *controller,
double x,
double y,
GtkPathWidget *self)
{
gsk_path_measure_get_closest_point_full (self->line_measure,
&GRAPHENE_POINT_INIT (x, y),
INFINITY,
NULL, NULL,
&self->line_closest,
NULL);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
pointer_leave (GtkEventControllerMotion *controller,
GtkPathWidget *self)
{
self->line_closest = -1;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
gtk_path_widget_init (GtkPathWidget *self)
{
GtkEventController *controller;
controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
g_signal_connect (controller, "drag-begin", G_CALLBACK (drag_begin), self);
g_signal_connect (controller, "drag-update", G_CALLBACK (drag_update), self);
g_signal_connect (controller, "drag-end", G_CALLBACK (drag_update), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
controller = GTK_EVENT_CONTROLLER (gtk_event_controller_motion_new ());
g_signal_connect (controller, "enter", G_CALLBACK (pointer_motion), self);
g_signal_connect (controller, "motion", G_CALLBACK (pointer_motion), self);
g_signal_connect (controller, "leave", G_CALLBACK (pointer_leave), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
self->line_closest = -1;
self->points[0] = GRAPHENE_POINT_INIT (0.1, 0.9);
self->points[1] = GRAPHENE_POINT_INIT (0.3, 0.1);
self->points[2] = GRAPHENE_POINT_INIT (0.7, 0.1);
self->points[3] = GRAPHENE_POINT_INIT (0.9, 0.9);
self->background = GDK_PAINTABLE (gdk_texture_new_from_resource ("/sliding_puzzle/portland-rose.jpg"));
gtk_path_widget_set_text (self, "It's almost working");
}
GtkWidget *
gtk_path_widget_new (void)
{
GtkPathWidget *self;
self = g_object_new (GTK_TYPE_PATH_WIDGET, NULL);
return GTK_WIDGET (self);
}
GtkWidget *
do_path_text (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkBuilder *builder;
g_type_ensure (GTK_TYPE_PATH_WIDGET);
builder = gtk_builder_new_from_resource ("/path_text/path_text.ui");
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
g_object_unref (builder);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}
+38
View File
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkWindow" id="window">
<property name="title" translatable="yes">Text along a Path</property>
<child type="titlebar">
<object class="GtkHeaderBar">
<child type="end">
<object class="GtkToggleButton" id="edit-toggle">
<property name="icon-name">document-edit-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkRevealer">
<property name="reveal-child" bind-source="edit-toggle" bind-property="active" bind-flags="sync-create"></property>
<child>
<object class="GtkEntry" id="text">
<property name="text">Through the looking glass</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkPathWidget" id="view">
<property name="editable" bind-source="edit-toggle" bind-property="active" bind-flags="sync-create"></property>
<property name="text" bind-source="text" bind-property="text" bind-flags="sync-create"></property>
<property name="hexpand">true</property>
<property name="vexpand">true</property>
</object>
</child>
</object>
</child>
</object>
</interface>
+1 -1
View File
@@ -16,7 +16,7 @@ executable('gtk4-icon-browser',
c_args: common_cflags,
dependencies: [ libgtk_dep, demo_conf_h ],
include_directories: confinc,
win_subsystem: 'windows',
gui_app: true,
link_args: extra_demo_ldflags,
install: true,
)
+1 -1
View File
@@ -3,7 +3,7 @@ demo_profile = get_option('profile')
demo_conf_h = declare_dependency(
sources: custom_target('demo-header',
command: [gen_demo_header, meson.project_source_root(), demo_profile],
command: [gen_demo_header, meson.source_root(), demo_profile],
capture: true,
output: 'demo_conf.h',
build_by_default: true,
+1 -1
View File
@@ -17,7 +17,7 @@ executable('gtk4-node-editor',
c_args: [
'-DNODE_EDITOR_SOURCE_DIR="@0@/../../testsuite/gsk/compare/"'.format(meson.current_source_dir())
] + common_cflags,
win_subsystem: 'windows',
gui_app: true,
link_args: extra_demo_ldflags,
install: false,
)
+1 -1
View File
@@ -3,7 +3,7 @@ executable('gtk4-print-editor',
c_args: common_cflags,
dependencies: [ libgtk_dep, demo_conf_h ],
include_directories: confinc,
win_subsystem: 'windows',
gui_app: true,
link_args: extra_demo_ldflags,
install: true,
)
+1 -1
View File
@@ -74,7 +74,7 @@ executable('gtk4-widget-factory',
c_args: common_cflags,
dependencies: [ libgtk_dep, demo_conf_h ],
include_directories: confinc,
win_subsystem: 'windows',
gui_app: true,
link_args: extra_demo_ldflags,
install: true,
)
-6
View File
@@ -1927,12 +1927,6 @@ microphone-sensitivity-medium-symbolic</property>
<property name="tooltip-text" translatable="1">Insert something</property>
</object>
</child>
<child>
<object class="GtkColorButton">
<property name="rgba">#9141AC</property>
<property name="tooltip-text" translatable="1">Select a color</property>
</object>
</child>
</object>
</child>
<child>
-9
View File
@@ -1276,15 +1276,6 @@ is provided in the form of a `GtkIconPaintable` (this can be checked with
[method@Gtk.IconPaintable.is_symbolic]), you have to call
[method@Gtk.IconPaintable.get_icon_name] and set the icon name on a `GtkImage`.
### Adapt to GtkImage changes
`GtkPicture`'s behaviour was "split out" of `GtkImage` as the latter was covering
too many use cases; if you're loading an icon, [class@Gtk.Image] in GTK3 and GTK4 are
perfectly equivalent. If you are loading a more complex image asset, like a picture
or a thumbnail, then [class@Gtk.Picture] is the appropriate widget.
One noteworthy distinction is that while `GtkImage` has its size computed by
GTK, `GtkPicture` lets you decide about the size.
### Update to GtkFileChooser API changes
`GtkFileChooser` moved to a GFile-based API. If you need to convert a path
+21 -2
View File
@@ -49,7 +49,7 @@
typedef struct _Deserializer Deserializer;
struct _Deserializer
struct _Deserializer
{
const char * mime_type; /* interned */
GType type;
@@ -264,7 +264,7 @@ gdk_content_deserializer_get_priority (GdkContentDeserializer *deserializer)
*
* This is the `GCancellable` that was passed to [func@Gdk.content_deserialize_async].
*
* Returns: (transfer none) (nullable): the cancellable for the current operation
* Returns: (transfer none): the cancellable for the current operation
*/
GCancellable *
gdk_content_deserializer_get_cancellable (GdkContentDeserializer *deserializer)
@@ -934,6 +934,25 @@ init (void)
formats = gdk_pixbuf_get_formats ();
/* Make sure png comes first */
for (f = formats; f; f = f->next)
{
GdkPixbufFormat *fmt = f->data;
char *name;
name = gdk_pixbuf_format_get_name (fmt);
if (g_str_equal (name, "png"))
{
formats = g_slist_delete_link (formats, f);
formats = g_slist_prepend (formats, fmt);
g_free (name);
break;
}
g_free (name);
}
for (f = formats; f; f = f->next)
{
GdkPixbufFormat *fmt = f->data;
+12 -26
View File
@@ -718,33 +718,19 @@ gdk_content_formats_builder_to_formats (GdkContentFormatsBuilder *builder)
g_return_val_if_fail (builder != NULL, NULL);
if (builder->n_gtypes > 0)
{
gtypes = g_new (GType, builder->n_gtypes + 1);
i = builder->n_gtypes;
gtypes[i--] = G_TYPE_INVALID;
/* add backwards because most important type is last in the list */
for (l = builder->gtypes; l; l = l->next)
gtypes[i--] = GPOINTER_TO_SIZE (l->data);
}
else
{
gtypes = NULL;
}
gtypes = g_new (GType, builder->n_gtypes + 1);
i = builder->n_gtypes;
gtypes[i--] = G_TYPE_INVALID;
/* add backwards because most important type is last in the list */
for (l = builder->gtypes; l; l = l->next)
gtypes[i--] = GPOINTER_TO_SIZE (l->data);
if (builder->n_mime_types > 0)
{
mime_types = g_new (const char *, builder->n_mime_types + 1);
i = builder->n_mime_types;
mime_types[i--] = NULL;
/* add backwards because most important type is last in the list */
for (l = builder->mime_types; l; l = l->next)
mime_types[i--] = l->data;
}
else
{
mime_types = NULL;
}
mime_types = g_new (const char *, builder->n_mime_types + 1);
i = builder->n_mime_types;
mime_types[i--] = NULL;
/* add backwards because most important type is last in the list */
for (l = builder->mime_types; l; l = l->next)
mime_types[i--] = l->data;
result = gdk_content_formats_new_take (gtypes, builder->n_gtypes,
mime_types, builder->n_mime_types);
+1 -1
View File
@@ -342,7 +342,7 @@ gdk_content_provider_write_mime_type_finish (GdkContentProvider *provider,
/**
* gdk_content_provider_get_value:
* @provider: a `GdkContentProvider`
* @value: (out caller-allocates): the `GValue` to fill
* @value: the `GValue` to fill
* @error: a `GError` location to store the error occurring
*
* Gets the contents of @provider stored in @value.
+26 -7
View File
@@ -54,7 +54,7 @@
typedef struct _Serializer Serializer;
struct _Serializer
struct _Serializer
{
const char * mime_type; /* interned */
GType type;
@@ -270,7 +270,7 @@ gdk_content_serializer_get_priority (GdkContentSerializer *serializer)
*
* This is the `GCancellable` that was passed to [func@content_serialize_async].
*
* Returns: (transfer none) (nullable): the cancellable for the current operation
* Returns: (transfer none): the cancellable for the current operation
*/
GCancellable *
gdk_content_serializer_get_cancellable (GdkContentSerializer *serializer)
@@ -446,7 +446,7 @@ lookup_serializer (const char *mime_type,
serializer->type == type)
return serializer;
}
return NULL;
}
@@ -630,7 +630,7 @@ pixbuf_serializer (GdkContentSerializer *serializer)
const GValue *value;
GdkPixbuf *pixbuf;
const char *name;
name = gdk_content_serializer_get_user_data (serializer);
value = gdk_content_serializer_get_value (serializer);
@@ -651,7 +651,7 @@ pixbuf_serializer (GdkContentSerializer *serializer)
gdk_pixbuf_save_to_stream_async (pixbuf,
gdk_content_serializer_get_output_stream (serializer),
name,
gdk_content_serializer_get_cancellable (serializer),
gdk_content_serializer_get_cancellable (serializer),
pixbuf_serializer_finish,
serializer,
g_str_equal (name, "png") ? "compression" : NULL, "2",
@@ -823,7 +823,7 @@ file_uri_serializer (GdkContentSerializer *serializer)
else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
{
GSList *l;
for (l = g_value_get_boxed (value); l; l = l->next)
{
uri = g_file_get_uri (l->data);
@@ -867,7 +867,7 @@ file_text_serializer (GdkContentSerializer *serializer)
{
GString *str;
GSList *l;
str = g_string_new (NULL);
for (l = g_value_get_boxed (value); l; l = l->next)
@@ -966,6 +966,25 @@ init (void)
formats = gdk_pixbuf_get_formats ();
/* Make sure png comes first */
for (f = formats; f; f = f->next)
{
GdkPixbufFormat *fmt = f->data;
char *name;
name = gdk_pixbuf_format_get_name (fmt);
if (g_str_equal (name, "png"))
{
formats = g_slist_delete_link (formats, f);
formats = g_slist_prepend (formats, fmt);
g_free (name);
break;
}
g_free (name);
}
for (f = formats; f; f = f->next)
{
GdkPixbufFormat *fmt = f->data;
+2 -1
View File
@@ -47,7 +47,8 @@
* Cursors by themselves are not very interesting: they must be bound to a
* window for users to see them. This is done with [method@Gdk.Surface.set_cursor]
* or [method@Gdk.Surface.set_device_cursor]. Applications will typically
* use higher-level GTK functions such as [method@Gtk.Widget.set_cursor] instead.
* use higher-level GTK functions such as [method@Gtk.Widget.set_cursor]`
* instead.
*
* Cursors are not bound to a given [class@Gdk.Display], so they can be shared.
* However, the appearance of cursors may vary when used on different
-4
View File
@@ -132,8 +132,6 @@ gdk_device_class_init (GdkDeviceClass *klass)
* GdkDevice:source: (attributes org.gtk.Property.get=gdk_device_get_source)
*
* Source type for the device.
*
* Deprecated: 4.6: Use GdkDeviceTool:tool-type instead
*/
device_props[PROP_SOURCE] =
g_param_spec_enum ("source",
@@ -598,8 +596,6 @@ gdk_device_get_has_cursor (GdkDevice *device)
* Determines the type of the device.
*
* Returns: a `GdkInputSource`
*
* Deprecated: 4.6: Use gdk_device_tool_get_tool_type() instead
*/
GdkInputSource
gdk_device_get_source (GdkDevice *device)
+1 -1
View File
@@ -92,7 +92,7 @@ GDK_AVAILABLE_IN_ALL
GdkDisplay * gdk_device_get_display (GdkDevice *device);
GDK_AVAILABLE_IN_ALL
GdkSeat * gdk_device_get_seat (GdkDevice *device);
GDK_DEPRECATED_IN_4_6_FOR(gdk_device_tool_get_tool_type)
GDK_AVAILABLE_IN_ALL
GdkDeviceTool * gdk_device_get_device_tool (GdkDevice *device);
GDK_AVAILABLE_IN_ALL
+62 -135
View File
@@ -19,7 +19,7 @@
* Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
@@ -522,9 +522,7 @@ _gdk_event_queue_find_first (GdkDisplay *display)
if (pending_motion)
return pending_motion;
if ((event->event_type == GDK_MOTION_NOTIFY ||
(event->event_type == GDK_SCROLL && gdk_scroll_event_get_direction (event) == GDK_SCROLL_SMOOTH)) &&
(event->flags & GDK_EVENT_FLUSHED) == 0)
if (event->event_type == GDK_MOTION_NOTIFY && (event->flags & GDK_EVENT_FLUSHED) == 0)
pending_motion = tmp_list;
else
return tmp_list;
@@ -598,9 +596,6 @@ _gdk_event_unqueue (GdkDisplay *display)
/*
* If the last N events in the event queue are smooth scroll events
* for the same surface and device, combine them into one.
*
* We give the remaining event a history with N items, and deltas
* that are the sum over the history entries.
*/
void
gdk_event_queue_handle_scroll_compression (GdkDisplay *display)
@@ -610,6 +605,7 @@ gdk_event_queue_handle_scroll_compression (GdkDisplay *display)
GdkDevice *device = NULL;
GdkEvent *last_event = NULL;
GList *scrolls = NULL;
double delta_x, delta_y;
GArray *history = NULL;
GdkTimeCoord hist;
@@ -644,42 +640,35 @@ gdk_event_queue_handle_scroll_compression (GdkDisplay *display)
l = l->prev;
}
delta_x = delta_y = 0;
while (scrolls && scrolls->next != NULL)
{
GdkEvent *event = scrolls->data;
GList *next = scrolls->next;
double dx, dy;
gboolean inherited = FALSE;
if (!history && ((GdkScrollEvent *)event)->history)
{
history = ((GdkScrollEvent *)event)->history;
((GdkScrollEvent *)event)->history = NULL;
inherited = TRUE;
}
if (!history)
history = g_array_new (FALSE, TRUE, sizeof (GdkTimeCoord));
if (!inherited)
{
gdk_scroll_event_get_deltas (event, &dx, &dy);
gdk_scroll_event_get_deltas (event, &dx, &dy);
delta_x += dx;
delta_y += dy;
memset (&hist, 0, sizeof (GdkTimeCoord));
hist.time = gdk_event_get_time (event);
hist.flags = GDK_AXIS_FLAG_DELTA_X | GDK_AXIS_FLAG_DELTA_Y;
hist.axes[GDK_AXIS_DELTA_X] = dx;
hist.axes[GDK_AXIS_DELTA_Y] = dy;
memset (&hist, 0, sizeof (GdkTimeCoord));
hist.time = gdk_event_get_time (event);
hist.flags = GDK_AXIS_FLAG_DELTA_X | GDK_AXIS_FLAG_DELTA_Y;
hist.axes[GDK_AXIS_DELTA_X] = dx;
hist.axes[GDK_AXIS_DELTA_Y] = dy;
g_array_append_val (history, hist);
}
g_array_append_val (history, hist);
gdk_event_unref (event);
g_queue_delete_link (&display->queued_events, scrolls);
scrolls = next;
}
if (scrolls && history)
if (scrolls)
{
GdkEvent *old_event, *event;
double dx, dy;
@@ -687,29 +676,13 @@ gdk_event_queue_handle_scroll_compression (GdkDisplay *display)
old_event = scrolls->data;
gdk_scroll_event_get_deltas (old_event, &dx, &dy);
memset (&hist, 0, sizeof (GdkTimeCoord));
hist.time = gdk_event_get_time (old_event);
hist.flags = GDK_AXIS_FLAG_DELTA_X | GDK_AXIS_FLAG_DELTA_Y;
hist.axes[GDK_AXIS_DELTA_X] = dx;
hist.axes[GDK_AXIS_DELTA_Y] = dy;
g_array_append_val (history, hist);
dx = dy = 0;
for (int i = 0; i < history->len; i++)
{
GdkTimeCoord *val = &g_array_index (history, GdkTimeCoord, i);
dx += val->axes[GDK_AXIS_DELTA_X];
dy += val->axes[GDK_AXIS_DELTA_Y];
}
event = gdk_scroll_event_new (surface,
device,
gdk_event_get_device_tool (old_event),
gdk_event_get_time (old_event),
gdk_event_get_modifier_state (old_event),
dx,
dy,
delta_x + dx,
delta_y + dy,
gdk_scroll_event_is_stop (old_event));
((GdkScrollEvent *)event)->history = history;
@@ -741,41 +714,24 @@ gdk_motion_event_push_history (GdkEvent *event,
g_assert (GDK_IS_EVENT_TYPE (event, GDK_MOTION_NOTIFY));
g_assert (GDK_IS_EVENT_TYPE (history_event, GDK_MOTION_NOTIFY));
if (G_UNLIKELY (!self->history))
self->history = g_array_new (FALSE, TRUE, sizeof (GdkTimeCoord));
if (((GdkMotionEvent *)history_event)->history)
{
GArray *history = ((GdkMotionEvent *)history_event)->history;
g_array_append_vals (self->history, history->data, history->len);
}
if (!self->tool)
return;
tool = gdk_event_get_device_tool (history_event);
memset (&hist, 0, sizeof (GdkTimeCoord));
hist.time = gdk_event_get_time (history_event);
if (tool)
{
hist.flags = gdk_device_tool_get_axes (tool);
for (i = GDK_AXIS_X; i < GDK_AXIS_LAST; i++)
gdk_event_get_axis (history_event, i, &hist.axes[i]);
}
else
{
hist.flags = GDK_AXIS_FLAG_X | GDK_AXIS_FLAG_Y;
gdk_event_get_position (history_event, &hist.axes[GDK_AXIS_X], &hist.axes[GDK_AXIS_Y]);
}
hist.flags = gdk_device_tool_get_axes (tool);
for (i = GDK_AXIS_X; i < GDK_AXIS_LAST; i++)
gdk_event_get_axis (history_event, i, &hist.axes[i]);
if (G_UNLIKELY (!self->history))
self->history = g_array_new (FALSE, TRUE, sizeof (GdkTimeCoord));
g_array_append_val (self->history, hist);
}
/* If the last N events in the event queue are motion notify
* events for the same surface, drop all but the last.
*
* If a button is held down or the device has a tool, then
* we give the remaining events a history containing the N-1
* dropped events.
*/
void
_gdk_event_queue_handle_motion_compression (GdkDisplay *display)
{
@@ -785,6 +741,9 @@ _gdk_event_queue_handle_motion_compression (GdkDisplay *display)
GdkDevice *pending_motion_device = NULL;
GdkEvent *last_motion = NULL;
/* If the last N events in the event queue are motion notify
* events for the same surface, drop all but the last */
tmp_list = g_queue_peek_tail_link (&display->queued_events);
while (tmp_list)
@@ -821,11 +780,12 @@ _gdk_event_queue_handle_motion_compression (GdkDisplay *display)
if (last_motion != NULL)
{
if ((gdk_event_get_modifier_state (last_motion) &
(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK |
GDK_BUTTON4_MASK | GDK_BUTTON5_MASK)) ||
gdk_event_get_device_tool (last_motion) != NULL)
gdk_motion_event_push_history (last_motion, pending_motions->data);
GdkModifierType state = gdk_event_get_modifier_state (last_motion);
if (state &
(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK |
GDK_BUTTON4_MASK | GDK_BUTTON5_MASK))
gdk_motion_event_push_history (last_motion, pending_motions->data);
}
gdk_event_unref (pending_motions->data);
@@ -943,9 +903,6 @@ gdk_event_get_pointer_emulated (GdkEvent *event)
* Extract the axis value for a particular axis use from
* an event structure.
*
* To find out which axes are used, use [method@Gdk.DeviceTool.get_axes]
* on the device tool returned by [method@Gdk.Event.get_device_tool].
*
* Returns: %TRUE if the specified axis was found, otherwise %FALSE
*/
gboolean
@@ -1172,9 +1129,6 @@ G_DEFINE_BOXED_TYPE (GdkEventSequence, gdk_event_sequence,
*
* Extracts all axis values from an event.
*
* To find out which axes are used, use [method@Gdk.DeviceTool.get_axes]
* on the device tool returned by [method@Gdk.Event.get_device_tool].
*
* Returns: %TRUE on success, otherwise %FALSE
*/
gboolean
@@ -1225,7 +1179,7 @@ gdk_event_get_event_type (GdkEvent *event)
*
* Extracts the surface associated with an event.
*
* Returns: (transfer none) (nullable): The `GdkSurface` associated with the event
* Returns: (transfer none): The `GdkSurface` associated with the event
*/
GdkSurface *
gdk_event_get_surface (GdkEvent *event)
@@ -2477,14 +2431,6 @@ gdk_touchpad_event_get_state (GdkEvent *event)
return self->state;
}
static GdkEventSequence *
gdk_touchpad_event_get_sequence (GdkEvent *event)
{
GdkTouchpadEvent *self = (GdkTouchpadEvent *) event;
return self->sequence;
}
static gboolean
gdk_touchpad_event_get_position (GdkEvent *event,
double *x,
@@ -2504,7 +2450,7 @@ static const GdkEventTypeInfo gdk_touchpad_event_info = {
NULL,
gdk_touchpad_event_get_state,
gdk_touchpad_event_get_position,
gdk_touchpad_event_get_sequence,
NULL,
NULL,
NULL,
};
@@ -2515,28 +2461,19 @@ GDK_DEFINE_EVENT_TYPE (GdkTouchpadEvent, gdk_touchpad_event,
GDK_EVENT_TYPE_SLOT (GDK_TOUCHPAD_PINCH))
GdkEvent *
gdk_touchpad_event_new_swipe (GdkSurface *surface,
GdkEventSequence *sequence,
GdkDevice *device,
guint32 time,
GdkModifierType state,
GdkTouchpadGesturePhase phase,
double x,
double y,
int n_fingers,
double dx,
double dy)
gdk_touchpad_event_new_swipe (GdkSurface *surface,
GdkDevice *device,
guint32 time,
GdkModifierType state,
GdkTouchpadGesturePhase phase,
double x,
double y,
int n_fingers,
double dx,
double dy)
{
GdkTouchpadEvent *self;
GdkTouchpadEvent *self = gdk_event_alloc (GDK_TOUCHPAD_SWIPE, surface, device, time);
g_return_val_if_fail (phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN ||
phase == GDK_TOUCHPAD_GESTURE_PHASE_END ||
phase == GDK_TOUCHPAD_GESTURE_PHASE_UPDATE ||
phase == GDK_TOUCHPAD_GESTURE_PHASE_CANCEL, NULL);
self = gdk_event_alloc (GDK_TOUCHPAD_SWIPE, surface, device, time);
self->sequence = sequence;
self->state = state;
self->phase = phase;
self->x = x;
@@ -2549,30 +2486,21 @@ gdk_touchpad_event_new_swipe (GdkSurface *surface,
}
GdkEvent *
gdk_touchpad_event_new_pinch (GdkSurface *surface,
GdkEventSequence *sequence,
GdkDevice *device,
guint32 time,
GdkModifierType state,
GdkTouchpadGesturePhase phase,
double x,
double y,
int n_fingers,
double dx,
double dy,
double scale,
double angle_delta)
gdk_touchpad_event_new_pinch (GdkSurface *surface,
GdkDevice *device,
guint32 time,
GdkModifierType state,
GdkTouchpadGesturePhase phase,
double x,
double y,
int n_fingers,
double dx,
double dy,
double scale,
double angle_delta)
{
GdkTouchpadEvent *self;
GdkTouchpadEvent *self = gdk_event_alloc (GDK_TOUCHPAD_PINCH, surface, device, time);
g_return_val_if_fail (phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN ||
phase == GDK_TOUCHPAD_GESTURE_PHASE_END ||
phase == GDK_TOUCHPAD_GESTURE_PHASE_UPDATE ||
phase == GDK_TOUCHPAD_GESTURE_PHASE_CANCEL, NULL);
self = gdk_event_alloc (GDK_TOUCHPAD_PINCH, surface, device, time);
self->sequence = sequence;
self->state = state;
self->phase = phase;
self->x = x;
@@ -2979,8 +2907,7 @@ gdk_motion_event_new (GdkSurface *surface,
* to the application because they occurred in the same frame as @event.
*
* Note that only motion and scroll events record history, and motion
* events do it only if one of the mouse buttons is down, or the device
* has a tool.
* events do it only if one of the mouse buttons is down.
*
* Returns: (transfer container) (array length=out_n_coords) (nullable): an
* array of time and coordinates
+11 -14
View File
@@ -209,7 +209,7 @@ struct _GdkTouchEvent
* @pointer_emulated: whether the scroll event was the result of
* a pointer emulation
* @tool: a `GdkDeviceTool`
* @history: (element-type GdkTimeCoord): array of times and deltas
* @history: (element-type GdkScrollHistory): array of times and deltas
* for other scroll events that were compressed before delivering the
* current event
*
@@ -233,7 +233,7 @@ struct _GdkScrollEvent
gboolean pointer_emulated;
gboolean is_stop;
GdkDeviceTool *tool;
GArray *history; /* <GdkTimeCoord> */
GArray *history; /* <GdkScrollHistory> */
};
/*
@@ -402,7 +402,6 @@ struct _GdkTouchpadEvent
{
GdkEvent parent_instance;
GdkEventSequence *sequence;
GdkModifierType state;
gint8 phase;
gint8 n_fingers;
@@ -507,20 +506,18 @@ GdkEvent * gdk_touch_event_new (GdkEventType type,
double *axes,
gboolean emulating);
GdkEvent * gdk_touchpad_event_new_swipe (GdkSurface *surface,
GdkEventSequence *sequence,
GdkDevice *device,
guint32 time,
GdkModifierType state,
GdkEvent * gdk_touchpad_event_new_swipe (GdkSurface *surface,
GdkDevice *device,
guint32 time,
GdkModifierType state,
GdkTouchpadGesturePhase phase,
double x,
double y,
int n_fingers,
double dx,
double dy);
double x,
double y,
int n_fingers,
double dx,
double dy);
GdkEvent * gdk_touchpad_event_new_pinch (GdkSurface *surface,
GdkEventSequence *sequence,
GdkDevice *device,
guint32 time,
GdkModifierType state,
+11 -69
View File
@@ -151,12 +151,6 @@ unmask_context (MaskedContext *mask)
return GDK_GL_CONTEXT (GSIZE_TO_POINTER (GPOINTER_TO_SIZE (mask) & ~(gsize) 1));
}
static inline gboolean
mask_is_surfaceless (MaskedContext *mask)
{
return GPOINTER_TO_SIZE (mask) & (gsize) 1;
}
static void
unref_unmasked (gpointer data)
{
@@ -580,8 +574,8 @@ gdk_gl_context_real_begin_frame (GdkDrawContext *draw_context,
glViewport (0, 0, ww, wh);
#ifdef HAVE_EGL
if (priv->egl_context && gdk_gl_context_check_version (context, 0, 0, 3, 0))
glDrawBuffers (1, (GLenum[1]) { gdk_gl_context_get_use_es (context) ? GL_BACK : GL_BACK_LEFT });
if (priv->egl_context)
glDrawBuffers (1, (GLenum[1]) { GL_BACK_LEFT });
#endif
}
@@ -1003,33 +997,16 @@ gdk_gl_context_set_required_version (GdkGLContext *context,
}
gboolean
gdk_gl_context_check_version (GdkGLContext *self,
int required_gl_major,
int required_gl_minor,
int required_gles_major,
int required_gles_minor)
gdk_gl_context_check_version (GdkGLContext *context,
int required_major,
int required_minor)
{
GdkGLContextPrivate *priv = gdk_gl_context_get_instance_private (self);
GdkGLContextPrivate *priv = gdk_gl_context_get_instance_private (context);
g_return_val_if_fail (GDK_IS_GL_CONTEXT (self), FALSE);
g_return_val_if_fail (required_gl_minor < 10, FALSE);
g_return_val_if_fail (required_gles_minor < 10, FALSE);
g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), FALSE);
g_return_val_if_fail (required_minor < 10, FALSE);
if (!gdk_gl_context_is_realized (self))
return FALSE;
switch (priv->api)
{
case GDK_GL_API_GL:
return priv->gl_version >= required_gl_major * 10 + required_gl_minor;
case GDK_GL_API_GLES:
return priv->gl_version >= required_gles_major * 10 + required_gles_minor;
default:
g_return_val_if_reached (FALSE);
}
return priv->gl_version >= required_major * 10 + required_minor;
}
/**
@@ -1346,7 +1323,6 @@ gl_debug_message_callback (GLenum source,
const char *message_source;
const char *message_type;
const char *message_severity;
GLogLevelFlags log_level;
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION)
return;
@@ -1408,31 +1384,22 @@ gl_debug_message_callback (GLenum source,
{
case GL_DEBUG_SEVERITY_HIGH:
message_severity = "High";
log_level = G_LOG_LEVEL_CRITICAL;
break;
case GL_DEBUG_SEVERITY_MEDIUM:
message_severity = "Medium";
log_level = G_LOG_LEVEL_WARNING;
break;
case GL_DEBUG_SEVERITY_LOW:
message_severity = "Low";
log_level = G_LOG_LEVEL_MESSAGE;
break;
case GL_DEBUG_SEVERITY_NOTIFICATION:
message_severity = "Notification";
log_level = G_LOG_LEVEL_INFO;
break;
default:
message_severity = "Unknown";
log_level = G_LOG_LEVEL_MESSAGE;
}
/* There's no higher level function taking a log level argument... */
g_log_structured_standard (G_LOG_DOMAIN, log_level,
__FILE__, G_STRINGIFY (__LINE__),
G_STRFUNC,
"OPENGL:\n Source: %s\n Type: %s\n Severity: %s\n Message: %s",
message_source, message_type, message_severity, message);
g_warning ("OPENGL:\n Source: %s\n Type: %s\n Severity: %s\n Message: %s",
message_source, message_type, message_severity, message);
}
/**
@@ -1688,31 +1655,6 @@ gdk_gl_context_clear_current (void)
}
}
/*<private>
* gdk_gl_context_clear_current_if_surface:
* @surface: surface to clear for
*
* Does a gdk_gl_context_clear_current() if the current context is attached
* to @surface, leaves the current context alone otherwise.
**/
void
gdk_gl_context_clear_current_if_surface (GdkSurface *surface)
{
MaskedContext *current;
current = g_private_get (&thread_current_context);
if (current != NULL && !mask_is_surfaceless (current))
{
GdkGLContext *context = unmask_context (current);
if (gdk_gl_context_get_surface (context) != surface)
return;
if (GDK_GL_CONTEXT_GET_CLASS (context)->clear_current (context))
g_private_replace (&thread_current_context, NULL);
}
}
/**
* gdk_gl_context_get_current:
*
+2 -6
View File
@@ -99,8 +99,6 @@ gboolean gdk_gl_backend_can_be_used (GdkGLBackend
GError **error);
void gdk_gl_backend_use (GdkGLBackend backend_type);
void gdk_gl_context_clear_current_if_surface (GdkSurface *surface);
GdkGLContext * gdk_gl_context_new (GdkDisplay *display,
GdkSurface *surface);
@@ -111,10 +109,8 @@ void gdk_gl_context_set_is_legacy (GdkGLContext
gboolean is_legacy);
gboolean gdk_gl_context_check_version (GdkGLContext *context,
int required_gl_major,
int required_gl_minor,
int required_gles_major,
int required_gles_minor);
int required_major,
int required_minor);
gboolean gdk_gl_context_has_unpack_subimage (GdkGLContext *context);
void gdk_gl_context_push_debug_group (GdkGLContext *context,
+2 -5
View File
@@ -21,7 +21,6 @@
#include "gdkgltextureprivate.h"
#include "gdkdisplayprivate.h"
#include "gdkglcontextprivate.h"
#include "gdkmemoryformatprivate.h"
#include "gdkmemorytextureprivate.h"
#include "gdktextureprivate.h"
@@ -306,11 +305,9 @@ gdk_gl_texture_determine_format (GdkGLTexture *self)
GLint active_texture;
GLint internal_format;
/* Abort if somebody else is GL-ing here... */
if (self->context != gdk_gl_context_get_current () ||
/* ... or glGetTexLevelParameter() isn't supported */
!gdk_gl_context_check_version (self->context, 0, 0, 3, 1))
if (self->context != gdk_gl_context_get_current ())
{
/* Somebody else is GL-ing here, abort! */
texture->format = GDK_MEMORY_DEFAULT;
return;
}
-1
View File
@@ -2,4 +2,3 @@ BOOLEAN:BOXED
BOOLEAN:OBJECT
BOOLEAN:POINTER
VOID:POINTER,POINTER,BOOLEAN,BOOLEAN
VOID:INT,INT
+1 -1
View File
@@ -187,7 +187,7 @@ gdk_popup_get_rect_anchor (GdkPopup *popup)
*
* Returns the parent surface of a popup.
*
* Returns: (transfer none) (nullable): the parent surface
* Returns: (transfer none): the parent surface
*/
GdkSurface *
gdk_popup_get_parent (GdkPopup *popup)
+1 -6
View File
@@ -605,14 +605,11 @@ gdk_surface_class_init (GdkSurfaceClass *klass)
0,
NULL,
NULL,
_gdk_marshal_VOID__INT_INT,
NULL,
G_TYPE_NONE,
2,
G_TYPE_INT,
G_TYPE_INT);
g_signal_set_va_marshaller (signals[LAYOUT],
G_OBJECT_CLASS_TYPE (object_class),
_gdk_marshal_VOID__INT_INTv);
/**
* GdkSurface::render:
@@ -1095,7 +1092,6 @@ gdk_surface_set_egl_native_window (GdkSurface *self,
if (priv->egl_surface != NULL)
{
gdk_gl_context_clear_current_if_surface (self);
eglDestroySurface (gdk_surface_get_display (self), priv->egl_surface);
priv->egl_surface = NULL;
}
@@ -1124,7 +1120,6 @@ gdk_surface_ensure_egl_surface (GdkSurface *self,
priv->egl_surface != NULL &&
gdk_display_get_egl_config_high_depth (display) != gdk_display_get_egl_config (display))
{
gdk_gl_context_clear_current_if_surface (self);
eglDestroySurface (gdk_surface_get_display (self), priv->egl_surface);
priv->egl_surface = NULL;
}
+1 -1
View File
@@ -454,7 +454,7 @@ gdk_texture_new_from_resource (const char *resource_path)
texture = NULL;
if (texture == NULL)
g_error ("Resource path %s is not a valid image: %s", resource_path, error->message);
g_error ("Resource path %s s not a valid image: %s", resource_path, error->message);
return texture;
}
+4
View File
@@ -89,6 +89,10 @@ GDK_AVAILABLE_IN_ALL
void gdk_texture_download (GdkTexture *texture,
guchar *data,
gsize stride);
GDK_AVAILABLE_IN_4_6
void gdk_texture_download_float (GdkTexture *texture,
float *data,
gsize stride);
GDK_AVAILABLE_IN_ALL
gboolean gdk_texture_save_to_png (GdkTexture *texture,
const char *filename);
+1 -8
View File
@@ -240,7 +240,7 @@ static const FormatData format_data[] = {
[GDK_MEMORY_A8R8G8B8] = { GDK_MEMORY_R8G8B8A8, 8, 4, SAMPLEFORMAT_UINT, EXTRASAMPLE_UNASSALPHA },
[GDK_MEMORY_R8G8B8A8] = { GDK_MEMORY_R8G8B8A8, 8, 4, SAMPLEFORMAT_UINT, EXTRASAMPLE_UNASSALPHA },
[GDK_MEMORY_A8B8G8R8] = { GDK_MEMORY_R8G8B8A8, 8, 4, SAMPLEFORMAT_UINT, EXTRASAMPLE_UNASSALPHA },
[GDK_MEMORY_R8G8B8] = { GDK_MEMORY_R8G8B8, 8, 3, SAMPLEFORMAT_UINT, 0 },
[GDK_MEMORY_R8G8B8] = { GDK_MEMORY_R8G8B8, 8, 3, SAMPLEFORMAT_UINT, 0 },
[GDK_MEMORY_B8G8R8] = { GDK_MEMORY_R8G8B8, 8, 3, SAMPLEFORMAT_UINT, 0 },
[GDK_MEMORY_R16G16B16] = { GDK_MEMORY_R16G16B16, 16, 3, SAMPLEFORMAT_UINT, 0 },
[GDK_MEMORY_R16G16B16A16_PREMULTIPLIED] = { GDK_MEMORY_R16G16B16A16_PREMULTIPLIED, 16, 4, SAMPLEFORMAT_UINT, EXTRASAMPLE_ASSOCALPHA },
@@ -376,13 +376,6 @@ gdk_load_tiff (GBytes *input_bytes,
G_GNUC_UNUSED gint64 before = GDK_PROFILER_CURRENT_TIME;
tif = tiff_open_read (input_bytes);
if (!tif)
{
g_set_error_literal (error,
GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_CORRUPT_IMAGE,
_("Could not load TIFF data"));
return NULL;
}
TIFFSetDirectory (tif, 0);
-1
View File
@@ -537,7 +537,6 @@ fill_pinch_event (GdkMacosDisplay *display,
seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
return gdk_touchpad_event_new_pinch (GDK_SURFACE (surface),
NULL, /* FIXME make up sequences */
gdk_seat_get_pointer (seat),
get_time_from_ns_event (nsevent),
get_keyboard_modifiers_from_ns_event (nsevent),
+4 -13
View File
@@ -137,7 +137,6 @@ struct _GdkWaylandPointerData {
guint cursor_timeout_id;
guint cursor_image_index;
guint cursor_image_delay;
guint touchpad_event_sequence;
guint current_output_scale;
GSList *pointer_surface_outputs;
@@ -1128,7 +1127,7 @@ data_offer_source_actions (void *data,
seat->pending_source_actions = gdk_wayland_actions_to_gdk_actions (source_actions);
return;
}
if (seat->drop == NULL)
return;
@@ -1153,7 +1152,7 @@ data_offer_action (void *data,
seat->pending_action = gdk_wayland_actions_to_gdk_actions (action);
return;
}
if (seat->drop == NULL)
return;
@@ -2165,7 +2164,7 @@ deliver_key_event (GdkWaylandSeat *seat,
key,
device_get_modifiers (seat->logical_pointer),
_gdk_wayland_keymap_key_is_modifier (keymap, key),
&translated,
&translated,
&no_lock);
_gdk_wayland_display_deliver_event (seat->display, event);
@@ -2668,11 +2667,7 @@ emit_gesture_swipe_event (GdkWaylandSeat *seat,
seat->pointer_info.time = _time;
if (phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN)
seat->pointer_info.touchpad_event_sequence++;
event = gdk_touchpad_event_new_swipe (seat->pointer_info.focus,
GDK_SLOT_TO_EVENT_SEQUENCE (seat->pointer_info.touchpad_event_sequence),
seat->logical_pointer,
_time,
device_get_modifiers (seat->logical_pointer),
@@ -2768,11 +2763,7 @@ emit_gesture_pinch_event (GdkWaylandSeat *seat,
seat->pointer_info.time = _time;
if (phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN)
seat->pointer_info.touchpad_event_sequence++;
event = gdk_touchpad_event_new_pinch (seat->pointer_info.focus,
GDK_SLOT_TO_EVENT_SEQUENCE (seat->pointer_info.touchpad_event_sequence),
seat->logical_pointer,
_time,
device_get_modifiers (seat->logical_pointer),
@@ -4097,7 +4088,7 @@ tablet_pad_strip_handle_frame (void *data,
event = gdk_pad_event_new_strip (seat->keyboard_focus,
pad->device,
time,
g_list_index (pad->mode_groups, group),
g_list_index (pad->mode_groups, group),
g_list_index (pad->strips, wp_tablet_pad_strip),
group->current_mode,
group->axis_tmp_info.value);
-43
View File
@@ -1662,7 +1662,6 @@ static TranslationEntry translations[] = {
{ FALSE, "org.gnome.desktop.wm.preferences", "action-middle-click-titlebar", "gtk-titlebar-middle-click", G_TYPE_STRING, { .s = "none" } },
{ FALSE, "org.gnome.desktop.wm.preferences", "action-right-click-titlebar", "gtk-titlebar-right-click", G_TYPE_STRING, { .s = "menu" } },
{ FALSE, "org.gnome.desktop.a11y", "always-show-text-caret", "gtk-keynav-use-caret", G_TYPE_BOOLEAN, { .b = FALSE } },
{ FALSE, "org.gnome.desktop.a11y.interface", "high-contrast", "high-contast", G_TYPE_NONE, { .b = FALSE } },
/* Note, this setting doesn't exist, the portal and gsd fake it */
{ FALSE, "org.gnome.fontconfig", "serial", "gtk-fontconfig-timestamp", G_TYPE_NONE, { .i = 0 } },
};
@@ -1712,13 +1711,6 @@ find_translation_entry_by_setting (const char *setting)
return NULL;
}
static void
high_contrast_changed (GdkDisplay *display)
{
gdk_display_setting_changed (display, "gtk-theme-name");
gdk_display_setting_changed (display, "gtk-icon-theme-name");
}
static void
settings_changed (GSettings *settings,
const char *key,
@@ -1732,8 +1724,6 @@ settings_changed (GSettings *settings,
{
if (entry->type != G_TYPE_NONE)
gdk_display_setting_changed (display, entry->setting);
else if (strcmp (key, "high-contrast") == 0)
high_contrast_changed (display);
else
update_xft_settings (display);
}
@@ -2100,36 +2090,6 @@ set_decoration_layout_from_entry (GdkDisplay *display,
}
}
static void
set_theme_from_entry (GdkDisplay *display,
TranslationEntry *entry,
GValue *value)
{
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
GSettings *settings = NULL;
GSettingsSchema *schema = NULL;
gboolean hc = FALSE;
if (display_wayland->settings_portal == NULL)
{
settings = (GSettings *)g_hash_table_lookup (display_wayland->settings,
"org.gnome.desktop.a11y.interface");
}
if (settings)
g_object_get (settings, "settings-schema", &schema, NULL);
if (schema && g_settings_schema_has_key (schema, "high-contrast"))
hc = g_settings_get_boolean (settings, "high-contrast");
g_clear_pointer (&schema, g_settings_schema_unref);
if (hc)
g_value_set_static_string (value, "HighContrast");
else
set_value_from_entry (display, entry, value);
}
static gboolean
set_capability_setting (GdkDisplay *display,
GValue *value,
@@ -2161,9 +2121,6 @@ gdk_wayland_display_get_setting (GdkDisplay *display,
{
if (strcmp (name, "gtk-decoration-layout") == 0)
set_decoration_layout_from_entry (display, entry, value);
else if (strcmp (name, "gtk-theme-name") == 0 ||
strcmp (name, "gtk-icon-theme-name") == 0)
set_theme_from_entry (display, entry, value);
else
set_value_from_entry (display, entry, value);
return TRUE;
+406
View File
@@ -0,0 +1,406 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 2001 Stefan Ondrejicka
*
* 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 <string.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <stdlib.h>
#include <glib.h>
typedef struct {
char *name;
int id;
char *bitmap;
int hotx;
int hoty;
} font_info_t;
typedef struct {
char *name;
int id;
int width;
int height;
int hotx;
int hoty;
char *data;
} cursor_info_t;
static GSList *fonts = NULL;
static GSList *cursors = NULL;
static int dw,dh;
static gboolean debug = FALSE;
#define HEX(c) (((c) >= '0' && (c) <= '9') ? \
((c) - '0') : (toupper(c) - 'A' + 10))
static void print_font(fi)
font_info_t *fi;
{
int x,y;
for (y = 0; y < dh; y++)
{
for (x = 0; x < dw; x++)
{
printf(fi->bitmap[y*dw+x]? "X" : " ");
}
printf("\n");
}
}
static void print_cursor(ci)
cursor_info_t *ci;
{
int x,y;
for (y = 0; y < ci->height; y++)
{
printf("/* ");
for (x = 0; x < ci->width; x++)
{
if (ci->hotx == x && ci->hoty == y)
printf("o");
else
switch (ci->data[y*ci->width+x])
{
case 0:
printf(" ");
break;
case 1:
printf(".");
break;
case 2:
printf("X");
break;
}
}
printf(" */\n");
}
}
static int read_bdf_font(fname)
char *fname;
{
FILE *f;
char line[2048];
int rv = 0;
gboolean startchar = FALSE, startbitmap = FALSE;
char *charname,*p,*bitmap;
int dx = 0,dy = 0;
int w,h,x,y,py;
int id,tmp;
dw = 0;
dh = 0;
if (!(f = fopen(fname, "r")))
{
perror(fname);
return -1;
}
if (fgets(line, sizeof(line), f) && strncasecmp("STARTFONT ", line, 10))
{
printf("!BDF font file\n");
fclose(f);
return -1;
}
p = line;
while (fgets(line, sizeof(line), f))
{
if (!startchar)
{
if (!strncasecmp("STARTCHAR ", line, 10))
{
startchar = TRUE;
charname = g_strndup(p + 10,
strcspn(p+10, "\r\n"));
}
else if (!strncasecmp("FONTBOUNDINGBOX ", line, 16))
sscanf(p+16, "%d %d %d %d", &dw, &dh, &dx, &dy);
}
else
{
if (!strncasecmp("ENDCHAR", line, 7))
{
font_info_t *nfi;
if (debug)
printf(" %*s*/\n", dw, "");
startchar = FALSE;
startbitmap = FALSE;
nfi = g_malloc(sizeof(font_info_t));
memset(nfi, '\0', sizeof(font_info_t));
nfi->name = charname;
nfi->id = id;
nfi->bitmap = bitmap;
nfi->hotx = 0 - dx;
nfi->hoty = 0 - dy;
fonts = g_slist_append(fonts, nfi);
}
else if (startbitmap)
{
int px,cx;
guchar mask;
px = x - dx + py * dw;
for (cx = 0; cx < w; cx++)
{
mask = 1 << (3 - (cx % 4));
bitmap[px+cx] =
(mask & HEX(line[cx/4])) != 0;
if (debug)
printf(bitmap[px+cx] ? "X" : " ");
}
py++;
if (debug)
printf(" %*s*/\n/* %*s", dw-w, "", dw+dx, "");
}
else if (!strncasecmp("BBX ", line, 4))
{
sscanf(p+4, "%d %d %d %d", &w, &h, &x, &y);
if (debug)
printf("/* %s: */\n/* %*s", charname, dw+dx, "");
}
else if (!strncasecmp("ENCODING ", line, 9))
{
if (sscanf(p+9, "%d %d", &tmp, &id) != 2)
id = tmp;
}
else if (!strncasecmp("BITMAP", line, 6))
{
py = y - dy;
startbitmap = TRUE;
bitmap = g_malloc(dw*dh);
memset(bitmap, '\0', dw*dh);
}
}
}
if (strncasecmp("ENDFONT", line, 7))
rv = -1;
fclose(f);
return rv;
}
static int font_info_compare(fi, name)
font_info_t *fi;
char *name;
{
return strcmp(name, fi->name);
}
static cursor_info_t *gen_cursor(bmap, mask)
font_info_t *bmap;
font_info_t *mask;
{
cursor_info_t *ci;
int bx = dw,by = dh,ex = 0,ey = 0;
int i,j;
for (j = 0; j < dh; j++)
{
gboolean havep = FALSE;
for (i = 0; i < dw; i++)
{
if (bmap->bitmap[j*dw+i] || mask->bitmap[j*dw+i])
{
havep = TRUE;
bx = MIN(bx, i);
ex = MAX(i+1, ex);
}
}
if (havep)
{
by = MIN(by, j);
ey = MAX(ey, j+1);
}
}
ci = g_malloc(sizeof(cursor_info_t));
ci->name = g_strdup(bmap->name);
ci->id = bmap->id;
ci->width = ex - bx;
ci->height = ey - by;
ci->hotx = bmap->hotx - bx;
ci->hoty = ci->height - (bmap->hoty - by);
ci->data = g_malloc(ci->width * ci->height);
memset(ci->data, '\0', ci->width * ci->height);
for (j = 0; j < ci->height; j++)
{
for (i = 0; i < ci->width; i++)
{
int ofs = (by + j) * dw + bx + i;
ci->data[j*ci->width + i] = mask->bitmap[ofs] *
(1 + bmap->bitmap[ofs]);
}
}
return ci;
}
static void compose_cursors_from_fonts()
{
GSList *l;
for (l = g_slist_copy (fonts); l; l = g_slist_delete_link (l,l))
{
font_info_t *fi = l->data;
char *name;
GSList *ml;
name = g_strconcat(fi->name, "_mask", NULL);
if ((ml = g_slist_find_custom(fonts, name,
(GCompareFunc) font_info_compare)))
{
cursors = g_slist_append(cursors, gen_cursor(l->data, ml->data));
fonts = g_slist_remove(fonts, l->data);
fonts = g_slist_remove(fonts, ml->data);
}
g_free(name);
}
}
static char *dump_cursor(ci, id)
cursor_info_t *ci;
int id;
{
static char cdata[8192];
char *p;
int i;
int c;
gboolean flushed;
sprintf(cdata, " { \"%s\", %d, %d, %d, %d, %d, \n \"",
ci->name, ci->id, ci->width, ci->height, ci->hotx, ci->hoty);
p = cdata + strlen(cdata);
for (i = 0; i < ci->width * ci->height; i++)
{
flushed = FALSE;
if (!(i%4))
c = 0;
c = c << 2;
c += ci->data[i];
if ((i % 4) == 3)
{
flushed = TRUE;
sprintf(p, "\\%03o", c);
p += strlen(p);
}
if (i > 0 && !(i % 64))
{
strcpy(p ,"\"\n \"");
p += strlen(p);
}
}
if (!flushed)
{
sprintf(p, "\\%03o", c);
p += strlen(p);
}
strcpy(p, "\" }");
return cdata;
}
static int dump_cursors()
{
GSList *ptr;
FILE *f = stdout;
fprintf(f, "static const struct { const char *name; int type; guchar width; guchar height; guchar hotx; guchar hoty; guchar *data; } cursors[] = {\n");
for (ptr = cursors; ptr; ptr = ptr->next)
{
if (debug)
print_cursor(ptr->data);
fprintf(f, "%s, \n", dump_cursor(ptr->data));
}
fprintf(f, " { NULL, 0, 0, 0, 0, 0, NULL },\n};\n");
return 0;
}
int main(argc, argv)
int argc;
char **argv;
{
if (argc != 2)
{
printf("missing parameters !\n");
printf("Usage: %s [BDF cursor file]\n", argv[0]);
return -1;
}
if (g_getenv ("BDFCURSOR_DEBUG") != NULL)
debug = TRUE;
if (read_bdf_font(argv[1]) || !fonts)
{
printf("Error reading font\n");
return 1;
}
compose_cursors_from_fonts();
if (!cursors)
{
printf("failed to generate cursors from font!\n");
return 1;
}
dump_cursors();
if (fonts)
{
printf("some fonts remained unconverted!\n");
return 1;
}
return 0;
}
+3385
View File
File diff suppressed because it is too large Load Diff
+24
View File
@@ -157,6 +157,7 @@ static HKL latin_locale = NULL;
static gboolean in_ime_composition = FALSE;
static UINT modal_timer;
static UINT sync_timer = 0;
static int debug_indent = 0;
@@ -1452,6 +1453,23 @@ _gdk_win32_end_modal_call (GdkWin32ModalOpKind kind)
}
}
static VOID CALLBACK
sync_timer_proc (HWND hwnd,
UINT msg,
UINT_PTR id,
DWORD time)
{
MSG message;
if (PeekMessageW (&message, hwnd, WM_PAINT, WM_PAINT, PM_REMOVE))
{
return;
}
RedrawWindow (hwnd, NULL, NULL, RDW_INVALIDATE|RDW_UPDATENOW|RDW_ALLCHILDREN);
KillTimer (hwnd, sync_timer);
}
static gboolean
handle_nchittest (HWND hwnd,
GdkSurface *window,
@@ -2836,6 +2854,12 @@ gdk_event_translate (MSG *msg,
*ret_valp = 1;
break;
case WM_SYNCPAINT:
sync_timer = SetTimer (GDK_SURFACE_HWND (window),
1,
200, sync_timer_proc);
break;
case WM_PAINT:
handle_wm_paint (msg, window);
break;
+140 -140
View File
@@ -653,7 +653,6 @@ _gdk_win32_display_create_surface (GdkDisplay *display,
g_object_unref (frame_clock);
impl->hdc = GetDC (impl->handle);
impl->inhibit_configure = TRUE;
return surface;
}
@@ -787,6 +786,26 @@ show_window_internal (GdkSurface *window,
if (!unminimize && !already_mapped && IsWindowVisible (GDK_SURFACE_HWND (window)))
return;
/* Other cases */
exstyle = GetWindowLong (GDK_SURFACE_HWND (window), GWL_EXSTYLE);
/* Use SetWindowPos to show transparent windows so automatic redraws
* in other windows can be suppressed.
*/
if (exstyle & WS_EX_TRANSPARENT)
{
UINT flags = SWP_SHOWWINDOW | SWP_NOREDRAW | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER;
if (GDK_IS_DRAG_SURFACE (window))
flags |= SWP_NOACTIVATE;
SetWindowPos (GDK_SURFACE_HWND (window),
SWP_NOZORDER_SPECIFIED, 0, 0, 0, 0, flags);
return;
}
/* For initial map of "normal" windows we want to emulate WM window
* positioning behaviour, which means:
* + default to the initial CW_USEDEFAULT placement,
@@ -933,8 +952,6 @@ show_window_internal (GdkSurface *window,
GtkShowWindow (window, SW_SHOW);
}
exstyle = GetWindowLong (GDK_SURFACE_HWND (window), GWL_EXSTYLE);
/* Sync STATE_ABOVE to TOPMOST */
if (!GDK_IS_DRAG_SURFACE (window) &&
(((window->state & GDK_TOPLEVEL_STATE_ABOVE) &&
@@ -971,7 +988,22 @@ gdk_win32_surface_hide (GdkSurface *window)
_gdk_surface_clear_update_area (window);
GtkShowWindow (window, SW_HIDE);
if (GDK_IS_TOPLEVEL (window))
ShowOwnedPopups (GDK_SURFACE_HWND (window), FALSE);
/* Use SetWindowPos to hide transparent windows so automatic redraws
* in other windows can be suppressed.
*/
if (GetWindowLong (GDK_SURFACE_HWND (window), GWL_EXSTYLE) & WS_EX_TRANSPARENT)
{
SetWindowPos (GDK_SURFACE_HWND (window), SWP_NOZORDER_SPECIFIED,
0, 0, 0, 0,
SWP_HIDEWINDOW | SWP_NOREDRAW | SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
}
else
{
GtkShowWindow (window, SW_HIDE);
}
}
static void
@@ -1010,15 +1042,14 @@ gdk_win32_surface_do_move (GdkSurface *window,
}
void
gdk_win32_surface_resize (GdkSurface *surface,
int width,
int height)
gdk_win32_surface_resize (GdkSurface *window,
int width, int height)
{
RECT outer_rect;
g_return_if_fail (GDK_IS_SURFACE (surface));
g_return_if_fail (GDK_IS_SURFACE (window));
if (GDK_SURFACE_DESTROYED (surface))
if (GDK_SURFACE_DESTROYED (window))
return;
if (width < 1)
@@ -1027,29 +1058,28 @@ gdk_win32_surface_resize (GdkSurface *surface,
height = 1;
GDK_NOTE (MISC, g_print ("gdk_win32_surface_resize: %p: %dx%d\n",
GDK_SURFACE_HWND (surface), width, height));
GDK_SURFACE_HWND (window), width, height));
if (surface->state & GDK_TOPLEVEL_STATE_FULLSCREEN)
if (window->state & GDK_TOPLEVEL_STATE_FULLSCREEN)
return;
get_outer_rect (surface, width, height, &outer_rect);
get_outer_rect (window, width, height, &outer_rect);
GDK_NOTE (MISC, g_print ("... SetWindowPos(%p,NULL,0,0,%ld,%ld,"
"NOACTIVATE|NOMOVE|NOZORDER)\n",
GDK_SURFACE_HWND (surface),
GDK_SURFACE_HWND (window),
outer_rect.right - outer_rect.left,
outer_rect.bottom - outer_rect.top));
API_CALL (SetWindowPos, (GDK_SURFACE_HWND (surface),
API_CALL (SetWindowPos, (GDK_SURFACE_HWND (window),
SWP_NOZORDER_SPECIFIED,
0, 0,
outer_rect.right - outer_rect.left,
outer_rect.bottom - outer_rect.top,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER));
surface->resize_count += 1;
window->resize_count += 1;
if (!GDK_WIN32_SURFACE (surface)->force_recompute_size)
gdk_surface_request_layout (surface);
gdk_surface_request_layout (window);
}
static void
@@ -1303,22 +1333,33 @@ gdk_win32_surface_set_urgency_hint (GdkSurface *window,
gboolean urgent)
{
FLASHWINFO flashwinfo;
typedef BOOL (WINAPI *PFN_FlashWindowEx) (FLASHWINFO*);
PFN_FlashWindowEx flashWindowEx = NULL;
g_return_if_fail (GDK_IS_SURFACE (window));
if (GDK_SURFACE_DESTROYED (window))
return;
flashwinfo.cbSize = sizeof (flashwinfo);
flashwinfo.hwnd = GDK_SURFACE_HWND (window);
if (urgent)
flashwinfo.dwFlags = FLASHW_ALL | FLASHW_TIMER;
else
flashwinfo.dwFlags = FLASHW_STOP;
flashwinfo.uCount = 0;
flashwinfo.dwTimeout = 0;
flashWindowEx = (PFN_FlashWindowEx) GetProcAddress (GetModuleHandle ("user32.dll"), "FlashWindowEx");
FlashWindowEx (&flashwinfo);
if (flashWindowEx)
{
flashwinfo.cbSize = sizeof (flashwinfo);
flashwinfo.hwnd = GDK_SURFACE_HWND (window);
if (urgent)
flashwinfo.dwFlags = FLASHW_ALL | FLASHW_TIMER;
else
flashwinfo.dwFlags = FLASHW_STOP;
flashwinfo.uCount = 0;
flashwinfo.dwTimeout = 0;
flashWindowEx (&flashwinfo);
}
else
{
FlashWindow (GDK_SURFACE_HWND (window), urgent);
}
}
static gboolean
@@ -2454,41 +2495,35 @@ _gdk_win32_surface_handle_aerosnap (GdkSurface *window,
}
static void
apply_snap (GdkSurface *surface,
GdkWin32AeroSnapState snap)
apply_snap (GdkSurface *window,
GdkWin32AeroSnapState snap)
{
GdkMonitor *monitor;
GdkDisplay *display;
display = gdk_surface_get_display (surface);
monitor = gdk_display_get_monitor_at_surface (display, surface);
display = gdk_surface_get_display (window);
monitor = gdk_display_get_monitor_at_surface (display, window);
switch (snap)
{
case GDK_WIN32_AEROSNAP_STATE_UNDETERMINED:
break;
case GDK_WIN32_AEROSNAP_STATE_MAXIMIZE:
unsnap (surface, monitor);
gdk_win32_surface_maximize (surface);
unsnap (window, monitor);
gdk_win32_surface_maximize (window);
break;
case GDK_WIN32_AEROSNAP_STATE_HALFLEFT:
unsnap (surface, monitor);
snap_left (surface, monitor, monitor);
unsnap (window, monitor);
snap_left (window, monitor, monitor);
break;
case GDK_WIN32_AEROSNAP_STATE_HALFRIGHT:
unsnap (surface, monitor);
snap_right (surface, monitor, monitor);
unsnap (window, monitor);
snap_right (window, monitor, monitor);
break;
case GDK_WIN32_AEROSNAP_STATE_FULLUP:
snap_up (surface);
snap_up (window);
break;
}
if (snap != GDK_WIN32_AEROSNAP_STATE_UNDETERMINED)
{
GDK_WIN32_SURFACE (surface)->inhibit_configure = TRUE;
GDK_WIN32_SURFACE (surface)->force_recompute_size = FALSE;
}
}
/* Registers a dumb window class. This window
@@ -3368,10 +3403,10 @@ get_cursor_name_from_op (GdkW32WindowDragOp op,
}
static void
setup_drag_move_resize_context (GdkSurface *surface,
setup_drag_move_resize_context (GdkSurface *window,
GdkW32DragMoveResizeContext *context,
GdkW32WindowDragOp op,
GdkSurfaceEdge edge,
GdkSurfaceEdge edge,
GdkDevice *device,
int button,
double x,
@@ -3380,13 +3415,12 @@ setup_drag_move_resize_context (GdkSurface *surface,
{
RECT rect;
const char *cursor_name;
GdkSurface *pointer_surface;
GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);
gboolean maximized = gdk_toplevel_get_state (GDK_TOPLEVEL (surface)) & GDK_TOPLEVEL_STATE_MAXIMIZED;
GdkSurface *pointer_window;
GdkWin32Surface *impl = GDK_WIN32_SURFACE (window);
gboolean maximized = gdk_toplevel_get_state (GDK_TOPLEVEL (window)) & GDK_TOPLEVEL_STATE_MAXIMIZED;
int root_x, root_y;
gboolean restore_configure = FALSE;
gdk_win32_surface_get_root_coords (surface, x, y, &root_x, &root_y);
gdk_win32_surface_get_root_coords (window, x, y, &root_x, &root_y);
/* Before we drag, we need to undo any maximization or snapping.
* AeroSnap behaviour:
@@ -3409,7 +3443,7 @@ setup_drag_move_resize_context (GdkSurface *surface,
* resize
* don't unsnap
* apply new width and x position to unsnapped cache,
* so that unsnapped surface only regains its height
* so that unsnapped window only regains its height
* and y position, but inherits x and width from
* the fullup snapped state
* vertical resize:
@@ -3424,7 +3458,7 @@ setup_drag_move_resize_context (GdkSurface *surface,
*
* TODO: make this implementation behave as AeroSnap on resizes?
* There's also the case where
* a halfleft/halfright surface isn't unsnapped when it's
* a halfleft/halfright window isn't unsnapped when it's
* being moved horizontally, but it's more difficult to implement.
*/
if (op == GDK_WIN32_DRAGOP_RESIZE &&
@@ -3432,8 +3466,7 @@ setup_drag_move_resize_context (GdkSurface *surface,
impl->snap_state == GDK_WIN32_AEROSNAP_STATE_HALFLEFT ||
impl->snap_state == GDK_WIN32_AEROSNAP_STATE_FULLUP))
{
discard_snapinfo (surface);
restore_configure = TRUE;
discard_snapinfo (window);
}
else if (maximized ||
(impl->snap_state == GDK_WIN32_AEROSNAP_STATE_HALFRIGHT ||
@@ -3443,25 +3476,24 @@ setup_drag_move_resize_context (GdkSurface *surface,
GdkMonitor *monitor;
int wx, wy, wwidth, wheight;
int swx, swy, swwidth, swheight;
gboolean pointer_outside_of_surface;
gboolean pointer_outside_of_window;
int offsetx, offsety;
gboolean left_half;
GdkDisplay *display;
restore_configure = TRUE;
display = gdk_surface_get_display (surface);
monitor = gdk_display_get_monitor_at_surface (display, surface);
gdk_surface_get_geometry (surface, &wx, &wy, &wwidth, &wheight);
display = gdk_surface_get_display (window);
monitor = gdk_display_get_monitor_at_surface (display, window);
gdk_surface_get_geometry (window, &wx, &wy, &wwidth, &wheight);
swx = wx;
swy = wy;
swwidth = wwidth;
swheight = wheight;
/* Subtract surface shadow. We don't want pointer to go outside of
* the visible surface during drag-move. For drag-resize it's OK.
* Don't take shadow into account if the surface is maximized -
* maximized surfaces don't have shadows.
/* Subtract window shadow. We don't want pointer to go outside of
* the visible window during drag-move. For drag-resize it's OK.
* Don't take shadow into account if the window is maximized -
* maximized windows don't have shadows.
*/
if (op == GDK_WIN32_DRAGOP_MOVE && !maximized)
{
@@ -3471,16 +3503,16 @@ setup_drag_move_resize_context (GdkSurface *surface,
swheight -= impl->shadow_y;
}
pointer_outside_of_surface = root_x < swx || root_x > swx + swwidth ||
pointer_outside_of_window = root_x < swx || root_x > swx + swwidth ||
root_y < swy || root_y > swy + swheight;
/* Calculate the offset of the pointer relative to the surface */
/* Calculate the offset of the pointer relative to the window */
offsetx = root_x - swx;
offsety = root_y - swy;
/* Figure out in which half of the surface the pointer is.
/* Figure out in which half of the window the pointer is.
* The code currently only concerns itself with horizontal
* dimension (left/right halves).
* There's no upper/lower half, because usually surface
* There's no upper/lower half, because usually window
* is dragged by its upper half anyway. If that changes, adjust
* accordingly.
*/
@@ -3490,26 +3522,26 @@ setup_drag_move_resize_context (GdkSurface *surface,
if (!left_half)
offsetx = swwidth - offsetx;
GDK_NOTE (MISC, g_print ("Pointer at %d : %d, this is %d : %d relative to the surface's %s\n",
GDK_NOTE (MISC, g_print ("Pointer at %d : %d, this is %d : %d relative to the window's %s\n",
root_x, root_y, offsetx, offsety,
left_half ? "left half" : "right half"));
/* Move surface in such a way that on unmaximization/unsnapping the pointer
* is still pointing at the appropriate half of the surface,
/* Move window in such a way that on unmaximization/unsnapping the pointer
* is still pointing at the appropriate half of the window,
* with the same offset from the left or right edge. If the new
* surface size is too small, and adding that offset puts the pointer
* window size is too small, and adding that offset puts the pointer
* into the other half or even beyond, move the pointer to the middle.
*/
if (!pointer_outside_of_surface && maximized)
if (!pointer_outside_of_window && maximized)
{
WINDOWPLACEMENT placement;
int unmax_width, unmax_height;
int shadow_unmax_width, shadow_unmax_height;
placement.length = sizeof (placement);
API_CALL (GetWindowPlacement, (GDK_SURFACE_HWND (surface), &placement));
API_CALL (GetWindowPlacement, (GDK_SURFACE_HWND (window), &placement));
GDK_NOTE (MISC, g_print ("W32 WM unmaximized surface placement is %ld x %ld @ %ld : %ld\n",
GDK_NOTE (MISC, g_print ("W32 WM unmaximized window placement is %ld x %ld @ %ld : %ld\n",
placement.rcNormalPosition.right - placement.rcNormalPosition.left,
placement.rcNormalPosition.bottom - placement.rcNormalPosition.top,
placement.rcNormalPosition.left,
@@ -3555,9 +3587,9 @@ setup_drag_move_resize_context (GdkSurface *surface,
placement.rcNormalPosition.left,
placement.rcNormalPosition.top));
API_CALL (SetWindowPlacement, (GDK_SURFACE_HWND (surface), &placement));
API_CALL (SetWindowPlacement, (GDK_SURFACE_HWND (window), &placement));
}
else if (!pointer_outside_of_surface && impl->snap_stash_int)
else if (!pointer_outside_of_window && impl->snap_stash_int)
{
GdkRectangle new_pos;
GdkRectangle snew_pos;
@@ -3587,22 +3619,22 @@ setup_drag_move_resize_context (GdkSurface *surface,
new_pos.y = root_y - new_pos.height / 2;
}
GDK_NOTE (MISC, g_print ("Unsnapped surface to %d : %d\n",
GDK_NOTE (MISC, g_print ("Unsnapped window to %d : %d\n",
new_pos.x, new_pos.y));
discard_snapinfo (surface);
gdk_win32_surface_move_resize (surface, new_pos.x, new_pos.y,
discard_snapinfo (window);
gdk_win32_surface_move_resize (window, new_pos.x, new_pos.y,
new_pos.width, new_pos.height);
}
if (maximized)
gdk_win32_surface_unmaximize (surface);
gdk_win32_surface_unmaximize (window);
else
unsnap (surface, monitor);
unsnap (window, monitor);
if (pointer_outside_of_surface)
if (pointer_outside_of_window)
{
/* Pointer outside of the surface, move pointer into surface */
/* Pointer outside of the window, move pointer into window */
GDK_NOTE (MISC, g_print ("Pointer at %d : %d is outside of %d x %d @ %d : %d, move it to %d : %d\n",
root_x, root_y, wwidth, wheight, wx, wy, wx + wwidth / 2, wy + wheight / 2));
root_x = wx + wwidth / 2;
@@ -3615,29 +3647,26 @@ setup_drag_move_resize_context (GdkSurface *surface,
}
}
if (restore_configure)
impl->inhibit_configure = FALSE;
_gdk_win32_get_window_rect (surface, &rect);
_gdk_win32_get_window_rect (window, &rect);
cursor_name = get_cursor_name_from_op (op, edge);
context->cursor = gdk_cursor_new_from_name (cursor_name, NULL);
pointer_surface = surface;
pointer_window = window;
/* Note: This triggers a WM_CAPTURECHANGED, which will trigger
* gdk_win32_surface_end_move_resize_drag(), which will end
* our op before it even begins, but only if context->op is not NONE.
* This is why we first do the grab, *then* set the op.
*/
gdk_device_grab (device, pointer_surface,
gdk_device_grab (device, pointer_window,
FALSE,
GDK_ALL_EVENTS_MASK,
context->cursor,
timestamp);
context->window = g_object_ref (surface);
context->window = g_object_ref (window);
context->op = op;
context->edge = edge;
context->device = device;
@@ -3657,10 +3686,10 @@ setup_drag_move_resize_context (GdkSurface *surface,
calculate_aerosnap_regions (context);
GDK_NOTE (EVENTS,
g_print ("begin drag moveresize: surface %p, toplevel %p, "
g_print ("begin drag moveresize: window %p, toplevel %p, "
"op %u, edge %d, device %p, "
"button %d, coord %d:%d, time %u\n",
pointer_surface, surface,
pointer_window, window,
context->op, context->edge, context->device,
context->button, context->start_root_x,
context->start_root_y, context->timestamp));
@@ -4055,61 +4084,45 @@ gdk_win32_surface_minimize (GdkSurface *window)
}
static void
gdk_win32_surface_maximize (GdkSurface *surface)
gdk_win32_surface_maximize (GdkSurface *window)
{
GdkWin32Surface *impl;
g_return_if_fail (GDK_IS_SURFACE (window));
g_return_if_fail (GDK_IS_SURFACE (surface));
if (GDK_SURFACE_DESTROYED (surface))
if (GDK_SURFACE_DESTROYED (window))
return;
GDK_NOTE (MISC, g_print ("gdk_surface_maximize: %p: %s\n",
GDK_SURFACE_HWND (surface),
_gdk_win32_surface_state_to_string (surface->state)));
GDK_SURFACE_HWND (window),
_gdk_win32_surface_state_to_string (window->state)));
impl = GDK_WIN32_SURFACE (surface);
impl->inhibit_configure = TRUE;
impl->force_recompute_size = FALSE;
if (GDK_SURFACE_IS_MAPPED (surface))
GtkShowWindow (surface, SW_MAXIMIZE);
if (GDK_SURFACE_IS_MAPPED (window))
GtkShowWindow (window, SW_MAXIMIZE);
else
gdk_synthesize_surface_state (surface,
gdk_synthesize_surface_state (window,
0,
GDK_TOPLEVEL_STATE_MAXIMIZED);
}
static void
gdk_win32_surface_unmaximize (GdkSurface *surface)
gdk_win32_surface_unmaximize (GdkSurface *window)
{
GdkWin32Surface *impl;
g_return_if_fail (GDK_IS_SURFACE (window));
g_return_if_fail (GDK_IS_SURFACE (surface));
if (GDK_SURFACE_DESTROYED (surface))
if (GDK_SURFACE_DESTROYED (window))
return;
GDK_NOTE (MISC, g_print ("gdk_surface_unmaximize: %p: %s\n",
GDK_SURFACE_HWND (surface),
_gdk_win32_surface_state_to_string (surface->state)));
GDK_SURFACE_HWND (window),
_gdk_win32_surface_state_to_string (window->state)));
_gdk_win32_surface_invalidate_egl_framebuffer (surface);
_gdk_win32_surface_invalidate_egl_framebuffer (window);
if (GDK_SURFACE_IS_MAPPED (surface))
GtkShowWindow (surface, SW_RESTORE);
if (GDK_SURFACE_IS_MAPPED (window))
GtkShowWindow (window, SW_RESTORE);
else
gdk_synthesize_surface_state (surface,
gdk_synthesize_surface_state (window,
GDK_TOPLEVEL_STATE_MAXIMIZED,
0);
impl = GDK_WIN32_SURFACE (surface);
if (impl->inhibit_configure)
{
impl->inhibit_configure = FALSE;
impl->force_recompute_size = TRUE;
}
}
static void
@@ -4534,9 +4547,6 @@ _gdk_win32_surface_request_layout (GdkSurface *surface)
&surface->x, &surface->y,
NULL, NULL);
}
if (!impl->inhibit_configure)
impl->force_recompute_size = TRUE;
}
}
@@ -4551,18 +4561,8 @@ _gdk_win32_surface_compute_size (GdkSurface *surface)
if (!impl->drag_move_resize_context.native_move_resize_pending)
{
if (GDK_IS_TOPLEVEL (surface) && impl->force_recompute_size)
{
surface->width = width;
surface->height = height;
gdk_win32_surface_resize (surface, width, height);
impl->force_recompute_size = FALSE;
}
else
{
surface->width = impl->next_layout.configured_width;
surface->height = impl->next_layout.configured_height;
}
surface->width = impl->next_layout.configured_width;
surface->height = impl->next_layout.configured_height;
_gdk_surface_update_size (surface);
}
-1
View File
@@ -337,7 +337,6 @@ struct _GdkWin32Surface
int configured_height;
RECT configured_rect;
} next_layout;
gboolean force_recompute_size;
#ifdef HAVE_EGL
guint egl_force_redraw_all : 1;
+30
View File
@@ -0,0 +1,30 @@
# libwntab32x.la - a libtool library file
# Generated by hand, compatible with libtool
# Just a wrapper for libwntab32x.a, which is just a copy of wntab32x.lib
#
# The name that we can dlopen(3).
dlname=''
# Names of this library.
library_names=''
# The name of the static archive.
old_library='libwntab32x.a'
# Libraries that this one depends upon.
dependency_libs=''
# Version information
current=0
age=0
revision=0
# Is this an already installed library?
installed=no
# Files to dlopen/dlpreopen
dlopen=''
dlpreopen=''
# Directory that this library needs to be installed in:
libdir=''
+2 -26
View File
@@ -76,30 +76,11 @@ print_atoms (GdkX11Clipboard *cb,
});
}
static void
gdk_x11_clipboard_default_output_closed (GObject *stream,
GAsyncResult *result,
gpointer user_data)
{
GError *error = NULL;
if (!g_output_stream_close_finish (G_OUTPUT_STREAM (stream), result, &error))
{
GDK_NOTE (CLIPBOARD,
g_printerr ("-------: failed to close stream: %s\n",
error->message));
g_error_free (error);
}
g_object_unref (stream);
}
static void
gdk_x11_clipboard_default_output_done (GObject *clipboard,
GAsyncResult *result,
gpointer user_data)
{
GOutputStream *stream = user_data;
GError *error = NULL;
if (!gdk_clipboard_write_finish (GDK_CLIPBOARD (clipboard), result, &error))
@@ -109,12 +90,6 @@ gdk_x11_clipboard_default_output_done (GObject *clipboard,
GDK_X11_CLIPBOARD (clipboard)->selection, error->message));
g_error_free (error);
}
g_output_stream_close_async (stream,
G_PRIORITY_DEFAULT,
NULL,
gdk_x11_clipboard_default_output_closed,
NULL);
}
static void
@@ -128,7 +103,8 @@ gdk_x11_clipboard_default_output_handler (GOutputStream *stream,
G_PRIORITY_DEFAULT,
NULL,
gdk_x11_clipboard_default_output_done,
stream);
NULL);
g_object_unref (stream);
}
static GInputStream *
+3 -5
View File
@@ -1665,7 +1665,7 @@ gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
_gdk_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group),
direction,
FALSE);
}
else
{
@@ -1799,7 +1799,7 @@ gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
_gdk_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group),
x, y,
axes);
}
break;
@@ -1838,7 +1838,7 @@ gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
x = (double) xev->event_x / scale;
y = (double) xev->event_y / scale;
event = gdk_touch_event_new (ev->evtype == XI_TouchBegin
? GDK_TOUCH_BEGIN
: GDK_TOUCH_END,
@@ -1946,7 +1946,6 @@ gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
y = (double) xev->event_y / scale;
event = gdk_touchpad_event_new_pinch (surface,
NULL, /* FIXME make up sequences */
device,
xev->time,
state,
@@ -2007,7 +2006,6 @@ gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
y = (double) xev->event_y / scale;
event = gdk_touchpad_event_new_swipe (surface,
NULL, /* FIXME make up sequences */
device,
xev->time,
state,
+2 -26
View File
@@ -1612,30 +1612,11 @@ gdk_x11_drag_set_hotspot (GdkDrag *drag,
}
}
static void
gdk_x11_drag_default_output_closed (GObject *stream,
GAsyncResult *result,
gpointer user_data)
{
GError *error = NULL;
if (!g_output_stream_close_finish (G_OUTPUT_STREAM (stream), result, &error))
{
GDK_NOTE (DND,
g_printerr ("failed to close stream: %s\n",
error->message));
g_error_free (error);
}
g_object_unref (stream);
}
static void
gdk_x11_drag_default_output_done (GObject *drag,
GAsyncResult *result,
gpointer user_data)
{
GOutputStream *stream = user_data;
GError *error = NULL;
if (!gdk_drag_write_finish (GDK_DRAG (drag), result, &error))
@@ -1643,12 +1624,6 @@ gdk_x11_drag_default_output_done (GObject *drag,
GDK_DISPLAY_NOTE (gdk_drag_get_display (GDK_DRAG (drag)), DND, g_printerr ("failed to write stream: %s\n", error->message));
g_error_free (error);
}
g_output_stream_close_async (stream,
G_PRIORITY_DEFAULT,
NULL,
gdk_x11_drag_default_output_closed,
NULL);
}
static void
@@ -1662,7 +1637,8 @@ gdk_x11_drag_default_output_handler (GOutputStream *stream,
G_PRIORITY_DEFAULT,
NULL,
gdk_x11_drag_default_output_done,
stream);
NULL);
g_object_unref (stream);
}
static gboolean
-2
View File
@@ -71,8 +71,6 @@ gdk_x11_surface_destroy_glx_drawable (GdkX11Surface *self)
if (self->glx_drawable == None)
return;
gdk_gl_context_clear_current_if_surface (GDK_SURFACE (self));
glXDestroyWindow (gdk_x11_display_get_xdisplay (gdk_surface_get_display (GDK_SURFACE (self))),
self->glx_drawable);
+61 -20
View File
@@ -57,8 +57,7 @@ struct _GdkX11SelectionOutputStreamPrivate {
GTask *pending_task;
guint incr : 1;
guint sent_end_of_stream : 1;
guint delete_pending : 1; /* owns a reference */
guint delete_pending : 1;
};
struct _GdkX11PendingSelectionNotify
@@ -177,16 +176,12 @@ gdk_x11_selection_output_stream_needs_flush_unlocked (GdkX11SelectionOutputStrea
{
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
if (priv->sent_end_of_stream)
return FALSE;
if (g_output_stream_is_closing (G_OUTPUT_STREAM (stream)) ||
g_output_stream_is_closed (G_OUTPUT_STREAM (stream)))
return TRUE;
if (priv->data->len == 0 && priv->notify == NULL)
return FALSE;
if (g_output_stream_is_closing (G_OUTPUT_STREAM (stream)))
return TRUE;
if (priv->flush_requested)
return TRUE;
@@ -289,8 +284,6 @@ gdk_x11_selection_output_stream_perform_flush (GdkX11SelectionOutputStream *stre
g_byte_array_remove_range (priv->data, 0, n_elements * element_size);
if (priv->data->len < element_size)
priv->flush_requested = FALSE;
if (!priv->incr || n_elements == 0)
priv->sent_end_of_stream = TRUE;
}
if (priv->notify)
@@ -299,7 +292,6 @@ gdk_x11_selection_output_stream_perform_flush (GdkX11SelectionOutputStream *stre
priv->notify = NULL;
}
g_object_ref (stream);
priv->delete_pending = TRUE;
g_cond_broadcast (&priv->cond);
g_mutex_unlock (&priv->mutex);
@@ -502,6 +494,60 @@ gdk_x11_selection_output_stream_flush_finish (GOutputStream *stream,
return g_task_propagate_boolean (G_TASK (result), error);
}
static gboolean
gdk_x11_selection_output_stream_invoke_close (gpointer stream)
{
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
GDK_X11_DISPLAY (priv->display)->streams = g_slist_remove (GDK_X11_DISPLAY (priv->display)->streams, stream);
g_signal_handlers_disconnect_by_func (priv->display,
gdk_x11_selection_output_stream_xevent,
stream);
g_object_unref (stream);
return G_SOURCE_REMOVE;
}
static gboolean
gdk_x11_selection_output_stream_close (GOutputStream *stream,
GCancellable *cancellable,
GError **error)
{
g_main_context_invoke (NULL, gdk_x11_selection_output_stream_invoke_close, g_object_ref (stream));
return TRUE;
}
static void
gdk_x11_selection_output_stream_close_async (GOutputStream *stream,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (stream, cancellable, callback, user_data);
g_task_set_source_tag (task, gdk_x11_selection_output_stream_close_async);
g_task_set_priority (task, io_priority);
gdk_x11_selection_output_stream_invoke_close (stream);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static gboolean
gdk_x11_selection_output_stream_close_finish (GOutputStream *stream,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
g_return_val_if_fail (g_async_result_is_tagged (result, gdk_x11_selection_output_stream_close_async), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
gdk_x11_selection_output_stream_finalize (GObject *object)
{
@@ -511,13 +557,6 @@ gdk_x11_selection_output_stream_finalize (GObject *object)
/* not sending a notify is terrible */
g_assert (priv->notify == NULL);
GDK_DISPLAY_NOTE (priv->display, SELECTION, g_printerr ("%s:%s: finalizing\n",
priv->selection, priv->target));
GDK_X11_DISPLAY (priv->display)->streams = g_slist_remove (GDK_X11_DISPLAY (priv->display)->streams, stream);
g_signal_handlers_disconnect_by_func (priv->display,
gdk_x11_selection_output_stream_xevent,
stream);
g_byte_array_unref (priv->data);
g_cond_clear (&priv->cond);
g_mutex_clear (&priv->mutex);
@@ -540,11 +579,14 @@ gdk_x11_selection_output_stream_class_init (GdkX11SelectionOutputStreamClass *kl
output_stream_class->write_fn = gdk_x11_selection_output_stream_write;
output_stream_class->flush = gdk_x11_selection_output_stream_flush;
output_stream_class->close_fn = gdk_x11_selection_output_stream_close;
output_stream_class->write_async = gdk_x11_selection_output_stream_write_async;
output_stream_class->write_finish = gdk_x11_selection_output_stream_write_finish;
output_stream_class->flush_async = gdk_x11_selection_output_stream_flush_async;
output_stream_class->flush_finish = gdk_x11_selection_output_stream_flush_finish;
output_stream_class->close_async = gdk_x11_selection_output_stream_close_async;
output_stream_class->close_finish = gdk_x11_selection_output_stream_close_finish;
}
static void
@@ -586,7 +628,6 @@ gdk_x11_selection_output_stream_xevent (GdkDisplay *display,
if (gdk_x11_selection_output_stream_needs_flush (stream) &&
gdk_x11_selection_output_stream_can_flush (stream))
gdk_x11_selection_output_stream_perform_flush (stream);
g_object_unref (stream); /* from unsetting the delete_pending */
return FALSE;
default:
+1 -1
View File
@@ -3535,7 +3535,7 @@ gdk_x11_surface_unfullscreen (GdkSurface *surface)
*
* Returns the group this surface belongs to.
*
* Returns: (transfer none) (nullable): The group of this surface;
* Returns: (transfer none): The group of this surface;
*/
GdkSurface *
gdk_x11_surface_get_group (GdkSurface *surface)
+4
View File
@@ -269,6 +269,8 @@ collect_reused_child_nodes (GskRenderer *renderer,
case GSK_BLEND_NODE:
case GSK_CROSS_FADE_NODE:
case GSK_BLUR_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
default:
@@ -855,6 +857,8 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer,
case GSK_CROSS_FADE_NODE:
case GSK_BLUR_NODE:
case GSK_GL_SHADER_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
default:
break; /* Fallback */
}
+3 -3
View File
@@ -952,9 +952,9 @@ gsk_gl_command_queue_execute (GskGLCommandQueue *self,
guint program = 0;
guint width = 0;
guint height = 0;
G_GNUC_UNUSED guint n_binds = 0;
guint n_binds = 0;
guint n_fbos = 0;
G_GNUC_UNUSED guint n_uniforms = 0;
guint n_uniforms = 0;
guint n_programs = 0;
guint vao_id;
guint vbo_id;
@@ -1396,7 +1396,7 @@ gsk_gl_command_queue_do_upload_texture (GskGLCommandQueue *self,
glTexImage2D (GL_TEXTURE_2D, 0, gl_internalformat, width, height, 0, gl_format, gl_type, data);
}
else if (stride % bpp == 0 &&
(gdk_gl_context_check_version (context, 0, 0, 3, 0) || gdk_gl_context_has_unpack_subimage (context)))
(!use_es || gdk_gl_context_check_version (context, 3, 0) || gdk_gl_context_has_unpack_subimage (context)))
{
glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / bpp);
+1 -2
View File
@@ -147,8 +147,7 @@ gsk_gl_compiler_new (GskGLDriver *driver,
gdk_gl_context_get_version (context, &maj, &min);
/* On Windows, legacy contexts can give us a GL 4.x context */
if (maj >= 3)
if (maj == 3)
self->glsl_version = SHADER_VERSION_GL3_LEGACY;
else
self->glsl_version = SHADER_VERSION_GL2_LEGACY;
+23 -74
View File
@@ -245,47 +245,6 @@ gsk_rounded_rect_shrink_to_minimum (GskRoundedRect *self)
self->corner[1].height + self->corner[2].height);
}
static inline gboolean G_GNUC_PURE
node_supports_2d_transform (const GskRenderNode *node)
{
switch ((int)gsk_render_node_get_node_type (node))
{
case GSK_COLOR_NODE:
case GSK_OPACITY_NODE:
case GSK_COLOR_MATRIX_NODE:
case GSK_TEXTURE_NODE:
case GSK_CROSS_FADE_NODE:
case GSK_LINEAR_GRADIENT_NODE:
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
case GSK_CONIC_GRADIENT_NODE:
case GSK_RADIAL_GRADIENT_NODE:
case GSK_REPEATING_RADIAL_GRADIENT_NODE:
case GSK_DEBUG_NODE:
case GSK_TEXT_NODE:
case GSK_CAIRO_NODE:
case GSK_BLEND_NODE:
case GSK_BLUR_NODE:
return TRUE;
case GSK_SHADOW_NODE:
return node_supports_2d_transform (gsk_shadow_node_get_child (node));
case GSK_TRANSFORM_NODE:
return node_supports_2d_transform (gsk_transform_node_get_child (node));
case GSK_CONTAINER_NODE:
for (guint i = 0, p = gsk_container_node_get_n_children (node); i < p; i++)
{
if (!node_supports_2d_transform (gsk_container_node_get_child (node, i)))
return FALSE;
}
return TRUE;
default:
return FALSE;
}
}
static inline gboolean G_GNUC_PURE
node_supports_transform (const GskRenderNode *node)
{
@@ -298,26 +257,24 @@ node_supports_transform (const GskRenderNode *node)
switch ((int)gsk_render_node_get_node_type (node))
{
case GSK_COLOR_NODE:
case GSK_OPACITY_NODE:
case GSK_COLOR_MATRIX_NODE:
case GSK_TEXTURE_NODE:
case GSK_CROSS_FADE_NODE:
case GSK_DEBUG_NODE:
case GSK_TEXT_NODE:
case GSK_CAIRO_NODE:
case GSK_BLEND_NODE:
case GSK_BLUR_NODE:
return TRUE;
case GSK_COLOR_NODE:
case GSK_OPACITY_NODE:
case GSK_COLOR_MATRIX_NODE:
case GSK_TEXTURE_NODE:
case GSK_CROSS_FADE_NODE:
case GSK_LINEAR_GRADIENT_NODE:
case GSK_DEBUG_NODE:
case GSK_TEXT_NODE:
return TRUE;
case GSK_SHADOW_NODE:
return node_supports_transform (gsk_shadow_node_get_child (node));
case GSK_SHADOW_NODE:
return node_supports_transform (gsk_shadow_node_get_child (node));
case GSK_TRANSFORM_NODE:
return node_supports_transform (gsk_transform_node_get_child (node));
case GSK_TRANSFORM_NODE:
return node_supports_transform (gsk_transform_node_get_child (node));
default:
return FALSE;
default:
return FALSE;
}
}
@@ -2060,14 +2017,6 @@ gsk_gl_render_job_visit_transform_node (GskGLRenderJob *job,
break;
case GSK_TRANSFORM_CATEGORY_2D:
if (node_supports_2d_transform (child))
{
gsk_gl_render_job_push_modelview (job, transform);
gsk_gl_render_job_visit_node (job, child);
gsk_gl_render_job_pop_modelview (job);
return;
}
G_GNUC_FALLTHROUGH;
case GSK_TRANSFORM_CATEGORY_3D:
case GSK_TRANSFORM_CATEGORY_ANY:
case GSK_TRANSFORM_CATEGORY_UNKNOWN:
@@ -3818,6 +3767,11 @@ gsk_gl_render_job_visit_node (GskGLRenderJob *job,
gsk_gl_render_job_visit_as_fallback (job, node);
break;
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
gsk_gl_render_job_visit_as_fallback (job, node);
break;
case GSK_NOT_A_RENDER_NODE:
default:
g_assert_not_reached ();
@@ -4117,19 +4071,14 @@ gsk_gl_render_job_set_debug_fallback (GskGLRenderJob *job,
}
static int
get_framebuffer_format (GdkGLContext *context,
guint framebuffer)
get_framebuffer_format (guint framebuffer)
{
int size;
if (!gdk_gl_context_check_version (context, 0, 0, 3, 0))
return GL_RGBA8;
glBindFramebuffer (GL_FRAMEBUFFER, framebuffer);
glGetFramebufferAttachmentParameteriv (GL_FRAMEBUFFER,
framebuffer ? GL_COLOR_ATTACHMENT0
: gdk_gl_context_get_use_es (context) ? GL_BACK
: GL_BACK_LEFT,
: GL_BACK_LEFT,
GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE, &size);
if (size > 16)
@@ -4166,7 +4115,7 @@ gsk_gl_render_job_new (GskGLDriver *driver,
job->scale_x = scale_factor;
job->scale_y = scale_factor;
job->viewport = *viewport;
job->target_format = get_framebuffer_format (job->command_queue->context, framebuffer);
job->target_format = get_framebuffer_format (framebuffer);
gsk_gl_render_job_set_alpha (job, 1.0f);
gsk_gl_render_job_set_projection_from_rect (job, viewport, NULL);
+2
View File
@@ -22,6 +22,8 @@
#ifndef __GI_SCANNER__
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathMeasure, gsk_path_measure_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskRenderer, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskRenderNode, gsk_render_node_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskTransform, gsk_transform_unref)
+5 -1
View File
@@ -21,11 +21,15 @@
#define __GSK_H_INSIDE__
#include <gsk/gskenums.h>
#include <gsk/gskglshader.h>
#include <gsk/gskpath.h>
#include <gsk/gskpathbuilder.h>
#include <gsk/gskpathmeasure.h>
#include <gsk/gskrenderer.h>
#include <gsk/gskrendernode.h>
#include <gsk/gskroundedrect.h>
#include <gsk/gskstroke.h>
#include <gsk/gsktransform.h>
#include <gsk/gskglshader.h>
#include <gsk/gskcairorenderer.h>
+80
View File
@@ -0,0 +1,80 @@
#include "config.h"
#include "gskboundingboxprivate.h"
GskBoundingBox *
gsk_bounding_box_init (GskBoundingBox *self,
const graphene_point_t *a,
const graphene_point_t *b)
{
self->min.x = MIN (a->x, b->x);
self->min.y = MIN (a->y, b->y);
self->max.x = MAX (a->x, b->x);
self->max.y = MAX (a->y, b->y);
return self;
}
GskBoundingBox *
gsk_bounding_box_init_copy (GskBoundingBox *self,
const GskBoundingBox *src)
{
self->min = src->min;
self->max = src->max;
return self;
}
GskBoundingBox *
gsk_bounding_box_init_from_rect (GskBoundingBox *self,
const graphene_rect_t *bounds)
{
self->min = bounds->origin;
self->max.x = bounds->origin.x + bounds->size.width;
self->max.y = bounds->origin.y + bounds->size.height;
return self;
}
void
gsk_bounding_box_expand (GskBoundingBox *self,
const graphene_point_t *p)
{
self->min.x = MIN (self->min.x, p->x);
self->min.y = MIN (self->min.y, p->y);
self->max.x = MAX (self->max.x, p->x);
self->max.y = MAX (self->max.y, p->y);
}
graphene_rect_t *
gsk_bounding_box_to_rect (const GskBoundingBox *self,
graphene_rect_t *rect)
{
rect->origin = self->min;
rect->size.width = self->max.x - self->min.x;
rect->size.height = self->max.y - self->min.y;
return rect;
}
gboolean
gsk_bounding_box_contains_point (const GskBoundingBox *self,
const graphene_point_t *p)
{
return self->min.x <= p->x && p->x <= self->max.x &&
self->min.y <= p->y && p->y <= self->max.y;
}
gboolean
gsk_bounding_box_intersection (const GskBoundingBox *a,
const GskBoundingBox *b,
GskBoundingBox *res)
{
graphene_point_t min, max;
min.x = MAX (a->min.x, b->min.x);
min.y = MAX (a->min.y, b->min.y);
max.x = MIN (a->max.x, b->max.x);
max.y = MIN (a->max.y, b->max.y);
if (res)
gsk_bounding_box_init (res, &min, &max);
return min.x <= max.x && min.y <= max.y;
}
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include <gsk/gsktypes.h>
G_BEGIN_DECLS
typedef struct _GskBoundingBox GskBoundingBox;
struct _GskBoundingBox {
graphene_point_t min;
graphene_point_t max;
};
GDK_AVAILABLE_IN_ALL
GskBoundingBox * gsk_bounding_box_init (GskBoundingBox *self,
const graphene_point_t *a,
const graphene_point_t *b);
GDK_AVAILABLE_IN_ALL
GskBoundingBox * gsk_bounding_box_init_copy (GskBoundingBox *self,
const GskBoundingBox *src);
GDK_AVAILABLE_IN_ALL
GskBoundingBox * gsk_bounding_box_init_from_rect (GskBoundingBox *self,
const graphene_rect_t *bounds);
GDK_AVAILABLE_IN_ALL
void gsk_bounding_box_expand (GskBoundingBox *self,
const graphene_point_t *p);
GDK_AVAILABLE_IN_ALL
graphene_rect_t *gsk_bounding_box_to_rect (const GskBoundingBox *self,
graphene_rect_t *rect);
GDK_AVAILABLE_IN_ALL
gboolean gsk_bounding_box_contains_point (const GskBoundingBox *self,
const graphene_point_t *p);
GDK_AVAILABLE_IN_ALL
gboolean gsk_bounding_box_intersection (const GskBoundingBox *a,
const GskBoundingBox *b,
GskBoundingBox *res);
G_END_DECLS
+2099
View File
File diff suppressed because it is too large Load Diff
+132
View File
@@ -0,0 +1,132 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_CONTOUR_PRIVATE_H__
#define __GSK_CONTOUR_PRIVATE_H__
#include <gskpath.h>
#include "gskpathopprivate.h"
G_BEGIN_DECLS
/* A path is flat if it contains no cubic or conic segments.
* A path is closed if all its contours end with a GSK_PATH_CLOSE
* operation.
*/
typedef enum
{
GSK_PATH_FLAT,
GSK_PATH_CLOSED
} GskPathFlags;
typedef struct _GskContour GskContour;
GskContour * gsk_rect_contour_new (const graphene_rect_t *rect);
GskContour * gsk_circle_contour_new (const graphene_point_t *center,
float radius,
float start_angle,
float end_angle);
GskContour * gsk_standard_contour_new (GskPathFlags flags,
const graphene_point_t *points,
gsize n_points,
const gskpathop *ops,
gsize n_ops,
gssize offset);
void gsk_contour_copy (GskContour * dest,
const GskContour *src);
GskContour * gsk_contour_dup (const GskContour *src);
GskContour * gsk_contour_reverse (const GskContour *src);
gsize gsk_contour_get_size (const GskContour *self);
GskPathFlags gsk_contour_get_flags (const GskContour *self);
void gsk_contour_print (const GskContour *self,
GString *string);
gboolean gsk_contour_get_bounds (const GskContour *self,
graphene_rect_t *bounds);
gpointer gsk_contour_init_measure (const GskContour *self,
float tolerance,
float *out_length);
void gsk_contour_free_measure (const GskContour *self,
gpointer data);
gboolean gsk_contour_foreach (const GskContour *self,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
void gsk_contour_get_start_end (const GskContour *self,
graphene_point_t *start,
graphene_point_t *end);
void gsk_contour_get_point (const GskContour *self,
gpointer measure_data,
float distance,
graphene_point_t *pos,
graphene_vec2_t *tangent);
float gsk_contour_get_curvature (const GskContour *self,
gpointer measure_data,
float distance,
graphene_point_t *center);
gboolean gsk_contour_get_closest_point (const GskContour *self,
gpointer measure_data,
float tolerance,
const graphene_point_t *point,
float threshold,
float *out_distance,
graphene_point_t *out_pos,
float *out_offset,
graphene_vec2_t *out_tangent);
int gsk_contour_get_winding (const GskContour *self,
gpointer measure_data,
const graphene_point_t *point,
gboolean *on_edge);
void gsk_contour_add_segment (const GskContour *self,
GskPathBuilder *builder,
gpointer measure_data,
gboolean emit_move_to,
float start,
float end);
gboolean gsk_contour_get_stroke_bounds (const GskContour *self,
const GskStroke *stroke,
graphene_rect_t *bounds);
void gsk_contour_add_stroke (const GskContour *contour,
GskPathBuilder *builder,
GskStroke *stroke);
void gsk_contour_default_add_stroke (const GskContour *contour,
GskPathBuilder *builder,
GskStroke *stroke);
void gsk_contour_offset (const GskContour *contour,
GskPathBuilder *builder,
float distance,
GskLineJoin line_join,
float miter_limit);
void gsk_contour_default_offset (const GskContour *contour,
GskPathBuilder *builder,
float distance,
GskLineJoin line_join,
float miter_limit);
gboolean gsk_contour_is_convex (const GskContour *contour);
G_END_DECLS
#endif /* __GSK_CONTOUR_PRIVATE_H__ */
+286
View File
@@ -0,0 +1,286 @@
#include "config.h"
#include "gskconvexityprivate.h"
#include "gskcontourprivate.h"
typedef enum
{
GSK_DIR_CHANGE_UNKNOWN,
GSK_DIR_CHANGE_LEFT,
GSK_DIR_CHANGE_RIGHT,
GSK_DIR_CHANGE_STRAIGHT,
GSK_DIR_CHANGE_REVERSE,
GSK_DIR_CHANGE_INVALID,
} GskDirChange;
typedef enum
{
GSK_PATH_DIRECTION_UNKNOWN,
GSK_PATH_DIRECTION_CLOCKWISE,
GSK_PATH_DIRECTION_COUNTERCLOCKWISE,
} GskPathDirection;
typedef struct _GskConvexityChecker GskConvexityChecker;
struct _GskConvexityChecker
{
graphene_point_t first_point;
graphene_vec2_t first_vec;
graphene_point_t last_point;
graphene_vec2_t last_vec;
GskDirChange expected_direction;
GskPathDirection first_direction;
int reversals;
gboolean finite;
GskConvexity convexity;
int xsign;
int ysign;
int dx;
int dy;
};
static float
cross_product (graphene_vec2_t *a,
graphene_vec2_t *b)
{
float fa[2];
float fb[2];
graphene_vec2_to_float (a, fa);
graphene_vec2_to_float (b, fb);
return fa[0] * fb[1] - fa[1] * fb[0];
}
static GskDirChange
direction_change (GskConvexityChecker *c,
graphene_vec2_t *v)
{
float cross = cross_product (&(c->last_vec), v);
if (!finite (cross))
return GSK_DIR_CHANGE_UNKNOWN;
if (cross == 0)
return graphene_vec2_dot (&(c->last_vec), v) < 0
? GSK_DIR_CHANGE_REVERSE
: GSK_DIR_CHANGE_STRAIGHT;
return cross > 0
? GSK_DIR_CHANGE_RIGHT
: GSK_DIR_CHANGE_LEFT;
}
static gboolean
add_vec (GskConvexityChecker *c,
graphene_vec2_t *v)
{
GskDirChange dir;
int sign;
dir = direction_change (c, v);
switch (dir)
{
case GSK_DIR_CHANGE_LEFT:
case GSK_DIR_CHANGE_RIGHT:
if (c->expected_direction == GSK_DIR_CHANGE_INVALID)
{
c->expected_direction = dir;
c->first_direction = (dir == GSK_DIR_CHANGE_RIGHT)
? GSK_PATH_DIRECTION_CLOCKWISE
: GSK_PATH_DIRECTION_COUNTERCLOCKWISE;
}
else if (c->expected_direction != dir)
{
c->first_direction = GSK_PATH_DIRECTION_UNKNOWN;
c->convexity = GSK_CONVEXITY_CONCAVE;
return FALSE;
}
graphene_vec2_init_from_vec2 (&c->last_vec, v);
break;
case GSK_DIR_CHANGE_STRAIGHT:
break;
case GSK_DIR_CHANGE_REVERSE:
graphene_vec2_init_from_vec2 (&c->last_vec, v);
c->reversals++;
if (c->reversals > 2)
c->convexity = GSK_CONVEXITY_CONCAVE;
return c->reversals < 3;
case GSK_DIR_CHANGE_UNKNOWN:
c->finite = FALSE;
return FALSE;
case GSK_DIR_CHANGE_INVALID:
default:
g_assert_not_reached ();
}
if (graphene_vec2_get_x (v) > 0)
sign = 1;
else if (graphene_vec2_get_x (v) < 0)
sign = -1;
else
sign = 0;
if (sign != 0)
{
if (c->xsign != 42)
{
if (c->xsign != sign)
c->dx++;
if (c->dx > 2)
{
c->convexity = GSK_CONVEXITY_CONCAVE;
return FALSE;
}
}
c->xsign = sign;
}
if (graphene_vec2_get_y (v) > 0)
sign = 1;
else if (graphene_vec2_get_y (v) < 0)
sign = -1;
else
sign = 0;
if (sign != 0)
{
if (c->ysign != 42)
{
if (c->ysign != sign)
c->dy++;
if (c->dy > 2)
{
c->convexity = GSK_CONVEXITY_CONCAVE;
return FALSE;
}
}
c->ysign = sign;
}
return TRUE;
}
static void
gsk_convexity_checker_init (GskConvexityChecker *c)
{
c->first_point = GRAPHENE_POINT_INIT(0,0);
c->last_point = GRAPHENE_POINT_INIT(0,0);
graphene_vec2_init (&c->first_vec, 0, 0);
graphene_vec2_init (&c->last_vec, 0, 0);
c->expected_direction = GSK_DIR_CHANGE_INVALID;
c->first_direction = GSK_PATH_DIRECTION_UNKNOWN;
c->reversals = 0;
c->finite = TRUE;
c->convexity = GSK_CONVEXITY_UNKNOWN;
c->xsign = 42;
c->ysign = 42;
c->dx = 0;
c->dy = 0;
}
static void
gsk_convexity_checker_move (GskConvexityChecker *c,
const graphene_point_t *p)
{
c->first_point = c->last_point = *p;
c->expected_direction = GSK_DIR_CHANGE_INVALID;
c->convexity = GSK_CONVEXITY_CONVEX;
}
static gboolean
gsk_convexity_checker_add_point (GskConvexityChecker *c,
const graphene_point_t *p)
{
graphene_vec2_t v;
if (graphene_point_equal (&c->last_point, p))
return TRUE;
graphene_vec2_init (&v,
p->x - c->last_point.x,
p->y - c->last_point.y);
if (graphene_point_equal (&c->first_point, &c->last_point) &&
c->expected_direction == GSK_DIR_CHANGE_INVALID)
{
graphene_vec2_init_from_vec2 (&c->last_vec, &v);
graphene_vec2_init_from_vec2 (&c->first_vec, &v);
}
else if (!add_vec (c, &v))
{
c->convexity = GSK_CONVEXITY_CONCAVE;
return FALSE;
}
c->last_point = *p;
return TRUE;
}
static gboolean
gsk_convexity_checker_close (GskConvexityChecker *c)
{
if (!(gsk_convexity_checker_add_point (c, &c->first_point) &&
add_vec (c, &c->first_vec)))
{
c->convexity = GSK_CONVEXITY_CONCAVE;
return FALSE;
}
return TRUE;
}
static gboolean
convex_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskConvexityChecker *c = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_convexity_checker_move (c, &pts[0]);
break;
case GSK_PATH_CLOSE:
if (!gsk_convexity_checker_close (c))
return FALSE;
break;
case GSK_PATH_LINE:
if (!gsk_convexity_checker_add_point (c, &pts[1]))
return FALSE;
break;
case GSK_PATH_CURVE:
if (!gsk_convexity_checker_add_point (c, &pts[1]) ||
!gsk_convexity_checker_add_point (c, &pts[2]) ||
!gsk_convexity_checker_add_point (c, &pts[3]))
return FALSE;
break;
case GSK_PATH_CONIC:
if (!gsk_convexity_checker_add_point (c, &pts[1]) ||
!gsk_convexity_checker_add_point (c, &pts[3]))
return FALSE;
break;
default:
g_assert_not_reached ();
}
return TRUE;
}
GskConvexity
gsk_contour_compute_convexity (const GskContour *contour)
{
GskConvexityChecker c;
gsk_convexity_checker_init (&c);
gsk_contour_foreach (contour, 0.001, convex_cb, &c);
g_assert (c.convexity != GSK_CONVEXITY_UNKNOWN);
return c.convexity;
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include "gskcontourprivate.h"
typedef enum
{
GSK_CONVEXITY_UNKNOWN,
GSK_CONVEXITY_CONVEX,
GSK_CONVEXITY_CONCAVE,
} GskConvexity;
GskConvexity gsk_contour_compute_convexity (const GskContour *contour);
+2003
View File
File diff suppressed because it is too large Load Diff
+621
View File
@@ -0,0 +1,621 @@
/*
* Copyright © 2020 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.1 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/>.
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
#include "config.h"
#include "gskcurveprivate.h"
static inline gboolean
acceptable (float t)
{
return 0 - FLT_EPSILON <= t && t <= 1 + FLT_EPSILON;
}
static int
line_intersect (const GskCurve *curve1,
const GskCurve *curve2,
float *t1,
float *t2,
graphene_point_t *p,
int n)
{
const graphene_point_t *pts1 = curve1->line.points;
const graphene_point_t *pts2 = curve2->line.points;
float a1 = pts1[0].x - pts1[1].x;
float b1 = pts1[0].y - pts1[1].y;
float a2 = pts2[0].x - pts2[1].x;
float b2 = pts2[0].y - pts2[1].y;
float det = a1 * b2 - b1 * a2;
if (fabs(det) > 0.01)
{
float tt = ((pts1[0].x - pts2[0].x) * b2 - (pts1[0].y - pts2[0].y) * a2) / det;
float ss = - ((pts1[0].y - pts2[0].y) * a1 - (pts1[0].x - pts2[0].x) * b1) / det;
if (acceptable (tt) && acceptable (ss))
{
p[0].x = pts1[0].x + tt * (pts1[1].x - pts1[0].x);
p[0].y = pts1[0].y + tt * (pts1[1].y - pts1[0].y);
t1[0] = tt;
t2[0] = ss;
return 1;
}
}
else /* parallel lines */
{
float r = a1 * (pts1[1].y - pts2[0].y) - (pts1[1].x - pts2[0].x) * b1;
float dist = (r * r) / (a1 * a1 + b1 * b1);
float t, s, tt, ss;
if (dist > 0.01)
return 0;
if (pts1[1].x != pts1[0].x)
{
t = (pts2[0].x - pts1[0].x) / (pts1[1].x - pts1[0].x);
s = (pts2[1].x - pts1[0].x) / (pts1[1].x - pts1[0].x);
}
else
{
t = (pts2[0].y - pts1[0].y) / (pts1[1].y - pts1[0].y);
s = (pts2[1].y - pts1[0].y) / (pts1[1].y - pts1[0].y);
}
if ((t < 0 && s < 0) || (t > 1 && s > 1))
return 0;
if (acceptable (t))
{
t1[0] = t;
t2[0] = 0;
p[0] = pts2[0];
}
else if (t < 0)
{
if (pts2[1].x != pts2[0].x)
tt = (pts1[0].x - pts2[0].x) / (pts2[1].x - pts2[0].x);
else
tt = (pts1[0].y - pts2[0].y) / (pts2[1].y - pts2[0].y);
t1[0] = 0;
t2[0] = tt;
p[0] = pts1[0];
}
else
{
if (pts2[1].x != pts2[0].x)
tt = (pts1[1].x - pts2[0].x) / (pts2[1].x - pts2[0].x);
else
tt = (pts1[1].y - pts2[0].y) / (pts2[1].y - pts2[0].y);
t1[0] = 1;
t2[0] = tt;
p[0] = pts1[1];
}
if (acceptable (s))
{
if (t2[0] == 1)
return 1;
t1[1] = s;
t2[1] = 1;
p[1] = pts2[1];
}
else if (s < 0)
{
if (t1[0] == 0)
return 1;
if (pts2[1].x != pts2[0].x)
ss = (pts1[0].x - pts2[0].x) / (pts2[1].x - pts2[0].x);
else
ss = (pts1[0].y - pts2[0].y) / (pts2[1].y - pts2[0].y);
t1[1] = 0;
t2[1] = ss;
p[1] = pts1[0];
}
else
{
if (t1[0] == 1)
return 1;
if (pts2[1].x != pts2[0].x)
ss = (pts1[1].x - pts2[0].x) / (pts2[1].x - pts2[0].x);
else
ss = (pts1[1].y - pts2[0].y) / (pts2[1].y - pts2[0].y);
t1[1] = 1;
t2[1] = ss;
p[1] = pts1[1];
}
return 2;
}
return 0;
}
static void
get_tangent (const graphene_point_t *p0,
const graphene_point_t *p1,
graphene_vec2_t *t)
{
graphene_vec2_init (t, p1->x - p0->x, p1->y - p0->y);
graphene_vec2_normalize (t, t);
}
static void
align_points (const graphene_point_t *p,
const graphene_point_t *a,
const graphene_point_t *b,
graphene_point_t *q,
int n)
{
graphene_vec2_t n1;
float angle;
float s, c;
float dist;
get_tangent (a, b, &n1);
angle = - atan2 (graphene_vec2_get_y (&n1), graphene_vec2_get_x (&n1));
sincosf (angle, &s, &c);
dist = sqrtf ((a->x - b->x)*(a->x - b->x) + (a->y - b->y)*(a->y - b->y));
for (int i = 0; i < n; i++)
{
q[i].x = ((p[i].x - a->x) * c - (p[i].y - a->y) * s) / dist;
q[i].y = ((p[i].x - a->x) * s + (p[i].y - a->y) * c) / dist;
}
}
static void
find_point_on_line (const graphene_point_t *p1,
const graphene_point_t *p2,
const graphene_point_t *q,
float *t)
{
if (p2->x != p1->x)
*t = (q->x - p1->x) / (p2->x - p1->x);
else
*t = (q->y - p1->y) / (p2->y - p1->y);
}
static float
cuberoot (float v)
{
if (v < 0)
return -pow (-v, 1.f / 3);
return pow (v, 1.f / 3);
}
/* Solve P = 0 where P is
* P = (1-t)^3*pa + 3*t*(1-t)^2*pb + 3*t^2*(1-t)*pc + t^3*pd
*/
static int
get_cubic_roots (float pa, float pb, float pc, float pd, float roots[3])
{
float a, b, c, d;
float q, q2;
float p, p3;
float discriminant;
float u1, v1, sd;
int n_roots = 0;
d = -pa + 3*pb - 3*pc + pd;
a = 3*pa - 6*pb + 3*pc;
b = -3*pa + 3*pb;
c = pa;
if (fabs (d) < 0.0001)
{
if (fabs (a) < 0.0001)
{
if (fabs (b) < 0.0001)
return 0;
if (acceptable (-c / b))
{
roots[0] = -c / b;
return 1;
}
return 0;
}
q = sqrt (b*b - 4*a*c);
roots[n_roots] = (-b + q) / (2 * a);
if (acceptable (roots[n_roots]))
n_roots++;
roots[n_roots] = (-b - q) / (2 * a);
if (acceptable (roots[n_roots]))
n_roots++;
return n_roots;
}
a /= d;
b /= d;
c /= d;
p = (3*b - a*a)/3;
p3 = p/3;
q = (2*a*a*a - 9*a*b + 27*c)/27;
q2 = q/2;
discriminant = q2*q2 + p3*p3*p3;
if (discriminant < 0)
{
float mp3 = -p/3;
float mp33 = mp3*mp3*mp3;
float r = sqrt (mp33);
float t = -q / (2*r);
float cosphi = t < -1 ? -1 : (t > 1 ? 1 : t);
float phi = acos (cosphi);
float crtr = cuberoot (r);
float t1 = 2*crtr;
roots[n_roots] = t1 * cos (phi/3) - a/3;
if (acceptable (roots[n_roots]))
n_roots++;
roots[n_roots] = t1 * cos ((phi + 2*M_PI) / 3) - a/3;
if (acceptable (roots[n_roots]))
n_roots++;
roots[n_roots] = t1 * cos ((phi + 4*M_PI) / 3) - a/3;
if (acceptable (roots[n_roots]))
n_roots++;
return n_roots;
}
if (discriminant == 0)
{
u1 = q2 < 0 ? cuberoot (-q2) : -cuberoot (q2);
roots[n_roots] = 2*u1 - a/3;
if (acceptable (roots[n_roots]))
n_roots++;
roots[n_roots] = -u1 - a/3;
if (acceptable (roots[n_roots]))
n_roots++;
return n_roots;
}
sd = sqrt (discriminant);
u1 = cuberoot (sd - q2);
v1 = cuberoot (sd + q2);
roots[n_roots] = u1 - v1 - a/3;
if (acceptable (roots[n_roots]))
n_roots++;
return n_roots;
}
static int
line_curve_intersect (const GskCurve *curve1,
const GskCurve *curve2,
float *t1,
float *t2,
graphene_point_t *p,
int n)
{
const graphene_point_t *a = &curve1->line.points[0];
const graphene_point_t *b = &curve1->line.points[1];
graphene_point_t pts[4];
float t[3];
int m, i, j;
/* Rotate things to place curve1 on the x axis,
* then solve curve2 for y == 0.
*/
align_points (curve2->curve.points, a, b, pts, 4);
m = get_cubic_roots (pts[0].y, pts[1].y, pts[2].y, pts[3].y, t);
j = 0;
for (i = 0; i < m; i++)
{
t2[j] = t[i];
gsk_curve_get_point (curve2, t2[j], &p[j]);
find_point_on_line (a, b, &p[j], &t1[j]);
if (acceptable (t1[j]))
j++;
if (j == n)
break;
}
return j;
}
static void
curve_intersect_recurse (const GskCurve *curve1,
const GskCurve *curve2,
float t1l,
float t1r,
float t2l,
float t2r,
float *t1,
float *t2,
graphene_point_t *p,
int n,
int *pos,
float tolerance)
{
GskCurve p11, p12, p21, p22;
GskBoundingBox b1, b2;
float d1, d2;
if (*pos == n)
return;
gsk_curve_get_tight_bounds (curve1, &b1);
gsk_curve_get_tight_bounds (curve2, &b2);
if (!gsk_bounding_box_intersection (&b1, &b2, NULL))
return;
d1 = (t1r - t1l) / 2;
d2 = (t2r - t2l) / 2;
if (b1.max.x - b1.min.x < tolerance && b1.max.y - b1.min.y < tolerance &&
b2.max.x - b2.min.x < tolerance && b2.max.y - b2.min.y < tolerance)
{
graphene_point_t c;
t1[*pos] = t1l + d1;
t2[*pos] = t2l + d2;
gsk_curve_get_point (curve1, 0.5, &c);
for (int i = 0; i < *pos; i++)
{
if (graphene_point_near (&c, &p[i], 0.1))
return;
}
p[*pos] = c;
(*pos)++;
return;
}
gsk_curve_split (curve1, 0.5, &p11, &p12);
gsk_curve_split (curve2, 0.5, &p21, &p22);
curve_intersect_recurse (&p11, &p21, t1l, t1l + d1, t2l, t2l + d2, t1, t2, p, n, pos, tolerance);
curve_intersect_recurse (&p11, &p22, t1l, t1l + d1, t2l + d2, t2r, t1, t2, p, n, pos, tolerance);
curve_intersect_recurse (&p12, &p21, t1l + d1, t1r, t2l, t2l + d2, t1, t2, p, n, pos, tolerance);
curve_intersect_recurse (&p12, &p22, t1l + d1, t1r, t2l + d2, t2r, t1, t2, p, n, pos, tolerance);
}
static int
curve_intersect (const GskCurve *curve1,
const GskCurve *curve2,
float *t1,
float *t2,
graphene_point_t *p,
int n,
float tolerance)
{
int pos = 0;
curve_intersect_recurse (curve1, curve2, 0, 1, 0, 1, t1, t2, p, n, &pos, tolerance);
return pos;
}
static void
get_bounds (const GskCurve *curve,
float tl,
float tr,
GskBoundingBox *bounds)
{
GskCurve c;
gsk_curve_segment (curve, tl, tr, &c);
gsk_curve_get_tight_bounds (&c, bounds);
}
static void
general_intersect_recurse (const GskCurve *curve1,
const GskCurve *curve2,
float t1l,
float t1r,
float t2l,
float t2r,
float *t1,
float *t2,
graphene_point_t *p,
int n,
int *pos,
float tolerance)
{
GskBoundingBox b1, b2;
float d1, d2;
if (*pos == n)
return;
get_bounds (curve1, t1l, t1r, &b1);
get_bounds (curve2, t2l, t2r, &b2);
if (!gsk_bounding_box_intersection (&b1, &b2, NULL))
return;
d1 = (t1r - t1l) / 2;
d2 = (t2r - t2l) / 2;
if (b1.max.x - b1.min.x < tolerance && b1.max.y - b1.min.y < tolerance &&
b2.max.x - b2.min.x < tolerance && b2.max.y - b2.min.y < tolerance)
{
graphene_point_t c;
t1[*pos] = t1l + d1;
t2[*pos] = t2l + d2;
gsk_curve_get_point (curve1, t1[*pos], &c);
for (int i = 0; i < *pos; i++)
{
if (graphene_point_near (&c, &p[i], tolerance))
return;
}
p[*pos] = c;
(*pos)++;
return;
}
/* Note that in the conic case, we cannot just split the curves and
* pass the two halves down, since splitting changes the parametrization,
* and we need the t's to be valid parameters wrt to the original curve.
*
* So, instead, we determine the bounding boxes above by always starting
* from the original curve. That is a bit less efficient, but also works
* for conics.
*/
general_intersect_recurse (curve1, curve2, t1l, t1l + d1, t2l, t2l + d2, t1, t2, p, n, pos, tolerance);
general_intersect_recurse (curve1, curve2, t1l, t1l + d1, t2l + d2, t2r, t1, t2, p, n, pos, tolerance);
general_intersect_recurse (curve1, curve2, t1l + d1, t1r, t2l, t2l + d2, t1, t2, p, n, pos, tolerance);
general_intersect_recurse (curve1, curve2, t1l + d1, t1r, t2l + d2, t2r, t1, t2, p, n, pos, tolerance);
}
static int
general_intersect (const GskCurve *curve1,
const GskCurve *curve2,
float *t1,
float *t2,
graphene_point_t *p,
int n,
float tolerance)
{
int pos = 0;
general_intersect_recurse (curve1, curve2, 0, 1, 0, 1, t1, t2, p, n, &pos, tolerance);
return pos;
}
static int
curve_self_intersect (const GskCurve *curve,
float *t1,
float *t2,
graphene_point_t *p,
int n)
{
float tt[3], ss[3], s;
graphene_point_t pp[3];
int m;
GskCurve cs, ce;
if (curve->op != GSK_PATH_CURVE)
return 0;
s = 0.5;
m = gsk_curve_get_curvature_points (curve, tt);
for (int i = 0; i < m; i++)
{
if (gsk_curve_get_curvature (curve, tt[i], NULL) == 0)
{
s = tt[i];
break;
}
}
gsk_curve_split (curve, s, &cs, &ce);
m = curve_intersect (&cs, &ce, tt, ss, pp, 3, 0.001);
if (m > 1)
{
/* One of the (at most 2) intersections we found
* must be the common point where we split the curve.
* It will have a t value of 1 and an s value of 0.
*/
if (fabs (tt[0] - 1) > 1e-3)
{
t1[0] = t2[0] = tt[0] * s;
p[0] = pp[0];
}
else if (fabs (tt[1] - 1) > 1e-3)
{
t1[0] = t2[0] = tt[1] * s;
p[0] = pp[1];
}
if (fabs (ss[0]) > 1e-3)
{
t1[1] = t2[1] = s + ss[0] * (1 - s);
p[1] = pp[0];
}
else if (fabs (ss[1]) > 1e-3)
{
t1[1] = t2[1] = s + ss[1] * (1 - s);
p[1] = pp[1];
}
return 2;
}
return 0;
}
/* Place intersections between the curves in p, and their Bezier positions
* in t1 and t2, up to n. Return the number of intersections found.
*
* Note that two cubic Beziers can have up to 9 intersections.
*/
int
gsk_curve_intersect (const GskCurve *curve1,
const GskCurve *curve2,
float *t1,
float *t2,
graphene_point_t *p,
int n)
{
GskPathOperation op1 = curve1->op;
GskPathOperation op2 = curve2->op;
if (op1 == GSK_PATH_CLOSE)
op1 = GSK_PATH_LINE;
if (op2 == GSK_PATH_CLOSE)
op2 = GSK_PATH_LINE;
if (memcmp (curve1, curve2, sizeof (GskCurve)) == 0)
return curve_self_intersect (curve1, t1, t2, p, n);
/* We special-case line-line and line-curve intersections,
* since we can solve them directly.
* Everything else is done via bisection.
*/
if (op1 == GSK_PATH_LINE && op2 == GSK_PATH_LINE)
return line_intersect (curve1, curve2, t1, t2, p, n);
else if (op1 == GSK_PATH_LINE && op2 == GSK_PATH_CURVE)
return line_curve_intersect (curve1, curve2, t1, t2, p, n);
else if (op1 == GSK_PATH_CURVE && op2 == GSK_PATH_LINE)
return line_curve_intersect (curve2, curve1, t2, t1, p, n);
else if (op1 == GSK_PATH_CURVE && op2 == GSK_PATH_CURVE)
return curve_intersect (curve1, curve2, t1, t2, p, n, 0.001);
else
return general_intersect (curve1, curve2, t1, t2, p, n, 0.001);
}
+164
View File
@@ -0,0 +1,164 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_CURVE_PRIVATE_H__
#define __GSK_CURVE_PRIVATE_H__
#include "gskpathopprivate.h"
#include "gskboundingboxprivate.h"
G_BEGIN_DECLS
typedef gpointer gskpathop;
typedef union _GskCurve GskCurve;
typedef struct _GskLineCurve GskLineCurve;
typedef struct _GskCurveCurve GskCurveCurve;
typedef struct _GskConicCurve GskConicCurve;
struct _GskLineCurve
{
GskPathOperation op;
gboolean padding;
graphene_point_t points[2];
};
struct _GskCurveCurve
{
GskPathOperation op;
gboolean has_coefficients;
graphene_point_t points[4];
graphene_point_t coeffs[4];
};
struct _GskConicCurve
{
GskPathOperation op;
gboolean has_coefficients;
/* points[0], points[1], points[3] are the control points,
* points[2].x is the weight
*/
graphene_point_t points[4];
graphene_point_t num[3];
graphene_point_t denom[3];
};
union _GskCurve
{
GskPathOperation op;
GskLineCurve line;
GskCurveCurve curve;
GskConicCurve conic;
};
typedef gboolean (* GskCurveAddLineFunc) (const graphene_point_t *from,
const graphene_point_t *to,
float from_progress,
float to_progress,
gpointer user_data);
typedef gboolean (* GskCurveAddCurveFunc) (const graphene_point_t points[4],
gpointer user_data);
void gsk_curve_init (GskCurve *curve,
gskpathop op);
void gsk_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight);
void gsk_curve_get_point (const GskCurve *curve,
float progress,
graphene_point_t *pos);
void gsk_curve_get_tangent (const GskCurve *curve,
float progress,
graphene_vec2_t *tangent);
void gsk_curve_get_normal (const GskCurve *curve,
float progress,
graphene_vec2_t *normal);
void gsk_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end);
void gsk_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment);
gboolean gsk_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data);
gboolean gsk_curve_decompose_curve (const GskCurve *curve,
float tolerance,
GskCurveAddCurveFunc add_curve_func,
gpointer user_data);
gskpathop gsk_curve_pathop (const GskCurve *curve);
#define gsk_curve_builder_to(curve, builder) gsk_path_builder_pathop_to ((builder), gsk_curve_pathop (curve))
const graphene_point_t *gsk_curve_get_start_point (const GskCurve *curve);
const graphene_point_t *gsk_curve_get_end_point (const GskCurve *curve);
void gsk_curve_get_start_tangent (const GskCurve *curve,
graphene_vec2_t *tangent);
void gsk_curve_get_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent);
void gsk_curve_get_bounds (const GskCurve *curve,
GskBoundingBox *bounds);
void gsk_curve_get_tight_bounds (const GskCurve *curve,
GskBoundingBox *bounds);
int gsk_curve_intersect (const GskCurve *curve1,
const GskCurve *curve2,
float *t1,
float *t2,
graphene_point_t *p,
int n);
void gsk_curve_offset (const GskCurve *curve,
float distance,
GskCurve *offset_curve);
void gsk_curve_reverse (const GskCurve *curve,
GskCurve *reverse);
float gsk_curve_get_curvature (const GskCurve *curve,
float t,
graphene_point_t *center);
int gsk_curve_get_curvature_points (const GskCurve *curve,
float t[3]);
int gsk_curve_get_cusps (const GskCurve *curve,
float t[2]);
void gsk_curve_print (const GskCurve *curve);
G_END_DECLS
#endif /* __GSK_CURVE_PRIVATE_H__ */
+1
View File
@@ -10,6 +10,7 @@ static const GdkDebugKey gsk_debug_keys[] = {
{ "surface", GSK_DEBUG_SURFACE, "Information about surfaces" },
{ "fallback", GSK_DEBUG_FALLBACK, "Information about fallbacks" },
{ "glyphcache", GSK_DEBUG_GLYPH_CACHE, "Information about glyph caching" },
{ "paths", GSK_DEBUG_PATHS, "Information about path processing" },
{ "geometry", GSK_DEBUG_GEOMETRY, "Show borders (when using cairo)" },
{ "full-redraw", GSK_DEBUG_FULL_REDRAW, "Force full redraws" },
{ "sync", GSK_DEBUG_SYNC, "Sync after each frame" },
+1
View File
@@ -14,6 +14,7 @@ typedef enum {
GSK_DEBUG_VULKAN = 1 << 5,
GSK_DEBUG_FALLBACK = 1 << 6,
GSK_DEBUG_GLYPH_CACHE = 1 << 7,
GSK_DEBUG_PATHS = 1 << 8,
/* flags below may affect behavior */
GSK_DEBUG_GEOMETRY = 1 << 9,
GSK_DEBUG_FULL_REDRAW = 1 << 10,
+5 -14
View File
@@ -356,8 +356,6 @@ compare (gconstpointer *elem1,
const GskDiffSettings *settings,
gpointer data)
{
GskDiffResult res;
/*
* Shrink the box by walking through each diagonal snake (SW and NE).
*/
@@ -366,9 +364,7 @@ compare (gconstpointer *elem1,
if (settings->compare_func (elem1[off1], elem2[off2], data) != 0)
break;
res = settings->keep_func (elem1[off1], elem2[off2], data);
if (res != GSK_DIFF_OK)
return res;
settings->keep_func (elem1[off1], elem2[off2], data);
}
for (; off1 < lim1 && off2 < lim2; lim1--, lim2--)
@@ -376,9 +372,7 @@ compare (gconstpointer *elem1,
if (settings->compare_func (elem1[lim1 - 1], elem2[lim2 - 1], data) != 0)
break;
res = settings->keep_func (elem1[lim1 - 1], elem2[lim2 - 1], data);
if (res != GSK_DIFF_OK)
return res;
settings->keep_func (elem1[lim1 - 1], elem2[lim2 - 1], data);
}
/*
@@ -389,23 +383,20 @@ compare (gconstpointer *elem1,
{
for (; off2 < lim2; off2++)
{
res = settings->insert_func (elem2[off2], off2, data);
if (res != GSK_DIFF_OK)
return res;
settings->insert_func (elem2[off2], off2, data);
}
}
else if (off2 == lim2)
{
for (; off1 < lim1; off1++)
{
res = settings->delete_func (elem1[off1], off1, data);
if (res != GSK_DIFF_OK)
return res;
settings->delete_func (elem1[off1], off1, data);
}
}
else
{
SplitResult spl = { 0, };
GskDiffResult res;
/*
* Divide ...
+3 -3
View File
@@ -29,9 +29,9 @@ typedef enum {
GSK_DIFF_ABORTED,
} GskDiffResult;
typedef GskDiffResult (* GskKeepFunc) (gconstpointer elem1, gconstpointer elem2, gpointer data);
typedef GskDiffResult (* GskDeleteFunc) (gconstpointer elem, gsize idx, gpointer data);
typedef GskDiffResult (* GskInsertFunc) (gconstpointer elem, gsize idx, gpointer data);
typedef void (* GskKeepFunc) (gconstpointer elem1, gconstpointer elem2, gpointer data);
typedef void (* GskDeleteFunc) (gconstpointer elem, gsize idx, gpointer data);
typedef void (* GskInsertFunc) (gconstpointer elem, gsize idx, gpointer data);
typedef struct _GskDiffSettings GskDiffSettings;
+105 -3
View File
@@ -1,5 +1,5 @@
/* GSK - The GTK Scene Kit
* Copyright 2016 Endless
* Copyright 2016 Endless
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -50,7 +50,7 @@
* @GSK_BLUR_NODE: A node that applies a blur
* @GSK_DEBUG_NODE: Debug information that does not affect the rendering
* @GSK_GL_SHADER_NODE: A node that uses OpenGL fragment shaders to render
* The type of a node determines what the node is rendering.
*/
typedef enum {
@@ -73,6 +73,8 @@ typedef enum {
GSK_REPEAT_NODE,
GSK_CLIP_NODE,
GSK_ROUNDED_CLIP_NODE,
GSK_FILL_NODE,
GSK_STROKE_NODE,
GSK_SHADOW_NODE,
GSK_BLEND_NODE,
GSK_CROSS_FADE_NODE,
@@ -167,6 +169,107 @@ typedef enum {
GSK_CORNER_BOTTOM_LEFT
} GskCorner;
/**
* GskFillRule:
* @GSK_FILL_RULE_WINDING: If the path crosses the ray from
* left-to-right, counts +1. If the path crosses the ray
* from right to left, counts -1. (Left and right are determined
* from the perspective of looking along the ray from the starting
* point.) If the total count is non-zero, the point will be filled.
* @GSK_FILL_RULE_EVEN_ODD: Counts the total number of
* intersections, without regard to the orientation of the contour. If
* the total number of intersections is odd, the point will be
* filled.
*
* #GskFillRule is used to select how paths are filled, for example in
* gsk_fill_node_new(). Whether or not a point is included in the fill is
* determined by taking a ray from that point to infinity and looking
* at intersections with the path. The ray can be in any direction,
* as long as it doesn't pass through the end point of a segment
* or have a tricky intersection such as intersecting tangent to the path.
* (Note that filling is not actually implemented in this way. This
* is just a description of the rule that is applied.)
*
* New entries may be added in future versions.
**/
typedef enum {
GSK_FILL_RULE_WINDING,
GSK_FILL_RULE_EVEN_ODD
} GskFillRule;
/**
* GskLineCap:
* @GSK_LINE_CAP_BUTT: Start and stop the line exactly at the start
* and end point
* @GSK_LINE_CAP_ROUND: Use a round ending, the center of the circle
* is the start or end point.
* @GSK_LINE_CAP_SQUARE: use squared ending, the center of the square
* is the start or end point.
*
* Specifies how to render the start and end points of contours or
* dashes when stroking.
*
* The default line cap style is @GSK_LINE_CAP_BUTT.
*/
typedef enum {
GSK_LINE_CAP_BUTT,
GSK_LINE_CAP_ROUND,
GSK_LINE_CAP_SQUARE
} GskLineCap;
/**
* GskLineJoin:
* @GSK_LINE_JOIN_MITER: Use a sharp, angled corner
* @GSK_LINE_JOIN_MITER_CLIP: Use a sharp, angled corner, at a distance
* @GSK_LINE_JOIN_ROUND: Use a round join, the center of the circle is
* the joint point
* @GSK_LINE_JOIN_BEVEL: use a cut-off join, the join is cut off at half
* the line width from the joint point
* @GSK_LINE_JOIN_ARCS: Use a sharp angled corner made from circles
*
* Specifies how to render the junction of two lines when stroking.
*
* See [method@Gsk.Stroke.set_miter_limit] for details on the difference
* between @GSK_LINE_JOIN_MITER and @GSK_LINE_JOIN_MITER_CLIP.
*
* The default line join style is @GSK_LINE_JOIN_MITER.
**/
typedef enum {
GSK_LINE_JOIN_MITER,
GSK_LINE_JOIN_MITER_CLIP,
GSK_LINE_JOIN_ROUND,
GSK_LINE_JOIN_BEVEL,
GSK_LINE_JOIN_ARCS
} GskLineJoin;
/**
* GskPathOperation:
* @GSK_PATH_MOVE: A move-to operation, with 1 point describing the
* target point.
* @GSK_PATH_LINE: A line-to operation, with 2 points describing the
* start and end point of a straight line.
* @GSK_PATH_CLOSE: A close operation ending the current contour with
* a line back to the starting point. Two points describe the start
* and end of the line.
* @GSK_PATH_CURVE: A curve-to operation describing a cubic Bézier curve
* with 4 points describing the start point, the two control points
* and the end point of the curve.
* @GSK_PATH_CONIC: A weighted quadratic Bézier curve with 3 points
* describing the start point, control point and end point of the
* curve. A weight for the curve will be passed, too.
*
* Path operations can be used to approximate a #GskPath.
*
* More values may be added in the future.
**/
typedef enum {
GSK_PATH_MOVE,
GSK_PATH_CLOSE,
GSK_PATH_LINE,
GSK_PATH_CURVE,
GSK_PATH_CONIC,
} GskPathOperation;
/**
* GskSerializationError:
* @GSK_SERIALIZATION_UNSUPPORTED_FORMAT: The format can not be identified
@@ -251,5 +354,4 @@ typedef enum
GSK_GL_UNIFORM_TYPE_VEC4,
} GskGLUniformType;
#endif /* __GSK_TYPES_H__ */
+1516
View File
File diff suppressed because it is too large Load Diff
+154
View File
@@ -0,0 +1,154 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_H__
#define __GSK_PATH_H__
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gsktypes.h>
G_BEGIN_DECLS
/**
* GskPathForeachFlags:
* @GSK_PATH_FOREACH_ALLOW_CURVE: Allow emission of `GSK_PATH_CURVE` operations.
* @GSK_PATH_FOREACH_ALLOW_CONIC: Allow emission of `GSK_PATH_CONIC` operations.
*
* Flags that can be passed to [method@Gsk.Path.foreach] to enable additional
* features.
*
* By default, [method@Gsk.Path.foreach] will only emit a path with all
* operations flattened to straight lines to allow for maximum compatibility.
* The only operations emitted will be `GSK_PATH_MOVE`, `GSK_PATH_LINE` and
* `GSK_PATH_CLOSE`.
*/
typedef enum
{
GSK_PATH_FOREACH_ALLOW_CURVE = (1 << 0),
GSK_PATH_FOREACH_ALLOW_CONIC = (1 << 1)
} GskPathForeachFlags;
/**
* GskPathForeachFunc:
* @op: The operation to perform
* @pts: The points of the operation
* @n_pts: The number of points
* @weight: The weight for conic curves, or unused if not a conic curve.
* @user_data: The user data provided with the function
*
* Prototype of the callback to iterate throught the operations of
* a path.
*
* Returns: %TRUE to continue evaluating the path, %FALSE to
* immediately abort and not call the function again.
*/
typedef gboolean (* GskPathForeachFunc) (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data);
#define GSK_TYPE_PATH (gsk_path_get_type ())
GDK_AVAILABLE_IN_ALL
GType gsk_path_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_new_from_cairo (const cairo_path_t *path);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_ref (GskPath *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_unref (GskPath *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_print (GskPath *self,
GString *string);
GDK_AVAILABLE_IN_ALL
char * gsk_path_to_string (GskPath *self);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_parse (const char *string);
GDK_AVAILABLE_IN_ALL
void gsk_path_to_cairo (GskPath *self,
cairo_t *cr);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_is_empty (GskPath *self);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_get_bounds (GskPath *self,
graphene_rect_t *bounds);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_get_stroke_bounds (GskPath *path,
const GskStroke *stroke,
graphene_rect_t *bounds);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_is_convex (GskPath *self);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_foreach (GskPath *self,
GskPathForeachFlags flags,
GskPathForeachFunc func,
gpointer user_data);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_reverse (GskPath *self);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_transform (GskPath *self,
GskTransform *transform);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_stroke (GskPath *self,
GskStroke *stroke);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_offset (GskPath *self,
float distance,
GskLineJoin line_join,
float miter_limit);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_union (GskPath *first,
GskPath *second);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_intersection (GskPath *first,
GskPath *second);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_difference (GskPath *first,
GskPath *second);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_symmetric_difference (GskPath *first,
GskPath *second);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_simplify (GskPath *self);
G_END_DECLS
#endif /* __GSK_PATH_H__ */
+964
View File
@@ -0,0 +1,964 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskpathbuilder.h"
#include "gskpathprivate.h"
/**
* GskPathBuilder:
*
* A `GskPathBuilder` is an auxiliary object that is used to
* create new `GskPath` objects.
*
* A path is constructed like this:
*
* ```
* GskPath *
* construct_path (void)
* {
* GskPathBuilder *builder;
*
* builder = gsk_path_builder_new ();
*
* // add contours to the path here
*
* return gsk_path_builder_free_to_path (builder);
* }
* ```
*
* Adding contours to the path can be done in two ways.
* The easiest option is to use the `gsk_path_builder_add_*` group
* of functions that add predefined contours to the current path,
* either common shapes like [method@Gsk.PathBuilder.add_circle]
* or by adding from other paths like [method@Gsk.PathBuilder.add_path].
*
* The other option is to define each line and curve manually with
* the `gsk_path_builder_*_to` group of functions. You start with
* a call to [method@Gsk.PathBuilder.move_to] to set the starting point
* and then use multiple calls to any of the drawing functions to
* move the pen along the plane. Once you are done, you can call
* [method@Gsk.PathBuilder.close] to close the path by connecting it
* back with a line to the starting point.
* This is similar for how paths are drawn in Cairo.
*/
struct _GskPathBuilder
{
int ref_count;
GSList *contours; /* (reverse) list of already recorded contours */
GskPathFlags flags; /* flags for the current path */
graphene_point_t current_point; /* the point all drawing ops start from */
GArray *ops; /* operations for current contour - size == 0 means no current contour */
GArray *points; /* points for the operations */
};
G_DEFINE_BOXED_TYPE (GskPathBuilder,
gsk_path_builder,
gsk_path_builder_ref,
gsk_path_builder_unref)
/**
* gsk_path_builder_new:
*
* Create a new `GskPathBuilder` object. The resulting builder
* would create an empty `GskPath`. Use addition functions to add
* types to it.
*
* Returns: a new `GskPathBuilder`
**/
GskPathBuilder *
gsk_path_builder_new (void)
{
GskPathBuilder *builder;
builder = g_slice_new0 (GskPathBuilder);
builder->ref_count = 1;
builder->ops = g_array_new (FALSE, FALSE, sizeof (gskpathop));
builder->points = g_array_new (FALSE, FALSE, sizeof (graphene_point_t));
/* Be explicit here */
builder->current_point = GRAPHENE_POINT_INIT (0, 0);
return builder;
}
/**
* gsk_path_builder_ref:
* @builder: a `GskPathBuilder`
*
* Acquires a reference on the given @builder.
*
* This function is intended primarily for bindings. `GskPathBuilder` objects
* should not be kept around.
*
* Returns: (transfer none): the given `GskPathBuilder` with
* its reference count increased
*/
GskPathBuilder *
gsk_path_builder_ref (GskPathBuilder *builder)
{
g_return_val_if_fail (builder != NULL, NULL);
g_return_val_if_fail (builder->ref_count > 0, NULL);
builder->ref_count += 1;
return builder;
}
/* We're cheating here. Out pathops are relative to the NULL pointer,
* so that we can not care about the points GArray reallocating itself
* until we create the contour.
* This does however mean that we need to not use gsk_pathop_get_points()
* without offsetting the returned pointer.
*/
static inline gskpathop
gsk_pathop_encode_index (GskPathOperation op,
gsize index)
{
return gsk_pathop_encode (op, ((graphene_point_t *) NULL) + index);
}
static void
gsk_path_builder_ensure_current (GskPathBuilder *builder)
{
if (builder->ops->len != 0)
return;
builder->flags = GSK_PATH_FLAT;
g_array_append_vals (builder->ops, (gskpathop[1]) { gsk_pathop_encode_index (GSK_PATH_MOVE, 0) }, 1);
g_array_append_val (builder->points, builder->current_point);
}
static void
gsk_path_builder_append_current (GskPathBuilder *builder,
GskPathOperation op,
gsize n_points,
const graphene_point_t *points)
{
gsk_path_builder_ensure_current (builder);
g_array_append_vals (builder->ops, (gskpathop[1]) { gsk_pathop_encode_index (op, builder->points->len - 1) }, 1);
g_array_append_vals (builder->points, points, n_points);
builder->current_point = points[n_points - 1];
}
static void
gsk_path_builder_end_current (GskPathBuilder *builder)
{
GskContour *contour;
if (builder->ops->len == 0)
return;
contour = gsk_standard_contour_new (builder->flags,
(graphene_point_t *) builder->points->data,
builder->points->len,
(gskpathop *) builder->ops->data,
builder->ops->len,
(graphene_point_t *) builder->points->data - (graphene_point_t *) NULL);
g_array_set_size (builder->ops, 0);
g_array_set_size (builder->points, 0);
/* do this at the end to avoid inflooping when add_contour calls back here */
gsk_path_builder_add_contour (builder, contour);
}
static void
gsk_path_builder_clear (GskPathBuilder *builder)
{
gsk_path_builder_end_current (builder);
g_slist_free_full (builder->contours, g_free);
builder->contours = NULL;
}
/**
* gsk_path_builder_unref:
* @builder: a `GskPathBuilder`
*
* Releases a reference on the given @builder.
*/
void
gsk_path_builder_unref (GskPathBuilder *builder)
{
g_return_if_fail (builder != NULL);
g_return_if_fail (builder->ref_count > 0);
builder->ref_count -= 1;
if (builder->ref_count > 0)
return;
gsk_path_builder_clear (builder);
g_array_unref (builder->ops);
g_array_unref (builder->points);
g_slice_free (GskPathBuilder, builder);
}
/**
* gsk_path_builder_free_to_path: (skip)
* @builder: a `GskPathBuilder`
*
* Creates a new `GskPath` from the current state of the
* given @builder, and frees the @builder instance.
*
* Returns: (transfer full): the newly created `GskPath`
* with all the contours added to @builder
*/
GskPath *
gsk_path_builder_free_to_path (GskPathBuilder *builder)
{
GskPath *res;
g_return_val_if_fail (builder != NULL, NULL);
res = gsk_path_builder_to_path (builder);
gsk_path_builder_unref (builder);
return res;
}
/**
* gsk_path_builder_to_path:
* @builder: a `GskPathBuilder`
*
* Creates a new `GskPath` from the given @builder.
*
* The given `GskPathBuilder` is reset once this function returns;
* you cannot call this function multiple times on the same @builder instance.
*
* This function is intended primarily for bindings. C code should use
* gsk_path_builder_free_to_path().
*
* Returns: (transfer full): the newly created `GskPath`
* with all the contours added to @builder
*/
GskPath *
gsk_path_builder_to_path (GskPathBuilder *builder)
{
GskPath *path;
g_return_val_if_fail (builder != NULL, NULL);
gsk_path_builder_end_current (builder);
builder->contours = g_slist_reverse (builder->contours);
path = gsk_path_new_from_contours (builder->contours);
gsk_path_builder_clear (builder);
return path;
}
void
gsk_path_builder_add_contour (GskPathBuilder *builder,
GskContour *contour)
{
gsk_path_builder_end_current (builder);
builder->contours = g_slist_prepend (builder->contours, contour);
}
/**
* gsk_path_builder_get_current_point:
* @builder: a #GskPathBuilder
*
* Gets the current point. The current point is used for relative
* drawing commands and updated after every operation.
*
* When @builder is created, the default current point is set to (0, 0).
*
* Returns: (transfer none): The current point
**/
const graphene_point_t *
gsk_path_builder_get_current_point (GskPathBuilder *builder)
{
g_return_val_if_fail (builder != NULL, NULL);
return &builder->current_point;
}
/**
* gsk_path_builder_add_path:
* @builder: a `GskPathBuilder`
* @path: (transfer none): the path to append
*
* Appends all of @path to @builder.
**/
void
gsk_path_builder_add_path (GskPathBuilder *builder,
GskPath *path)
{
gsize i;
g_return_if_fail (builder != NULL);
g_return_if_fail (path != NULL);
for (i = 0; i < gsk_path_get_n_contours (path); i++)
{
const GskContour *contour = gsk_path_get_contour (path, i);
gsk_path_builder_add_contour (builder, gsk_contour_dup (contour));
}
}
/**
* gsk_path_builder_add_rect:
* @builder: A `GskPathBuilder`
* @rect: The rectangle to add to @builder
*
* Adds a path representing the given rectangle.
*
* If the width or height of the rectangle is negative, the start
* point will be on the right or bottom, respectively.
*
* If the the width or height are 0, the path will be a closed
* horizontal or vertical line. If both are 0, it'll be a closed dot.
**/
void
gsk_path_builder_add_rect (GskPathBuilder *builder,
const graphene_rect_t *rect)
{
GskContour *contour;
g_return_if_fail (builder != NULL);
contour = gsk_rect_contour_new (rect);
gsk_path_builder_add_contour (builder, contour);
gsk_contour_get_start_end (contour, NULL, &builder->current_point);
}
/**
* gsk_path_builder_add_rounded_rect:
* @builder: a `GskPathBuilder`
* @rect: the rounded rect
*
* Adds @rect as a new contour to the path built in @builder.
**/
void
gsk_path_builder_add_rounded_rect (GskPathBuilder *builder,
const GskRoundedRect *rect)
{
const float weight = sqrt(0.5f);
g_return_if_fail (builder != NULL);
g_return_if_fail (rect != NULL);
gsk_path_builder_move_to (builder,
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->bounds.origin.y);
/* top */
gsk_path_builder_line_to (builder,
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_TOP_RIGHT].width,
rect->bounds.origin.y);
/* topright corner */
gsk_path_builder_conic_to (builder,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height,
weight);
/* right */
gsk_path_builder_line_to (builder,
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_conic_to (builder,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->bounds.size.height,
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
rect->bounds.origin.y + rect->bounds.size.height,
weight);
/* bottom */
gsk_path_builder_line_to (builder,
rect->bounds.origin.x + rect->corner[GSK_CORNER_BOTTOM_LEFT].width,
rect->bounds.origin.y + rect->bounds.size.height);
/* bottomleft corner */
gsk_path_builder_conic_to (builder,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->bounds.size.height,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height,
weight);
/* left */
gsk_path_builder_line_to (builder,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_LEFT].height);
/* topleft corner */
gsk_path_builder_conic_to (builder,
rect->bounds.origin.x,
rect->bounds.origin.y,
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->bounds.origin.y,
weight);
/* done */
gsk_path_builder_close (builder);
}
/**
* gsk_path_builder_add_circle:
* @builder: a `GskPathBuilder`
* @center: the center of the circle
* @radius: the radius of the circle
*
* Adds a circle with the @center and @radius.
**/
void
gsk_path_builder_add_circle (GskPathBuilder *builder,
const graphene_point_t *center,
float radius)
{
GskContour *contour;
g_return_if_fail (builder != NULL);
g_return_if_fail (center != NULL);
g_return_if_fail (radius > 0);
contour = gsk_circle_contour_new (center, radius, 0, 360);
gsk_path_builder_add_contour (builder, contour);
}
/**
* gsk_path_builder_add_ellipse:
* @builder: a `GskPathBuilder`
* @center: the center point of the ellipse
* @radius: the radius of the ellipse in x/y direction
*
* Adds an ellipse with the given @center and the @radius in
* x/y direction.
**/
void
gsk_path_builder_add_ellipse (GskPathBuilder *builder,
const graphene_point_t *center,
const graphene_size_t *radius)
{
const float weight = sqrt(0.5f);
graphene_point_t pts[8];
g_return_if_fail (builder != NULL);
g_return_if_fail (center != NULL);
g_return_if_fail (radius != NULL);
pts[0] = GRAPHENE_POINT_INIT (center->x + radius->width / 2,
center->y);
pts[1] = GRAPHENE_POINT_INIT (center->x + radius->width / 2,
center->y + radius->height / 2);
pts[2] = GRAPHENE_POINT_INIT (center->x,
center->y + radius->height / 2);
pts[3] = GRAPHENE_POINT_INIT (center->x - radius->width / 2,
center->y + radius->height / 2);
pts[4] = GRAPHENE_POINT_INIT (center->x - radius->width / 2,
center->y);
pts[5] = GRAPHENE_POINT_INIT (center->x - radius->width / 2,
center->y - radius->height / 2);
pts[6] = GRAPHENE_POINT_INIT (center->x,
center->y - radius->height / 2);
pts[7] = GRAPHENE_POINT_INIT (center->x + radius->width / 2,
center->y - radius->height / 2);
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
gsk_path_builder_conic_to (builder, pts[3].x, pts[3].y, pts[4].x, pts[4].y, weight);
gsk_path_builder_conic_to (builder, pts[5].x, pts[5].y, pts[6].x, pts[6].y, weight);
gsk_path_builder_conic_to (builder, pts[7].x, pts[7].y, pts[0].x, pts[0].y, weight);
gsk_path_builder_close (builder);
}
/**
* gsk_path_builder_move_to:
* @builder: a `GskPathBuilder`
* @x: x coordinate
* @y: y coordinate
*
* Starts a new contour by placing the pen at @x, @y.
*
* If `gsk_path_builder_move_to()` is called twice in succession,
* the first call will result in a contour made up of a single point.
* The second call will start a new contour.
**/
void
gsk_path_builder_move_to (GskPathBuilder *builder,
float x,
float y)
{
g_return_if_fail (builder != NULL);
gsk_path_builder_end_current (builder);
builder->current_point = GRAPHENE_POINT_INIT(x, y);
gsk_path_builder_ensure_current (builder);
}
/**
* gsk_path_builder_rel_move_to:
* @builder: a `GskPathBuilder`
* @x: x offset
* @y: y offset
*
* Starts a new contour by placing the pen at @x, @y relative to the current
* point.
*
* This is the relative version of [method@Gsk.PathBuilder.move_to].
**/
void
gsk_path_builder_rel_move_to (GskPathBuilder *builder,
float x,
float y)
{
g_return_if_fail (builder != NULL);
gsk_path_builder_move_to (builder,
builder->current_point.x + x,
builder->current_point.y + y);
}
/**
* gsk_path_builder_line_to:
* @builder: a `GskPathBuilder`
* @x: x coordinate
* @y: y coordinate
*
* Draws a line from the current point to @x, @y and makes it the new current
* point.
**/
void
gsk_path_builder_line_to (GskPathBuilder *builder,
float x,
float y)
{
g_return_if_fail (builder != NULL);
/* skip the line if it goes to the same point */
if (graphene_point_equal (&builder->current_point,
&GRAPHENE_POINT_INIT (x, y)))
return;
gsk_path_builder_append_current (builder,
GSK_PATH_LINE,
1, (graphene_point_t[1]) {
GRAPHENE_POINT_INIT (x, y)
});
}
/**
* gsk_path_builder_rel_line_to:
* @builder: a `GskPathBuilder`
* @x: x offset
* @y: y offset
*
* Draws a line from the current point to a point offset to it by @x, @y
* and makes it the new current point.
*
* This is the relative version of [method@Gsk.PathBuilder.line_to].
**/
void
gsk_path_builder_rel_line_to (GskPathBuilder *builder,
float x,
float y)
{
g_return_if_fail (builder != NULL);
gsk_path_builder_line_to (builder,
builder->current_point.x + x,
builder->current_point.y + y);
}
/**
* gsk_path_builder_curve_to:
* @builder: a `GskPathBuilder`
* @x1: x coordinate of first control point
* @y1: y coordinate of first control point
* @x2: x coordinate of second control point
* @y2: y coordinate of second control point
* @x3: x coordinate of the end of the curve
* @y3: y coordinate of the end of the curve
*
* Adds a [cubic Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
* from the current point to @x3, @y3 with @x1, @y1 and @x2, @y2 as the control
* points.
*
* After this, @x3, @y3 will be the new current point.
**/
void
gsk_path_builder_curve_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3)
{
g_return_if_fail (builder != NULL);
builder->flags &= ~GSK_PATH_FLAT;
gsk_path_builder_append_current (builder,
GSK_PATH_CURVE,
3, (graphene_point_t[3]) {
GRAPHENE_POINT_INIT (x1, y1),
GRAPHENE_POINT_INIT (x2, y2),
GRAPHENE_POINT_INIT (x3, y3)
});
}
/**
* gsk_path_builder_rel_curve_to:
* @builder: a `GskPathBuilder`
* @x1: x offset of first control point
* @y1: y offset of first control point
* @x2: x offset of second control point
* @y2: y offset of second control point
* @x3: x offset of the end of the curve
* @y3: y offset of the end of the curve
*
* Adds a [cubic Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
* from the current point to @x3, @y3 with @x1, @y1 and @x2, @y2 as the control
* points. All coordinates are given relative to the current point.
*
* This is the relative version of [method@Gsk.PathBuilder.curve_to].
**/
void
gsk_path_builder_rel_curve_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3)
{
g_return_if_fail (builder != NULL);
gsk_path_builder_curve_to (builder,
builder->current_point.x + x1,
builder->current_point.y + y1,
builder->current_point.x + x2,
builder->current_point.y + y2,
builder->current_point.x + x3,
builder->current_point.y + y3);
}
/**
* gsk_path_builder_conic_to:
* @builder: a `GskPathBuilder`
* @x1: x coordinate of control point
* @y1: y coordinate of control point
* @x2: x coordinate of the end of the curve
* @y2: y coordinate of the end of the curve
* @weight: weight of the curve
*
* Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline)
* from the current point to @x2, @y2 with the given
* @weight and @x1, @y1 as the single control point.
*
* Conic curves can be used to draw ellipses and circles.
*
* After this, @x2, @y2 will be the new current point.
**/
void
gsk_path_builder_conic_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float weight)
{
g_return_if_fail (builder != NULL);
g_return_if_fail (weight >= 0);
builder->flags &= ~GSK_PATH_FLAT;
gsk_path_builder_append_current (builder,
GSK_PATH_CONIC,
3, (graphene_point_t[3]) {
GRAPHENE_POINT_INIT (x1, y1),
GRAPHENE_POINT_INIT (weight, 0),
GRAPHENE_POINT_INIT (x2, y2)
});
}
/**
* gsk_path_builder_rel_conic_to:
* @builder: a `GskPathBuilder`
* @x1: x offset of control point
* @y1: y offset of control point
* @x2: x offset of the end of the curve
* @y2: y offset of the end of the curve
* @weight: weight of the curve
*
* Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline)
* from the current point to @x2, @y2 with the given
* @weight and @x1, @y1 as the single control point.
*
* This is the relative version of [method@Gsk.PathBuilder.conic_to].
**/
void
gsk_path_builder_rel_conic_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float weight)
{
g_return_if_fail (builder != NULL);
g_return_if_fail (weight >= 0);
gsk_path_builder_conic_to (builder,
builder->current_point.x + x1,
builder->current_point.y + y1,
builder->current_point.x + x2,
builder->current_point.y + y2,
weight);
}
/**
* gsk_path_builder_close:
* @builder: a `GskPathBuilder`
*
* Ends the current contour with a line back to the start point.
*
* Note that this is different from calling [method@Gsk.PathBuilder.line_to]
* with the start point in that the contour will be closed. A closed
* contour behaves different from an open one when stroking its start
* and end point are considered connected, so they will be joined
* via the line join, and not ended with line caps.
**/
void
gsk_path_builder_close (GskPathBuilder *builder)
{
g_return_if_fail (builder != NULL);
if (builder->ops->len == 0)
return;
builder->flags |= GSK_PATH_CLOSED;
gsk_path_builder_append_current (builder,
GSK_PATH_CLOSE,
1, (graphene_point_t[1]) {
g_array_index (builder->points, graphene_point_t, 0)
});
gsk_path_builder_end_current (builder);
}
static void
arc_segment (GskPathBuilder *builder,
double cx,
double cy,
double rx,
double ry,
double sin_phi,
double cos_phi,
double sin_th0,
double cos_th0,
double sin_th1,
double cos_th1,
double t)
{
double x1, y1, x2, y2, x3, y3;
x1 = rx * (cos_th0 - t * sin_th0);
y1 = ry * (sin_th0 + t * cos_th0);
x3 = rx * cos_th1;
y3 = ry * sin_th1;
x2 = x3 + rx * (t * sin_th1);
y2 = y3 + ry * (-t * cos_th1);
gsk_path_builder_curve_to (builder,
cx + cos_phi * x1 - sin_phi * y1,
cy + sin_phi * x1 + cos_phi * y1,
cx + cos_phi * x2 - sin_phi * y2,
cy + sin_phi * x2 + cos_phi * y2,
cx + cos_phi * x3 - sin_phi * y3,
cy + sin_phi * x3 + cos_phi * y3);
}
void
gsk_path_builder_svg_arc_to (GskPathBuilder *builder,
float rx,
float ry,
float x_axis_rotation,
gboolean large_arc,
gboolean positive_sweep,
float x,
float y)
{
graphene_point_t *current;
double x1, y1, x2, y2;
double phi, sin_phi, cos_phi;
double mid_x, mid_y;
double lambda;
double d;
double k;
double x1_, y1_;
double cx_, cy_;
double cx, cy;
double ux, uy, u_len;
double cos_theta1, theta1;
double vx, vy, v_len;
double dp_uv;
double cos_delta_theta, delta_theta;
int i, n_segs;
double d_theta, theta;
double sin_th0, cos_th0;
double sin_th1, cos_th1;
double th_half;
double t;
if (builder->points->len > 0)
{
current = &g_array_index (builder->points, graphene_point_t, builder->points->len - 1);
x1 = current->x;
y1 = current->y;
}
else
{
x1 = 0;
y1 = 0;
}
x2 = x;
y2 = y;
phi = x_axis_rotation * M_PI / 180.0;
sincos (phi, &sin_phi, &cos_phi);
rx = fabs (rx);
ry = fabs (ry);
mid_x = (x1 - x2) / 2;
mid_y = (y1 - y2) / 2;
x1_ = cos_phi * mid_x + sin_phi * mid_y;
y1_ = - sin_phi * mid_x + cos_phi * mid_y;
lambda = (x1_ / rx) * (x1_ / rx) + (y1_ / ry) * (y1_ / ry);
if (lambda > 1)
{
lambda = sqrt (lambda);
rx *= lambda;
ry *= lambda;
}
d = (rx * y1_) * (rx * y1_) + (ry * x1_) * (ry * x1_);
if (d == 0)
return;
k = sqrt (fabs ((rx * ry) * (rx * ry) / d - 1.0));
if (positive_sweep == large_arc)
k = -k;
cx_ = k * rx * y1_ / ry;
cy_ = -k * ry * x1_ / rx;
cx = cos_phi * cx_ - sin_phi * cy_ + (x1 + x2) / 2;
cy = sin_phi * cx_ + cos_phi * cy_ + (y1 + y2) / 2;
ux = (x1_ - cx_) / rx;
uy = (y1_ - cy_) / ry;
u_len = sqrt (ux * ux + uy * uy);
if (u_len == 0)
return;
cos_theta1 = CLAMP (ux / u_len, -1, 1);
theta1 = acos (cos_theta1);
if (uy < 0)
theta1 = - theta1;
vx = (- x1_ - cx_) / rx;
vy = (- y1_ - cy_) / ry;
v_len = sqrt (vx * vx + vy * vy);
if (v_len == 0)
return;
dp_uv = ux * vx + uy * vy;
cos_delta_theta = CLAMP (dp_uv / (u_len * v_len), -1, 1);
delta_theta = acos (cos_delta_theta);
if (ux * vy - uy * vx < 0)
delta_theta = - delta_theta;
if (positive_sweep && delta_theta < 0)
delta_theta += 2 * M_PI;
else if (!positive_sweep && delta_theta > 0)
delta_theta -= 2 * M_PI;
n_segs = ceil (fabs (delta_theta / (M_PI_2 + 0.001)));
d_theta = delta_theta / n_segs;
theta = theta1;
sincos (theta1, &sin_th1, &cos_th1);
th_half = d_theta / 2;
t = (8.0 / 3.0) * sin (th_half / 2) * sin (th_half / 2) / sin (th_half);
for (i = 0; i < n_segs; i++)
{
theta = theta1;
theta1 = theta + d_theta;
sin_th0 = sin_th1;
cos_th0 = cos_th1;
sincos (theta1, &sin_th1, &cos_th1);
arc_segment (builder,
cx, cy, rx, ry,
sin_phi, cos_phi,
sin_th0, cos_th0,
sin_th1, cos_th1,
t);
}
}
/**
* gsk_path_builder_add_layout:
* @builder: a `GskPathBuilder`
* @layout: the pango layout to add
*
* Adds the outlines for the glyphs in @layout to @builder.
*/
void
gsk_path_builder_add_layout (GskPathBuilder *builder,
PangoLayout *layout)
{
cairo_surface_t *surface;
cairo_t *cr;
cairo_path_t *cairo_path;
GskPath *path;
surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
cr = cairo_create (surface);
pango_cairo_layout_path (cr, layout);
cairo_path = cairo_copy_path_flat (cr);
path = gsk_path_new_from_cairo (cairo_path);
gsk_path_builder_add_path (builder, path);
gsk_path_unref (path);
cairo_path_destroy (cairo_path);
cairo_destroy (cr);
cairo_surface_destroy (surface);
}
+132
View File
@@ -0,0 +1,132 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_BUILDER_H__
#define __GSK_PATH_BUILDER_H__
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gskroundedrect.h>
#include <gsk/gsktypes.h>
G_BEGIN_DECLS
#define GSK_TYPE_PATH_BUILDER (gsk_path_builder_get_type ())
GDK_AVAILABLE_IN_ALL
GType gsk_path_builder_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskPathBuilder * gsk_path_builder_new (void);
GDK_AVAILABLE_IN_ALL
GskPathBuilder * gsk_path_builder_ref (GskPathBuilder *builder);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_unref (GskPathBuilder *builder);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_builder_free_to_path (GskPathBuilder *builder) G_GNUC_WARN_UNUSED_RESULT;
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_builder_to_path (GskPathBuilder *builder) G_GNUC_WARN_UNUSED_RESULT;
GDK_AVAILABLE_IN_ALL
const graphene_point_t *gsk_path_builder_get_current_point (GskPathBuilder *builder);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_path (GskPathBuilder *builder,
GskPath *path);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_rect (GskPathBuilder *builder,
const graphene_rect_t *rect);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_rounded_rect (GskPathBuilder *builder,
const GskRoundedRect *rect);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_circle (GskPathBuilder *builder,
const graphene_point_t *center,
float radius);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_ellipse (GskPathBuilder *builder,
const graphene_point_t *center,
const graphene_size_t *radius);
/* next function implemented in gskpathmeasure.c */
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_segment (GskPathBuilder *builder,
GskPathMeasure *measure,
float start,
float end);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_move_to (GskPathBuilder *builder,
float x,
float y);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_rel_move_to (GskPathBuilder *builder,
float x,
float y);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_line_to (GskPathBuilder *builder,
float x,
float y);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_rel_line_to (GskPathBuilder *builder,
float x,
float y);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_curve_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_rel_curve_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_conic_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float weight);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_rel_conic_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float weight);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_close (GskPathBuilder *builder);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_layout (GskPathBuilder *builder,
PangoLayout *layout);
G_END_DECLS
#endif /* __GSK_PATH_BUILDER_H__ */
+291
View File
@@ -0,0 +1,291 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskpathdashprivate.h"
#include "gskcontourprivate.h"
#include "gskcurveprivate.h"
#include "gskpathprivate.h"
#include "gskstrokeprivate.h"
typedef struct
{
float offset; /* how much of the current dash we've spent */
gsize dash_index; /* goes from 0 to n_dash * 2, so we don't have to care about on/off
for uneven dashes */
gboolean on; /* If we're currently dashing or not */
gboolean may_close; /* TRUE if we haven't turned the dash off in this contour */
gboolean needs_move_to; /* If we have emitted the initial move_to() yet */
enum {
NORMAL, /* no special behavior required */
SKIP, /* skip the next dash */
ONLY, /* only do the first dash */
DONE /* done with the first dash */
} first_dash_behavior; /* How to handle the first dash in the loop. We loop closed contours
twice to make sure the first dash and the last dash can get joined */
GskCurve curve; /* Curve we are currently processing */
float collect_start; /* We're collecting multiple line segments when decomposing. */
float collect_length; /* No need to emit a curve for every line segment when the dash is long enough. */
/* from the stroke */
float *dash;
gsize n_dash;
float dash_length;
float dash_offset;
float tolerance;
GskPathForeachFunc func;
gpointer user_data;
} GskPathDash;
static void
gsk_path_dash_setup (GskPathDash *self)
{
self->offset = fmodf (self->dash_offset, 2 * self->dash_length);
self->dash_index = 0;
self->on = TRUE;
self->may_close = TRUE;
while (self->offset > self->dash[self->dash_index % self->n_dash])
{
self->offset -= self->dash[self->dash_index % self->n_dash];
self->dash_index++;
self->on = !self->on;
}
if (self->first_dash_behavior != ONLY)
self->needs_move_to = TRUE;
}
static gboolean
gsk_path_dash_ensure_move_to (GskPathDash *self,
const graphene_point_t *pt)
{
if (!self->needs_move_to)
return TRUE;
if (!self->func (GSK_PATH_MOVE, pt, 1, 0, self->user_data))
return FALSE;
self->needs_move_to = FALSE;
return TRUE;
}
static gboolean
gsk_path_dash_add_line_segment (const graphene_point_t *start,
const graphene_point_t *end,
float t_start,
float t_end,
gpointer user_data)
{
GskPathDash *self = user_data;
float remaining, length, t_step;
length = graphene_point_distance (start, end, NULL, NULL);
if (self->collect_length)
{
t_start = self->collect_start;
length += self->collect_length;
self->collect_length = 0;
}
t_step = t_end - t_start;
remaining = length;
while (remaining)
{
float piece;
if (self->offset + remaining <= self->dash[self->dash_index % self->n_dash])
{
/* try collecting multiple line segments */
if (t_end < 1.0)
{
self->collect_start = t_start + t_step * (length - remaining) / length;
self->collect_length = remaining;
return TRUE;
}
piece = remaining;
}
else
piece = self->dash[self->dash_index % self->n_dash] - self->offset;
if (self->on)
{
if (self->first_dash_behavior != SKIP)
{
GskCurve segment;
if (piece)
{
gsk_curve_segment (&self->curve,
t_start + t_step * (length - remaining) / length,
t_start + t_step * (length - (remaining - piece)) / length,
&segment);
if (!gsk_path_dash_ensure_move_to (self, gsk_curve_get_start_point (&segment)))
return FALSE;
if (!gsk_pathop_foreach (gsk_curve_pathop (&segment), self->func, self->user_data))
return FALSE;
}
else
{
graphene_point_t p;
gsk_curve_get_point (&self->curve, t_start + t_step * (length - remaining) / length, &p);
if (!gsk_path_dash_ensure_move_to (self, &p))
return FALSE;
}
}
}
else
{
self->may_close = FALSE;
if (self->first_dash_behavior == ONLY)
{
self->first_dash_behavior = DONE;
return FALSE;
}
self->first_dash_behavior = NORMAL;
}
if (self->offset + remaining <= self->dash[self->dash_index % self->n_dash])
{
self->offset += remaining;
remaining = 0;
}
else
{
remaining -= piece;
self->offset = 0;
self->dash_index++;
self->dash_index %= 2 * self->n_dash;
self->on = !self->on;
self->needs_move_to = TRUE;
}
}
return TRUE;
}
static gboolean
gsk_path_dash_foreach (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathDash *self = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_dash_setup (self);
break;
case GSK_PATH_CLOSE:
if (self->may_close)
{
if (graphene_point_equal (&pts[0], &pts[1]))
return self->func (GSK_PATH_CLOSE, pts, 2, 0, self->user_data);
}
else
op = GSK_PATH_LINE;
G_GNUC_FALLTHROUGH;
case GSK_PATH_LINE:
case GSK_PATH_CURVE:
case GSK_PATH_CONIC:
gsk_curve_init_foreach (&self->curve, op, pts, n_pts, weight);
if (!gsk_curve_decompose (&self->curve, self->tolerance, gsk_path_dash_add_line_segment, self))
return FALSE;
break;
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
gboolean
gsk_contour_dash (const GskContour *contour,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data)
{
GskPathDash self = {
.offset = 0,
.dash = stroke->dash,
.n_dash = stroke->n_dash,
.dash_length = stroke->dash_length,
.dash_offset = stroke->dash_offset,
.tolerance = tolerance,
.func = func,
.user_data = user_data
};
gboolean is_closed = gsk_contour_get_flags (contour) & GSK_PATH_CLOSED ? TRUE : FALSE;
self.first_dash_behavior = is_closed ? SKIP : NORMAL;
if (!gsk_contour_foreach (contour, tolerance, gsk_path_dash_foreach, &self))
return FALSE;
if (is_closed)
{
if (self.first_dash_behavior == NORMAL)
self.first_dash_behavior = ONLY;
else
self.first_dash_behavior = NORMAL;
self.needs_move_to = !self.on;
if (!gsk_contour_foreach (contour, tolerance, gsk_path_dash_foreach, &self) &&
self.first_dash_behavior != DONE)
return FALSE;
}
return TRUE;
}
gboolean
gsk_path_dash (GskPath *path,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data)
{
gsize i;
/* Dashing disabled, no need to do any work */
if (stroke->dash_length <= 0)
return gsk_path_foreach (path, -1, func, user_data);
for (i = 0; i < gsk_path_get_n_contours (path); i++)
{
if (!gsk_contour_dash (gsk_path_get_contour (path, i), stroke, tolerance, func, user_data))
return FALSE;
}
return TRUE;
}
+50
View File
@@ -0,0 +1,50 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_DASH_PRIVATE_H__
#define __GSK_PATH_DASH_PRIVATE_H__
#include <gsk/gskpath.h>
G_BEGIN_DECLS
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_dash (GskPath *path,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
#ifdef GTK_COMPILATION
#include "gskcontourprivate.h"
gboolean gsk_contour_dash (const GskContour *contour,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
#endif /* GTK_COMPILATION */
G_END_DECLS
#endif /* __GSK_PATH_DASH_PRIVATE_H__ */
+677
View File
@@ -0,0 +1,677 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskpathmeasure.h"
#include "gskpathbuilder.h"
#include "gskpathbuilder.h"
#include "gskpathprivate.h"
/**
* GskPathMeasure:
*
* `GskPathMeasure` is an object that allows measuring operations
* on `GskPath` objects, to determine quantities like the arc
* length of the path, its curvature, or closest points.
*
* These operations are useful when implementing animations.
*/
typedef struct _GskContourMeasure GskContourMeasure;
struct _GskContourMeasure
{
float length;
gpointer contour_data;
};
struct _GskPathMeasure
{
/*< private >*/
guint ref_count;
GskPath *path;
float tolerance;
gsize first;
gsize last;
float length;
gsize n_contours;
GskContourMeasure measures[];
};
G_DEFINE_BOXED_TYPE (GskPathMeasure, gsk_path_measure,
gsk_path_measure_ref,
gsk_path_measure_unref)
/**
* gsk_path_measure_new:
* @path: the path to measure
*
* Creates a measure object for the given @path with a
* default tolerance.
*
* Returns: a new `GskPathMeasure` representing @path
**/
GskPathMeasure *
gsk_path_measure_new (GskPath *path)
{
return gsk_path_measure_new_with_tolerance (path, GSK_PATH_TOLERANCE_DEFAULT);
}
/**
* gsk_path_measure_new_with_tolerance:
* @path: the path to measure
* @tolerance: the tolerance for measuring operations
*
* Creates a measure object for the given @path and @tolerance.
*
* Returns: a new `GskPathMeasure` representing @path
**/
GskPathMeasure *
gsk_path_measure_new_with_tolerance (GskPath *path,
float tolerance)
{
GskPathMeasure *self;
gsize i, n_contours;
g_return_val_if_fail (path != NULL, NULL);
g_return_val_if_fail (tolerance > 0, NULL);
n_contours = gsk_path_get_n_contours (path);
self = g_malloc0 (sizeof (GskPathMeasure) + n_contours * sizeof (GskContourMeasure));
self->ref_count = 1;
self->path = gsk_path_ref (path);
self->tolerance = tolerance;
self->n_contours = n_contours;
self->first = 0;
self->last = n_contours;
for (i = 0; i < n_contours; i++)
{
self->measures[i].contour_data = gsk_contour_init_measure (gsk_path_get_contour (path, i),
self->tolerance,
&self->measures[i].length);
self->length += self->measures[i].length;
}
return self;
}
/**
* gsk_path_measure_ref:
* @self: a `GskPathMeasure`
*
* Increases the reference count of a `GskPathMeasure` by one.
*
* Returns: the passed in `GskPathMeasure`.
**/
GskPathMeasure *
gsk_path_measure_ref (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, NULL);
self->ref_count++;
return self;
}
/**
* gsk_path_measure_unref:
* @self: a `GskPathMeasure`
*
* Decreases the reference count of a `GskPathMeasure` by one.
*
* If the resulting reference count is zero, frees the object.
**/
void
gsk_path_measure_unref (GskPathMeasure *self)
{
gsize i;
g_return_if_fail (self != NULL);
g_return_if_fail (self->ref_count > 0);
self->ref_count--;
if (self->ref_count > 0)
return;
for (i = 0; i < self->n_contours; i++)
{
gsk_contour_free_measure (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data);
}
gsk_path_unref (self->path);
g_free (self);
}
/**
* gsk_path_measure_get_path:
* @self: a `GskPathMeasure`
*
* Returns the path that the measure was created for.
*
* Returns: (transfer none): the path of @self
*/
GskPath *
gsk_path_measure_get_path (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, NULL);
return self->path;
}
/**
* gsk_path_measure_get_tolerance:
* @self: a `GskPathMeasure`
*
* Returns the tolerance that the measure was created with.
*
* Returns: the tolerance of @self
*/
float
gsk_path_measure_get_tolerance (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, 0.f);
return self->tolerance;
}
/**
* gsk_path_measure_get_n_contours:
* @self: a `GskPathMeasure`
*
* Returns the number of contours in the path being measured.
*
* The returned value is independent of whether @self if restricted
* or not.
*
* Returns: The number of contours
**/
gsize
gsk_path_measure_get_n_contours (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, 0);
return self->n_contours;
}
/**
* gsk_path_measure_restrict_to_contour:
* @self: a `GskPathMeasure`
* @contour: contour to restrict to or (gsize) -1 for using the
* whole path
*
* Restricts all functions on the path to just the given @contour.
*
* If @contour >= gsk_path_measure_get_n_contours() - so in
* particular when it is set to -1 - the whole path will be used.
**/
void
gsk_path_measure_restrict_to_contour (GskPathMeasure *self,
gsize contour)
{
if (contour >= self->n_contours)
{
/* use the whole path */
self->first = 0;
self->last = self->n_contours;
}
else
{
/* use just one contour */
self->first = contour;
self->last = contour + 1;
}
self->length = 0;
for (gsize i = self->first; i < self->last; i++)
{
self->length += self->measures[i].length;
}
}
/**
* gsk_path_measure_get_length:
* @self: a `GskPathMeasure`
*
* Gets the length of the path being measured.
*
* The length is cached, so this function does not do any work.
*
* Returns: The length of the path measured by @self
**/
float
gsk_path_measure_get_length (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, 0);
return self->length;
}
/**
* gsk_path_measure_is_closed:
* @self: a `GskPathMeasure`
*
* Returns if the path being measured represents a single closed
* contour.
*
* Returns: %TRUE if the current path is closed
**/
gboolean
gsk_path_measure_is_closed (GskPathMeasure *self)
{
const GskContour *contour;
g_return_val_if_fail (self != NULL, FALSE);
/* XXX: is the empty path closed? Currently it's not */
if (self->last - self->first != 1)
return FALSE;
contour = gsk_path_get_contour (self->path, self->first);
return gsk_contour_get_flags (contour) & GSK_PATH_CLOSED ? TRUE : FALSE;
}
static float
gsk_path_measure_clamp_distance (GskPathMeasure *self,
float distance)
{
if (isnan (distance))
return 0;
return CLAMP (distance, 0, self->length);
}
/**
* gsk_path_measure_get_point:
* @self: a `GskPathMeasure`
* @distance: distance into the path
* @pos: (optional) (out caller-allocates): The coordinates
* of the position at @distance
* @tangent: (optional) (out caller-allocates): The tangent
* to the position at @distance
*
* Calculates the coordinates and tangent of the point @distance
* units into the path. The value will be clamped to the length
* of the path.
*
* If the point is a discontinuous edge in the path, the returned
* point and tangent will describe the line starting at that point
* going forward.
*
* If @self describes an empty path, the returned point will be
* set to `(0, 0)` and the tangent will be the x axis or `(1, 0)`.
**/
void
gsk_path_measure_get_point (GskPathMeasure *self,
float distance,
graphene_point_t *pos,
graphene_vec2_t *tangent)
{
gsize i;
g_return_if_fail (self != NULL);
if (pos == NULL && tangent == NULL)
return;
distance = gsk_path_measure_clamp_distance (self, distance);
for (i = self->first; i < self->last; i++)
{
if (distance < self->measures[i].length)
break;
distance -= self->measures[i].length;
}
/* weird corner cases */
if (i == self->last)
{
/* the empty path goes here */
if (self->first == self->last)
{
if (pos)
graphene_point_init (pos, 0.f, 0.f);
if (tangent)
graphene_vec2_init (tangent, 1.f, 0.f);
return;
}
/* rounding errors can make this happen */
i = self->last - 1;
distance = self->measures[i].length;
}
gsk_contour_get_point (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data,
distance,
pos,
tangent);
}
/**
* gsk_path_measure_get_curvature:
* @self: a `GskPathMeasure`
* @distance: distance into the path
* @center: (optional) (out caller-allocates): The center
* of the osculating circle at the point
*
* Calculates the curvature at the point @distance units into
* the path.
*
* Optionally, returns the center of the osculating circle as well.
*
* If the curvature is infinite (at line segments), or does
* not exist (at sharp turns), zero is returned, and @center
* is not modified.
*
* Returns: The curvature of the path at the given point
*/
float
gsk_path_measure_get_curvature (GskPathMeasure *self,
float distance,
graphene_point_t *center)
{
gsize i;
g_return_val_if_fail (self != NULL, 0);
distance = gsk_path_measure_clamp_distance (self, distance);
for (i = self->first; i < self->last; i++)
{
if (distance < self->measures[i].length)
break;
distance -= self->measures[i].length;
}
/* weird corner cases */
if (i == self->last)
{
/* the empty path goes here */
if (self->first == self->last)
return 0;
/* rounding errors can make this happen */
i = self->last - 1;
distance = self->measures[i].length;
}
return gsk_contour_get_curvature (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data,
distance,
center);
}
/**
* gsk_path_measure_get_closest_point:
* @self: a `GskPathMeasure`
* @point: the point to find the closest point to
* @out_pos: (optional) (out caller-allocates): return location
* for the closest point
*
* Gets the point on the path that is closest to @point.
*
* If the path being measured is empty, return 0 and set
* @out_pos to (0, 0).
*
* This is a simpler and slower version of
* [method@Gsk.PathMeasure.get_closest_point_full].
* Use that one if you need more control.
*
* Returns: The offset into the path of the closest point
**/
float
gsk_path_measure_get_closest_point (GskPathMeasure *self,
const graphene_point_t *point,
graphene_point_t *out_pos)
{
float result;
g_return_val_if_fail (self != NULL, 0.0f);
if (gsk_path_measure_get_closest_point_full (self,
point,
INFINITY,
NULL,
out_pos,
&result,
NULL))
return result;
if (out_pos)
*out_pos = GRAPHENE_POINT_INIT (0, 0);
return 0;
}
/**
* gsk_path_measure_get_closest_point_full:
* @self: a `GskPathMeasure`
* @point: the point to find the closest point to
* @threshold: The maximum allowed distance between the path and @point.
* Use INFINITY to look for any point.
* @out_distance: (optional) (out caller-allocates): The
* distance between the found closest point on the path and the given
* @point.
* @out_pos: (optional) (out caller-allocates): return location
* for the closest point
* @out_offset: (optional) (out caller-allocates): The offset into
* the path of the found point
* @out_tangent: (optional) (out caller-allocates): return location for
* the tangent at the closest point
*
* Gets the point on the path that is closest to @point. If no point on
* path is closer to @point than @threshold, return %FALSE.
*
* Returns: %TRUE if a point was found, %FALSE otherwise.
**/
gboolean
gsk_path_measure_get_closest_point_full (GskPathMeasure *self,
const graphene_point_t *point,
float threshold,
float *out_distance,
graphene_point_t *out_pos,
float *out_offset,
graphene_vec2_t *out_tangent)
{
gboolean result;
gsize i;
float distance, length;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (point != NULL, FALSE);
result = FALSE;
length = 0;
for (i = self->first; i < self->last; i++)
{
if (gsk_contour_get_closest_point (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data,
self->tolerance,
point,
threshold,
&distance,
out_pos,
out_offset,
out_tangent))
{
result = TRUE;
if (out_offset)
*out_offset += length;
if (distance < self->tolerance)
break;
threshold = distance - self->tolerance;
}
length += self->measures[i].length;
}
if (result && out_distance)
*out_distance = distance;
return result;
}
/**
* gsk_path_measure_in_fill:
* @self: a #GskPathMeasure
* @point: the point to test
* @fill_rule: the fill rule to follow
*
* Returns whether the given point is inside the area that would be
* affected if the path of @self was filled according to @fill_rule.
*
* Returns: %TRUE if @point is inside
*/
gboolean
gsk_path_measure_in_fill (GskPathMeasure *self,
graphene_point_t *point,
GskFillRule fill_rule)
{
int winding = 0;
gboolean on_edge = FALSE;
int i;
for (i = self->first; i < self->last; i++)
{
winding += gsk_contour_get_winding (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data,
point,
&on_edge);
if (on_edge)
return TRUE;
}
switch (fill_rule)
{
case GSK_FILL_RULE_EVEN_ODD:
return winding & 1;
case GSK_FILL_RULE_WINDING:
return winding != 0;
default:
g_assert_not_reached ();
}
}
static void
gsk_path_builder_add_segment_chunk (GskPathBuilder *self,
GskPathMeasure *measure,
gboolean emit_move_to,
float start,
float end)
{
g_assert (start < end);
for (gsize i = measure->first; i < measure->last; i++)
{
if (measure->measures[i].length < start)
{
start -= measure->measures[i].length;
end -= measure->measures[i].length;
}
else if (start > 0 || end < measure->measures[i].length)
{
float len = MIN (end, measure->measures[i].length);
gsk_contour_add_segment (gsk_path_get_contour (measure->path, i),
self,
measure->measures[i].contour_data,
emit_move_to,
start,
len);
end -= len;
start = 0;
if (end <= 0)
break;
}
else
{
end -= measure->measures[i].length;
gsk_path_builder_add_contour (self, gsk_contour_dup (gsk_path_get_contour (measure->path, i)));
}
emit_move_to = TRUE;
}
}
/**
* gsk_path_builder_add_segment:
* @builder: a `GskPathBuilder`
* @measure: the `GskPathMeasure` to take the segment to
* @start: start distance into the path
* @end: end distance into the path
*
* Adds to @builder the segment of @measure from @start to @end.
*
* The distances are given relative to the length of @measure's path,
* from 0 for the beginning of the path to [method@Gsk.PathMeasure.get_length]
* for the end of the path. The values will be clamped to that range.
*
* If @start >= @end after clamping, the path will first add the segment
* from @start to the end of the path, and then add the segment from
* the beginning to @end. If the path is closed, these segments will
* be connected.
**/
void
gsk_path_builder_add_segment (GskPathBuilder *builder,
GskPathMeasure *measure,
float start,
float end)
{
g_return_if_fail (builder != NULL);
g_return_if_fail (measure != NULL);
start = gsk_path_measure_clamp_distance (measure, start);
end = gsk_path_measure_clamp_distance (measure, end);
if (start < end)
{
gsk_path_builder_add_segment_chunk (builder, measure, TRUE, start, end);
}
else
{
/* If the path is closed, we can connect the 2 subpaths. */
gboolean closed = gsk_path_measure_is_closed (measure);
gboolean need_move_to = !closed;
if (start < measure->length)
gsk_path_builder_add_segment_chunk (builder, measure,
TRUE,
start, measure->length);
else
need_move_to = TRUE;
if (end > 0)
gsk_path_builder_add_segment_chunk (builder, measure,
need_move_to,
0, end);
if (start == end && closed)
gsk_path_builder_close (builder);
}
}
+92
View File
@@ -0,0 +1,92 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_MEASURE_H__
#define __GSK_PATH_MEASURE_H__
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gskpath.h>
G_BEGIN_DECLS
#define GSK_TYPE_PATH_MEASURE (gsk_path_measure_get_type ())
GDK_AVAILABLE_IN_ALL
GType gsk_path_measure_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskPathMeasure * gsk_path_measure_new (GskPath *path);
GDK_AVAILABLE_IN_ALL
GskPathMeasure * gsk_path_measure_new_with_tolerance (GskPath *path,
float tolerance);
GDK_AVAILABLE_IN_ALL
GskPathMeasure * gsk_path_measure_ref (GskPathMeasure *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_measure_unref (GskPathMeasure *self);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_measure_get_path (GskPathMeasure *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_ALL
float gsk_path_measure_get_tolerance (GskPathMeasure *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_ALL
gsize gsk_path_measure_get_n_contours (GskPathMeasure *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_ALL
void gsk_path_measure_restrict_to_contour (GskPathMeasure *self,
gsize contour);
GDK_AVAILABLE_IN_ALL
float gsk_path_measure_get_length (GskPathMeasure *self);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_measure_is_closed (GskPathMeasure *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_measure_get_point (GskPathMeasure *self,
float distance,
graphene_point_t *pos,
graphene_vec2_t *tangent);
GDK_AVAILABLE_IN_ALL
float gsk_path_measure_get_curvature (GskPathMeasure *self,
float distance,
graphene_point_t *center);
GDK_AVAILABLE_IN_ALL
float gsk_path_measure_get_closest_point (GskPathMeasure *self,
const graphene_point_t *point,
graphene_point_t *out_pos);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_measure_get_closest_point_full (GskPathMeasure *self,
const graphene_point_t *point,
float threshold,
float *out_distance,
graphene_point_t *out_pos,
float *out_offset,
graphene_vec2_t *out_tangent);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_measure_in_fill (GskPathMeasure *self,
graphene_point_t *point,
GskFillRule fill_rule);
G_END_DECLS
#endif /* __GSK_PATH_MEASURE_H__ */
+179
View File
@@ -0,0 +1,179 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATHOP_PRIVATE_H__
#define __GSK_PATHOP_PRIVATE_H__
#include <gsk/gskpath.h>
#include <gsk/gskpathbuilder.h>
G_BEGIN_DECLS
typedef gpointer gskpathop;
static inline
gskpathop gsk_pathop_encode (GskPathOperation op,
const graphene_point_t *pts);
static inline
const graphene_point_t *gsk_pathop_points (gskpathop pop);
static inline
GskPathOperation gsk_pathop_op (gskpathop pop);
static inline
gboolean gsk_pathop_foreach (gskpathop pop,
GskPathForeachFunc func,
gpointer user_data);
/* included inline so tests can use them */
static inline
void gsk_path_builder_pathop_to (GskPathBuilder *builder,
gskpathop op);
static inline
void gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
gskpathop op);
/* IMPLEMENTATION */
#define GSK_PATHOP_OPERATION_MASK (0x7)
static inline gskpathop
gsk_pathop_encode (GskPathOperation op,
const graphene_point_t *pts)
{
/* g_assert (op & GSK_PATHOP_OPERATION_MASK == op); */
g_assert ((GPOINTER_TO_SIZE (pts) & GSK_PATHOP_OPERATION_MASK) == 0);
return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pts) | op);
}
static inline const graphene_point_t *
gsk_pathop_points (gskpathop pop)
{
return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pop) & ~GSK_PATHOP_OPERATION_MASK);
}
static inline
GskPathOperation gsk_pathop_op (gskpathop pop)
{
return GPOINTER_TO_SIZE (pop) & GSK_PATHOP_OPERATION_MASK;
}
static inline gboolean
gsk_pathop_foreach (gskpathop pop,
GskPathForeachFunc func,
gpointer user_data)
{
switch (gsk_pathop_op (pop))
{
case GSK_PATH_MOVE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 1, 0, user_data);
case GSK_PATH_CLOSE:
case GSK_PATH_LINE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 2, 0, user_data);
case GSK_PATH_CURVE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, 0, user_data);
case GSK_PATH_CONIC:
{
const graphene_point_t *pts = gsk_pathop_points (pop);
return func (gsk_pathop_op (pop), (graphene_point_t[3]) { pts[0], pts[1], pts[3] }, 3, pts[2].x, user_data);
}
default:
g_assert_not_reached ();
return TRUE;
}
}
static inline void
gsk_path_builder_pathop_to (GskPathBuilder *builder,
gskpathop op)
{
const graphene_point_t *pts = gsk_pathop_points (op);
switch (gsk_pathop_op (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_CURVE:
gsk_path_builder_curve_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[3].x, pts[3].y, pts[2].x);
break;
default:
g_assert_not_reached ();
break;
}
}
static inline void
gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
gskpathop op)
{
const graphene_point_t *pts = gsk_pathop_points (op);
switch (gsk_pathop_op (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_line_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_CURVE:
gsk_path_builder_curve_to (builder, pts[2].x, pts[2].y, pts[1].x, pts[1].y, pts[0].x, pts[0].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[0].x, pts[0].y, pts[2].x);
break;
default:
g_assert_not_reached ();
break;
}
}
G_END_DECLS
#endif /* __GSK_PATHOP_PRIVATE_H__ */
+1427
View File
File diff suppressed because it is too large Load Diff
+78
View File
@@ -0,0 +1,78 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_PRIVATE_H__
#define __GSK_PATH_PRIVATE_H__
#include "gskpath.h"
#include "gskcontourprivate.h"
#include "gskpathopprivate.h"
G_BEGIN_DECLS
/* Same as Skia, so looks like a good value. ¯\_(ツ)_/¯ */
#define GSK_PATH_TOLERANCE_DEFAULT (0.5)
GskPath * gsk_path_new_from_contours (const GSList *contours);
gsize gsk_path_get_n_contours (GskPath *path);
const GskContour * gsk_path_get_contour (GskPath *path,
gsize i);
GskPathFlags gsk_path_get_flags (GskPath *self);
gboolean gsk_path_foreach_with_tolerance (GskPath *self,
GskPathForeachFlags flags,
double tolerance,
GskPathForeachFunc func,
gpointer user_data);
void gsk_path_builder_add_contour (GskPathBuilder *builder,
GskContour *contour);
void gsk_path_builder_svg_arc_to (GskPathBuilder *builder,
float rx,
float ry,
float x_axis_rotation,
gboolean large_arc,
gboolean positive_sweep,
float x,
float y);
typedef enum
{
GSK_PATH_OP_SIMPLIFY,
GSK_PATH_OP_UNION,
GSK_PATH_OP_INTERSECTION,
GSK_PATH_OP_DIFFERENCE,
GSK_PATH_OP_XOR
} GskPathOp;
GskPath * gsk_path_op (GskPathOp operation,
GskPath *first,
GskPath *second);
G_END_DECLS
#endif /* __GSK_PATH_PRIVATE_H__ */
+2073
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -158,6 +158,8 @@ GskRenderNode * gsk_render_node_deserialize (GBytes
#define GSK_TYPE_REPEAT_NODE (gsk_repeat_node_get_type())
#define GSK_TYPE_CLIP_NODE (gsk_clip_node_get_type())
#define GSK_TYPE_ROUNDED_CLIP_NODE (gsk_rounded_clip_node_get_type())
#define GSK_TYPE_FILL_NODE (gsk_fill_node_get_type())
#define GSK_TYPE_STROKE_NODE (gsk_stroke_node_get_type())
#define GSK_TYPE_SHADOW_NODE (gsk_shadow_node_get_type())
#define GSK_TYPE_BLEND_NODE (gsk_blend_node_get_type())
#define GSK_TYPE_CROSS_FADE_NODE (gsk_cross_fade_node_get_type())
@@ -184,6 +186,8 @@ typedef struct _GskColorMatrixNode GskColorMatrixNode;
typedef struct _GskRepeatNode GskRepeatNode;
typedef struct _GskClipNode GskClipNode;
typedef struct _GskRoundedClipNode GskRoundedClipNode;
typedef struct _GskFillNode GskFillNode;
typedef struct _GskStrokeNode GskStrokeNode;
typedef struct _GskShadowNode GskShadowNode;
typedef struct _GskBlendNode GskBlendNode;
typedef struct _GskCrossFadeNode GskCrossFadeNode;
@@ -445,6 +449,32 @@ GskRenderNode * gsk_rounded_clip_node_get_child (const GskRender
GDK_AVAILABLE_IN_ALL
const GskRoundedRect * gsk_rounded_clip_node_get_clip (const GskRenderNode *node) G_GNUC_PURE;
GDK_AVAILABLE_IN_ALL
GType gsk_fill_node_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskRenderNode * gsk_fill_node_new (GskRenderNode *child,
GskPath *path,
GskFillRule fill_rule);
GDK_AVAILABLE_IN_ALL
GskRenderNode * gsk_fill_node_get_child (const GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_fill_node_get_path (const GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GskFillRule gsk_fill_node_get_fill_rule (const GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GType gsk_stroke_node_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskRenderNode * gsk_stroke_node_new (GskRenderNode *child,
GskPath *path,
const GskStroke *stroke);
GDK_AVAILABLE_IN_ALL
GskRenderNode * gsk_stroke_node_get_child (const GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_stroke_node_get_path (const GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
const GskStroke * gsk_stroke_node_get_stroke (const GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GType gsk_shadow_node_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
+386 -22
View File
@@ -23,8 +23,10 @@
#include "gskcairoblurprivate.h"
#include "gskdebugprivate.h"
#include "gskdiffprivate.h"
#include "gskpath.h"
#include "gskrendererprivate.h"
#include "gskroundedrectprivate.h"
#include "gskstrokeprivate.h"
#include "gsktransformprivate.h"
#include "gdk/gdktextureprivate.h"
@@ -33,12 +35,6 @@
#include <hb-ot.h>
/* maximal number of rectangles we keep in a diff region before we throw
* the towel and just use the bounding box of the parent node.
* Meant to avoid performance corner cases.
*/
#define MAX_RECTS_IN_DIFF 30
static inline void
gsk_cairo_rectangle (cairo_t *cr,
const graphene_rect_t *rect)
@@ -2607,35 +2603,32 @@ gsk_container_node_draw (GskRenderNode *node,
}
}
static void
gsk_render_node_add_to_region (GskRenderNode *node,
cairo_region_t *region)
{
cairo_rectangle_int_t rect;
rectangle_init_from_graphene (&rect, &node->bounds);
cairo_region_union_rectangle (region, &rect);
}
static int
gsk_container_node_compare_func (gconstpointer elem1, gconstpointer elem2, gpointer data)
{
return gsk_render_node_can_diff ((const GskRenderNode *) elem1, (const GskRenderNode *) elem2) ? 0 : 1;
}
static GskDiffResult
static void
gsk_container_node_keep_func (gconstpointer elem1, gconstpointer elem2, gpointer data)
{
gsk_render_node_diff ((GskRenderNode *) elem1, (GskRenderNode *) elem2, data);
if (cairo_region_num_rectangles (data) > MAX_RECTS_IN_DIFF)
return GSK_DIFF_ABORTED;
return GSK_DIFF_OK;
}
static GskDiffResult
static void
gsk_container_node_change_func (gconstpointer elem, gsize idx, gpointer data)
{
const GskRenderNode *node = elem;
cairo_region_t *region = data;
cairo_rectangle_int_t rect;
rectangle_init_from_graphene (&rect, &node->bounds);
cairo_region_union_rectangle (region, &rect);
if (cairo_region_num_rectangles (region) > MAX_RECTS_IN_DIFF)
return GSK_DIFF_ABORTED;
return GSK_DIFF_OK;
gsk_render_node_add_to_region ((GskRenderNode *) elem, data);
}
static GskDiffSettings *
@@ -3770,6 +3763,343 @@ gsk_rounded_clip_node_get_clip (const GskRenderNode *node)
return &self->clip;
}
/*** GSK_FILL_NODE ***/
struct _GskFillNode
{
GskRenderNode render_node;
GskRenderNode *child;
GskPath *path;
GskFillRule fill_rule;
};
static void
gsk_fill_node_finalize (GskRenderNode *node)
{
GskFillNode *self = (GskFillNode *) node;
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_FILL_NODE));
gsk_render_node_unref (self->child);
gsk_path_unref (self->path);
parent_class->finalize (node);
}
static void
gsk_fill_node_draw (GskRenderNode *node,
cairo_t *cr)
{
GskFillNode *self = (GskFillNode *) node;
cairo_save (cr);
switch (self->fill_rule)
{
case GSK_FILL_RULE_WINDING:
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_WINDING);
break;
case GSK_FILL_RULE_EVEN_ODD:
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
break;
default:
g_assert_not_reached ();
break;
}
gsk_path_to_cairo (self->path, cr);
cairo_clip (cr);
gsk_render_node_draw (self->child, cr);
cairo_restore (cr);
}
static void
gsk_fill_node_diff (GskRenderNode *node1,
GskRenderNode *node2,
cairo_region_t *region)
{
GskFillNode *self1 = (GskFillNode *) node1;
GskFillNode *self2 = (GskFillNode *) node2;
if (self1->path == self2->path)
{
cairo_region_t *sub;
cairo_rectangle_int_t clip_rect;
graphene_rect_t rect;
sub = cairo_region_create();
gsk_render_node_diff (self1->child, self2->child, sub);
graphene_rect_union (&node1->bounds, &node2->bounds, &rect);
rectangle_init_from_graphene (&clip_rect, &rect);
cairo_region_intersect_rectangle (sub, &clip_rect);
cairo_region_union (region, sub);
cairo_region_destroy (sub);
}
else
{
gsk_render_node_diff_impossible (node1, node2, region);
}
}
/**
* gsk_fill_node_new:
* @child: The node to fill the area with
* @path: The path describing the area to fill
* @fill_rule: The fill rule to use
*
* Creates a `GskRenderNode` that will fill the @child in the area
* given by @path and @fill_rule.
*
* Returns: (transfer none) (type GskFillNode): A new `GskRenderNode`
*/
GskRenderNode *
gsk_fill_node_new (GskRenderNode *child,
GskPath *path,
GskFillRule fill_rule)
{
GskFillNode *self;
GskRenderNode *node;
graphene_rect_t path_bounds;
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
g_return_val_if_fail (path != NULL, NULL);
self = gsk_render_node_alloc (GSK_FILL_NODE);
node = (GskRenderNode *) self;
self->child = gsk_render_node_ref (child);
self->path = gsk_path_ref (path);
self->fill_rule = fill_rule;
if (gsk_path_get_bounds (path, &path_bounds))
graphene_rect_intersection (&path_bounds, &child->bounds, &node->bounds);
else
graphene_rect_init_from_rect (&node->bounds, graphene_rect_zero ());
return node;
}
/**
* gsk_fill_node_get_child:
* @node: (type GskFillNode): a fill `GskRenderNode`
*
* Gets the child node that is getting drawn by the given @node.
*
* Returns: (transfer none): The child that is getting drawn
**/
GskRenderNode *
gsk_fill_node_get_child (const GskRenderNode *node)
{
const GskFillNode *self = (const GskFillNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), NULL);
return self->child;
}
/**
* gsk_fill_node_get_path:
* @node: (type GskFillNode): a fill `GskRenderNode`
*
* Retrievs the path used to describe the area filled with the contents of
* the @node.
*
* Returns: (transfer none): a `GskPath`
*/
GskPath *
gsk_fill_node_get_path (const GskRenderNode *node)
{
const GskFillNode *self = (const GskFillNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), NULL);
return self->path;
}
/**
* gsk_fill_node_get_fill_rule:
* @node: (type GskFillNode): a fill `GskRenderNode`
*
* Retrievs the fill rule used to determine how the path is filled.
*
* Returns: a `GskFillRule`
*/
GskFillRule
gsk_fill_node_get_fill_rule (const GskRenderNode *node)
{
const GskFillNode *self = (const GskFillNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), GSK_FILL_RULE_WINDING);
return self->fill_rule;
}
/*** GSK_STROKE_NODE ***/
struct _GskStrokeNode
{
GskRenderNode render_node;
GskRenderNode *child;
GskPath *path;
GskStroke stroke;
};
static void
gsk_stroke_node_finalize (GskRenderNode *node)
{
GskStrokeNode *self = (GskStrokeNode *) node;
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_STROKE_NODE));
gsk_render_node_unref (self->child);
gsk_path_unref (self->path);
gsk_stroke_clear (&self->stroke);
parent_class->finalize (node);
}
static void
gsk_stroke_node_draw (GskRenderNode *node,
cairo_t *cr)
{
GskStrokeNode *self = (GskStrokeNode *) node;
cairo_save (cr);
gsk_cairo_rectangle (cr, &self->child->bounds);
cairo_clip (cr);
cairo_push_group (cr);
gsk_render_node_draw (self->child, cr);
cairo_pop_group_to_source (cr);
gsk_stroke_to_cairo (&self->stroke, cr);
gsk_path_to_cairo (self->path, cr);
cairo_stroke (cr);
cairo_restore (cr);
}
static void
gsk_stroke_node_diff (GskRenderNode *node1,
GskRenderNode *node2,
cairo_region_t *region)
{
GskStrokeNode *self1 = (GskStrokeNode *) node1;
GskStrokeNode *self2 = (GskStrokeNode *) node2;
if (self1->path == self2->path &&
gsk_stroke_equal (&self1->stroke, &self2->stroke))
{
cairo_region_t *sub;
sub = cairo_region_create();
gsk_render_node_diff (self1->child, self2->child, sub);
cairo_region_union (region, sub);
cairo_region_destroy (sub);
}
else
{
gsk_render_node_diff_impossible (node1, node2, region);
}
}
/**
* gsk_stroke_node_new:
* @child: The node to stroke the area with
* @path: (transfer none): The path describing the area to stroke
* @stroke: (transfer none): The stroke attributes to use
*
* Creates a `GskRenderNode` that will stroke the @child along the given
* @path using the attributes defined in @stroke.
*
* Returns: (transfer none) (type GskStrokeNode): A new `GskRenderNode`
*/
GskRenderNode *
gsk_stroke_node_new (GskRenderNode *child,
GskPath *path,
const GskStroke *stroke)
{
GskStrokeNode *self;
GskRenderNode *node;
graphene_rect_t path_bounds;
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
g_return_val_if_fail (path != NULL, NULL);
g_return_val_if_fail (stroke != NULL, NULL);
self = gsk_render_node_alloc (GSK_STROKE_NODE);
node = (GskRenderNode *) self;
self->child = gsk_render_node_ref (child);
self->path = gsk_path_ref (path);
gsk_stroke_init_copy (&self->stroke, stroke);
if (gsk_path_get_stroke_bounds (path, stroke, &path_bounds))
graphene_rect_intersection (&path_bounds, &child->bounds, &node->bounds);
else
graphene_rect_init_from_rect (&node->bounds, graphene_rect_zero ());
return node;
}
/**
* gsk_stroke_node_get_child:
* @node: (type GskStrokeNode): a stroke `GskRenderNode`
*
* Gets the child node that is getting drawn by the given @node.
*
* Returns: (transfer none): The child that is getting drawn
**/
GskRenderNode *
gsk_stroke_node_get_child (const GskRenderNode *node)
{
const GskStrokeNode *self = (const GskStrokeNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
return self->child;
}
/**
* gsk_stroke_node_get_path:
* @node: (type GskStrokeNode): a stroke `GskRenderNode`
*
* Retrievs the path that will be stroked with the contents of
* the @node.
*
* Returns: (transfer none): a `GskPath`
*/
GskPath *
gsk_stroke_node_get_path (const GskRenderNode *node)
{
const GskStrokeNode *self = (const GskStrokeNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
return self->path;
}
/**
* gsk_stroke_node_get_stroke:
* @node: (type GskStrokeNode): a stroke `GskRenderNode`
*
* Retrievs the stroke attributes used in this @node.
*
* Returns: a `GskStroke`
*/
const GskStroke *
gsk_stroke_node_get_stroke (const GskRenderNode *node)
{
const GskStrokeNode *self = (const GskStrokeNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
return &self->stroke;
}
/*** GSK_SHADOW_NODE ***/
/**
@@ -5292,6 +5622,8 @@ GSK_DEFINE_RENDER_NODE_TYPE (gsk_color_matrix_node, GSK_COLOR_MATRIX_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_repeat_node, GSK_REPEAT_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_clip_node, GSK_CLIP_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_rounded_clip_node, GSK_ROUNDED_CLIP_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_fill_node, GSK_FILL_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_stroke_node, GSK_STROKE_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_shadow_node, GSK_SHADOW_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_blend_node, GSK_BLEND_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_cross_fade_node, GSK_CROSS_FADE_NODE)
@@ -5591,6 +5923,38 @@ gsk_render_node_init_types_once (void)
gsk_render_node_types[GSK_ROUNDED_CLIP_NODE] = node_type;
}
{
const GskRenderNodeTypeInfo node_info =
{
GSK_FILL_NODE,
sizeof (GskFillNode),
NULL,
gsk_fill_node_finalize,
gsk_fill_node_draw,
NULL,
gsk_fill_node_diff,
};
GType node_type = gsk_render_node_type_register_static (I_("GskFillNode"), &node_info);
gsk_render_node_types[GSK_FILL_NODE] = node_type;
}
{
const GskRenderNodeTypeInfo node_info =
{
GSK_STROKE_NODE,
sizeof (GskStrokeNode),
NULL,
gsk_stroke_node_finalize,
gsk_stroke_node_draw,
NULL,
gsk_stroke_node_diff,
};
GType node_type = gsk_render_node_type_register_static (I_("GskStrokeNode"), &node_info);
gsk_render_node_types[GSK_STROKE_NODE] = node_type;
}
{
const GskRenderNodeTypeInfo node_info =
{
+354 -6
View File
@@ -23,9 +23,13 @@
#include "gskrendernodeparserprivate.h"
#include "gskpath.h"
#include "gskpathbuilder.h"
#include "gskroundedrectprivate.h"
#include "gskrendernodeprivate.h"
#include "gskstroke.h"
#include "gsktransformprivate.h"
#include "gskenumtypes.h"
#include "gdk/gdkrgbaprivate.h"
#include "gdk/gdktextureprivate.h"
@@ -399,7 +403,10 @@ parse_string (GtkCssParser *parser,
token = gtk_css_parser_get_token (parser);
if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
return FALSE;
{
gtk_css_parser_error_syntax (parser, "Expected a string");
return FALSE;
}
s = g_strdup (token->string.string);
gtk_css_parser_consume_token (parser);
@@ -955,6 +962,26 @@ create_default_render_node (void)
return gsk_color_node_new (&GDK_RGBA("FF00CC"), &GRAPHENE_RECT_INIT (0, 0, 50, 50));
}
static GskPath *
create_default_path (void)
{
GskPathBuilder *builder;
guint i;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, 25, 0);
for (i = 1; i < 5; i++)
{
gsk_path_builder_line_to (builder,
sin (i * G_PI * 0.8) * 25 + 25,
-cos (i * G_PI * 0.8) * 25 + 25);
}
gsk_path_builder_close (builder);
return gsk_path_builder_free_to_path (builder);
}
static GskRenderNode *
parse_color_node (GtkCssParser *parser)
{
@@ -1102,7 +1129,7 @@ parse_conic_gradient_node (GtkCssParser *parser)
g_array_append_val (stops, to);
}
result = gsk_conic_gradient_node_new (&bounds, &center, rotation,
result = gsk_conic_gradient_node_new (&bounds, &center, rotation,
(GskColorStop *) stops->data, stops->len);
g_array_free (stops, TRUE);
@@ -1388,7 +1415,7 @@ parse_cairo_node (GtkCssParser *parser)
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
node = gsk_cairo_node_new (&bounds);
if (surface != NULL)
{
cairo_t *cr = gsk_cairo_node_get_draw_context (node);
@@ -1765,6 +1792,208 @@ parse_rounded_clip_node (GtkCssParser *parser)
return result;
}
static gboolean
parse_path (GtkCssParser *parser,
gpointer out_path)
{
GskPath *path;
char *str = NULL;
if (!parse_string (parser, &str))
return FALSE;
path = gsk_path_parse (str);
g_free (str);
if (path == NULL)
{
gtk_css_parser_error_value (parser, "Invalid path");
return FALSE;
}
*((GskPath **) out_path) = path;
return TRUE;
}
static void
clear_path (gpointer inout_path)
{
g_clear_pointer ((GskPath **) inout_path, gsk_path_unref);
}
static gboolean
parse_dash (GtkCssParser *parser,
gpointer out_dash)
{
GArray *dash;
double d;
/* because CSS does this, too */
if (gtk_css_parser_try_ident (parser, "none"))
{
*((GArray **) out_dash) = NULL;
return TRUE;
}
dash = g_array_new (FALSE, FALSE, sizeof (float));
do {
if (!gtk_css_parser_consume_number (parser, &d))
{
g_array_free (dash, TRUE);
return FALSE;
}
g_array_append_vals (dash, (float[1]) { d }, 1);
} while (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_NUMBER) ||
gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_INTEGER));
*((GArray **) out_dash) = dash;
return TRUE;
}
static void
clear_dash (gpointer inout_array)
{
g_clear_pointer ((GArray **) inout_array, g_array_unref);
}
static gboolean
parse_enum (GtkCssParser *parser,
GType type,
gpointer out_value)
{
GEnumClass *class;
GEnumValue *v;
const GtkCssToken *token;
token = gtk_css_parser_get_token (parser);
if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
{
gtk_css_parser_error_syntax (parser, "Expected a valid identifier");
return FALSE;
}
class = g_type_class_ref (type);
v = g_enum_get_value_by_nick (class, token->string.string);
if (v == NULL)
{
gtk_css_parser_error_value (parser, "\"%s\" is not a valid identifier here", token->string.string);
g_type_class_unref (class);
return FALSE;
}
*(int*)out_value = v->value;
g_type_class_unref (class);
gtk_css_parser_consume_token (parser);
return TRUE;
}
static gboolean
parse_fill_rule (GtkCssParser *parser,
gpointer out_rule)
{
return parse_enum (parser, GSK_TYPE_FILL_RULE, out_rule);
}
static GskRenderNode *
parse_fill_node (GtkCssParser *parser)
{
GskRenderNode *child = NULL;
GskPath *path = NULL;
int rule = GSK_FILL_RULE_WINDING;
const Declaration declarations[] = {
{ "child", parse_node, clear_node, &child },
{ "path", parse_path, clear_path, &path },
{ "fill-rule", parse_fill_rule, NULL, &rule },
};
GskRenderNode *result;
parse_declarations (parser, declarations, G_N_ELEMENTS (declarations));
if (child == NULL)
child = create_default_render_node ();
if (path == NULL)
path = create_default_path ();
result = gsk_fill_node_new (child, path, rule);
gsk_path_unref (path);
gsk_render_node_unref (child);
return result;
}
static gboolean
parse_line_cap (GtkCssParser *parser,
gpointer out)
{
return parse_enum (parser, GSK_TYPE_LINE_CAP, out);
}
static gboolean
parse_line_join (GtkCssParser *parser,
gpointer out)
{
return parse_enum (parser, GSK_TYPE_LINE_JOIN, out);
}
static GskRenderNode *
parse_stroke_node (GtkCssParser *parser)
{
GskRenderNode *child = NULL;
GskPath *path = NULL;
double line_width = 1.0;
int line_cap = GSK_LINE_CAP_BUTT;
int line_join = GSK_LINE_JOIN_MITER;
double miter_limit = 4.0;
GArray *dash = NULL;
double dash_offset = 0.0;
GskStroke *stroke;
const Declaration declarations[] = {
{ "child", parse_node, clear_node, &child },
{ "path", parse_path, clear_path, &path },
{ "line-width", parse_double, NULL, &line_width },
{ "line-cap", parse_line_cap, NULL, &line_cap },
{ "line-join", parse_line_join, NULL, &line_join },
{ "miter-limit", parse_double, NULL, &miter_limit },
{ "dash", parse_dash, clear_dash, &dash },
{ "dash-offset", parse_double, NULL, &dash_offset},
};
GskRenderNode *result;
parse_declarations (parser, declarations, G_N_ELEMENTS (declarations));
if (child == NULL)
child = create_default_render_node ();
if (path == NULL)
path = create_default_path ();
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 (dash)
{
gsk_stroke_set_dash (stroke, (float *) dash->data, dash->len);
g_array_free (dash, TRUE);
}
gsk_stroke_set_dash_offset (stroke, dash_offset);
result = gsk_stroke_node_new (child, path, stroke);
gsk_path_unref (path);
gsk_stroke_free (stroke);
gsk_render_node_unref (child);
return result;
}
static GskRenderNode *
parse_shadow_node (GtkCssParser *parser)
{
@@ -1834,6 +2063,7 @@ parse_node (GtkCssParser *parser,
{ "container", parse_container_node },
{ "cross-fade", parse_cross_fade_node },
{ "debug", parse_debug_node },
{ "fill", parse_fill_node },
{ "inset-shadow", parse_inset_shadow_node },
{ "linear-gradient", parse_linear_gradient_node },
{ "radial-gradient", parse_radial_gradient_node },
@@ -1845,6 +2075,7 @@ parse_node (GtkCssParser *parser,
{ "repeating-radial-gradient", parse_repeating_radial_gradient_node },
{ "rounded-clip", parse_rounded_clip_node },
{ "shadow", parse_shadow_node },
{ "stroke", parse_stroke_node },
{ "text", parse_text_node },
{ "texture", parse_texture_node },
{ "transform", parse_transform_node },
@@ -2100,7 +2331,7 @@ append_float_param (Printer *p,
float value,
float default_value)
{
/* Don't approximate-compare here, better be topo verbose */
/* Don't approximate-compare here, better be too verbose */
if (value == default_value)
return;
@@ -2275,8 +2506,11 @@ append_escaping_newlines (GString *str,
len = strcspn (string, "\n");
g_string_append_len (str, string, len);
string += len;
g_string_append (str, "\\\n");
string++;
if (*string)
{
g_string_append (str, "\\\n");
string++;
}
} while (*string);
}
@@ -2391,6 +2625,83 @@ gsk_text_node_serialize_glyphs (GskRenderNode *node,
pango_glyph_string_free (ascii);
}
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 void
append_enum_param (Printer *p,
const char *param_name,
GType type,
int value)
{
_indent (p);
g_string_append_printf (p->str, "%s: ", param_name);
g_string_append (p->str, enum_to_nick (type, value));
g_string_append_c (p->str, ';');
g_string_append_c (p->str, '\n');
}
static void
append_path_param (Printer *p,
const char *param_name,
GskPath *path)
{
char *str, *s;
_indent (p);
g_string_append (p->str, "path: \"\\\n");
str = gsk_path_to_string (path);
/* Put each command on a new line */
for (s = str; *s; s++)
{
if (*s == ' ' &&
(s[1] == 'M' || s[1] == 'C' || s[1] == 'Z' || s[1] == 'L' || s[1] == 'O'))
*s = '\n';
}
append_escaping_newlines (p->str, str);
g_string_append (p->str, "\";\n");
g_free (str);
}
static void
append_dash_param (Printer *p,
const char *param_name,
const float *dash,
gsize n_dash)
{
_indent (p);
g_string_append (p->str, "dash: ");
if (n_dash == 0)
{
g_string_append (p->str, "none");
}
else
{
gsize i;
string_append_double (p->str, dash[0]);
for (i = 1; i < n_dash; i++)
{
g_string_append_c (p->str, ' ');
string_append_double (p->str, dash[i]);
}
}
g_string_append (p->str, ";\n");
}
static void
render_node_print (Printer *p,
GskRenderNode *node)
@@ -2539,6 +2850,42 @@ render_node_print (Printer *p,
append_rounded_rect_param (p, "clip", gsk_rounded_clip_node_get_clip (node));
append_node_param (p, "child", gsk_rounded_clip_node_get_child (node));
end_node (p);
}
break;
case GSK_FILL_NODE:
{
start_node (p, "fill");
append_node_param (p, "child", gsk_fill_node_get_child (node));
append_path_param (p, "path", gsk_fill_node_get_path (node));
append_enum_param (p, "fill-rule", GSK_TYPE_FILL_RULE, gsk_fill_node_get_fill_rule (node));
end_node (p);
}
break;
case GSK_STROKE_NODE:
{
const GskStroke *stroke;
const float *dash;
gsize n_dash;
start_node (p, "stroke");
append_node_param (p, "child", gsk_stroke_node_get_child (node));
append_path_param (p, "path", gsk_stroke_node_get_path (node));
stroke = gsk_stroke_node_get_stroke (node);
append_float_param (p, "line-width", gsk_stroke_get_line_width (stroke), 0.0f);
append_enum_param (p, "line-cap", GSK_TYPE_LINE_CAP, gsk_stroke_get_line_cap (stroke));
append_enum_param (p, "line-join", GSK_TYPE_LINE_JOIN, gsk_stroke_get_line_join (stroke));
append_float_param (p, "miter-limit", gsk_stroke_get_miter_limit (stroke), 4.0f);
dash = gsk_stroke_get_dash (stroke, &n_dash);
if (dash)
append_dash_param (p, "dash", dash, n_dash);
append_float_param (p, "dash-offset", gsk_stroke_get_dash_offset (stroke), 0.0f);
end_node (p);
}
@@ -3045,3 +3392,4 @@ gsk_render_node_serialize (GskRenderNode *node)
return g_string_free_to_bytes (p.str);
}
+208
View File
@@ -0,0 +1,208 @@
/*
* Copyright © 2002 University of Southern California
* 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
* Carl D. Worth <cworth@cworth.org>
*/
#include "config.h"
#include "gsksplineprivate.h"
#include <math.h>
/* Spline deviation from the circle in radius would be given by:
error = sqrt (x**2 + y**2) - 1
A simpler error function to work with is:
e = x**2 + y**2 - 1
From "Good approximation of circles by curvature-continuous Bezier
curves", Tor Dokken and Morten Daehlen, Computer Aided Geometric
Design 8 (1990) 22-41, we learn:
abs (max(e)) = 4/27 * sin**6(angle/4) / cos**2(angle/4)
and
abs (error) =~ 1/2 * e
Of course, this error value applies only for the particular spline
approximation that is used in _cairo_gstate_arc_segment.
*/
static float
arc_error_normalized (float angle)
{
return 2.0/27.0 * pow (sin (angle / 4), 6) / pow (cos (angle / 4), 2);
}
static float
arc_max_angle_for_tolerance_normalized (float tolerance)
{
float angle, error;
guint i;
/* Use table lookup to reduce search time in most cases. */
struct {
float angle;
float error;
} table[] = {
{ G_PI / 1.0, 0.0185185185185185036127 },
{ G_PI / 2.0, 0.000272567143730179811158 },
{ G_PI / 3.0, 2.38647043651461047433e-05 },
{ G_PI / 4.0, 4.2455377443222443279e-06 },
{ G_PI / 5.0, 1.11281001494389081528e-06 },
{ G_PI / 6.0, 3.72662000942734705475e-07 },
{ G_PI / 7.0, 1.47783685574284411325e-07 },
{ G_PI / 8.0, 6.63240432022601149057e-08 },
{ G_PI / 9.0, 3.2715520137536980553e-08 },
{ G_PI / 10.0, 1.73863223499021216974e-08 },
{ G_PI / 11.0, 9.81410988043554039085e-09 },
};
for (i = 0; i < G_N_ELEMENTS (table); i++)
{
if (table[i].error < tolerance)
return table[i].angle;
}
i++;
do {
angle = G_PI / i++;
error = arc_error_normalized (angle);
} while (error > tolerance);
return angle;
}
static guint
arc_segments_needed (float angle,
float radius,
float tolerance)
{
float max_angle;
/* the error is amplified by at most the length of the
* major axis of the circle; see cairo-pen.c for a more detailed analysis
* of this. */
max_angle = arc_max_angle_for_tolerance_normalized (tolerance / radius);
return ceil (fabs (angle) / max_angle);
}
/* We want to draw a single spline approximating a circular arc radius
R from angle A to angle B. Since we want a symmetric spline that
matches the endpoints of the arc in position and slope, we know
that the spline control points must be:
(R * cos(A), R * sin(A))
(R * cos(A) - h * sin(A), R * sin(A) + h * cos (A))
(R * cos(B) + h * sin(B), R * sin(B) - h * cos (B))
(R * cos(B), R * sin(B))
for some value of h.
"Approximation of circular arcs by cubic polynomials", Michael
Goldapp, Computer Aided Geometric Design 8 (1991) 227-238, provides
various values of h along with error analysis for each.
From that paper, a very practical value of h is:
h = 4/3 * R * tan(angle/4)
This value does not give the spline with minimal error, but it does
provide a very good approximation, (6th-order convergence), and the
error expression is quite simple, (see the comment for
_arc_error_normalized).
*/
static gboolean
gsk_spline_decompose_arc_segment (const graphene_point_t *center,
float radius,
float angle_A,
float angle_B,
GskSplineAddCurveFunc curve_func,
gpointer user_data)
{
float r_sin_A, r_cos_A;
float r_sin_B, r_cos_B;
float h;
r_sin_A = radius * sin (angle_A);
r_cos_A = radius * cos (angle_A);
r_sin_B = radius * sin (angle_B);
r_cos_B = radius * cos (angle_B);
h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0);
return curve_func ((graphene_point_t[4]) {
GRAPHENE_POINT_INIT (
center->x + r_cos_A,
center->y + r_sin_A
),
GRAPHENE_POINT_INIT (
center->x + r_cos_A - h * r_sin_A,
center->y + r_sin_A + h * r_cos_A
),
GRAPHENE_POINT_INIT (
center->x + r_cos_B + h * r_sin_B,
center->y + r_sin_B - h * r_cos_B
),
GRAPHENE_POINT_INIT (
center->x + r_cos_B,
center->y + r_sin_B
)
},
user_data);
}
gboolean
gsk_spline_decompose_arc (const graphene_point_t *center,
float radius,
float tolerance,
float start_angle,
float end_angle,
GskSplineAddCurveFunc curve_func,
gpointer user_data)
{
float step = start_angle - end_angle;
guint i, n_segments;
/* Recurse if drawing arc larger than pi */
if (ABS (step) > G_PI)
{
float mid_angle = (start_angle + end_angle) / 2.0;
return gsk_spline_decompose_arc (center, radius, tolerance, start_angle, mid_angle, curve_func, user_data)
&& gsk_spline_decompose_arc (center, radius, tolerance, mid_angle, end_angle, curve_func, user_data);
}
else if (ABS (step) < tolerance)
{
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
}
n_segments = arc_segments_needed (ABS (step), radius, tolerance);
step = (end_angle - start_angle) / n_segments;
for (i = 0; i < n_segments - 1; i++, start_angle += step)
{
if (!gsk_spline_decompose_arc_segment (center, radius, start_angle, start_angle + step, curve_func, user_data))
return FALSE;
}
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
}
+41
View File
@@ -0,0 +1,41 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_SPLINE_PRIVATE_H__
#define __GSK_SPLINE_PRIVATE_H__
#include "gskpath.h"
G_BEGIN_DECLS
typedef gboolean (* GskSplineAddCurveFunc) (const graphene_point_t curve[4],
gpointer user_data);
gboolean gsk_spline_decompose_arc (const graphene_point_t *center,
float radius,
float tolerance,
float start_angle,
float end_angle,
GskSplineAddCurveFunc curve_func,
gpointer user_data);
G_END_DECLS
#endif /* __GSK_SPLINE_PRIVATE_H__ */
+458
View File
@@ -0,0 +1,458 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskstrokeprivate.h"
/**
* GskStroke:
*
* A `GskStroke` describes the attributes that are used when stroking a path.
*/
G_DEFINE_BOXED_TYPE (GskStroke, gsk_stroke,
gsk_stroke_copy,
gsk_stroke_free)
/**
* gsk_stroke_new:
* @line_width: line width of the stroke. Must be > 0
*
* Creates a new `GskStroke` with the given @line_width.
*
* Returns: a new `GskStroke`
**/
GskStroke *
gsk_stroke_new (float line_width)
{
GskStroke *self;
g_return_val_if_fail (line_width > 0, NULL);
self = g_new0 (GskStroke, 1);
self->line_width = line_width;
self->line_cap = GSK_LINE_CAP_BUTT;
self->line_join = GSK_LINE_JOIN_MITER;
self->miter_limit = 4.f; /* following svg */
return self;
}
/**
* gsk_stroke_copy:
* @other: `GskStroke` to copy
*
* Creates a copy of the given @other stroke.
*
* Returns: a new `GskStroke`. Use [method@Gsk.Stroke.free] to free it.
**/
GskStroke *
gsk_stroke_copy (const GskStroke *other)
{
GskStroke *self;
g_return_val_if_fail (other != NULL, NULL);
self = g_new (GskStroke, 1);
gsk_stroke_init_copy (self, other);
return self;
}
/**
* gsk_stroke_free:
* @self: a `GskStroke`
*
* Frees a `GskStroke`.
**/
void
gsk_stroke_free (GskStroke *self)
{
if (self == NULL)
return;
gsk_stroke_clear (self);
g_free (self);
}
/**
* gsk_stroke_to_cairo:
* @self: a #GskStroke
* @cr: the cairo context to configure
*
* A helper function that sets the stroke parameters
* of @cr from the values found in @self.
*/
void
gsk_stroke_to_cairo (const GskStroke *self,
cairo_t *cr)
{
cairo_set_line_width (cr, self->line_width);
/* gcc can optimize that to a direct case. This catches later additions to the enum */
switch (self->line_cap)
{
case GSK_LINE_CAP_BUTT:
cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
break;
case GSK_LINE_CAP_ROUND:
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
break;
case GSK_LINE_CAP_SQUARE:
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
break;
default:
g_assert_not_reached ();
break;
}
/* gcc can optimize that to a direct case. This catches later additions to the enum */
switch (self->line_join)
{
case GSK_LINE_JOIN_MITER:
case GSK_LINE_JOIN_MITER_CLIP:
case GSK_LINE_JOIN_ARCS:
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
break;
case GSK_LINE_JOIN_ROUND:
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
break;
case GSK_LINE_JOIN_BEVEL:
cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL);
break;
default:
g_assert_not_reached ();
break;
}
cairo_set_miter_limit (cr, self->miter_limit);
if (self->dash_length)
{
gsize i;
double *dash = g_newa (double, self->n_dash);
for (i = 0; i < self->n_dash; i++)
{
dash[i] = self->dash[i];
}
cairo_set_dash (cr, dash, self->n_dash, self->dash_offset);
}
else
cairo_set_dash (cr, NULL, 0, 0.0);
}
/**
* gsk_stroke_equal:
* @stroke1: the first `GskStroke`
* @stroke2: the second `GskStroke`
*
* Checks if two strokes are identical.
*
* Returns: %TRUE if the two strokes are equal, %FALSE otherwise
**/
gboolean
gsk_stroke_equal (gconstpointer stroke1,
gconstpointer stroke2)
{
const GskStroke *self1 = stroke1;
const GskStroke *self2 = stroke2;
if (self1->line_width != self2->line_width ||
self1->line_cap != self2->line_cap ||
self1->line_join != self2->line_join ||
self1->miter_limit != self2->miter_limit)
return FALSE;
if (self1->n_dash != self2->n_dash)
return FALSE;
for (int i = 0; i < self1->n_dash; i++)
{
if (self1->dash[i] != self2->dash[i])
return FALSE;
}
if (self1->dash_offset != self2->dash_offset)
return FALSE;
return TRUE;
}
/**
* gsk_stroke_set_line_width:
* @self: a `GskStroke`
* @line_width: width of the line in pixels
*
* Sets the line width to be used when stroking.
*
* The line width must be > 0.
**/
void
gsk_stroke_set_line_width (GskStroke *self,
float line_width)
{
g_return_if_fail (self != NULL);
g_return_if_fail (line_width > 0);
self->line_width = line_width;
}
/**
* gsk_stroke_get_line_width:
* @self: a `GskStroke`
*
* Gets the line width used.
*
* Returns: The line width
**/
float
gsk_stroke_get_line_width (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 0.0);
return self->line_width;
}
/**
* gsk_stroke_set_line_cap:
* @self: a `GskStroke`
* @line_cap: the `GskLineCap`
*
* Sets the line cap to be used when stroking.
*
* See `GskLineCap` for details.
**/
void
gsk_stroke_set_line_cap (GskStroke *self,
GskLineCap line_cap)
{
g_return_if_fail (self != NULL);
self->line_cap = line_cap;
}
/**
* gsk_stroke_get_line_cap:
* @self: a `GskStroke`
*
* Gets the line cap used.
*
* See `GskLineCap` for details.
*
* Returns: The line cap
**/
GskLineCap
gsk_stroke_get_line_cap (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 0.0);
return self->line_cap;
}
/**
* gsk_stroke_set_line_join:
* @self: a `GskStroke`
* @line_join: The line join to use
*
* Sets the line join to be used when stroking.
*
* See `GskLineJoin` for details.
**/
void
gsk_stroke_set_line_join (GskStroke *self,
GskLineJoin line_join)
{
g_return_if_fail (self != NULL);
self->line_join = line_join;
}
/**
* gsk_stroke_get_line_join:
* @self: a `GskStroke`
*
* Gets the line join used.
*
* See `GskLineJoin` for details.
*
* Returns: The line join
**/
GskLineJoin
gsk_stroke_get_line_join (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 0.0);
return self->line_join;
}
/**
* gsk_stroke_set_miter_limit:
* @self: a `GskStroke`
* @limit: the miter limit, must be non-negative
*
* Sets the limit for the distance from the corner where sharp
* turns of joins get cut off. The miter limit is in units of
* line width.
*
* For joins of type `GSK_LINE_JOIN_MITER` that exceed the miter
* limit, the join gets rendered as if it was of type
* `GSK_LINE_JOIN_BEVEL`. For joins of type `GSK_LINE_JOIN_MITER_CLIP`,
* the miter is clipped at a distance of half the miter limit.
*/
void
gsk_stroke_set_miter_limit (GskStroke *self,
float limit)
{
g_return_if_fail (self != NULL);
g_return_if_fail (limit >= 0);
self->miter_limit = limit;
}
/**
* gsk_stroke_get_miter_limit:
* @self: a `GskStroke`
*
* Returns the miter limit of a `GskStroke`.
*/
float
gsk_stroke_get_miter_limit (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 4.f);
return self->miter_limit;
}
/**
* gsk_stroke_set_dash:
* @self: a `GskStroke`
* @dash: (array length=n_dash) (transfer none) (allow-none): the array of dashes
* @n_dash: number of elements in @dash
*
* Sets the dash pattern to use by this stroke. A dash pattern is specified by
* an array of alternating non-negative values. Each value provides the length
* of alternate "on" and "off" portions of the stroke.
*
* Each "on" segment will have caps applied as if the segment were a separate
* contour. In particular, it is valid to use an "on" length of 0 with
* `GSK_LINE_CAP_ROUND` or `GSK_LINE_CAP_SQUARE` to draw dots or squares along
* a path.
*
* If @n_dash is 0, if all elements in @dash are 0, or if there are negative
* values in @dash, then dashing is disabled.
*
* If @n_dash is 1, an alternating "on" and "off" pattern with the single
* dash length provided is assumed.
*
* If @n_dash is uneven, the dash array will be used with the first element
* in @dash defining an "on" or "off" in alternating passes through the array.
*
* You can specify a starting offset into the dash with [method@Gsk.Stroke.set_dash_offset].
**/
void
gsk_stroke_set_dash (GskStroke *self,
const float *dash,
gsize n_dash)
{
float dash_length;
gsize i;
g_return_if_fail (self != NULL);
g_return_if_fail (dash != NULL || n_dash == 0);
dash_length = 0;
for (i = 0; i < n_dash; i++)
{
if (!(dash[i] >= 0)) /* should catch NaN */
{
g_critical ("invalid value in dash array at position %zu", i);
return;
}
dash_length += dash[i];
}
self->dash_length = dash_length;
g_free (self->dash);
self->dash = g_memdup (dash, sizeof (gfloat) * n_dash);
self->n_dash = n_dash;
}
/**
* gsk_stroke_get_dash:
* @self: a `GskStroke`
* @n_dash: (out caller-allocates): number of elements
* in the array returned
*
* Gets the dash array in use or %NULL if dashing is disabled.
*
* Returns: (array length=n_dash) (transfer none) (allow-none):
* The dash array or %NULL if the dash array is empty.
**/
const float *
gsk_stroke_get_dash (const GskStroke *self,
gsize *n_dash)
{
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (n_dash != NULL, NULL);
*n_dash = self->n_dash;
return self->dash;
}
/**
* gsk_stroke_set_dash_offset:
* @self: a `GskStroke`
* @offset: offset into the dash pattern
*
* Sets the offset into the dash pattern set via [method@Gsk.Stroke.set_dash]
* where dashing should begin.
*
* This is an offset into the length of the path, not an index into
* the array values of the dash array.
**/
void
gsk_stroke_set_dash_offset (GskStroke *self,
float offset)
{
g_return_if_fail (self != NULL);
self->dash_offset = offset;
}
/**
* gsk_stroke_get_dash_offset:
* @self: a `GskStroke`
*
* Returns the dash offset of a `GskStroke`.
*/
float
gsk_stroke_get_dash_offset (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 4.f);
return self->dash_offset;
}
+91
View File
@@ -0,0 +1,91 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_STROKE_H__
#define __GSK_STROKE_H__
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gsktypes.h>
G_BEGIN_DECLS
#define GSK_TYPE_STROKE (gsk_stroke_get_type ())
GDK_AVAILABLE_IN_ALL
GType gsk_stroke_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskStroke * gsk_stroke_new (float line_width);
GDK_AVAILABLE_IN_ALL
GskStroke * gsk_stroke_copy (const GskStroke *other);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_free (GskStroke *self);
GDK_AVAILABLE_IN_ALL
gboolean gsk_stroke_equal (gconstpointer stroke1,
gconstpointer stroke2);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_line_width (GskStroke *self,
float line_width);
GDK_AVAILABLE_IN_ALL
float gsk_stroke_get_line_width (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_line_cap (GskStroke *self,
GskLineCap line_cap);
GDK_AVAILABLE_IN_ALL
GskLineCap gsk_stroke_get_line_cap (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_line_join (GskStroke *self,
GskLineJoin line_join);
GDK_AVAILABLE_IN_ALL
GskLineJoin gsk_stroke_get_line_join (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_miter_limit (GskStroke *self,
float limit);
GDK_AVAILABLE_IN_ALL
float gsk_stroke_get_miter_limit (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_dash (GskStroke *self,
const float *dash,
gsize n_dash);
GDK_AVAILABLE_IN_ALL
const float * gsk_stroke_get_dash (const GskStroke *self,
gsize *n_dash);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_dash_offset (GskStroke *self,
float offset);
GDK_AVAILABLE_IN_ALL
float gsk_stroke_get_dash_offset (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_to_cairo (const GskStroke *self,
cairo_t *cr);
G_END_DECLS
#endif /* __GSK_STROKE_H__ */
+60
View File
@@ -0,0 +1,60 @@
/*
* Copyright © 2020 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_STROKE_PRIVATE_H__
#define __GSK_STROKE_PRIVATE_H__
#include "gskstroke.h"
G_BEGIN_DECLS
struct _GskStroke
{
float line_width;
GskLineCap line_cap;
GskLineJoin line_join;
float miter_limit;
float *dash;
gsize n_dash;
float dash_length; /* sum of all dashes in the array */
float dash_offset;
};
static inline void
gsk_stroke_init_copy (GskStroke *stroke,
const GskStroke *other)
{
*stroke = *other;
stroke->dash = g_memdup (other->dash, stroke->n_dash * sizeof (float));
}
static inline void
gsk_stroke_clear (GskStroke *stroke)
{
g_clear_pointer (&stroke->dash, g_free);
stroke->n_dash = 0; /* better safe than sorry */
}
G_END_DECLS
#endif /* __GSK_STROKE_PRIVATE_H__ */
+4
View File
@@ -26,7 +26,11 @@
#include <gdk/gdk.h>
#include <gsk/gskenums.h>
typedef struct _GskPath GskPath;
typedef struct _GskPathBuilder GskPathBuilder;
typedef struct _GskPathMeasure GskPathMeasure;
typedef struct _GskRenderer GskRenderer;
typedef struct _GskStroke GskStroke;
typedef struct _GskTransform GskTransform;
#endif /* __GSK_TYPES_H__ */

Some files were not shown because too many files have changed in this diff Show More