Compare commits
510 Commits
path-itera
...
path-ops2
Author | SHA1 | Date | |
---|---|---|---|
|
08b786a984 | ||
|
54bf63259c | ||
|
53b5441e39 | ||
|
3b2ab7ad1e | ||
|
42eaacbab4 | ||
|
6a1660b78e | ||
|
821fc35941 | ||
|
af0492ed0c | ||
|
dcdbb73702 | ||
|
60b2cf5d4a | ||
|
2fcb20cc4a | ||
|
89567767c5 | ||
|
6577f386b0 | ||
|
6ce8bd6581 | ||
|
6da5b8cb25 | ||
|
7bee7bf5fc | ||
|
fac66ade01 | ||
|
96eae5f62e | ||
|
1cc99847f4 | ||
|
d8b14cd3e3 | ||
|
0c80eb5afb | ||
|
fdcb1d92c5 | ||
|
d65dc6c730 | ||
|
c721c4d6ef | ||
|
1a7ba3c512 | ||
|
25ee1a7784 | ||
|
0b2cebe3d8 | ||
|
5dd0d39a6b | ||
|
9f3ff427c1 | ||
|
14e60d21ac | ||
|
65c3796135 | ||
|
25acc26531 | ||
|
859bf8b15d | ||
|
e0bf96f329 | ||
|
11c5ea1fec | ||
|
bc063350ef | ||
|
347b0317e5 | ||
|
72d832a4f3 | ||
|
76df3ade18 | ||
|
d849a53b98 | ||
|
ef6d3af6b9 | ||
|
f513914306 | ||
|
7135f80094 | ||
|
ef82e0ce4e | ||
|
1910834f8f | ||
|
f94a0624fa | ||
|
17c59d6da6 | ||
|
56c0aa596e | ||
|
a1c73a9b2e | ||
|
cac0cb7f02 | ||
|
7eb999720a | ||
|
05e15241b4 | ||
|
a730867778 | ||
|
8513621709 | ||
|
14d99bacbc | ||
|
cbbd3e8fc3 | ||
|
8fa45d5fef | ||
|
3e1706679c | ||
|
7f359e31c6 | ||
|
e4af2c4d80 | ||
|
2d121c07c9 | ||
|
cc904698a6 | ||
|
a5fdd1228f | ||
|
271d7632cb | ||
|
a8613377a5 | ||
|
d7cf5c2b1b | ||
|
95865cb1bf | ||
|
0b813de72a | ||
|
9c159cf129 | ||
|
957d494090 | ||
|
968b4237a9 | ||
|
f67c57b1f8 | ||
|
84f8c2d91d | ||
|
8f1d8d7cb5 | ||
|
e98d9d62eb | ||
|
5a0b65c611 | ||
|
5777587e7a | ||
|
ecfc661054 | ||
|
e00722dee6 | ||
|
5af21e70ec | ||
|
7e13cfea91 | ||
|
68b4d9c35e | ||
|
1f3b69e7db | ||
|
ca63552cf8 | ||
|
a6d2d983b8 | ||
|
7cd97192e5 | ||
|
8ac8bca52a | ||
|
cf8f6d254e | ||
|
a267dfac5d | ||
|
745b28ef38 | ||
|
5c27899efa | ||
|
fd006592b5 | ||
|
201f27fe19 | ||
|
36c4dc8aea | ||
|
f0bd0c3e50 | ||
|
4d71ff6da1 | ||
|
6d16776e27 | ||
|
7cdff6bbb3 | ||
|
bf43859bdc | ||
|
4b45f8415f | ||
|
86e0d5c13e | ||
|
d21ee115d0 | ||
|
2dd8e3b0eb | ||
|
6d001f79f9 | ||
|
eb6ca8f39a | ||
|
abebd92b19 | ||
|
014ca76334 | ||
|
a520f9fcf7 | ||
|
de724b2a57 | ||
|
061637f4eb | ||
|
dab1897d4e | ||
|
9ebb030c78 | ||
|
93477ec019 | ||
|
f2a71898b1 | ||
|
24048dce43 | ||
|
41af8ee2e2 | ||
|
60c20fa6ed | ||
|
5152c13081 | ||
|
e9089f65e3 | ||
|
9aaec91f95 | ||
|
1c971c595f | ||
|
f8627755b5 | ||
|
55ae8dc39e | ||
|
dd7d145249 | ||
|
0b2c249de3 | ||
|
ea6b95f60d | ||
|
ed44f37de4 | ||
|
3780a5ed6a | ||
|
fec3f0191a | ||
|
359fa99945 | ||
|
5441ed2227 | ||
|
ec1a1d0e34 | ||
|
8f4fb45715 | ||
|
9db2288064 | ||
|
1502c21e97 | ||
|
408dd4b34d | ||
|
609e1f54ef | ||
|
f49d0fbbf6 | ||
|
dde47b7966 | ||
|
f84da62740 | ||
|
cbfbe6dc23 | ||
|
2c63a34791 | ||
|
9ab07553a3 | ||
|
c29fb838e9 | ||
|
aecc76d916 | ||
|
713a5188cf | ||
|
911f3bf555 | ||
|
c752598a3d | ||
|
9dfdb1b65b | ||
|
a05a021fd1 | ||
|
9ffd7840ba | ||
|
dd64084b44 | ||
|
21277a37d6 | ||
|
ddc7e36543 | ||
|
f429dff03e | ||
|
6f07c6a3f1 | ||
|
4cb4aa1029 | ||
|
cecab7801e | ||
|
ea7fd1ff5a | ||
|
ad474a60c0 | ||
|
1e8e7e0c00 | ||
|
1e9e8d24c3 | ||
|
9bd9b6f2ca | ||
|
913a6ddcc9 | ||
|
31da6f60d0 | ||
|
c4c118e425 | ||
|
02599acf4d | ||
|
6a653e4675 | ||
|
29e92d6a0e | ||
|
b4fcc8b5f0 | ||
|
b00333c603 | ||
|
63614727a3 | ||
|
061a56d213 | ||
|
3f63ba36ce | ||
|
d9b96c34be | ||
|
39a8c64624 | ||
|
360b77cc50 | ||
|
1186eff1b7 | ||
|
0b8582ccba | ||
|
dc2914e1f9 | ||
|
a0d8678a9c | ||
|
c8a43da526 | ||
|
90ee05ae64 | ||
|
67a7602080 | ||
|
37db270d19 | ||
|
f3312f677b | ||
|
05547d98d6 | ||
|
11b219bc61 | ||
|
5721c3cb8f | ||
|
f90ca697af | ||
|
7a0e27b6e8 | ||
|
e81aa18c82 | ||
|
827bbc0cc1 | ||
|
7095a67910 | ||
|
dcbca3f0d7 | ||
|
702d7c238a | ||
|
5a3ed65ad8 | ||
|
2e24a9ece4 | ||
|
2a17320314 | ||
|
0dbff14555 | ||
|
c5d89d00f1 | ||
|
6f3be310f4 | ||
|
0ea6b70d55 | ||
|
2ca9982b91 | ||
|
a40282b2fb | ||
|
ad9fb1e101 | ||
|
02a9652af4 | ||
|
6f823d2d0d | ||
|
a5c9cd5657 | ||
|
21ab5b6fa3 | ||
|
2582fd45e4 | ||
|
0a28a5d53a | ||
|
ddd4855bbc | ||
|
204216e3d5 | ||
|
1e6a124665 | ||
|
78c5aff956 | ||
|
8d1844135b | ||
|
b5dd9dae0d | ||
|
ebcb518e4f | ||
|
b7ea22f168 | ||
|
f0b3381660 | ||
|
0796c72049 | ||
|
822e988efe | ||
|
cee043f977 | ||
|
b420540b15 | ||
|
031c0ec3e5 | ||
|
43b6822eb0 | ||
|
ba3a657c48 | ||
|
5f2f116c28 | ||
|
1db75e521d | ||
|
2297a353b8 | ||
|
80903e5f44 | ||
|
7342ce5bca | ||
|
167b38dfa1 | ||
|
ead88c36ec | ||
|
b8a3d7fa00 | ||
|
0fce24674a | ||
|
739084e9bc | ||
|
99ad585252 | ||
|
949b6692ac | ||
|
eb0479d8ed | ||
|
6defdb4e8a | ||
|
9fdad6d4ee | ||
|
b6e285844f | ||
|
c989a06718 | ||
|
1c8bd8658d | ||
|
ab50cba4e9 | ||
|
57918813e2 | ||
|
a4df8d8818 | ||
|
4b00cfc1ce | ||
|
889688c978 | ||
|
0bf1ae033d | ||
|
994afcaeed | ||
|
b69cc832ef | ||
|
ca188b41ae | ||
|
81c8d1dd28 | ||
|
32d00ca9ed | ||
|
01b9997590 | ||
|
677e19042d | ||
|
bd79f61cc5 | ||
|
361cdecfe4 | ||
|
581ad5fc04 | ||
|
ff40dcffec | ||
|
eb2fa4195a | ||
|
e2d2b57f0e | ||
|
6c1a128ea3 | ||
|
cfaa31eeb8 | ||
|
2097b15f9c | ||
|
46a8f57735 | ||
|
2e5639a077 | ||
|
007cfeac94 | ||
|
553499522c | ||
|
2782daadb4 | ||
|
81821978ae | ||
|
787b1a661e | ||
|
04e6fc3f74 | ||
|
54d2f0063a | ||
|
ae7ec40cf2 | ||
|
d586a5c67c | ||
|
c97dc6e903 | ||
|
eec90d1026 | ||
|
f4fb4d04e5 | ||
|
3283157352 | ||
|
d07a6b8566 | ||
|
4e8e59cd58 | ||
|
9f811ccee5 | ||
|
6cd6da050b | ||
|
a906dbea35 | ||
|
5bc82f4141 | ||
|
25e6231a88 | ||
|
650e4b1920 | ||
|
5dc0cbc20f | ||
|
e1faf28310 | ||
|
854c695153 | ||
|
ebd4ceca15 | ||
|
d873643407 | ||
|
8a789d0234 | ||
|
286b4cbc72 | ||
|
b4428029af | ||
|
9aeb5be8ad | ||
|
007dd55a71 | ||
|
7a05e40ce7 | ||
|
8d0654db6b | ||
|
63d5615fe7 | ||
|
37d18816be | ||
|
f1de7a524c | ||
|
644692a2e0 | ||
|
da2e8b78c2 | ||
|
0b5a6ecbff | ||
|
9a6aac0459 | ||
|
8aaecc3416 | ||
|
d3f9b3e28a | ||
|
4229a37e90 | ||
|
12297fd88a | ||
|
7943610066 | ||
|
e19246d2db | ||
|
e70117bd9f | ||
|
20c4e24b74 | ||
|
3c5efbc77e | ||
|
f6c2b6bb88 | ||
|
4ab03e79b7 | ||
|
7a12abdea3 | ||
|
36ca1f10dc | ||
|
07a92c1690 | ||
|
0f7b9e686c | ||
|
71584107b0 | ||
|
88c0ace644 | ||
|
d33ed4f9ab | ||
|
a63af177a4 | ||
|
43c8e136b5 | ||
|
efd597c6ed | ||
|
024a753a6a | ||
|
e1dd3fb801 | ||
|
82011569ce | ||
|
747c9c4f94 | ||
|
7d7a090875 | ||
|
0f125aad90 | ||
|
a7d5796750 | ||
|
79e132ab7b | ||
|
85957b5d3e | ||
|
373972b8ca | ||
|
30e6d70b14 | ||
|
c78b34649b | ||
|
b3fe68ca95 | ||
|
417888f847 | ||
|
de8fdd822c | ||
|
ee8e6ac546 | ||
|
486674927a | ||
|
acf9612ed8 | ||
|
8c664b669c | ||
|
b367bf83b4 | ||
|
b84f60877d | ||
|
8a84024a54 | ||
|
56cb614f79 | ||
|
957d8845fd | ||
|
231c0ad77f | ||
|
da635d6392 | ||
|
b8a103b993 | ||
|
162759f34b | ||
|
79fa785dbc | ||
|
2a03e50030 | ||
|
fb0f5b5c14 | ||
|
cd7726bb31 | ||
|
2b4e577b53 | ||
|
1ed695931f | ||
|
51a44ddd1d | ||
|
29cdac18e8 | ||
|
418f6b35af | ||
|
96a6e05f0b | ||
|
d665d2fb89 | ||
|
8862666b5a | ||
|
3adf02445f | ||
|
bfdd325d36 | ||
|
ac76cadf32 | ||
|
5516ff6a02 | ||
|
f2578fcfb9 | ||
|
4da80520b8 | ||
|
b12a27083d | ||
|
07ec266a00 | ||
|
965b3025d9 | ||
|
88e74ca21e | ||
|
148da12cfc | ||
|
3ded6ada0c | ||
|
0c39890e0f | ||
|
e45cba13ed | ||
|
b4977decc1 | ||
|
2da9d5862f | ||
|
b948e03e8a | ||
|
2c90cadef4 | ||
|
08c064d79a | ||
|
73853f1a6b | ||
|
8634d67cff | ||
|
125ea90894 | ||
|
ffba5a3f42 | ||
|
60df21213b | ||
|
14ba577acb | ||
|
7d5ef1117e | ||
|
af0e2bff94 | ||
|
7d65f81d7f | ||
|
b08ecfcf9e | ||
|
e2cd362f24 | ||
|
1a1b6fe66b | ||
|
122176a3a1 | ||
|
5beccc8208 | ||
|
6715a3267a | ||
|
313078b77c | ||
|
246a7cfeab | ||
|
082fdfdb24 | ||
|
823cb714ec | ||
|
cc93347fe6 | ||
|
d6a07cd617 | ||
|
858bd9f84c | ||
|
be7e5c866f | ||
|
93a90bafc3 | ||
|
090e01b383 | ||
|
03a511aab4 | ||
|
5cf9a09c07 | ||
|
9558c70683 | ||
|
57d98a12fa | ||
|
3dd40049a9 | ||
|
225e626ada | ||
|
081f504ae4 | ||
|
45aa8e4e00 | ||
|
87430be658 | ||
|
012c16ccd6 | ||
|
de0cef5d4d | ||
|
e807a84967 | ||
|
b5e105d4f4 | ||
|
a85ab3585e | ||
|
a1d40c5236 | ||
|
95ea6b2615 | ||
|
82d8bc9403 | ||
|
536d432444 | ||
|
3b8c26a116 | ||
|
6ace4b60d8 | ||
|
7601b89561 | ||
|
0cdd2e7008 | ||
|
595152bdba | ||
|
90f0733673 | ||
|
625e83e1de | ||
|
daac04be82 | ||
|
8ec9bba892 | ||
|
b63e365fd2 | ||
|
859a25b0e7 | ||
|
f5d47a4658 | ||
|
fa6645f157 | ||
|
69b6067c5e | ||
|
e645f59748 | ||
|
2929686c1d | ||
|
c83256e080 | ||
|
4b7e1b1233 | ||
|
95a8821614 | ||
|
73500f9ebb | ||
|
eae38756c2 | ||
|
8bf7187b89 | ||
|
e7fb8842b5 | ||
|
aaac0cb4a1 | ||
|
67a4279d3f | ||
|
dd4b8a4c89 | ||
|
4cc5a54da3 | ||
|
07add0144e | ||
|
d1c7376b05 | ||
|
a71388d2d6 | ||
|
62176a3bd0 | ||
|
8475a29836 | ||
|
44bc5a139c | ||
|
11f48f3c5b | ||
|
fa4ad1590d | ||
|
47ddc742fc | ||
|
31d994837b | ||
|
83d692e2d3 | ||
|
2a96dde115 | ||
|
f55b3c68ad | ||
|
f2835bb48b | ||
|
857c22db32 | ||
|
fff1645060 | ||
|
4f0c3c81fb | ||
|
421365cfc3 | ||
|
59c5b41f3d | ||
|
267367355f | ||
|
0aa3356499 | ||
|
14a3b35333 | ||
|
77def95659 | ||
|
4e91cd1155 | ||
|
2d923d362f | ||
|
b7544636d4 | ||
|
6220baf230 | ||
|
917307a6a4 | ||
|
5f49b7b492 | ||
|
b03abea402 | ||
|
d4fcc883cb | ||
|
ca2a2697d6 | ||
|
68c6f63e90 | ||
|
0e89fed8ed | ||
|
4d726543d0 | ||
|
2bcc998e90 | ||
|
f707185248 | ||
|
ded30049db | ||
|
5c55e9d11e | ||
|
c35bb11c18 | ||
|
cc19781d27 | ||
|
08d08678e8 | ||
|
11b632a691 | ||
|
4881ef251e | ||
|
5b39abd1a4 | ||
|
f0089097dd | ||
|
d603925ab2 | ||
|
eebe370e0a | ||
|
5240d8cdf5 | ||
|
afbcbb8404 |
@@ -30,8 +30,16 @@ variables:
|
||||
|
||||
workflow:
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
# run merge request pipelines
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
# do not run branch pipelines if corresponding merge requests exist...
|
||||
# (this avoids duplicate pipelines)
|
||||
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
|
||||
when: never
|
||||
# ...but otherwise run branch pipelines
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
# run tag pipelines
|
||||
- if: $CI_COMMIT_TAG
|
||||
|
||||
default:
|
||||
retry:
|
||||
@@ -197,21 +205,30 @@ msys2-mingw64:
|
||||
paths:
|
||||
- "${CI_PROJECT_DIR}/_build/gtkdll.tar.gz"
|
||||
|
||||
macos:
|
||||
# Sadly, this fails regularly, and its failure is never enlightening
|
||||
allow_failure: true
|
||||
macos-x86_64:
|
||||
rules:
|
||||
# Do not run in forks as the runner is not available there.
|
||||
- if: $CI_PROJECT_NAMESPACE == "GNOME"
|
||||
stage: build
|
||||
tags:
|
||||
- macos
|
||||
- macosintel
|
||||
needs: []
|
||||
variables:
|
||||
MESON_FORCE_BACKTRACKE: 1
|
||||
TMPDIR: /Users/Shared/work/tmp
|
||||
SDKROOT: /opt/sdks/MacOSX10.13.4.sdk
|
||||
CCACHE_DIR: /Users/Shared/work/ccache
|
||||
PIP_CACHE_DIR: /Users/Shared/build/cache
|
||||
PIPENV_CACHE_DIR: $PIP_CACHE_DIR
|
||||
PYTHONPYCACHEPREFIX: $PIP_CACHE_DIR
|
||||
before_script:
|
||||
- bash .gitlab-ci/show-info-osx.sh
|
||||
- pip3 install --user meson~=1.0
|
||||
- pip3 install --user ninja
|
||||
- export PATH=/Users/gitlabrunner/Library/Python/3.7/bin:$PATH
|
||||
- export MESON_FORCE_BACKTRACE=1
|
||||
- python3 -m venv .venv
|
||||
- ln -s /opt/cmake/CMake.app/Contents/bin/cmake .venv/bin
|
||||
- ln -s /opt/ccache/ccache .venv/bin
|
||||
- source .venv/bin/activate
|
||||
- pip3 install meson==1.2.0
|
||||
- pip3 install ninja==1.11.1
|
||||
script:
|
||||
- meson setup ${COMMON_MESON_FLAGS}
|
||||
-Dx11-backend=false
|
||||
|
@@ -23,6 +23,7 @@ case "${backend}" in
|
||||
--suite=gtk \
|
||||
--no-suite=failing \
|
||||
--no-suite=flaky \
|
||||
--no-suite=headless \
|
||||
--no-suite=gsk-compare-broadway
|
||||
|
||||
# Store the exit code for the CI run, but always
|
||||
@@ -45,6 +46,7 @@ case "${backend}" in
|
||||
--suite=gtk \
|
||||
--no-suite=failing \
|
||||
--no-suite=flaky \
|
||||
--no-suite=headless \
|
||||
--no-suite=${backend}_failing \
|
||||
--no-suite=gsk-compare-broadway
|
||||
exit_code=$?
|
||||
@@ -67,6 +69,7 @@ case "${backend}" in
|
||||
--suite=gtk \
|
||||
--no-suite=failing \
|
||||
--no-suite=flaky \
|
||||
--no-suite=headless \
|
||||
--no-suite=gsk-compare-opengl
|
||||
|
||||
kill ${server}
|
||||
|
@@ -6,7 +6,7 @@ call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliar
|
||||
|
||||
:: FIXME: make warnings fatal
|
||||
pip3 install --upgrade --user meson~=0.64 || goto :error
|
||||
meson -Ddebug=false -Dmedia-gstreamer=disabled _build || goto :error
|
||||
meson setup -Dbackend_max_links=1 -Ddebug=false -Dmedia-gstreamer=disabled _build || goto :error
|
||||
ninja -C _build || goto :error
|
||||
|
||||
goto :EOF
|
||||
|
@@ -35,8 +35,7 @@ The issue tracker is meant to be used for actionable issues only.
|
||||
|
||||
You should not open a new issue for security related questions.
|
||||
|
||||
When in doubt, send an email to the [security](mailto:security@gnome.org)
|
||||
mailing list.
|
||||
When in doubt, follow [security](https://security.gnome.org/).
|
||||
|
||||
### Bug reports
|
||||
|
||||
@@ -244,13 +243,11 @@ people committing to GTK to follow a few rules:
|
||||
code, you should always ask. If your change is minor and you've been
|
||||
working on GTK for a while it probably isn't necessary to ask. But when
|
||||
in doubt, ask. Even if your change is correct, somebody may know a
|
||||
better way to do things. If you are making changes to GTK, you should
|
||||
be subscribed to the [gtk-devel](https://mail.gnome.org/mailman/listinfo/gtk-devel-list)
|
||||
mailing list; this is a good place to ask about intended changes.
|
||||
The `#gtk` IRC channel on irc.gnome.org is also a good place to find GTK
|
||||
developers to discuss changes, but if you live outside of the EU/US time
|
||||
zones, an email to the gtk-devel mailing list is the most certain and
|
||||
preferred method.
|
||||
better way to do things.
|
||||
The `gtk` [room on matrix](https://matrix.to/#/#gtk:gnome.org) is also a
|
||||
good place to find GTK developers to discuss changes, but if you live
|
||||
outside of the EU/US time zones, the [gtk tag on the GNOME Discourse instance](https://discourse.gnome.org/tag/gtk)
|
||||
is the most certain and preferred method.
|
||||
|
||||
0. Ask _first_.
|
||||
|
||||
|
135
NEWS
@@ -1,6 +1,139 @@
|
||||
Overview of Changes in 4.13.0, xx-xx-xxxx
|
||||
Overview of Changes in 4.13.1, xx-xx-xxxx
|
||||
=========================================
|
||||
|
||||
* GtkTooltip:
|
||||
- Don't cross native boundaries when looking for tooltips
|
||||
|
||||
* GtkCenterLayout, GtkEntry, GtkSearchEntry:
|
||||
- Fix some issues with baseline handling
|
||||
|
||||
* GtkFileChooser:
|
||||
- Make "Visit file" scroll to the file
|
||||
|
||||
* GtkSwitch:
|
||||
- Respect text direction
|
||||
|
||||
* Paths:
|
||||
- GskPathMeasure performance has been improved
|
||||
- Add custom contours for circles, rounded rectangles and rectangles
|
||||
- Simplify GskPathPoint handling
|
||||
- gsk_path_point_get_closest_point now returns the distance as well
|
||||
|
||||
* Input:
|
||||
- Handle (some) single-key compose sequences
|
||||
|
||||
* GSK:
|
||||
- Make the repeated gradients match between GL and cairo
|
||||
- Make rounded rect shrinking match between Vulkan, GL and cairo
|
||||
- Fix parsing of text nodes with color glyphs
|
||||
- Restrict an optimization to the cases where it is crrect
|
||||
- Fix rendering of shadows with opacity
|
||||
- The Vulkan renderer now requires Vulkan 1.2
|
||||
|
||||
* macOS:
|
||||
- Clamp damage regions to the surface size
|
||||
|
||||
* Tools:
|
||||
- gtk4-path-tool gained restrict and reverse commands
|
||||
- gtk4-path-tool show and render can show control points
|
||||
|
||||
* Demos:
|
||||
- Add a demo for hit testing with paths
|
||||
|
||||
* Build:
|
||||
- Fix build problems with C++ compilers
|
||||
|
||||
* Translation updates
|
||||
Brazilian Portuguese
|
||||
British English
|
||||
Catalan
|
||||
Czech
|
||||
Danish
|
||||
Galician
|
||||
Georgian
|
||||
Italian
|
||||
Korean
|
||||
Lithuanian
|
||||
Polish
|
||||
Punjabi
|
||||
Slovenian
|
||||
|
||||
|
||||
Overview of Changes in 4.13.0, 25-08-2023
|
||||
=========================================
|
||||
|
||||
* GskPath, GskPathBuilder, GskPathMeasure:
|
||||
Data types and APIs for path rendering. These APIs are still
|
||||
considered experimental, and may change until 4.14. Please try
|
||||
them out and give us feedback. Documentation can be found
|
||||
here: https://docs.gtk.org/gsk4/paths.html
|
||||
|
||||
* GtkGridView:
|
||||
- Fix a crash when scrolling
|
||||
|
||||
* GtkColumnView:
|
||||
- Fix a refcounting issue in the new scroll_to api
|
||||
|
||||
* GtkTreeView
|
||||
- Fix style classes for sort arrows
|
||||
|
||||
* GtkEntry:
|
||||
- Improve tracking of user changes (for undo)
|
||||
|
||||
* GtkNotebook:
|
||||
- Fix a critical when switching pages
|
||||
|
||||
* GtkColor/FontDialogButton:
|
||||
- Make these widgets activatable
|
||||
|
||||
* GtkMenuButton:
|
||||
- Fix problems with focus handling
|
||||
- Fix problems with DND
|
||||
- Make flags a settable property
|
||||
|
||||
* GtkShortcutsWindow:
|
||||
- Add API to build shortcuts windows programmatically
|
||||
|
||||
* Printing
|
||||
- Fix the cpdb backend build
|
||||
|
||||
* MacOS:
|
||||
- Make file filters work again
|
||||
|
||||
* GSK:
|
||||
- Fix issues with color matrix nodes
|
||||
|
||||
* Wayland:
|
||||
- Fix a crash with compositors other than gnome-shell
|
||||
|
||||
* Deprecations:
|
||||
- Remaining GtkTreeModel-related types
|
||||
|
||||
* Demos:
|
||||
- Add a few path demos to gtk4-demo
|
||||
|
||||
* Tools:
|
||||
- gtk4-path-tool provides a commandline interface for paths
|
||||
|
||||
* Translation updates:
|
||||
Basque
|
||||
Catalan
|
||||
Finnish
|
||||
Galician
|
||||
Georgian
|
||||
German
|
||||
Greek
|
||||
Indonesian
|
||||
Kazakh
|
||||
Persian
|
||||
Polish
|
||||
Romanian
|
||||
Spanish
|
||||
Swedish
|
||||
Turkish
|
||||
Ukrainian
|
||||
|
||||
|
||||
Overview of Changes in 4.12.0, 05-08-2023
|
||||
=========================================
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -223,7 +223,7 @@ delete_messages (gpointer data)
|
||||
static void
|
||||
pop_message (GtkWidget *status)
|
||||
{
|
||||
GList *messages = (GList *) g_object_get_data (G_OBJECT (status), "messages");
|
||||
GList *messages = (GList *) g_object_steal_data (G_OBJECT (status), "messages");
|
||||
|
||||
if (messages)
|
||||
{
|
||||
@@ -241,7 +241,7 @@ static void
|
||||
push_message (GtkWidget *status,
|
||||
const char *message)
|
||||
{
|
||||
GList *messages = (GList *) g_object_get_data (G_OBJECT (status), "messages");
|
||||
GList *messages = (GList *) g_object_steal_data (G_OBJECT (status), "messages");
|
||||
|
||||
gtk_label_set_label (GTK_LABEL (status), message);
|
||||
messages = g_list_prepend (messages, g_strdup (message));
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 985 B After Width: | Height: | Size: 1019 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -127,6 +127,7 @@
|
||||
<file>fishbowl.ui</file>
|
||||
<file>gtkfishbowl.c</file>
|
||||
<file>gtkfishbowl.h</file>
|
||||
<file>tiger.node</file>
|
||||
</gresource>
|
||||
<gresource prefix="/frames">
|
||||
<file>frames.ui</file>
|
||||
@@ -294,6 +295,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>
|
||||
@@ -336,6 +338,11 @@
|
||||
<file>panes.c</file>
|
||||
<file>password_entry.c</file>
|
||||
<file>path_fill.c</file>
|
||||
<file>path_maze.c</file>
|
||||
<file>path_spinner.c</file>
|
||||
<file>path_sweep.c</file>
|
||||
<file>path_walk.c</file>
|
||||
<file>path_text.c</file>
|
||||
<file>peg_solitaire.c</file>
|
||||
<file>pickers.c</file>
|
||||
<file>printing.c</file>
|
||||
@@ -421,6 +428,17 @@
|
||||
<gresource prefix="/fontrendering">
|
||||
<file>fontrendering.ui</file>
|
||||
</gresource>
|
||||
<gresource prefix="/path_sweep">
|
||||
<file>path_sweep.ui</file>
|
||||
<file compressed="true">path_world.txt</file>
|
||||
</gresource>
|
||||
<gresource prefix="/path_walk">
|
||||
<file>path_walk.ui</file>
|
||||
<file compressed="true">path_world.txt</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>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -11,6 +11,9 @@
|
||||
#include "gtkgears.h"
|
||||
#include "gskshaderpaintable.h"
|
||||
|
||||
#include "nodewidget.h"
|
||||
#include "graphwidget.h"
|
||||
|
||||
const char *const css =
|
||||
".blurred-button {"
|
||||
" box-shadow: 0px 0px 5px 10px rgba(0, 0, 0, 0.5);"
|
||||
@@ -71,14 +74,7 @@ create_blurred_button (void)
|
||||
static GtkWidget *
|
||||
create_font_button (void)
|
||||
{
|
||||
GtkFontDialog *dialog;
|
||||
GtkWidget *button;
|
||||
|
||||
dialog = gtk_font_dialog_new ();
|
||||
button = gtk_font_dialog_button_new (dialog);
|
||||
g_object_unref (dialog);
|
||||
|
||||
return button;
|
||||
return gtk_font_dialog_button_new (gtk_font_dialog_new ());
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
@@ -208,6 +204,18 @@ create_menu_button (void)
|
||||
return w;
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
create_tiger (void)
|
||||
{
|
||||
return node_widget_new ("/fishbowl/tiger.node");
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
create_graph (void)
|
||||
{
|
||||
return graph_widget_new ();
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
GtkWidget * (*create_func) (void);
|
||||
@@ -225,6 +233,8 @@ static const struct {
|
||||
{ "Switch", create_switch },
|
||||
{ "Menubutton", create_menu_button },
|
||||
{ "Shader", create_cogs },
|
||||
{ "Tiger", create_tiger },
|
||||
{ "Graph", create_graph },
|
||||
};
|
||||
|
||||
static int selected_widget_type = -1;
|
||||
|
1186
demos/gtk-demo/glyphs.c
Normal file
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
153
demos/gtk-demo/graphwidget.c
Normal file
@@ -0,0 +1,153 @@
|
||||
#include "graphwidget.h"
|
||||
|
||||
struct _GraphWidget
|
||||
{
|
||||
GtkWidget parent_instance;
|
||||
|
||||
GskPath *path;
|
||||
GskStroke *stroke;
|
||||
GdkRGBA color;
|
||||
|
||||
guint tick_cb;
|
||||
guint64 start_time;
|
||||
|
||||
double period;
|
||||
double amplitude;
|
||||
};
|
||||
|
||||
struct _GraphWidgetClass
|
||||
{
|
||||
GtkWidgetClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (GraphWidget, graph_widget, GTK_TYPE_WIDGET)
|
||||
|
||||
static void
|
||||
update_path (GraphWidget *self,
|
||||
float amplitude)
|
||||
{
|
||||
graphene_point_t p[20];
|
||||
GskPathBuilder *builder;
|
||||
|
||||
g_clear_pointer (&self->path, gsk_path_unref);
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
p[i].x = 10 * i;
|
||||
p[i].y = 50;
|
||||
|
||||
if (i % 4 == 1 || i % 4 == 2)
|
||||
{
|
||||
if (i % 8 < 4)
|
||||
p[i].y += amplitude;
|
||||
else
|
||||
p[i].y -= amplitude;
|
||||
}
|
||||
}
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_move_to (builder, p[0].x, p[0].y);
|
||||
|
||||
for (int i = 0; i < 20; i += 4)
|
||||
gsk_path_builder_cubic_to (builder,
|
||||
p[i+1].x, p[i+1].y,
|
||||
p[i+2].x, p[i+2].y,
|
||||
p[i+3].x, p[i+3].y);
|
||||
|
||||
self->path = gsk_path_builder_free_to_path (builder);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
tick_cb (GtkWidget *widget,
|
||||
GdkFrameClock *frame_clock,
|
||||
gpointer user_data)
|
||||
{
|
||||
GraphWidget *self = GRAPH_WIDGET (widget);
|
||||
guint64 now;
|
||||
double angle;
|
||||
|
||||
now = gdk_frame_clock_get_frame_time (frame_clock);
|
||||
|
||||
if (self->start_time == 0)
|
||||
self->start_time = now;
|
||||
|
||||
angle = 360 * (now - self->start_time) / (double)(self->period * G_TIME_SPAN_MINUTE);
|
||||
update_path (self, sin (angle) * self->amplitude);
|
||||
|
||||
gtk_widget_queue_draw (widget);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
graph_widget_init (GraphWidget *self)
|
||||
{
|
||||
self->color.red = g_random_double_range (0, 1);
|
||||
self->color.green = g_random_double_range (0, 1);
|
||||
self->color.blue = g_random_double_range (0, 1);
|
||||
self->color.alpha = 1;
|
||||
|
||||
self->period = g_random_double_range (0.5, 1);
|
||||
self->amplitude = g_random_double_range (10, 25);
|
||||
|
||||
self->stroke = gsk_stroke_new (2);
|
||||
|
||||
update_path (self, 0);
|
||||
|
||||
self->start_time = 0;
|
||||
self->tick_cb = gtk_widget_add_tick_callback (GTK_WIDGET (self), tick_cb, NULL, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
graph_widget_dispose (GObject *object)
|
||||
{
|
||||
GraphWidget *self = GRAPH_WIDGET (object);
|
||||
|
||||
g_clear_pointer (&self->path, gsk_path_unref);
|
||||
gsk_stroke_free (self->stroke);
|
||||
|
||||
G_OBJECT_CLASS (graph_widget_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
graph_widget_snapshot (GtkWidget *widget,
|
||||
GtkSnapshot *snapshot)
|
||||
{
|
||||
GraphWidget *self = GRAPH_WIDGET (widget);
|
||||
|
||||
gtk_snapshot_append_stroke (snapshot, self->path, self->stroke, &self->color);
|
||||
}
|
||||
|
||||
static void
|
||||
graph_widget_measure (GtkWidget *widget,
|
||||
GtkOrientation orientation,
|
||||
int for_size,
|
||||
int *minimum,
|
||||
int *natural,
|
||||
int *minimum_baseline,
|
||||
int *natural_baseline)
|
||||
{
|
||||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||
*minimum = *natural = 200;
|
||||
else
|
||||
*minimum = *natural = 100;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
graph_widget_class_init (GraphWidgetClass *class)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
|
||||
|
||||
object_class->dispose = graph_widget_dispose;
|
||||
|
||||
widget_class->snapshot = graph_widget_snapshot;
|
||||
widget_class->measure = graph_widget_measure;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
graph_widget_new (void)
|
||||
{
|
||||
return g_object_new (GRAPH_TYPE_WIDGET, NULL);
|
||||
}
|
8
demos/gtk-demo/graphwidget.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#define GRAPH_TYPE_WIDGET (graph_widget_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GraphWidget, graph_widget, GRAPH, WIDGET, GtkWidget)
|
||||
|
||||
GtkWidget * graph_widget_new (void);
|
@@ -840,24 +840,24 @@ gtk_gears_unrealize (GtkWidget *widget)
|
||||
GtkGearsPrivate *priv = gtk_gears_get_instance_private ((GtkGears *) widget);
|
||||
|
||||
gtk_gl_area_make_current (glarea);
|
||||
if (gtk_gl_area_get_error (glarea) != NULL)
|
||||
return;
|
||||
if (gtk_gl_area_get_error (glarea) == NULL)
|
||||
{
|
||||
/* Release the resources associated with OpenGL */
|
||||
if (priv->gear_vbo[0] != 0)
|
||||
glDeleteBuffers (1, &(priv->gear_vbo[0]));
|
||||
|
||||
/* Release the resources associated with OpenGL */
|
||||
if (priv->gear_vbo[0] != 0)
|
||||
glDeleteBuffers (1, &(priv->gear_vbo[0]));
|
||||
if (priv->gear_vbo[1] != 0)
|
||||
glDeleteBuffers (1, &(priv->gear_vbo[1]));
|
||||
|
||||
if (priv->gear_vbo[1] != 0)
|
||||
glDeleteBuffers (1, &(priv->gear_vbo[1]));
|
||||
if (priv->gear_vbo[2] != 0)
|
||||
glDeleteBuffers (1, &(priv->gear_vbo[2]));
|
||||
|
||||
if (priv->gear_vbo[2] != 0)
|
||||
glDeleteBuffers (1, &(priv->gear_vbo[2]));
|
||||
if (priv->vao != 0)
|
||||
glDeleteVertexArrays (1, &priv->vao);
|
||||
|
||||
if (priv->vao != 0)
|
||||
glDeleteVertexArrays (1, &priv->vao);
|
||||
|
||||
if (priv->program != 0)
|
||||
glDeleteProgram (priv->program);
|
||||
if (priv->program != 0)
|
||||
glDeleteProgram (priv->program);
|
||||
}
|
||||
|
||||
priv->ModelViewProjectionMatrix_location = 0;
|
||||
priv->NormalMatrix_location = 0;
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -34,6 +34,7 @@ demos = files([
|
||||
'gestures.c',
|
||||
'glarea.c',
|
||||
'gltransition.c',
|
||||
'glyphs.c',
|
||||
'headerbar.c',
|
||||
'hypertext.c',
|
||||
'iconscroll.c',
|
||||
@@ -73,6 +74,11 @@ demos = files([
|
||||
'panes.c',
|
||||
'password_entry.c',
|
||||
'path_fill.c',
|
||||
'path_maze.c',
|
||||
'path_spinner.c',
|
||||
'path_sweep.c',
|
||||
'path_walk.c',
|
||||
'path_text.c',
|
||||
'peg_solitaire.c',
|
||||
'pickers.c',
|
||||
'printing.c',
|
||||
@@ -137,6 +143,8 @@ extra_demo_sources = files([
|
||||
'unicode-names.c',
|
||||
'suggestionentry.c',
|
||||
'language-names.c',
|
||||
'nodewidget.c',
|
||||
'graphwidget.c',
|
||||
])
|
||||
|
||||
if os_unix
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.7 KiB |
76
demos/gtk-demo/nodewidget.c
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "nodewidget.h"
|
||||
|
||||
struct _NodeWidget
|
||||
{
|
||||
GtkWidget parent_instance;
|
||||
|
||||
GskRenderNode *node;
|
||||
};
|
||||
|
||||
struct _NodeWidgetClass
|
||||
{
|
||||
GtkWidgetClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (NodeWidget, node_widget, GTK_TYPE_WIDGET)
|
||||
|
||||
static void
|
||||
node_widget_init (NodeWidget *self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
node_widget_dispose (GObject *object)
|
||||
{
|
||||
NodeWidget *self = NODE_WIDGET (object);
|
||||
|
||||
gsk_render_node_unref (self->node);
|
||||
|
||||
G_OBJECT_CLASS (node_widget_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
node_widget_snapshot (GtkWidget *widget,
|
||||
GtkSnapshot *snapshot)
|
||||
{
|
||||
NodeWidget *self = NODE_WIDGET (widget);
|
||||
|
||||
gtk_snapshot_append_node (snapshot, self->node);
|
||||
}
|
||||
|
||||
static void
|
||||
node_widget_class_init (NodeWidgetClass *class)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
|
||||
|
||||
object_class->dispose = node_widget_dispose;
|
||||
|
||||
widget_class->snapshot = node_widget_snapshot;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
node_widget_new (const char *resource)
|
||||
{
|
||||
NodeWidget *self;
|
||||
GBytes *bytes;
|
||||
GskRenderNode *node;
|
||||
graphene_rect_t bounds;
|
||||
float scale;
|
||||
GskTransform *transform;
|
||||
|
||||
self = g_object_new (NODE_TYPE_WIDGET, NULL);
|
||||
|
||||
bytes = g_resources_lookup_data (resource, 0, NULL);
|
||||
node = gsk_render_node_deserialize (bytes, NULL, NULL);
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
gsk_render_node_get_bounds (node, &bounds);
|
||||
scale = MIN (100.0/bounds.size.width, 100.0/bounds.size.height);
|
||||
transform = gsk_transform_scale (NULL, scale, scale);
|
||||
self->node = gsk_transform_node_new (node, transform);
|
||||
gsk_transform_unref (transform);
|
||||
gsk_render_node_unref (node);
|
||||
|
||||
return GTK_WIDGET (self);
|
||||
}
|
8
demos/gtk-demo/nodewidget.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#define NODE_TYPE_WIDGET (node_widget_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (NodeWidget, node_widget, NODE, WIDGET, GtkWidget)
|
||||
|
||||
GtkWidget * node_widget_new (const char *file);
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -177,7 +177,7 @@ do_path_fill (GtkWidget *do_widget)
|
||||
|
||||
window = gtk_window_new ();
|
||||
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Path Fill");
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Fill and Stroke");
|
||||
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
|
||||
|
||||
paintable = gtk_logo_paintable_new ();
|
||||
|
369
demos/gtk-demo/path_maze.c
Normal file
@@ -0,0 +1,369 @@
|
||||
/* Path/Maze
|
||||
*
|
||||
* This demo shows how to use a GskPath to create a maze and use
|
||||
* gsk_path_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 "config.h"
|
||||
#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
|
||||
celebrate (gboolean win)
|
||||
{
|
||||
char *path;
|
||||
GtkMediaStream *stream;
|
||||
|
||||
if (win)
|
||||
path = g_build_filename (GTK_DATADIR, "sounds", "freedesktop", "stereo", "complete.oga", NULL);
|
||||
else
|
||||
path = g_build_filename (GTK_DATADIR, "sounds", "freedesktop", "stereo", "suspend-error.oga", NULL);
|
||||
stream = gtk_media_file_new_for_filename (path);
|
||||
gtk_media_stream_set_volume (stream, 1.0);
|
||||
gtk_media_stream_play (stream);
|
||||
|
||||
g_signal_connect (stream, "notify::ended", G_CALLBACK (g_object_unref), NULL);
|
||||
g_free (path);
|
||||
}
|
||||
|
||||
static void
|
||||
pointer_motion (GtkEventControllerMotion *controller,
|
||||
double x,
|
||||
double y,
|
||||
GtkMaze *self)
|
||||
{
|
||||
GskPathPoint point;
|
||||
float distance;
|
||||
|
||||
if (!self->active)
|
||||
return;
|
||||
|
||||
if (gsk_path_get_closest_point (self->path,
|
||||
&GRAPHENE_POINT_INIT (x, y),
|
||||
INFINITY,
|
||||
&point,
|
||||
&distance))
|
||||
{
|
||||
if (distance < MAZE_STROKE_SIZE_ACTIVE / 2.f)
|
||||
return;
|
||||
}
|
||||
|
||||
celebrate (FALSE);
|
||||
|
||||
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] = { FALSE, FALSE, FALSE, FALSE };
|
||||
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_window_present (GTK_WINDOW (window));
|
||||
else
|
||||
gtk_window_destroy (GTK_WINDOW (window));
|
||||
|
||||
return window;
|
||||
}
|
323
demos/gtk-demo/path_spinner.c
Normal file
@@ -0,0 +1,323 @@
|
||||
/* Path/Spinner
|
||||
*
|
||||
* This demo shows how to use GskPath to draw a simple animation
|
||||
* that could be used as a spinner.
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "paintable.h"
|
||||
|
||||
#undef SHOW_CONTROLS
|
||||
|
||||
#define GTK_TYPE_SPINNER_PAINTABLE (gtk_spinner_paintable_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GtkSpinnerPaintable, gtk_spinner_paintable, GTK, SPINNER_PAINTABLE, GObject)
|
||||
|
||||
struct _GtkSpinnerPaintable
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
gint64 start_time;
|
||||
|
||||
int width;
|
||||
double angle;
|
||||
double completion;
|
||||
|
||||
GskPath *circle;
|
||||
GskPath *path;
|
||||
GskStroke *stroke;
|
||||
GdkRGBA color;
|
||||
GdkRGBA circle_color;
|
||||
#ifdef SHOW_CONTROLS
|
||||
GskPath *controls;
|
||||
GdkRGBA control_color;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct _GtkSpinnerPaintableClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
static int
|
||||
gtk_spinner_paintable_get_intrinsic_width (GdkPaintable *paintable)
|
||||
{
|
||||
GtkSpinnerPaintable *self = GTK_SPINNER_PAINTABLE (paintable);
|
||||
|
||||
return self->width;
|
||||
}
|
||||
|
||||
static int
|
||||
gtk_spinner_paintable_get_intrinsic_height (GdkPaintable *paintable)
|
||||
{
|
||||
GtkSpinnerPaintable *self = GTK_SPINNER_PAINTABLE (paintable);
|
||||
|
||||
return self->width;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_snapshot (GdkPaintable *paintable,
|
||||
GdkSnapshot *snapshot,
|
||||
double width,
|
||||
double height)
|
||||
{
|
||||
GtkSpinnerPaintable *self = GTK_SPINNER_PAINTABLE (paintable);
|
||||
|
||||
gtk_snapshot_append_stroke (snapshot, self->circle, self->stroke, &self->circle_color);
|
||||
gtk_snapshot_append_stroke (snapshot, self->path, self->stroke, &self->color);
|
||||
#ifdef SHOW_CONTROLS
|
||||
GskStroke *stroke = gsk_stroke_new (1);
|
||||
gtk_snapshot_append_stroke (snapshot, self->controls, stroke, &self->control_color);
|
||||
gsk_stroke_free (stroke);
|
||||
#endif
|
||||
}
|
||||
|
||||
static GdkPaintableFlags
|
||||
gtk_spinner_paintable_get_flags (GdkPaintable *paintable)
|
||||
{
|
||||
return GDK_PAINTABLE_STATIC_SIZE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_paintable_init (GdkPaintableInterface *iface)
|
||||
{
|
||||
iface->get_intrinsic_width = gtk_spinner_paintable_get_intrinsic_width;
|
||||
iface->get_intrinsic_height = gtk_spinner_paintable_get_intrinsic_height;
|
||||
iface->snapshot = gtk_spinner_paintable_snapshot;
|
||||
iface->get_flags = gtk_spinner_paintable_get_flags;
|
||||
}
|
||||
|
||||
/* When defining the GType, we need to implement the GdkPaintable interface */
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkSpinnerPaintable, gtk_spinner_paintable, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
|
||||
gtk_spinner_paintable_paintable_init))
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_dispose (GObject *object)
|
||||
{
|
||||
GtkSpinnerPaintable *self = GTK_SPINNER_PAINTABLE (object);
|
||||
|
||||
gsk_path_unref (self->circle);
|
||||
gsk_path_unref (self->path);
|
||||
#ifdef SHOW_CONTROLS
|
||||
gsk_path_unref (self->controls);
|
||||
#endif
|
||||
|
||||
gsk_stroke_free (self->stroke);
|
||||
|
||||
G_OBJECT_CLASS (gtk_spinner_paintable_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_class_init (GtkSpinnerPaintableClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = gtk_spinner_paintable_dispose;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_init (GtkSpinnerPaintable *self)
|
||||
{
|
||||
}
|
||||
|
||||
static GdkPaintable *
|
||||
gtk_spinner_paintable_new (void)
|
||||
{
|
||||
GtkSpinnerPaintable *self;
|
||||
GskPathBuilder *builder;
|
||||
|
||||
self = g_object_new (GTK_TYPE_SPINNER_PAINTABLE, NULL);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (50, 50), 40);
|
||||
self->circle = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
self->width = 100;
|
||||
self->angle = 0;
|
||||
self->completion = 1;
|
||||
gdk_rgba_parse (&self->color, "green");
|
||||
gdk_rgba_parse (&self->circle_color, "lightgray");
|
||||
#ifdef SHOW_CONTROLS
|
||||
gdk_rgba_parse (&self->control_color, "black");
|
||||
#endif
|
||||
self->stroke = gsk_stroke_new (5);
|
||||
|
||||
return GDK_PAINTABLE (self);
|
||||
}
|
||||
|
||||
#ifdef SHOW_CONTROLS
|
||||
static gboolean
|
||||
add_controls (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight,
|
||||
gpointer data)
|
||||
{
|
||||
GskPathBuilder *builder = data;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
case GSK_PATH_LINE:
|
||||
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
case GSK_PATH_CONIC:
|
||||
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
|
||||
gsk_path_builder_line_to (builder, pts[2].x, pts[2].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
|
||||
gsk_path_builder_line_to (builder, pts[2].x, pts[2].y);
|
||||
gsk_path_builder_line_to (builder, pts[3].x, pts[3].y);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
update_path (GtkSpinnerPaintable *self)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPathPoint start, end;
|
||||
graphene_point_t p0, p1;
|
||||
float start_angle, end_angle;
|
||||
|
||||
start_angle = self->angle;
|
||||
end_angle = fmod (self->angle + 360 * self->completion / 100, 360);
|
||||
|
||||
p0 = GRAPHENE_POINT_INIT (50 + 40 * cos (M_PI * start_angle / 180),
|
||||
50 + 40 * sin (M_PI * start_angle / 180));
|
||||
p1 = GRAPHENE_POINT_INIT (50 + 40 * cos (M_PI * end_angle / 180),
|
||||
50 + 40 * sin (M_PI * end_angle / 180));
|
||||
|
||||
g_clear_pointer (&self->path, gsk_path_unref);
|
||||
|
||||
gsk_path_get_closest_point (self->circle, &p0, INFINITY, &start, NULL);
|
||||
gsk_path_get_closest_point (self->circle, &p1, INFINITY, &end, NULL);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_segment (builder, self->circle, &start, &end);
|
||||
self->path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
#ifdef SHOW_CONTROLS
|
||||
g_clear_pointer (&self->controls, gsk_path_unref);
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_foreach (self->path, -1, add_controls, builder);
|
||||
self->controls = gsk_path_builder_free_to_path (builder);
|
||||
#endif
|
||||
|
||||
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_set_completion (GtkSpinnerPaintable *self,
|
||||
float completion)
|
||||
{
|
||||
self->completion = CLAMP (completion, 0, 100);
|
||||
update_path (self);
|
||||
}
|
||||
|
||||
static float
|
||||
gtk_spinner_paintable_get_completion (GtkSpinnerPaintable *self)
|
||||
{
|
||||
return self->completion;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_set_frame_time (GtkSpinnerPaintable *self,
|
||||
gint64 time)
|
||||
{
|
||||
double delta;
|
||||
|
||||
if (self->start_time == 0)
|
||||
self->start_time = time;
|
||||
|
||||
delta = (time - self->start_time) / (double) G_TIME_SPAN_SECOND;
|
||||
self->angle = fmod (60 * delta, 360);
|
||||
update_path (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
tick_cb (GtkWidget *widget,
|
||||
GdkFrameClock *clock,
|
||||
gpointer data)
|
||||
{
|
||||
GtkSpinnerPaintable *self = data;
|
||||
|
||||
gtk_spinner_paintable_set_frame_time (self, gdk_frame_clock_get_frame_time (clock));
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
progress_timeout (gpointer data)
|
||||
{
|
||||
GtkSpinnerPaintable *self = data;
|
||||
static float progress_delta = 0.5;
|
||||
float progress;
|
||||
|
||||
progress = gtk_spinner_paintable_get_completion (self);
|
||||
if (progress >= 100 || progress <= 0)
|
||||
progress_delta = -progress_delta;
|
||||
|
||||
gtk_spinner_paintable_set_completion (self, progress + progress_delta);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
unset_timeout (gpointer data)
|
||||
{
|
||||
g_source_remove (GPOINTER_TO_UINT (data));
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
do_path_spinner (GtkWidget *do_widget)
|
||||
{
|
||||
static GtkWidget *window = NULL;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
GtkWidget *picture;
|
||||
GdkPaintable *paintable;
|
||||
guint timeout_id;
|
||||
|
||||
window = gtk_window_new ();
|
||||
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Spinner");
|
||||
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
|
||||
|
||||
paintable = gtk_spinner_paintable_new ();
|
||||
picture = gtk_picture_new_for_paintable (paintable);
|
||||
gtk_picture_set_content_fit (GTK_PICTURE (picture), GTK_CONTENT_FIT_CONTAIN);
|
||||
gtk_picture_set_can_shrink (GTK_PICTURE (picture), FALSE);
|
||||
g_object_unref (paintable);
|
||||
|
||||
gtk_widget_add_tick_callback (picture, tick_cb, paintable, NULL);
|
||||
timeout_id = g_timeout_add (100, progress_timeout, paintable);
|
||||
|
||||
g_object_set_data_full (G_OBJECT (picture), "timeout", GUINT_TO_POINTER (timeout_id), unset_timeout);
|
||||
|
||||
gtk_window_set_child (GTK_WINDOW (window), picture);
|
||||
}
|
||||
|
||||
if (!gtk_widget_get_visible (window))
|
||||
gtk_window_present (GTK_WINDOW (window));
|
||||
else
|
||||
gtk_window_destroy (GTK_WINDOW (window));
|
||||
|
||||
return window;
|
||||
}
|
319
demos/gtk-demo/path_sweep.c
Normal file
@@ -0,0 +1,319 @@
|
||||
/* Path/Sweep
|
||||
*
|
||||
* This demo shows how path intersections can be used.
|
||||
*
|
||||
* The world map that is used here is a path with 211 lines and 1569 cubic
|
||||
* Bėzier segments in 121 contours.
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#define GTK_TYPE_PATH_SWEEP (gtk_path_sweep_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GtkPathSweep, gtk_path_sweep, GTK, PATH_SWEEP, GtkWidget)
|
||||
|
||||
#define POINT_SIZE 8
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_PATH,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
struct _GtkPathSweep
|
||||
{
|
||||
GtkWidget parent_instance;
|
||||
|
||||
GskPath *path;
|
||||
graphene_rect_t bounds;
|
||||
float y_pos;
|
||||
gboolean in;
|
||||
};
|
||||
|
||||
struct _GtkPathSweepClass
|
||||
{
|
||||
GtkWidgetClass parent_class;
|
||||
};
|
||||
|
||||
static GParamSpec *properties[N_PROPS] = { NULL, };
|
||||
|
||||
G_DEFINE_TYPE (GtkPathSweep, gtk_path_sweep, GTK_TYPE_WIDGET)
|
||||
|
||||
static gboolean
|
||||
intersection_cb (GskPath *path1,
|
||||
const GskPathPoint *point1,
|
||||
GskPath *path2,
|
||||
const GskPathPoint *point2,
|
||||
GskPathIntersection kind,
|
||||
gpointer data)
|
||||
{
|
||||
GskPathBuilder *builder = data;
|
||||
graphene_point_t p;
|
||||
|
||||
gsk_path_point_get_position (point1, path1, &p);
|
||||
gsk_path_builder_add_circle (builder, &p, 4);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GskPath *
|
||||
get_intersection_path (GskPath *path1,
|
||||
GskPath *path2)
|
||||
{
|
||||
GskPathBuilder *builder = gsk_path_builder_new ();
|
||||
|
||||
gsk_path_foreach_intersection (path1, path2, intersection_cb, builder);
|
||||
|
||||
return gsk_path_builder_to_path (builder);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_snapshot (GtkWidget *widget,
|
||||
GtkSnapshot *snapshot)
|
||||
{
|
||||
GtkPathSweep *self = GTK_PATH_SWEEP (widget);
|
||||
GskStroke *stroke;
|
||||
|
||||
if (self->path == NULL)
|
||||
return;
|
||||
|
||||
gtk_snapshot_save (snapshot);
|
||||
|
||||
stroke = gsk_stroke_new (2.0);
|
||||
|
||||
gtk_snapshot_append_stroke (snapshot, self->path, stroke, &(GdkRGBA) { 0, 0, 0, 1 });
|
||||
|
||||
if (self->in)
|
||||
{
|
||||
graphene_rect_t bounds;
|
||||
GskPathBuilder *builder;
|
||||
GskPath *line, *isecs;
|
||||
|
||||
gsk_path_get_stroke_bounds (self->path, stroke, &bounds);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_move_to (builder, bounds.origin.x, bounds.origin.y + self->y_pos);
|
||||
gsk_path_builder_line_to (builder, bounds.origin.x + bounds.size.width, bounds.origin.y + self->y_pos);
|
||||
line = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
gtk_snapshot_append_stroke (snapshot, line, stroke, &(GdkRGBA) { 0, 0, 0, 1 });
|
||||
|
||||
isecs = get_intersection_path (self->path, line);
|
||||
|
||||
gtk_snapshot_append_fill (snapshot, isecs, GSK_FILL_RULE_WINDING, &(GdkRGBA) { 1, 0, 0, 1 });
|
||||
gtk_snapshot_append_stroke (snapshot, isecs, stroke, &(GdkRGBA) { 0, 0, 0, 1 });
|
||||
|
||||
gsk_path_unref (isecs);
|
||||
gsk_path_unref (line);
|
||||
}
|
||||
|
||||
gsk_stroke_free (stroke);
|
||||
|
||||
gtk_snapshot_restore (snapshot);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_measure (GtkWidget *widget,
|
||||
GtkOrientation orientation,
|
||||
int for_size,
|
||||
int *minimum,
|
||||
int *natural,
|
||||
int *minimum_baseline,
|
||||
int *natural_baseline)
|
||||
{
|
||||
GtkPathSweep *self = GTK_PATH_SWEEP (widget);
|
||||
|
||||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||
*minimum = *natural = (int) ceilf (self->bounds.size.width);
|
||||
else
|
||||
*minimum = *natural = (int) ceilf (self->bounds.size.height);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_set_path (GtkPathSweep *self,
|
||||
GskPath *path)
|
||||
{
|
||||
if (self->path == path)
|
||||
return;
|
||||
|
||||
g_clear_pointer (&self->path, gsk_path_unref);
|
||||
graphene_rect_init (&self->bounds, 0, 0, 0, 0);
|
||||
if (path)
|
||||
{
|
||||
GskStroke *stroke;
|
||||
|
||||
self->path = gsk_path_ref (path);
|
||||
stroke = gsk_stroke_new (2.0);
|
||||
gsk_path_get_stroke_bounds (path, stroke, &self->bounds);
|
||||
gsk_stroke_free (stroke);
|
||||
}
|
||||
|
||||
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PATH]);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
|
||||
{
|
||||
GtkPathSweep *self = GTK_PATH_SWEEP (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PATH:
|
||||
gtk_path_sweep_set_path (self, g_value_get_boxed (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkPathSweep *self = GTK_PATH_SWEEP (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PATH:
|
||||
g_value_set_boxed (value, self->path);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_dispose (GObject *object)
|
||||
{
|
||||
GtkPathSweep *self = GTK_PATH_SWEEP (object);
|
||||
|
||||
g_clear_pointer (&self->path, gsk_path_unref);
|
||||
|
||||
G_OBJECT_CLASS (gtk_path_sweep_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_class_init (GtkPathSweepClass *klass)
|
||||
{
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = gtk_path_sweep_dispose;
|
||||
object_class->set_property = gtk_path_sweep_set_property;
|
||||
object_class->get_property = gtk_path_sweep_get_property;
|
||||
|
||||
widget_class->snapshot = gtk_path_sweep_snapshot;
|
||||
widget_class->measure = gtk_path_sweep_measure;
|
||||
|
||||
properties[PROP_PATH] =
|
||||
g_param_spec_boxed ("path", NULL, NULL,
|
||||
GSK_TYPE_PATH,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
motion_cb (GtkEventControllerMotion *controller,
|
||||
double x,
|
||||
double y,
|
||||
gpointer data)
|
||||
{
|
||||
GtkPathSweep *self = data;
|
||||
|
||||
self->y_pos = y;
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
enter_cb (GtkEventControllerMotion *controller,
|
||||
double x,
|
||||
double y,
|
||||
gpointer data)
|
||||
{
|
||||
GtkPathSweep *self = data;
|
||||
|
||||
self->in = TRUE;
|
||||
self->y_pos = y;
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
leave_cb (GtkEventControllerMotion *controller,
|
||||
gpointer data)
|
||||
{
|
||||
GtkPathSweep *self = data;
|
||||
|
||||
self->in = FALSE;
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_init (GtkPathSweep *self)
|
||||
{
|
||||
GtkEventController *controller;
|
||||
|
||||
/* Data taken from
|
||||
* https://commons.wikimedia.org/wiki/Maps_of_the_world#/media/File:Simplified_blank_world_map_without_Antartica_(no_borders).svg
|
||||
*/
|
||||
GBytes *data = g_resources_lookup_data ("/path_sweep/path_world.txt", 0, NULL);
|
||||
GskPath *path = gsk_path_parse (g_bytes_get_data (data, NULL));
|
||||
g_bytes_unref (data);
|
||||
gtk_path_sweep_set_path (self, path);
|
||||
gsk_path_unref (path);
|
||||
|
||||
controller = gtk_event_controller_motion_new ();
|
||||
g_signal_connect (controller, "motion", G_CALLBACK (motion_cb), self);
|
||||
g_signal_connect (controller, "enter", G_CALLBACK (enter_cb), self);
|
||||
g_signal_connect (controller, "leave", G_CALLBACK (leave_cb), self);
|
||||
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
gtk_path_sweep_new (void)
|
||||
{
|
||||
GtkPathSweep *self;
|
||||
|
||||
self = g_object_new (GTK_TYPE_PATH_SWEEP, NULL);
|
||||
|
||||
return GTK_WIDGET (self);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
do_path_sweep (GtkWidget *do_widget)
|
||||
{
|
||||
static GtkWidget *window = NULL;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
GtkBuilder *builder;
|
||||
|
||||
g_type_ensure (GTK_TYPE_PATH_SWEEP);
|
||||
|
||||
builder = gtk_builder_new_from_resource ("/path_sweep/path_sweep.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_window_present (GTK_WINDOW (window));
|
||||
else
|
||||
gtk_window_destroy (GTK_WINDOW (window));
|
||||
|
||||
return window;
|
||||
}
|
17
demos/gtk-demo/path_sweep.ui
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkWindow" id="window">
|
||||
<property name="title" translatable="yes">World Map</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkPathSweep" id="view">
|
||||
<property name="hexpand">true</property>
|
||||
<property name="vexpand">true</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
584
demos/gtk-demo/path_text.c
Normal file
@@ -0,0 +1,584 @@
|
||||
/* Path/Text
|
||||
*
|
||||
* This demo shows how to use GskPath to transform a path along another path.
|
||||
*
|
||||
* It also demonstrates that paths can be filled with more interesting
|
||||
* content than just plain colors.
|
||||
*/
|
||||
|
||||
#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;
|
||||
|
||||
GskPath *line_path;
|
||||
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,
|
||||
graphene_point_t *out_offset)
|
||||
{
|
||||
PangoLayout *layout;
|
||||
PangoFontDescription *desc;
|
||||
GskPathBuilder *builder;
|
||||
GskPath *result;
|
||||
|
||||
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);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_layout (builder, layout);
|
||||
result = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
if (out_offset)
|
||||
graphene_point_init (out_offset, 0, - pango_layout_get_baseline (layout) / (double) PANGO_SCALE);
|
||||
g_object_unref (layout);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GskPathMeasure *measure;
|
||||
GskPathBuilder *builder;
|
||||
graphene_point_t offset;
|
||||
double scale;
|
||||
} GtkPathTransform;
|
||||
|
||||
static void
|
||||
gtk_path_transform_point (GskPathMeasure *measure,
|
||||
const graphene_point_t *pt,
|
||||
const graphene_point_t *offset,
|
||||
float scale,
|
||||
graphene_point_t *res)
|
||||
{
|
||||
graphene_vec2_t tangent;
|
||||
GskPathPoint point;
|
||||
|
||||
if (gsk_path_measure_get_point (measure, (pt->x + offset->x) * scale, &point))
|
||||
{
|
||||
GskPath *path = gsk_path_measure_get_path (measure);
|
||||
|
||||
gsk_path_point_get_position (&point, path, res);
|
||||
gsk_path_point_get_tangent (&point, path, GSK_PATH_TO_END, &tangent);
|
||||
|
||||
res->x -= (pt->y + offset->y) * scale * graphene_vec2_get_y (&tangent);
|
||||
res->y += (pt->y + offset->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->offset, 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->offset, transform->scale, &res);
|
||||
gsk_path_builder_line_to (transform->builder, res.x, res.y);
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
{
|
||||
graphene_point_t res[2];
|
||||
gtk_path_transform_point (transform->measure, &pts[1], &transform->offset, transform->scale, &res[0]);
|
||||
gtk_path_transform_point (transform->measure, &pts[2], &transform->offset, transform->scale, &res[1]);
|
||||
gsk_path_builder_quad_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y);
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
{
|
||||
graphene_point_t res[3];
|
||||
gtk_path_transform_point (transform->measure, &pts[1], &transform->offset, transform->scale, &res[0]);
|
||||
gtk_path_transform_point (transform->measure, &pts[2], &transform->offset, transform->scale, &res[1]);
|
||||
gtk_path_transform_point (transform->measure, &pts[3], &transform->offset, transform->scale, &res[2]);
|
||||
gsk_path_builder_cubic_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->offset, transform->scale, &res[0]);
|
||||
gtk_path_transform_point (transform->measure, &pts[3], &transform->offset, 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 (GskPath *line_path,
|
||||
GskPath *path,
|
||||
const graphene_point_t *offset)
|
||||
{
|
||||
GskPathMeasure *measure = gsk_path_measure_new (line_path);
|
||||
GtkPathTransform transform = { measure, gsk_path_builder_new (), *offset };
|
||||
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, -1, gtk_path_transform_op, &transform);
|
||||
|
||||
gsk_path_measure_unref (measure);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_widget_create_text_path (GtkPathWidget *self)
|
||||
{
|
||||
GskPath *path;
|
||||
graphene_point_t offset;
|
||||
|
||||
gtk_path_widget_clear_text_path (self);
|
||||
|
||||
path = create_path_from_text (GTK_WIDGET (self), self->text, &offset);
|
||||
self->text_path = gtk_path_transform (self->line_path, path, &offset);
|
||||
|
||||
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_cubic_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);
|
||||
|
||||
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.6 }, &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)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
|
||||
/* 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);
|
||||
|
||||
/* 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)
|
||||
{
|
||||
GskPathPoint point;
|
||||
|
||||
if (gsk_path_get_closest_point (self->line_path,
|
||||
&GRAPHENE_POINT_INIT (x, y),
|
||||
INFINITY,
|
||||
&point,
|
||||
NULL))
|
||||
{
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
pointer_leave (GtkEventControllerMotion *controller,
|
||||
GtkPathWidget *self)
|
||||
{
|
||||
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->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_window_present (GTK_WINDOW (window));
|
||||
else
|
||||
gtk_window_destroy (GTK_WINDOW (window));
|
||||
|
||||
return window;
|
||||
}
|
38
demos/gtk-demo/path_text.ui
Normal 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>
|
373
demos/gtk-demo/path_walk.c
Normal file
@@ -0,0 +1,373 @@
|
||||
/* Path/Walk
|
||||
*
|
||||
* This demo draws a world map and shows how to animate objects along a GskPath.
|
||||
*
|
||||
* The world map that is used here is a path with 211 lines and 1569 cubic
|
||||
* Bėzier segments in 121 contours.
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#define GTK_TYPE_PATH_WALK (gtk_path_walk_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GtkPathWalk, gtk_path_walk, GTK, PATH_WALK, GtkWidget)
|
||||
|
||||
#define POINT_SIZE 8
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_N_POINTS,
|
||||
PROP_PATH,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
struct _GtkPathWalk
|
||||
{
|
||||
GtkWidget parent_instance;
|
||||
|
||||
GskPath *path;
|
||||
GskPathMeasure *measure;
|
||||
graphene_rect_t bounds;
|
||||
GskPath *arrow_path;
|
||||
guint n_points;
|
||||
};
|
||||
|
||||
struct _GtkPathWalkClass
|
||||
{
|
||||
GtkWidgetClass parent_class;
|
||||
};
|
||||
|
||||
static GParamSpec *properties[N_PROPS] = { NULL, };
|
||||
|
||||
G_DEFINE_TYPE (GtkPathWalk, gtk_path_walk, GTK_TYPE_WIDGET)
|
||||
|
||||
static void
|
||||
rgba_init_from_hsla (GdkRGBA *rgba,
|
||||
float hue,
|
||||
float saturation,
|
||||
float lightness,
|
||||
float alpha)
|
||||
{
|
||||
float m1, m2;
|
||||
|
||||
if (lightness <= 0.5)
|
||||
m2 = lightness * (1 + saturation);
|
||||
else
|
||||
m2 = lightness + saturation - lightness * saturation;
|
||||
m1 = 2 * lightness - m2;
|
||||
|
||||
rgba->alpha = alpha;
|
||||
|
||||
if (saturation == 0)
|
||||
{
|
||||
rgba->red = lightness;
|
||||
rgba->green = lightness;
|
||||
rgba->blue = lightness;
|
||||
}
|
||||
else
|
||||
{
|
||||
hue = hue + 120;
|
||||
while (hue > 360)
|
||||
hue -= 360;
|
||||
while (hue < 0)
|
||||
hue += 360;
|
||||
|
||||
if (hue < 60)
|
||||
rgba->red = m1 + (m2 - m1) * hue / 60;
|
||||
else if (hue < 180)
|
||||
rgba->red = m2;
|
||||
else if (hue < 240)
|
||||
rgba->red = m1 + (m2 - m1) * (240 - hue) / 60;
|
||||
else
|
||||
rgba->red = m1;
|
||||
|
||||
hue -= 120;
|
||||
if (hue < 0)
|
||||
hue += 360;
|
||||
|
||||
if (hue < 60)
|
||||
rgba->green = m1 + (m2 - m1) * hue / 60;
|
||||
else if (hue < 180)
|
||||
rgba->green = m2;
|
||||
else if (hue < 240)
|
||||
rgba->green = m1 + (m2 - m1) * (240 - hue) / 60;
|
||||
else
|
||||
rgba->green = m1;
|
||||
|
||||
hue -= 120;
|
||||
if (hue < 0)
|
||||
hue += 360;
|
||||
|
||||
if (hue < 60)
|
||||
rgba->blue = m1 + (m2 - m1) * hue / 60;
|
||||
else if (hue < 180)
|
||||
rgba->blue = m2;
|
||||
else if (hue < 240)
|
||||
rgba->blue = m1 + (m2 - m1) * (240 - hue) / 60;
|
||||
else
|
||||
rgba->blue = m1;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_walk_snapshot (GtkWidget *widget,
|
||||
GtkSnapshot *snapshot)
|
||||
{
|
||||
GtkPathWalk *self = GTK_PATH_WALK (widget);
|
||||
double width = gtk_widget_get_width (widget);
|
||||
double height = gtk_widget_get_height (widget);
|
||||
float length, progress;
|
||||
GskStroke *stroke;
|
||||
guint i;
|
||||
|
||||
if (self->path == NULL)
|
||||
return;
|
||||
|
||||
gtk_snapshot_save (snapshot);
|
||||
|
||||
stroke = gsk_stroke_new (2.0);
|
||||
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
|
||||
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
|
||||
gtk_snapshot_pop (snapshot);
|
||||
gsk_stroke_free (stroke);
|
||||
|
||||
length = gsk_path_measure_get_length (self->measure);
|
||||
progress = 25.f * gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget)) / G_USEC_PER_SEC;
|
||||
|
||||
stroke = gsk_stroke_new (1.0);
|
||||
for (i = 0; i < self->n_points; i++)
|
||||
{
|
||||
GskPathPoint point;
|
||||
graphene_point_t position;
|
||||
float angle;
|
||||
GdkRGBA color;
|
||||
float distance;
|
||||
|
||||
distance = i * length / self->n_points;
|
||||
distance = fmod (distance + progress, length);
|
||||
|
||||
gsk_path_measure_get_point (self->measure, distance, &point);
|
||||
gsk_path_point_get_position (&point, self->path, &position);
|
||||
angle = gsk_path_point_get_rotation (&point, self->path, GSK_PATH_FROM_START);
|
||||
rgba_init_from_hsla (&color, 360.f * i / self->n_points, 1, 0.5, 1);
|
||||
|
||||
gtk_snapshot_save (snapshot);
|
||||
gtk_snapshot_translate (snapshot, &position);
|
||||
gtk_snapshot_rotate (snapshot, angle);
|
||||
gtk_snapshot_append_fill (snapshot, self->arrow_path, GSK_FILL_RULE_EVEN_ODD, &color);
|
||||
gtk_snapshot_append_stroke (snapshot, self->arrow_path, stroke, &(GdkRGBA) { 0, 0, 0, 1 });
|
||||
gtk_snapshot_restore (snapshot);
|
||||
}
|
||||
|
||||
gsk_stroke_free (stroke);
|
||||
gtk_snapshot_restore (snapshot);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_walk_measure (GtkWidget *widget,
|
||||
GtkOrientation orientation,
|
||||
int for_size,
|
||||
int *minimum,
|
||||
int *natural,
|
||||
int *minimum_baseline,
|
||||
int *natural_baseline)
|
||||
{
|
||||
GtkPathWalk *self = GTK_PATH_WALK (widget);
|
||||
|
||||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||
*minimum = *natural = (int) ceilf (self->bounds.size.width);
|
||||
else
|
||||
*minimum = *natural = (int) ceilf (self->bounds.size.height);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_walk_set_n_points (GtkPathWalk *self,
|
||||
gsize n_points)
|
||||
{
|
||||
if (self->n_points == n_points)
|
||||
return;
|
||||
|
||||
self->n_points = n_points;
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_POINTS]);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_walk_set_path (GtkPathWalk *self,
|
||||
GskPath *path)
|
||||
{
|
||||
if (self->path == path)
|
||||
return;
|
||||
|
||||
g_clear_pointer (&self->path, gsk_path_unref);
|
||||
graphene_rect_init (&self->bounds, 0, 0, 0, 0);
|
||||
if (path)
|
||||
{
|
||||
GskStroke *stroke;
|
||||
|
||||
self->path = gsk_path_ref (path);
|
||||
stroke = gsk_stroke_new (2.0);
|
||||
gsk_path_get_stroke_bounds (path, stroke, &self->bounds);
|
||||
gsk_stroke_free (stroke);
|
||||
self->measure = gsk_path_measure_new (self->path);
|
||||
}
|
||||
|
||||
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PATH]);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_walk_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
|
||||
{
|
||||
GtkPathWalk *self = GTK_PATH_WALK (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_N_POINTS:
|
||||
gtk_path_walk_set_n_points (self, g_value_get_uint (value));
|
||||
break;
|
||||
|
||||
case PROP_PATH:
|
||||
gtk_path_walk_set_path (self, g_value_get_boxed (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_walk_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkPathWalk *self = GTK_PATH_WALK (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_N_POINTS:
|
||||
g_value_set_uint (value, self->n_points);
|
||||
break;
|
||||
|
||||
case PROP_PATH:
|
||||
g_value_set_boxed (value, self->path);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_walk_dispose (GObject *object)
|
||||
{
|
||||
GtkPathWalk *self = GTK_PATH_WALK (object);
|
||||
|
||||
g_clear_pointer (&self->path, gsk_path_unref);
|
||||
g_clear_pointer (&self->measure, gsk_path_measure_unref);
|
||||
g_clear_pointer (&self->arrow_path, gsk_path_unref);
|
||||
|
||||
G_OBJECT_CLASS (gtk_path_walk_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_walk_class_init (GtkPathWalkClass *klass)
|
||||
{
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = gtk_path_walk_dispose;
|
||||
object_class->set_property = gtk_path_walk_set_property;
|
||||
object_class->get_property = gtk_path_walk_get_property;
|
||||
|
||||
widget_class->snapshot = gtk_path_walk_snapshot;
|
||||
widget_class->measure = gtk_path_walk_measure;
|
||||
|
||||
properties[PROP_N_POINTS] =
|
||||
g_param_spec_uint ("n-points",
|
||||
NULL, NULL,
|
||||
1, G_MAXUINT,
|
||||
500,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
properties[PROP_PATH] =
|
||||
g_param_spec_boxed ("path",
|
||||
NULL, NULL,
|
||||
GSK_TYPE_PATH,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
tick_tick_tick (GtkWidget *self,
|
||||
GdkFrameClock *frame_clock,
|
||||
gpointer unused)
|
||||
{
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_walk_init (GtkPathWalk *self)
|
||||
{
|
||||
/* Data taken from
|
||||
* https://commons.wikimedia.org/wiki/Maps_of_the_world#/media/File:Simplified_blank_world_map_without_Antartica_(no_borders).svg
|
||||
*/
|
||||
GBytes *data = g_resources_lookup_data ("/path_walk/path_world.txt", 0, NULL);
|
||||
GskPath *path = gsk_path_parse (g_bytes_get_data (data, NULL));
|
||||
g_bytes_unref (data);
|
||||
gtk_path_walk_set_path (self, path);
|
||||
gsk_path_unref (path);
|
||||
self->arrow_path = gsk_path_parse ("M 5 0 L 0 -5. 0 -2, -5 -2, -5 2, 0 2, 0 5 Z");
|
||||
self->n_points = 500;
|
||||
gtk_widget_add_tick_callback (GTK_WIDGET (self), tick_tick_tick, NULL, NULL);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
gtk_path_walk_new (void)
|
||||
{
|
||||
GtkPathWalk *self;
|
||||
|
||||
self = g_object_new (GTK_TYPE_PATH_WALK, NULL);
|
||||
|
||||
return GTK_WIDGET (self);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
do_path_walk (GtkWidget *do_widget)
|
||||
{
|
||||
static GtkWidget *window = NULL;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
GtkBuilder *builder;
|
||||
|
||||
g_type_ensure (GTK_TYPE_PATH_WALK);
|
||||
|
||||
builder = gtk_builder_new_from_resource ("/path_walk/path_walk.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_window_present (GTK_WINDOW (window));
|
||||
else
|
||||
gtk_window_destroy (GTK_WINDOW (window));
|
||||
|
||||
return window;
|
||||
}
|
35
demos/gtk-demo/path_walk.ui
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkWindow" id="window">
|
||||
<property name="title" translatable="yes">World Map</property>
|
||||
<property name="titlebar">
|
||||
<object class="GtkHeaderBar">
|
||||
<child type="end">
|
||||
<object class="GtkSpinButton">
|
||||
<property name="adjustment">
|
||||
<object class="GtkAdjustment" id="adjustment">
|
||||
<property name="lower">0</property>
|
||||
<property name="upper">5000</property>
|
||||
<property name="value">500</property>
|
||||
<property name="step-increment">1</property>
|
||||
<property name="page-increment">10</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkPathWalk" id="view">
|
||||
<property name="n-points" bind-source="adjustment" bind-property="value"/>
|
||||
<property name="hexpand">true</property>
|
||||
<property name="vexpand">true</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
1
demos/gtk-demo/path_world.txt
Normal file
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 844 B After Width: | Height: | Size: 1.1 KiB |
2218
demos/gtk-demo/tiger.node
Normal file
Before Width: | Height: | Size: 823 B After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -147,6 +147,19 @@ Creates a node like `gsk_cross_fade_node_new()` with the given properties.
|
||||
|
||||
Creates a node like `gsk_debug_node_new()` with the given properties.
|
||||
|
||||
### fill
|
||||
|
||||
| property | syntax | default | printed |
|
||||
| --------- | --------------- | ---------------------- | ----------- |
|
||||
| child | `<node>` | *see below* | always |
|
||||
| path | `<string>` | "" | always |
|
||||
| fill-rule | `<fill-rule>` | winding | always |
|
||||
|
||||
Creates a node like `gsk_fill_node_new()` with the given properties.
|
||||
|
||||
The default child node is the default color node, but created with the
|
||||
bounds of the path.
|
||||
|
||||
### glshader
|
||||
|
||||
| property | syntax | default | printed |
|
||||
@@ -289,6 +302,24 @@ Creates a node like `gsk_rounded_clip_node_new()` with the given properties.
|
||||
|
||||
Creates a node like `gsk_shadow_node_new()` with the given properties.
|
||||
|
||||
### stroke
|
||||
|
||||
| property | syntax | default | printed |
|
||||
| ----------- | ------------------ | ----------------- | ----------- |
|
||||
| child | `<node>` | *see below* | always |
|
||||
| path | `<string>` | "" | always |
|
||||
| line-width | `<number>` | 0 | non-default |
|
||||
| line-cap | `<line-cap>` | butt | always |
|
||||
| line-join | `<line-join>` | miter | always |
|
||||
| miter-limit | `<number>` | 4 | non-default |
|
||||
| dash | `<number>{+}|none` | none | non-default |
|
||||
| dash-offset | `<number>` | 0 | non-default |
|
||||
|
||||
Creates a node like `gsk_stroke_node_new()` with the given properties.
|
||||
|
||||
The default child node is the default color node, but created with the
|
||||
stroke bounds of the path.
|
||||
|
||||
### text
|
||||
|
||||
| property | syntax | default | printed |
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 985 B After Width: | Height: | Size: 1019 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 844 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 823 B After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -32,7 +32,34 @@ show_class_hierarchy = true
|
||||
base_url = "https://gitlab.gnome.org/GNOME/gtk/-/blob/main/"
|
||||
|
||||
[extra]
|
||||
content_files = [
|
||||
"paths.md",
|
||||
]
|
||||
content_images = [
|
||||
"gtk-logo.svg",
|
||||
"images/arc-dark.png",
|
||||
"images/arc-light.png",
|
||||
"images/caps-dark.png",
|
||||
"images/caps-light.png",
|
||||
"images/conic-light.png",
|
||||
"images/conic-dark.png",
|
||||
"images/cubic-dark.png",
|
||||
"images/cubic-light.png",
|
||||
"images/curvature-dark.png",
|
||||
"images/curvature-light.png",
|
||||
"images/directions-dark.png",
|
||||
"images/directions-light.png",
|
||||
"images/fill-even-odd.png",
|
||||
"images/fill-winding.png",
|
||||
"images/join-dark.png",
|
||||
"images/join-light.png",
|
||||
"images/line-dark.png",
|
||||
"images/line-light.png",
|
||||
"images/path-dark.png",
|
||||
"images/path-light.png",
|
||||
"images/quad-dark.png",
|
||||
"images/quad-light.png",
|
||||
"images/stroke-miter.png",
|
||||
"images/stroke-round.png",
|
||||
]
|
||||
urlmap_file = "urlmap.js"
|
||||
|
BIN
docs/reference/gsk/images/arc-dark.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
docs/reference/gsk/images/arc-light.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
92
docs/reference/gsk/images/arc.svg
Normal file
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
sodipodi:docname="arcto.svg"
|
||||
inkscape:export-filename="cubic-light.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.75989759"
|
||||
inkscape:cx="399.39592"
|
||||
inkscape:cy="466.51023"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1123"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false" />
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
d="M 58.033485,123.96862 C 75.231194,113.95411 92.489919,103.26728 107.81401,113.89786"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<ellipse
|
||||
style="fill:none;stroke:#000000;stroke-width:0.20061772;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
id="path2"
|
||||
cx="-16.837238"
|
||||
cy="154.55043"
|
||||
rx="35.832706"
|
||||
ry="17.920988"
|
||||
transform="matrix(0.86643544,-0.49928912,0.59215321,0.8058254,0,0)" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
d="M 57.432798,124.38759 86.59638,102.1496 107.25717,113.71009"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<circle
|
||||
style="fill:#ff0404;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
id="path2-8-7-9"
|
||||
cx="86.580566"
|
||||
cy="102.81618"
|
||||
r="1.5" />
|
||||
<path
|
||||
style="fill:#f90048;stroke:#a2a2a2;stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 45.061871,134.70541 12.60825,-10.20893"
|
||||
id="path4-4"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:#f90048;stroke:#a2a2a2;stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:1, 3;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 35.405008,142.35717 8.95641,-7.26298"
|
||||
id="path4-8-8"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<circle
|
||||
style="fill:#818181;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
id="path2-8-7-61-1"
|
||||
cx="57.597607"
|
||||
cy="124.23325"
|
||||
r="1.5" />
|
||||
<circle
|
||||
style="fill:#ff0404;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
id="path2-8-7-6-5"
|
||||
cx="108.01463"
|
||||
cy="114.17829"
|
||||
r="1.5" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.5 KiB |