mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
Compare commits
631 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a5fa17a39 | |||
| 5191409708 | |||
| 3eab806940 | |||
| 30dc7ff294 | |||
| 9e18cb93fb | |||
| e4932e163e | |||
| a8fe27488f | |||
| 06307313cd | |||
| 15335a2b37 | |||
| acf5635575 | |||
| caccb608a5 | |||
| d52632b475 | |||
| dec57aea1c | |||
| e2e2c1a046 | |||
| e8c1e1ba11 | |||
| b1cadbdd76 | |||
| 451b7d3a74 | |||
| e49c5b02c7 | |||
| dfe4ff0200 | |||
| 65d86895b8 | |||
| c3a6e85f5c | |||
| 8c2e45b8f8 | |||
| 47ab4c97b2 | |||
| 760369174d | |||
| 92fa23b59b | |||
| f98baf7f76 | |||
| a6da4083de | |||
| bbb54d5f54 | |||
| 8b96643490 | |||
| 4ed9695d57 | |||
| 9b38545fc2 | |||
| 0d08eaa55c | |||
| 9ab8095d1a | |||
| 3d6cafa94d | |||
| 9966e922f9 | |||
| 3af0ebb3a8 | |||
| 99647591f6 | |||
| e38f80adc6 | |||
| 93419a77bd | |||
| c250d7d562 | |||
| 6e5bbfe8d4 | |||
| 42bc707f7c | |||
| 7bcb315ee1 | |||
| 74671a7f9e | |||
| f48cc2c47f | |||
| 818061a18a | |||
| 45963441c1 | |||
| be8d9df848 | |||
| 3f4f8ba7c1 | |||
| 6553915e59 | |||
| 4c0e526c0b | |||
| f45039734f | |||
| 30f8ac10b7 | |||
| 176570527d | |||
| 36a2fa37c7 | |||
| bbb6d05f09 | |||
| a274f6fe0f | |||
| 0c145b3815 | |||
| ba82ae46d0 | |||
| 4e5a344cc2 | |||
| 78b54615cc | |||
| 31564b98c9 | |||
| 3b1effb7f5 | |||
| d76b4272aa | |||
| 3e601e73c4 | |||
| b2b12d6288 | |||
| b2f6707ad1 | |||
| 420f4c32f4 | |||
| ea979b436d | |||
| d50ef687b8 | |||
| 6ce7ffa4ca | |||
| c3ce484b5e | |||
| 8104d527c7 | |||
| 809a6d0dc7 | |||
| 0eac4f2c6a | |||
| 1a17e9ea37 | |||
| 0a209f7d39 | |||
| 0225334124 | |||
| f73287be87 | |||
| 86668e8b45 | |||
| e18db64991 | |||
| efd31ad621 | |||
| 35ec76e516 | |||
| cc6048bc25 | |||
| 016714a668 | |||
| a3117d3823 | |||
| fff2375470 | |||
| 72a72a2bc2 | |||
| 14a9fbcc0d | |||
| 0d23240556 | |||
| 77709d1771 | |||
| 4f720af6f2 | |||
| af446e8b87 | |||
| 95354d80bd | |||
| a5068a1996 | |||
| 593c5fbf4a | |||
| adaace864d | |||
| f200f0e9a3 | |||
| 1ae2b9b8fe | |||
| fe4bf00c70 | |||
| 8107ec2206 | |||
| d2db4f6875 | |||
| 971f86ea7e | |||
| e1fbb189e5 | |||
| a206e5e847 | |||
| e025ed1344 | |||
| b576252963 | |||
| 1d0d5f0383 | |||
| 26cddb00b1 | |||
| 0cbbacc45a | |||
| 1d95c7c6b3 | |||
| 7337c06daf | |||
| 94355e5201 | |||
| ea456f42c7 | |||
| 8daff13af6 | |||
| aa158e0c9c | |||
| 520efdfa75 | |||
| d7a1ec7461 | |||
| d4c5e54375 | |||
| 44ac50506d | |||
| 38e4b1c6a4 | |||
| 5d886b4d7f | |||
| c7277581f8 | |||
| a5f7e7b2c7 | |||
| 0781e62b56 | |||
| 95f63c66b6 | |||
| 6efcb65c4b | |||
| f6e9f6861d | |||
| 6c6cc157c4 | |||
| 8cf449034c | |||
| 6722c7960a | |||
| 97044e53b5 | |||
| af6dda67a5 | |||
| 042ed4ab49 | |||
| 6aa027136e | |||
| b1db32ef72 | |||
| 9dceca6c95 | |||
| 7293fa8a41 | |||
| 82cbb7b8da | |||
| 6c19821a6c | |||
| 86349775b7 | |||
| 4a1b4c2642 | |||
| c900d58fb3 | |||
| 74ccc9c2c7 | |||
| 3f671afea8 | |||
| 42357a29e0 | |||
| 8fb6d3da2b | |||
| 706940ff9b | |||
| 5542aac772 | |||
| 026d3f9daa | |||
| 38fca9ae13 | |||
| e878dbc598 | |||
| 1151eac07d | |||
| 56177c55cf | |||
| e6da07a85a | |||
| 175e134478 | |||
| 46cc311d19 | |||
| b8821875ed | |||
| f6e33914d9 | |||
| a394b330e8 | |||
| 24b0c2a4a1 | |||
| 7a9ce51988 | |||
| 8242234274 | |||
| 691adf4e77 | |||
| ba1e34e570 | |||
| 0deb7b3efc | |||
| e8c73bcb37 | |||
| cf9f2bbffe | |||
| ed28861309 | |||
| 971b01d836 | |||
| 5083a23301 | |||
| 61b603d521 | |||
| 3b5148a64e | |||
| ee6cdec60b | |||
| 42ef3476cc | |||
| 98d3a2eb24 | |||
| 109226b7e9 | |||
| 477bd5c744 | |||
| 1802761c93 | |||
| 0558bb3f1c | |||
| 78ce5e3ad5 | |||
| 1be34eec6f | |||
| 44ede4eceb | |||
| 993ec907be | |||
| 35785e9c96 | |||
| 93ac0b03f1 | |||
| d58d386b9b | |||
| f3fc0d62b8 | |||
| 5e5d164929 | |||
| 1527e91a02 | |||
| caba422d9c | |||
| 390242f214 | |||
| fe1df00d02 | |||
| 0d7f00c634 | |||
| d7528a8338 | |||
| 9bca5bfdcf | |||
| a880d6403d | |||
| 6879a30582 | |||
| 5cda660e6e | |||
| 8cb06ffa30 | |||
| 297c03925d | |||
| c645c2ec8e | |||
| 95ca7b5b59 | |||
| 5a843bee61 | |||
| dbc10056f9 | |||
| eae39d8556 | |||
| e59f91dfd0 | |||
| c3f2abebca | |||
| 77ff21605c | |||
| f5aa745038 | |||
| 1fc646a8c2 | |||
| 33f9a850c8 | |||
| 5fcfc40ab8 | |||
| 20c28ef282 | |||
| ed0d5d5767 | |||
| d9a007586c | |||
| 496fc3cc82 | |||
| 8e209fd2eb | |||
| ab9f42c820 | |||
| 6dcebf205f | |||
| 75314fcee2 | |||
| c515b7804e | |||
| 0fab08bd6c | |||
| 88f5ac9cb9 | |||
| bce1dbd654 | |||
| fc208248b7 | |||
| 67b58a6006 | |||
| 4d8caba6c9 | |||
| 9dfada745c | |||
| 651094d692 | |||
| 3ba5fc557c | |||
| d25912678c | |||
| 27ef6dd7a2 | |||
| 73c3849da4 | |||
| 12a0ad7038 | |||
| ef8171b971 | |||
| d2085ab7c5 | |||
| d7636ea273 | |||
| be86450ea5 | |||
| 1bcbaa8137 | |||
| 676b670119 | |||
| d51aea553f | |||
| a3c539b3c2 | |||
| eed93aaffe | |||
| 813d836641 | |||
| 627e028d3c | |||
| 9de80749e1 | |||
| 8334d2abb4 | |||
| 5dd41f74d3 | |||
| be36fc88aa | |||
| a11f35fe0d | |||
| 6027517949 | |||
| c319f3c214 | |||
| 4fcd96ac4b | |||
| d5a0d2cf60 | |||
| 99399184ac | |||
| dd36ec5e07 | |||
| cb1e605203 | |||
| 60bfb9e064 | |||
| 3648bdc02a | |||
| e19a248815 | |||
| 7cfd226b57 | |||
| 05d28ad76a | |||
| 40706d3782 | |||
| adba14c062 | |||
| ab021c4566 | |||
| fe2a164d30 | |||
| 4eca2c7d26 | |||
| 7ea432fa13 | |||
| e666ef35ca | |||
| a8ec3968d9 | |||
| 2128f7adea | |||
| a454d5fa38 | |||
| 7d1ea02267 | |||
| f7aa4b5c81 | |||
| 52987e53f6 | |||
| e32417353a | |||
| c458eb30f0 | |||
| d96c954769 | |||
| f39245df99 | |||
| 8097df9930 | |||
| fc6e51deba | |||
| 5fa94ff67b | |||
| 23b6f06e3e | |||
| c8801fe233 | |||
| 3fde0c0061 | |||
| 9d89f7c8b1 | |||
| 48bd5952b1 | |||
| df8a8789a3 | |||
| 62edabe137 | |||
| 49296bd0ca | |||
| d078bf0ed7 | |||
| ea58aacde2 | |||
| ae2b1f42b2 | |||
| 63fea3d2bd | |||
| ce8475a0b9 | |||
| 37717d0df9 | |||
| 7550d85447 | |||
| c756986d9e | |||
| dc170033cd | |||
| d42dae73f0 | |||
| 23e44292bb | |||
| fe85136f99 | |||
| b9837def5c | |||
| dc97871122 | |||
| 4a4fe5a69b | |||
| 1686874d07 | |||
| 650ccea28d | |||
| e1b3928819 | |||
| b66dcc436c | |||
| 526db27c75 | |||
| 27193ae8e8 | |||
| 313c488ec3 | |||
| f30e936d9a | |||
| ae3bd2a1e1 | |||
| ae43d18ced | |||
| b4d93379c4 | |||
| b9654eb4eb | |||
| 89d20c7d99 | |||
| 14bab8efae | |||
| f437aaf359 | |||
| cf5ae4aad9 | |||
| 8679f49fff | |||
| 83202263b9 | |||
| 7fde80e805 | |||
| e9d0619641 | |||
| 2e524200ab | |||
| cc477e9ca6 | |||
| 290b5fe821 | |||
| e9cb0b326d | |||
| 0e77a2b521 | |||
| 63550cc81e | |||
| 03c21dc1b5 | |||
| 05f0dc2513 | |||
| c1d975cced | |||
| 32f15ede7b | |||
| d414116990 | |||
| 341978dbcd | |||
| 80da4d6b02 | |||
| edbf872b44 | |||
| c7c49c3258 | |||
| fdd102aaf9 | |||
| 8dc03ed655 | |||
| 1d8b54892a | |||
| 7966832536 | |||
| 36a39f7d38 | |||
| d62057a62e | |||
| ddf770b9d5 | |||
| acab582487 | |||
| 6384ab6087 | |||
| 43c47f0883 | |||
| babe7a292b | |||
| 92bc52c25c | |||
| df782ea7c5 | |||
| 74a87b1092 | |||
| 6ea4119a3c | |||
| 15031d0b52 | |||
| 5606a961f2 | |||
| 6d925a12ff | |||
| cc2d2c0abf | |||
| e5fe3a0732 | |||
| 55404aec91 | |||
| 0edc00a721 | |||
| b183774063 | |||
| 90e57c2b18 | |||
| 49bb767048 | |||
| 2327604f58 | |||
| 3bb6cca157 | |||
| c6e4eecf21 | |||
| a252394356 | |||
| e3ef98dda3 | |||
| 4677b72a4c | |||
| 8571b25ff7 | |||
| febadd3145 | |||
| 59695984e5 | |||
| 0e5ec18a82 | |||
| 2a5f8950fd | |||
| bcb123a05e | |||
| 816bda7ac7 | |||
| bba91263b0 | |||
| 880cd27f59 | |||
| 0dfd8c3da6 | |||
| ccf24c0bd2 | |||
| a7c9ca99f3 | |||
| f77bf9a42c | |||
| cda73efa6a | |||
| b6e0376ad2 | |||
| 6937a5dd1f | |||
| 73787b8478 | |||
| c0d3f67b04 | |||
| ad3db5212d | |||
| e768fe347a | |||
| e0ceb9f335 | |||
| d71f170c29 | |||
| 7e8c10927b | |||
| 9d0a53fc9f | |||
| 5e6e1217da | |||
| 959f5889a1 | |||
| 5c54268d40 | |||
| 36e768e716 | |||
| 25171df66a | |||
| 3bdbcab874 | |||
| 6ab3ff40a6 | |||
| 47d25c1394 | |||
| d7b1c7c33b | |||
| 51b11486c5 | |||
| fa34121f00 | |||
| b09ef80d9f | |||
| 107401cf07 | |||
| dc9a4a4009 | |||
| 4a1962e5e8 | |||
| 35a8231963 | |||
| 39b1158410 | |||
| 1210bbb34a | |||
| 5f818bc5e7 | |||
| 8af4472672 | |||
| bb12508a8a | |||
| 0dba85f52e | |||
| 32c6a9b10d | |||
| bce4153640 | |||
| 14a33f3cb7 | |||
| db6b4de0f7 | |||
| 22aa00f476 | |||
| ac2c284d16 | |||
| e0cf570339 | |||
| af7afea5a3 | |||
| 1eb5c7dbcd | |||
| 940f0f6021 | |||
| 1c49532447 | |||
| 044390c9df | |||
| f6d56dba89 | |||
| f8221bb2ab | |||
| 1a84517b12 | |||
| 827e20d84d | |||
| 8bc6737dea | |||
| c81a1f9671 | |||
| e9bce02b24 | |||
| aa2a948b86 | |||
| dc6fbf07f0 | |||
| e98c8955bb | |||
| f99aff96ee | |||
| 5d6cc2892d | |||
| 0b456579a9 | |||
| c455f0f342 | |||
| fe5878bc63 | |||
| 5d1d1df206 | |||
| 12da71821a | |||
| 5b40d3cd47 | |||
| b0483975b7 | |||
| bfb47538aa | |||
| 2db1a7bfb9 | |||
| 719278bb36 | |||
| b7d126e24c | |||
| 513250122c | |||
| 98f098f53f | |||
| eccc94dceb | |||
| a22e0f527a | |||
| 42b2174dec | |||
| dee53b3645 | |||
| 5c84cf7e90 | |||
| 4f5a6b3212 | |||
| b1942f64b0 | |||
| c67d8cde4b | |||
| 5c896eabbb | |||
| 9f62230c38 | |||
| f7c14e9964 | |||
| 2340664570 | |||
| dee3cc44f9 | |||
| 64db3720fc | |||
| b67b322978 | |||
| 1be58a2bc4 | |||
| b46c0f5907 | |||
| bebc73db37 | |||
| e69ef4f0b4 | |||
| b707b199b3 | |||
| dead6e007f | |||
| 80196f3c3e | |||
| 24eb1a4fc5 | |||
| 90688fdd17 | |||
| b1dba5f27d | |||
| 83cb383523 | |||
| e37deed8bb | |||
| dbf6429026 | |||
| 276b7eefdd | |||
| 020eb27ff5 | |||
| 911b526dc1 | |||
| 3b2f2efac7 | |||
| 8425d2a6aa | |||
| a55065af9c | |||
| 5fc9312f46 | |||
| 96d6fd2791 | |||
| 65a4366e44 | |||
| 61b2e37691 | |||
| 3e9d4d966c | |||
| dbbae0519e | |||
| 5326ca5fbe | |||
| f7bc744a24 | |||
| 0f51cb9084 | |||
| fc5689ea44 | |||
| 26e71011f5 | |||
| b67bef3e0d | |||
| b9416c7c9c | |||
| 28c206fc78 | |||
| 2993ba1838 | |||
| 63d2353864 | |||
| e711cbc004 | |||
| c73125e1c3 | |||
| 41de0048db | |||
| 1d9ab65313 | |||
| 48e9cdaffd | |||
| 53da73de35 | |||
| 162250392b | |||
| b4acc239cd | |||
| 0777afb85f | |||
| 0057e871d0 | |||
| 6bf5d4dc2d | |||
| f8f68a4e9f | |||
| 17f604fb50 | |||
| e7dd180447 | |||
| f8efc9c2a6 | |||
| d3d2c51717 | |||
| 9de13e37e9 | |||
| 3670f70c01 | |||
| 2adf4efcbd | |||
| 2957d007a2 | |||
| cab1184818 | |||
| a5f8aa35e1 | |||
| 992f568ac7 | |||
| 6981a88720 | |||
| f229601e2d | |||
| aea376fbaf | |||
| 8f990a6fdc | |||
| ea37124686 | |||
| d8766f6d2d | |||
| fa3978e18e | |||
| 78d1eab950 | |||
| e21c665e70 | |||
| 4d593927ae | |||
| 5dc8e0e39d | |||
| 29f4a1d07b | |||
| b82b9b258a | |||
| dab796808a | |||
| fa538f219f | |||
| 546d971e49 | |||
| 9151009b2a | |||
| 414a91c49e | |||
| 3f38e67ce0 | |||
| b53cdfef8d | |||
| b34dc63531 | |||
| fd3a3eb1b9 | |||
| 45443a2c3a | |||
| 5bdf5950b2 | |||
| 2973c7fa5a | |||
| 5e7bf1716e | |||
| 8ef0ad43cb | |||
| df43ba8be0 | |||
| 5cf657065e | |||
| 59480066b4 | |||
| 69e4a3cff3 | |||
| c5e07ba01f | |||
| 8457df2d1f | |||
| 8a9382940a | |||
| 6a5d3f996a | |||
| 9f91fecdb3 | |||
| 3ca4c98596 | |||
| 371de3462b | |||
| 43116400d0 | |||
| e31aa35622 | |||
| 6a6ddc3f19 | |||
| 55c96adb91 | |||
| 72669e19bc | |||
| bf6371c8e9 | |||
| 5ff316ed6d | |||
| 916efb4612 | |||
| 11192a5142 | |||
| 9bf68963da | |||
| a10a8a816a | |||
| 4872bd3cd6 | |||
| 44ec48d325 | |||
| a896a467ec | |||
| b5f12c5f26 | |||
| fc79ec5c94 | |||
| 3d37491342 | |||
| 48a8540a68 | |||
| a63e0cb44a | |||
| 14805af367 | |||
| 87be31cbec | |||
| 99d0332067 | |||
| cbbb5865e5 | |||
| 2a0a196d1a | |||
| 28acb79b82 | |||
| 79f037f983 | |||
| 0e2e02a662 | |||
| 7fc594fa4b | |||
| 7ced0d29ab | |||
| c68417aaf9 | |||
| 4996337d26 | |||
| c97f976e7d | |||
| 45e8c781e2 | |||
| a9a9a7c02f | |||
| aa14056350 | |||
| 4e6a9c0909 | |||
| 1071f56119 | |||
| d22ec125ea | |||
| 1a833ab0a4 | |||
| 6ede60d84e | |||
| 8630fee623 | |||
| 3c45a6d420 | |||
| 2381c5ad70 | |||
| 0273203743 | |||
| f3265e56b9 | |||
| 30fa85f518 | |||
| 6ddc13ce66 | |||
| 7629874237 | |||
| 3a4b8b81ec | |||
| 6c76fa6dec | |||
| ad7c1eb78d | |||
| d37197f45b | |||
| 380f96b3fc | |||
| bec0283e54 | |||
| 9c59612e08 | |||
| ed8d3e8566 | |||
| dc25afda07 | |||
| 6a14269682 | |||
| 43865ddabd | |||
| 405215f862 | |||
| 6e66203881 | |||
| 38ff78df5d | |||
| 25fae8de30 | |||
| 49bd5787e4 | |||
| 4e488f4c70 |
+19
-19
@@ -1,5 +1,5 @@
|
|||||||
# SPDX-License-Identifier: Unlicense OR MIT
|
# SPDX-License-Identifier: Unlicense OR MIT
|
||||||
image: debian/testing
|
image: debian/stable
|
||||||
packages:
|
packages:
|
||||||
- clang
|
- clang
|
||||||
- cmake
|
- cmake
|
||||||
@@ -8,23 +8,28 @@ packages:
|
|||||||
- libxml2-dev
|
- libxml2-dev
|
||||||
- libssl-dev
|
- libssl-dev
|
||||||
- libz-dev
|
- libz-dev
|
||||||
- llvm-dev # for cctools
|
- llvm-dev # cctools
|
||||||
- uuid-dev ## for cctools
|
- uuid-dev # cctools
|
||||||
|
- ninja-build # cctools
|
||||||
|
- systemtap-sdt-dev # cctools
|
||||||
|
- libbsd-dev # cctools
|
||||||
|
- linux-libc-dev # cctools
|
||||||
- libplist-utils # for gogio
|
- libplist-utils # for gogio
|
||||||
sources:
|
sources:
|
||||||
- https://git.sr.ht/~eliasnaur/applesdks
|
- https://git.sr.ht/~eliasnaur/applesdks
|
||||||
- https://git.sr.ht/~eliasnaur/gio
|
- https://git.sr.ht/~eliasnaur/gio
|
||||||
- https://git.sr.ht/~eliasnaur/giouiorg
|
- https://git.sr.ht/~eliasnaur/giouiorg
|
||||||
- https://github.com/tpoechtrager/cctools-port.git
|
- https://github.com/tpoechtrager/cctools-port
|
||||||
- https://github.com/tpoechtrager/apple-libtapi.git
|
- https://github.com/tpoechtrager/apple-libtapi
|
||||||
- https://github.com/mackyle/xar.git
|
- https://github.com/tpoechtrager/apple-libdispatch
|
||||||
|
- https://github.com/mackyle/xar
|
||||||
environment:
|
environment:
|
||||||
APPLE_TOOLCHAIN_ROOT: /home/build/appletools
|
APPLE_TOOLCHAIN_ROOT: /home/build/appletools
|
||||||
PATH: /home/build/sdk/go/bin:/home/build/go/bin:/usr/bin
|
PATH: /home/build/sdk/go/bin:/home/build/go/bin:/usr/bin
|
||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
mkdir -p /home/build/sdk
|
||||||
curl -s https://dl.google.com/go/go1.17.7.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||||
- prepare_toolchain: |
|
- prepare_toolchain: |
|
||||||
mkdir -p $APPLE_TOOLCHAIN_ROOT
|
mkdir -p $APPLE_TOOLCHAIN_ROOT
|
||||||
cd $APPLE_TOOLCHAIN_ROOT
|
cd $APPLE_TOOLCHAIN_ROOT
|
||||||
@@ -42,6 +47,11 @@ tasks:
|
|||||||
- install_appletoolchain: |
|
- install_appletoolchain: |
|
||||||
cd giouiorg
|
cd giouiorg
|
||||||
go build -o $APPLE_TOOLCHAIN_ROOT/tools ./cmd/appletoolchain
|
go build -o $APPLE_TOOLCHAIN_ROOT/tools ./cmd/appletoolchain
|
||||||
|
- build_libdispatch: |
|
||||||
|
cd apple-libdispatch
|
||||||
|
cmake -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=$APPLE_TOOLCHAIN_ROOT/libdispatch .
|
||||||
|
ninja
|
||||||
|
ninja install
|
||||||
- build_xar: |
|
- build_xar: |
|
||||||
cd xar/xar
|
cd xar/xar
|
||||||
ac_cv_lib_crypto_OpenSSL_add_all_ciphers=yes CC=clang ./autogen.sh --prefix=/usr
|
ac_cv_lib_crypto_OpenSSL_add_all_ciphers=yes CC=clang ./autogen.sh --prefix=/usr
|
||||||
@@ -53,7 +63,7 @@ tasks:
|
|||||||
./install.sh
|
./install.sh
|
||||||
- build_cctools: |
|
- build_cctools: |
|
||||||
cd cctools-port/cctools
|
cd cctools-port/cctools
|
||||||
./configure --prefix $APPLE_TOOLCHAIN_ROOT/toolchain --with-libtapi=$APPLE_TOOLCHAIN_ROOT/libtapi --target=x86_64-apple-darwin19
|
./configure --target=x86_64-apple-darwin19 --prefix $APPLE_TOOLCHAIN_ROOT/toolchain --with-libtapi=$APPLE_TOOLCHAIN_ROOT/libtapi --with-libdispatch=$APPLE_TOOLCHAIN_ROOT/libdispatch --with-libblocksruntime=$APPLE_TOOLCHAIN_ROOT/libdispatch
|
||||||
make install
|
make install
|
||||||
- test_macos: |
|
- test_macos: |
|
||||||
cd gio
|
cd gio
|
||||||
@@ -61,14 +71,4 @@ tasks:
|
|||||||
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-macos GOOS=darwin CGO_ENABLED=1 go build ./...
|
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-macos GOOS=darwin CGO_ENABLED=1 go build ./...
|
||||||
- test_ios: |
|
- test_ios: |
|
||||||
cd gio
|
cd gio
|
||||||
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./...
|
CGO_CFLAGS=-Wno-deprecated-module-dot-map CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./...
|
||||||
- install_gogio: |
|
|
||||||
cd gio/cmd
|
|
||||||
go install ./gogio
|
|
||||||
- test_ios_gogio: |
|
|
||||||
mkdir tmp
|
|
||||||
cd tmp
|
|
||||||
go mod init example.com
|
|
||||||
go get -d gioui.org/example/kitchen
|
|
||||||
export PATH=/home/build/appletools/bin:$PATH
|
|
||||||
gogio -target ios -o app.app gioui.org/example/kitchen
|
|
||||||
|
|||||||
+2
-5
@@ -1,5 +1,5 @@
|
|||||||
# SPDX-License-Identifier: Unlicense OR MIT
|
# SPDX-License-Identifier: Unlicense OR MIT
|
||||||
image: freebsd/13.x
|
image: freebsd/latest
|
||||||
packages:
|
packages:
|
||||||
- libX11
|
- libX11
|
||||||
- libxkbcommon
|
- libxkbcommon
|
||||||
@@ -16,10 +16,7 @@ environment:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
mkdir -p /home/build/sdk
|
||||||
curl https://dl.google.com/go/go1.17.7.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
curl https://dl.google.com/go/go1.24.2.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||||
- test_gio: |
|
- test_gio: |
|
||||||
cd gio
|
cd gio
|
||||||
go test ./...
|
go test ./...
|
||||||
- test_cmd: |
|
|
||||||
cd gio/cmd
|
|
||||||
go test ./...
|
|
||||||
|
|||||||
+20
-25
@@ -1,8 +1,9 @@
|
|||||||
# SPDX-License-Identifier: Unlicense OR MIT
|
# SPDX-License-Identifier: Unlicense OR MIT
|
||||||
image: debian/testing
|
image: debian/stable
|
||||||
packages:
|
packages:
|
||||||
- curl
|
- curl
|
||||||
- pkg-config
|
- pkg-config
|
||||||
|
- gcc-multilib
|
||||||
- libwayland-dev
|
- libwayland-dev
|
||||||
- libx11-dev
|
- libx11-dev
|
||||||
- libx11-xcb-dev
|
- libx11-xcb-dev
|
||||||
@@ -17,6 +18,12 @@ packages:
|
|||||||
- libxinerama-dev
|
- libxinerama-dev
|
||||||
- libxi-dev
|
- libxi-dev
|
||||||
- libxxf86vm-dev
|
- libxxf86vm-dev
|
||||||
|
- libegl-mesa0
|
||||||
|
- libglx-mesa0
|
||||||
|
- libgl1-mesa-dri
|
||||||
|
- mesa-libgallium
|
||||||
|
- libgbm1
|
||||||
|
- libegl1
|
||||||
- mesa-vulkan-drivers
|
- mesa-vulkan-drivers
|
||||||
- wine
|
- wine
|
||||||
- xvfb
|
- xvfb
|
||||||
@@ -24,12 +31,11 @@ packages:
|
|||||||
- scrot
|
- scrot
|
||||||
- sway
|
- sway
|
||||||
- grim
|
- grim
|
||||||
- wine
|
|
||||||
- unzip
|
- unzip
|
||||||
sources:
|
sources:
|
||||||
- https://git.sr.ht/~eliasnaur/gio
|
- https://git.sr.ht/~eliasnaur/gio
|
||||||
environment:
|
environment:
|
||||||
GOFLAGS: -mod=readonly
|
PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig/:/usr/lib/i386-linux-gnu/pkgconfig/
|
||||||
PATH: /home/build/sdk/go/bin:/usr/bin:/home/build/go/bin:/home/build/android/tools/bin
|
PATH: /home/build/sdk/go/bin:/usr/bin:/home/build/go/bin:/home/build/android/tools/bin
|
||||||
ANDROID_SDK_ROOT: /home/build/android
|
ANDROID_SDK_ROOT: /home/build/android
|
||||||
android_sdk_tools_zip: sdk-tools-linux-3859397.zip
|
android_sdk_tools_zip: sdk-tools-linux-3859397.zip
|
||||||
@@ -40,7 +46,7 @@ secrets:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
mkdir -p /home/build/sdk
|
||||||
curl -s https://dl.google.com/go/go1.17.7.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||||
- check_gofmt: |
|
- check_gofmt: |
|
||||||
cd gio
|
cd gio
|
||||||
test -z "$(gofmt -s -l .)"
|
test -z "$(gofmt -s -l .)"
|
||||||
@@ -54,20 +60,19 @@ tasks:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
- mirror: |
|
||||||
|
# mirror to github
|
||||||
|
ssh-keyscan github.com > "$HOME"/.ssh/known_hosts && cd gio && git push --mirror "$github_mirror" || echo "failed mirroring"
|
||||||
|
- add_32bit_arch: |
|
||||||
|
sudo dpkg --add-architecture i386
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y "libwayland-dev:i386" "libx11-dev:i386" "libx11-xcb-dev:i386" "libxkbcommon-dev:i386" "libxkbcommon-x11-dev:i386" "libgles2-mesa-dev:i386" "libegl1-mesa-dev:i386" "libffi-dev:i386" "libvulkan-dev:i386" "libxcursor-dev:i386" "libegl-mesa0:i386" "libglx-mesa0:i386" "libgbm1:i386" "mesa-libgallium:i386" "libgl1-mesa-dri:i386"
|
||||||
- test_gio: |
|
- test_gio: |
|
||||||
cd gio
|
cd gio
|
||||||
go test -race ./...
|
go test -race ./...
|
||||||
|
CGO_ENABLED=1 GOARCH=386 go test ./...
|
||||||
GOOS=windows go test -exec=wine ./...
|
GOOS=windows go test -exec=wine ./...
|
||||||
GOOS=js GOARCH=wasm go build -o /dev/null ./...
|
GOOS=js GOARCH=wasm go build -o /dev/null ./...
|
||||||
- install_chrome: |
|
|
||||||
curl -s https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
|
|
||||||
sudo sh -c 'echo "deb [arch=amd64] https://dl-ssl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
|
|
||||||
sudo apt-get -qq update
|
|
||||||
sudo apt-get -qq install -y google-chrome-stable
|
|
||||||
- test_cmd: |
|
|
||||||
cd gio/cmd
|
|
||||||
go test ./...
|
|
||||||
go test -race ./...
|
|
||||||
- install_jdk8: |
|
- install_jdk8: |
|
||||||
curl -so jdk.deb "https://cdn.azul.com/zulu/bin/zulu8.42.0.21-ca-jdk8.0.232-linux_amd64.deb"
|
curl -so jdk.deb "https://cdn.azul.com/zulu/bin/zulu8.42.0.21-ca-jdk8.0.232-linux_amd64.deb"
|
||||||
sudo apt-get -qq install -y -f ./jdk.deb
|
sudo apt-get -qq install -y -f ./jdk.deb
|
||||||
@@ -81,21 +86,11 @@ tasks:
|
|||||||
unzip -q ndk.zip
|
unzip -q ndk.zip
|
||||||
rm ndk.zip
|
rm ndk.zip
|
||||||
mv android-ndk-* ndk-bundle
|
mv android-ndk-* ndk-bundle
|
||||||
|
# sdkmanager needs lots of file descriptors
|
||||||
|
ulimit -n 10000
|
||||||
yes|sdkmanager --licenses
|
yes|sdkmanager --licenses
|
||||||
sdkmanager "platforms;android-31" "build-tools;32.0.0"
|
sdkmanager "platforms;android-31" "build-tools;32.0.0"
|
||||||
- test_android: |
|
- test_android: |
|
||||||
cd gio
|
cd gio
|
||||||
CC=$ANDROID_SDK_ROOT/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang GOOS=android GOARCH=arm64 CGO_ENABLED=1 go build ./...
|
CC=$ANDROID_SDK_ROOT/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang GOOS=android GOARCH=arm64 CGO_ENABLED=1 go build ./...
|
||||||
CC=$ANDROID_SDK_ROOT/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi29-clang GOOS=android GOARCH=arm CGO_ENABLED=1 go build ./...
|
CC=$ANDROID_SDK_ROOT/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi29-clang GOOS=android GOARCH=arm CGO_ENABLED=1 go build ./...
|
||||||
- install_gogio: |
|
|
||||||
cd gio/cmd
|
|
||||||
go install ./gogio
|
|
||||||
- test_android_gogio: |
|
|
||||||
mkdir tmp
|
|
||||||
cd tmp
|
|
||||||
go mod init example.com
|
|
||||||
go get -d gioui.org/example/kitchen
|
|
||||||
gogio -target android gioui.org/example/kitchen
|
|
||||||
- mirror: |
|
|
||||||
# mirror to github
|
|
||||||
ssh-keyscan github.com > "$HOME"/.ssh/known_hosts && cd gio && git push --mirror "$github_mirror" || echo "failed mirroring"
|
|
||||||
|
|||||||
+1
-4
@@ -10,12 +10,9 @@ environment:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
mkdir -p /home/build/sdk
|
||||||
curl https://dl.google.com/go/go1.17.7.src.tar.gz | tar -C /home/build/sdk -xzf -
|
curl https://dl.google.com/go/go1.24.2.src.tar.gz | tar -C /home/build/sdk -xzf -
|
||||||
cd /home/build/sdk/go/src
|
cd /home/build/sdk/go/src
|
||||||
./make.bash
|
./make.bash
|
||||||
- test_gio: |
|
- test_gio: |
|
||||||
cd gio
|
cd gio
|
||||||
go test ./...
|
go test ./...
|
||||||
- test_cmd: |
|
|
||||||
cd gio/cmd
|
|
||||||
go test ./...
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.gradle
|
|
||||||
**/android/build
|
|
||||||
@@ -24,3 +24,12 @@ account is required and you can post without being subscribed.
|
|||||||
See the [contribution guide](https://gioui.org/doc/contribute) for more details.
|
See the [contribution guide](https://gioui.org/doc/contribute) for more details.
|
||||||
|
|
||||||
An [official GitHub mirror](https://github.com/gioui/gio) is available.
|
An [official GitHub mirror](https://github.com/gioui/gio) is available.
|
||||||
|
|
||||||
|
## Tags
|
||||||
|
|
||||||
|
Pre-1.0 tags are provided for reference only, and do not designate releases with ongoing support. Bugfixes will not be backported to older tags.
|
||||||
|
|
||||||
|
Tags follow semantic versioning. In particular, as the major version is zero:
|
||||||
|
|
||||||
|
- breaking API or behavior changes will increment the *minor* version component.
|
||||||
|
- non-breaking changes will increment the *patch* version component.
|
||||||
|
|||||||
+34
-3
@@ -4,17 +4,33 @@ package org.gioui;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
public final class GioActivity extends Activity {
|
public final class GioActivity extends Activity {
|
||||||
private GioView view;
|
private GioView view;
|
||||||
|
public FrameLayout layer;
|
||||||
|
|
||||||
@Override public void onCreate(Bundle state) {
|
@Override public void onCreate(Bundle state) {
|
||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
|
|
||||||
this.view = new GioView(this);
|
layer = new FrameLayout(this);
|
||||||
|
view = new GioView(this);
|
||||||
|
|
||||||
setContentView(view);
|
view.setLayoutParams(new FrameLayout.LayoutParams(
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT
|
||||||
|
));
|
||||||
|
view.setFocusable(true);
|
||||||
|
view.setFocusableInTouchMode(true);
|
||||||
|
|
||||||
|
layer.addView(view);
|
||||||
|
setContentView(layer);
|
||||||
|
onNewIntent(this.getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onDestroy() {
|
@Override public void onDestroy() {
|
||||||
@@ -32,6 +48,16 @@ public final class GioActivity extends Activity {
|
|||||||
super.onStop();
|
super.onStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
view.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
view.resume();
|
||||||
|
}
|
||||||
|
|
||||||
@Override public void onConfigurationChanged(Configuration c) {
|
@Override public void onConfigurationChanged(Configuration c) {
|
||||||
super.onConfigurationChanged(c);
|
super.onConfigurationChanged(c);
|
||||||
view.configurationChanged();
|
view.configurationChanged();
|
||||||
@@ -46,4 +72,9 @@ public final class GioActivity extends Activity {
|
|||||||
if (!view.backPressed())
|
if (!view.backPressed())
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override protected void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
view.onIntentEvent(intent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+89
-9
@@ -12,6 +12,7 @@ import android.app.Fragment;
|
|||||||
import android.app.FragmentManager;
|
import android.app.FragmentManager;
|
||||||
import android.app.FragmentTransaction;
|
import android.app.FragmentTransaction;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
@@ -26,6 +27,7 @@ import android.text.SpannableStringBuilder;
|
|||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Choreographer;
|
import android.view.Choreographer;
|
||||||
|
import android.view.Display;
|
||||||
import android.view.KeyCharacterMap;
|
import android.view.KeyCharacterMap;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
@@ -60,12 +62,11 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
|||||||
private static boolean jniLoaded;
|
private static boolean jniLoaded;
|
||||||
|
|
||||||
private final SurfaceHolder.Callback surfCallbacks;
|
private final SurfaceHolder.Callback surfCallbacks;
|
||||||
private final View.OnFocusChangeListener focusCallback;
|
|
||||||
private final InputMethodManager imm;
|
private final InputMethodManager imm;
|
||||||
private final float scrollXScale;
|
private final float scrollXScale;
|
||||||
private final float scrollYScale;
|
private final float scrollYScale;
|
||||||
|
private final AccessibilityManager accessManager;
|
||||||
private int keyboardHint;
|
private int keyboardHint;
|
||||||
private AccessibilityManager accessManager;
|
|
||||||
|
|
||||||
private long nhandle;
|
private long nhandle;
|
||||||
|
|
||||||
@@ -105,17 +106,13 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
|||||||
scrollYScale = px;
|
scrollYScale = px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setHighRefreshRate();
|
||||||
|
|
||||||
accessManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
accessManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||||
imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
nhandle = onCreateView(this);
|
nhandle = onCreateView(this);
|
||||||
setFocusable(true);
|
setFocusable(true);
|
||||||
setFocusableInTouchMode(true);
|
setFocusableInTouchMode(true);
|
||||||
focusCallback = new View.OnFocusChangeListener() {
|
|
||||||
@Override public void onFocusChange(View v, boolean focus) {
|
|
||||||
GioView.this.onFocusChange(nhandle, focus);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
setOnFocusChangeListener(focusCallback);
|
|
||||||
surfCallbacks = new SurfaceHolder.Callback() {
|
surfCallbacks = new SurfaceHolder.Callback() {
|
||||||
@Override public void surfaceCreated(SurfaceHolder holder) {
|
@Override public void surfaceCreated(SurfaceHolder holder) {
|
||||||
// Ignore; surfaceChanged is guaranteed to be called immediately after this.
|
// Ignore; surfaceChanged is guaranteed to be called immediately after this.
|
||||||
@@ -255,6 +252,72 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
|||||||
this.setBarColor(Bar.NAVIGATION, color, luminance);
|
this.setBarColor(Bar.NAVIGATION, color, luminance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setHighRefreshRate() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Context context = getContext();
|
||||||
|
Display display = context.getDisplay();
|
||||||
|
Display.Mode[] supportedModes = display.getSupportedModes();
|
||||||
|
if (supportedModes.length <= 1) {
|
||||||
|
// Nothing to set
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Display.Mode currentMode = display.getMode();
|
||||||
|
int currentWidth = currentMode.getPhysicalWidth();
|
||||||
|
int currentHeight = currentMode.getPhysicalHeight();
|
||||||
|
|
||||||
|
float minRefreshRate = -1;
|
||||||
|
float maxRefreshRate = -1;
|
||||||
|
float bestRefreshRate = -1;
|
||||||
|
int bestModeId = -1;
|
||||||
|
for (Display.Mode mode : supportedModes) {
|
||||||
|
float refreshRate = mode.getRefreshRate();
|
||||||
|
float width = mode.getPhysicalWidth();
|
||||||
|
float height = mode.getPhysicalHeight();
|
||||||
|
|
||||||
|
if (minRefreshRate == -1 || refreshRate < minRefreshRate) {
|
||||||
|
minRefreshRate = refreshRate;
|
||||||
|
}
|
||||||
|
if (maxRefreshRate == -1 || refreshRate > maxRefreshRate) {
|
||||||
|
maxRefreshRate = refreshRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean refreshRateIsBetter = bestRefreshRate == -1 || refreshRate > bestRefreshRate;
|
||||||
|
if (width == currentWidth && height == currentHeight && refreshRateIsBetter) {
|
||||||
|
int modeId = mode.getModeId();
|
||||||
|
bestRefreshRate = refreshRate;
|
||||||
|
bestModeId = modeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestModeId == -1) {
|
||||||
|
// Not expecting this but just in case
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minRefreshRate == maxRefreshRate) {
|
||||||
|
// Can't improve the refresh rate
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Window window = ((Activity) context).getWindow();
|
||||||
|
WindowManager.LayoutParams layoutParams = window.getAttributes();
|
||||||
|
layoutParams.preferredDisplayModeId = bestModeId;
|
||||||
|
window.setAttributes(layoutParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onIntentEvent(Intent intent) {
|
||||||
|
if (intent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (intent.getData() != null) {
|
||||||
|
this.onOpenURI(nhandle, intent.getData().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override protected boolean dispatchHoverEvent(MotionEvent event) {
|
@Override protected boolean dispatchHoverEvent(MotionEvent event) {
|
||||||
if (!accessManager.isTouchExplorationEnabled()) {
|
if (!accessManager.isTouchExplorationEnabled()) {
|
||||||
return super.dispatchHoverEvent(event);
|
return super.dispatchHoverEvent(event);
|
||||||
@@ -412,6 +475,18 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void pause() {
|
||||||
|
if (nhandle != 0) {
|
||||||
|
onFocusChange(nhandle, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resume() {
|
||||||
|
if (nhandle != 0) {
|
||||||
|
onFocusChange(nhandle, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
if (nhandle != 0) {
|
if (nhandle != 0) {
|
||||||
onDestroyView(nhandle);
|
onDestroyView(nhandle);
|
||||||
@@ -493,6 +568,7 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
|||||||
static private native void onExitTouchExploration(long handle);
|
static private native void onExitTouchExploration(long handle);
|
||||||
static private native void onA11yFocus(long handle, int viewId);
|
static private native void onA11yFocus(long handle, int viewId);
|
||||||
static private native void onClearA11yFocus(long handle, int viewId);
|
static private native void onClearA11yFocus(long handle, int viewId);
|
||||||
|
static private native void onOpenURI(long handle, String uri);
|
||||||
static private native void imeSetSnippet(long handle, int start, int end);
|
static private native void imeSetSnippet(long handle, int start, int end);
|
||||||
static private native String imeSnippet(long handle);
|
static private native String imeSnippet(long handle);
|
||||||
static private native int imeSnippetStart(long handle);
|
static private native int imeSnippetStart(long handle);
|
||||||
@@ -555,7 +631,11 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
|||||||
@Override public int getCursorCapsMode(int reqModes) {
|
@Override public int getCursorCapsMode(int reqModes) {
|
||||||
Snippet snip = getSnippet();
|
Snippet snip = getSnippet();
|
||||||
int selStart = imeSelectionStart(nhandle);
|
int selStart = imeSelectionStart(nhandle);
|
||||||
return TextUtils.getCapsMode(snip.snippet, imeToUTF16(nhandle, selStart), reqModes);
|
int off = imeToUTF16(nhandle, selStart - snip.offset);
|
||||||
|
if (off < 0 || off > snip.snippet.length()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return TextUtils.getCapsMode(snip.snippet, off, reqModes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
|
@Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
|
||||||
|
|||||||
+159
-6
@@ -3,8 +3,19 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"gioui.org/io/event"
|
||||||
|
"golang.org/x/net/idna"
|
||||||
|
"image"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gioui.org/io/input"
|
||||||
|
"gioui.org/layout"
|
||||||
|
"gioui.org/op"
|
||||||
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
// extraArgs contains extra arguments to append to
|
// extraArgs contains extra arguments to append to
|
||||||
@@ -14,10 +25,102 @@ import (
|
|||||||
// Set with the go linker flag -X.
|
// Set with the go linker flag -X.
|
||||||
var extraArgs string
|
var extraArgs string
|
||||||
|
|
||||||
func init() {
|
// ID is the app id exposed to the platform.
|
||||||
if extraArgs != "" {
|
//
|
||||||
args := strings.Split(extraArgs, "|")
|
// On Android ID is the package property of AndroidManifest.xml,
|
||||||
os.Args = append(os.Args, args...)
|
// on iOS ID is the CFBundleIdentifier of the app Info.plist,
|
||||||
|
// on Wayland it is the toplevel app_id,
|
||||||
|
// on X11 it is the X11 XClassHint.
|
||||||
|
//
|
||||||
|
// ID is set by the [gioui.org/cmd/gogio] tool or manually with the -X linker flag. For example,
|
||||||
|
//
|
||||||
|
// go build -ldflags="-X 'gioui.org/app.ID=org.gioui.example.Kitchen'" .
|
||||||
|
//
|
||||||
|
// Note that ID is treated as a constant, and that changing it at runtime
|
||||||
|
// is not supported. The default value of ID is filepath.Base(os.Args[0]).
|
||||||
|
var ID = ""
|
||||||
|
|
||||||
|
// A FrameEvent requests a new frame in the form of a list of
|
||||||
|
// operations that describes the window content.
|
||||||
|
type FrameEvent struct {
|
||||||
|
// Now is the current animation. Use Now instead of time.Now to
|
||||||
|
// synchronize animation and to avoid the time.Now call overhead.
|
||||||
|
Now time.Time
|
||||||
|
// Metric converts device independent dp and sp to device pixels.
|
||||||
|
Metric unit.Metric
|
||||||
|
// Size is the dimensions of the window.
|
||||||
|
Size image.Point
|
||||||
|
// Insets represent the space occupied by system decorations and controls.
|
||||||
|
Insets Insets
|
||||||
|
// Frame completes the FrameEvent by drawing the graphical operations
|
||||||
|
// from ops into the window.
|
||||||
|
Frame func(frame *op.Ops)
|
||||||
|
// Source is the interface between the window and widgets.
|
||||||
|
Source input.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLEvent is generated for external requests to open a URL. Unlike window specific events,
|
||||||
|
// it is delivered through the [Events] iterator.
|
||||||
|
//
|
||||||
|
// In order to receive URLEvents the program must register one or more URL schemes. A scheme can
|
||||||
|
// be registered using gogio, with the `-schemes` flag.
|
||||||
|
type URLEvent struct {
|
||||||
|
URL *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewEvent provides handles to the underlying window objects for the
|
||||||
|
// current display protocol.
|
||||||
|
type ViewEvent interface {
|
||||||
|
implementsViewEvent()
|
||||||
|
ImplementsEvent()
|
||||||
|
// Valid will return true when the ViewEvent does contains valid handles.
|
||||||
|
// If a window receives an invalid ViewEvent, it should deinitialize any
|
||||||
|
// state referring to handles from a previous ViewEvent.
|
||||||
|
Valid() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insets is the space taken up by
|
||||||
|
// system decoration such as translucent
|
||||||
|
// system bars and software keyboards.
|
||||||
|
type Insets struct {
|
||||||
|
// Values are in pixels.
|
||||||
|
Top, Bottom, Left, Right unit.Dp
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext is shorthand for
|
||||||
|
//
|
||||||
|
// layout.Context{
|
||||||
|
// Ops: ops,
|
||||||
|
// Now: e.Now,
|
||||||
|
// Source: e.Source,
|
||||||
|
// Metric: e.Metric,
|
||||||
|
// Constraints: layout.Exact(e.Size),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// NewContext calls ops.Reset and adjusts ops for e.Insets.
|
||||||
|
func NewContext(ops *op.Ops, e FrameEvent) layout.Context {
|
||||||
|
ops.Reset()
|
||||||
|
|
||||||
|
size := e.Size
|
||||||
|
|
||||||
|
if e.Insets != (Insets{}) {
|
||||||
|
left := e.Metric.Dp(e.Insets.Left)
|
||||||
|
top := e.Metric.Dp(e.Insets.Top)
|
||||||
|
op.Offset(image.Point{
|
||||||
|
X: left,
|
||||||
|
Y: top,
|
||||||
|
}).Add(ops)
|
||||||
|
|
||||||
|
size.X -= left + e.Metric.Dp(e.Insets.Right)
|
||||||
|
size.Y -= top + e.Metric.Dp(e.Insets.Bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
return layout.Context{
|
||||||
|
Ops: ops,
|
||||||
|
Now: e.Now,
|
||||||
|
Source: e.Source,
|
||||||
|
Metric: e.Metric,
|
||||||
|
Constraints: layout.Exact(size),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,8 +130,7 @@ func init() {
|
|||||||
// On iOS NSDocumentDirectory is queried.
|
// On iOS NSDocumentDirectory is queried.
|
||||||
// For Android Context.getFilesDir is used.
|
// For Android Context.getFilesDir is used.
|
||||||
//
|
//
|
||||||
// BUG: DataDir blocks on Android until init functions
|
// BUG: On Android, DataDir panics if called before main.
|
||||||
// have completed.
|
|
||||||
func DataDir() (string, error) {
|
func DataDir() (string, error) {
|
||||||
return dataDir()
|
return dataDir()
|
||||||
}
|
}
|
||||||
@@ -44,3 +146,54 @@ func DataDir() (string, error) {
|
|||||||
func Main() {
|
func Main() {
|
||||||
osMain()
|
osMain()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Events is an iterator that yields events that are not specific to any window,
|
||||||
|
// such as [URLEvent]. It never returns.
|
||||||
|
//
|
||||||
|
// Events must be called by the main goroutine, and replaces the
|
||||||
|
// call to [Main].
|
||||||
|
func Events(yield func(event.Event) bool) {
|
||||||
|
yieldGlobalEvent = yield
|
||||||
|
osMain()
|
||||||
|
}
|
||||||
|
|
||||||
|
var yieldGlobalEvent func(evt event.Event) bool
|
||||||
|
|
||||||
|
func processGlobalEvent(evt event.Event) {
|
||||||
|
if yieldGlobalEvent == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !yieldGlobalEvent(evt) {
|
||||||
|
yieldGlobalEvent = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (FrameEvent) ImplementsEvent() {}
|
||||||
|
func (URLEvent) ImplementsEvent() {}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if extraArgs != "" {
|
||||||
|
args := strings.Split(extraArgs, "|")
|
||||||
|
os.Args = append(os.Args, args...)
|
||||||
|
}
|
||||||
|
if ID == "" {
|
||||||
|
ID = filepath.Base(os.Args[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newURLEvent creates a URLEvent from a raw URL string, handling Punycode decoding.
|
||||||
|
func newURLEvent(rawurl string) (URLEvent, error) {
|
||||||
|
u, err := url.Parse(rawurl)
|
||||||
|
if err != nil {
|
||||||
|
return URLEvent{}, err
|
||||||
|
}
|
||||||
|
u.Host, err = idna.Punycode.ToUnicode(u.Hostname())
|
||||||
|
if err != nil {
|
||||||
|
return URLEvent{}, err
|
||||||
|
}
|
||||||
|
u, err = url.Parse(u.String())
|
||||||
|
if err != nil {
|
||||||
|
return URLEvent{}, err
|
||||||
|
}
|
||||||
|
return URLEvent{URL: u}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type d3d11Context struct {
|
|||||||
width, height int
|
width, height int
|
||||||
}
|
}
|
||||||
|
|
||||||
const debug = false
|
const debugDirectX = false
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
drivers = append(drivers, gpuAPI{
|
drivers = append(drivers, gpuAPI{
|
||||||
@@ -28,7 +28,7 @@ func init() {
|
|||||||
initializer: func(w *window) (context, error) {
|
initializer: func(w *window) (context, error) {
|
||||||
hwnd, _, _ := w.HWND()
|
hwnd, _, _ := w.HWND()
|
||||||
var flags uint32
|
var flags uint32
|
||||||
if debug {
|
if debugDirectX {
|
||||||
flags |= d3d11.CREATE_DEVICE_DEBUG
|
flags |= d3d11.CREATE_DEVICE_DEBUG
|
||||||
}
|
}
|
||||||
dev, ctx, _, err := d3d11.CreateDevice(
|
dev, ctx, _, err := d3d11.CreateDevice(
|
||||||
@@ -60,10 +60,10 @@ func (c *d3d11Context) RenderTarget() (gpu.RenderTarget, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *d3d11Context) Present() error {
|
func (c *d3d11Context) Present() error {
|
||||||
err := c.swchain.Present(1, 0)
|
return wrapErr(c.swchain.Present(1, 0))
|
||||||
if err == nil {
|
}
|
||||||
return nil
|
|
||||||
}
|
func wrapErr(err error) error {
|
||||||
if err, ok := err.(d3d11.ErrorCode); ok {
|
if err, ok := err.(d3d11.ErrorCode); ok {
|
||||||
switch err.Code {
|
switch err.Code {
|
||||||
case d3d11.DXGI_STATUS_OCCLUDED:
|
case d3d11.DXGI_STATUS_OCCLUDED:
|
||||||
@@ -84,7 +84,7 @@ func (c *d3d11Context) Refresh() error {
|
|||||||
}
|
}
|
||||||
c.releaseFBO()
|
c.releaseFBO()
|
||||||
if err := c.swchain.ResizeBuffers(0, 0, 0, d3d11.DXGI_FORMAT_UNKNOWN, 0); err != nil {
|
if err := c.swchain.ResizeBuffers(0, 0, 0, d3d11.DXGI_FORMAT_UNKNOWN, 0); err != nil {
|
||||||
return err
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
c.width = width
|
c.width = width
|
||||||
c.height = height
|
c.height = height
|
||||||
@@ -122,7 +122,7 @@ func (c *d3d11Context) Release() {
|
|||||||
d3d11.IUnknownRelease(unsafe.Pointer(c.dev), c.dev.Vtbl.Release)
|
d3d11.IUnknownRelease(unsafe.Pointer(c.dev), c.dev.Vtbl.Release)
|
||||||
}
|
}
|
||||||
*c = d3d11Context{}
|
*c = d3d11Context{}
|
||||||
if debug {
|
if debugDirectX {
|
||||||
d3d11.ReportLiveObjects()
|
d3d11.ReportLiveObjects()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
//go:build !android
|
//go:build !android
|
||||||
// +build !android
|
|
||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
|
|||||||
+20
-22
@@ -6,23 +6,22 @@ functionality for running graphical user interfaces.
|
|||||||
|
|
||||||
See https://gioui.org for instructions to set up and run Gio programs.
|
See https://gioui.org for instructions to set up and run Gio programs.
|
||||||
|
|
||||||
Windows
|
# Windows
|
||||||
|
|
||||||
Create a new Window by calling NewWindow. On mobile platforms or when Gio
|
A Window is run by calling its Event method in a loop. The first time a
|
||||||
is embedded in another project, NewWindow merely connects with a previously
|
method on Window is called, a new GUI window is created and shown. On mobile
|
||||||
created window.
|
platforms or when Gio is embedded in another project, Window merely connects
|
||||||
|
with a previously created GUI window.
|
||||||
|
|
||||||
A Window is run by receiving events from its Events channel. The most
|
The most important event is [FrameEvent] that prompts an update of the window
|
||||||
important event is FrameEvent that prompts an update of the window
|
contents.
|
||||||
contents and state.
|
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
import "gioui.org/unit"
|
w := new(app.Window)
|
||||||
|
for {
|
||||||
w := app.NewWindow()
|
e := w.Event()
|
||||||
for e := range w.Events() {
|
if e, ok := e.(app.FrameEvent); ok {
|
||||||
if e, ok := e.(system.FrameEvent); ok {
|
|
||||||
ops.Reset()
|
ops.Reset()
|
||||||
// Add operations to ops.
|
// Add operations to ops.
|
||||||
...
|
...
|
||||||
@@ -32,9 +31,9 @@ For example:
|
|||||||
}
|
}
|
||||||
|
|
||||||
A program must keep receiving events from the event channel until
|
A program must keep receiving events from the event channel until
|
||||||
DestroyEvent is received.
|
[DestroyEvent] is received.
|
||||||
|
|
||||||
Main
|
# Main
|
||||||
|
|
||||||
The Main function must be called from a program's main function, to hand over
|
The Main function must be called from a program's main function, to hand over
|
||||||
control of the main thread to operating systems that need it.
|
control of the main thread to operating systems that need it.
|
||||||
@@ -49,21 +48,20 @@ For example, to display a blank but otherwise functional window:
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
go func() {
|
go func() {
|
||||||
w := app.NewWindow()
|
w := new(app.Window)
|
||||||
for range w.Events() {
|
for {
|
||||||
|
w.Event()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
app.Main()
|
app.Main()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Events
|
||||||
|
|
||||||
Event queue
|
The [Events] iterator yields app-specific events such as [URLEvent]. [Window.Event]
|
||||||
|
yields events that target a particular window.
|
||||||
|
|
||||||
A FrameEvent's Queue method returns an event.Queue implementation that distributes
|
# Permissions
|
||||||
incoming events to the event handlers declared in the last frame.
|
|
||||||
See the gioui.org/io/event package for more information about event handlers.
|
|
||||||
|
|
||||||
Permissions
|
|
||||||
|
|
||||||
The packages under gioui.org/app/permission should be imported
|
The packages under gioui.org/app/permission should be imported
|
||||||
by a Gio program or by one of its dependencies to indicate that specific
|
by a Gio program or by one of its dependencies to indicate that specific
|
||||||
|
|||||||
+6
-6
@@ -1,5 +1,7 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
//go:build !noopengl
|
||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -15,9 +17,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type androidContext struct {
|
type androidContext struct {
|
||||||
win *window
|
win *window
|
||||||
eglSurf egl.NativeWindowType
|
eglSurf egl.NativeWindowType
|
||||||
width, height int
|
|
||||||
*egl.Context
|
*egl.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,9 +44,8 @@ func (c *androidContext) Refresh() error {
|
|||||||
if err := c.win.setVisual(c.Context.VisualID()); err != nil {
|
if err := c.win.setVisual(c.Context.VisualID()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
win, width, height := c.win.nativeWindow()
|
win, _, _ := c.win.nativeWindow()
|
||||||
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
|
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
|
||||||
c.width, c.height = width, height
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ func (c *androidContext) Lock() error {
|
|||||||
// The Android emulator creates a broken surface if it is not
|
// The Android emulator creates a broken surface if it is not
|
||||||
// created on the same thread as the context is made current.
|
// created on the same thread as the context is made current.
|
||||||
if c.eglSurf != nil {
|
if c.eglSurf != nil {
|
||||||
if err := c.Context.CreateSurface(c.eglSurf, c.width, c.height); err != nil {
|
if err := c.Context.CreateSurface(c.eglSurf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.eglSurf = nil
|
c.eglSurf = nil
|
||||||
|
|||||||
+22
-14
@@ -1,8 +1,9 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
//go:build ((linux && !android) || freebsd) && !nowayland
|
//go:build ((linux && !android) || freebsd) && !nowayland && !noopengl
|
||||||
// +build linux,!android freebsd
|
// +build linux,!android freebsd
|
||||||
// +build !nowayland
|
// +build !nowayland
|
||||||
|
// +build !noopengl
|
||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
@@ -37,7 +38,24 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &wlContext{Context: ctx, win: w}, nil
|
|
||||||
|
surf, width, height := w.surface()
|
||||||
|
if surf == nil {
|
||||||
|
return nil, errors.New("wayland: no surface")
|
||||||
|
}
|
||||||
|
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
||||||
|
if eglWin == nil {
|
||||||
|
return nil, errors.New("wayland: wl_egl_window_create failed")
|
||||||
|
}
|
||||||
|
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||||
|
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// We're in charge of the frame callbacks, don't let eglSwapBuffers
|
||||||
|
// wait for callbacks that may never arrive.
|
||||||
|
ctx.EnableVSync(false)
|
||||||
|
|
||||||
|
return &wlContext{Context: ctx, win: w, eglWin: eglWin}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,22 +71,12 @@ func (c *wlContext) Release() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *wlContext) Refresh() error {
|
func (c *wlContext) Refresh() error {
|
||||||
c.Context.ReleaseSurface()
|
|
||||||
if c.eglWin != nil {
|
|
||||||
C.wl_egl_window_destroy(c.eglWin)
|
|
||||||
c.eglWin = nil
|
|
||||||
}
|
|
||||||
surf, width, height := c.win.surface()
|
surf, width, height := c.win.surface()
|
||||||
if surf == nil {
|
if surf == nil {
|
||||||
return errors.New("wayland: no surface")
|
return errors.New("wayland: no surface")
|
||||||
}
|
}
|
||||||
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
C.wl_egl_window_resize(c.eglWin, C.int(width), C.int(height), 0, 0)
|
||||||
if eglWin == nil {
|
return nil
|
||||||
return errors.New("wayland: wl_egl_window_create failed")
|
|
||||||
}
|
|
||||||
c.eglWin = eglWin
|
|
||||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
|
||||||
return c.Context.CreateSurface(eglSurf, width, height)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *wlContext) Lock() error {
|
func (c *wlContext) Lock() error {
|
||||||
|
|||||||
+14
-17
@@ -1,10 +1,10 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
//go:build !noopengl
|
||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
|
|
||||||
"gioui.org/internal/egl"
|
"gioui.org/internal/egl"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,6 +22,18 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
win, _, _ := w.HWND()
|
||||||
|
eglSurf := egl.NativeWindowType(win)
|
||||||
|
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||||
|
ctx.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := ctx.MakeCurrent(); err != nil {
|
||||||
|
ctx.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer ctx.ReleaseCurrent()
|
||||||
|
ctx.EnableVSync(true)
|
||||||
return &glContext{win: w, Context: ctx}, nil
|
return &glContext{win: w, Context: ctx}, nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -35,21 +47,6 @@ func (c *glContext) Release() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *glContext) Refresh() error {
|
func (c *glContext) Refresh() error {
|
||||||
c.Context.ReleaseSurface()
|
|
||||||
var (
|
|
||||||
win windows.Handle
|
|
||||||
width, height int
|
|
||||||
)
|
|
||||||
win, width, height = c.win.HWND()
|
|
||||||
eglSurf := egl.NativeWindowType(win)
|
|
||||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.Context.MakeCurrent(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.Context.EnableVSync(true)
|
|
||||||
c.Context.ReleaseCurrent()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-12
@@ -1,8 +1,9 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
//go:build ((linux && !android) || freebsd || openbsd) && !nox11
|
//go:build ((linux && !android) || freebsd || openbsd) && !nox11 && !noopengl
|
||||||
// +build linux,!android freebsd openbsd
|
// +build linux,!android freebsd openbsd
|
||||||
// +build !nox11
|
// +build !nox11
|
||||||
|
// +build !noopengl
|
||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
@@ -24,6 +25,18 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
win, _, _ := w.window()
|
||||||
|
eglSurf := egl.NativeWindowType(uintptr(win))
|
||||||
|
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||||
|
ctx.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := ctx.MakeCurrent(); err != nil {
|
||||||
|
ctx.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer ctx.ReleaseCurrent()
|
||||||
|
ctx.EnableVSync(true)
|
||||||
return &x11Context{win: w, Context: ctx}, nil
|
return &x11Context{win: w, Context: ctx}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,17 +49,6 @@ func (c *x11Context) Release() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *x11Context) Refresh() error {
|
func (c *x11Context) Refresh() error {
|
||||||
c.Context.ReleaseSurface()
|
|
||||||
win, width, height := c.win.window()
|
|
||||||
eglSurf := egl.NativeWindowType(uintptr(win))
|
|
||||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.Context.MakeCurrent(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.Context.EnableVSync(true)
|
|
||||||
c.Context.ReleaseCurrent()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
//go:build darwin && ios && nometal
|
//go:build darwin && ios && nometal
|
||||||
// +build darwin,ios,nometal
|
|
||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
|
|||||||
+14
-4
@@ -13,6 +13,7 @@ import (
|
|||||||
type glContext struct {
|
type glContext struct {
|
||||||
ctx js.Value
|
ctx js.Value
|
||||||
cnv js.Value
|
cnv js.Value
|
||||||
|
w *window
|
||||||
}
|
}
|
||||||
|
|
||||||
func newContext(w *window) (*glContext, error) {
|
func newContext(w *window) (*glContext, error) {
|
||||||
@@ -32,11 +33,15 @@ func newContext(w *window) (*glContext, error) {
|
|||||||
c := &glContext{
|
c := &glContext{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cnv: w.cnv,
|
cnv: w.cnv,
|
||||||
|
w: w,
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
|
func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||||
|
if c.w.contextStatus != contextStatusOkay {
|
||||||
|
return nil, gpu.ErrDeviceLost
|
||||||
|
}
|
||||||
return gpu.OpenGLRenderTarget{}, nil
|
return gpu.OpenGLRenderTarget{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,9 +53,6 @@ func (c *glContext) Release() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *glContext) Present() error {
|
func (c *glContext) Present() error {
|
||||||
if c.ctx.Call("isContextLost").Bool() {
|
|
||||||
return errors.New("context lost")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +63,15 @@ func (c *glContext) Lock() error {
|
|||||||
func (c *glContext) Unlock() {}
|
func (c *glContext) Unlock() {}
|
||||||
|
|
||||||
func (c *glContext) Refresh() error {
|
func (c *glContext) Refresh() error {
|
||||||
return nil
|
switch c.w.contextStatus {
|
||||||
|
case contextStatusLost:
|
||||||
|
return errOutOfDate
|
||||||
|
case contextStatusRestored:
|
||||||
|
c.w.contextStatus = contextStatusOkay
|
||||||
|
return gpu.ErrDeviceLost
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) NewContext() (context, error) {
|
func (w *window) NewContext() (context, error) {
|
||||||
|
|||||||
+3
-2
@@ -1,14 +1,12 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
//go:build darwin && !ios && nometal
|
//go:build darwin && !ios && nometal
|
||||||
// +build darwin,!ios,nometal
|
|
||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/gpu"
|
"gioui.org/gpu"
|
||||||
@@ -16,6 +14,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -xobjective-c -fobjc-arc
|
||||||
|
#cgo LDFLAGS: -framework OpenGL
|
||||||
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
#include <CoreGraphics/CoreGraphics.h>
|
#include <CoreGraphics/CoreGraphics.h>
|
||||||
#include <AppKit/AppKit.h>
|
#include <AppKit/AppKit.h>
|
||||||
|
|||||||
+2
-3
@@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
// +build darwin,!ios,nometal
|
// +build darwin,!ios,nometal
|
||||||
|
|
||||||
@import AppKit;
|
#import <AppKit/AppKit.h>
|
||||||
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
#include <OpenGL/OpenGL.h>
|
#include <OpenGL/OpenGL.h>
|
||||||
#include "_cgo_export.h"
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
CALayer *gio_layerFactory(void) {
|
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
return [CALayer layer];
|
return [CALayer layer];
|
||||||
}
|
}
|
||||||
|
|||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"gioui.org/io/input"
|
||||||
|
"gioui.org/io/key"
|
||||||
|
)
|
||||||
|
|
||||||
|
type editorState struct {
|
||||||
|
input.EditorState
|
||||||
|
compose key.Range
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *editorState) Replace(r key.Range, text string) {
|
||||||
|
if r.Start > r.End {
|
||||||
|
r.Start, r.End = r.End, r.Start
|
||||||
|
}
|
||||||
|
runes := []rune(text)
|
||||||
|
newEnd := r.Start + len(runes)
|
||||||
|
adjust := func(pos int) int {
|
||||||
|
switch {
|
||||||
|
case newEnd < pos && pos <= r.End:
|
||||||
|
return newEnd
|
||||||
|
case r.End < pos:
|
||||||
|
diff := newEnd - r.End
|
||||||
|
return pos + diff
|
||||||
|
}
|
||||||
|
return pos
|
||||||
|
}
|
||||||
|
e.Selection.Start = adjust(e.Selection.Start)
|
||||||
|
e.Selection.End = adjust(e.Selection.End)
|
||||||
|
if e.compose.Start != -1 {
|
||||||
|
e.compose.Start = adjust(e.compose.Start)
|
||||||
|
e.compose.End = adjust(e.compose.End)
|
||||||
|
}
|
||||||
|
s := e.Snippet
|
||||||
|
if r.End < s.Start || r.Start > s.End {
|
||||||
|
// Discard snippet if it doesn't overlap with replacement.
|
||||||
|
s = key.Snippet{
|
||||||
|
Range: key.Range{
|
||||||
|
Start: r.Start,
|
||||||
|
End: r.Start,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var newSnippet []rune
|
||||||
|
snippet := []rune(s.Text)
|
||||||
|
// Append first part of existing snippet.
|
||||||
|
if end := r.Start - s.Start; end > 0 {
|
||||||
|
newSnippet = append(newSnippet, snippet[:end]...)
|
||||||
|
}
|
||||||
|
// Append replacement.
|
||||||
|
newSnippet = append(newSnippet, runes...)
|
||||||
|
// Append last part of existing snippet.
|
||||||
|
if start := r.End; start < s.End {
|
||||||
|
newSnippet = append(newSnippet, snippet[start-s.Start:]...)
|
||||||
|
}
|
||||||
|
// Adjust snippet range to include replacement.
|
||||||
|
if r.Start < s.Start {
|
||||||
|
s.Start = r.Start
|
||||||
|
}
|
||||||
|
s.End = s.Start + len(newSnippet)
|
||||||
|
s.Text = string(newSnippet)
|
||||||
|
e.Snippet = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// UTF16Index converts the given index in runes into an index in utf16 characters.
|
||||||
|
func (e *editorState) UTF16Index(runes int) int {
|
||||||
|
if runes == -1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if runes < e.Snippet.Start {
|
||||||
|
// Assume runes before sippet are one UTF-16 character each.
|
||||||
|
return runes
|
||||||
|
}
|
||||||
|
chars := e.Snippet.Start
|
||||||
|
runes -= e.Snippet.Start
|
||||||
|
for _, r := range e.Snippet.Text {
|
||||||
|
if runes == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
runes--
|
||||||
|
chars++
|
||||||
|
if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
|
||||||
|
chars++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Assume runes after snippets are one UTF-16 character each.
|
||||||
|
return chars + runes
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunesIndex converts the given index in utf16 characters to an index in runes.
|
||||||
|
func (e *editorState) RunesIndex(chars int) int {
|
||||||
|
if chars == -1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if chars < e.Snippet.Start {
|
||||||
|
// Assume runes before offset are one UTF-16 character each.
|
||||||
|
return chars
|
||||||
|
}
|
||||||
|
runes := e.Snippet.Start
|
||||||
|
chars -= e.Snippet.Start
|
||||||
|
for _, r := range e.Snippet.Text {
|
||||||
|
if chars == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
chars--
|
||||||
|
runes++
|
||||||
|
if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
|
||||||
|
chars--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Assume runes after snippets are one UTF-16 character each.
|
||||||
|
return runes + chars
|
||||||
|
}
|
||||||
|
|
||||||
|
// areSnippetsConsistent reports whether the content of the old snippet is
|
||||||
|
// consistent with the content of the new.
|
||||||
|
func areSnippetsConsistent(old, new key.Snippet) bool {
|
||||||
|
// Compute the overlapping range.
|
||||||
|
r := old.Range
|
||||||
|
r.Start = max(r.Start, new.Start)
|
||||||
|
r.End = max(r.End, r.Start)
|
||||||
|
r.End = min(r.End, new.End)
|
||||||
|
return snippetSubstring(old, r) == snippetSubstring(new, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func snippetSubstring(s key.Snippet, r key.Range) string {
|
||||||
|
for r.Start > s.Start && r.Start < s.End {
|
||||||
|
_, n := utf8.DecodeRuneInString(s.Text)
|
||||||
|
s.Text = s.Text[n:]
|
||||||
|
s.Start++
|
||||||
|
}
|
||||||
|
for r.End < s.End && r.End > s.Start {
|
||||||
|
_, n := utf8.DecodeLastRuneInString(s.Text)
|
||||||
|
s.Text = s.Text[:len(s.Text)-n]
|
||||||
|
s.End--
|
||||||
|
}
|
||||||
|
return s.Text
|
||||||
|
}
|
||||||
+32
-10
@@ -1,17 +1,16 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
//go:build go1.18
|
|
||||||
// +build go1.18
|
|
||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"gioui.org/f32"
|
||||||
"testing"
|
"testing"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"gioui.org/font"
|
||||||
"gioui.org/font/gofont"
|
"gioui.org/font/gofont"
|
||||||
|
"gioui.org/io/input"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/router"
|
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
@@ -28,17 +27,18 @@ func FuzzIME(f *testing.F) {
|
|||||||
f.Add([]byte("20007800002\x02000"))
|
f.Add([]byte("20007800002\x02000"))
|
||||||
f.Add([]byte("200A02000990\x19002\x17\x0200"))
|
f.Add([]byte("200A02000990\x19002\x17\x0200"))
|
||||||
f.Fuzz(func(t *testing.T, cmds []byte) {
|
f.Fuzz(func(t *testing.T, cmds []byte) {
|
||||||
cache := text.NewCache(gofont.Collection())
|
cache := text.NewShaper(text.WithCollection(gofont.Collection()))
|
||||||
e := new(widget.Editor)
|
e := new(widget.Editor)
|
||||||
e.Focus()
|
|
||||||
|
|
||||||
var r router.Router
|
var r input.Router
|
||||||
gtx := layout.Context{Ops: new(op.Ops), Queue: &r}
|
gtx := layout.Context{Ops: new(op.Ops), Source: r.Source()}
|
||||||
|
gtx.Execute(key.FocusCmd{Tag: e})
|
||||||
// Layout once to register focus.
|
// Layout once to register focus.
|
||||||
e.Layout(gtx, cache, text.Font{}, unit.Px(10), nil)
|
e.Layout(gtx, cache, font.Font{}, unit.Sp(10), op.CallOp{}, op.CallOp{})
|
||||||
r.Frame(gtx.Ops)
|
r.Frame(gtx.Ops)
|
||||||
|
|
||||||
var state editorState
|
var state editorState
|
||||||
|
state.Selection.Transform = f32.AffineId()
|
||||||
const (
|
const (
|
||||||
cmdReplace = iota
|
cmdReplace = iota
|
||||||
cmdSelect
|
cmdSelect
|
||||||
@@ -103,11 +103,32 @@ func FuzzIME(f *testing.F) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmds = cmds[cmdLen:]
|
cmds = cmds[cmdLen:]
|
||||||
e.Layout(gtx, cache, text.Font{}, unit.Px(10), nil)
|
e.Layout(gtx, cache, font.Font{}, unit.Sp(10), op.CallOp{}, op.CallOp{})
|
||||||
r.Frame(gtx.Ops)
|
r.Frame(gtx.Ops)
|
||||||
newState := r.EditorState()
|
newState := r.EditorState()
|
||||||
// We don't track caret position.
|
// We don't track caret position.
|
||||||
state.Selection.Caret = newState.Selection.Caret
|
state.Selection.Caret = newState.Selection.Caret
|
||||||
|
// Expanded snippets are ok.
|
||||||
|
their, our := newState.Snippet, state.EditorState.Snippet
|
||||||
|
beforeLen := 0
|
||||||
|
for before := our.Start - their.Start; before > 0; before-- {
|
||||||
|
_, n := utf8.DecodeRuneInString(their.Text[beforeLen:])
|
||||||
|
beforeLen += n
|
||||||
|
}
|
||||||
|
afterLen := 0
|
||||||
|
for after := their.End - our.End; after > 0; after-- {
|
||||||
|
_, n := utf8.DecodeLastRuneInString(their.Text[:len(their.Text)-afterLen])
|
||||||
|
afterLen += n
|
||||||
|
}
|
||||||
|
if beforeLen > 0 {
|
||||||
|
our.Text = their.Text[:beforeLen] + our.Text
|
||||||
|
our.Start = their.Start
|
||||||
|
}
|
||||||
|
if afterLen > 0 {
|
||||||
|
our.Text = our.Text + their.Text[len(their.Text)-afterLen:]
|
||||||
|
our.End = their.End
|
||||||
|
}
|
||||||
|
state.EditorState.Snippet = our
|
||||||
if newState != state.EditorState {
|
if newState != state.EditorState {
|
||||||
t.Errorf("IME state: %+v\neditor state: %+v", state.EditorState, newState)
|
t.Errorf("IME state: %+v\neditor state: %+v", state.EditorState, newState)
|
||||||
}
|
}
|
||||||
@@ -117,6 +138,7 @@ func FuzzIME(f *testing.F) {
|
|||||||
|
|
||||||
func TestEditorIndices(t *testing.T) {
|
func TestEditorIndices(t *testing.T) {
|
||||||
var s editorState
|
var s editorState
|
||||||
|
s.Selection.Transform = f32.AffineId()
|
||||||
const str = "Hello, 😀"
|
const str = "Hello, 😀"
|
||||||
s.Snippet = key.Snippet{
|
s.Snippet = key.Snippet{
|
||||||
Text: str,
|
Text: str,
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
// Package points standard output, standard error and the standard
|
|
||||||
// library package log to the platform logger.
|
|
||||||
package log
|
|
||||||
|
|
||||||
var appID = "gio"
|
|
||||||
+243
-42
@@ -47,6 +47,13 @@ type WndClassEx struct {
|
|||||||
HIconSm syscall.Handle
|
HIconSm syscall.Handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Margins struct {
|
||||||
|
CxLeftWidth int32
|
||||||
|
CxRightWidth int32
|
||||||
|
CyTopHeight int32
|
||||||
|
CyBottomHeight int32
|
||||||
|
}
|
||||||
|
|
||||||
type Msg struct {
|
type Msg struct {
|
||||||
Hwnd syscall.Handle
|
Hwnd syscall.Handle
|
||||||
Message uint32
|
Message uint32
|
||||||
@@ -69,6 +76,21 @@ type MinMaxInfo struct {
|
|||||||
PtMaxTrackSize Point
|
PtMaxTrackSize Point
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NCCalcSizeParams struct {
|
||||||
|
Rgrc [3]Rect
|
||||||
|
LpPos *WindowPos
|
||||||
|
}
|
||||||
|
|
||||||
|
type WindowPos struct {
|
||||||
|
HWND syscall.Handle
|
||||||
|
HWNDInsertAfter syscall.Handle
|
||||||
|
x int32
|
||||||
|
y int32
|
||||||
|
cx int32
|
||||||
|
cy int32
|
||||||
|
flags uint32
|
||||||
|
}
|
||||||
|
|
||||||
type WindowPlacement struct {
|
type WindowPlacement struct {
|
||||||
length uint32
|
length uint32
|
||||||
flags uint32
|
flags uint32
|
||||||
@@ -86,6 +108,80 @@ type MonitorInfo struct {
|
|||||||
Flags uint32
|
Flags uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CopyDataStruct struct {
|
||||||
|
DwData uintptr
|
||||||
|
CbData uint32
|
||||||
|
LpData uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type POINTER_INPUT_TYPE int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
PT_POINTER POINTER_INPUT_TYPE = 1
|
||||||
|
PT_TOUCH POINTER_INPUT_TYPE = 2
|
||||||
|
PT_PEN POINTER_INPUT_TYPE = 3
|
||||||
|
PT_MOUSE POINTER_INPUT_TYPE = 4
|
||||||
|
PT_TOUCHPAD POINTER_INPUT_TYPE = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
type POINTER_INFO_POINTER_FLAGS int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
POINTER_FLAG_NEW POINTER_INFO_POINTER_FLAGS = 0x00000001
|
||||||
|
POINTER_FLAG_INRANGE POINTER_INFO_POINTER_FLAGS = 0x00000002
|
||||||
|
POINTER_FLAG_INCONTACT POINTER_INFO_POINTER_FLAGS = 0x00000004
|
||||||
|
POINTER_FLAG_FIRSTBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000010
|
||||||
|
POINTER_FLAG_SECONDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000020
|
||||||
|
POINTER_FLAG_THIRDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000040
|
||||||
|
POINTER_FLAG_FOURTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000080
|
||||||
|
POINTER_FLAG_FIFTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000100
|
||||||
|
POINTER_FLAG_PRIMARY POINTER_INFO_POINTER_FLAGS = 0x00002000
|
||||||
|
POINTER_FLAG_CONFIDENCE POINTER_INFO_POINTER_FLAGS = 0x00004000
|
||||||
|
POINTER_FLAG_CANCELED POINTER_INFO_POINTER_FLAGS = 0x00008000
|
||||||
|
POINTER_FLAG_DOWN POINTER_INFO_POINTER_FLAGS = 0x00010000
|
||||||
|
POINTER_FLAG_UPDATE POINTER_INFO_POINTER_FLAGS = 0x00020000
|
||||||
|
POINTER_FLAG_UP POINTER_INFO_POINTER_FLAGS = 0x00040000
|
||||||
|
POINTER_FLAG_WHEEL POINTER_INFO_POINTER_FLAGS = 0x00080000
|
||||||
|
POINTER_FLAG_HWHEEL POINTER_INFO_POINTER_FLAGS = 0x00100000
|
||||||
|
POINTER_FLAG_CAPTURECHANGED POINTER_INFO_POINTER_FLAGS = 0x00200000
|
||||||
|
POINTER_FLAG_HASTRANSFORM POINTER_INFO_POINTER_FLAGS = 0x00400000
|
||||||
|
)
|
||||||
|
|
||||||
|
type POINTER_BUTTON_CHANGE_TYPE int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
POINTER_CHANGE_NONE POINTER_BUTTON_CHANGE_TYPE = 0
|
||||||
|
POINTER_CHANGE_FIRSTBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 1
|
||||||
|
POINTER_CHANGE_FIRSTBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 2
|
||||||
|
POINTER_CHANGE_SECONDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 3
|
||||||
|
POINTER_CHANGE_SECONDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 4
|
||||||
|
POINTER_CHANGE_THIRDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 5
|
||||||
|
POINTER_CHANGE_THIRDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 6
|
||||||
|
POINTER_CHANGE_FOURTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 7
|
||||||
|
POINTER_CHANGE_FOURTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 8
|
||||||
|
POINTER_CHANGE_FIFTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 9
|
||||||
|
POINTER_CHANGE_FIFTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
type PointerInfo struct {
|
||||||
|
PointerType POINTER_INPUT_TYPE
|
||||||
|
PointerId uint32
|
||||||
|
FrameId uint32
|
||||||
|
PointerFlags POINTER_INFO_POINTER_FLAGS
|
||||||
|
SourceDevice syscall.Handle
|
||||||
|
HwndTarget syscall.Handle
|
||||||
|
PtPixelLocation Point
|
||||||
|
PtHimetricLocation Point
|
||||||
|
PtPixelLocationRaw Point
|
||||||
|
PtHimetricLocationRaw Point
|
||||||
|
DwTime uint32
|
||||||
|
HistoryCount uint32
|
||||||
|
InputData int32
|
||||||
|
DwKeyStates uint32
|
||||||
|
PerformanceCount uint64
|
||||||
|
ButtonChangeType POINTER_BUTTON_CHANGE_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TRUE = 1
|
TRUE = 1
|
||||||
|
|
||||||
@@ -111,9 +207,20 @@ const (
|
|||||||
CFS_POINT = 0x0002
|
CFS_POINT = 0x0002
|
||||||
CFS_CANDIDATEPOS = 0x0040
|
CFS_CANDIDATEPOS = 0x0040
|
||||||
|
|
||||||
HWND_TOPMOST = ^(uint32(1) - 1) // -1
|
HWND_TOP = syscall.Handle(0)
|
||||||
|
HWND_TOPMOST = ^(syscall.Handle(1) - 1) // -1
|
||||||
|
HWND_NOTOPMOST = ^(syscall.Handle(2) - 1) // -2
|
||||||
|
|
||||||
HTCLIENT = 1
|
HTCAPTION = 2
|
||||||
|
HTCLIENT = 1
|
||||||
|
HTLEFT = 10
|
||||||
|
HTRIGHT = 11
|
||||||
|
HTTOP = 12
|
||||||
|
HTTOPLEFT = 13
|
||||||
|
HTTOPRIGHT = 14
|
||||||
|
HTBOTTOM = 15
|
||||||
|
HTBOTTOMLEFT = 16
|
||||||
|
HTBOTTOMRIGHT = 17
|
||||||
|
|
||||||
IDC_APPSTARTING = 32650 // Standard arrow and small hourglass
|
IDC_APPSTARTING = 32650 // Standard arrow and small hourglass
|
||||||
IDC_ARROW = 32512 // Standard arrow
|
IDC_ARROW = 32512 // Standard arrow
|
||||||
@@ -146,13 +253,16 @@ const (
|
|||||||
|
|
||||||
SCS_SETSTR = GCS_COMPREADSTR | GCS_COMPSTR
|
SCS_SETSTR = GCS_COMPREADSTR | GCS_COMPSTR
|
||||||
|
|
||||||
|
SM_CXSIZEFRAME = 32
|
||||||
|
SM_CYSIZEFRAME = 33
|
||||||
|
|
||||||
SW_SHOWDEFAULT = 10
|
SW_SHOWDEFAULT = 10
|
||||||
SW_SHOWMINIMIZED = 2
|
SW_SHOWMINIMIZED = 2
|
||||||
SW_SHOWMAXIMIZED = 3
|
SW_SHOWMAXIMIZED = 3
|
||||||
SW_SHOWNORMAL = 1
|
SW_SHOWNORMAL = 1
|
||||||
SW_SHOW = 5
|
SW_SHOW = 5
|
||||||
SWP_FRAMECHANGED = 0x0020
|
|
||||||
|
|
||||||
|
SWP_FRAMECHANGED = 0x0020
|
||||||
SWP_NOMOVE = 0x0002
|
SWP_NOMOVE = 0x0002
|
||||||
SWP_NOOWNERZORDER = 0x0200
|
SWP_NOOWNERZORDER = 0x0200
|
||||||
SWP_NOSIZE = 0x0001
|
SWP_NOSIZE = 0x0001
|
||||||
@@ -210,43 +320,54 @@ const (
|
|||||||
|
|
||||||
UNICODE_NOCHAR = 65535
|
UNICODE_NOCHAR = 65535
|
||||||
|
|
||||||
WM_CANCELMODE = 0x001F
|
WM_CANCELMODE = 0x001F
|
||||||
WM_CHAR = 0x0102
|
WM_CHAR = 0x0102
|
||||||
WM_CLOSE = 0x0010
|
WM_CLOSE = 0x0010
|
||||||
WM_CREATE = 0x0001
|
WM_COPYDATA = 0x004A
|
||||||
WM_DPICHANGED = 0x02E0
|
WM_CREATE = 0x0001
|
||||||
WM_DESTROY = 0x0002
|
WM_DPICHANGED = 0x02E0
|
||||||
WM_ERASEBKGND = 0x0014
|
WM_DESTROY = 0x0002
|
||||||
WM_GETMINMAXINFO = 0x0024
|
WM_ERASEBKGND = 0x0014
|
||||||
WM_IME_COMPOSITION = 0x010F
|
WM_GETMINMAXINFO = 0x0024
|
||||||
WM_IME_ENDCOMPOSITION = 0x010E
|
WM_IME_COMPOSITION = 0x010F
|
||||||
WM_IME_STARTCOMPOSITION = 0x010D
|
WM_IME_ENDCOMPOSITION = 0x010E
|
||||||
WM_KEYDOWN = 0x0100
|
WM_IME_STARTCOMPOSITION = 0x010D
|
||||||
WM_KEYUP = 0x0101
|
WM_KEYDOWN = 0x0100
|
||||||
WM_KILLFOCUS = 0x0008
|
WM_KEYUP = 0x0101
|
||||||
WM_LBUTTONDOWN = 0x0201
|
WM_KILLFOCUS = 0x0008
|
||||||
WM_LBUTTONUP = 0x0202
|
WM_LBUTTONDOWN = 0x0201
|
||||||
WM_MBUTTONDOWN = 0x0207
|
WM_LBUTTONUP = 0x0202
|
||||||
WM_MBUTTONUP = 0x0208
|
WM_MBUTTONDOWN = 0x0207
|
||||||
WM_MOUSEMOVE = 0x0200
|
WM_MBUTTONUP = 0x0208
|
||||||
WM_MOUSEWHEEL = 0x020A
|
WM_MOUSEMOVE = 0x0200
|
||||||
WM_MOUSEHWHEEL = 0x020E
|
WM_MOUSEWHEEL = 0x020A
|
||||||
WM_PAINT = 0x000F
|
WM_MOUSEHWHEEL = 0x020E
|
||||||
WM_QUIT = 0x0012
|
WM_NCACTIVATE = 0x0086
|
||||||
WM_SETCURSOR = 0x0020
|
WM_NCHITTEST = 0x0084
|
||||||
WM_SETFOCUS = 0x0007
|
WM_NCCALCSIZE = 0x0083
|
||||||
WM_SHOWWINDOW = 0x0018
|
WM_PAINT = 0x000F
|
||||||
WM_SIZE = 0x0005
|
WM_POINTERCAPTURECHANGED = 0x024C
|
||||||
WM_SYSKEYDOWN = 0x0104
|
WM_POINTERDOWN = 0x0246
|
||||||
WM_SYSKEYUP = 0x0105
|
WM_POINTERUP = 0x0247
|
||||||
WM_RBUTTONDOWN = 0x0204
|
WM_POINTERUPDATE = 0x0245
|
||||||
WM_RBUTTONUP = 0x0205
|
WM_POINTERWHEEL = 0x024E
|
||||||
WM_TIMER = 0x0113
|
WM_POINTERHWHEEL = 0x024F
|
||||||
WM_UNICHAR = 0x0109
|
WM_QUIT = 0x0012
|
||||||
WM_USER = 0x0400
|
WM_RBUTTONDOWN = 0x0204
|
||||||
WM_WINDOWPOSCHANGED = 0x0047
|
WM_RBUTTONUP = 0x0205
|
||||||
|
WM_SETCURSOR = 0x0020
|
||||||
|
WM_SETFOCUS = 0x0007
|
||||||
|
WM_SHOWWINDOW = 0x0018
|
||||||
|
WM_SIZE = 0x0005
|
||||||
|
WM_STYLECHANGED = 0x007D
|
||||||
|
WM_SYSKEYDOWN = 0x0104
|
||||||
|
WM_SYSKEYUP = 0x0105
|
||||||
|
WM_TIMER = 0x0113
|
||||||
|
WM_UNICHAR = 0x0109
|
||||||
|
WM_USER = 0x0400
|
||||||
|
WM_WINDOWPOSCHANGED = 0x0047
|
||||||
|
|
||||||
WS_CLIPCHILDREN = 0x00010000
|
WS_CLIPCHILDREN = 0x02000000
|
||||||
WS_CLIPSIBLINGS = 0x04000000
|
WS_CLIPSIBLINGS = 0x04000000
|
||||||
WS_MAXIMIZE = 0x01000000
|
WS_MAXIMIZE = 0x01000000
|
||||||
WS_ICONIC = 0x20000000
|
WS_ICONIC = 0x20000000
|
||||||
@@ -307,8 +428,11 @@ var (
|
|||||||
_DefWindowProc = user32.NewProc("DefWindowProcW")
|
_DefWindowProc = user32.NewProc("DefWindowProcW")
|
||||||
_DestroyWindow = user32.NewProc("DestroyWindow")
|
_DestroyWindow = user32.NewProc("DestroyWindow")
|
||||||
_DispatchMessage = user32.NewProc("DispatchMessageW")
|
_DispatchMessage = user32.NewProc("DispatchMessageW")
|
||||||
|
_FindWindow = user32.NewProc("FindWindowW")
|
||||||
_EmptyClipboard = user32.NewProc("EmptyClipboard")
|
_EmptyClipboard = user32.NewProc("EmptyClipboard")
|
||||||
|
_EnableMouseInPointer = user32.NewProc("EnableMouseInPointer")
|
||||||
_GetWindowRect = user32.NewProc("GetWindowRect")
|
_GetWindowRect = user32.NewProc("GetWindowRect")
|
||||||
|
_GetClientRect = user32.NewProc("GetClientRect")
|
||||||
_GetClipboardData = user32.NewProc("GetClipboardData")
|
_GetClipboardData = user32.NewProc("GetClipboardData")
|
||||||
_GetDC = user32.NewProc("GetDC")
|
_GetDC = user32.NewProc("GetDC")
|
||||||
_GetDpiForWindow = user32.NewProc("GetDpiForWindow")
|
_GetDpiForWindow = user32.NewProc("GetDpiForWindow")
|
||||||
@@ -316,6 +440,8 @@ var (
|
|||||||
_GetMessage = user32.NewProc("GetMessageW")
|
_GetMessage = user32.NewProc("GetMessageW")
|
||||||
_GetMessageTime = user32.NewProc("GetMessageTime")
|
_GetMessageTime = user32.NewProc("GetMessageTime")
|
||||||
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
|
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
|
||||||
|
_GetPointerInfo = user32.NewProc("GetPointerInfo")
|
||||||
|
_GetSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||||
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
|
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
|
||||||
_GetWindowLong32 = user32.NewProc("GetWindowLongW")
|
_GetWindowLong32 = user32.NewProc("GetWindowLongW")
|
||||||
_GetWindowPlacement = user32.NewProc("GetWindowPlacement")
|
_GetWindowPlacement = user32.NewProc("GetWindowPlacement")
|
||||||
@@ -332,9 +458,11 @@ var (
|
|||||||
_PostQuitMessage = user32.NewProc("PostQuitMessage")
|
_PostQuitMessage = user32.NewProc("PostQuitMessage")
|
||||||
_ReleaseCapture = user32.NewProc("ReleaseCapture")
|
_ReleaseCapture = user32.NewProc("ReleaseCapture")
|
||||||
_RegisterClassExW = user32.NewProc("RegisterClassExW")
|
_RegisterClassExW = user32.NewProc("RegisterClassExW")
|
||||||
|
_RegisterTouchWindow = user32.NewProc("RegisterTouchWindow")
|
||||||
_ReleaseDC = user32.NewProc("ReleaseDC")
|
_ReleaseDC = user32.NewProc("ReleaseDC")
|
||||||
_ScreenToClient = user32.NewProc("ScreenToClient")
|
_ScreenToClient = user32.NewProc("ScreenToClient")
|
||||||
_ShowWindow = user32.NewProc("ShowWindow")
|
_ShowWindow = user32.NewProc("ShowWindow")
|
||||||
|
_SendMessage = user32.NewProc("SendMessageW")
|
||||||
_SetCapture = user32.NewProc("SetCapture")
|
_SetCapture = user32.NewProc("SetCapture")
|
||||||
_SetCursor = user32.NewProc("SetCursor")
|
_SetCursor = user32.NewProc("SetCursor")
|
||||||
_SetClipboardData = user32.NewProc("SetClipboardData")
|
_SetClipboardData = user32.NewProc("SetClipboardData")
|
||||||
@@ -364,6 +492,9 @@ var (
|
|||||||
_ImmReleaseContext = imm32.NewProc("ImmReleaseContext")
|
_ImmReleaseContext = imm32.NewProc("ImmReleaseContext")
|
||||||
_ImmSetCandidateWindow = imm32.NewProc("ImmSetCandidateWindow")
|
_ImmSetCandidateWindow = imm32.NewProc("ImmSetCandidateWindow")
|
||||||
_ImmSetCompositionWindow = imm32.NewProc("ImmSetCompositionWindow")
|
_ImmSetCompositionWindow = imm32.NewProc("ImmSetCompositionWindow")
|
||||||
|
|
||||||
|
dwmapi = syscall.NewLazySystemDLL("dwmapi")
|
||||||
|
_DwmExtendFrameIntoClientArea = dwmapi.NewProc("DwmExtendFrameIntoClientArea")
|
||||||
)
|
)
|
||||||
|
|
||||||
func AdjustWindowRectEx(r *Rect, dwStyle uint32, bMenu int, dwExStyle uint32) {
|
func AdjustWindowRectEx(r *Rect, dwStyle uint32, bMenu int, dwExStyle uint32) {
|
||||||
@@ -384,7 +515,10 @@ func CloseClipboard() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, dwStyle uint32, x, y, w, h int32, hWndParent, hMenu, hInstance syscall.Handle, lpParam uintptr) (syscall.Handle, error) {
|
func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, dwStyle uint32, x, y, w, h int32, hWndParent, hMenu, hInstance syscall.Handle, lpParam uintptr) (syscall.Handle, error) {
|
||||||
wname := syscall.StringToUTF16Ptr(lpWindowName)
|
wname, err := syscall.UTF16PtrFromString(lpWindowName)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("CreateWindowEx failed: %v", err)
|
||||||
|
}
|
||||||
hwnd, _, err := _CreateWindowEx.Call(
|
hwnd, _, err := _CreateWindowEx.Call(
|
||||||
uintptr(dwExStyle),
|
uintptr(dwExStyle),
|
||||||
uintptr(lpClassName),
|
uintptr(lpClassName),
|
||||||
@@ -402,6 +536,31 @@ func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, d
|
|||||||
return syscall.Handle(hwnd), nil
|
return syscall.Handle(hwnd), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetPointerInfo(pointerId uint32) (PointerInfo, error) {
|
||||||
|
var info PointerInfo
|
||||||
|
r1, _, err := _GetPointerInfo.Call(uintptr(pointerId), uintptr(unsafe.Pointer(&info)))
|
||||||
|
if r1 == 0 {
|
||||||
|
return PointerInfo{}, fmt.Errorf("GetPointerInfo failed: %v", err)
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterTouchWindow(hwnd syscall.Handle, flags uint32) error {
|
||||||
|
r1, _, err := _RegisterTouchWindow.Call(uintptr(hwnd), uintptr(flags))
|
||||||
|
if r1 == 0 {
|
||||||
|
return fmt.Errorf("RegisterTouchWindow failed: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EnableMouseInPointer(enable uint) error {
|
||||||
|
r1, _, err := _EnableMouseInPointer.Call(uintptr(enable))
|
||||||
|
if r1 == 0 {
|
||||||
|
return fmt.Errorf("EnableMouseInPointer failed: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
||||||
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
|
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
|
||||||
return r
|
return r
|
||||||
@@ -415,6 +574,14 @@ func DispatchMessage(m *Msg) {
|
|||||||
_DispatchMessage.Call(uintptr(unsafe.Pointer(m)))
|
_DispatchMessage.Call(uintptr(unsafe.Pointer(m)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DwmExtendFrameIntoClientArea(hwnd syscall.Handle, margins Margins) error {
|
||||||
|
r, _, _ := _DwmExtendFrameIntoClientArea.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&margins)))
|
||||||
|
if r != 0 {
|
||||||
|
return fmt.Errorf("DwmExtendFrameIntoClientArea: %#x", r)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func EmptyClipboard() error {
|
func EmptyClipboard() error {
|
||||||
r, _, err := _EmptyClipboard.Call()
|
r, _, err := _EmptyClipboard.Call()
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
@@ -423,12 +590,30 @@ func EmptyClipboard() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FindWindow(lpClassName string) (syscall.Handle, error) {
|
||||||
|
className, err := syscall.UTF16PtrFromString(lpClassName)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("FindWindow failed: %v", err)
|
||||||
|
}
|
||||||
|
hwnd, _, err := _FindWindow.Call(uintptr(unsafe.Pointer(className)), 0)
|
||||||
|
if hwnd == 0 {
|
||||||
|
return 0, fmt.Errorf("FindWindow failed: %v", err)
|
||||||
|
}
|
||||||
|
return syscall.Handle(hwnd), nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetWindowRect(hwnd syscall.Handle) Rect {
|
func GetWindowRect(hwnd syscall.Handle) Rect {
|
||||||
var r Rect
|
var r Rect
|
||||||
_GetWindowRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
|
_GetWindowRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetClientRect(hwnd syscall.Handle) Rect {
|
||||||
|
var r Rect
|
||||||
|
_GetClientRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func GetClipboardData(format uint32) (syscall.Handle, error) {
|
func GetClipboardData(format uint32) (syscall.Handle, error) {
|
||||||
r, _, err := _GetClipboardData.Call(uintptr(format))
|
r, _, err := _GetClipboardData.Call(uintptr(format))
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
@@ -499,6 +684,11 @@ func GetMessageTime() time.Duration {
|
|||||||
return time.Duration(r) * time.Millisecond
|
return time.Duration(r) * time.Millisecond
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSystemMetrics(nIndex int) int {
|
||||||
|
r, _, _ := _GetSystemMetrics.Call(uintptr(nIndex))
|
||||||
|
return int(r)
|
||||||
|
}
|
||||||
|
|
||||||
// GetWindowDPI returns the effective DPI of the window.
|
// GetWindowDPI returns the effective DPI of the window.
|
||||||
func GetWindowDPI(hwnd syscall.Handle) int {
|
func GetWindowDPI(hwnd syscall.Handle) int {
|
||||||
// Check for GetDpiForWindow, introduced in Windows 10.
|
// Check for GetDpiForWindow, introduced in Windows 10.
|
||||||
@@ -594,7 +784,7 @@ func SetWindowPlacement(hwnd syscall.Handle, wp *WindowPlacement) {
|
|||||||
_SetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wp)))
|
_SetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wp)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetWindowPos(hwnd syscall.Handle, hwndInsertAfter uint32, x, y, dx, dy int32, style uintptr) {
|
func SetWindowPos(hwnd, hwndInsertAfter syscall.Handle, x, y, dx, dy int32, style uintptr) {
|
||||||
_SetWindowPos.Call(uintptr(hwnd), uintptr(hwndInsertAfter),
|
_SetWindowPos.Call(uintptr(hwnd), uintptr(hwndInsertAfter),
|
||||||
uintptr(x), uintptr(y),
|
uintptr(x), uintptr(y),
|
||||||
uintptr(dx), uintptr(dy),
|
uintptr(dx), uintptr(dy),
|
||||||
@@ -603,7 +793,10 @@ func SetWindowPos(hwnd syscall.Handle, hwndInsertAfter uint32, x, y, dx, dy int3
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SetWindowText(hwnd syscall.Handle, title string) {
|
func SetWindowText(hwnd syscall.Handle, title string) {
|
||||||
wname := syscall.StringToUTF16Ptr(title)
|
wname, err := syscall.UTF16PtrFromString(title)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
_SetWindowText.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wname)))
|
_SetWindowText.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wname)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -719,6 +912,14 @@ func ReleaseDC(hdc syscall.Handle) {
|
|||||||
_ReleaseDC.Call(uintptr(hdc))
|
_ReleaseDC.Call(uintptr(hdc))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SendMessage(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) error {
|
||||||
|
r, _, err := _SendMessage.Call(uintptr(hwnd), uintptr(msg), wParam, lParam)
|
||||||
|
if r == 0 {
|
||||||
|
return fmt.Errorf("SendMessage failed: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func SetForegroundWindow(hwnd syscall.Handle) {
|
func SetForegroundWindow(hwnd syscall.Handle) {
|
||||||
_SetForegroundWindow.Call(uintptr(hwnd))
|
_SetForegroundWindow.Call(uintptr(hwnd))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,10 @@ func (x *Context) LoadKeymap(format int, fd int, size int) error {
|
|||||||
|
|
||||||
func (x *Context) Modifiers() key.Modifiers {
|
func (x *Context) Modifiers() key.Modifiers {
|
||||||
var mods key.Modifiers
|
var mods key.Modifiers
|
||||||
|
if x.state == nil {
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_CTRL[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
|
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_CTRL[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
|
||||||
mods |= key.ModCtrl
|
mods |= key.ModCtrl
|
||||||
}
|
}
|
||||||
@@ -219,6 +223,9 @@ func (x *Context) charsForKeycode(keyCode C.xkb_keycode_t) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (x *Context) IsRepeatKey(keyCode uint32) bool {
|
func (x *Context) IsRepeatKey(keyCode uint32) bool {
|
||||||
|
if x.state == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
kc := C.xkb_keycode_t(keyCode)
|
kc := C.xkb_keycode_t(keyCode)
|
||||||
return C.xkb_keymap_key_repeats(x.keyMap, kc) == 1
|
return C.xkb_keymap_key_repeats(x.keyMap, kc) == 1
|
||||||
}
|
}
|
||||||
@@ -231,14 +238,17 @@ func (x *Context) UpdateMask(depressed, latched, locked, depressedGroup, latched
|
|||||||
C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
|
C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
func convertKeysym(s C.xkb_keysym_t) (key.Name, bool) {
|
||||||
if 'a' <= s && s <= 'z' {
|
if 'a' <= s && s <= 'z' {
|
||||||
return string(rune(s - 'a' + 'A')), true
|
return key.Name(rune(s - 'a' + 'A')), true
|
||||||
|
}
|
||||||
|
if C.XKB_KEY_KP_0 <= s && s <= C.XKB_KEY_KP_9 {
|
||||||
|
return key.Name(rune(s - C.XKB_KEY_KP_0 + '0')), true
|
||||||
}
|
}
|
||||||
if ' ' < s && s <= '~' {
|
if ' ' < s && s <= '~' {
|
||||||
return string(rune(s)), true
|
return key.Name(rune(s)), true
|
||||||
}
|
}
|
||||||
var n string
|
var n key.Name
|
||||||
switch s {
|
switch s {
|
||||||
case C.XKB_KEY_Escape:
|
case C.XKB_KEY_Escape:
|
||||||
n = key.NameEscape
|
n = key.NameEscape
|
||||||
@@ -248,8 +258,6 @@ func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
|||||||
n = key.NameRightArrow
|
n = key.NameRightArrow
|
||||||
case C.XKB_KEY_Return:
|
case C.XKB_KEY_Return:
|
||||||
n = key.NameReturn
|
n = key.NameReturn
|
||||||
case C.XKB_KEY_KP_Enter:
|
|
||||||
n = key.NameEnter
|
|
||||||
case C.XKB_KEY_Up:
|
case C.XKB_KEY_Up:
|
||||||
n = key.NameUpArrow
|
n = key.NameUpArrow
|
||||||
case C.XKB_KEY_Down:
|
case C.XKB_KEY_Down:
|
||||||
@@ -290,9 +298,9 @@ func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
|||||||
n = key.NameF11
|
n = key.NameF11
|
||||||
case C.XKB_KEY_F12:
|
case C.XKB_KEY_F12:
|
||||||
n = key.NameF12
|
n = key.NameF12
|
||||||
case C.XKB_KEY_Tab, C.XKB_KEY_KP_Tab, C.XKB_KEY_ISO_Left_Tab:
|
case C.XKB_KEY_Tab, C.XKB_KEY_ISO_Left_Tab:
|
||||||
n = key.NameTab
|
n = key.NameTab
|
||||||
case 0x20, C.XKB_KEY_KP_Space:
|
case 0x20:
|
||||||
n = key.NameSpace
|
n = key.NameSpace
|
||||||
case C.XKB_KEY_Control_L, C.XKB_KEY_Control_R:
|
case C.XKB_KEY_Control_L, C.XKB_KEY_Control_R:
|
||||||
n = key.NameCtrl
|
n = key.NameCtrl
|
||||||
@@ -302,6 +310,64 @@ func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
|||||||
n = key.NameAlt
|
n = key.NameAlt
|
||||||
case C.XKB_KEY_Super_L, C.XKB_KEY_Super_R:
|
case C.XKB_KEY_Super_L, C.XKB_KEY_Super_R:
|
||||||
n = key.NameSuper
|
n = key.NameSuper
|
||||||
|
|
||||||
|
case C.XKB_KEY_KP_Space:
|
||||||
|
n = key.NameSpace
|
||||||
|
case C.XKB_KEY_KP_Tab:
|
||||||
|
n = key.NameTab
|
||||||
|
case C.XKB_KEY_KP_Enter:
|
||||||
|
n = key.NameEnter
|
||||||
|
case C.XKB_KEY_KP_F1:
|
||||||
|
n = key.NameF1
|
||||||
|
case C.XKB_KEY_KP_F2:
|
||||||
|
n = key.NameF2
|
||||||
|
case C.XKB_KEY_KP_F3:
|
||||||
|
n = key.NameF3
|
||||||
|
case C.XKB_KEY_KP_F4:
|
||||||
|
n = key.NameF4
|
||||||
|
case C.XKB_KEY_KP_Home:
|
||||||
|
n = key.NameHome
|
||||||
|
case C.XKB_KEY_KP_Left:
|
||||||
|
n = key.NameLeftArrow
|
||||||
|
case C.XKB_KEY_KP_Up:
|
||||||
|
n = key.NameUpArrow
|
||||||
|
case C.XKB_KEY_KP_Right:
|
||||||
|
n = key.NameRightArrow
|
||||||
|
case C.XKB_KEY_KP_Down:
|
||||||
|
n = key.NameDownArrow
|
||||||
|
case C.XKB_KEY_KP_Prior:
|
||||||
|
// not supported
|
||||||
|
return "", false
|
||||||
|
case C.XKB_KEY_KP_Next:
|
||||||
|
// not supported
|
||||||
|
return "", false
|
||||||
|
case C.XKB_KEY_KP_End:
|
||||||
|
n = key.NameEnd
|
||||||
|
case C.XKB_KEY_KP_Begin:
|
||||||
|
n = key.NameHome
|
||||||
|
case C.XKB_KEY_KP_Insert:
|
||||||
|
// not supported
|
||||||
|
return "", false
|
||||||
|
case C.XKB_KEY_KP_Delete:
|
||||||
|
n = key.NameDeleteForward
|
||||||
|
case C.XKB_KEY_KP_Multiply:
|
||||||
|
n = "*"
|
||||||
|
case C.XKB_KEY_KP_Add:
|
||||||
|
n = "+"
|
||||||
|
case C.XKB_KEY_KP_Separator:
|
||||||
|
// not supported
|
||||||
|
return "", false
|
||||||
|
case C.XKB_KEY_KP_Subtract:
|
||||||
|
n = "-"
|
||||||
|
case C.XKB_KEY_KP_Decimal:
|
||||||
|
// TODO(dh): does a German keyboard layout also translate the numpad key to XKB_KEY_KP_DECIMAL? Because in
|
||||||
|
// German, the decimal is a comma, not a period.
|
||||||
|
n = "."
|
||||||
|
case C.XKB_KEY_KP_Divide:
|
||||||
|
n = "/"
|
||||||
|
case C.XKB_KEY_KP_Equal:
|
||||||
|
n = "="
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
package log
|
package app
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo LDFLAGS: -llog
|
#cgo LDFLAGS: -llog
|
||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
// 1024 is the truncation limit from android/log.h, plus a \n.
|
// 1024 is the truncation limit from android/log.h, plus a \n.
|
||||||
const logLineLimit = 1024
|
const logLineLimit = 1024
|
||||||
|
|
||||||
var logTag = C.CString(appID)
|
var logTag = C.CString(ID)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Android's logcat already includes timestamps.
|
// Android's logcat already includes timestamps.
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
//go:build darwin && ios
|
//go:build darwin && ios
|
||||||
// +build darwin,ios
|
|
||||||
|
|
||||||
package log
|
package app
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
package log
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
syscall "golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
type logger struct{}
|
type logger struct{}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
kernel32 = syscall.NewLazySystemDLL("kernel32")
|
||||||
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
|
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
|
||||||
debugView *logger
|
debugView *logger
|
||||||
)
|
)
|
||||||
+7
-5
@@ -12,10 +12,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo CFLAGS: -Werror -xobjective-c -fmodules -fobjc-arc
|
#cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
|
||||||
|
#cgo LDFLAGS: -framework QuartzCore -framework Metal
|
||||||
|
|
||||||
@import Metal;
|
#import <Metal/Metal.h>
|
||||||
@import QuartzCore.CAMetalLayer;
|
#import <QuartzCore/CAMetalLayer.h>
|
||||||
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
|
||||||
@@ -59,8 +60,9 @@ static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
|
|||||||
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
||||||
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
||||||
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
||||||
[cmdBuffer presentDrawable:drawable];
|
|
||||||
[cmdBuffer commit];
|
[cmdBuffer commit];
|
||||||
|
[cmdBuffer waitUntilScheduled];
|
||||||
|
[drawable present];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +96,7 @@ func newMtlContext(w *window) (*mtlContext, error) {
|
|||||||
return nil, errors.New("metal: CAMetalLayer construction failed")
|
return nil, errors.New("metal: CAMetalLayer construction failed")
|
||||||
}
|
}
|
||||||
queue := C.newCommandQueue(dev)
|
queue := C.newCommandQueue(dev)
|
||||||
if layer == 0 {
|
if queue == 0 {
|
||||||
C.CFRelease(dev)
|
C.CFRelease(dev)
|
||||||
C.CFRelease(layer)
|
C.CFRelease(layer)
|
||||||
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
|
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
|
||||||
|
|||||||
+4
-2
@@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
//go:build !nometal
|
//go:build !nometal
|
||||||
// +build !nometal
|
|
||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
@@ -21,7 +20,10 @@ Class gio_layerClass(void) {
|
|||||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
UIView *view = (__bridge UIView *)viewRef;
|
UIView *view = (__bridge UIView *)viewRef;
|
||||||
return CFBridgingRetain(view.layer);
|
CAMetalLayer *l = (CAMetalLayer *)view.layer;
|
||||||
|
l.needsDisplayOnBoundsChange = YES;
|
||||||
|
l.presentsWithTransaction = YES;
|
||||||
|
return CFBridgingRetain(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-7
@@ -6,17 +6,19 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo CFLAGS: -Werror -xobjective-c -fmodules -fobjc-arc
|
#cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
|
||||||
|
|
||||||
@import AppKit;
|
|
||||||
|
|
||||||
@import QuartzCore.CAMetalLayer;
|
|
||||||
|
|
||||||
|
#import <AppKit/AppKit.h>
|
||||||
|
#import <QuartzCore/CAMetalLayer.h>
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
|
||||||
CALayer *gio_layerFactory(void) {
|
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
return [CAMetalLayer layer];
|
CAMetalLayer *l = [CAMetalLayer layer];
|
||||||
|
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
|
||||||
|
l.needsDisplayOnBoundsChange = YES;
|
||||||
|
l.presentsWithTransaction = presentWithTrans;
|
||||||
|
return l;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
|
"gioui.org/op"
|
||||||
|
|
||||||
"gioui.org/gpu"
|
"gioui.org/gpu"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
@@ -41,10 +43,15 @@ type Config struct {
|
|||||||
// CustomRenderer is true when the window content is rendered by the
|
// CustomRenderer is true when the window content is rendered by the
|
||||||
// client.
|
// client.
|
||||||
CustomRenderer bool
|
CustomRenderer bool
|
||||||
// center is a flag used to center the window. Set by option.
|
|
||||||
center bool
|
|
||||||
// Decorated reports whether window decorations are provided automatically.
|
// Decorated reports whether window decorations are provided automatically.
|
||||||
Decorated bool
|
Decorated bool
|
||||||
|
// TopMost windows render above all other non-top-most windows.
|
||||||
|
TopMost bool
|
||||||
|
// Focused reports whether the window is focused.
|
||||||
|
Focused bool
|
||||||
|
// decoHeight is the height of the fallback decoration for platforms such
|
||||||
|
// as Wayland that may need fallback client-side decorations.
|
||||||
|
decoHeight unit.Dp
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigEvent is sent whenever the configuration of a Window changes.
|
// ConfigEvent is sent whenever the configuration of a Window changes.
|
||||||
@@ -130,8 +137,30 @@ func (o Orientation) String() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eventLoop implements the functionality required for drivers where
|
||||||
|
// window event loops must run on a separate thread.
|
||||||
|
type eventLoop struct {
|
||||||
|
win *callbacks
|
||||||
|
// wakeup is the callback to wake up the event loop.
|
||||||
|
wakeup func()
|
||||||
|
// driverFuncs is a channel of functions to run the next
|
||||||
|
// time the window loop waits for events.
|
||||||
|
driverFuncs chan func()
|
||||||
|
// invalidates is notified when an invalidate is requested by the client.
|
||||||
|
invalidates chan struct{}
|
||||||
|
// immediateInvalidates is an optimistic invalidates that doesn't require a wakeup.
|
||||||
|
immediateInvalidates chan struct{}
|
||||||
|
// events is where the platform backend delivers events bound for the
|
||||||
|
// user program.
|
||||||
|
events chan event.Event
|
||||||
|
frames chan *op.Ops
|
||||||
|
frameAck chan struct{}
|
||||||
|
// delivering avoids re-entrant event delivery.
|
||||||
|
delivering bool
|
||||||
|
}
|
||||||
|
|
||||||
type frameEvent struct {
|
type frameEvent struct {
|
||||||
system.FrameEvent
|
FrameEvent
|
||||||
|
|
||||||
Sync bool
|
Sync bool
|
||||||
}
|
}
|
||||||
@@ -146,9 +175,13 @@ type context interface {
|
|||||||
Unlock()
|
Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Driver is the interface for the platform implementation
|
// driver is the interface for the platform implementation
|
||||||
// of a window.
|
// of a window.
|
||||||
type driver interface {
|
type driver interface {
|
||||||
|
// Event blocks until an event is available and returns it.
|
||||||
|
Event() event.Event
|
||||||
|
// Invalidate requests a FrameEvent.
|
||||||
|
Invalidate()
|
||||||
// SetAnimating sets the animation flag. When the window is animating,
|
// SetAnimating sets the animation flag. When the window is animating,
|
||||||
// FrameEvents are delivered as fast as the display can handle them.
|
// FrameEvents are delivered as fast as the display can handle them.
|
||||||
SetAnimating(anim bool)
|
SetAnimating(anim bool)
|
||||||
@@ -159,27 +192,27 @@ type driver interface {
|
|||||||
// ReadClipboard requests the clipboard content.
|
// ReadClipboard requests the clipboard content.
|
||||||
ReadClipboard()
|
ReadClipboard()
|
||||||
// WriteClipboard requests a clipboard write.
|
// WriteClipboard requests a clipboard write.
|
||||||
WriteClipboard(s string)
|
WriteClipboard(mime string, s []byte)
|
||||||
// Configure the window.
|
// Configure the window.
|
||||||
Configure([]Option)
|
Configure([]Option)
|
||||||
// SetCursor updates the current cursor to name.
|
// SetCursor updates the current cursor to name.
|
||||||
SetCursor(cursor pointer.Cursor)
|
SetCursor(cursor pointer.Cursor)
|
||||||
// Raise the window at the top.
|
|
||||||
Raise()
|
|
||||||
// Close the window.
|
|
||||||
Close()
|
|
||||||
// Wakeup wakes up the event loop and sends a WakeupEvent.
|
|
||||||
Wakeup()
|
|
||||||
// Perform actions on the window.
|
// Perform actions on the window.
|
||||||
Perform(system.Action)
|
Perform(system.Action)
|
||||||
// EditorStateChanged notifies the driver that the editor state changed.
|
// EditorStateChanged notifies the driver that the editor state changed.
|
||||||
EditorStateChanged(old, new editorState)
|
EditorStateChanged(old, new editorState)
|
||||||
|
// Run a function on the window thread.
|
||||||
|
Run(f func())
|
||||||
|
// Frame receives a frame.
|
||||||
|
Frame(frame *op.Ops)
|
||||||
|
// ProcessEvent processes an event.
|
||||||
|
ProcessEvent(e event.Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
type windowRendezvous struct {
|
type windowRendezvous struct {
|
||||||
in chan windowAndConfig
|
in chan windowAndConfig
|
||||||
out chan windowAndConfig
|
out chan windowAndConfig
|
||||||
errs chan error
|
windows chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type windowAndConfig struct {
|
type windowAndConfig struct {
|
||||||
@@ -189,32 +222,137 @@ type windowAndConfig struct {
|
|||||||
|
|
||||||
func newWindowRendezvous() *windowRendezvous {
|
func newWindowRendezvous() *windowRendezvous {
|
||||||
wr := &windowRendezvous{
|
wr := &windowRendezvous{
|
||||||
in: make(chan windowAndConfig),
|
in: make(chan windowAndConfig),
|
||||||
out: make(chan windowAndConfig),
|
out: make(chan windowAndConfig),
|
||||||
errs: make(chan error),
|
windows: make(chan struct{}),
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
var main windowAndConfig
|
in := wr.in
|
||||||
|
var window windowAndConfig
|
||||||
var out chan windowAndConfig
|
var out chan windowAndConfig
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case w := <-wr.in:
|
case w := <-in:
|
||||||
var err error
|
window = w
|
||||||
if main.window != nil {
|
|
||||||
err = errors.New("multiple windows are not supported")
|
|
||||||
}
|
|
||||||
wr.errs <- err
|
|
||||||
main = w
|
|
||||||
out = wr.out
|
out = wr.out
|
||||||
case out <- main:
|
case out <- window:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return wr
|
return wr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wakeupEvent) ImplementsEvent() {}
|
func newEventLoop(w *callbacks, wakeup func()) *eventLoop {
|
||||||
func (ConfigEvent) ImplementsEvent() {}
|
return &eventLoop{
|
||||||
|
win: w,
|
||||||
|
wakeup: wakeup,
|
||||||
|
events: make(chan event.Event),
|
||||||
|
invalidates: make(chan struct{}, 1),
|
||||||
|
immediateInvalidates: make(chan struct{}),
|
||||||
|
frames: make(chan *op.Ops),
|
||||||
|
frameAck: make(chan struct{}),
|
||||||
|
driverFuncs: make(chan func(), 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame receives a frame and waits for its processing. It is called by
|
||||||
|
// the client goroutine.
|
||||||
|
func (e *eventLoop) Frame(frame *op.Ops) {
|
||||||
|
e.frames <- frame
|
||||||
|
<-e.frameAck
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event returns the next available event. It is called by the client
|
||||||
|
// goroutine.
|
||||||
|
func (e *eventLoop) Event() event.Event {
|
||||||
|
for {
|
||||||
|
evt := <-e.events
|
||||||
|
// Receiving a flushEvent indicates to the platform backend that
|
||||||
|
// all previous events have been processed by the user program.
|
||||||
|
if _, ok := evt.(flushEvent); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate requests invalidation of the window. It is called by the client
|
||||||
|
// goroutine.
|
||||||
|
func (e *eventLoop) Invalidate() {
|
||||||
|
select {
|
||||||
|
case e.immediateInvalidates <- struct{}{}:
|
||||||
|
// The event loop was waiting, no need for a wakeup.
|
||||||
|
case e.invalidates <- struct{}{}:
|
||||||
|
// The event loop is sleeping, wake it up.
|
||||||
|
e.wakeup()
|
||||||
|
default:
|
||||||
|
// A redraw is pending.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run f in the window loop thread. It is called by the client goroutine.
|
||||||
|
func (e *eventLoop) Run(f func()) {
|
||||||
|
e.driverFuncs <- f
|
||||||
|
e.wakeup()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlushEvents delivers pending events to the client.
|
||||||
|
func (e *eventLoop) FlushEvents() {
|
||||||
|
if e.delivering {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.delivering = true
|
||||||
|
defer func() { e.delivering = false }()
|
||||||
|
for {
|
||||||
|
evt, ok := e.win.nextEvent()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e.deliverEvent(evt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *eventLoop) deliverEvent(evt event.Event) {
|
||||||
|
var frames <-chan *op.Ops
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case f := <-e.driverFuncs:
|
||||||
|
f()
|
||||||
|
case frame := <-frames:
|
||||||
|
// The client called FrameEvent.Frame.
|
||||||
|
frames = nil
|
||||||
|
e.win.ProcessFrame(frame, e.frameAck)
|
||||||
|
case e.events <- evt:
|
||||||
|
switch evt.(type) {
|
||||||
|
case flushEvent, DestroyEvent:
|
||||||
|
// DestroyEvents are not flushed.
|
||||||
|
return
|
||||||
|
case FrameEvent:
|
||||||
|
frames = e.frames
|
||||||
|
}
|
||||||
|
evt = theFlushEvent
|
||||||
|
case <-e.invalidates:
|
||||||
|
e.win.Invalidate()
|
||||||
|
case <-e.immediateInvalidates:
|
||||||
|
e.win.Invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *eventLoop) Wakeup() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case f := <-e.driverFuncs:
|
||||||
|
f()
|
||||||
|
case <-e.invalidates:
|
||||||
|
e.win.Invalidate()
|
||||||
|
case <-e.immediateInvalidates:
|
||||||
|
e.win.Invalidate()
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func walkActions(actions system.Action, do func(system.Action)) {
|
func walkActions(actions system.Action, do func(system.Action)) {
|
||||||
for a := system.Action(1); actions != 0; a <<= 1 {
|
for a := system.Action(1); actions != 0; a <<= 1 {
|
||||||
@@ -224,3 +362,6 @@ func walkActions(actions system.Action, do func(system.Action)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wakeupEvent) ImplementsEvent() {}
|
||||||
|
func (ConfigEvent) ImplementsEvent() {}
|
||||||
|
|||||||
+239
-165
@@ -123,24 +123,29 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/cgo"
|
"runtime/cgo"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"gioui.org/io/transfer"
|
||||||
|
|
||||||
"gioui.org/internal/f32color"
|
"gioui.org/internal/f32color"
|
||||||
|
"gioui.org/op"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/clipboard"
|
"gioui.org/io/event"
|
||||||
|
"gioui.org/io/input"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/router"
|
|
||||||
"gioui.org/io/semantic"
|
"gioui.org/io/semantic"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
@@ -148,26 +153,28 @@ import (
|
|||||||
|
|
||||||
type window struct {
|
type window struct {
|
||||||
callbacks *callbacks
|
callbacks *callbacks
|
||||||
|
loop *eventLoop
|
||||||
|
|
||||||
view C.jobject
|
view C.jobject
|
||||||
handle cgo.Handle
|
handle cgo.Handle
|
||||||
|
|
||||||
dpi int
|
dpi int
|
||||||
fontScale float32
|
fontScale float32
|
||||||
insets system.Insets
|
insets pixelInsets
|
||||||
|
|
||||||
stage system.Stage
|
visible bool
|
||||||
started bool
|
started bool
|
||||||
animating bool
|
animating bool
|
||||||
|
|
||||||
win *C.ANativeWindow
|
win *C.ANativeWindow
|
||||||
config Config
|
config Config
|
||||||
|
inputHint key.InputHint
|
||||||
|
|
||||||
semantic struct {
|
semantic struct {
|
||||||
hoverID router.SemanticID
|
hoverID input.SemanticID
|
||||||
rootID router.SemanticID
|
rootID input.SemanticID
|
||||||
focusID router.SemanticID
|
focusID input.SemanticID
|
||||||
diffs []router.SemanticID
|
diffs []input.SemanticID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,9 +202,13 @@ var gioView struct {
|
|||||||
updateCaret C.jmethodID
|
updateCaret C.jmethodID
|
||||||
}
|
}
|
||||||
|
|
||||||
// ViewEvent is sent whenever the Window's underlying Android view
|
type pixelInsets struct {
|
||||||
|
top, bottom, left, right int
|
||||||
|
}
|
||||||
|
|
||||||
|
// AndroidViewEvent is sent whenever the Window's underlying Android view
|
||||||
// changes.
|
// changes.
|
||||||
type ViewEvent struct {
|
type AndroidViewEvent struct {
|
||||||
// View is a JNI global reference to the android.view.View
|
// View is a JNI global reference to the android.view.View
|
||||||
// instance backing the Window. The reference is valid until
|
// instance backing the Window. The reference is valid until
|
||||||
// the next ViewEvent is received.
|
// the next ViewEvent is received.
|
||||||
@@ -207,8 +218,6 @@ type ViewEvent struct {
|
|||||||
|
|
||||||
type jvalue uint64 // The largest JNI type fits in 64 bits.
|
type jvalue uint64 // The largest JNI type fits in 64 bits.
|
||||||
|
|
||||||
var dataDirChan = make(chan string, 1)
|
|
||||||
|
|
||||||
var android struct {
|
var android struct {
|
||||||
// mu protects all fields of this structure. However, once a
|
// mu protects all fields of this structure. However, once a
|
||||||
// non-nil jvm is returned from javaVM, all the other fields may
|
// non-nil jvm is returned from javaVM, all the other fields may
|
||||||
@@ -284,8 +293,7 @@ var mainWindow = newWindowRendezvous()
|
|||||||
var mainFuncs = make(chan func(env *C.JNIEnv), 1)
|
var mainFuncs = make(chan func(env *C.JNIEnv), 1)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dataDirOnce sync.Once
|
dataPath string
|
||||||
dataPath string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -310,7 +318,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (w *window) NewContext() (context, error) {
|
func (w *window) NewContext() (context, error) {
|
||||||
funcs := []func(w *window) (context, error){newAndroidVulkanContext, newAndroidGLESContext}
|
funcs := []func(w *window) (context, error){newAndroidGLESContext, newAndroidVulkanContext}
|
||||||
var firstErr error
|
var firstErr error
|
||||||
for _, f := range funcs {
|
for _, f := range funcs {
|
||||||
if f == nil {
|
if f == nil {
|
||||||
@@ -332,23 +340,9 @@ func (w *window) NewContext() (context, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dataDir() (string, error) {
|
func dataDir() (string, error) {
|
||||||
dataDirOnce.Do(func() {
|
if dataPath == "" {
|
||||||
dataPath = <-dataDirChan
|
panic("DataDir isn't valid before main")
|
||||||
// Set XDG_CACHE_HOME to make os.UserCacheDir work.
|
}
|
||||||
if _, exists := os.LookupEnv("XDG_CACHE_HOME"); !exists {
|
|
||||||
cachePath := filepath.Join(dataPath, "cache")
|
|
||||||
os.Setenv("XDG_CACHE_HOME", cachePath)
|
|
||||||
}
|
|
||||||
// Set XDG_CONFIG_HOME to make os.UserConfigDir work.
|
|
||||||
if _, exists := os.LookupEnv("XDG_CONFIG_HOME"); !exists {
|
|
||||||
cfgPath := filepath.Join(dataPath, "config")
|
|
||||||
os.Setenv("XDG_CONFIG_HOME", cfgPath)
|
|
||||||
}
|
|
||||||
// Set HOME to make os.UserHomeDir work.
|
|
||||||
if _, exists := os.LookupEnv("HOME"); !exists {
|
|
||||||
os.Setenv("HOME", dataPath)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return dataPath, nil
|
return dataPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,7 +379,23 @@ func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyt
|
|||||||
}
|
}
|
||||||
n := C.jni_GetArrayLength(env, jdataDir)
|
n := C.jni_GetArrayLength(env, jdataDir)
|
||||||
dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
|
dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
|
||||||
dataDirChan <- dataDir
|
|
||||||
|
// Set XDG_CACHE_HOME to make os.UserCacheDir work.
|
||||||
|
if _, exists := os.LookupEnv("XDG_CACHE_HOME"); !exists {
|
||||||
|
cachePath := filepath.Join(dataDir, "cache")
|
||||||
|
os.Setenv("XDG_CACHE_HOME", cachePath)
|
||||||
|
}
|
||||||
|
// Set XDG_CONFIG_HOME to make os.UserConfigDir work.
|
||||||
|
if _, exists := os.LookupEnv("XDG_CONFIG_HOME"); !exists {
|
||||||
|
cfgPath := filepath.Join(dataDir, "config")
|
||||||
|
os.Setenv("XDG_CONFIG_HOME", cfgPath)
|
||||||
|
}
|
||||||
|
// Set HOME to make os.UserHomeDir work.
|
||||||
|
if _, exists := os.LookupEnv("HOME"); !exists {
|
||||||
|
os.Setenv("HOME", dataDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
dataPath = dataDir
|
||||||
C.jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
|
C.jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
|
||||||
|
|
||||||
runMain()
|
runMain()
|
||||||
@@ -479,24 +489,30 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j
|
|||||||
})
|
})
|
||||||
view = C.jni_NewGlobalRef(env, view)
|
view = C.jni_NewGlobalRef(env, view)
|
||||||
wopts := <-mainWindow.out
|
wopts := <-mainWindow.out
|
||||||
|
var cnf Config
|
||||||
w, ok := windows[wopts.window]
|
w, ok := windows[wopts.window]
|
||||||
if !ok {
|
if !ok {
|
||||||
w = &window{
|
w = &window{
|
||||||
callbacks: wopts.window,
|
callbacks: wopts.window,
|
||||||
}
|
}
|
||||||
|
w.loop = newEventLoop(w.callbacks, w.wakeup)
|
||||||
|
w.callbacks.SetDriver(w)
|
||||||
|
cnf.apply(unit.Metric{}, wopts.options)
|
||||||
windows[wopts.window] = w
|
windows[wopts.window] = w
|
||||||
|
} else {
|
||||||
|
cnf = w.config
|
||||||
}
|
}
|
||||||
|
mainWindow.windows <- struct{}{}
|
||||||
if w.view != 0 {
|
if w.view != 0 {
|
||||||
w.detach(env)
|
w.detach(env)
|
||||||
}
|
}
|
||||||
w.view = view
|
w.view = view
|
||||||
|
w.visible = false
|
||||||
w.handle = cgo.NewHandle(w)
|
w.handle = cgo.NewHandle(w)
|
||||||
w.callbacks.SetDriver(w)
|
|
||||||
w.loadConfig(env, class)
|
w.loadConfig(env, class)
|
||||||
w.Configure(wopts.options)
|
w.setConfig(env, cnf)
|
||||||
w.SetInputHint(key.HintAny)
|
w.SetInputHint(w.inputHint)
|
||||||
w.setStage(system.StagePaused)
|
w.processEvent(AndroidViewEvent{View: uintptr(view)})
|
||||||
w.callbacks.Event(ViewEvent{View: uintptr(view)})
|
|
||||||
return C.jlong(w.handle)
|
return C.jlong(w.handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,7 +526,7 @@ func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle
|
|||||||
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||||
w := cgo.Handle(handle).Value().(*window)
|
w := cgo.Handle(handle).Value().(*window)
|
||||||
w.started = false
|
w.started = false
|
||||||
w.setStage(system.StagePaused)
|
w.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onStartView
|
//export Java_org_gioui_GioView_onStartView
|
||||||
@@ -526,7 +542,7 @@ func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.
|
|||||||
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||||
w := cgo.Handle(handle).Value().(*window)
|
w := cgo.Handle(handle).Value().(*window)
|
||||||
w.win = nil
|
w.win = nil
|
||||||
w.setStage(system.StagePaused)
|
w.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onSurfaceChanged
|
//export Java_org_gioui_GioView_onSurfaceChanged
|
||||||
@@ -548,9 +564,7 @@ func Java_org_gioui_GioView_onLowMemory(env *C.JNIEnv, class C.jclass) {
|
|||||||
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
|
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
w.loadConfig(env, class)
|
w.loadConfig(env, class)
|
||||||
if w.stage >= system.StageRunning {
|
w.draw(env, true)
|
||||||
w.draw(env, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onFrameCallback
|
//export Java_org_gioui_GioView_onFrameCallback
|
||||||
@@ -559,21 +573,13 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
|
|||||||
if !exist {
|
if !exist {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if w.stage < system.StageRunning {
|
w.draw(env, false)
|
||||||
return
|
|
||||||
}
|
|
||||||
if w.animating {
|
|
||||||
w.draw(env, false)
|
|
||||||
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onBack
|
//export Java_org_gioui_GioView_onBack
|
||||||
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
ev := &system.CommandEvent{Type: system.CommandBack}
|
if w.processEvent(key.Event{Name: key.NameBack}) {
|
||||||
w.callbacks.Event(ev)
|
|
||||||
if ev.Cancel {
|
|
||||||
return C.JNI_TRUE
|
return C.JNI_TRUE
|
||||||
}
|
}
|
||||||
return C.JNI_FALSE
|
return C.JNI_FALSE
|
||||||
@@ -582,21 +588,20 @@ func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong)
|
|||||||
//export Java_org_gioui_GioView_onFocusChange
|
//export Java_org_gioui_GioView_onFocusChange
|
||||||
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
|
w.config.Focused = focus == C.JNI_TRUE
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onWindowInsets
|
//export Java_org_gioui_GioView_onWindowInsets
|
||||||
func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) {
|
func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
w.insets = system.Insets{
|
w.insets = pixelInsets{
|
||||||
Top: unit.Px(float32(top)),
|
top: int(top),
|
||||||
Bottom: unit.Px(float32(bottom)),
|
bottom: int(bottom),
|
||||||
Left: unit.Px(float32(left)),
|
left: int(left),
|
||||||
Right: unit.Px(float32(right)),
|
right: int(right),
|
||||||
}
|
|
||||||
if w.stage >= system.StageRunning {
|
|
||||||
w.draw(env, true)
|
|
||||||
}
|
}
|
||||||
|
w.draw(env, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_initializeAccessibilityNodeInfo
|
//export Java_org_gioui_GioView_initializeAccessibilityNodeInfo
|
||||||
@@ -657,7 +662,44 @@ func Java_org_gioui_GioView_onClearA11yFocus(env *C.JNIEnv, class C.jclass, view
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem router.SemanticNode, off image.Point, info C.jobject) error {
|
//export Java_org_gioui_GioView_onOpenURI
|
||||||
|
func Java_org_gioui_GioView_onOpenURI(env *C.JNIEnv, class C.jclass, view C.jlong, uri C.jstring) {
|
||||||
|
evt, err := newURLEvent(goString(env, uri))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
processGlobalEvent(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
|
w.processEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) processEvent(e event.Event) bool {
|
||||||
|
if !w.callbacks.ProcessEvent(e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
w.loop.FlushEvents()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Event() event.Event {
|
||||||
|
return w.loop.Event()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
w.loop.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
w.loop.Run(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.loop.Frame(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem input.SemanticNode, off image.Point, info C.jobject) error {
|
||||||
for _, ch := range sem.Children {
|
for _, ch := range sem.Children {
|
||||||
err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
|
err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -700,7 +742,7 @@ func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem router.SemanticNod
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if d.Gestures&router.ClickGesture != 0 {
|
if d.Gestures&input.ClickGesture != 0 {
|
||||||
addAction(ACTION_CLICK)
|
addAction(ACTION_CLICK)
|
||||||
}
|
}
|
||||||
clsName := android.strings.androidViewView
|
clsName := android.strings.androidViewView
|
||||||
@@ -745,25 +787,23 @@ func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem router.SemanticNod
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) virtualIDFor(id router.SemanticID) C.jint {
|
func (w *window) virtualIDFor(id input.SemanticID) C.jint {
|
||||||
// TODO: Android virtual IDs are 32-bit Java integers, but childID is a int64.
|
|
||||||
if id == w.semantic.rootID {
|
if id == w.semantic.rootID {
|
||||||
return HOST_VIEW_ID
|
return HOST_VIEW_ID
|
||||||
}
|
}
|
||||||
return C.jint(id)
|
return C.jint(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) semIDFor(virtID C.jint) router.SemanticID {
|
func (w *window) semIDFor(virtID C.jint) input.SemanticID {
|
||||||
if virtID == HOST_VIEW_ID {
|
if virtID == HOST_VIEW_ID {
|
||||||
return w.semantic.rootID
|
return w.semantic.rootID
|
||||||
}
|
}
|
||||||
return router.SemanticID(virtID)
|
return input.SemanticID(virtID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) detach(env *C.JNIEnv) {
|
func (w *window) detach(env *C.JNIEnv) {
|
||||||
callVoidMethod(env, w.view, gioView.unregister)
|
callVoidMethod(env, w.view, gioView.unregister)
|
||||||
w.callbacks.Event(ViewEvent{})
|
w.processEvent(AndroidViewEvent{})
|
||||||
w.callbacks.SetDriver(nil)
|
|
||||||
w.handle.Delete()
|
w.handle.Delete()
|
||||||
C.jni_DeleteGlobalRef(env, w.view)
|
C.jni_DeleteGlobalRef(env, w.view)
|
||||||
w.view = 0
|
w.view = 0
|
||||||
@@ -774,18 +814,10 @@ func (w *window) setVisible(env *C.JNIEnv) {
|
|||||||
if width == 0 || height == 0 {
|
if width == 0 || height == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.setStage(system.StageRunning)
|
w.visible = true
|
||||||
w.draw(env, true)
|
w.draw(env, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setStage(stage system.Stage) {
|
|
||||||
if stage == w.stage {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.stage = stage
|
|
||||||
w.callbacks.Event(system.StageEvent{stage})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) setVisual(visID int) error {
|
func (w *window) setVisual(visID int) error {
|
||||||
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
||||||
return errors.New("ANativeWindow_setBuffersGeometry failed")
|
return errors.New("ANativeWindow_setBuffersGeometry failed")
|
||||||
@@ -822,21 +854,31 @@ func (w *window) SetAnimating(anim bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw(env *C.JNIEnv, sync bool) {
|
func (w *window) draw(env *C.JNIEnv, sync bool) {
|
||||||
|
if !w.visible {
|
||||||
|
return
|
||||||
|
}
|
||||||
size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
|
size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = size
|
w.config.Size = size
|
||||||
w.callbacks.Event(ConfigEvent{Config: w.config})
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
if size.X == 0 || size.Y == 0 {
|
if size.X == 0 || size.Y == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const inchPrDp = 1.0 / 160
|
const inchPrDp = 1.0 / 160
|
||||||
ppdp := float32(w.dpi) * inchPrDp
|
ppdp := float32(w.dpi) * inchPrDp
|
||||||
w.callbacks.Event(frameEvent{
|
dppp := unit.Dp(1.0 / ppdp)
|
||||||
FrameEvent: system.FrameEvent{
|
insets := Insets{
|
||||||
|
Top: unit.Dp(w.insets.top) * dppp,
|
||||||
|
Bottom: unit.Dp(w.insets.bottom) * dppp,
|
||||||
|
Left: unit.Dp(w.insets.left) * dppp,
|
||||||
|
Right: unit.Dp(w.insets.right) * dppp,
|
||||||
|
}
|
||||||
|
w.processEvent(frameEvent{
|
||||||
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
Insets: w.insets,
|
Insets: insets,
|
||||||
Metric: unit.Metric{
|
Metric: unit.Metric{
|
||||||
PxPerDp: ppdp,
|
PxPerDp: ppdp,
|
||||||
PxPerSp: w.fontScale * ppdp,
|
PxPerSp: w.fontScale * ppdp,
|
||||||
@@ -844,6 +886,9 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
|
|||||||
},
|
},
|
||||||
Sync: sync,
|
Sync: sync,
|
||||||
})
|
})
|
||||||
|
if w.animating {
|
||||||
|
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||||
|
}
|
||||||
a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive)
|
a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -867,8 +912,6 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyMapper func(devId, keyCode C.int32_t) rune
|
|
||||||
|
|
||||||
func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
|
func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
|
||||||
if jvm == nil {
|
if jvm == nil {
|
||||||
panic("nil JVM")
|
panic("nil JVM")
|
||||||
@@ -889,8 +932,8 @@ func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
|
|||||||
f(env)
|
f(env)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertKeyCode(code C.jint) (string, bool) {
|
func convertKeyCode(code C.jint) (key.Name, bool) {
|
||||||
var n string
|
var n key.Name
|
||||||
switch code {
|
switch code {
|
||||||
case C.AKEYCODE_FORWARD_DEL:
|
case C.AKEYCODE_FORWARD_DEL:
|
||||||
n = key.NameDeleteForward
|
n = key.NameDeleteForward
|
||||||
@@ -908,6 +951,14 @@ func convertKeyCode(code C.jint) (string, bool) {
|
|||||||
n = key.NameAlt
|
n = key.NameAlt
|
||||||
case C.AKEYCODE_META_LEFT, C.AKEYCODE_META_RIGHT:
|
case C.AKEYCODE_META_LEFT, C.AKEYCODE_META_RIGHT:
|
||||||
n = key.NameSuper
|
n = key.NameSuper
|
||||||
|
case C.AKEYCODE_DPAD_UP:
|
||||||
|
n = key.NameUpArrow
|
||||||
|
case C.AKEYCODE_DPAD_DOWN:
|
||||||
|
n = key.NameDownArrow
|
||||||
|
case C.AKEYCODE_DPAD_LEFT:
|
||||||
|
n = key.NameLeftArrow
|
||||||
|
case C.AKEYCODE_DPAD_RIGHT:
|
||||||
|
n = key.NameRightArrow
|
||||||
default:
|
default:
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
@@ -917,26 +968,16 @@ func convertKeyCode(code C.jint) (string, bool) {
|
|||||||
//export Java_org_gioui_GioView_onKeyEvent
|
//export Java_org_gioui_GioView_onKeyEvent
|
||||||
func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, pressed C.jboolean, t C.jlong) {
|
func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, pressed C.jboolean, t C.jlong) {
|
||||||
w := cgo.Handle(handle).Value().(*window)
|
w := cgo.Handle(handle).Value().(*window)
|
||||||
if pressed == C.JNI_TRUE {
|
if pressed == C.JNI_TRUE && keyCode == C.AKEYCODE_DPAD_CENTER {
|
||||||
switch keyCode {
|
w.callbacks.ClickFocus()
|
||||||
case C.AKEYCODE_DPAD_UP:
|
return
|
||||||
w.callbacks.MoveFocus(router.FocusUp)
|
|
||||||
case C.AKEYCODE_DPAD_DOWN:
|
|
||||||
w.callbacks.MoveFocus(router.FocusDown)
|
|
||||||
case C.AKEYCODE_DPAD_LEFT:
|
|
||||||
w.callbacks.MoveFocus(router.FocusLeft)
|
|
||||||
case C.AKEYCODE_DPAD_RIGHT:
|
|
||||||
w.callbacks.MoveFocus(router.FocusRight)
|
|
||||||
case C.AKEYCODE_DPAD_CENTER:
|
|
||||||
w.callbacks.ClickFocus()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if n, ok := convertKeyCode(keyCode); ok {
|
if n, ok := convertKeyCode(keyCode); ok {
|
||||||
state := key.Release
|
state := key.Release
|
||||||
if pressed == C.JNI_TRUE {
|
if pressed == C.JNI_TRUE {
|
||||||
state = key.Press
|
state = key.Press
|
||||||
}
|
}
|
||||||
w.callbacks.Event(key.Event{Name: n, State: state})
|
w.processEvent(key.Event{Name: n, State: state})
|
||||||
}
|
}
|
||||||
if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
|
if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
|
||||||
w.callbacks.EditorInsert(string(rune(r)))
|
w.callbacks.EditorInsert(string(rune(r)))
|
||||||
@@ -946,18 +987,18 @@ func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.j
|
|||||||
//export Java_org_gioui_GioView_onTouchEvent
|
//export Java_org_gioui_GioView_onTouchEvent
|
||||||
func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y, scrollX, scrollY C.jfloat, jbtns C.jint, t C.jlong) {
|
func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y, scrollX, scrollY C.jfloat, jbtns C.jint, t C.jlong) {
|
||||||
w := cgo.Handle(handle).Value().(*window)
|
w := cgo.Handle(handle).Value().(*window)
|
||||||
var typ pointer.Type
|
var kind pointer.Kind
|
||||||
switch action {
|
switch action {
|
||||||
case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
|
case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
|
||||||
typ = pointer.Press
|
kind = pointer.Press
|
||||||
case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
|
case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
|
||||||
typ = pointer.Release
|
kind = pointer.Release
|
||||||
case C.AMOTION_EVENT_ACTION_CANCEL:
|
case C.AMOTION_EVENT_ACTION_CANCEL:
|
||||||
typ = pointer.Cancel
|
kind = pointer.Cancel
|
||||||
case C.AMOTION_EVENT_ACTION_MOVE:
|
case C.AMOTION_EVENT_ACTION_MOVE:
|
||||||
typ = pointer.Move
|
kind = pointer.Move
|
||||||
case C.AMOTION_EVENT_ACTION_SCROLL:
|
case C.AMOTION_EVENT_ACTION_SCROLL:
|
||||||
typ = pointer.Scroll
|
kind = pointer.Scroll
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -986,8 +1027,8 @@ func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C
|
|||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.callbacks.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Type: typ,
|
Kind: kind,
|
||||||
Source: src,
|
Source: src,
|
||||||
Buttons: btns,
|
Buttons: btns,
|
||||||
PointerID: pointer.ID(pointerID),
|
PointerID: pointer.ID(pointerID),
|
||||||
@@ -1057,6 +1098,12 @@ func Java_org_gioui_GioView_imeSnippetStart(env *C.JNIEnv, class C.jclass, handl
|
|||||||
//export Java_org_gioui_GioView_imeSetSnippet
|
//export Java_org_gioui_GioView_imeSetSnippet
|
||||||
func Java_org_gioui_GioView_imeSetSnippet(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) {
|
func Java_org_gioui_GioView_imeSetSnippet(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) {
|
||||||
w := cgo.Handle(handle).Value().(*window)
|
w := cgo.Handle(handle).Value().(*window)
|
||||||
|
if start < 0 {
|
||||||
|
start = 0
|
||||||
|
}
|
||||||
|
if end < start {
|
||||||
|
end = start
|
||||||
|
}
|
||||||
r := key.Range{Start: int(start), End: int(end)}
|
r := key.Range{Start: int(start), End: int(end)}
|
||||||
w.callbacks.SetEditorSnippet(r)
|
w.callbacks.SetEditorSnippet(r)
|
||||||
}
|
}
|
||||||
@@ -1132,22 +1179,41 @@ func (w *window) ShowTextInput(show bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) SetInputHint(mode key.InputHint) {
|
func (w *window) SetInputHint(mode key.InputHint) {
|
||||||
|
w.inputHint = mode
|
||||||
|
|
||||||
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
||||||
const (
|
const (
|
||||||
TYPE_NULL = 0
|
TYPE_NULL = 0
|
||||||
TYPE_CLASS_TEXT = 1
|
|
||||||
TYPE_CLASS_NUMBER = 2
|
TYPE_CLASS_TEXT = 1
|
||||||
TYPE_NUMBER_FLAG_DECIMAL = 8192
|
TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 32
|
||||||
TYPE_NUMBER_FLAG_SIGNED = 4096
|
TYPE_TEXT_VARIATION_URI = 16
|
||||||
TYPE_TEXT_FLAG_NO_SUGGESTIONS = 524288
|
TYPE_TEXT_VARIATION_PASSWORD = 128
|
||||||
TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 144
|
TYPE_TEXT_FLAG_CAP_SENTENCES = 16384
|
||||||
|
TYPE_TEXT_FLAG_AUTO_CORRECT = 32768
|
||||||
|
|
||||||
|
TYPE_CLASS_NUMBER = 2
|
||||||
|
TYPE_NUMBER_FLAG_DECIMAL = 8192
|
||||||
|
TYPE_NUMBER_FLAG_SIGNED = 4096
|
||||||
|
|
||||||
|
TYPE_CLASS_PHONE = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||||
var m jvalue
|
var m jvalue
|
||||||
switch mode {
|
switch mode {
|
||||||
|
case key.HintText:
|
||||||
|
m = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_AUTO_CORRECT | TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||||
case key.HintNumeric:
|
case key.HintNumeric:
|
||||||
m = TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_DECIMAL | TYPE_NUMBER_FLAG_SIGNED
|
m = TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_DECIMAL | TYPE_NUMBER_FLAG_SIGNED
|
||||||
|
case key.HintEmail:
|
||||||
|
m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
||||||
|
case key.HintURL:
|
||||||
|
m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_URI
|
||||||
|
case key.HintTelephone:
|
||||||
|
m = TYPE_CLASS_PHONE
|
||||||
|
case key.HintPassword:
|
||||||
|
m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD
|
||||||
default:
|
default:
|
||||||
m = TYPE_CLASS_TEXT
|
m = TYPE_CLASS_TEXT
|
||||||
}
|
}
|
||||||
@@ -1259,16 +1325,17 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func osMain() {
|
func osMain() {
|
||||||
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) error {
|
func newWindow(window *callbacks, options []Option) {
|
||||||
mainWindow.in <- windowAndConfig{window, options}
|
mainWindow.in <- windowAndConfig{window, options}
|
||||||
return <-mainWindow.errs
|
<-mainWindow.windows
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) WriteClipboard(s string) {
|
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||||
jstr := javaString(env, s)
|
jstr := javaString(env, string(s))
|
||||||
callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
|
callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
|
||||||
jvalue(android.appCtx), jvalue(jstr))
|
jvalue(android.appCtx), jvalue(jstr))
|
||||||
})
|
})
|
||||||
@@ -1282,52 +1349,57 @@ func (w *window) ReadClipboard() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
content := goString(env, C.jstring(c))
|
content := goString(env, C.jstring(c))
|
||||||
w.callbacks.Event(clipboard.Event{Text: content})
|
w.processEvent(transfer.DataEvent{
|
||||||
|
Type: "application/text",
|
||||||
|
Open: func() io.ReadCloser {
|
||||||
|
return io.NopCloser(strings.NewReader(content))
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
|
cnf := w.config
|
||||||
|
cnf.apply(unit.Metric{}, options)
|
||||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||||
prev := w.config
|
w.setConfig(env, cnf)
|
||||||
cnf := w.config
|
|
||||||
cnf.apply(unit.Metric{}, options)
|
|
||||||
// Decorations are never disabled.
|
|
||||||
cnf.Decorated = true
|
|
||||||
|
|
||||||
if prev.Orientation != cnf.Orientation {
|
|
||||||
w.config.Orientation = cnf.Orientation
|
|
||||||
setOrientation(env, w.view, cnf.Orientation)
|
|
||||||
}
|
|
||||||
if prev.NavigationColor != cnf.NavigationColor {
|
|
||||||
w.config.NavigationColor = cnf.NavigationColor
|
|
||||||
setNavigationColor(env, w.view, cnf.NavigationColor)
|
|
||||||
}
|
|
||||||
if prev.StatusColor != cnf.StatusColor {
|
|
||||||
w.config.StatusColor = cnf.StatusColor
|
|
||||||
setStatusColor(env, w.view, cnf.StatusColor)
|
|
||||||
}
|
|
||||||
if prev.Mode != cnf.Mode {
|
|
||||||
switch cnf.Mode {
|
|
||||||
case Fullscreen:
|
|
||||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
|
|
||||||
w.config.Mode = Fullscreen
|
|
||||||
case Windowed:
|
|
||||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
|
|
||||||
w.config.Mode = Windowed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cnf.Decorated != prev.Decorated {
|
|
||||||
w.config.Decorated = cnf.Decorated
|
|
||||||
}
|
|
||||||
if w.config != prev {
|
|
||||||
w.callbacks.Event(ConfigEvent{Config: w.config})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Perform(system.Action) {}
|
func (w *window) setConfig(env *C.JNIEnv, cnf Config) {
|
||||||
|
prev := w.config
|
||||||
|
// Decorations are never disabled.
|
||||||
|
cnf.Decorated = true
|
||||||
|
|
||||||
func (w *window) Raise() {}
|
if prev.Orientation != cnf.Orientation {
|
||||||
|
w.config.Orientation = cnf.Orientation
|
||||||
|
setOrientation(env, w.view, cnf.Orientation)
|
||||||
|
}
|
||||||
|
if prev.NavigationColor != cnf.NavigationColor {
|
||||||
|
w.config.NavigationColor = cnf.NavigationColor
|
||||||
|
setNavigationColor(env, w.view, cnf.NavigationColor)
|
||||||
|
}
|
||||||
|
if prev.StatusColor != cnf.StatusColor {
|
||||||
|
w.config.StatusColor = cnf.StatusColor
|
||||||
|
setStatusColor(env, w.view, cnf.StatusColor)
|
||||||
|
}
|
||||||
|
if prev.Mode != cnf.Mode {
|
||||||
|
switch cnf.Mode {
|
||||||
|
case Fullscreen:
|
||||||
|
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
|
||||||
|
w.config.Mode = Fullscreen
|
||||||
|
case Windowed:
|
||||||
|
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
|
||||||
|
w.config.Mode = Windowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cnf.Decorated != prev.Decorated {
|
||||||
|
w.config.Decorated = cnf.Decorated
|
||||||
|
}
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Perform(system.Action) {}
|
||||||
|
|
||||||
func (w *window) SetCursor(cursor pointer.Cursor) {
|
func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||||
@@ -1335,9 +1407,10 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
func (w *window) wakeup() {
|
||||||
runOnMain(func(env *C.JNIEnv) {
|
runOnMain(func(env *C.JNIEnv) {
|
||||||
w.callbacks.Event(wakeupEvent{})
|
w.loop.Wakeup()
|
||||||
|
w.loop.FlushEvents()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1406,9 +1479,6 @@ func setNavigationColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the window. Not implemented for Android.
|
|
||||||
func (w *window) Close() {}
|
|
||||||
|
|
||||||
// runOnMain runs a function on the Java main thread.
|
// runOnMain runs a function on the Java main thread.
|
||||||
func runOnMain(f func(env *C.JNIEnv)) {
|
func runOnMain(f func(env *C.JNIEnv)) {
|
||||||
go func() {
|
go func() {
|
||||||
@@ -1431,4 +1501,8 @@ func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ ViewEvent) ImplementsEvent() {}
|
func (AndroidViewEvent) implementsViewEvent() {}
|
||||||
|
func (AndroidViewEvent) ImplementsEvent() {}
|
||||||
|
func (a AndroidViewEvent) Valid() bool {
|
||||||
|
return a != (AndroidViewEvent{})
|
||||||
|
}
|
||||||
|
|||||||
+35
-30
@@ -5,8 +5,8 @@ package app
|
|||||||
/*
|
/*
|
||||||
#include <Foundation/Foundation.h>
|
#include <Foundation/Foundation.h>
|
||||||
|
|
||||||
__attribute__ ((visibility ("hidden"))) void gio_wakeupMainThread(void);
|
__attribute__ ((visibility ("hidden"))) void gio_runOnMain(uintptr_t h);
|
||||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(uintptr_t handle);
|
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void);
|
||||||
__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
|
__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
|
||||||
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
|
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
|
||||||
__attribute__ ((visibility ("hidden"))) int gio_stopDisplayLink(CFTypeRef dl);
|
__attribute__ ((visibility ("hidden"))) int gio_stopDisplayLink(CFTypeRef dl);
|
||||||
@@ -40,9 +40,11 @@ static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"runtime/cgo"
|
"runtime/cgo"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
@@ -70,30 +72,28 @@ type displayLink struct {
|
|||||||
running uint32
|
running uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
var mainFuncs = make(chan func(), 1)
|
// displayLinks maps CFTypeRefs to *displayLinks.
|
||||||
|
var displayLinks sync.Map
|
||||||
|
|
||||||
|
func isMainThread() bool {
|
||||||
|
return bool(C.isMainThread())
|
||||||
|
}
|
||||||
|
|
||||||
// runOnMain runs the function on the main thread.
|
// runOnMain runs the function on the main thread.
|
||||||
func runOnMain(f func()) {
|
func runOnMain(f func()) {
|
||||||
if C.isMainThread() {
|
if isMainThread() {
|
||||||
f()
|
f()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go func() {
|
C.gio_runOnMain(C.uintptr_t(cgo.NewHandle(f)))
|
||||||
mainFuncs <- f
|
|
||||||
C.gio_wakeupMainThread()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_dispatchMainFuncs
|
//export gio_runFunc
|
||||||
func gio_dispatchMainFuncs() {
|
func gio_runFunc(h C.uintptr_t) {
|
||||||
for {
|
handle := cgo.Handle(h)
|
||||||
select {
|
defer handle.Delete()
|
||||||
case f := <-mainFuncs:
|
f := handle.Value().(func())
|
||||||
f()
|
f()
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nsstringToString converts a NSString to a Go string.
|
// nsstringToString converts a NSString to a Go string.
|
||||||
@@ -121,25 +121,25 @@ func stringToNSString(str string) C.CFTypeRef {
|
|||||||
return C.newNSString(chars, C.NSUInteger(len(u16)))
|
return C.newNSString(chars, C.NSUInteger(len(u16)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDisplayLink(callback func()) (*displayLink, error) {
|
func newDisplayLink(callback func()) (*displayLink, error) {
|
||||||
d := &displayLink{
|
d := &displayLink{
|
||||||
callback: callback,
|
callback: callback,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
states: make(chan bool),
|
states: make(chan bool),
|
||||||
dids: make(chan uint64),
|
dids: make(chan uint64),
|
||||||
}
|
}
|
||||||
h := cgo.NewHandle(d)
|
dl := C.gio_createDisplayLink()
|
||||||
dl := C.gio_createDisplayLink(C.uintptr_t(h))
|
|
||||||
if dl == 0 {
|
if dl == 0 {
|
||||||
return nil, errors.New("app: failed to create display link")
|
return nil, errors.New("app: failed to create display link")
|
||||||
}
|
}
|
||||||
go d.run(dl, h)
|
go d.run(dl)
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *displayLink) run(dl C.CFTypeRef, h cgo.Handle) {
|
func (d *displayLink) run(dl C.CFTypeRef) {
|
||||||
defer C.gio_releaseDisplayLink(dl)
|
defer C.gio_releaseDisplayLink(dl)
|
||||||
defer h.Delete()
|
displayLinks.Store(dl, d)
|
||||||
|
defer displayLinks.Delete(dl)
|
||||||
var stopTimer *time.Timer
|
var stopTimer *time.Timer
|
||||||
var tchan <-chan time.Time
|
var tchan <-chan time.Time
|
||||||
started := false
|
started := false
|
||||||
@@ -200,10 +200,14 @@ func (d *displayLink) SetDisplayID(did uint64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onFrameCallback
|
//export gio_onFrameCallback
|
||||||
func gio_onFrameCallback(dl C.CFTypeRef, handle C.uintptr_t) {
|
func gio_onFrameCallback(ref C.CFTypeRef) {
|
||||||
d := cgo.Handle(handle).Value().(*displayLink)
|
d, exists := displayLinks.Load(ref)
|
||||||
if atomic.LoadUint32(&d.running) != 0 {
|
if !exists {
|
||||||
d.callback()
|
return
|
||||||
|
}
|
||||||
|
dl := d.(*displayLink)
|
||||||
|
if atomic.LoadUint32(&dl.running) != 0 {
|
||||||
|
dl.callback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,8 +257,9 @@ func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
|
|||||||
return to
|
return to
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
func (w *window) wakeup() {
|
||||||
runOnMain(func() {
|
runOnMain(func() {
|
||||||
w.w.Event(wakeupEvent{})
|
w.loop.Wakeup()
|
||||||
|
w.loop.FlushEvents()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-4
@@ -1,12 +1,11 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
@import Dispatch;
|
#import <Foundation/Foundation.h>
|
||||||
@import Foundation;
|
|
||||||
|
|
||||||
#include "_cgo_export.h"
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
void gio_wakeupMainThread(void) {
|
void gio_runOnMain(uintptr_t h) {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
gio_dispatchMainFuncs();
|
gio_runFunc(h);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+181
-90
@@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
//go:build darwin && ios
|
//go:build darwin && ios
|
||||||
// +build darwin,ios
|
|
||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
@@ -12,6 +11,9 @@ package app
|
|||||||
#include <UIKit/UIKit.h>
|
#include <UIKit/UIKit.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
__attribute__ ((visibility ("hidden"))) int gio_applicationMain(int argc, char *argv[]);
|
||||||
|
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||||
|
|
||||||
struct drawParams {
|
struct drawParams {
|
||||||
CGFloat dpi, sdpi;
|
CGFloat dpi, sdpi;
|
||||||
CGFloat width, height;
|
CGFloat width, height;
|
||||||
@@ -19,6 +21,7 @@ struct drawParams {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static void writeClipboard(unichar *chars, NSUInteger length) {
|
static void writeClipboard(unichar *chars, NSUInteger length) {
|
||||||
|
#if !TARGET_OS_TV
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSString *s = [NSString string];
|
NSString *s = [NSString string];
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
@@ -27,13 +30,18 @@ static void writeClipboard(unichar *chars, NSUInteger length) {
|
|||||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||||
p.string = s;
|
p.string = s;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static CFTypeRef readClipboard(void) {
|
static CFTypeRef readClipboard(void) {
|
||||||
|
#if !TARGET_OS_TV
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||||
return (__bridge_retained CFTypeRef)p.string;
|
return (__bridge_retained CFTypeRef)p.string;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
return nil;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void showTextInput(CFTypeRef viewRef) {
|
static void showTextInput(CFTypeRef viewRef) {
|
||||||
@@ -72,21 +80,27 @@ import "C"
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/cgo"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/clipboard"
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
|
"gioui.org/io/transfer"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ViewEvent struct {
|
type UIKitViewEvent struct {
|
||||||
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
||||||
ViewController uintptr
|
ViewController uintptr
|
||||||
}
|
}
|
||||||
@@ -95,18 +109,17 @@ type window struct {
|
|||||||
view C.CFTypeRef
|
view C.CFTypeRef
|
||||||
w *callbacks
|
w *callbacks
|
||||||
displayLink *displayLink
|
displayLink *displayLink
|
||||||
|
loop *eventLoop
|
||||||
|
|
||||||
visible bool
|
hidden bool
|
||||||
cursor pointer.Cursor
|
cursor pointer.Cursor
|
||||||
config Config
|
config Config
|
||||||
|
|
||||||
pointerMap []C.CFTypeRef
|
pointerMap []C.CFTypeRef
|
||||||
}
|
}
|
||||||
|
|
||||||
var mainWindow = newWindowRendezvous()
|
var mainWindow = newWindowRendezvous()
|
||||||
|
|
||||||
var views = make(map[C.CFTypeRef]*window)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Darwin requires UI operations happen on the main thread only.
|
// Darwin requires UI operations happen on the main thread only.
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
@@ -114,85 +127,99 @@ func init() {
|
|||||||
|
|
||||||
//export onCreate
|
//export onCreate
|
||||||
func onCreate(view, controller C.CFTypeRef) {
|
func onCreate(view, controller C.CFTypeRef) {
|
||||||
|
wopts := <-mainWindow.out
|
||||||
w := &window{
|
w := &window{
|
||||||
view: view,
|
view: view,
|
||||||
|
w: wopts.window,
|
||||||
}
|
}
|
||||||
dl, err := NewDisplayLink(func() {
|
w.loop = newEventLoop(w.w, w.wakeup)
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
mainWindow.windows <- struct{}{}
|
||||||
|
dl, err := newDisplayLink(func() {
|
||||||
w.draw(false)
|
w.draw(false)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
w.w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
w.displayLink = dl
|
w.displayLink = dl
|
||||||
wopts := <-mainWindow.out
|
C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
|
||||||
w.w = wopts.window
|
|
||||||
w.w.SetDriver(w)
|
|
||||||
views[view] = w
|
|
||||||
w.Configure(wopts.options)
|
w.Configure(wopts.options)
|
||||||
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)})
|
||||||
w.w.Event(ViewEvent{ViewController: uintptr(controller)})
|
}
|
||||||
|
|
||||||
|
func viewFor(h C.uintptr_t) *window {
|
||||||
|
return cgo.Handle(h).Value().(*window)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onDraw
|
//export gio_onDraw
|
||||||
func gio_onDraw(view C.CFTypeRef) {
|
func gio_onDraw(h C.uintptr_t) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
w.draw(true)
|
w.draw(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw(sync bool) {
|
func (w *window) draw(sync bool) {
|
||||||
|
if w.hidden {
|
||||||
|
return
|
||||||
|
}
|
||||||
params := C.viewDrawParams(w.view)
|
params := C.viewDrawParams(w.view)
|
||||||
if params.width == 0 || params.height == 0 {
|
if params.width == 0 || params.height == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wasVisible := w.visible
|
|
||||||
w.visible = true
|
|
||||||
if !wasVisible {
|
|
||||||
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
|
||||||
}
|
|
||||||
const inchPrDp = 1.0 / 163
|
const inchPrDp = 1.0 / 163
|
||||||
w.w.Event(frameEvent{
|
m := unit.Metric{
|
||||||
FrameEvent: system.FrameEvent{
|
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||||
|
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||||
|
}
|
||||||
|
dppp := unit.Dp(1. / m.PxPerDp)
|
||||||
|
w.ProcessEvent(frameEvent{
|
||||||
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: image.Point{
|
Size: image.Point{
|
||||||
X: int(params.width + .5),
|
X: int(params.width + .5),
|
||||||
Y: int(params.height + .5),
|
Y: int(params.height + .5),
|
||||||
},
|
},
|
||||||
Insets: system.Insets{
|
Insets: Insets{
|
||||||
Top: unit.Px(float32(params.top)),
|
Top: unit.Dp(params.top) * dppp,
|
||||||
Bottom: unit.Px(float32(params.bottom)),
|
Bottom: unit.Dp(params.bottom) * dppp,
|
||||||
Left: unit.Px(float32(params.left)),
|
Left: unit.Dp(params.left) * dppp,
|
||||||
Right: unit.Px(float32(params.right)),
|
Right: unit.Dp(params.right) * dppp,
|
||||||
},
|
|
||||||
Metric: unit.Metric{
|
|
||||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
|
||||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
|
||||||
},
|
},
|
||||||
|
Metric: m,
|
||||||
},
|
},
|
||||||
Sync: sync,
|
Sync: sync,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onStop
|
//export onStop
|
||||||
func onStop(view C.CFTypeRef) {
|
func onStop(h C.uintptr_t) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
w.visible = false
|
w.hidden = true
|
||||||
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
}
|
||||||
|
|
||||||
|
//export onStart
|
||||||
|
func onStart(h C.uintptr_t) {
|
||||||
|
w := viewFor(h)
|
||||||
|
w.hidden = false
|
||||||
|
w.draw(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onDestroy
|
//export onDestroy
|
||||||
func onDestroy(view C.CFTypeRef) {
|
func onDestroy(h C.uintptr_t) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
delete(views, view)
|
w.ProcessEvent(UIKitViewEvent{})
|
||||||
w.w.Event(ViewEvent{})
|
w.ProcessEvent(DestroyEvent{})
|
||||||
w.w.Event(system.DestroyEvent{})
|
|
||||||
w.displayLink.Close()
|
w.displayLink.Close()
|
||||||
|
w.displayLink = nil
|
||||||
|
cgo.Handle(h).Delete()
|
||||||
w.view = 0
|
w.view = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onFocus
|
//export onFocus
|
||||||
func onFocus(view C.CFTypeRef, focus int) {
|
func onFocus(h C.uintptr_t, focus int) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
w.w.Event(key.FocusEvent{Focus: focus != 0})
|
w.config.Focused = focus != 0
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onLowMemory
|
//export onLowMemory
|
||||||
@@ -202,56 +229,56 @@ func onLowMemory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export onUpArrow
|
//export onUpArrow
|
||||||
func onUpArrow(view C.CFTypeRef) {
|
func onUpArrow(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameUpArrow)
|
viewFor(h).onKeyCommand(key.NameUpArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onDownArrow
|
//export onDownArrow
|
||||||
func onDownArrow(view C.CFTypeRef) {
|
func onDownArrow(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameDownArrow)
|
viewFor(h).onKeyCommand(key.NameDownArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onLeftArrow
|
//export onLeftArrow
|
||||||
func onLeftArrow(view C.CFTypeRef) {
|
func onLeftArrow(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameLeftArrow)
|
viewFor(h).onKeyCommand(key.NameLeftArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onRightArrow
|
//export onRightArrow
|
||||||
func onRightArrow(view C.CFTypeRef) {
|
func onRightArrow(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameRightArrow)
|
viewFor(h).onKeyCommand(key.NameRightArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onDeleteBackward
|
//export onDeleteBackward
|
||||||
func onDeleteBackward(view C.CFTypeRef) {
|
func onDeleteBackward(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameDeleteBackward)
|
viewFor(h).onKeyCommand(key.NameDeleteBackward)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onText
|
//export onText
|
||||||
func onText(view, str C.CFTypeRef) {
|
func onText(h C.uintptr_t, str C.CFTypeRef) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
w.w.EditorInsert(nsstringToString(str))
|
w.w.EditorInsert(nsstringToString(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onTouch
|
//export onTouch
|
||||||
func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
||||||
var typ pointer.Type
|
var kind pointer.Kind
|
||||||
switch phase {
|
switch phase {
|
||||||
case C.UITouchPhaseBegan:
|
case C.UITouchPhaseBegan:
|
||||||
typ = pointer.Press
|
kind = pointer.Press
|
||||||
case C.UITouchPhaseMoved:
|
case C.UITouchPhaseMoved:
|
||||||
typ = pointer.Move
|
kind = pointer.Move
|
||||||
case C.UITouchPhaseEnded:
|
case C.UITouchPhaseEnded:
|
||||||
typ = pointer.Release
|
kind = pointer.Release
|
||||||
case C.UITouchPhaseCancelled:
|
case C.UITouchPhaseCancelled:
|
||||||
typ = pointer.Cancel
|
kind = pointer.Cancel
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
t := time.Duration(float64(ti) * float64(time.Second))
|
t := time.Duration(float64(ti) * float64(time.Second))
|
||||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Type: typ,
|
Kind: kind,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||||
Position: p,
|
Position: p,
|
||||||
@@ -263,11 +290,16 @@ func (w *window) ReadClipboard() {
|
|||||||
cstr := C.readClipboard()
|
cstr := C.readClipboard()
|
||||||
defer C.CFRelease(cstr)
|
defer C.CFRelease(cstr)
|
||||||
content := nsstringToString(cstr)
|
content := nsstringToString(cstr)
|
||||||
w.w.Event(clipboard.Event{Text: content})
|
w.ProcessEvent(transfer.DataEvent{
|
||||||
|
Type: "application/text",
|
||||||
|
Open: func() io.ReadCloser {
|
||||||
|
return io.NopCloser(strings.NewReader(content))
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) WriteClipboard(s string) {
|
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||||
u16 := utf16.Encode([]rune(s))
|
u16 := utf16.Encode([]rune(string(s)))
|
||||||
var chars *C.unichar
|
var chars *C.unichar
|
||||||
if len(u16) > 0 {
|
if len(u16) > 0 {
|
||||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||||
@@ -276,25 +308,16 @@ func (w *window) WriteClipboard(s string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Configure([]Option) {
|
func (w *window) Configure([]Option) {
|
||||||
prev := w.config
|
|
||||||
// Decorations are never disabled.
|
// Decorations are never disabled.
|
||||||
w.config.Decorated = true
|
w.config.Decorated = true
|
||||||
if w.config != prev {
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||||
|
|
||||||
func (w *window) Perform(system.Action) {}
|
func (w *window) Perform(system.Action) {}
|
||||||
|
|
||||||
func (w *window) Raise() {}
|
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
v := w.view
|
|
||||||
if v == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if anim {
|
if anim {
|
||||||
w.displayLink.Start()
|
w.displayLink.Start()
|
||||||
} else {
|
} else {
|
||||||
@@ -306,8 +329,8 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
w.cursor = windowSetCursor(w.cursor, cursor)
|
w.cursor = windowSetCursor(w.cursor, cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) onKeyCommand(name string) {
|
func (w *window) onKeyCommand(name key.Name) {
|
||||||
w.w.Event(key.Event{
|
w.ProcessEvent(key.Event{
|
||||||
Name: name,
|
Name: name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -346,20 +369,88 @@ func (w *window) ShowTextInput(show bool) {
|
|||||||
|
|
||||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||||
|
|
||||||
// Close the window. Not implemented for iOS.
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
func (w *window) Close() {}
|
w.w.ProcessEvent(e)
|
||||||
|
w.loop.FlushEvents()
|
||||||
func newWindow(win *callbacks, options []Option) error {
|
|
||||||
mainWindow.in <- windowAndConfig{win, options}
|
|
||||||
return <-mainWindow.errs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) Event() event.Event {
|
||||||
|
return w.loop.Event()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
w.loop.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
w.loop.Run(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.loop.Frame(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWindow(win *callbacks, options []Option) {
|
||||||
|
mainWindow.in <- windowAndConfig{win, options}
|
||||||
|
<-mainWindow.windows
|
||||||
|
}
|
||||||
|
|
||||||
|
var mainMode = mainModeUndefined
|
||||||
|
|
||||||
|
const (
|
||||||
|
mainModeUndefined = iota
|
||||||
|
mainModeExe
|
||||||
|
mainModeLibrary
|
||||||
|
)
|
||||||
|
|
||||||
func osMain() {
|
func osMain() {
|
||||||
|
switch mainMode {
|
||||||
|
case mainModeUndefined:
|
||||||
|
if !isMainThread() {
|
||||||
|
panic("app.Main must be run on the main goroutine")
|
||||||
|
}
|
||||||
|
|
||||||
|
mainMode = mainModeExe
|
||||||
|
var argv []*C.char
|
||||||
|
for _, arg := range os.Args {
|
||||||
|
a := C.CString(arg)
|
||||||
|
defer C.free(unsafe.Pointer(a))
|
||||||
|
argv = append(argv, a)
|
||||||
|
}
|
||||||
|
C.gio_applicationMain(C.int(len(argv)), unsafe.SliceData(argv))
|
||||||
|
case mainModeExe:
|
||||||
|
panic("app.Main may be called only once")
|
||||||
|
case mainModeLibrary:
|
||||||
|
// Do nothing, we're embedded as a library.
|
||||||
|
}
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export gio_onOpenURI
|
||||||
|
func gio_onOpenURI(uri C.CFTypeRef) {
|
||||||
|
evt, err := newURLEvent(nsstringToString(uri))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
processGlobalEvent(evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_runMain
|
//export gio_runMain
|
||||||
func gio_runMain() {
|
func gio_runMain() {
|
||||||
runMain()
|
if !isMainThread() {
|
||||||
|
panic("app.Main must be run on the main goroutine")
|
||||||
|
}
|
||||||
|
switch mainMode {
|
||||||
|
case mainModeUndefined:
|
||||||
|
mainMode = mainModeLibrary
|
||||||
|
runMain()
|
||||||
|
case mainModeExe:
|
||||||
|
// Do nothing, main has already been called.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ ViewEvent) ImplementsEvent() {}
|
func (UIKitViewEvent) implementsViewEvent() {}
|
||||||
|
func (UIKitViewEvent) ImplementsEvent() {}
|
||||||
|
func (u UIKitViewEvent) Valid() bool {
|
||||||
|
return u != (UIKitViewEvent{})
|
||||||
|
}
|
||||||
|
|||||||
+107
-50
@@ -11,6 +11,7 @@
|
|||||||
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
|
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
|
||||||
|
|
||||||
@interface GioView: UIView <UIKeyInput>
|
@interface GioView: UIView <UIKeyInput>
|
||||||
|
@property uintptr_t handle;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GioViewController
|
@implementation GioViewController
|
||||||
@@ -25,12 +26,13 @@ CGFloat _keyboardHeight;
|
|||||||
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||||
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
|
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
|
||||||
[self.view addSubview: drawView];
|
[self.view addSubview: drawView];
|
||||||
#ifndef TARGET_OS_TV
|
#if !TARGET_OS_TV
|
||||||
drawView.multipleTouchEnabled = YES;
|
drawView.multipleTouchEnabled = YES;
|
||||||
#endif
|
#endif
|
||||||
drawView.preservesSuperviewLayoutMargins = YES;
|
drawView.preservesSuperviewLayoutMargins = YES;
|
||||||
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||||
onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
|
onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
|
||||||
|
#if !TARGET_OS_TV
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
selector:@selector(keyboardWillChange:)
|
selector:@selector(keyboardWillChange:)
|
||||||
name:UIKeyboardWillShowNotification
|
name:UIKeyboardWillShowNotification
|
||||||
@@ -43,6 +45,7 @@ CGFloat _keyboardHeight;
|
|||||||
selector:@selector(keyboardWillHide:)
|
selector:@selector(keyboardWillHide:)
|
||||||
name:UIKeyboardWillHideNotification
|
name:UIKeyboardWillHideNotification
|
||||||
object:nil];
|
object:nil];
|
||||||
|
#endif
|
||||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||||
selector: @selector(applicationDidEnterBackground:)
|
selector: @selector(applicationDidEnterBackground:)
|
||||||
name: UIApplicationDidEnterBackgroundNotification
|
name: UIApplicationDidEnterBackgroundNotification
|
||||||
@@ -54,33 +57,33 @@ CGFloat _keyboardHeight;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||||
UIView *drawView = self.view.subviews[0];
|
GioView *view = (GioView *)self.view.subviews[0];
|
||||||
if (drawView != nil) {
|
if (view != nil) {
|
||||||
gio_onDraw((__bridge CFTypeRef)drawView);
|
onStart(view.handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||||
UIView *drawView = self.view.subviews[0];
|
GioView *view = (GioView *)self.view.subviews[0];
|
||||||
if (drawView != nil) {
|
if (view != nil) {
|
||||||
onStop((__bridge CFTypeRef)drawView);
|
onStop(view.handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidDisappear:(BOOL)animated {
|
- (void)viewDidDisappear:(BOOL)animated {
|
||||||
[super viewDidDisappear:animated];
|
[super viewDidDisappear:animated];
|
||||||
CFTypeRef viewRef = (__bridge CFTypeRef)self.view.subviews[0];
|
GioView *view = (GioView *)self.view.subviews[0];
|
||||||
onDestroy(viewRef);
|
onDestroy(view.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidLayoutSubviews {
|
- (void)viewDidLayoutSubviews {
|
||||||
[super viewDidLayoutSubviews];
|
[super viewDidLayoutSubviews];
|
||||||
UIView *view = self.view.subviews[0];
|
GioView *view = (GioView *)self.view.subviews[0];
|
||||||
CGRect frame = self.view.bounds;
|
CGRect frame = self.view.bounds;
|
||||||
// Adjust view bounds to make room for the keyboard.
|
// Adjust view bounds to make room for the keyboard.
|
||||||
frame.size.height -= _keyboardHeight;
|
frame.size.height -= _keyboardHeight;
|
||||||
view.frame = frame;
|
view.frame = frame;
|
||||||
gio_onDraw((__bridge CFTypeRef)view);
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didReceiveMemoryWarning {
|
- (void)didReceiveMemoryWarning {
|
||||||
@@ -88,6 +91,7 @@ CGFloat _keyboardHeight;
|
|||||||
[super didReceiveMemoryWarning];
|
[super didReceiveMemoryWarning];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !TARGET_OS_TV
|
||||||
- (void)keyboardWillChange:(NSNotification *)note {
|
- (void)keyboardWillChange:(NSNotification *)note {
|
||||||
NSDictionary *userInfo = note.userInfo;
|
NSDictionary *userInfo = note.userInfo;
|
||||||
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||||
@@ -99,13 +103,13 @@ CGFloat _keyboardHeight;
|
|||||||
_keyboardHeight = 0.0;
|
_keyboardHeight = 0.0;
|
||||||
[self.view setNeedsLayout];
|
[self.view setNeedsLayout];
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
@end
|
@end
|
||||||
|
|
||||||
static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||||
CGFloat scale = view.contentScaleFactor;
|
CGFloat scale = view.contentScaleFactor;
|
||||||
NSUInteger i = 0;
|
NSUInteger i = 0;
|
||||||
NSUInteger n = [touches count];
|
NSUInteger n = [touches count];
|
||||||
CFTypeRef viewRef = (__bridge CFTypeRef)view;
|
|
||||||
for (UITouch *touch in touches) {
|
for (UITouch *touch in touches) {
|
||||||
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
||||||
i++;
|
i++;
|
||||||
@@ -116,45 +120,84 @@ static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIE
|
|||||||
CGPoint loc = [coalescedTouch locationInView:view];
|
CGPoint loc = [coalescedTouch locationInView:view];
|
||||||
j++;
|
j++;
|
||||||
int lastTouch = last && i == n && j == m;
|
int lastTouch = last && i == n && j == m;
|
||||||
onTouch(lastTouch, viewRef, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
onTouch(view.handle, lastTouch, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@implementation GioView
|
@implementation GioView
|
||||||
NSArray<UIKeyCommand *> *_keyCommands;
|
NSArray<UIKeyCommand *> *_keyCommands;
|
||||||
|
+ (void)onFrameCallback:(CADisplayLink *)link {
|
||||||
|
gio_onFrameCallback((__bridge CFTypeRef)link);
|
||||||
|
}
|
||||||
+ (Class)layerClass {
|
+ (Class)layerClass {
|
||||||
return gio_layerClass();
|
return gio_layerClass();
|
||||||
}
|
}
|
||||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||||
|
self.contentScaleFactor = newWindow.screen.nativeScale;
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
[self registerSceneNotifications:newWindow];
|
||||||
|
}else{
|
||||||
|
[self registerWindowNotifications:newWindow];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)registerSceneNotifications:(UIWindow *)newWindow {
|
||||||
|
if (self.window != nil) {
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||||
|
name:UISceneDidActivateNotification
|
||||||
|
object:self.window.windowScene];
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||||
|
name:UISceneWillDeactivateNotification
|
||||||
|
object:self.window.windowScene];
|
||||||
|
}
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(onSceneDidActivate:)
|
||||||
|
name:UISceneDidActivateNotification
|
||||||
|
object:newWindow.windowScene];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(onSceneWillDeactivate:)
|
||||||
|
name:UISceneWillDeactivateNotification
|
||||||
|
object:newWindow.windowScene];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)onSceneDidActivate:(NSNotification *)note API_AVAILABLE(ios(13.0)){
|
||||||
|
onFocus(self.handle, YES);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)onSceneWillDeactivate:(NSNotification *)note API_AVAILABLE(ios(13.0)){
|
||||||
|
onFocus(self.handle, NO);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)registerWindowNotifications:(UIWindow *)newWindow {
|
||||||
if (self.window != nil) {
|
if (self.window != nil) {
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||||
name:UIWindowDidBecomeKeyNotification
|
name:UIWindowDidBecomeKeyNotification
|
||||||
object:self.window];
|
object:self.window];
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||||
name:UIWindowDidResignKeyNotification
|
name:UIWindowDidResignKeyNotification
|
||||||
object:self.window];
|
object:self.window];
|
||||||
}
|
}
|
||||||
self.contentScaleFactor = newWindow.screen.nativeScale;
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
selector:@selector(onWindowDidBecomeKey:)
|
selector:@selector(onWindowDidBecomeKey:)
|
||||||
name:UIWindowDidBecomeKeyNotification
|
name:UIWindowDidBecomeKeyNotification
|
||||||
object:newWindow];
|
object:newWindow];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
selector:@selector(onWindowDidResignKey:)
|
selector:@selector(onWindowDidResignKey:)
|
||||||
name:UIWindowDidResignKeyNotification
|
name:UIWindowDidResignKeyNotification
|
||||||
object:newWindow];
|
object:newWindow];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onWindowDidBecomeKey:(NSNotification *)note {
|
- (void)onWindowDidBecomeKey:(NSNotification *)note {
|
||||||
if (self.isFirstResponder) {
|
if (self.isFirstResponder) {
|
||||||
onFocus((__bridge CFTypeRef)self, YES);
|
onFocus(self.handle, YES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onWindowDidResignKey:(NSNotification *)note {
|
- (void)onWindowDidResignKey:(NSNotification *)note {
|
||||||
if (self.isFirstResponder) {
|
if (self.isFirstResponder) {
|
||||||
onFocus((__bridge CFTypeRef)self, NO);
|
onFocus(self.handle, NO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +218,7 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)insertText:(NSString *)text {
|
- (void)insertText:(NSString *)text {
|
||||||
onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)text);
|
onText(self.handle, (__bridge CFTypeRef)text);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)canBecomeFirstResponder {
|
- (BOOL)canBecomeFirstResponder {
|
||||||
@@ -187,23 +230,23 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)deleteBackward {
|
- (void)deleteBackward {
|
||||||
onDeleteBackward((__bridge CFTypeRef)self);
|
onDeleteBackward(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onUpArrow {
|
- (void)onUpArrow {
|
||||||
onUpArrow((__bridge CFTypeRef)self);
|
onUpArrow(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onDownArrow {
|
- (void)onDownArrow {
|
||||||
onDownArrow((__bridge CFTypeRef)self);
|
onDownArrow(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onLeftArrow {
|
- (void)onLeftArrow {
|
||||||
onLeftArrow((__bridge CFTypeRef)self);
|
onLeftArrow(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onRightArrow {
|
- (void)onRightArrow {
|
||||||
onRightArrow((__bridge CFTypeRef)self);
|
onRightArrow(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray<UIKeyCommand *> *)keyCommands {
|
- (NSArray<UIKeyCommand *> *)keyCommands {
|
||||||
@@ -227,23 +270,8 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
|||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface DisplayLinkHandle : NSObject {
|
CFTypeRef gio_createDisplayLink(void) {
|
||||||
}
|
CADisplayLink *dl = [CADisplayLink displayLinkWithTarget:[GioView class] selector:@selector(onFrameCallback:)];
|
||||||
@property uintptr_t handle;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation DisplayLinkHandle {
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)onFrameCallback:(CADisplayLink *)link {
|
|
||||||
gio_onFrameCallback((__bridge CFTypeRef)link, _handle);
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
CFTypeRef gio_createDisplayLink(uintptr_t handle) {
|
|
||||||
DisplayLinkHandle *h = [DisplayLinkHandle alloc];
|
|
||||||
h.handle = handle;
|
|
||||||
CADisplayLink *dl = [CADisplayLink displayLinkWithTarget:h selector:@selector(onFrameCallback:)];
|
|
||||||
dl.paused = YES;
|
dl.paused = YES;
|
||||||
NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
|
NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
|
||||||
[dl addToRunLoop:runLoop forMode:[runLoop currentMode]];
|
[dl addToRunLoop:runLoop forMode:[runLoop currentMode]];
|
||||||
@@ -283,3 +311,32 @@ void gio_showCursor() {
|
|||||||
void gio_setCursor(NSUInteger curID) {
|
void gio_setCursor(NSUInteger curID) {
|
||||||
// Not supported.
|
// Not supported.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||||
|
GioView *v = (__bridge GioView *)viewRef;
|
||||||
|
v.handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@interface _gioAppDelegate : UIResponder <UIApplicationDelegate>
|
||||||
|
@property (strong, nonatomic) UIWindow *window;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation _gioAppDelegate
|
||||||
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||||
|
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||||
|
GioViewController *controller = [[GioViewController alloc] initWithNibName:nil bundle:nil];
|
||||||
|
self.window.rootViewController = controller;
|
||||||
|
[self.window makeKeyAndVisible];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
|
||||||
|
gio_onOpenURI((__bridge CFTypeRef)url.absoluteString);
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
int gio_applicationMain(int argc, char *argv[]) {
|
||||||
|
@autoreleasepool {
|
||||||
|
return UIApplicationMain(argc, argv, nil, NSStringFromClass([_gioAppDelegate class]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+492
-118
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,16 +14,28 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"gioui.org/internal/f32color"
|
"gioui.org/internal/f32color"
|
||||||
|
"gioui.org/op"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/clipboard"
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
|
"gioui.org/io/transfer"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ViewEvent struct{}
|
type JSViewEvent struct {
|
||||||
|
Element js.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
type contextStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
contextStatusOkay contextStatus = iota
|
||||||
|
contextStatusLost
|
||||||
|
contextStatusRestored
|
||||||
|
)
|
||||||
|
|
||||||
type window struct {
|
type window struct {
|
||||||
window js.Value
|
window js.Value
|
||||||
@@ -40,12 +53,10 @@ type window struct {
|
|||||||
screenOrientation js.Value
|
screenOrientation js.Value
|
||||||
cleanfuncs []func()
|
cleanfuncs []func()
|
||||||
touches []js.Value
|
touches []js.Value
|
||||||
composing bool
|
composing int
|
||||||
|
lastCursor int
|
||||||
requestFocus bool
|
requestFocus bool
|
||||||
|
|
||||||
chanAnimation chan struct{}
|
|
||||||
chanRedraw chan struct{}
|
|
||||||
|
|
||||||
config Config
|
config Config
|
||||||
inset f32.Point
|
inset f32.Point
|
||||||
scale float32
|
scale float32
|
||||||
@@ -54,9 +65,11 @@ type window struct {
|
|||||||
// is pending.
|
// is pending.
|
||||||
animRequested bool
|
animRequested bool
|
||||||
wakeups chan struct{}
|
wakeups chan struct{}
|
||||||
|
|
||||||
|
contextStatus contextStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) error {
|
func newWindow(win *callbacks, options []Option) {
|
||||||
doc := js.Global().Get("document")
|
doc := js.Global().Get("document")
|
||||||
cont := getContainer(doc)
|
cont := getContainer(doc)
|
||||||
cnv := createCanvas(doc)
|
cnv := createCanvas(doc)
|
||||||
@@ -71,7 +84,10 @@ func newWindow(win *callbacks, options []Option) error {
|
|||||||
head: doc.Get("head"),
|
head: doc.Get("head"),
|
||||||
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
||||||
wakeups: make(chan struct{}, 1),
|
wakeups: make(chan struct{}, 1),
|
||||||
|
w: win,
|
||||||
|
composing: -1,
|
||||||
}
|
}
|
||||||
|
w.w.SetDriver(w)
|
||||||
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
||||||
w.browserHistory = w.window.Get("history")
|
w.browserHistory = w.window.Get("history")
|
||||||
w.visualViewport = w.window.Get("visualViewport")
|
w.visualViewport = w.window.Get("visualViewport")
|
||||||
@@ -81,41 +97,28 @@ func newWindow(win *callbacks, options []Option) error {
|
|||||||
if screen := w.window.Get("screen"); screen.Truthy() {
|
if screen := w.window.Get("screen"); screen.Truthy() {
|
||||||
w.screenOrientation = screen.Get("orientation")
|
w.screenOrientation = screen.Get("orientation")
|
||||||
}
|
}
|
||||||
w.chanAnimation = make(chan struct{}, 1)
|
|
||||||
w.chanRedraw = make(chan struct{}, 1)
|
|
||||||
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
w.chanAnimation <- struct{}{}
|
w.draw(false)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
content := args[0].String()
|
content := args[0].String()
|
||||||
go win.Event(clipboard.Event{Text: content})
|
w.processEvent(transfer.DataEvent{
|
||||||
|
Type: "application/text",
|
||||||
|
Open: func() io.ReadCloser {
|
||||||
|
return io.NopCloser(strings.NewReader(content))
|
||||||
|
},
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListeners()
|
w.addEventListeners()
|
||||||
w.addHistory()
|
w.addHistory()
|
||||||
w.w = win
|
|
||||||
|
|
||||||
go func() {
|
w.Configure(options)
|
||||||
defer w.cleanup()
|
w.blur()
|
||||||
w.w.SetDriver(w)
|
w.processEvent(JSViewEvent{Element: cont})
|
||||||
w.Configure(options)
|
w.resize()
|
||||||
w.blur()
|
w.draw(true)
|
||||||
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
|
||||||
w.resize()
|
|
||||||
w.draw(true)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-w.wakeups:
|
|
||||||
w.w.Event(wakeupEvent{})
|
|
||||||
case <-w.chanAnimation:
|
|
||||||
w.animCallback()
|
|
||||||
case <-w.chanRedraw:
|
|
||||||
w.draw(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContainer(doc js.Value) js.Value {
|
func getContainer(doc js.Value) js.Value {
|
||||||
@@ -129,8 +132,10 @@ func getContainer(doc js.Value) js.Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createTextArea(doc js.Value) js.Value {
|
func createTextArea(doc js.Value) js.Value {
|
||||||
tarea := doc.Call("createElement", "input")
|
tarea := doc.Call("createElement", "textarea")
|
||||||
style := tarea.Get("style")
|
style := tarea.Get("style")
|
||||||
|
// Position absolute so left/top coordinates actually place the element
|
||||||
|
style.Set("position", "absolute")
|
||||||
style.Set("width", "1px")
|
style.Set("width", "1px")
|
||||||
style.Set("height", "1px")
|
style.Set("height", "1px")
|
||||||
style.Set("opacity", "0")
|
style.Set("opacity", "0")
|
||||||
@@ -140,6 +145,12 @@ func createTextArea(doc js.Value) js.Value {
|
|||||||
tarea.Set("autocorrect", "off")
|
tarea.Set("autocorrect", "off")
|
||||||
tarea.Set("autocapitalize", "off")
|
tarea.Set("autocapitalize", "off")
|
||||||
tarea.Set("spellcheck", false)
|
tarea.Set("spellcheck", false)
|
||||||
|
// Enable multiline text input for better composition support on some browsers.
|
||||||
|
tarea.Set("rows", 1)
|
||||||
|
style.Set("resize", "none")
|
||||||
|
style.Set("overflow", "hidden")
|
||||||
|
style.Set("white-space", "pre-wrap")
|
||||||
|
style.Set("word-break", "normal")
|
||||||
return tarea
|
return tarea
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,9 +173,25 @@ func (w *window) cleanup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) addEventListeners() {
|
func (w *window) addEventListeners() {
|
||||||
|
w.addEventListener(w.cnv, "webglcontextlost", func(this js.Value, args []js.Value) interface{} {
|
||||||
|
args[0].Call("preventDefault")
|
||||||
|
w.contextStatus = contextStatusLost
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
w.addEventListener(w.cnv, "webglcontextrestored", func(this js.Value, args []js.Value) interface{} {
|
||||||
|
args[0].Call("preventDefault")
|
||||||
|
w.contextStatus = contextStatusRestored
|
||||||
|
|
||||||
|
// Resize is required to force update the canvas content when restored.
|
||||||
|
w.cnv.Set("width", 0)
|
||||||
|
w.cnv.Set("height", 0)
|
||||||
|
w.resize()
|
||||||
|
w.draw(true)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.resize()
|
w.resize()
|
||||||
w.chanRedraw <- struct{}{}
|
w.draw(true)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
||||||
@@ -172,25 +199,11 @@ func (w *window) addEventListeners() {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
||||||
ev := &system.CommandEvent{Type: system.CommandBack}
|
if w.processEvent(key.Event{Name: key.NameBack}) {
|
||||||
w.w.Event(ev)
|
|
||||||
if ev.Cancel {
|
|
||||||
return w.browserHistory.Call("forward")
|
return w.browserHistory.Call("forward")
|
||||||
}
|
}
|
||||||
|
|
||||||
return w.browserHistory.Call("back")
|
return w.browserHistory.Call("back")
|
||||||
})
|
})
|
||||||
w.addEventListener(w.document, "visibilitychange", func(this js.Value, args []js.Value) interface{} {
|
|
||||||
ev := system.StageEvent{}
|
|
||||||
switch w.document.Get("visibilityState").String() {
|
|
||||||
case "hidden", "prerender", "unloaded":
|
|
||||||
ev.Stage = system.StagePaused
|
|
||||||
default:
|
|
||||||
ev.Stage = system.StageRunning
|
|
||||||
}
|
|
||||||
w.w.Event(ev)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.pointerEvent(pointer.Move, 0, 0, args[0])
|
w.pointerEvent(pointer.Move, 0, 0, args[0])
|
||||||
return nil
|
return nil
|
||||||
@@ -210,6 +223,10 @@ func (w *window) addEventListeners() {
|
|||||||
w.addEventListener(w.cnv, "wheel", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.cnv, "wheel", func(this js.Value, args []js.Value) interface{} {
|
||||||
e := args[0]
|
e := args[0]
|
||||||
dx, dy := e.Get("deltaX").Float(), e.Get("deltaY").Float()
|
dx, dy := e.Get("deltaX").Float(), e.Get("deltaY").Float()
|
||||||
|
// horizontal scroll if shift is pressed.
|
||||||
|
if e.Get("shiftKey").Bool() {
|
||||||
|
dx, dy = dy, dx
|
||||||
|
}
|
||||||
mode := e.Get("deltaMode").Int()
|
mode := e.Get("deltaMode").Int()
|
||||||
switch mode {
|
switch mode {
|
||||||
case 0x01: // DOM_DELTA_LINE
|
case 0x01: // DOM_DELTA_LINE
|
||||||
@@ -244,18 +261,28 @@ func (w *window) addEventListeners() {
|
|||||||
w.touches[i] = js.Null()
|
w.touches[i] = js.Null()
|
||||||
}
|
}
|
||||||
w.touches = w.touches[:0]
|
w.touches = w.touches[:0]
|
||||||
w.w.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Type: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.w.Event(key.FocusEvent{Focus: true})
|
w.config.Focused = true
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.w.Event(key.FocusEvent{Focus: false})
|
if w.composing != -1 {
|
||||||
|
// If we're composing, try to cancel.
|
||||||
|
// On Javascript is not possible to cancel the composition once started.
|
||||||
|
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
||||||
|
w.composing = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
w.config.Focused = false
|
||||||
|
w.lastCursor = 0 // Reset cursor tracking on blur
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
w.blur()
|
w.blur()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -268,19 +295,205 @@ func (w *window) addEventListeners() {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "compositionstart", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "compositionstart", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.composing = true
|
st := w.w.EditorState()
|
||||||
|
sel := st.Selection.Range
|
||||||
|
if sel.Start == -1 {
|
||||||
|
sel.Start = 0
|
||||||
|
sel.End = 0
|
||||||
|
}
|
||||||
|
w.w.SetEditorSnippet(key.Range{Start: sel.Start, End: sel.End})
|
||||||
|
w.composing = sel.Start
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "compositionend", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "compositionend", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.composing = false
|
finalText := w.tarea.Get("value").String()
|
||||||
w.flushInput()
|
|
||||||
|
if w.composing != -1 && finalText != "" {
|
||||||
|
// Replace the entire composition range with the final text.
|
||||||
|
compEnd := w.composing + utf8.RuneCountInString(finalText)
|
||||||
|
replaceRange := key.Range{Start: w.composing, End: compEnd}
|
||||||
|
w.w.EditorReplace(replaceRange, finalText)
|
||||||
|
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
||||||
|
|
||||||
|
// Position cursor after the final composition text.
|
||||||
|
newEnd := w.composing + utf8.RuneCountInString(finalText)
|
||||||
|
w.w.SetEditorSelection(key.Range{Start: newEnd, End: newEnd})
|
||||||
|
}
|
||||||
|
|
||||||
|
w.composing = -1
|
||||||
|
w.tarea.Set("value", "")
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "input", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "input", func(this js.Value, args []js.Value) interface{} {
|
||||||
if w.composing {
|
e := args[0]
|
||||||
return nil
|
inputType := e.Get("inputType").String()
|
||||||
|
|
||||||
|
dataVal := e.Get("data")
|
||||||
|
var data string
|
||||||
|
if dataVal.Truthy() {
|
||||||
|
data = dataVal.String()
|
||||||
}
|
}
|
||||||
w.flushInput()
|
|
||||||
|
// Get the current textarea value.
|
||||||
|
tareaValue := w.tarea.Get("value").String()
|
||||||
|
st := w.w.EditorState()
|
||||||
|
|
||||||
|
sel := st.Selection.Range
|
||||||
|
var absStart, absEnd int
|
||||||
|
|
||||||
|
snippetStart := st.Snippet.Range.Start
|
||||||
|
snippetEnd := st.Snippet.Range.End
|
||||||
|
|
||||||
|
cursorPos := sel.Start
|
||||||
|
selectionEnd := sel.End
|
||||||
|
if cursorPos < 0 {
|
||||||
|
cursorPos = 0
|
||||||
|
selectionEnd = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we need to expand the snippet to include the range.
|
||||||
|
if st.Snippet.Range.Start == 0 && st.Snippet.Range.End == 0 && tareaValue != "" {
|
||||||
|
// Empty snippet - set it to include the selection/cursor.
|
||||||
|
w.w.SetEditorSnippet(key.Range{Start: cursorPos, End: selectionEnd})
|
||||||
|
absStart = cursorPos
|
||||||
|
absEnd = selectionEnd
|
||||||
|
} else if cursorPos < snippetStart || selectionEnd > snippetEnd {
|
||||||
|
// Selection is outside the snippet
|
||||||
|
newStart := snippetStart
|
||||||
|
newEnd := snippetEnd
|
||||||
|
if cursorPos < newStart {
|
||||||
|
newStart = cursorPos
|
||||||
|
}
|
||||||
|
if selectionEnd > newEnd {
|
||||||
|
newEnd = selectionEnd
|
||||||
|
}
|
||||||
|
w.w.SetEditorSnippet(key.Range{Start: newStart, End: newEnd})
|
||||||
|
// Refresh state after snippet update.
|
||||||
|
st = w.w.EditorState()
|
||||||
|
// Use the selection range directly.
|
||||||
|
absStart = cursorPos
|
||||||
|
absEnd = selectionEnd
|
||||||
|
} else {
|
||||||
|
// Selection is within snippet to absolute positions.
|
||||||
|
absStart = cursorPos
|
||||||
|
absEnd = selectionEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
switch inputType {
|
||||||
|
case "insertCompositionText":
|
||||||
|
if w.composing == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
compEnd := absEnd
|
||||||
|
if compEnd < w.composing {
|
||||||
|
compEnd = w.composing
|
||||||
|
}
|
||||||
|
replaceRange := key.Range{Start: w.composing, End: compEnd}
|
||||||
|
w.w.EditorReplace(replaceRange, data)
|
||||||
|
|
||||||
|
newEnd := w.composing + utf8.RuneCountInString(data)
|
||||||
|
w.w.SetComposingRegion(key.Range{Start: w.composing, End: newEnd})
|
||||||
|
w.w.SetEditorSelection(key.Range{Start: newEnd, End: newEnd})
|
||||||
|
|
||||||
|
case "deleteContentBackward", "deleteContentForward", "deleteByCut":
|
||||||
|
if w.composing != -1 {
|
||||||
|
compEnd := w.composing + utf8.RuneCountInString(tareaValue)
|
||||||
|
replaceRange := key.Range{Start: w.composing, End: compEnd}
|
||||||
|
w.w.EditorReplace(replaceRange, tareaValue)
|
||||||
|
|
||||||
|
newEnd := w.composing + utf8.RuneCountInString(tareaValue)
|
||||||
|
w.w.SetComposingRegion(key.Range{Start: w.composing, End: newEnd})
|
||||||
|
w.w.SetEditorSelection(key.Range{Start: newEnd, End: newEnd})
|
||||||
|
} else {
|
||||||
|
replaceRange := key.Range{Start: absStart, End: absEnd}
|
||||||
|
w.w.EditorReplace(replaceRange, "")
|
||||||
|
w.w.SetEditorSelection(key.Range{Start: absStart, End: absStart})
|
||||||
|
}
|
||||||
|
|
||||||
|
case "insertReplacementText":
|
||||||
|
if w.composing != -1 {
|
||||||
|
// During composition, replace the entire composition.
|
||||||
|
compEnd := w.composing + utf8.RuneCountInString(data)
|
||||||
|
replaceRange := key.Range{Start: w.composing, End: compEnd}
|
||||||
|
w.w.EditorReplace(replaceRange, data)
|
||||||
|
|
||||||
|
newEnd := w.composing + utf8.RuneCountInString(data)
|
||||||
|
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
||||||
|
w.w.SetEditorSelection(key.Range{Start: newEnd, End: newEnd})
|
||||||
|
w.composing = -1
|
||||||
|
w.lastCursor = newEnd
|
||||||
|
} else {
|
||||||
|
// Safari sends "insertReplacementText" for autocorrect, but the cursor is at the end of the word, so we need to find the word start.
|
||||||
|
insertLen := utf8.RuneCountInString(data)
|
||||||
|
wordStart := absStart
|
||||||
|
|
||||||
|
if absStart > snippetStart {
|
||||||
|
relPos := absStart - snippetStart
|
||||||
|
snippetRunes := []rune(st.Snippet.Text)
|
||||||
|
|
||||||
|
for i := relPos - 1; i >= 0; i-- {
|
||||||
|
if i >= len(snippetRunes) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r := snippetRunes[i]
|
||||||
|
if r == ' ' || r == '\t' || r == '\n' || r == '\r' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
wordStart = snippetStart + i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceRange := key.Range{Start: wordStart, End: absStart}
|
||||||
|
w.w.EditorReplace(replaceRange, data)
|
||||||
|
|
||||||
|
newCursor := wordStart + insertLen
|
||||||
|
w.w.SetEditorSelection(key.Range{Start: newCursor, End: newCursor})
|
||||||
|
w.lastCursor = newCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
case "insertText":
|
||||||
|
if w.composing != -1 {
|
||||||
|
compEnd := w.composing + utf8.RuneCountInString(data)
|
||||||
|
replaceRange := key.Range{Start: w.composing, End: compEnd}
|
||||||
|
w.w.EditorReplace(replaceRange, data)
|
||||||
|
|
||||||
|
newEnd := w.composing + utf8.RuneCountInString(data)
|
||||||
|
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
||||||
|
w.w.SetEditorSelection(key.Range{Start: newEnd, End: newEnd})
|
||||||
|
w.composing = -1
|
||||||
|
w.lastCursor = newEnd
|
||||||
|
} else {
|
||||||
|
insertLen := utf8.RuneCountInString(data)
|
||||||
|
replaceRange := key.Range{Start: absStart, End: absStart}
|
||||||
|
if absStart != absEnd {
|
||||||
|
replaceRange = key.Range{Start: absStart, End: absEnd}
|
||||||
|
}
|
||||||
|
|
||||||
|
newCursor := replaceRange.Start + insertLen
|
||||||
|
w.w.EditorReplace(replaceRange, data)
|
||||||
|
w.w.SetEditorSelection(key.Range{Start: newCursor, End: newCursor})
|
||||||
|
w.lastCursor = newCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
default: // paste and other input types
|
||||||
|
if w.composing != -1 {
|
||||||
|
compEnd := w.composing + utf8.RuneCountInString(tareaValue)
|
||||||
|
replaceRange := key.Range{Start: w.composing, End: compEnd}
|
||||||
|
w.w.EditorReplace(replaceRange, tareaValue)
|
||||||
|
|
||||||
|
newEnd := w.composing + utf8.RuneCountInString(tareaValue)
|
||||||
|
w.w.SetComposingRegion(key.Range{Start: w.composing, End: newEnd})
|
||||||
|
w.w.SetEditorSelection(key.Range{Start: newEnd, End: newEnd})
|
||||||
|
} else {
|
||||||
|
replaceRange := key.Range{Start: absStart, End: absEnd}
|
||||||
|
w.w.EditorReplace(replaceRange, tareaValue)
|
||||||
|
|
||||||
|
newCursor := absStart + utf8.RuneCountInString(tareaValue)
|
||||||
|
w.w.SetEditorSelection(key.Range{Start: newCursor, End: newCursor})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "paste", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "paste", func(this js.Value, args []js.Value) interface{} {
|
||||||
@@ -297,12 +510,6 @@ func (w *window) addHistory() {
|
|||||||
w.browserHistory.Call("pushState", nil, nil, w.window.Get("location").Get("href"))
|
w.browserHistory.Call("pushState", nil, nil, w.window.Get("location").Get("href"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) flushInput() {
|
|
||||||
val := w.tarea.Get("value").String()
|
|
||||||
w.tarea.Set("value", "")
|
|
||||||
w.w.EditorInsert(string(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) blur() {
|
func (w *window) blur() {
|
||||||
w.tarea.Call("blur")
|
w.tarea.Call("blur")
|
||||||
w.requestFocus = false
|
w.requestFocus = false
|
||||||
@@ -328,24 +535,104 @@ func (w *window) keyboard(hint key.InputHint) {
|
|||||||
m = "url"
|
m = "url"
|
||||||
case key.HintTelephone:
|
case key.HintTelephone:
|
||||||
m = "tel"
|
m = "tel"
|
||||||
|
case key.HintPassword:
|
||||||
|
m = "password"
|
||||||
default:
|
default:
|
||||||
m = "text"
|
m = "text"
|
||||||
}
|
}
|
||||||
w.tarea.Set("inputMode", m)
|
w.tarea.Set("inputMode", m)
|
||||||
|
|
||||||
|
// Update autocomplete / autocorrect attributes.
|
||||||
|
var autocomplete, autocorrect, autocapitalize string
|
||||||
|
var spellcheck bool
|
||||||
|
|
||||||
|
switch hint {
|
||||||
|
case key.HintAny, key.HintText:
|
||||||
|
autocomplete, autocorrect, autocapitalize, spellcheck = "on", "on", "on", true
|
||||||
|
case key.HintEmail:
|
||||||
|
autocomplete, autocorrect, autocapitalize, spellcheck = "email", "off", "off", false
|
||||||
|
case key.HintURL:
|
||||||
|
autocomplete, autocorrect, autocapitalize, spellcheck = "url", "off", "off", false
|
||||||
|
case key.HintTelephone:
|
||||||
|
autocomplete, autocorrect, autocapitalize, spellcheck = "tel", "off", "off", false
|
||||||
|
case key.HintPassword:
|
||||||
|
autocomplete, autocorrect, autocapitalize, spellcheck = "current-password", "off", "off", false
|
||||||
|
default: // key.HintNumeric and others
|
||||||
|
autocomplete, autocorrect, autocapitalize, spellcheck = "off", "off", "off", false
|
||||||
|
}
|
||||||
|
|
||||||
|
w.tarea.Set("autocomplete", autocomplete)
|
||||||
|
w.tarea.Set("autocorrect", autocorrect)
|
||||||
|
w.tarea.Set("autocapitalize", autocapitalize)
|
||||||
|
w.tarea.Set("spellcheck", spellcheck)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) keyEvent(e js.Value, ks key.State) {
|
func (w *window) keyEvent(e js.Value, ks key.State) {
|
||||||
k := e.Get("key").String()
|
k := e.Get("key").String()
|
||||||
|
|
||||||
if n, ok := translateKey(k); ok {
|
if n, ok := translateKey(k); ok {
|
||||||
|
if ks == key.Press {
|
||||||
|
isMod := n == key.NameAlt || n == key.NameCommand || n == key.NameCtrl || n == key.NameShift || n == key.NameSuper
|
||||||
|
isFunc := n == key.NameUpArrow || n == key.NameDownArrow || n == key.NameLeftArrow || n == key.NameRightArrow ||
|
||||||
|
n == key.NamePageUp || n == key.NamePageDown || n == key.NameHome || n == key.NameEnd ||
|
||||||
|
n == key.NameEscape || n == key.NameReturn || n == key.NameEnter || n == key.NameTab ||
|
||||||
|
n == key.NameDeleteBackward || n == key.NameDeleteForward
|
||||||
|
|
||||||
|
if isMod || isFunc {
|
||||||
|
// Gio will request the browser to change the selection/carret position natively.
|
||||||
|
e.Call("preventDefault")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cmd := key.Event{
|
cmd := key.Event{
|
||||||
Name: n,
|
Name: n,
|
||||||
Modifiers: modifiersFor(e),
|
Modifiers: modifiersFor(e),
|
||||||
State: ks,
|
State: ks,
|
||||||
}
|
}
|
||||||
w.w.Event(cmd)
|
w.processEvent(cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
|
w.processEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) processEvent(e event.Event) bool {
|
||||||
|
if !w.w.ProcessEvent(e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case w.wakeups <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Event() event.Event {
|
||||||
|
for {
|
||||||
|
evt, ok := w.w.nextEvent()
|
||||||
|
if ok {
|
||||||
|
if _, destroy := evt.(DestroyEvent); destroy {
|
||||||
|
w.cleanup()
|
||||||
|
}
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
<-w.wakeups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
w.w.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.w.ProcessFrame(frame, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
||||||
// KeyEvent.
|
// KeyEvent.
|
||||||
func modifiersFor(e js.Value) key.Modifiers {
|
func modifiersFor(e js.Value) key.Modifiers {
|
||||||
@@ -363,10 +650,16 @@ func modifiersFor(e js.Value) key.Modifiers {
|
|||||||
if e.Call("getModifierState", "Shift").Bool() {
|
if e.Call("getModifierState", "Shift").Bool() {
|
||||||
mods |= key.ModShift
|
mods |= key.ModShift
|
||||||
}
|
}
|
||||||
|
if e.Call("getModifierState", "Meta").Bool() {
|
||||||
|
mods |= key.ModCommand
|
||||||
|
}
|
||||||
|
if e.Call("getModifierState", "OS").Bool() {
|
||||||
|
mods |= key.ModSuper
|
||||||
|
}
|
||||||
return mods
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) touchEvent(typ pointer.Type, e js.Value) {
|
func (w *window) touchEvent(kind pointer.Kind, e js.Value) {
|
||||||
e.Call("preventDefault")
|
e.Call("preventDefault")
|
||||||
t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
|
t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
|
||||||
changedTouches := e.Get("changedTouches")
|
changedTouches := e.Get("changedTouches")
|
||||||
@@ -383,6 +676,9 @@ func (w *window) touchEvent(typ pointer.Type, e js.Value) {
|
|||||||
if e.Get("ctrlKey").Bool() {
|
if e.Get("ctrlKey").Bool() {
|
||||||
mods |= key.ModCtrl
|
mods |= key.ModCtrl
|
||||||
}
|
}
|
||||||
|
if e.Get("metaKey").Bool() {
|
||||||
|
mods |= key.ModCommand
|
||||||
|
}
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
touch := changedTouches.Index(i)
|
touch := changedTouches.Index(i)
|
||||||
pid := w.touchIDFor(touch)
|
pid := w.touchIDFor(touch)
|
||||||
@@ -393,8 +689,8 @@ func (w *window) touchEvent(typ pointer.Type, e js.Value) {
|
|||||||
X: float32(x) * scale,
|
X: float32(x) * scale,
|
||||||
Y: float32(y) * scale,
|
Y: float32(y) * scale,
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Type: typ,
|
Kind: kind,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
Position: pos,
|
Position: pos,
|
||||||
PointerID: pid,
|
PointerID: pid,
|
||||||
@@ -416,7 +712,7 @@ func (w *window) touchIDFor(touch js.Value) pointer.ID {
|
|||||||
return pid
|
return pid
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) pointerEvent(typ pointer.Type, dx, dy float32, e js.Value) {
|
func (w *window) pointerEvent(kind pointer.Kind, dx, dy float32, e js.Value) {
|
||||||
e.Call("preventDefault")
|
e.Call("preventDefault")
|
||||||
x, y := e.Get("clientX").Float(), e.Get("clientY").Float()
|
x, y := e.Get("clientX").Float(), e.Get("clientY").Float()
|
||||||
rect := w.cnv.Call("getBoundingClientRect")
|
rect := w.cnv.Call("getBoundingClientRect")
|
||||||
@@ -443,8 +739,8 @@ func (w *window) pointerEvent(typ pointer.Type, dx, dy float32, e js.Value) {
|
|||||||
if jbtns&4 != 0 {
|
if jbtns&4 != 0 {
|
||||||
btns |= pointer.ButtonTertiary
|
btns |= pointer.ButtonTertiary
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Type: typ,
|
Kind: kind,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: btns,
|
Buttons: btns,
|
||||||
Position: pos,
|
Position: pos,
|
||||||
@@ -470,19 +766,91 @@ func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.F
|
|||||||
return jsf
|
return jsf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) animCallback() {
|
func (w *window) EditorStateChanged(old, new editorState) {
|
||||||
anim := w.animating
|
if w.composing != -1 {
|
||||||
w.animRequested = anim
|
// Do not interfere with browser state while composing.
|
||||||
if anim {
|
// On Javascript is not possible to cancel the composition once started!
|
||||||
w.requestAnimationFrame.Invoke(w.redraw)
|
return
|
||||||
}
|
}
|
||||||
if anim {
|
|
||||||
w.draw(false)
|
// Update textarea value to match the snippet.
|
||||||
|
if old.Snippet != new.Snippet {
|
||||||
|
w.tarea.Set("value", new.Snippet.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update selection to match Gio's selection.
|
||||||
|
if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
|
||||||
|
if new.Selection.Range.Start != -1 && new.Selection.Range.End != -1 {
|
||||||
|
// Calculate selection positions relative to snippet start.
|
||||||
|
// The textarea contains only the snippet text.
|
||||||
|
snippetStart := new.Snippet.Range.Start
|
||||||
|
snippetEnd := new.Snippet.Range.End
|
||||||
|
|
||||||
|
selStart := new.Selection.Range.Start
|
||||||
|
selEnd := new.Selection.Range.End
|
||||||
|
if selStart < snippetStart {
|
||||||
|
selStart = snippetStart
|
||||||
|
}
|
||||||
|
if selStart > snippetEnd {
|
||||||
|
selStart = snippetEnd
|
||||||
|
}
|
||||||
|
if selEnd < snippetStart {
|
||||||
|
selEnd = snippetStart
|
||||||
|
}
|
||||||
|
if selEnd > snippetEnd {
|
||||||
|
selEnd = snippetEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert absolute rune positions to UTF-16 positions for the textarea.
|
||||||
|
startUTF16 := new.UTF16Index(selStart)
|
||||||
|
endUTF16 := new.UTF16Index(selEnd)
|
||||||
|
|
||||||
|
// Convert to snippet-relative UTF-16 positions.
|
||||||
|
snippetStartUTF16 := new.UTF16Index(snippetStart)
|
||||||
|
start := startUTF16 - snippetStartUTF16
|
||||||
|
end := endUTF16 - snippetStartUTF16
|
||||||
|
|
||||||
|
if start < 0 {
|
||||||
|
start = 0
|
||||||
|
}
|
||||||
|
if end < 0 {
|
||||||
|
end = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate max UTF-16 length of snippet text.
|
||||||
|
textLen := new.UTF16Index(snippetEnd) - snippetStartUTF16
|
||||||
|
if start > textLen {
|
||||||
|
start = textLen
|
||||||
|
}
|
||||||
|
if end > textLen {
|
||||||
|
end = textLen
|
||||||
|
}
|
||||||
|
|
||||||
|
if start > end {
|
||||||
|
start, end = end, start
|
||||||
|
}
|
||||||
|
|
||||||
|
w.tarea.Set("selectionStart", start)
|
||||||
|
w.tarea.Set("selectionEnd", end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move DOM element to position the caret.
|
||||||
|
if old.Selection.Caret != new.Selection.Caret || old.Selection.Transform != new.Selection.Transform {
|
||||||
|
pos := new.Selection.Transform.Transform(new.Selection.Caret.Pos.Add(f32.Pt(0, new.Selection.Caret.Descent)))
|
||||||
|
bounds := w.cnv.Call("getBoundingClientRect")
|
||||||
|
left := bounds.Get("left").Float() + float64(pos.X)/float64(w.scale)
|
||||||
|
top := bounds.Get("top").Float() + float64(pos.Y-new.Selection.Caret.Ascent)/float64(w.scale)
|
||||||
|
height := float64(new.Selection.Caret.Ascent+new.Selection.Caret.Descent) / float64(w.scale)
|
||||||
|
|
||||||
|
style := w.tarea.Get("style")
|
||||||
|
style.Set("left", fmt.Sprintf("%fpx", left))
|
||||||
|
style.Set("top", fmt.Sprintf("%fpx", top))
|
||||||
|
style.Set("height", fmt.Sprintf("%fpx", height))
|
||||||
|
style.Set("width", "1px")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
w.animating = anim
|
w.animating = anim
|
||||||
if anim && !w.animRequested {
|
if anim && !w.animRequested {
|
||||||
@@ -495,20 +863,19 @@ func (w *window) ReadClipboard() {
|
|||||||
if w.clipboard.IsUndefined() {
|
if w.clipboard.IsUndefined() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if w.clipboard.Get("readText").IsUndefined() {
|
if w.clipboard.Get("readText").Truthy() {
|
||||||
return
|
w.clipboard.Call("readText").Call("then", w.clipboardCallback)
|
||||||
}
|
}
|
||||||
w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) WriteClipboard(s string) {
|
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||||
if w.clipboard.IsUndefined() {
|
if w.clipboard.IsUndefined() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if w.clipboard.Get("writeText").IsUndefined() {
|
if w.clipboard.Get("writeText").IsUndefined() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.clipboard.Call("writeText", s)
|
w.clipboard.Call("writeText", string(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
@@ -536,15 +903,11 @@ func (w *window) Configure(options []Option) {
|
|||||||
if cnf.Decorated != prev.Decorated {
|
if cnf.Decorated != prev.Decorated {
|
||||||
w.config.Decorated = cnf.Decorated
|
w.config.Decorated = cnf.Decorated
|
||||||
}
|
}
|
||||||
if w.config != prev {
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Perform(system.Action) {}
|
func (w *window) Perform(system.Action) {}
|
||||||
|
|
||||||
func (w *window) Raise() {}
|
|
||||||
|
|
||||||
var webCursor = [...]string{
|
var webCursor = [...]string{
|
||||||
pointer.CursorDefault: "default",
|
pointer.CursorDefault: "default",
|
||||||
pointer.CursorNone: "none",
|
pointer.CursorNone: "none",
|
||||||
@@ -579,32 +942,27 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
style.Set("cursor", webCursor[cursor])
|
style.Set("cursor", webCursor[cursor])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
|
||||||
select {
|
|
||||||
case w.wakeups <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) ShowTextInput(show bool) {
|
func (w *window) ShowTextInput(show bool) {
|
||||||
// Run in a goroutine to avoid a deadlock if the
|
// Run in a goroutine to avoid a deadlock if the
|
||||||
// focus change result in an event.
|
// focus change result in an event.
|
||||||
go func() {
|
if show {
|
||||||
if show {
|
w.focus()
|
||||||
w.focus()
|
} else {
|
||||||
} else {
|
// If we're composing, end composition first by clearing the textarea.
|
||||||
w.blur()
|
// That is a attempt to force the browser to end composition.
|
||||||
|
if w.composing != -1 {
|
||||||
|
w.tarea.Set("value", "")
|
||||||
|
w.composing = -1
|
||||||
|
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
||||||
}
|
}
|
||||||
}()
|
w.blur()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) SetInputHint(mode key.InputHint) {
|
func (w *window) SetInputHint(mode key.InputHint) {
|
||||||
w.keyboard(mode)
|
w.keyboard(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the window. Not implemented for js.
|
|
||||||
func (w *window) Close() {}
|
|
||||||
|
|
||||||
func (w *window) resize() {
|
func (w *window) resize() {
|
||||||
w.scale = float32(w.window.Get("devicePixelRatio").Float())
|
w.scale = float32(w.window.Get("devicePixelRatio").Float())
|
||||||
|
|
||||||
@@ -615,7 +973,7 @@ func (w *window) resize() {
|
|||||||
}
|
}
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = size
|
w.config.Size = size
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
|
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
|
||||||
@@ -632,12 +990,23 @@ func (w *window) resize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw(sync bool) {
|
func (w *window) draw(sync bool) {
|
||||||
|
if w.contextStatus == contextStatusLost {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
anim := w.animating
|
||||||
|
w.animRequested = anim
|
||||||
|
if anim {
|
||||||
|
w.requestAnimationFrame.Invoke(w.redraw)
|
||||||
|
} else if !sync {
|
||||||
|
return
|
||||||
|
}
|
||||||
size, insets, metric := w.getConfig()
|
size, insets, metric := w.getConfig()
|
||||||
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.w.Event(frameEvent{
|
|
||||||
FrameEvent: system.FrameEvent{
|
w.processEvent(frameEvent{
|
||||||
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: size,
|
Size: size,
|
||||||
Insets: insets,
|
Insets: insets,
|
||||||
@@ -647,11 +1016,12 @@ func (w *window) draw(sync bool) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) getConfig() (image.Point, system.Insets, unit.Metric) {
|
func (w *window) getConfig() (image.Point, Insets, unit.Metric) {
|
||||||
|
invscale := unit.Dp(1. / w.scale)
|
||||||
return image.Pt(w.config.Size.X, w.config.Size.Y),
|
return image.Pt(w.config.Size.X, w.config.Size.Y),
|
||||||
system.Insets{
|
Insets{
|
||||||
Bottom: unit.Px(w.inset.Y),
|
Bottom: unit.Dp(w.inset.Y) * invscale,
|
||||||
Right: unit.Px(w.inset.X),
|
Right: unit.Dp(w.inset.X) * invscale,
|
||||||
}, unit.Metric{
|
}, unit.Metric{
|
||||||
PxPerDp: w.scale,
|
PxPerDp: w.scale,
|
||||||
PxPerSp: w.scale,
|
PxPerSp: w.scale,
|
||||||
@@ -709,8 +1079,8 @@ func osMain() {
|
|||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
func translateKey(k string) (string, bool) {
|
func translateKey(k string) (key.Name, bool) {
|
||||||
var n string
|
var n key.Name
|
||||||
|
|
||||||
switch k {
|
switch k {
|
||||||
case "ArrowUp":
|
case "ArrowUp":
|
||||||
@@ -777,11 +1147,15 @@ func translateKey(k string) (string, bool) {
|
|||||||
r, s := utf8.DecodeRuneInString(k)
|
r, s := utf8.DecodeRuneInString(k)
|
||||||
// If there is exactly one printable character, return that.
|
// If there is exactly one printable character, return that.
|
||||||
if s == len(k) && unicode.IsPrint(r) {
|
if s == len(k) && unicode.IsPrint(r) {
|
||||||
return strings.ToUpper(k), true
|
return key.Name(strings.ToUpper(k)), true
|
||||||
}
|
}
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return n, true
|
return n, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ ViewEvent) ImplementsEvent() {}
|
func (JSViewEvent) implementsViewEvent() {}
|
||||||
|
func (JSViewEvent) ImplementsEvent() {}
|
||||||
|
func (j JSViewEvent) Valid() bool {
|
||||||
|
return !(j.Element.IsNull() || j.Element.IsUndefined())
|
||||||
|
}
|
||||||
|
|||||||
+614
-335
File diff suppressed because it is too large
Load Diff
+142
-79
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
// +build darwin,!ios
|
// +build darwin,!ios
|
||||||
|
|
||||||
@import AppKit;
|
#import <AppKit/AppKit.h>
|
||||||
|
|
||||||
#include "_cgo_export.h"
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWithTrans);
|
||||||
|
|
||||||
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
||||||
@end
|
@end
|
||||||
@@ -14,45 +14,55 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
|||||||
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||||
|
@property uintptr_t handle;
|
||||||
|
@property BOOL presentWithTrans;
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation GioWindowDelegate
|
@implementation GioWindowDelegate
|
||||||
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onHide((__bridge CFTypeRef)window.contentView);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onShow((__bridge CFTypeRef)window.contentView);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onFullscreen((__bridge CFTypeRef)window.contentView);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onWindowed((__bridge CFTypeRef)window.contentView);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||||
CFTypeRef view = (__bridge CFTypeRef)window.contentView;
|
GioView *view = (GioView *)window.contentView;
|
||||||
gio_onChangeScreen(view, dispID);
|
gio_onChangeScreen(view.handle, dispID);
|
||||||
}
|
}
|
||||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onFocus((__bridge CFTypeRef)window.contentView, 1);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
if ([window firstResponder] == view) {
|
||||||
|
gio_onFocus(view.handle, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
- (void)windowDidResignKey:(NSNotification *)notification {
|
- (void)windowDidResignKey:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onFocus((__bridge CFTypeRef)window.contentView, 0);
|
GioView *view = (GioView *)window.contentView;
|
||||||
}
|
if ([window firstResponder] == view) {
|
||||||
- (void)windowWillClose:(NSNotification *)notification {
|
gio_onFocus(view.handle, 0);
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
}
|
||||||
window.delegate = nil;
|
|
||||||
gio_onClose((__bridge CFTypeRef)window.contentView);
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
||||||
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
|
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
|
||||||
if (!event.hasPreciseScrollingDeltas) {
|
if (!event.hasPreciseScrollingDeltas) {
|
||||||
// dx and dy are in rows and columns.
|
// dx and dy are in rows and columns.
|
||||||
@@ -61,12 +71,9 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
}
|
}
|
||||||
// Origin is in the lower left corner. Convert to upper left.
|
// Origin is in the lower left corner. Convert to upper left.
|
||||||
CGFloat height = view.bounds.size.height;
|
CGFloat height = view.bounds.size.height;
|
||||||
gio_onMouse((__bridge CFTypeRef)view, typ, [NSEvent pressedMouseButtons], p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GioView
|
@implementation GioView
|
||||||
- (void)setFrameSize:(NSSize)newSize {
|
- (void)setFrameSize:(NSSize)newSize {
|
||||||
[super setFrameSize:newSize];
|
[super setFrameSize:newSize];
|
||||||
@@ -75,75 +82,87 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
||||||
// Don't know why.
|
// Don't know why.
|
||||||
- (void)drawRect:(NSRect)r {
|
- (void)drawRect:(NSRect)r {
|
||||||
gio_onDraw((__bridge CFTypeRef)self);
|
gio_onDraw(self.handle);
|
||||||
}
|
}
|
||||||
- (void)displayLayer:(CALayer *)layer {
|
- (void)displayLayer:(CALayer *)layer {
|
||||||
layer.contentsScale = self.window.backingScaleFactor;
|
layer.contentsScale = self.window.backingScaleFactor;
|
||||||
gio_onDraw((__bridge CFTypeRef)self);
|
gio_onDraw(self.handle);
|
||||||
}
|
}
|
||||||
- (CALayer *)makeBackingLayer {
|
- (CALayer *)makeBackingLayer {
|
||||||
CALayer *layer = gio_layerFactory();
|
CALayer *layer = gio_layerFactory(self.presentWithTrans);
|
||||||
layer.delegate = self;
|
layer.delegate = self;
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
|
- (void)viewDidMoveToWindow {
|
||||||
|
gio_onAttached(self.handle, self.window != nil ? 1 : 0);
|
||||||
|
}
|
||||||
- (void)mouseDown:(NSEvent *)event {
|
- (void)mouseDown:(NSEvent *)event {
|
||||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||||
}
|
}
|
||||||
- (void)mouseUp:(NSEvent *)event {
|
- (void)mouseUp:(NSEvent *)event {
|
||||||
handleMouse(self, event, MOUSE_UP, 0, 0);
|
handleMouse(self, event, MOUSE_UP, 0, 0);
|
||||||
}
|
}
|
||||||
- (void)middleMouseDown:(NSEvent *)event {
|
|
||||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
|
||||||
}
|
|
||||||
- (void)middletMouseUp:(NSEvent *)event {
|
|
||||||
handleMouse(self, event, MOUSE_UP, 0, 0);
|
|
||||||
}
|
|
||||||
- (void)rightMouseDown:(NSEvent *)event {
|
- (void)rightMouseDown:(NSEvent *)event {
|
||||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||||
}
|
}
|
||||||
- (void)rightMouseUp:(NSEvent *)event {
|
- (void)rightMouseUp:(NSEvent *)event {
|
||||||
handleMouse(self, event, MOUSE_UP, 0, 0);
|
handleMouse(self, event, MOUSE_UP, 0, 0);
|
||||||
}
|
}
|
||||||
|
- (void)otherMouseDown:(NSEvent *)event {
|
||||||
|
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||||
|
}
|
||||||
|
- (void)otherMouseUp:(NSEvent *)event {
|
||||||
|
handleMouse(self, event, MOUSE_UP, 0, 0);
|
||||||
|
}
|
||||||
- (void)mouseMoved:(NSEvent *)event {
|
- (void)mouseMoved:(NSEvent *)event {
|
||||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||||
}
|
}
|
||||||
- (void)mouseDragged:(NSEvent *)event {
|
- (void)mouseDragged:(NSEvent *)event {
|
||||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||||
}
|
}
|
||||||
|
- (void)rightMouseDragged:(NSEvent *)event {
|
||||||
|
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||||
|
}
|
||||||
|
- (void)otherMouseDragged:(NSEvent *)event {
|
||||||
|
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||||
|
}
|
||||||
- (void)scrollWheel:(NSEvent *)event {
|
- (void)scrollWheel:(NSEvent *)event {
|
||||||
CGFloat dx = -event.scrollingDeltaX;
|
CGFloat dx = -event.scrollingDeltaX;
|
||||||
CGFloat dy = -event.scrollingDeltaY;
|
CGFloat dy = -event.scrollingDeltaY;
|
||||||
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
||||||
}
|
}
|
||||||
- (void)keyDown:(NSEvent *)event {
|
- (void)keyDown:(NSEvent *)event {
|
||||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
|
||||||
NSString *keys = [event charactersIgnoringModifiers];
|
NSString *keys = [event charactersIgnoringModifiers];
|
||||||
gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
||||||
|
}
|
||||||
|
- (void)flagsChanged:(NSEvent *)event {
|
||||||
|
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||||
|
gio_onFlagsChanged(self.handle, [event modifierFlags]);
|
||||||
}
|
}
|
||||||
- (void)keyUp:(NSEvent *)event {
|
- (void)keyUp:(NSEvent *)event {
|
||||||
NSString *keys = [event charactersIgnoringModifiers];
|
NSString *keys = [event charactersIgnoringModifiers];
|
||||||
gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
||||||
}
|
}
|
||||||
- (void)insertText:(id)string {
|
- (void)insertText:(id)string {
|
||||||
gio_onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)string);
|
gio_onText(self.handle, (__bridge CFTypeRef)string);
|
||||||
}
|
}
|
||||||
- (void)doCommandBySelector:(SEL)sel {
|
- (void)doCommandBySelector:(SEL)action {
|
||||||
// Don't pass commands up the responder chain.
|
if (!gio_onCommandBySelector(self.handle)) {
|
||||||
// They will end up in a beep.
|
[super doCommandBySelector:action];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)hasMarkedText {
|
- (BOOL)hasMarkedText {
|
||||||
int res = gio_hasMarkedText((__bridge CFTypeRef)self);
|
int res = gio_hasMarkedText(self.handle);
|
||||||
return res ? YES : NO;
|
return res ? YES : NO;
|
||||||
}
|
}
|
||||||
- (NSRange)markedRange {
|
- (NSRange)markedRange {
|
||||||
return gio_markedRange((__bridge CFTypeRef)self);
|
return gio_markedRange(self.handle);
|
||||||
}
|
}
|
||||||
- (NSRange)selectedRange {
|
- (NSRange)selectedRange {
|
||||||
return gio_selectedRange((__bridge CFTypeRef)self);
|
return gio_selectedRange(self.handle);
|
||||||
}
|
}
|
||||||
- (void)unmarkText {
|
- (void)unmarkText {
|
||||||
gio_unmarkText((__bridge CFTypeRef)self);
|
gio_unmarkText(self.handle);
|
||||||
}
|
}
|
||||||
- (void)setMarkedText:(id)string
|
- (void)setMarkedText:(id)string
|
||||||
selectedRange:(NSRange)selRange
|
selectedRange:(NSRange)selRange
|
||||||
@@ -155,14 +174,14 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
} else {
|
} else {
|
||||||
str = string;
|
str = string;
|
||||||
}
|
}
|
||||||
gio_setMarkedText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, selRange, replaceRange);
|
gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange);
|
||||||
}
|
}
|
||||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
||||||
actualRange:(NSRangePointer)actualRange {
|
actualRange:(NSRangePointer)actualRange {
|
||||||
NSString *str = CFBridgingRelease(gio_substringForProposedRange((__bridge CFTypeRef)self, range, actualRange));
|
NSString *str = CFBridgingRelease(gio_substringForProposedRange(self.handle, range, actualRange));
|
||||||
return [[NSAttributedString alloc] initWithString:str attributes:nil];
|
return [[NSAttributedString alloc] initWithString:str attributes:nil];
|
||||||
}
|
}
|
||||||
- (void)insertText:(id)string
|
- (void)insertText:(id)string
|
||||||
@@ -174,17 +193,34 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
} else {
|
} else {
|
||||||
str = string;
|
str = string;
|
||||||
}
|
}
|
||||||
gio_insertText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, replaceRange);
|
gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
|
||||||
}
|
}
|
||||||
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
||||||
return gio_characterIndexForPoint((__bridge CFTypeRef)self, p);
|
return gio_characterIndexForPoint(self.handle, p);
|
||||||
}
|
}
|
||||||
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
||||||
actualRange:(NSRangePointer)actual {
|
actualRange:(NSRangePointer)actual {
|
||||||
NSRect r = gio_firstRectForCharacterRange((__bridge CFTypeRef)self, rng, actual);
|
NSRect r = gio_firstRectForCharacterRange(self.handle, rng, actual);
|
||||||
r = [self convertRect:r toView:nil];
|
r = [self convertRect:r toView:nil];
|
||||||
return [[self window] convertRectToScreen:r];
|
return [[self window] convertRectToScreen:r];
|
||||||
}
|
}
|
||||||
|
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||||
|
gio_onDraw(self.handle);
|
||||||
|
}
|
||||||
|
- (void)applicationDidHide:(NSNotification *)notification {
|
||||||
|
gio_onDraw(self.handle);
|
||||||
|
}
|
||||||
|
- (void)dealloc {
|
||||||
|
gio_onDestroy(self.handle);
|
||||||
|
}
|
||||||
|
- (BOOL) becomeFirstResponder {
|
||||||
|
gio_onFocus(self.handle, 1);
|
||||||
|
return [super becomeFirstResponder];
|
||||||
|
}
|
||||||
|
- (BOOL) resignFirstResponder {
|
||||||
|
gio_onFocus(self.handle, 0);
|
||||||
|
return [super resignFirstResponder];
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
// Delegates are weakly referenced from their peers. Nothing
|
// Delegates are weakly referenced from their peers. Nothing
|
||||||
@@ -193,14 +229,14 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
static GioWindowDelegate *globalWindowDel;
|
static GioWindowDelegate *globalWindowDel;
|
||||||
|
|
||||||
static CVReturn displayLinkCallback(CVDisplayLinkRef dl, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *handle) {
|
static CVReturn displayLinkCallback(CVDisplayLinkRef dl, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *handle) {
|
||||||
gio_onFrameCallback(dl, (uintptr_t)handle);
|
gio_onFrameCallback(dl);
|
||||||
return kCVReturnSuccess;
|
return kCVReturnSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
CFTypeRef gio_createDisplayLink(uintptr_t handle) {
|
CFTypeRef gio_createDisplayLink(void) {
|
||||||
CVDisplayLinkRef dl;
|
CVDisplayLinkRef dl;
|
||||||
CVDisplayLinkCreateWithActiveCGDisplays(&dl);
|
CVDisplayLinkCreateWithActiveCGDisplays(&dl);
|
||||||
CVDisplayLinkSetOutputCallback(dl, displayLinkCallback, (void *)(handle));
|
CVDisplayLinkSetOutputCallback(dl, displayLinkCallback, nil);
|
||||||
return dl;
|
return dl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +270,7 @@ void gio_showCursor() {
|
|||||||
|
|
||||||
// some cursors are not public, this tries to use a private cursor
|
// some cursors are not public, this tries to use a private cursor
|
||||||
// and uses fallback when the use of private cursor fails.
|
// and uses fallback when the use of private cursor fails.
|
||||||
void gio_trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
||||||
if ([NSCursor respondsToSelector:cursorName]) {
|
if ([NSCursor respondsToSelector:cursorName]) {
|
||||||
id object = [NSCursor performSelector:cursorName];
|
id object = [NSCursor performSelector:cursorName];
|
||||||
if ([object isKindOfClass:[NSCursor class]]) {
|
if ([object isKindOfClass:[NSCursor class]]) {
|
||||||
@@ -266,7 +302,7 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
break;
|
break;
|
||||||
case 6: // pointer.CursorAllScroll
|
case 6: // pointer.CursorAllScroll
|
||||||
// For some reason, using _moveCursor fails on Monterey.
|
// For some reason, using _moveCursor fails on Monterey.
|
||||||
// gio_trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
// trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
||||||
[NSCursor.arrowCursor set];
|
[NSCursor.arrowCursor set];
|
||||||
break;
|
break;
|
||||||
case 7: // pointer.CursorColResize
|
case 7: // pointer.CursorColResize
|
||||||
@@ -276,33 +312,31 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
[NSCursor.resizeUpDownCursor set];
|
[NSCursor.resizeUpDownCursor set];
|
||||||
break;
|
break;
|
||||||
case 9: // pointer.CursorGrab
|
case 9: // pointer.CursorGrab
|
||||||
// [NSCursor.openHandCursor set];
|
[NSCursor.openHandCursor set];
|
||||||
gio_trySetPrivateCursor(@selector(openHandCursor), NSCursor.arrowCursor);
|
|
||||||
break;
|
break;
|
||||||
case 10: // pointer.CursorGrabbing
|
case 10: // pointer.CursorGrabbing
|
||||||
// [NSCursor.closedHandCursor set];
|
[NSCursor.closedHandCursor set];
|
||||||
gio_trySetPrivateCursor(@selector(closedHandCursor), NSCursor.arrowCursor);
|
|
||||||
break;
|
break;
|
||||||
case 11: // pointer.CursorNotAllowed
|
case 11: // pointer.CursorNotAllowed
|
||||||
[NSCursor.operationNotAllowedCursor set];
|
[NSCursor.operationNotAllowedCursor set];
|
||||||
break;
|
break;
|
||||||
case 12: // pointer.CursorWait
|
case 12: // pointer.CursorWait
|
||||||
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||||
break;
|
break;
|
||||||
case 13: // pointer.CursorProgress
|
case 13: // pointer.CursorProgress
|
||||||
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||||
break;
|
break;
|
||||||
case 14: // pointer.CursorNorthWestResize
|
case 14: // pointer.CursorNorthWestResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 15: // pointer.CursorNorthEastResize
|
case 15: // pointer.CursorNorthEastResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 16: // pointer.CursorSouthWestResize
|
case 16: // pointer.CursorSouthWestResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 17: // pointer.CursorSouthEastResize
|
case 17: // pointer.CursorSouthEastResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 18: // pointer.CursorNorthSouthResize
|
case 18: // pointer.CursorNorthSouthResize
|
||||||
[NSCursor.resizeUpDownCursor set];
|
[NSCursor.resizeUpDownCursor set];
|
||||||
@@ -323,10 +357,10 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
[NSCursor.resizeDownCursor set];
|
[NSCursor.resizeDownCursor set];
|
||||||
break;
|
break;
|
||||||
case 24: // pointer.CursorNorthEastSouthWestResize
|
case 24: // pointer.CursorNorthEastSouthWestResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 25: // pointer.CursorNorthWestSouthEastResize
|
case 25: // pointer.CursorNorthWestSouthEastResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
[NSCursor.arrowCursor set];
|
[NSCursor.arrowCursor set];
|
||||||
@@ -335,7 +369,7 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) {
|
CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSRect rect = NSMakeRect(0, 0, width, height);
|
NSRect rect = NSMakeRect(0, 0, width, height);
|
||||||
NSUInteger styleMask = NSTitledWindowMask |
|
NSUInteger styleMask = NSTitledWindowMask |
|
||||||
@@ -347,43 +381,50 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
|
|||||||
styleMask:styleMask
|
styleMask:styleMask
|
||||||
backing:NSBackingStoreBuffered
|
backing:NSBackingStoreBuffered
|
||||||
defer:NO];
|
defer:NO];
|
||||||
if (minWidth > 0 || minHeight > 0) {
|
|
||||||
window.contentMinSize = NSMakeSize(minWidth, minHeight);
|
|
||||||
}
|
|
||||||
if (maxWidth > 0 || maxHeight > 0) {
|
|
||||||
window.contentMaxSize = NSMakeSize(maxWidth, maxHeight);
|
|
||||||
}
|
|
||||||
[window setAcceptsMouseMovedEvents:YES];
|
[window setAcceptsMouseMovedEvents:YES];
|
||||||
NSView *view = (__bridge NSView *)viewRef;
|
NSView *view = (__bridge NSView *)viewRef;
|
||||||
[window setContentView:view];
|
[window setContentView:view];
|
||||||
[window makeFirstResponder:view];
|
|
||||||
window.releasedWhenClosed = NO;
|
|
||||||
window.delegate = globalWindowDel;
|
window.delegate = globalWindowDel;
|
||||||
return (__bridge_retained CFTypeRef)window;
|
return (__bridge_retained CFTypeRef)window;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CFTypeRef gio_createView(void) {
|
CFTypeRef gio_createView(int presentWithTrans) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
||||||
GioView* view = [[GioView alloc] initWithFrame:frame];
|
GioView* view = [[GioView alloc] initWithFrame:frame];
|
||||||
|
view.presentWithTrans = presentWithTrans ? YES : NO;
|
||||||
view.wantsLayer = YES;
|
view.wantsLayer = YES;
|
||||||
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||||
|
selector:@selector(applicationWillUnhide:)
|
||||||
|
name:NSApplicationWillUnhideNotification
|
||||||
|
object:nil];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||||
|
selector:@selector(applicationDidHide:)
|
||||||
|
name:NSApplicationDidHideNotification
|
||||||
|
object:nil];
|
||||||
return CFBridgingRetain(view);
|
return CFBridgingRetain(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||||
|
@autoreleasepool {
|
||||||
|
GioView *v = (__bridge GioView *)viewRef;
|
||||||
|
v.handle = handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@implementation GioAppDelegate
|
@implementation GioAppDelegate
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||||
[NSApp activateIgnoringOtherApps:YES];
|
[NSApp activateIgnoringOtherApps:YES];
|
||||||
gio_onFinishLaunching();
|
|
||||||
}
|
}
|
||||||
- (void)applicationDidHide:(NSNotification *)aNotification {
|
- (void)application:(NSApplication *)application openURLs:(NSArray<NSURL *> *)urls {
|
||||||
gio_onAppHide();
|
for (NSURL *url in urls) {
|
||||||
}
|
gio_onOpenURI((__bridge CFTypeRef)url.absoluteString);
|
||||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
}
|
||||||
gio_onAppShow();
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -414,3 +455,25 @@ void gio_main() {
|
|||||||
[NSApp run];
|
[NSApp run];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@interface AppListener : NSObject
|
||||||
|
@end
|
||||||
|
|
||||||
|
static AppListener *appListener;
|
||||||
|
|
||||||
|
@implementation AppListener
|
||||||
|
- (void)launchFinished:(NSNotification *)notification {
|
||||||
|
appListener = nil;
|
||||||
|
gio_onFinishLaunching();
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
void gio_init() {
|
||||||
|
@autoreleasepool {
|
||||||
|
appListener = [[AppListener alloc] init];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:appListener
|
||||||
|
selector:@selector(launchFinished:)
|
||||||
|
name:NSApplicationDidFinishLaunchingNotification
|
||||||
|
object:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+11
-13
@@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
//go:build (linux && !android) || freebsd || openbsd
|
//go:build (linux && !android) || freebsd || openbsd
|
||||||
// +build linux,!android freebsd openbsd
|
|
||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
@@ -12,13 +11,6 @@ import (
|
|||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ViewEvent provides handles to the underlying window objects for the
|
|
||||||
// current display protocol.
|
|
||||||
type ViewEvent interface {
|
|
||||||
implementsViewEvent()
|
|
||||||
ImplementsEvent()
|
|
||||||
}
|
|
||||||
|
|
||||||
type X11ViewEvent struct {
|
type X11ViewEvent struct {
|
||||||
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
||||||
Display unsafe.Pointer
|
Display unsafe.Pointer
|
||||||
@@ -28,6 +20,9 @@ type X11ViewEvent struct {
|
|||||||
|
|
||||||
func (X11ViewEvent) implementsViewEvent() {}
|
func (X11ViewEvent) implementsViewEvent() {}
|
||||||
func (X11ViewEvent) ImplementsEvent() {}
|
func (X11ViewEvent) ImplementsEvent() {}
|
||||||
|
func (x X11ViewEvent) Valid() bool {
|
||||||
|
return x != (X11ViewEvent{})
|
||||||
|
}
|
||||||
|
|
||||||
type WaylandViewEvent struct {
|
type WaylandViewEvent struct {
|
||||||
// Display is the *wl_display returned by wl_display_connect.
|
// Display is the *wl_display returned by wl_display_connect.
|
||||||
@@ -38,6 +33,9 @@ type WaylandViewEvent struct {
|
|||||||
|
|
||||||
func (WaylandViewEvent) implementsViewEvent() {}
|
func (WaylandViewEvent) implementsViewEvent() {}
|
||||||
func (WaylandViewEvent) ImplementsEvent() {}
|
func (WaylandViewEvent) ImplementsEvent() {}
|
||||||
|
func (w WaylandViewEvent) Valid() bool {
|
||||||
|
return w != (WaylandViewEvent{})
|
||||||
|
}
|
||||||
|
|
||||||
func osMain() {
|
func osMain() {
|
||||||
select {}
|
select {}
|
||||||
@@ -49,7 +47,7 @@ type windowDriver func(*callbacks, []Option) error
|
|||||||
// let each driver initialize these variables with their own version of createWindow.
|
// let each driver initialize these variables with their own version of createWindow.
|
||||||
var wlDriver, x11Driver windowDriver
|
var wlDriver, x11Driver windowDriver
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) error {
|
func newWindow(window *callbacks, options []Option) {
|
||||||
var errFirst error
|
var errFirst error
|
||||||
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
||||||
if d == nil {
|
if d == nil {
|
||||||
@@ -57,16 +55,16 @@ func newWindow(window *callbacks, options []Option) error {
|
|||||||
}
|
}
|
||||||
err := d(window, options)
|
err := d(window, options)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
if errFirst == nil {
|
if errFirst == nil {
|
||||||
errFirst = err
|
errFirst = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if errFirst != nil {
|
if errFirst == nil {
|
||||||
return errFirst
|
errFirst = errors.New("app: no window driver available")
|
||||||
}
|
}
|
||||||
return errors.New("app: no window driver available")
|
window.ProcessEvent(DestroyEvent{Err: errFirst})
|
||||||
}
|
}
|
||||||
|
|
||||||
// xCursor contains mapping from pointer.Cursor to XCursor.
|
// xCursor contains mapping from pointer.Cursor to XCursor.
|
||||||
|
|||||||
+410
-222
File diff suppressed because it is too large
Load Diff
+677
-254
File diff suppressed because it is too large
Load Diff
+176
-131
@@ -26,22 +26,25 @@ package app
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"os"
|
"io"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/clipboard"
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
|
"gioui.org/io/transfer"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
|
|
||||||
syscall "golang.org/x/sys/unix"
|
syscall "golang.org/x/sys/unix"
|
||||||
@@ -93,12 +96,10 @@ type x11Window struct {
|
|||||||
// _NET_WM_STATE_MAXIMIZED_VERT
|
// _NET_WM_STATE_MAXIMIZED_VERT
|
||||||
wmStateMaximizedVert C.Atom
|
wmStateMaximizedVert C.Atom
|
||||||
}
|
}
|
||||||
stage system.Stage
|
|
||||||
metric unit.Metric
|
metric unit.Metric
|
||||||
notify struct {
|
notify struct {
|
||||||
read, write int
|
read, write int
|
||||||
}
|
}
|
||||||
dead bool
|
|
||||||
|
|
||||||
animating bool
|
animating bool
|
||||||
|
|
||||||
@@ -111,6 +112,8 @@ type x11Window struct {
|
|||||||
config Config
|
config Config
|
||||||
|
|
||||||
wakeups chan struct{}
|
wakeups chan struct{}
|
||||||
|
handler x11EventHandler
|
||||||
|
buf [100]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -153,8 +156,8 @@ func (w *x11Window) ReadClipboard() {
|
|||||||
C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
|
C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) WriteClipboard(s string) {
|
func (w *x11Window) WriteClipboard(mime string, s []byte) {
|
||||||
w.clipboard.content = []byte(s)
|
w.clipboard.content = s
|
||||||
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
|
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
|
||||||
C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime)
|
C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime)
|
||||||
}
|
}
|
||||||
@@ -172,7 +175,7 @@ func (w *x11Window) Configure(options []Option) {
|
|||||||
switch prev.Mode {
|
switch prev.Mode {
|
||||||
case Fullscreen:
|
case Fullscreen:
|
||||||
case Minimized:
|
case Minimized:
|
||||||
w.Raise()
|
w.raise()
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
w.config.Mode = Fullscreen
|
w.config.Mode = Fullscreen
|
||||||
@@ -190,7 +193,7 @@ func (w *x11Window) Configure(options []Option) {
|
|||||||
switch prev.Mode {
|
switch prev.Mode {
|
||||||
case Fullscreen:
|
case Fullscreen:
|
||||||
case Minimized:
|
case Minimized:
|
||||||
w.Raise()
|
w.raise()
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
w.config.Mode = Maximized
|
w.config.Mode = Maximized
|
||||||
@@ -205,7 +208,7 @@ func (w *x11Window) Configure(options []Option) {
|
|||||||
C.XResizeWindow(w.x, w.xw, C.uint(cnf.Size.X), C.uint(cnf.Size.Y))
|
C.XResizeWindow(w.x, w.xw, C.uint(cnf.Size.X), C.uint(cnf.Size.Y))
|
||||||
case Minimized:
|
case Minimized:
|
||||||
w.config.Mode = Windowed
|
w.config.Mode = Windowed
|
||||||
w.Raise()
|
w.raise()
|
||||||
case Maximized:
|
case Maximized:
|
||||||
w.config.Mode = Windowed
|
w.config.Mode = Windowed
|
||||||
w.sendWMStateEvent(_NET_WM_STATE_REMOVE, w.atoms.wmStateMaximizedHorz, w.atoms.wmStateMaximizedVert)
|
w.sendWMStateEvent(_NET_WM_STATE_REMOVE, w.atoms.wmStateMaximizedHorz, w.atoms.wmStateMaximizedVert)
|
||||||
@@ -230,29 +233,11 @@ func (w *x11Window) Configure(options []Option) {
|
|||||||
if shints.flags != 0 {
|
if shints.flags != 0 {
|
||||||
C.XSetWMNormalHints(w.x, w.xw, &shints)
|
C.XSetWMNormalHints(w.x, w.xw, &shints)
|
||||||
}
|
}
|
||||||
if cnf.center {
|
|
||||||
screen := C.XDefaultScreen(w.x)
|
|
||||||
width := C.XDisplayWidth(w.x, screen)
|
|
||||||
height := C.XDisplayHeight(w.x, screen)
|
|
||||||
|
|
||||||
var attrs C.XWindowAttributes
|
|
||||||
C.XGetWindowAttributes(w.x, w.xw, &attrs)
|
|
||||||
width -= attrs.border_width
|
|
||||||
height -= attrs.border_width
|
|
||||||
|
|
||||||
sz := w.config.Size
|
|
||||||
x := (int(width) - sz.X) / 2
|
|
||||||
y := (int(height) - sz.Y) / 2
|
|
||||||
|
|
||||||
C.XMoveResizeWindow(w.x, w.xw, C.int(x), C.int(y), C.uint(sz.X), C.uint(sz.Y))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if cnf.Decorated != prev.Decorated {
|
if cnf.Decorated != prev.Decorated {
|
||||||
w.config.Decorated = cnf.Decorated
|
w.config.Decorated = cnf.Decorated
|
||||||
}
|
}
|
||||||
if w.config != prev {
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) setTitle(prev, cnf Config) {
|
func (w *x11Window) setTitle(prev, cnf Config) {
|
||||||
@@ -273,9 +258,38 @@ func (w *x11Window) setTitle(prev, cnf Config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) Perform(system.Action) {}
|
func (w *x11Window) Perform(acts system.Action) {
|
||||||
|
walkActions(acts, func(a system.Action) {
|
||||||
|
switch a {
|
||||||
|
case system.ActionCenter:
|
||||||
|
w.center()
|
||||||
|
case system.ActionRaise:
|
||||||
|
w.raise()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if acts&system.ActionClose != 0 {
|
||||||
|
w.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (w *x11Window) Raise() {
|
func (w *x11Window) center() {
|
||||||
|
screen := C.XDefaultScreen(w.x)
|
||||||
|
width := C.XDisplayWidth(w.x, screen)
|
||||||
|
height := C.XDisplayHeight(w.x, screen)
|
||||||
|
|
||||||
|
var attrs C.XWindowAttributes
|
||||||
|
C.XGetWindowAttributes(w.x, w.xw, &attrs)
|
||||||
|
width -= attrs.border_width
|
||||||
|
height -= attrs.border_width
|
||||||
|
|
||||||
|
sz := w.config.Size
|
||||||
|
x := (int(width) - sz.X) / 2
|
||||||
|
y := (int(height) - sz.Y) / 2
|
||||||
|
|
||||||
|
C.XMoveResizeWindow(w.x, w.xw, C.int(x), C.int(y), C.uint(sz.X), C.uint(sz.Y))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) raise() {
|
||||||
var xev C.XEvent
|
var xev C.XEvent
|
||||||
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
|
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
|
||||||
*ev = C.XClientMessageEvent{
|
*ev = C.XClientMessageEvent{
|
||||||
@@ -321,8 +335,8 @@ func (w *x11Window) SetInputHint(_ key.InputHint) {}
|
|||||||
|
|
||||||
func (w *x11Window) EditorStateChanged(old, new editorState) {}
|
func (w *x11Window) EditorStateChanged(old, new editorState) {}
|
||||||
|
|
||||||
// Close the window.
|
// close the window.
|
||||||
func (w *x11Window) Close() {
|
func (w *x11Window) close() {
|
||||||
var xev C.XEvent
|
var xev C.XEvent
|
||||||
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
|
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
|
||||||
*ev = C.XClientMessageEvent{
|
*ev = C.XClientMessageEvent{
|
||||||
@@ -366,7 +380,36 @@ func (w *x11Window) sendWMStateEvent(action C.long, atom1, atom2 C.ulong) {
|
|||||||
|
|
||||||
var x11OneByte = make([]byte, 1)
|
var x11OneByte = make([]byte, 1)
|
||||||
|
|
||||||
func (w *x11Window) Wakeup() {
|
func (w *x11Window) ProcessEvent(e event.Event) {
|
||||||
|
w.w.ProcessEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) shutdown(err error) {
|
||||||
|
w.ProcessEvent(X11ViewEvent{})
|
||||||
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
|
w.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) Event() event.Event {
|
||||||
|
for {
|
||||||
|
evt, ok := w.w.nextEvent()
|
||||||
|
if !ok {
|
||||||
|
w.dispatch()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) Run(f func()) {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) Frame(frame *op.Ops) {
|
||||||
|
w.w.ProcessFrame(frame, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) Invalidate() {
|
||||||
select {
|
select {
|
||||||
case w.wakeups <- struct{}{}:
|
case w.wakeups <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
@@ -384,16 +427,20 @@ func (w *x11Window) window() (C.Window, int, int) {
|
|||||||
return w.xw, w.config.Size.X, w.config.Size.Y
|
return w.xw, w.config.Size.X, w.config.Size.Y
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) setStage(s system.Stage) {
|
func (w *x11Window) dispatch() {
|
||||||
if s == w.stage {
|
if w.x == nil {
|
||||||
|
// Only Invalidate can wake us up.
|
||||||
|
<-w.wakeups
|
||||||
|
w.w.Invalidate()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.stage = s
|
|
||||||
w.w.Event(system.StageEvent{Stage: s})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *x11Window) loop() {
|
select {
|
||||||
h := x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
case <-w.wakeups:
|
||||||
|
w.w.Invalidate()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
xfd := C.XConnectionNumber(w.x)
|
xfd := C.XConnectionNumber(w.x)
|
||||||
|
|
||||||
// Poll for events and notifications.
|
// Poll for events and notifications.
|
||||||
@@ -403,62 +450,55 @@ func (w *x11Window) loop() {
|
|||||||
}
|
}
|
||||||
xEvents := &pollfds[0].Revents
|
xEvents := &pollfds[0].Revents
|
||||||
// Plenty of room for a backlog of notifications.
|
// Plenty of room for a backlog of notifications.
|
||||||
buf := make([]byte, 100)
|
|
||||||
|
|
||||||
loop:
|
var syn, anim bool
|
||||||
for !w.dead {
|
// Check for pending draw events before checking animation or blocking.
|
||||||
var syn, anim bool
|
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||||
// Check for pending draw events before checking animation or blocking.
|
// poll will still block. This also prevents no-op calls to poll.
|
||||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
syn = w.handler.handleEvents()
|
||||||
// poll will still block. This also prevents no-op calls to poll.
|
if w.x == nil {
|
||||||
if syn = h.handleEvents(); !syn {
|
// handleEvents received a close request and destroyed the window.
|
||||||
anim = w.animating
|
return
|
||||||
if !anim {
|
}
|
||||||
// Clear poll events.
|
if !syn {
|
||||||
*xEvents = 0
|
anim = w.animating
|
||||||
// Wait for X event or gio notification.
|
if !anim {
|
||||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
// Clear poll events.
|
||||||
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
*xEvents = 0
|
||||||
}
|
// Wait for X event or gio notification.
|
||||||
switch {
|
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||||
case *xEvents&syscall.POLLIN != 0:
|
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
||||||
syn = h.handleEvents()
|
}
|
||||||
if w.dead {
|
switch {
|
||||||
break loop
|
case *xEvents&syscall.POLLIN != 0:
|
||||||
}
|
syn = w.handler.handleEvents()
|
||||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
if w.x == nil {
|
||||||
break loop
|
return
|
||||||
}
|
}
|
||||||
|
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Clear notifications.
|
|
||||||
for {
|
|
||||||
_, err := syscall.Read(w.notify.read, buf)
|
|
||||||
if err == syscall.EAGAIN {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-w.wakeups:
|
|
||||||
w.w.Event(wakeupEvent{})
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
|
||||||
w.w.Event(frameEvent{
|
|
||||||
FrameEvent: system.FrameEvent{
|
|
||||||
Now: time.Now(),
|
|
||||||
Size: w.config.Size,
|
|
||||||
Metric: w.metric,
|
|
||||||
},
|
|
||||||
Sync: syn,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
w.w.Event(system.DestroyEvent{Err: nil})
|
// Clear notifications.
|
||||||
|
for {
|
||||||
|
_, err := syscall.Read(w.notify.read, w.buf[:])
|
||||||
|
if err == syscall.EAGAIN {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
||||||
|
w.ProcessEvent(frameEvent{
|
||||||
|
FrameEvent: FrameEvent{
|
||||||
|
Now: time.Now(),
|
||||||
|
Size: w.config.Size,
|
||||||
|
Metric: w.metric,
|
||||||
|
},
|
||||||
|
Sync: syn,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) destroy() {
|
func (w *x11Window) destroy() {
|
||||||
@@ -476,11 +516,11 @@ func (w *x11Window) destroy() {
|
|||||||
}
|
}
|
||||||
C.XDestroyWindow(w.x, w.xw)
|
C.XDestroyWindow(w.x, w.xw)
|
||||||
C.XCloseDisplay(w.x)
|
C.XCloseDisplay(w.x)
|
||||||
|
w.x = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// atom is a wrapper around XInternAtom. Callers should cache the result
|
// atom is a wrapper around XInternAtom. Callers should cache the result
|
||||||
// in order to limit round-trips to the X server.
|
// in order to limit round-trips to the X server.
|
||||||
//
|
|
||||||
func (w *x11Window) atom(name string, onlyIfExists bool) C.Atom {
|
func (w *x11Window) atom(name string, onlyIfExists bool) C.Atom {
|
||||||
cname := C.CString(name)
|
cname := C.CString(name)
|
||||||
defer C.free(unsafe.Pointer(cname))
|
defer C.free(unsafe.Pointer(cname))
|
||||||
@@ -494,7 +534,6 @@ func (w *x11Window) atom(name string, onlyIfExists bool) C.Atom {
|
|||||||
// x11EventHandler wraps static variables for the main event loop.
|
// x11EventHandler wraps static variables for the main event loop.
|
||||||
// Its sole purpose is to prevent heap allocation and reduce clutter
|
// Its sole purpose is to prevent heap allocation and reduce clutter
|
||||||
// in x11window.loop.
|
// in x11window.loop.
|
||||||
//
|
|
||||||
type x11EventHandler struct {
|
type x11EventHandler struct {
|
||||||
w *x11Window
|
w *x11Window
|
||||||
text []byte
|
text []byte
|
||||||
@@ -502,7 +541,6 @@ type x11EventHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleEvents returns true if the window needs to be redrawn.
|
// handleEvents returns true if the window needs to be redrawn.
|
||||||
//
|
|
||||||
func (h *x11EventHandler) handleEvents() bool {
|
func (h *x11EventHandler) handleEvents() bool {
|
||||||
w := h.w
|
w := h.w
|
||||||
xev := h.xev
|
xev := h.xev
|
||||||
@@ -536,13 +574,13 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
// There's no support for IME yet.
|
// There's no support for IME yet.
|
||||||
w.w.EditorInsert(ee.Text)
|
w.w.EditorInsert(ee.Text)
|
||||||
} else {
|
} else {
|
||||||
w.w.Event(e)
|
w.ProcessEvent(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case C.ButtonPress, C.ButtonRelease:
|
case C.ButtonPress, C.ButtonRelease:
|
||||||
bevt := (*C.XButtonEvent)(unsafe.Pointer(xev))
|
bevt := (*C.XButtonEvent)(unsafe.Pointer(xev))
|
||||||
ev := pointer.Event{
|
ev := pointer.Event{
|
||||||
Type: pointer.Press,
|
Kind: pointer.Press,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Position: f32.Point{
|
Position: f32.Point{
|
||||||
X: float32(bevt.x),
|
X: float32(bevt.x),
|
||||||
@@ -552,7 +590,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
Modifiers: w.xkb.Modifiers(),
|
Modifiers: w.xkb.Modifiers(),
|
||||||
}
|
}
|
||||||
if bevt._type == C.ButtonRelease {
|
if bevt._type == C.ButtonRelease {
|
||||||
ev.Type = pointer.Release
|
ev.Kind = pointer.Release
|
||||||
}
|
}
|
||||||
var btn pointer.Buttons
|
var btn pointer.Buttons
|
||||||
const scrollScale = 10
|
const scrollScale = 10
|
||||||
@@ -564,21 +602,29 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
case C.Button3:
|
case C.Button3:
|
||||||
btn = pointer.ButtonSecondary
|
btn = pointer.ButtonSecondary
|
||||||
case C.Button4:
|
case C.Button4:
|
||||||
// scroll up
|
ev.Kind = pointer.Scroll
|
||||||
ev.Type = pointer.Scroll
|
// scroll up or left (if shift is pressed).
|
||||||
ev.Scroll.Y = -scrollScale
|
if ev.Modifiers == key.ModShift {
|
||||||
|
ev.Scroll.X = -scrollScale
|
||||||
|
} else {
|
||||||
|
ev.Scroll.Y = -scrollScale
|
||||||
|
}
|
||||||
case C.Button5:
|
case C.Button5:
|
||||||
// scroll down
|
// scroll down or right (if shift is pressed).
|
||||||
ev.Type = pointer.Scroll
|
ev.Kind = pointer.Scroll
|
||||||
ev.Scroll.Y = +scrollScale
|
if ev.Modifiers == key.ModShift {
|
||||||
|
ev.Scroll.X = +scrollScale
|
||||||
|
} else {
|
||||||
|
ev.Scroll.Y = +scrollScale
|
||||||
|
}
|
||||||
case 6:
|
case 6:
|
||||||
// http://xahlee.info/linux/linux_x11_mouse_button_number.html
|
// http://xahlee.info/linux/linux_x11_mouse_button_number.html
|
||||||
// scroll left
|
// scroll left.
|
||||||
ev.Type = pointer.Scroll
|
ev.Kind = pointer.Scroll
|
||||||
ev.Scroll.X = -scrollScale * 2
|
ev.Scroll.X = -scrollScale * 2
|
||||||
case 7:
|
case 7:
|
||||||
// scroll right
|
// scroll right
|
||||||
ev.Type = pointer.Scroll
|
ev.Kind = pointer.Scroll
|
||||||
ev.Scroll.X = +scrollScale * 2
|
ev.Scroll.X = +scrollScale * 2
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
@@ -590,11 +636,11 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
w.pointerBtns &^= btn
|
w.pointerBtns &^= btn
|
||||||
}
|
}
|
||||||
ev.Buttons = w.pointerBtns
|
ev.Buttons = w.pointerBtns
|
||||||
w.w.Event(ev)
|
w.ProcessEvent(ev)
|
||||||
case C.MotionNotify:
|
case C.MotionNotify:
|
||||||
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Type: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
Position: f32.Point{
|
Position: f32.Point{
|
||||||
@@ -608,14 +654,16 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
// redraw only on the last expose event
|
// redraw only on the last expose event
|
||||||
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
||||||
case C.FocusIn:
|
case C.FocusIn:
|
||||||
w.w.Event(key.FocusEvent{Focus: true})
|
w.config.Focused = true
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
case C.FocusOut:
|
case C.FocusOut:
|
||||||
w.w.Event(key.FocusEvent{Focus: false})
|
w.config.Focused = false
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
case C.ConfigureNotify: // window configuration change
|
case C.ConfigureNotify: // window configuration change
|
||||||
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
||||||
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
||||||
w.config.Size = sz
|
w.config.Size = sz
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
// redraw will be done by a later expose event
|
// redraw will be done by a later expose event
|
||||||
case C.SelectionNotify:
|
case C.SelectionNotify:
|
||||||
@@ -637,7 +685,12 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
||||||
w.w.Event(clipboard.Event{Text: str})
|
w.ProcessEvent(transfer.DataEvent{
|
||||||
|
Type: "application/text",
|
||||||
|
Open: func() io.ReadCloser {
|
||||||
|
return io.NopCloser(strings.NewReader(str))
|
||||||
|
},
|
||||||
|
})
|
||||||
case C.SelectionRequest:
|
case C.SelectionRequest:
|
||||||
cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
|
||||||
if (cevt.selection != w.atoms.clipboard && cevt.selection != w.atoms.primary) || cevt.property == C.None {
|
if (cevt.selection != w.atoms.clipboard && cevt.selection != w.atoms.primary) || cevt.property == C.None {
|
||||||
@@ -691,7 +744,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
||||||
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
||||||
case C.long(w.atoms.evDelWindow):
|
case C.long(w.atoms.evDelWindow):
|
||||||
w.dead = true
|
w.shutdown(nil)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -699,9 +752,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
return redraw
|
return redraw
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var x11Threads sync.Once
|
||||||
x11Threads sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
x11Driver = newX11Window
|
x11Driver = newX11Window
|
||||||
@@ -773,8 +824,10 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
|||||||
wakeups: make(chan struct{}, 1),
|
wakeups: make(chan struct{}, 1),
|
||||||
config: Config{Size: cnf.Size},
|
config: Config{Size: cnf.Size},
|
||||||
}
|
}
|
||||||
|
w.handler = x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||||
w.notify.read = pipe[0]
|
w.notify.read = pipe[0]
|
||||||
w.notify.write = pipe[1]
|
w.notify.write = pipe[1]
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
|
||||||
if err := w.updateXkbKeymap(); err != nil {
|
if err := w.updateXkbKeymap(); err != nil {
|
||||||
w.destroy()
|
w.destroy()
|
||||||
@@ -786,7 +839,7 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
|||||||
hints.flags = C.InputHint
|
hints.flags = C.InputHint
|
||||||
C.XSetWMHints(dpy, win, &hints)
|
C.XSetWMHints(dpy, win, &hints)
|
||||||
|
|
||||||
name := C.CString(filepath.Base(os.Args[0]))
|
name := C.CString(ID)
|
||||||
defer C.free(unsafe.Pointer(name))
|
defer C.free(unsafe.Pointer(name))
|
||||||
wmhints := C.XClassHint{name, name}
|
wmhints := C.XClassHint{name, name}
|
||||||
C.XSetClassHint(dpy, win, &wmhints)
|
C.XSetClassHint(dpy, win, &wmhints)
|
||||||
@@ -810,18 +863,10 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
|||||||
// extensions
|
// extensions
|
||||||
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
||||||
|
|
||||||
go func() {
|
// make the window visible on the screen
|
||||||
w.w.SetDriver(w)
|
C.XMapWindow(dpy, win)
|
||||||
|
w.Configure(options)
|
||||||
// make the window visible on the screen
|
w.ProcessEvent(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
||||||
C.XMapWindow(dpy, win)
|
|
||||||
w.Configure(options)
|
|
||||||
w.w.Event(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
|
||||||
w.setStage(system.StageRunning)
|
|
||||||
w.loop()
|
|
||||||
w.w.Event(X11ViewEvent{})
|
|
||||||
w.destroy()
|
|
||||||
}()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,15 @@
|
|||||||
Package bluetooth implements permissions to access Bluetooth and Bluetooth
|
Package bluetooth implements permissions to access Bluetooth and Bluetooth
|
||||||
Low Energy hardware, including the ability to discover and pair devices.
|
Low Energy hardware, including the ability to discover and pair devices.
|
||||||
|
|
||||||
Android
|
# Android
|
||||||
|
|
||||||
The following entries will be added to AndroidManifest.xml:
|
The following entries will be added to AndroidManifest.xml:
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH"/>
|
<uses-permission android:name="android.permission.BLUETOOTH"/>
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||||
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
|
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
|
||||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
|
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
|
||||||
|
|
||||||
Note that ACCESS_FINE_LOCATION is required on Android before the Bluetooth
|
Note that ACCESS_FINE_LOCATION is required on Android before the Bluetooth
|
||||||
device may be used.
|
device may be used.
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
/*
|
/*
|
||||||
Package camera implements permissions to access camera hardware.
|
Package camera implements permissions to access camera hardware.
|
||||||
|
|
||||||
Android
|
# Android
|
||||||
|
|
||||||
The following entries will be added to AndroidManifest.xml:
|
The following entries will be added to AndroidManifest.xml:
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||||
|
|
||||||
CAMERA is a "dangerous" permission. See documentation for package
|
CAMERA is a "dangerous" permission. See documentation for package
|
||||||
gioui.org/app/permission for more information.
|
gioui.org/app/permission for more information.
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ program's source code:
|
|||||||
_ "net"
|
_ "net"
|
||||||
)
|
)
|
||||||
|
|
||||||
Android -- Dangerous Permissions
|
# Android -- Dangerous Permissions
|
||||||
|
|
||||||
Certain permissions on Android are marked with a protection level of
|
Certain permissions on Android are marked with a protection level of
|
||||||
"dangerous". This means that, in addition to including the relevant
|
"dangerous". This means that, in addition to including the relevant
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package microphone implements permissions to access microphone hardware.
|
||||||
|
|
||||||
|
# Android
|
||||||
|
|
||||||
|
The following entries will be added to AndroidManifest.xml:
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||||
|
|
||||||
|
RECORD_AUDIO is a "dangerous" permission. See documentation for package
|
||||||
|
gioui.org/app/permission for more information.
|
||||||
|
*/
|
||||||
|
package microphone
|
||||||
@@ -3,11 +3,10 @@
|
|||||||
/*
|
/*
|
||||||
Package networkstate implements permissions to access network connectivity information.
|
Package networkstate implements permissions to access network connectivity information.
|
||||||
|
|
||||||
Android
|
# Android
|
||||||
|
|
||||||
The following entries will be added to AndroidManifest.xml:
|
The following entries will be added to AndroidManifest.xml:
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package networkstate
|
package networkstate
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
Package storage implements read and write storage permissions
|
Package storage implements read and write storage permissions
|
||||||
on mobile devices.
|
on mobile devices.
|
||||||
|
|
||||||
Android
|
# Android
|
||||||
|
|
||||||
The following entries will be added to AndroidManifest.xml:
|
The following entries will be added to AndroidManifest.xml:
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE are "dangerous" permissions.
|
READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE are "dangerous" permissions.
|
||||||
See documentation for package gioui.org/app/permission for more information.
|
See documentation for package gioui.org/app/permission for more information.
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package wakelock implements permission to acquire locks that keep the system
|
||||||
|
from suspending.
|
||||||
|
|
||||||
|
# Android
|
||||||
|
|
||||||
|
The following entries will be added to AndroidManifest.xml:
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
|
*/
|
||||||
|
package wakelock
|
||||||
+1
-2
@@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
//go:build android || (darwin && ios)
|
//go:build android || (darwin && ios)
|
||||||
// +build android darwin,ios
|
|
||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
@@ -25,6 +24,6 @@ func runMain() {
|
|||||||
// Indirect call, since the linker does not know the address of main when
|
// Indirect call, since the linker does not know the address of main when
|
||||||
// laying down this package.
|
// laying down this package.
|
||||||
fn := mainMain
|
fn := mainMain
|
||||||
fn()
|
go fn()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
// DestroyEvent is the last event sent through
|
||||||
|
// a window event channel.
|
||||||
|
type DestroyEvent struct {
|
||||||
|
// Err is nil for normal window closures. If a
|
||||||
|
// window is prematurely closed, Err is the cause.
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DestroyEvent) ImplementsEvent() {}
|
||||||
+1
-1
@@ -175,7 +175,7 @@ func (c *vkContext) refresh(surf vk.Surface, width, height int) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
minExt, maxExt := caps.MinExtent(), caps.MaxExtent()
|
minExt, maxExt := vk.SurfaceCapabilitiesMinExtent(caps), vk.SurfaceCapabilitiesMaxExtent(caps)
|
||||||
if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height {
|
if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height {
|
||||||
return errOutOfDate
|
return errOutOfDate
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
//go:build !novulkan
|
//go:build !novulkan
|
||||||
// +build !novulkan
|
|
||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
|
|||||||
+515
-622
File diff suppressed because it is too large
Load Diff
-14
@@ -1,14 +0,0 @@
|
|||||||
module gioui.org/cmd
|
|
||||||
|
|
||||||
go 1.13
|
|
||||||
|
|
||||||
require (
|
|
||||||
gioui.org v0.0.0-20220328154813-a3f147541fd0
|
|
||||||
github.com/akavel/rsrc v0.10.1
|
|
||||||
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4
|
|
||||||
github.com/chromedp/chromedp v0.5.2
|
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
|
||||||
golang.org/x/text v0.3.6
|
|
||||||
golang.org/x/tools v0.1.0
|
|
||||||
)
|
|
||||||
-475
@@ -1,475 +0,0 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
|
||||||
eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
|
||||||
gioui.org v0.0.0-20220328154813-a3f147541fd0 h1:n4FUiCT6P4a2wF6hwX4a5R8TpjAhu/d+3nhwZW16MAI=
|
|
||||||
gioui.org v0.0.0-20220328154813-a3f147541fd0/go.mod h1:b8vBukexG6eYuXZa14asjLAWJ+JjbZ/ophEnS2FjYUg=
|
|
||||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
|
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
|
||||||
gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y=
|
|
||||||
gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
|
||||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
|
||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
|
||||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
|
||||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
|
||||||
github.com/akavel/rsrc v0.10.1 h1:hCCPImjmFKVNGpeLZyTDRHEFC283DzyTXTo0cO0Rq9o=
|
|
||||||
github.com/akavel/rsrc v0.10.1/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
|
||||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
|
||||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
|
||||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
|
||||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
|
||||||
github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE=
|
|
||||||
github.com/benoitkugler/textlayout v0.0.5/go.mod h1:puH4v13Uz7uIhIH0XMk5jgc8U3MXcn5r3VlV9K8n0D8=
|
|
||||||
github.com/benoitkugler/textlayout v0.0.10 h1:uIaQgH4pBFw1LQ0tPkfjgxo94WYcckzzQaB41L2X84w=
|
|
||||||
github.com/benoitkugler/textlayout v0.0.10/go.mod h1:puH4v13Uz7uIhIH0XMk5jgc8U3MXcn5r3VlV9K8n0D8=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
|
||||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4 h1:QD3KxSJ59L2lxG6MXBjNHxiQO2RmxTQ3XcK+wO44WOg=
|
|
||||||
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
|
|
||||||
github.com/chromedp/chromedp v0.5.2 h1:W8xBXQuUnd2dZK0SN/lyVwsQM7KgW+kY5HGnntms194=
|
|
||||||
github.com/chromedp/chromedp v0.5.2/go.mod h1:rsTo/xRo23KZZwFmWk2Ui79rBaVRRATCjLzNQlOFSiA=
|
|
||||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
|
|
||||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
|
||||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
||||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
|
||||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
|
||||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
|
||||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
|
||||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
|
||||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12 h1:1bjaB/5IIicfKpP4k0s30T2WEw//Kh00zULa8DQ0cxA=
|
|
||||||
github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12/go.mod h1:kDhBRTA/i3H46PVdhqcw26TdGSIj42TOKNWKY+Kipnw=
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
|
||||||
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
|
||||||
github.com/go-text/typesetting v0.0.0-20220112121102-58fe93c84506 h1:1TPz/Gn/MsXwJ6bEtI9wdkPcQYr2X3V9I+wz4wPYUdY=
|
|
||||||
github.com/go-text/typesetting v0.0.0-20220112121102-58fe93c84506/go.mod h1:R0mlTNeyszZ/tKQhbZA7SRGjx+OHsmNzgN2jTV7yZcs=
|
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
|
||||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
|
||||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
|
||||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
|
||||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
|
||||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
|
||||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
|
||||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
|
||||||
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
|
||||||
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
|
||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
||||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
|
||||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
|
|
||||||
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
|
||||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
|
||||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
|
||||||
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
|
|
||||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
|
||||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
|
||||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
|
||||||
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
|
||||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
|
||||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
|
||||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
|
||||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
|
||||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
|
||||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
|
||||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
|
||||||
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
|
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
|
||||||
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
|
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
|
||||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
|
||||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
|
||||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
|
||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
|
||||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
|
||||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
|
||||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
|
||||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
|
||||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
|
||||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
|
||||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
|
||||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
|
||||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
|
||||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
|
||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
|
||||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
|
||||||
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
|
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
|
||||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
|
||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
|
||||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
|
||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
|
||||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
|
||||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
|
||||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
|
||||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
|
||||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
|
||||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
|
||||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
|
||||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
|
||||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
|
|
||||||
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
|
|
||||||
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
|
|
||||||
go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
|
|
||||||
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
|
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
|
||||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
|
||||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
|
||||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
|
||||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
|
||||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|
||||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
|
||||||
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
|
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
|
||||||
golang.org/x/exp v0.0.0-20210722180016-6781d3edade3 h1:IlrJD2AM5p8JhN/wVny9jt6gJ9hut2VALhSeZ3SYluk=
|
|
||||||
golang.org/x/exp v0.0.0-20210722180016-6781d3edade3/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
|
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
||||||
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
|
|
||||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
|
||||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package main_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/png"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AndroidTestDriver struct {
|
|
||||||
driverBase
|
|
||||||
|
|
||||||
sdkDir string
|
|
||||||
adbPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
var rxAdbDevice = regexp.MustCompile(`(.*)\s+device$`)
|
|
||||||
|
|
||||||
func (d *AndroidTestDriver) Start(path string) {
|
|
||||||
d.sdkDir = os.Getenv("ANDROID_SDK_ROOT")
|
|
||||||
if d.sdkDir == "" {
|
|
||||||
d.Skipf("Android SDK is required; set $ANDROID_SDK_ROOT")
|
|
||||||
}
|
|
||||||
d.adbPath = filepath.Join(d.sdkDir, "platform-tools", "adb")
|
|
||||||
if _, err := os.Stat(d.adbPath); os.IsNotExist(err) {
|
|
||||||
d.Skipf("adb not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
devOut := bytes.TrimSpace(d.adb("devices"))
|
|
||||||
devices := rxAdbDevice.FindAllSubmatch(devOut, -1)
|
|
||||||
switch len(devices) {
|
|
||||||
case 0:
|
|
||||||
d.Skipf("no Android devices attached via adb; skipping")
|
|
||||||
case 1:
|
|
||||||
default:
|
|
||||||
d.Skipf("multiple Android devices attached via adb; skipping")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the device is attached but asleep, it's probably just charging.
|
|
||||||
// Don't use it; the screen needs to be on and unlocked for the test to
|
|
||||||
// work.
|
|
||||||
if !bytes.Contains(
|
|
||||||
d.adb("shell", "dumpsys", "power"),
|
|
||||||
[]byte(" mWakefulness=Awake"),
|
|
||||||
) {
|
|
||||||
d.Skipf("Android device isn't awake; skipping")
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, build the app.
|
|
||||||
apk := filepath.Join(d.tempDir("gio-endtoend-android"), "e2e.apk")
|
|
||||||
d.gogio("-target=android", "-appid="+appid, "-o="+apk, path)
|
|
||||||
|
|
||||||
// Make sure the app isn't installed already, and try to uninstall it
|
|
||||||
// when we finish. Previous failed test runs might have left the app.
|
|
||||||
d.tryUninstall()
|
|
||||||
d.adb("install", apk)
|
|
||||||
d.Cleanup(d.tryUninstall)
|
|
||||||
|
|
||||||
// Force our e2e app to be fullscreen, so that the android system bar at
|
|
||||||
// the top doesn't mess with our screenshots.
|
|
||||||
// TODO(mvdan): is there a way to do this via gio, so that we don't need
|
|
||||||
// to set up a global Android setting via the shell?
|
|
||||||
d.adb("shell", "settings", "put", "global", "policy_control", "immersive.full="+appid)
|
|
||||||
|
|
||||||
// Make sure the app isn't already running.
|
|
||||||
d.adb("shell", "pm", "clear", appid)
|
|
||||||
|
|
||||||
// Start listening for log messages.
|
|
||||||
{
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
cmd := exec.CommandContext(ctx, d.adbPath,
|
|
||||||
"logcat",
|
|
||||||
"-s", // suppress other logs
|
|
||||||
"-T1", // don't show previous log messages
|
|
||||||
appid+":*", // show all logs from our gio app ID
|
|
||||||
)
|
|
||||||
output, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
cmd.Stderr = cmd.Stdout
|
|
||||||
d.output = output
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
d.Cleanup(cancel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the app.
|
|
||||||
d.adb("shell", "monkey", "-p", appid, "1")
|
|
||||||
|
|
||||||
// Wait for the gio app to render.
|
|
||||||
d.waitForFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AndroidTestDriver) Screenshot() image.Image {
|
|
||||||
out := d.adb("shell", "screencap", "-p")
|
|
||||||
img, err := png.Decode(bytes.NewReader(out))
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AndroidTestDriver) tryUninstall() {
|
|
||||||
cmd := exec.Command(d.adbPath, "shell", "pm", "uninstall", appid)
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
if bytes.Contains(out, []byte("Unknown package")) {
|
|
||||||
// The package is not installed. Don't log anything.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.Logf("could not uninstall: %v\n%s", err, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AndroidTestDriver) adb(args ...interface{}) []byte {
|
|
||||||
strs := []string{}
|
|
||||||
for _, arg := range args {
|
|
||||||
strs = append(strs, fmt.Sprint(arg))
|
|
||||||
}
|
|
||||||
cmd := exec.Command(d.adbPath, strs...)
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
d.Errorf("%s", out)
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AndroidTestDriver) Click(x, y int) {
|
|
||||||
d.adb("shell", "input", "tap", x, y)
|
|
||||||
|
|
||||||
// Wait for the gio app to render after this click.
|
|
||||||
d.waitForFrame()
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,156 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type buildInfo struct {
|
|
||||||
appID string
|
|
||||||
archs []string
|
|
||||||
ldflags string
|
|
||||||
minsdk int
|
|
||||||
name string
|
|
||||||
pkgDir string
|
|
||||||
pkgPath string
|
|
||||||
iconPath string
|
|
||||||
tags string
|
|
||||||
target string
|
|
||||||
version int
|
|
||||||
key string
|
|
||||||
password string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBuildInfo(pkgPath string) (*buildInfo, error) {
|
|
||||||
pkgMetadata, err := getPkgMetadata(pkgPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
appID := getAppID(pkgMetadata)
|
|
||||||
appIcon := filepath.Join(pkgMetadata.Dir, "appicon.png")
|
|
||||||
if *iconPath != "" {
|
|
||||||
appIcon = *iconPath
|
|
||||||
}
|
|
||||||
bi := &buildInfo{
|
|
||||||
appID: appID,
|
|
||||||
archs: getArchs(),
|
|
||||||
ldflags: getLdFlags(appID),
|
|
||||||
minsdk: *minsdk,
|
|
||||||
name: getPkgName(pkgMetadata),
|
|
||||||
pkgDir: pkgMetadata.Dir,
|
|
||||||
pkgPath: pkgPath,
|
|
||||||
iconPath: appIcon,
|
|
||||||
tags: *extraTags,
|
|
||||||
target: *target,
|
|
||||||
version: *version,
|
|
||||||
key: *signKey,
|
|
||||||
password: *signPass,
|
|
||||||
}
|
|
||||||
return bi, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getArchs() []string {
|
|
||||||
if *archNames != "" {
|
|
||||||
return strings.Split(*archNames, ",")
|
|
||||||
}
|
|
||||||
switch *target {
|
|
||||||
case "js":
|
|
||||||
return []string{"wasm"}
|
|
||||||
case "ios", "tvos":
|
|
||||||
// Only 64-bit support.
|
|
||||||
return []string{"arm64", "amd64"}
|
|
||||||
case "android":
|
|
||||||
return []string{"arm", "arm64", "386", "amd64"}
|
|
||||||
case "windows":
|
|
||||||
goarch := os.Getenv("GOARCH")
|
|
||||||
if goarch == "" {
|
|
||||||
goarch = runtime.GOARCH
|
|
||||||
}
|
|
||||||
return []string{goarch}
|
|
||||||
default:
|
|
||||||
// TODO: Add flag tests.
|
|
||||||
panic("The target value has already been validated, this will never execute.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLdFlags(appID string) string {
|
|
||||||
var ldflags []string
|
|
||||||
if extra := *extraLdflags; extra != "" {
|
|
||||||
ldflags = append(ldflags, strings.Split(extra, " ")...)
|
|
||||||
}
|
|
||||||
// Pass appID along, to be used for logging on platforms like Android.
|
|
||||||
ldflags = append(ldflags, fmt.Sprintf("-X gioui.org/app/internal/log.appID=%s", appID))
|
|
||||||
// Pass along all remaining arguments to the app.
|
|
||||||
if appArgs := flag.Args()[1:]; len(appArgs) > 0 {
|
|
||||||
ldflags = append(ldflags, fmt.Sprintf("-X gioui.org/app.extraArgs=%s", strings.Join(appArgs, "|")))
|
|
||||||
}
|
|
||||||
if m := *linkMode; m != "" {
|
|
||||||
ldflags = append(ldflags, "-linkmode="+m)
|
|
||||||
}
|
|
||||||
return strings.Join(ldflags, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
type packageMetadata struct {
|
|
||||||
PkgPath string
|
|
||||||
Dir string
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPkgMetadata(pkgPath string) (*packageMetadata, error) {
|
|
||||||
pkgImportPath, err := runCmd(exec.Command("go", "list", "-f", "{{.ImportPath}}", pkgPath))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pkgDir, err := runCmd(exec.Command("go", "list", "-f", "{{.Dir}}", pkgPath))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &packageMetadata{
|
|
||||||
PkgPath: pkgImportPath,
|
|
||||||
Dir: pkgDir,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAppID(pkgMetadata *packageMetadata) string {
|
|
||||||
if *appID != "" {
|
|
||||||
return *appID
|
|
||||||
}
|
|
||||||
elems := strings.Split(pkgMetadata.PkgPath, "/")
|
|
||||||
domain := strings.Split(elems[0], ".")
|
|
||||||
name := ""
|
|
||||||
if len(elems) > 1 {
|
|
||||||
name = "." + elems[len(elems)-1]
|
|
||||||
}
|
|
||||||
if len(elems) < 2 && len(domain) < 2 {
|
|
||||||
name = "." + domain[0]
|
|
||||||
domain[0] = "localhost"
|
|
||||||
} else {
|
|
||||||
for i := 0; i < len(domain)/2; i++ {
|
|
||||||
opp := len(domain) - 1 - i
|
|
||||||
domain[i], domain[opp] = domain[opp], domain[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgDomain := strings.Join(domain, ".")
|
|
||||||
appid := []rune(pkgDomain + name)
|
|
||||||
|
|
||||||
// a Java-language-style package name may contain upper- and lower-case
|
|
||||||
// letters and underscores with individual parts separated by '.'.
|
|
||||||
// https://developer.android.com/guide/topics/manifest/manifest-element
|
|
||||||
for i, c := range appid {
|
|
||||||
if !('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' ||
|
|
||||||
c == '_' || c == '.') {
|
|
||||||
appid[i] = '_'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(appid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPkgName(pkgMetadata *packageMetadata) string {
|
|
||||||
return path.Base(pkgMetadata.PkgPath)
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
type expval struct {
|
|
||||||
in, out string
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAppID(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []expval{
|
|
||||||
{"example", "localhost.example"},
|
|
||||||
{"example.com", "com.example"},
|
|
||||||
{"www.example.com", "com.example.www"},
|
|
||||||
{"examplecom/app", "examplecom.app"},
|
|
||||||
{"example.com/app", "com.example.app"},
|
|
||||||
{"www.example.com/app", "com.example.www.app"},
|
|
||||||
{"www.en.example.com/app", "com.example.en.www.app"},
|
|
||||||
{"example.com/dir/app", "com.example.app"},
|
|
||||||
{"example.com/dir.ext/app", "com.example.app"},
|
|
||||||
{"example.com/dir/app.ext", "com.example.app.ext"},
|
|
||||||
{"example-com.net/dir/app", "net.example_com.app"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
got := getAppID(&packageMetadata{PkgPath: test.in})
|
|
||||||
if exp := test.out; got != exp {
|
|
||||||
t.Errorf("(%d): expected '%s', got '%s'", i, exp, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
/*
|
|
||||||
The gogio tool builds and packages Gio programs for Android, iOS/tvOS
|
|
||||||
and WebAssembly.
|
|
||||||
|
|
||||||
Run gogio with no arguments for instructions, or see the examples at
|
|
||||||
https://gioui.org.
|
|
||||||
*/
|
|
||||||
package main
|
|
||||||
@@ -1,331 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package main_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var raceEnabled = false
|
|
||||||
|
|
||||||
var headless = flag.Bool("headless", true, "run end-to-end tests in headless mode")
|
|
||||||
|
|
||||||
const appid = "localhost.gogio.endtoend"
|
|
||||||
|
|
||||||
// TestDriver is implemented by each of the platforms we can run end-to-end
|
|
||||||
// tests on. None of its methods return any errors, as the errors are directly
|
|
||||||
// reported to testing.T via methods like Fatal.
|
|
||||||
type TestDriver interface {
|
|
||||||
initBase(t *testing.T, width, height int)
|
|
||||||
|
|
||||||
// Start opens the Gio app found at path. The driver should attempt to
|
|
||||||
// run the app with the base driver's width and height, and the
|
|
||||||
// platform's background should be white.
|
|
||||||
//
|
|
||||||
// When the function returns, the gio app must be ready to use on the
|
|
||||||
// platform, with its initial frame fully drawn.
|
|
||||||
Start(path string)
|
|
||||||
|
|
||||||
// Screenshot takes a screenshot of the Gio app on the platform.
|
|
||||||
Screenshot() image.Image
|
|
||||||
|
|
||||||
// Click performs a pointer click at the specified coordinates,
|
|
||||||
// including both press and release. It returns when the next frame is
|
|
||||||
// fully drawn.
|
|
||||||
Click(x, y int)
|
|
||||||
}
|
|
||||||
|
|
||||||
type driverBase struct {
|
|
||||||
*testing.T
|
|
||||||
|
|
||||||
width, height int
|
|
||||||
|
|
||||||
output io.Reader
|
|
||||||
frameNotifs chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driverBase) initBase(t *testing.T, width, height int) {
|
|
||||||
d.T = t
|
|
||||||
d.width, d.height = width, height
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEndToEnd(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skipf("end-to-end tests tend to be slow")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const (
|
|
||||||
testdataWithGoImportPkgPath = "gioui.org/cmd/gogio/testdata"
|
|
||||||
testdataWithRelativePkgPath = "testdata/testdata.go"
|
|
||||||
)
|
|
||||||
// Keep this list local, to not reuse TestDriver objects.
|
|
||||||
subtests := []struct {
|
|
||||||
name string
|
|
||||||
driver TestDriver
|
|
||||||
pkgPath string
|
|
||||||
}{
|
|
||||||
{"X11 using go import path", &X11TestDriver{}, testdataWithGoImportPkgPath},
|
|
||||||
{"X11", &X11TestDriver{}, testdataWithRelativePkgPath},
|
|
||||||
// Doesn't work on the builders.
|
|
||||||
//{"Wayland", &WaylandTestDriver{}, testdataWithRelativePkgPath},
|
|
||||||
{"JS", &JSTestDriver{}, testdataWithRelativePkgPath},
|
|
||||||
{"Android", &AndroidTestDriver{}, testdataWithRelativePkgPath},
|
|
||||||
{"Windows", &WineTestDriver{}, testdataWithRelativePkgPath},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, subtest := range subtests {
|
|
||||||
t.Run(subtest.name, func(t *testing.T) {
|
|
||||||
subtest := subtest // copy the changing loop variable
|
|
||||||
t.Parallel()
|
|
||||||
runEndToEndTest(t, subtest.driver, subtest.pkgPath)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runEndToEndTest(t *testing.T, driver TestDriver, pkgPath string) {
|
|
||||||
size := image.Point{X: 800, Y: 600}
|
|
||||||
driver.initBase(t, size.X, size.Y)
|
|
||||||
|
|
||||||
t.Log("starting driver and gio app")
|
|
||||||
driver.Start(pkgPath)
|
|
||||||
|
|
||||||
beef := color.NRGBA{R: 0xde, G: 0xad, B: 0xbe, A: 0xff}
|
|
||||||
white := color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
|
|
||||||
black := color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xff}
|
|
||||||
gray := color.NRGBA{R: 0xbb, G: 0xbb, B: 0xbb, A: 0xff}
|
|
||||||
red := color.NRGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff}
|
|
||||||
|
|
||||||
// These are the four colors at the beginning.
|
|
||||||
t.Log("taking initial screenshot")
|
|
||||||
withRetries(t, 4*time.Second, func() error {
|
|
||||||
img := driver.Screenshot()
|
|
||||||
size = img.Bounds().Size() // override the default size
|
|
||||||
return checkImageCorners(img, beef, white, black, gray)
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO(mvdan): implement this properly in the Wayland driver; swaymsg
|
|
||||||
// almost works to automate clicks, but the button presses end up in the
|
|
||||||
// wrong coordinates.
|
|
||||||
if _, ok := driver.(*WaylandTestDriver); ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Click the first and last sections to turn them red.
|
|
||||||
t.Log("clicking twice and taking another screenshot")
|
|
||||||
driver.Click(1*(size.X/4), 1*(size.Y/4))
|
|
||||||
driver.Click(3*(size.X/4), 3*(size.Y/4))
|
|
||||||
withRetries(t, 4*time.Second, func() error {
|
|
||||||
img := driver.Screenshot()
|
|
||||||
return checkImageCorners(img, red, white, black, red)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// withRetries keeps retrying fn until it succeeds, or until the timeout is hit.
|
|
||||||
// It uses a rudimentary kind of backoff, which starts with 100ms delays. As
|
|
||||||
// such, timeout should generally be in the order of seconds.
|
|
||||||
func withRetries(t *testing.T, timeout time.Duration, fn func() error) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
timeoutTimer := time.NewTimer(timeout)
|
|
||||||
defer timeoutTimer.Stop()
|
|
||||||
backoff := 100 * time.Millisecond
|
|
||||||
|
|
||||||
tries := 0
|
|
||||||
var lastErr error
|
|
||||||
for {
|
|
||||||
if lastErr = fn(); lastErr == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tries++
|
|
||||||
t.Logf("retrying after %s", backoff)
|
|
||||||
|
|
||||||
// Use a timer instead of a sleep, so that the timeout can stop
|
|
||||||
// the backoff early. Don't reuse this timer, since we're not in
|
|
||||||
// a hot loop, and we don't want tricky code.
|
|
||||||
backoffTimer := time.NewTimer(backoff)
|
|
||||||
defer backoffTimer.Stop()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-timeoutTimer.C:
|
|
||||||
t.Errorf("last error: %v", lastErr)
|
|
||||||
t.Fatalf("hit timeout of %s after %d tries", timeout, tries)
|
|
||||||
case <-backoffTimer.C:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep doubling it until a maximum. With the start at 100ms,
|
|
||||||
// we'll do: 100ms, 200ms, 400ms, 800ms, 1.6s, and 2s forever.
|
|
||||||
backoff *= 2
|
|
||||||
if max := 2 * time.Second; backoff > max {
|
|
||||||
backoff = max
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type colorMismatch struct {
|
|
||||||
x, y int
|
|
||||||
wantRGB, gotRGB [3]uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m colorMismatch) String() string {
|
|
||||||
return fmt.Sprintf("%3d,%-3d got 0x%04x%04x%04x, want 0x%04x%04x%04x",
|
|
||||||
m.x, m.y,
|
|
||||||
m.gotRGB[0], m.gotRGB[1], m.gotRGB[2],
|
|
||||||
m.wantRGB[0], m.wantRGB[1], m.wantRGB[2],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkImageCorners(img image.Image, topLeft, topRight, botLeft, botRight color.Color) error {
|
|
||||||
// The colors are split in four rectangular sections. Check the corners
|
|
||||||
// of each of the sections. We check the corners left to right, top to
|
|
||||||
// bottom, like when reading left-to-right text.
|
|
||||||
|
|
||||||
size := img.Bounds().Size()
|
|
||||||
var mismatches []colorMismatch
|
|
||||||
|
|
||||||
checkColor := func(x, y int, want color.Color) {
|
|
||||||
r, g, b, _ := want.RGBA()
|
|
||||||
got := img.At(x, y)
|
|
||||||
r_, g_, b_, _ := got.RGBA()
|
|
||||||
if r_ != r || g_ != g || b_ != b {
|
|
||||||
mismatches = append(mismatches, colorMismatch{
|
|
||||||
x: x,
|
|
||||||
y: y,
|
|
||||||
wantRGB: [3]uint32{r, g, b},
|
|
||||||
gotRGB: [3]uint32{r_, g_, b_},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
minX, minY := 5, 5
|
|
||||||
maxX, maxY := (size.X/2)-5, (size.Y/2)-5
|
|
||||||
checkColor(minX, minY, topLeft)
|
|
||||||
checkColor(maxX, minY, topLeft)
|
|
||||||
checkColor(minX, maxY, topLeft)
|
|
||||||
checkColor(maxX, maxY, topLeft)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
minX, minY := (size.X/2)+5, 5
|
|
||||||
maxX, maxY := size.X-5, (size.Y/2)-5
|
|
||||||
checkColor(minX, minY, topRight)
|
|
||||||
checkColor(maxX, minY, topRight)
|
|
||||||
checkColor(minX, maxY, topRight)
|
|
||||||
checkColor(maxX, maxY, topRight)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
minX, minY := 5, (size.Y/2)+5
|
|
||||||
maxX, maxY := (size.X/2)-5, size.Y-5
|
|
||||||
checkColor(minX, minY, botLeft)
|
|
||||||
checkColor(maxX, minY, botLeft)
|
|
||||||
checkColor(minX, maxY, botLeft)
|
|
||||||
checkColor(maxX, maxY, botLeft)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
minX, minY := (size.X/2)+5, (size.Y/2)+5
|
|
||||||
maxX, maxY := size.X-5, size.Y-5
|
|
||||||
checkColor(minX, minY, botRight)
|
|
||||||
checkColor(maxX, minY, botRight)
|
|
||||||
checkColor(minX, maxY, botRight)
|
|
||||||
checkColor(maxX, maxY, botRight)
|
|
||||||
}
|
|
||||||
if n := len(mismatches); n > 0 {
|
|
||||||
b := new(strings.Builder)
|
|
||||||
fmt.Fprintf(b, "encountered %d color mismatches:\n", n)
|
|
||||||
for _, m := range mismatches {
|
|
||||||
fmt.Fprintf(b, "%s\n", m)
|
|
||||||
}
|
|
||||||
return errors.New(b.String())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driverBase) waitForFrame() {
|
|
||||||
d.Helper()
|
|
||||||
|
|
||||||
if d.frameNotifs == nil {
|
|
||||||
// Start the goroutine that reads output lines and notifies of
|
|
||||||
// new frames via frameNotifs. The test doesn't wait for this
|
|
||||||
// goroutine to finish; it will naturally end when the output
|
|
||||||
// reader reaches an error like EOF.
|
|
||||||
d.frameNotifs = make(chan bool, 1)
|
|
||||||
if d.output == nil {
|
|
||||||
d.Fatal("need an output reader to be notified of frames")
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
scanner := bufio.NewScanner(d.output)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
d.Log(line)
|
|
||||||
if strings.Contains(line, "gio frame ready") {
|
|
||||||
d.frameNotifs <- true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Since we're only interested in the output while the
|
|
||||||
// app runs, and we don't know when it finishes here,
|
|
||||||
// ignore "already closed" pipe errors.
|
|
||||||
if err := scanner.Err(); err != nil && !errors.Is(err, os.ErrClosed) {
|
|
||||||
d.Errorf("reading app output: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unfortunately, there isn't a way to select on a test failing, since
|
|
||||||
// testing.T doesn't have anything like a context or a "done" channel.
|
|
||||||
//
|
|
||||||
// We can't let selects block forever, since the default -test.timeout
|
|
||||||
// is ten minutes - far too long for tests that take seconds.
|
|
||||||
//
|
|
||||||
// For now, a static short timeout is better than nothing. 5s is plenty
|
|
||||||
// for our simple test app to render on any device.
|
|
||||||
select {
|
|
||||||
case <-d.frameNotifs:
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
d.Fatalf("timed out waiting for a frame to be ready")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driverBase) needPrograms(names ...string) {
|
|
||||||
d.Helper()
|
|
||||||
for _, name := range names {
|
|
||||||
if _, err := exec.LookPath(name); err != nil {
|
|
||||||
d.Skipf("%s needed to run", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driverBase) tempDir(name string) string {
|
|
||||||
d.Helper()
|
|
||||||
dir, err := ioutil.TempDir("", name)
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
d.Cleanup(func() { os.RemoveAll(dir) })
|
|
||||||
return dir
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driverBase) gogio(args ...string) {
|
|
||||||
d.Helper()
|
|
||||||
prog, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
cmd := exec.Command(prog, args...)
|
|
||||||
cmd.Env = append(os.Environ(), "RUN_GOGIO=1")
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
d.Fatalf("gogio error: %s:\n%s", err, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
const mainUsage = `The gogio command builds and packages Gio (gioui.org) programs.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
gogio -target <target> [flags] <package> [run arguments]
|
|
||||||
|
|
||||||
The gogio tool builds and packages Gio programs for platforms where additional
|
|
||||||
metadata or support files are required.
|
|
||||||
|
|
||||||
The package argument specifies an import path or a single Go source file to
|
|
||||||
package. Any run arguments are appended to os.Args at runtime.
|
|
||||||
|
|
||||||
Compiled Java class files from jar files in the package directory are
|
|
||||||
included in Android builds.
|
|
||||||
|
|
||||||
The mandatory -target flag selects the target platform: ios or android for the
|
|
||||||
mobile platforms, tvos for Apple's tvOS, js for WebAssembly/WebGL.
|
|
||||||
|
|
||||||
The -arch flag specifies a comma separated list of GOARCHs to include. The
|
|
||||||
default is all supported architectures.
|
|
||||||
|
|
||||||
The -o flag specifies an output file or directory, depending on the target.
|
|
||||||
|
|
||||||
The -buildmode flag selects the build mode. Two build modes are available, exe
|
|
||||||
and archive. Buildmode exe outputs an .ipa file for iOS or tvOS, an .apk file
|
|
||||||
for Android or a directory with the WebAssembly module and support files for
|
|
||||||
a browser.
|
|
||||||
|
|
||||||
The -ldflags and -tags flags pass extra linker flags and tags to the go tool.
|
|
||||||
|
|
||||||
As a special case for iOS or tvOS, specifying a path that ends with ".app"
|
|
||||||
will output an app directory suitable for a simulator.
|
|
||||||
|
|
||||||
The other buildmode is archive, which will output an .aar library for Android
|
|
||||||
or a .framework for iOS and tvOS.
|
|
||||||
|
|
||||||
The -icon flag specifies a path to a PNG image to use as app icon on iOS and Android.
|
|
||||||
If left unspecified, the appicon.png file from the main package is used
|
|
||||||
(if it exists).
|
|
||||||
|
|
||||||
The -appid flag specifies the package name for Android or the bundle id for
|
|
||||||
iOS and tvOS. A bundle id must be provisioned through Xcode before the gogio
|
|
||||||
tool can use it.
|
|
||||||
|
|
||||||
The -version flag specifies the integer version code for Android and the last
|
|
||||||
component of the 1.0.X version for iOS and tvOS.
|
|
||||||
|
|
||||||
For Android builds the -minsdk flag specify the minimum SDK level. For example,
|
|
||||||
use -minsdk 22 to target Android 5.1 (Lollipop) and later.
|
|
||||||
|
|
||||||
For Windows builds the -minsdk flag specify the minimum OS version. For example,
|
|
||||||
use -mindk 10 to target Windows 10 and later, -minsdk 6 for Windows Vista and later.
|
|
||||||
|
|
||||||
For iOS builds the -minsdk flag specify the minimum iOS version. For example,
|
|
||||||
use -mindk 15 to target iOS 15.0 and later.
|
|
||||||
|
|
||||||
The -work flag prints the path to the working directory and suppress
|
|
||||||
its deletion.
|
|
||||||
|
|
||||||
The -x flag will print all the external commands executed by the gogio tool.
|
|
||||||
|
|
||||||
The -signkey flag specifies the path of the keystore, used for signing Android apk/aab files.
|
|
||||||
|
|
||||||
The -signpass flag specifies the password of the keystore, ignored if -signkey is not provided.
|
|
||||||
`
|
|
||||||
@@ -1,589 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
minIOSVersion = 10
|
|
||||||
// Metal is available from iOS 8 on devices, yet from version 13 on the
|
|
||||||
// simulator.
|
|
||||||
minSimulatorVersion = 13
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildIOS(tmpDir, target string, bi *buildInfo) error {
|
|
||||||
appName := bi.name
|
|
||||||
switch *buildMode {
|
|
||||||
case "archive":
|
|
||||||
framework := *destPath
|
|
||||||
if framework == "" {
|
|
||||||
framework = fmt.Sprintf("%s.framework", strings.Title(appName))
|
|
||||||
}
|
|
||||||
return archiveIOS(tmpDir, target, framework, bi)
|
|
||||||
case "exe":
|
|
||||||
out := *destPath
|
|
||||||
if out == "" {
|
|
||||||
out = appName + ".ipa"
|
|
||||||
}
|
|
||||||
forDevice := strings.HasSuffix(out, ".ipa")
|
|
||||||
// Filter out unsupported architectures.
|
|
||||||
for i := len(bi.archs) - 1; i >= 0; i-- {
|
|
||||||
switch bi.archs[i] {
|
|
||||||
case "arm", "arm64":
|
|
||||||
if forDevice {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case "386", "amd64":
|
|
||||||
if !forDevice {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bi.archs = append(bi.archs[:i], bi.archs[i+1:]...)
|
|
||||||
}
|
|
||||||
tmpFramework := filepath.Join(tmpDir, "Gio.framework")
|
|
||||||
if err := archiveIOS(tmpDir, target, tmpFramework, bi); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !forDevice && !strings.HasSuffix(out, ".app") {
|
|
||||||
return fmt.Errorf("the specified output directory %q does not end in .app or .ipa", out)
|
|
||||||
}
|
|
||||||
if !forDevice {
|
|
||||||
return exeIOS(tmpDir, target, out, bi)
|
|
||||||
}
|
|
||||||
payload := filepath.Join(tmpDir, "Payload")
|
|
||||||
appDir := filepath.Join(payload, appName+".app")
|
|
||||||
if err := os.MkdirAll(appDir, 0755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := exeIOS(tmpDir, target, appDir, bi); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := signIOS(bi, tmpDir, appDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return zipDir(out, tmpDir, "Payload")
|
|
||||||
default:
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func signIOS(bi *buildInfo, tmpDir, app string) error {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
provPattern := filepath.Join(home, "Library", "MobileDevice", "Provisioning Profiles", "*.mobileprovision")
|
|
||||||
provisions, err := filepath.Glob(provPattern)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
provInfo := filepath.Join(tmpDir, "provision.plist")
|
|
||||||
var avail []string
|
|
||||||
for _, prov := range provisions {
|
|
||||||
// Decode the provision file to a plist.
|
|
||||||
_, err := runCmd(exec.Command("security", "cms", "-D", "-i", prov, "-o", provInfo))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
expUnix, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-c", "Print:ExpirationDate", provInfo))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
exp, err := time.Parse(time.UnixDate, expUnix)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("sign: failed to parse expiration date from %q: %v", prov, err)
|
|
||||||
}
|
|
||||||
if exp.Before(time.Now()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
appIDPrefix, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-c", "Print:ApplicationIdentifierPrefix:0", provInfo))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
provAppID, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-c", "Print:Entitlements:application-identifier", provInfo))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
expAppID := fmt.Sprintf("%s.%s", appIDPrefix, bi.appID)
|
|
||||||
avail = append(avail, provAppID)
|
|
||||||
if expAppID != provAppID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Copy provisioning file.
|
|
||||||
embedded := filepath.Join(app, "embedded.mobileprovision")
|
|
||||||
if err := copyFile(embedded, prov); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
certDER, err := runCmdRaw(exec.Command("/usr/libexec/PlistBuddy", "-c", "Print:DeveloperCertificates:0", provInfo))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Omit trailing newline.
|
|
||||||
certDER = certDER[:len(certDER)-1]
|
|
||||||
entitlements, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-x", "-c", "Print:Entitlements", provInfo))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
entFile := filepath.Join(tmpDir, "entitlements.plist")
|
|
||||||
if err := ioutil.WriteFile(entFile, []byte(entitlements), 0660); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
identity := sha1.Sum(certDER)
|
|
||||||
idHex := hex.EncodeToString(identity[:])
|
|
||||||
_, err = runCmd(exec.Command("codesign", "-s", idHex, "-v", "--entitlements", entFile, app))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf("sign: no valid provisioning profile found for bundle id %q among %v", bi.appID, avail)
|
|
||||||
}
|
|
||||||
|
|
||||||
func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
|
|
||||||
if bi.appID == "" {
|
|
||||||
return errors.New("app id is empty; use -appid to set it")
|
|
||||||
}
|
|
||||||
if err := os.RemoveAll(app); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := os.Mkdir(app, 0755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
mainm := filepath.Join(tmpDir, "main.m")
|
|
||||||
const mainmSrc = `@import UIKit;
|
|
||||||
@import Gio;
|
|
||||||
|
|
||||||
@interface GioAppDelegate : UIResponder <UIApplicationDelegate>
|
|
||||||
@property (strong, nonatomic) UIWindow *window;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GioAppDelegate
|
|
||||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
|
||||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
|
||||||
GioViewController *controller = [[GioViewController alloc] initWithNibName:nil bundle:nil];
|
|
||||||
self.window.rootViewController = controller;
|
|
||||||
[self.window makeKeyAndVisible];
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
int main(int argc, char * argv[]) {
|
|
||||||
@autoreleasepool {
|
|
||||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([GioAppDelegate class]));
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
if err := ioutil.WriteFile(mainm, []byte(mainmSrc), 0660); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
appName := strings.Title(bi.name)
|
|
||||||
exe := filepath.Join(app, appName)
|
|
||||||
lipo := exec.Command("xcrun", "lipo", "-o", exe, "-create")
|
|
||||||
var builds errgroup.Group
|
|
||||||
for _, a := range bi.archs {
|
|
||||||
clang, cflags, err := iosCompilerFor(target, a, bi.minsdk)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
exeSlice := filepath.Join(tmpDir, "app-"+a)
|
|
||||||
lipo.Args = append(lipo.Args, exeSlice)
|
|
||||||
compile := exec.Command(clang, cflags...)
|
|
||||||
compile.Args = append(compile.Args,
|
|
||||||
"-Werror",
|
|
||||||
"-fmodules",
|
|
||||||
"-fobjc-arc",
|
|
||||||
"-x", "objective-c",
|
|
||||||
"-F", tmpDir,
|
|
||||||
"-o", exeSlice,
|
|
||||||
mainm,
|
|
||||||
)
|
|
||||||
builds.Go(func() error {
|
|
||||||
_, err := runCmd(compile)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if err := builds.Wait(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := runCmd(lipo); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
infoPlist := buildInfoPlist(bi)
|
|
||||||
plistFile := filepath.Join(app, "Info.plist")
|
|
||||||
if err := ioutil.WriteFile(plistFile, []byte(infoPlist), 0660); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(bi.iconPath); err == nil {
|
|
||||||
assetPlist, err := iosIcons(bi, tmpDir, app, bi.iconPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Merge assets plist with Info.plist
|
|
||||||
cmd := exec.Command(
|
|
||||||
"/usr/libexec/PlistBuddy",
|
|
||||||
"-c", "Merge "+assetPlist,
|
|
||||||
plistFile,
|
|
||||||
)
|
|
||||||
if _, err := runCmd(cmd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := runCmd(exec.Command("plutil", "-convert", "binary1", plistFile)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// iosIcons builds an asset catalog and compile it with the Xcode command actool.
|
|
||||||
// iosIcons returns the asset plist file to be merged into Info.plist.
|
|
||||||
func iosIcons(bi *buildInfo, tmpDir, appDir, icon string) (string, error) {
|
|
||||||
assets := filepath.Join(tmpDir, "Assets.xcassets")
|
|
||||||
if err := os.Mkdir(assets, 0700); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
appIcon := filepath.Join(assets, "AppIcon.appiconset")
|
|
||||||
err := buildIcons(appIcon, icon, []iconVariant{
|
|
||||||
{path: "ios_2x.png", size: 120},
|
|
||||||
{path: "ios_3x.png", size: 180},
|
|
||||||
// The App Store icon is not allowed to contain
|
|
||||||
// transparent pixels.
|
|
||||||
{path: "ios_store.png", size: 1024, fill: true},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
contentJson := `{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"size" : "60x60",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "ios_2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "60x60",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "ios_3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "1024x1024",
|
|
||||||
"idiom" : "ios-marketing",
|
|
||||||
"filename" : "ios_store.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`
|
|
||||||
contentFile := filepath.Join(appIcon, "Contents.json")
|
|
||||||
if err := ioutil.WriteFile(contentFile, []byte(contentJson), 0600); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
assetPlist := filepath.Join(tmpDir, "assets.plist")
|
|
||||||
|
|
||||||
minsdk := bi.minsdk
|
|
||||||
if minsdk == 0 {
|
|
||||||
minsdk = minIOSVersion
|
|
||||||
}
|
|
||||||
compile := exec.Command(
|
|
||||||
"actool",
|
|
||||||
"--compile", appDir,
|
|
||||||
"--platform", iosPlatformFor(bi.target),
|
|
||||||
"--minimum-deployment-target", strconv.Itoa(minsdk),
|
|
||||||
"--app-icon", "AppIcon",
|
|
||||||
"--output-partial-info-plist", assetPlist,
|
|
||||||
assets)
|
|
||||||
_, err = runCmd(compile)
|
|
||||||
return assetPlist, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildInfoPlist(bi *buildInfo) string {
|
|
||||||
appName := strings.Title(bi.name)
|
|
||||||
platform := iosPlatformFor(bi.target)
|
|
||||||
var supportPlatform string
|
|
||||||
switch bi.target {
|
|
||||||
case "ios":
|
|
||||||
supportPlatform = "iPhoneOS"
|
|
||||||
case "tvos":
|
|
||||||
supportPlatform = "AppleTVOS"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>en</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>%s</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>%s</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>%s</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.0.%d</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>%d</string>
|
|
||||||
<key>UILaunchStoryboardName</key>
|
|
||||||
<string>LaunchScreen</string>
|
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
|
||||||
<array><string>arm64</string></array>
|
|
||||||
<key>DTPlatformName</key>
|
|
||||||
<string>%s</string>
|
|
||||||
<key>DTPlatformVersion</key>
|
|
||||||
<string>12.4</string>
|
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>%d</string>
|
|
||||||
<key>UIDeviceFamily</key>
|
|
||||||
<array>
|
|
||||||
<integer>1</integer>
|
|
||||||
<integer>2</integer>
|
|
||||||
</array>
|
|
||||||
<key>CFBundleSupportedPlatforms</key>
|
|
||||||
<array>
|
|
||||||
<string>%s</string>
|
|
||||||
</array>
|
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>DTCompiler</key>
|
|
||||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
|
||||||
<key>DTPlatformBuild</key>
|
|
||||||
<string>16G73</string>
|
|
||||||
<key>DTSDKBuild</key>
|
|
||||||
<string>16G73</string>
|
|
||||||
<key>DTSDKName</key>
|
|
||||||
<string>%s12.4</string>
|
|
||||||
<key>DTXcode</key>
|
|
||||||
<string>1030</string>
|
|
||||||
<key>DTXcodeBuild</key>
|
|
||||||
<string>10G8</string>
|
|
||||||
</dict>
|
|
||||||
</plist>`, appName, bi.appID, appName, bi.version, bi.version, platform, minIOSVersion, supportPlatform, platform)
|
|
||||||
}
|
|
||||||
|
|
||||||
func iosPlatformFor(target string) string {
|
|
||||||
switch target {
|
|
||||||
case "ios":
|
|
||||||
return "iphoneos"
|
|
||||||
case "tvos":
|
|
||||||
return "appletvos"
|
|
||||||
default:
|
|
||||||
panic("invalid platform " + target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func archiveIOS(tmpDir, target, frameworkRoot string, bi *buildInfo) error {
|
|
||||||
framework := filepath.Base(frameworkRoot)
|
|
||||||
const suf = ".framework"
|
|
||||||
if !strings.HasSuffix(framework, suf) {
|
|
||||||
return fmt.Errorf("the specified output %q does not end in '.framework'", frameworkRoot)
|
|
||||||
}
|
|
||||||
framework = framework[:len(framework)-len(suf)]
|
|
||||||
if err := os.RemoveAll(frameworkRoot); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
frameworkDir := filepath.Join(frameworkRoot, "Versions", "A")
|
|
||||||
for _, dir := range []string{"Headers", "Modules"} {
|
|
||||||
p := filepath.Join(frameworkDir, dir)
|
|
||||||
if err := os.MkdirAll(p, 0755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
symlinks := [][2]string{
|
|
||||||
{"Versions/Current/Headers", "Headers"},
|
|
||||||
{"Versions/Current/Modules", "Modules"},
|
|
||||||
{"Versions/Current/" + framework, framework},
|
|
||||||
{"A", filepath.Join("Versions", "Current")},
|
|
||||||
}
|
|
||||||
for _, l := range symlinks {
|
|
||||||
if err := os.Symlink(l[0], filepath.Join(frameworkRoot, l[1])); err != nil && !os.IsExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exe := filepath.Join(frameworkDir, framework)
|
|
||||||
lipo := exec.Command("xcrun", "lipo", "-o", exe, "-create")
|
|
||||||
var builds errgroup.Group
|
|
||||||
tags := bi.tags
|
|
||||||
goos := "ios"
|
|
||||||
supportsIOS, err := supportsGOOS("ios")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !supportsIOS {
|
|
||||||
// Go 1.15 and earlier target iOS with GOOS=darwin, tags=ios.
|
|
||||||
goos = "darwin"
|
|
||||||
tags = "ios " + tags
|
|
||||||
}
|
|
||||||
for _, a := range bi.archs {
|
|
||||||
clang, cflags, err := iosCompilerFor(target, a, bi.minsdk)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
lib := filepath.Join(tmpDir, "gio-"+a)
|
|
||||||
cmd := exec.Command(
|
|
||||||
"go",
|
|
||||||
"build",
|
|
||||||
"-ldflags=-s -w "+bi.ldflags,
|
|
||||||
"-buildmode=c-archive",
|
|
||||||
"-o", lib,
|
|
||||||
"-tags", tags,
|
|
||||||
bi.pkgPath,
|
|
||||||
)
|
|
||||||
lipo.Args = append(lipo.Args, lib)
|
|
||||||
cflagsLine := strings.Join(cflags, " ")
|
|
||||||
cmd.Env = append(
|
|
||||||
os.Environ(),
|
|
||||||
"GOOS="+goos,
|
|
||||||
"GOARCH="+a,
|
|
||||||
"CGO_ENABLED=1",
|
|
||||||
"CC="+clang,
|
|
||||||
"CGO_CFLAGS="+cflagsLine,
|
|
||||||
"CGO_LDFLAGS="+cflagsLine,
|
|
||||||
)
|
|
||||||
builds.Go(func() error {
|
|
||||||
_, err := runCmd(cmd)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if err := builds.Wait(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := runCmd(lipo); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
appDir, err := runCmd(exec.Command("go", "list", "-f", "{{.Dir}}", "gioui.org/app/"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
headerDst := filepath.Join(frameworkDir, "Headers", framework+".h")
|
|
||||||
headerSrc := filepath.Join(appDir, "framework_ios.h")
|
|
||||||
if err := copyFile(headerDst, headerSrc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
module := fmt.Sprintf(`framework module "%s" {
|
|
||||||
header "%[1]s.h"
|
|
||||||
|
|
||||||
export *
|
|
||||||
}`, framework)
|
|
||||||
moduleFile := filepath.Join(frameworkDir, "Modules", "module.modulemap")
|
|
||||||
return ioutil.WriteFile(moduleFile, []byte(module), 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func supportsGOOS(wantGoos string) (bool, error) {
|
|
||||||
geese, err := runCmd(exec.Command("go", "tool", "dist", "list"))
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
for _, pair := range strings.Split(geese, "\n") {
|
|
||||||
s := strings.SplitN(pair, "/", 2)
|
|
||||||
if len(s) != 2 {
|
|
||||||
return false, fmt.Errorf("go tool dist list: invalid GOOS/GOARCH pair: %s", pair)
|
|
||||||
}
|
|
||||||
goos := s[0]
|
|
||||||
if goos == wantGoos {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func iosCompilerFor(target, arch string, minsdk int) (string, []string, error) {
|
|
||||||
var (
|
|
||||||
platformSDK string
|
|
||||||
platformOS string
|
|
||||||
)
|
|
||||||
switch target {
|
|
||||||
case "ios":
|
|
||||||
platformOS = "ios"
|
|
||||||
platformSDK = "iphone"
|
|
||||||
case "tvos":
|
|
||||||
platformOS = "tvos"
|
|
||||||
platformSDK = "appletv"
|
|
||||||
}
|
|
||||||
switch arch {
|
|
||||||
case "arm", "arm64":
|
|
||||||
platformSDK += "os"
|
|
||||||
if minsdk == 0 {
|
|
||||||
minsdk = minIOSVersion
|
|
||||||
}
|
|
||||||
case "386", "amd64":
|
|
||||||
platformOS += "-simulator"
|
|
||||||
platformSDK += "simulator"
|
|
||||||
if minsdk == 0 {
|
|
||||||
minsdk = minSimulatorVersion
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return "", nil, fmt.Errorf("unsupported -arch: %s", arch)
|
|
||||||
}
|
|
||||||
sdkPath, err := runCmd(exec.Command("xcrun", "--sdk", platformSDK, "--show-sdk-path"))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
clang, err := runCmd(exec.Command("xcrun", "--sdk", platformSDK, "--find", "clang"))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
cflags := []string{
|
|
||||||
"-fembed-bitcode",
|
|
||||||
"-arch", allArchs[arch].iosArch,
|
|
||||||
"-isysroot", sdkPath,
|
|
||||||
"-m" + platformOS + "-version-min=" + strconv.Itoa(minsdk),
|
|
||||||
}
|
|
||||||
return clang, cflags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func zipDir(dst, base, dir string) (err error) {
|
|
||||||
f, err := os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if cerr := f.Close(); err == nil {
|
|
||||||
err = cerr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
zipf := zip.NewWriter(f)
|
|
||||||
err = filepath.Walk(filepath.Join(base, dir), func(path string, f os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if f.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
rel := filepath.ToSlash(path[len(base)+1:])
|
|
||||||
entry, err := zipf.Create(rel)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
src, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer src.Close()
|
|
||||||
_, err = io.Copy(entry, src)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return zipf.Close()
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package main_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"image"
|
|
||||||
"image/png"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/chromedp/cdproto/runtime"
|
|
||||||
"github.com/chromedp/chromedp"
|
|
||||||
|
|
||||||
_ "gioui.org/unit" // the build tool adds it to go.mod, so keep it there
|
|
||||||
)
|
|
||||||
|
|
||||||
type JSTestDriver struct {
|
|
||||||
driverBase
|
|
||||||
|
|
||||||
// ctx is the chromedp context.
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *JSTestDriver) Start(path string) {
|
|
||||||
if raceEnabled {
|
|
||||||
d.Skipf("js/wasm doesn't support -race; skipping")
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, build the app.
|
|
||||||
dir := d.tempDir("gio-endtoend-js")
|
|
||||||
d.gogio("-target=js", "-o="+dir, path)
|
|
||||||
|
|
||||||
// Second, start Chrome.
|
|
||||||
opts := append(chromedp.DefaultExecAllocatorOptions[:],
|
|
||||||
chromedp.Flag("headless", *headless),
|
|
||||||
)
|
|
||||||
|
|
||||||
actx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
|
|
||||||
d.Cleanup(cancel)
|
|
||||||
|
|
||||||
ctx, cancel := chromedp.NewContext(actx,
|
|
||||||
// Send all logf/errf calls to t.Logf
|
|
||||||
chromedp.WithLogf(d.Logf),
|
|
||||||
)
|
|
||||||
d.Cleanup(cancel)
|
|
||||||
d.ctx = ctx
|
|
||||||
|
|
||||||
if err := chromedp.Run(ctx); err != nil {
|
|
||||||
if errors.Is(err, exec.ErrNotFound) {
|
|
||||||
d.Skipf("test requires Chrome to be installed: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
pr, pw := io.Pipe()
|
|
||||||
d.Cleanup(func() { pw.Close() })
|
|
||||||
d.output = pr
|
|
||||||
chromedp.ListenTarget(ctx, func(ev interface{}) {
|
|
||||||
switch ev := ev.(type) {
|
|
||||||
case *runtime.EventConsoleAPICalled:
|
|
||||||
switch ev.Type {
|
|
||||||
case "log", "info", "warning", "error":
|
|
||||||
var b bytes.Buffer
|
|
||||||
b.WriteString("console.")
|
|
||||||
b.WriteString(string(ev.Type))
|
|
||||||
b.WriteString("(")
|
|
||||||
for i, arg := range ev.Args {
|
|
||||||
if i > 0 {
|
|
||||||
b.WriteString(", ")
|
|
||||||
}
|
|
||||||
b.Write(arg.Value)
|
|
||||||
}
|
|
||||||
b.WriteString(")\n")
|
|
||||||
pw.Write(b.Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Third, serve the app folder, set the browser tab dimensions, and
|
|
||||||
// navigate to the folder.
|
|
||||||
ts := httptest.NewServer(http.FileServer(http.Dir(dir)))
|
|
||||||
d.Cleanup(ts.Close)
|
|
||||||
|
|
||||||
if err := chromedp.Run(ctx,
|
|
||||||
chromedp.EmulateViewport(int64(d.width), int64(d.height)),
|
|
||||||
chromedp.Navigate(ts.URL),
|
|
||||||
); err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the gio app to render.
|
|
||||||
d.waitForFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *JSTestDriver) Screenshot() image.Image {
|
|
||||||
var buf []byte
|
|
||||||
if err := chromedp.Run(d.ctx,
|
|
||||||
chromedp.CaptureScreenshot(&buf),
|
|
||||||
); err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
img, err := png.Decode(bytes.NewReader(buf))
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *JSTestDriver) Click(x, y int) {
|
|
||||||
if err := chromedp.Run(d.ctx,
|
|
||||||
chromedp.MouseClickXY(float64(x), float64(y)),
|
|
||||||
); err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the gio app to render after this click.
|
|
||||||
d.waitForFrame()
|
|
||||||
}
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/packages"
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildJS(bi *buildInfo) error {
|
|
||||||
out := *destPath
|
|
||||||
if out == "" {
|
|
||||||
out = bi.name
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(out, 0700); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmd := exec.Command(
|
|
||||||
"go",
|
|
||||||
"build",
|
|
||||||
"-ldflags="+bi.ldflags,
|
|
||||||
"-tags="+bi.tags,
|
|
||||||
"-o", filepath.Join(out, "main.wasm"),
|
|
||||||
bi.pkgPath,
|
|
||||||
)
|
|
||||||
cmd.Env = append(
|
|
||||||
os.Environ(),
|
|
||||||
"GOOS=js",
|
|
||||||
"GOARCH=wasm",
|
|
||||||
)
|
|
||||||
_, err := runCmd(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var faviconPath string
|
|
||||||
if _, err := os.Stat(bi.iconPath); err == nil {
|
|
||||||
// Copy icon to the output folder
|
|
||||||
icon, err := ioutil.ReadFile(bi.iconPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(filepath.Join(out, filepath.Base(bi.iconPath)), icon, 0600); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
faviconPath = filepath.Base(bi.iconPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
indexTemplate, err := template.New("").Parse(jsIndex)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
if err := indexTemplate.Execute(&b, struct {
|
|
||||||
Name string
|
|
||||||
Icon string
|
|
||||||
}{
|
|
||||||
Name: bi.name,
|
|
||||||
Icon: faviconPath,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ioutil.WriteFile(filepath.Join(out, "index.html"), b.Bytes(), 0600); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
goroot, err := runCmd(exec.Command("go", "env", "GOROOT"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
wasmJS := filepath.Join(goroot, "misc", "wasm", "wasm_exec.js")
|
|
||||||
if _, err := os.Stat(wasmJS); err != nil {
|
|
||||||
return fmt.Errorf("failed to find $GOROOT/misc/wasm/wasm_exec.js driver: %v", err)
|
|
||||||
}
|
|
||||||
pkgs, err := packages.Load(&packages.Config{
|
|
||||||
Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps,
|
|
||||||
Env: append(os.Environ(), "GOOS=js", "GOARCH=wasm"),
|
|
||||||
}, bi.pkgPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
extraJS, err := findPackagesJS(pkgs[0], make(map[string]bool))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return mergeJSFiles(filepath.Join(out, "wasm.js"), append([]string{wasmJS}, extraJS...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findPackagesJS(p *packages.Package, visited map[string]bool) (extraJS []string, err error) {
|
|
||||||
if len(p.GoFiles) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
js, err := filepath.Glob(filepath.Join(filepath.Dir(p.GoFiles[0]), "*_js.js"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
extraJS = append(extraJS, js...)
|
|
||||||
for _, imp := range p.Imports {
|
|
||||||
if !visited[imp.ID] {
|
|
||||||
extra, err := findPackagesJS(imp, visited)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
extraJS = append(extraJS, extra...)
|
|
||||||
visited[imp.ID] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return extraJS, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mergeJSFiles will merge all files into a single `wasm.js`. It will prepend the jsSetGo
|
|
||||||
// and append the jsStartGo.
|
|
||||||
func mergeJSFiles(dst string, files ...string) (err error) {
|
|
||||||
w, err := os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if cerr := w.Close(); err != nil {
|
|
||||||
err = cerr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
_, err = io.Copy(w, strings.NewReader(jsSetGo))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i := range files {
|
|
||||||
r, err := os.Open(files[i])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = io.Copy(w, r)
|
|
||||||
r.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = io.Copy(w, strings.NewReader(jsStartGo))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
jsIndex = `<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
|
||||||
{{ if .Icon }}<link rel="icon" href="{{.Icon}}" type="image/x-icon" />{{ end }}
|
|
||||||
{{ if .Name }}<title>{{.Name}}</title>{{ end }}
|
|
||||||
<script src="wasm.js"></script>
|
|
||||||
<style>
|
|
||||||
body,pre { margin:0;padding:0; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
</html>`
|
|
||||||
// jsSetGo sets the `window.go` variable.
|
|
||||||
jsSetGo = `(() => {
|
|
||||||
window.go = {argv: [], env: {}, importObject: {go: {}}};
|
|
||||||
const argv = new URLSearchParams(location.search).get("argv");
|
|
||||||
if (argv) {
|
|
||||||
window.go["argv"] = argv.split(" ");
|
|
||||||
}
|
|
||||||
})();`
|
|
||||||
// jsStartGo initializes the main.wasm.
|
|
||||||
jsStartGo = `(() => {
|
|
||||||
defaultGo = new Go();
|
|
||||||
Object.assign(defaultGo["argv"], defaultGo["argv"].concat(go["argv"]));
|
|
||||||
Object.assign(defaultGo["env"], go["env"]);
|
|
||||||
for (let key in go["importObject"]) {
|
|
||||||
if (typeof defaultGo["importObject"][key] === "undefined") {
|
|
||||||
defaultGo["importObject"][key] = {};
|
|
||||||
}
|
|
||||||
Object.assign(defaultGo["importObject"][key], go["importObject"][key]);
|
|
||||||
}
|
|
||||||
window.go = defaultGo;
|
|
||||||
if (!WebAssembly.instantiateStreaming) { // polyfill
|
|
||||||
WebAssembly.instantiateStreaming = async (resp, importObject) => {
|
|
||||||
const source = await (await resp).arrayBuffer();
|
|
||||||
return await WebAssembly.instantiate(source, importObject);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
|
|
||||||
go.run(result.instance);
|
|
||||||
});
|
|
||||||
})();`
|
|
||||||
)
|
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/png"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/image/draw"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
target = flag.String("target", "", "specify target (ios, tvos, android, js).\n")
|
|
||||||
archNames = flag.String("arch", "", "specify architecture(s) to include (arm, arm64, amd64).")
|
|
||||||
minsdk = flag.Int("minsdk", 0, "specify the minimum supported operating system level")
|
|
||||||
buildMode = flag.String("buildmode", "exe", "specify buildmode (archive, exe)")
|
|
||||||
destPath = flag.String("o", "", "output file or directory.\nFor -target ios or tvos, use the .app suffix to target simulators.")
|
|
||||||
appID = flag.String("appid", "", "app identifier (for -buildmode=exe)")
|
|
||||||
version = flag.Int("version", 1, "app version (for -buildmode=exe)")
|
|
||||||
printCommands = flag.Bool("x", false, "print the commands")
|
|
||||||
keepWorkdir = flag.Bool("work", false, "print the name of the temporary work directory and do not delete it when exiting.")
|
|
||||||
linkMode = flag.String("linkmode", "", "set the -linkmode flag of the go tool")
|
|
||||||
extraLdflags = flag.String("ldflags", "", "extra flags to the Go linker")
|
|
||||||
extraTags = flag.String("tags", "", "extra tags to the Go tool")
|
|
||||||
iconPath = flag.String("icon", "", "specify an icon for iOS and Android")
|
|
||||||
signKey = flag.String("signkey", "", "specify the path of the keystore to be used to sign Android apk files.")
|
|
||||||
signPass = flag.String("signpass", "", "specify the password to decrypt the signkey.")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Usage = func() {
|
|
||||||
fmt.Fprint(os.Stderr, mainUsage)
|
|
||||||
}
|
|
||||||
flag.Parse()
|
|
||||||
if err := flagValidate(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "gogio: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
buildInfo, err := newBuildInfo(flag.Arg(0))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "gogio: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if err := build(buildInfo); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "gogio: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func flagValidate() error {
|
|
||||||
pkgPathArg := flag.Arg(0)
|
|
||||||
if pkgPathArg == "" {
|
|
||||||
return errors.New("specify a package")
|
|
||||||
}
|
|
||||||
if *target == "" {
|
|
||||||
return errors.New("please specify -target")
|
|
||||||
}
|
|
||||||
switch *target {
|
|
||||||
case "ios", "tvos", "android", "js", "windows":
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid -target %s", *target)
|
|
||||||
}
|
|
||||||
switch *buildMode {
|
|
||||||
case "archive", "exe":
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid -buildmode %s", *buildMode)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func build(bi *buildInfo) error {
|
|
||||||
tmpDir, err := ioutil.TempDir("", "gogio-")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if *keepWorkdir {
|
|
||||||
fmt.Fprintf(os.Stderr, "WORKDIR=%s\n", tmpDir)
|
|
||||||
} else {
|
|
||||||
defer os.RemoveAll(tmpDir)
|
|
||||||
}
|
|
||||||
switch *target {
|
|
||||||
case "js":
|
|
||||||
return buildJS(bi)
|
|
||||||
case "ios", "tvos":
|
|
||||||
return buildIOS(tmpDir, *target, bi)
|
|
||||||
case "android":
|
|
||||||
return buildAndroid(tmpDir, bi)
|
|
||||||
case "windows":
|
|
||||||
return buildWindows(tmpDir, bi)
|
|
||||||
default:
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCmdRaw(cmd *exec.Cmd) ([]byte, error) {
|
|
||||||
if *printCommands {
|
|
||||||
fmt.Printf("%s\n", strings.Join(cmd.Args, " "))
|
|
||||||
}
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err == nil {
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
if err, ok := err.(*exec.ExitError); ok {
|
|
||||||
return nil, fmt.Errorf("%s failed: %s%s", strings.Join(cmd.Args, " "), out, err.Stderr)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCmd(cmd *exec.Cmd) (string, error) {
|
|
||||||
out, err := runCmdRaw(cmd)
|
|
||||||
return string(bytes.TrimSpace(out)), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFile(dst, src string) (err error) {
|
|
||||||
r, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
w, err := os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if cerr := w.Close(); err == nil {
|
|
||||||
err = cerr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
_, err = io.Copy(w, r)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type arch struct {
|
|
||||||
iosArch string
|
|
||||||
jniArch string
|
|
||||||
clangArch string
|
|
||||||
}
|
|
||||||
|
|
||||||
var allArchs = map[string]arch{
|
|
||||||
"arm": {
|
|
||||||
iosArch: "armv7",
|
|
||||||
jniArch: "armeabi-v7a",
|
|
||||||
clangArch: "armv7a-linux-androideabi",
|
|
||||||
},
|
|
||||||
"arm64": {
|
|
||||||
iosArch: "arm64",
|
|
||||||
jniArch: "arm64-v8a",
|
|
||||||
clangArch: "aarch64-linux-android",
|
|
||||||
},
|
|
||||||
"386": {
|
|
||||||
iosArch: "i386",
|
|
||||||
jniArch: "x86",
|
|
||||||
clangArch: "i686-linux-android",
|
|
||||||
},
|
|
||||||
"amd64": {
|
|
||||||
iosArch: "x86_64",
|
|
||||||
jniArch: "x86_64",
|
|
||||||
clangArch: "x86_64-linux-android",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type iconVariant struct {
|
|
||||||
path string
|
|
||||||
size int
|
|
||||||
fill bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildIcons(baseDir, icon string, variants []iconVariant) error {
|
|
||||||
f, err := os.Open(icon)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
img, _, err := image.Decode(f)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var resizes errgroup.Group
|
|
||||||
for _, v := range variants {
|
|
||||||
v := v
|
|
||||||
resizes.Go(func() (err error) {
|
|
||||||
path := filepath.Join(baseDir, v.path)
|
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if cerr := f.Close(); err == nil {
|
|
||||||
err = cerr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return png.Encode(f, resizeIcon(v, img))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return resizes.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func resizeIcon(v iconVariant, img image.Image) *image.NRGBA {
|
|
||||||
scaled := image.NewNRGBA(image.Rectangle{Max: image.Point{X: v.size, Y: v.size}})
|
|
||||||
op := draw.Src
|
|
||||||
if v.fill {
|
|
||||||
op = draw.Over
|
|
||||||
draw.Draw(scaled, scaled.Bounds(), &image.Uniform{color.White}, image.Point{}, draw.Src)
|
|
||||||
}
|
|
||||||
draw.CatmullRom.Scale(scaled, scaled.Bounds(), img, img.Bounds(), op, nil)
|
|
||||||
|
|
||||||
return scaled
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
if os.Getenv("RUN_GOGIO") != "" {
|
|
||||||
// Allow the end-to-end tests to call the gogio tool without
|
|
||||||
// having to build it from scratch, nor having to refactor the
|
|
||||||
// main function to avoid using global variables.
|
|
||||||
main()
|
|
||||||
os.Exit(0) // main already exits, but just in case.
|
|
||||||
}
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
var AndroidPermissions = map[string][]string{
|
|
||||||
"network": {
|
|
||||||
"android.permission.INTERNET",
|
|
||||||
},
|
|
||||||
"networkstate": {
|
|
||||||
"android.permission.ACCESS_NETWORK_STATE",
|
|
||||||
},
|
|
||||||
"bluetooth": {
|
|
||||||
"android.permission.BLUETOOTH",
|
|
||||||
"android.permission.BLUETOOTH_ADMIN",
|
|
||||||
"android.permission.ACCESS_FINE_LOCATION",
|
|
||||||
},
|
|
||||||
"camera": {
|
|
||||||
"android.permission.CAMERA",
|
|
||||||
},
|
|
||||||
"storage": {
|
|
||||||
"android.permission.READ_EXTERNAL_STORAGE",
|
|
||||||
"android.permission.WRITE_EXTERNAL_STORAGE",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var AndroidFeatures = map[string][]string{
|
|
||||||
"default": {`glEsVersion="0x00020000"`, `name="android.hardware.type.pc"`},
|
|
||||||
"bluetooth": {
|
|
||||||
`name="android.hardware.bluetooth"`,
|
|
||||||
`name="android.hardware.bluetooth_le"`,
|
|
||||||
},
|
|
||||||
"camera": {
|
|
||||||
`name="android.hardware.camera"`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
//go:build race
|
|
||||||
// +build race
|
|
||||||
|
|
||||||
package main_test
|
|
||||||
|
|
||||||
func init() { raceEnabled = true }
|
|
||||||
Vendored
-142
@@ -1,142 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
// A simple app used for gogio's end-to-end tests.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"gioui.org/app"
|
|
||||||
"gioui.org/io/pointer"
|
|
||||||
"gioui.org/io/system"
|
|
||||||
"gioui.org/layout"
|
|
||||||
"gioui.org/op"
|
|
||||||
"gioui.org/op/clip"
|
|
||||||
"gioui.org/op/paint"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
go func() {
|
|
||||||
w := app.NewWindow()
|
|
||||||
if err := loop(w); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
app.Main()
|
|
||||||
}
|
|
||||||
|
|
||||||
type notifyFrame int
|
|
||||||
|
|
||||||
const (
|
|
||||||
notifyNone notifyFrame = iota
|
|
||||||
notifyInvalidate
|
|
||||||
notifyPrint
|
|
||||||
)
|
|
||||||
|
|
||||||
// notify keeps track of whether we want to print to stdout to notify the user
|
|
||||||
// when a frame is ready. Initially we want to notify about the first frame.
|
|
||||||
var notify = notifyInvalidate
|
|
||||||
|
|
||||||
type (
|
|
||||||
C = layout.Context
|
|
||||||
D = layout.Dimensions
|
|
||||||
)
|
|
||||||
|
|
||||||
func loop(w *app.Window) error {
|
|
||||||
topLeft := quarterWidget{
|
|
||||||
color: color.NRGBA{R: 0xde, G: 0xad, B: 0xbe, A: 0xff},
|
|
||||||
}
|
|
||||||
topRight := quarterWidget{
|
|
||||||
color: color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff},
|
|
||||||
}
|
|
||||||
botLeft := quarterWidget{
|
|
||||||
color: color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xff},
|
|
||||||
}
|
|
||||||
botRight := quarterWidget{
|
|
||||||
color: color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0x80},
|
|
||||||
}
|
|
||||||
|
|
||||||
var ops op.Ops
|
|
||||||
for {
|
|
||||||
e := <-w.Events()
|
|
||||||
switch e := e.(type) {
|
|
||||||
case system.DestroyEvent:
|
|
||||||
return e.Err
|
|
||||||
case system.FrameEvent:
|
|
||||||
gtx := layout.NewContext(&ops, e)
|
|
||||||
// Clear background to white, even on embedded platforms such as webassembly.
|
|
||||||
paint.Fill(gtx.Ops, color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff})
|
|
||||||
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
|
||||||
layout.Flexed(1, func(gtx C) D {
|
|
||||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
|
||||||
// r1c1
|
|
||||||
layout.Flexed(1, func(gtx C) D { return topLeft.Layout(gtx) }),
|
|
||||||
// r1c2
|
|
||||||
layout.Flexed(1, func(gtx C) D { return topRight.Layout(gtx) }),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
layout.Flexed(1, func(gtx C) D {
|
|
||||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
|
||||||
// r2c1
|
|
||||||
layout.Flexed(1, func(gtx C) D { return botLeft.Layout(gtx) }),
|
|
||||||
// r2c2
|
|
||||||
layout.Flexed(1, func(gtx C) D { return botRight.Layout(gtx) }),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
e.Frame(gtx.Ops)
|
|
||||||
|
|
||||||
switch notify {
|
|
||||||
case notifyInvalidate:
|
|
||||||
notify = notifyPrint
|
|
||||||
w.Invalidate()
|
|
||||||
case notifyPrint:
|
|
||||||
notify = notifyNone
|
|
||||||
fmt.Println("gio frame ready")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// quarterWidget paints a quarter of the screen with one color. When clicked, it
|
|
||||||
// turns red, going back to its normal color when clicked again.
|
|
||||||
type quarterWidget struct {
|
|
||||||
color color.NRGBA
|
|
||||||
|
|
||||||
clicked bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var red = color.NRGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff}
|
|
||||||
|
|
||||||
func (w *quarterWidget) Layout(gtx layout.Context) layout.Dimensions {
|
|
||||||
var color color.NRGBA
|
|
||||||
if w.clicked {
|
|
||||||
color = red
|
|
||||||
} else {
|
|
||||||
color = w.color
|
|
||||||
}
|
|
||||||
|
|
||||||
r := image.Rectangle{Max: gtx.Constraints.Max}
|
|
||||||
paint.FillShape(gtx.Ops, color, clip.Rect(r).Op())
|
|
||||||
|
|
||||||
defer clip.Rect(image.Rectangle{
|
|
||||||
Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y),
|
|
||||||
}).Push(gtx.Ops).Pop()
|
|
||||||
pointer.InputOp{
|
|
||||||
Tag: w,
|
|
||||||
Types: pointer.Press,
|
|
||||||
}.Add(gtx.Ops)
|
|
||||||
|
|
||||||
for _, e := range gtx.Events(w) {
|
|
||||||
if e, ok := e.(pointer.Event); ok && e.Type == pointer.Press {
|
|
||||||
w.clicked = !w.clicked
|
|
||||||
// notify when we're done updating the frame.
|
|
||||||
notify = notifyInvalidate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Max}
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package main_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/png"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"text/template"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type WaylandTestDriver struct {
|
|
||||||
driverBase
|
|
||||||
|
|
||||||
runtimeDir string
|
|
||||||
socket string
|
|
||||||
display string
|
|
||||||
}
|
|
||||||
|
|
||||||
// No bars or anything fancy. Just a white background with our dimensions.
|
|
||||||
var tmplSwayConfig = template.Must(template.New("").Parse(`
|
|
||||||
output * bg #FFFFFF solid_color
|
|
||||||
output * mode {{.Width}}x{{.Height}}
|
|
||||||
default_border none
|
|
||||||
`))
|
|
||||||
|
|
||||||
var rxSwayReady = regexp.MustCompile(`Running compositor on wayland display '(.*)'`)
|
|
||||||
|
|
||||||
func (d *WaylandTestDriver) Start(path string) {
|
|
||||||
// We want os.Environ, so that it can e.g. find $DISPLAY to run within
|
|
||||||
// X11. wlroots env vars are documented at:
|
|
||||||
// https://github.com/swaywm/wlroots/blob/master/docs/env_vars.md
|
|
||||||
env := os.Environ()
|
|
||||||
if *headless {
|
|
||||||
env = append(env, "WLR_BACKENDS=headless")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.needPrograms(
|
|
||||||
"sway", // to run a wayland compositor
|
|
||||||
"grim", // to take screenshots
|
|
||||||
"swaymsg", // to send input
|
|
||||||
)
|
|
||||||
|
|
||||||
// First, build the app.
|
|
||||||
dir := d.tempDir("gio-endtoend-wayland")
|
|
||||||
bin := filepath.Join(dir, "red")
|
|
||||||
flags := []string{"build", "-tags", "nox11", "-o=" + bin}
|
|
||||||
if raceEnabled {
|
|
||||||
flags = append(flags, "-race")
|
|
||||||
}
|
|
||||||
flags = append(flags, path)
|
|
||||||
cmd := exec.Command("go", flags...)
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
d.Fatalf("could not build app: %s:\n%s", err, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := filepath.Join(dir, "config")
|
|
||||||
f, err := os.Create(conf)
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
if err := tmplSwayConfig.Execute(f, struct{ Width, Height int }{
|
|
||||||
d.width, d.height,
|
|
||||||
}); err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.socket = filepath.Join(dir, "socket")
|
|
||||||
env = append(env, "SWAYSOCK="+d.socket)
|
|
||||||
d.runtimeDir = dir
|
|
||||||
env = append(env, "XDG_RUNTIME_DIR="+d.runtimeDir)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
d.Cleanup(wg.Wait)
|
|
||||||
|
|
||||||
// First, start sway.
|
|
||||||
{
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
cmd := exec.CommandContext(ctx, "sway", "--config", conf, "--verbose")
|
|
||||||
cmd.Env = env
|
|
||||||
stderr, err := cmd.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
d.Cleanup(cancel)
|
|
||||||
d.Cleanup(func() {
|
|
||||||
// Give it a chance to exit gracefully, cleaning up
|
|
||||||
// after itself. After 10ms, the deferred cancel above
|
|
||||||
// will signal an os.Kill.
|
|
||||||
cmd.Process.Signal(os.Interrupt)
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait for sway to be ready. We probably don't need a deadline
|
|
||||||
// here.
|
|
||||||
br := bufio.NewReader(stderr)
|
|
||||||
for {
|
|
||||||
line, err := br.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
if m := rxSwayReady.FindStringSubmatch(line); m != nil {
|
|
||||||
d.display = m[1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
if err := cmd.Wait(); err != nil && ctx.Err() == nil && !strings.Contains(err.Error(), "interrupt") {
|
|
||||||
// Don't print all stderr, since we use --verbose.
|
|
||||||
// TODO(mvdan): if it's useful, probably filter
|
|
||||||
// errors and show them.
|
|
||||||
d.Error(err)
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then, start our program on the sway compositor above.
|
|
||||||
{
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
cmd := exec.CommandContext(ctx, bin)
|
|
||||||
cmd.Env = []string{"XDG_RUNTIME_DIR=" + d.runtimeDir, "WAYLAND_DISPLAY=" + d.display}
|
|
||||||
output, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
cmd.Stderr = cmd.Stdout
|
|
||||||
d.output = output
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
d.Cleanup(cancel)
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
if err := cmd.Wait(); err != nil && ctx.Err() == nil {
|
|
||||||
d.Error(err)
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the gio app to render.
|
|
||||||
d.waitForFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *WaylandTestDriver) Screenshot() image.Image {
|
|
||||||
cmd := exec.Command("grim", "/dev/stdout")
|
|
||||||
cmd.Env = []string{"XDG_RUNTIME_DIR=" + d.runtimeDir, "WAYLAND_DISPLAY=" + d.display}
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
d.Errorf("%s", out)
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
img, err := png.Decode(bytes.NewReader(out))
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *WaylandTestDriver) swaymsg(args ...interface{}) {
|
|
||||||
strs := []string{"--socket", d.socket}
|
|
||||||
for _, arg := range args {
|
|
||||||
strs = append(strs, fmt.Sprint(arg))
|
|
||||||
}
|
|
||||||
cmd := exec.Command("swaymsg", strs...)
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
d.Errorf("%s", out)
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *WaylandTestDriver) Click(x, y int) {
|
|
||||||
d.swaymsg("seat", "-", "cursor", "set", x, y)
|
|
||||||
d.swaymsg("seat", "-", "cursor", "press", "button1")
|
|
||||||
d.swaymsg("seat", "-", "cursor", "release", "button1")
|
|
||||||
|
|
||||||
// Wait for the gio app to render after this click.
|
|
||||||
d.waitForFrame()
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package main_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"image"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/image/draw"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Wine is tightly coupled with X11 at the moment, and we can reuse the same
|
|
||||||
// methods to automate screenshots and clicks. The main difference is how we
|
|
||||||
// build and run the app.
|
|
||||||
|
|
||||||
// The only quirk is that it seems impossible for the Wine window to take the
|
|
||||||
// entirety of the X server's dimensions, even if we try to resize it to take
|
|
||||||
// the entire display. It seems to want to leave some vertical space empty,
|
|
||||||
// presumably for window decorations or the "start" bar on Windows. To work
|
|
||||||
// around that, make the X server 50x50px bigger, and crop the screenshots back
|
|
||||||
// to the original size.
|
|
||||||
|
|
||||||
type WineTestDriver struct {
|
|
||||||
X11TestDriver
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *WineTestDriver) Start(path string) {
|
|
||||||
d.needPrograms("wine")
|
|
||||||
|
|
||||||
// First, build the app.
|
|
||||||
bin := filepath.Join(d.tempDir("gio-endtoend-windows"), "red.exe")
|
|
||||||
flags := []string{"build", "-o=" + bin}
|
|
||||||
if raceEnabled {
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
// cross-compilation disables CGo, which breaks -race.
|
|
||||||
d.Skipf("can't cross-compile -race for Windows; skipping")
|
|
||||||
}
|
|
||||||
flags = append(flags, "-race")
|
|
||||||
}
|
|
||||||
flags = append(flags, path)
|
|
||||||
cmd := exec.Command("go", flags...)
|
|
||||||
cmd.Env = os.Environ()
|
|
||||||
cmd.Env = append(cmd.Env, "GOOS=windows")
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
d.Fatalf("could not build app: %s:\n%s", err, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
d.Cleanup(wg.Wait)
|
|
||||||
|
|
||||||
// Add 50x50px to the display dimensions, as discussed earlier.
|
|
||||||
d.startServer(&wg, d.width+50, d.height+50)
|
|
||||||
|
|
||||||
// Then, start our program via Wine on the X server above.
|
|
||||||
{
|
|
||||||
cacheDir, err := os.UserCacheDir()
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
// Use a wine directory separate from the default ~/.wine, so
|
|
||||||
// that the user's winecfg doesn't affect our test. This will
|
|
||||||
// default to ~/.cache/gio-e2e-wine. We use the user's cache,
|
|
||||||
// to reuse a previously set up wineprefix.
|
|
||||||
wineprefix := filepath.Join(cacheDir, "gio-e2e-wine")
|
|
||||||
|
|
||||||
// First, ensure that wineprefix is up to date with wineboot.
|
|
||||||
// Wait for this separately from the first frame, as setting up
|
|
||||||
// a new prefix might take 5s on its own.
|
|
||||||
env := []string{
|
|
||||||
"DISPLAY=" + d.display,
|
|
||||||
"WINEDEBUG=fixme-all", // hide "fixme" noise
|
|
||||||
"WINEPREFIX=" + wineprefix,
|
|
||||||
|
|
||||||
// Disable wine-gecko (Explorer) and wine-mono (.NET).
|
|
||||||
// Otherwise, if not installed, wineboot will get stuck
|
|
||||||
// with a prompt to install them on the virtual X
|
|
||||||
// display. Moreover, Gio doesn't need either, and wine
|
|
||||||
// is faster without them.
|
|
||||||
"WINEDLLOVERRIDES=mscoree,mshtml=",
|
|
||||||
}
|
|
||||||
{
|
|
||||||
start := time.Now()
|
|
||||||
cmd := exec.Command("wine", "wineboot", "-i")
|
|
||||||
cmd.Env = env
|
|
||||||
// Use a combined output pipe instead of CombinedOutput,
|
|
||||||
// so that we only wait for the child process to exit,
|
|
||||||
// and we don't need to wait for all of wine's
|
|
||||||
// grandchildren to exit and stop writing. This is
|
|
||||||
// relevant as wine leaves "wineserver" lingering for
|
|
||||||
// three seconds by default, to be reused later.
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
cmd.Stderr = cmd.Stdout
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
io.Copy(os.Stderr, stdout)
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
d.Logf("set up WINEPREFIX in %s", time.Since(start))
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
cmd := exec.CommandContext(ctx, "wine", bin)
|
|
||||||
cmd.Env = env
|
|
||||||
output, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
cmd.Stderr = cmd.Stdout
|
|
||||||
d.output = output
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
d.Cleanup(cancel)
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
if err := cmd.Wait(); err != nil && ctx.Err() == nil {
|
|
||||||
d.Error(err)
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
// Wait for the gio app to render.
|
|
||||||
d.waitForFrame()
|
|
||||||
|
|
||||||
// xdotool seems to fail at actually moving the window if we use it
|
|
||||||
// immediately after Gio is ready. Why?
|
|
||||||
// We can't tell if the windowmove operation worked until we take a
|
|
||||||
// screenshot, because the getwindowgeometry op reports the 0x0
|
|
||||||
// coordinates even if the window wasn't moved properly.
|
|
||||||
// A sleep of ~20ms seems to be enough on an idle laptop. Use 20x that.
|
|
||||||
// TODO(mvdan): revisit this, when you have a spare three hours.
|
|
||||||
time.Sleep(400 * time.Millisecond)
|
|
||||||
id := d.xdotool("search", "--sync", "--onlyvisible", "--name", "Gio")
|
|
||||||
d.xdotool("windowmove", "--sync", id, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *WineTestDriver) Screenshot() image.Image {
|
|
||||||
img := d.X11TestDriver.Screenshot()
|
|
||||||
// Crop the screenshot back to the original dimensions.
|
|
||||||
cropped := image.NewRGBA(image.Rect(0, 0, d.width, d.height))
|
|
||||||
draw.Draw(cropped, cropped.Bounds(), img, image.Point{}, draw.Src)
|
|
||||||
return cropped
|
|
||||||
}
|
|
||||||
@@ -1,416 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"image/png"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/akavel/rsrc/binutil"
|
|
||||||
"github.com/akavel/rsrc/coff"
|
|
||||||
"golang.org/x/text/encoding/unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildWindows(tmpDir string, bi *buildInfo) error {
|
|
||||||
builder := &windowsBuilder{TempDir: tmpDir}
|
|
||||||
builder.DestDir = *destPath
|
|
||||||
if builder.DestDir == "" {
|
|
||||||
builder.DestDir = bi.pkgPath
|
|
||||||
}
|
|
||||||
|
|
||||||
name := bi.name
|
|
||||||
if *destPath != "" {
|
|
||||||
if filepath.Ext(*destPath) != ".exe" {
|
|
||||||
return fmt.Errorf("invalid output name %q, it must end with `.exe`", *destPath)
|
|
||||||
}
|
|
||||||
name = filepath.Base(*destPath)
|
|
||||||
}
|
|
||||||
name = strings.TrimSuffix(name, ".exe")
|
|
||||||
sdk := bi.minsdk
|
|
||||||
if sdk > 10 {
|
|
||||||
return fmt.Errorf("invalid minsdk (%d) it's higher than Windows 10", sdk)
|
|
||||||
}
|
|
||||||
version := strconv.Itoa(bi.version)
|
|
||||||
if bi.version > math.MaxUint16 {
|
|
||||||
return fmt.Errorf("version (%d) is larger than the maximum (%d)", bi.version, math.MaxUint16)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, arch := range bi.archs {
|
|
||||||
builder.Coff = coff.NewRSRC()
|
|
||||||
builder.Coff.Arch(arch)
|
|
||||||
|
|
||||||
if err := builder.embedIcon(bi.iconPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := builder.embedManifest(windowsManifest{
|
|
||||||
Version: "1.0.0." + version,
|
|
||||||
WindowsVersion: sdk,
|
|
||||||
Name: name,
|
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("can't create manifest: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := builder.embedInfo(windowsResources{
|
|
||||||
Version: [2]uint32{uint32(1) << 16, uint32(bi.version)},
|
|
||||||
VersionHuman: "1.0.0." + version,
|
|
||||||
Name: name,
|
|
||||||
Language: 0x0400, // Process Default Language: https://docs.microsoft.com/en-us/previous-versions/ms957130(v=msdn.10)
|
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("can't create info: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := builder.buildResource(bi, name, arch); err != nil {
|
|
||||||
return fmt.Errorf("can't build the resources: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := builder.buildProgram(bi, name, arch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
windowsResources struct {
|
|
||||||
Version [2]uint32
|
|
||||||
VersionHuman string
|
|
||||||
Language uint16
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
windowsManifest struct {
|
|
||||||
Version string
|
|
||||||
WindowsVersion int
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
windowsBuilder struct {
|
|
||||||
TempDir string
|
|
||||||
DestDir string
|
|
||||||
Coff *coff.Coff
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/menurc/resource-types
|
|
||||||
windowsResourceIcon = 3
|
|
||||||
windowsResourceIconGroup = windowsResourceIcon + 11
|
|
||||||
windowsResourceManifest = 24
|
|
||||||
windowsResourceVersion = 16
|
|
||||||
)
|
|
||||||
|
|
||||||
type bufferCoff struct {
|
|
||||||
bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bufferCoff) Size() int64 {
|
|
||||||
return int64(b.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *windowsBuilder) embedIcon(path string) (err error) {
|
|
||||||
iconFile, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("can't read the icon located at %s: %v", path, err)
|
|
||||||
}
|
|
||||||
defer iconFile.Close()
|
|
||||||
|
|
||||||
iconImage, err := png.Decode(iconFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't decode the PNG file (%s): %v", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sizes := []int{16, 32, 48, 64, 128, 256}
|
|
||||||
var iconHeader bufferCoff
|
|
||||||
|
|
||||||
// GRPICONDIR structure.
|
|
||||||
if err := binary.Write(&iconHeader, binary.LittleEndian, [3]uint16{0, 1, uint16(len(sizes))}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, size := range sizes {
|
|
||||||
var iconBuffer bufferCoff
|
|
||||||
|
|
||||||
if err := png.Encode(&iconBuffer, resizeIcon(iconVariant{size: size, fill: false}, iconImage)); err != nil {
|
|
||||||
return fmt.Errorf("can't encode image: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Coff.AddResource(windowsResourceIcon, uint16(size), &iconBuffer)
|
|
||||||
|
|
||||||
if err := binary.Write(&iconHeader, binary.LittleEndian, struct {
|
|
||||||
Size [2]uint8
|
|
||||||
Color [2]uint8
|
|
||||||
Planes uint16
|
|
||||||
BitCount uint16
|
|
||||||
Length uint32
|
|
||||||
Id uint16
|
|
||||||
}{
|
|
||||||
Size: [2]uint8{uint8(size % 256), uint8(size % 256)}, // "0" means 256px.
|
|
||||||
Planes: 1,
|
|
||||||
BitCount: 32,
|
|
||||||
Length: uint32(iconBuffer.Len()),
|
|
||||||
Id: uint16(size),
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Coff.AddResource(windowsResourceIconGroup, 1, &iconHeader)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *windowsBuilder) buildResource(buildInfo *buildInfo, name string, arch string) error {
|
|
||||||
out, err := os.Create(filepath.Join(buildInfo.pkgPath, name+"_windows_"+arch+".syso"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
b.Coff.Freeze()
|
|
||||||
|
|
||||||
// See https://github.com/akavel/rsrc/internal/write.go#L13.
|
|
||||||
w := binutil.Writer{W: out}
|
|
||||||
binutil.Walk(b.Coff, func(v reflect.Value, path string) error {
|
|
||||||
if binutil.Plain(v.Kind()) {
|
|
||||||
w.WriteLE(v.Interface())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
vv, ok := v.Interface().(binutil.SizedReader)
|
|
||||||
if ok {
|
|
||||||
w.WriteFromSized(vv)
|
|
||||||
return binutil.WALK_SKIP
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if w.Err != nil {
|
|
||||||
return fmt.Errorf("error writing output file: %s", w.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *windowsBuilder) buildProgram(buildInfo *buildInfo, name string, arch string) error {
|
|
||||||
dest := b.DestDir
|
|
||||||
if len(buildInfo.archs) > 1 {
|
|
||||||
dest = filepath.Join(filepath.Dir(b.DestDir), name+"_"+arch+".exe")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(
|
|
||||||
"go",
|
|
||||||
"build",
|
|
||||||
"-ldflags=-H=windowsgui "+buildInfo.ldflags,
|
|
||||||
"-tags="+buildInfo.tags,
|
|
||||||
"-o", dest,
|
|
||||||
buildInfo.pkgPath,
|
|
||||||
)
|
|
||||||
cmd.Env = append(
|
|
||||||
os.Environ(),
|
|
||||||
"GOOS=windows",
|
|
||||||
"GOARCH="+arch,
|
|
||||||
)
|
|
||||||
_, err := runCmd(cmd)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *windowsBuilder) embedManifest(v windowsManifest) error {
|
|
||||||
t, err := template.New("manifest").Parse(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
|
||||||
<assemblyIdentity type="win32" name="{{.Name}}" version="{{.Version}}" />
|
|
||||||
<description>{{.Name}}</description>
|
|
||||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
|
||||||
<application>
|
|
||||||
{{if (le .WindowsVersion 10)}}<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
|
||||||
{{end}}
|
|
||||||
{{if (le .WindowsVersion 9)}}<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
|
||||||
{{end}}
|
|
||||||
{{if (le .WindowsVersion 8)}}<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
|
||||||
{{end}}
|
|
||||||
{{if (le .WindowsVersion 7)}}<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
|
||||||
{{end}}
|
|
||||||
{{if (le .WindowsVersion 6)}}<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
|
||||||
{{end}}
|
|
||||||
</application>
|
|
||||||
</compatibility>
|
|
||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
|
||||||
<security>
|
|
||||||
<requestedPrivileges>
|
|
||||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
|
||||||
</requestedPrivileges>
|
|
||||||
</security>
|
|
||||||
</trustInfo>
|
|
||||||
<asmv3:application>
|
|
||||||
<asmv3:windowsSettings>
|
|
||||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
|
||||||
</asmv3:windowsSettings>
|
|
||||||
</asmv3:application>
|
|
||||||
</assembly>`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var manifest bufferCoff
|
|
||||||
if err := t.Execute(&manifest, v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Coff.AddResource(windowsResourceManifest, 1, &manifest)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *windowsBuilder) embedInfo(v windowsResources) error {
|
|
||||||
page := uint16(1)
|
|
||||||
|
|
||||||
// https://docs.microsoft.com/pt-br/windows/win32/menurc/vs-versioninfo
|
|
||||||
t := newValue(valueBinary, "VS_VERSION_INFO", []io.WriterTo{
|
|
||||||
// https://docs.microsoft.com/pt-br/windows/win32/api/VerRsrc/ns-verrsrc-vs_fixedfileinfo
|
|
||||||
windowsInfoValueFixed{
|
|
||||||
Signature: 0xFEEF04BD,
|
|
||||||
StructVersion: 0x00010000,
|
|
||||||
FileVersion: v.Version,
|
|
||||||
ProductVersion: v.Version,
|
|
||||||
FileFlagMask: 0x3F,
|
|
||||||
FileFlags: 0,
|
|
||||||
FileOS: 0x40004,
|
|
||||||
FileType: 0x1,
|
|
||||||
FileSubType: 0,
|
|
||||||
},
|
|
||||||
// https://docs.microsoft.com/pt-br/windows/win32/menurc/stringfileinfo
|
|
||||||
newValue(valueText, "StringFileInfo", []io.WriterTo{
|
|
||||||
// https://docs.microsoft.com/pt-br/windows/win32/menurc/stringtable
|
|
||||||
newValue(valueText, fmt.Sprintf("%04X%04X", v.Language, page), []io.WriterTo{
|
|
||||||
// https://docs.microsoft.com/pt-br/windows/win32/menurc/string-str
|
|
||||||
newValue(valueText, "ProductVersion", v.VersionHuman),
|
|
||||||
newValue(valueText, "FileVersion", v.VersionHuman),
|
|
||||||
newValue(valueText, "FileDescription", v.Name),
|
|
||||||
newValue(valueText, "ProductName", v.Name),
|
|
||||||
// TODO include more data: gogio must have some way to provide such information (like Company Name, Copyright...)
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
// https://docs.microsoft.com/pt-br/windows/win32/menurc/varfileinfo
|
|
||||||
newValue(valueBinary, "VarFileInfo", []io.WriterTo{
|
|
||||||
// https://docs.microsoft.com/pt-br/windows/win32/menurc/var-str
|
|
||||||
newValue(valueBinary, "Translation", uint32(page)<<16|uint32(v.Language)),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
// For some reason the ValueLength of the VS_VERSIONINFO must be the byte-length of `windowsInfoValueFixed`:
|
|
||||||
t.ValueLength = 52
|
|
||||||
|
|
||||||
var verrsrc bufferCoff
|
|
||||||
if _, err := t.WriteTo(&verrsrc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Coff.AddResource(windowsResourceVersion, 1, &verrsrc)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type windowsInfoValueFixed struct {
|
|
||||||
Signature uint32
|
|
||||||
StructVersion uint32
|
|
||||||
FileVersion [2]uint32
|
|
||||||
ProductVersion [2]uint32
|
|
||||||
FileFlagMask uint32
|
|
||||||
FileFlags uint32
|
|
||||||
FileOS uint32
|
|
||||||
FileType uint32
|
|
||||||
FileSubType uint32
|
|
||||||
FileDate [2]uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v windowsInfoValueFixed) WriteTo(w io.Writer) (_ int64, err error) {
|
|
||||||
return 0, binary.Write(w, binary.LittleEndian, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
type windowsInfoValue struct {
|
|
||||||
Length uint16
|
|
||||||
ValueLength uint16
|
|
||||||
Type uint16
|
|
||||||
Key []byte
|
|
||||||
Value []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v windowsInfoValue) WriteTo(w io.Writer) (_ int64, err error) {
|
|
||||||
// binary.Write doesn't support []byte inside struct.
|
|
||||||
if err = binary.Write(w, binary.LittleEndian, [3]uint16{v.Length, v.ValueLength, v.Type}); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if _, err = w.Write(v.Key); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if _, err = w.Write(v.Value); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
valueBinary uint16 = 0
|
|
||||||
valueText uint16 = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
func newValue(valueType uint16, key string, input interface{}) windowsInfoValue {
|
|
||||||
v := windowsInfoValue{
|
|
||||||
Type: valueType,
|
|
||||||
Length: 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
padding := func(in []byte) []byte {
|
|
||||||
if l := uint16(len(in)) + v.Length; l%4 != 0 {
|
|
||||||
return append(in, make([]byte, 4-l%4)...)
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
v.Key = padding(utf16Encode(key))
|
|
||||||
v.Length += uint16(len(v.Key))
|
|
||||||
|
|
||||||
switch in := input.(type) {
|
|
||||||
case string:
|
|
||||||
v.Value = padding(utf16Encode(in))
|
|
||||||
v.ValueLength = uint16(len(v.Value) / 2)
|
|
||||||
case []io.WriterTo:
|
|
||||||
var buff bytes.Buffer
|
|
||||||
for k := range in {
|
|
||||||
if _, err := in[k].WriteTo(&buff); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v.Value = buff.Bytes()
|
|
||||||
default:
|
|
||||||
var buff bytes.Buffer
|
|
||||||
if err := binary.Write(&buff, binary.LittleEndian, in); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
v.ValueLength = uint16(buff.Len())
|
|
||||||
v.Value = buff.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
v.Length += uint16(len(v.Value))
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// utf16Encode encodes the string to UTF16 with null-termination.
|
|
||||||
func utf16Encode(s string) []byte {
|
|
||||||
b, err := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder().Bytes([]byte(s))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return append(b, 0x00, 0x00) // null-termination.
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package main_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/png"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type X11TestDriver struct {
|
|
||||||
driverBase
|
|
||||||
|
|
||||||
display string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *X11TestDriver) Start(path string) {
|
|
||||||
// First, build the app.
|
|
||||||
bin := filepath.Join(d.tempDir("gio-endtoend-x11"), "red")
|
|
||||||
flags := []string{"build", "-tags", "nowayland", "-o=" + bin}
|
|
||||||
if raceEnabled {
|
|
||||||
flags = append(flags, "-race")
|
|
||||||
}
|
|
||||||
flags = append(flags, path)
|
|
||||||
cmd := exec.Command("go", flags...)
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
d.Fatalf("could not build app: %s:\n%s", err, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
d.Cleanup(wg.Wait)
|
|
||||||
|
|
||||||
d.startServer(&wg, d.width, d.height)
|
|
||||||
|
|
||||||
// Then, start our program on the X server above.
|
|
||||||
{
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
cmd := exec.CommandContext(ctx, bin)
|
|
||||||
cmd.Env = []string{"DISPLAY=" + d.display}
|
|
||||||
output, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
cmd.Stderr = cmd.Stdout
|
|
||||||
d.output = output
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
d.Cleanup(cancel)
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
if err := cmd.Wait(); err != nil && ctx.Err() == nil {
|
|
||||||
d.Error(err)
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the gio app to render.
|
|
||||||
d.waitForFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *X11TestDriver) startServer(wg *sync.WaitGroup, width, height int) {
|
|
||||||
// Pick a random display number between 1 and 100,000. Most machines
|
|
||||||
// will only be using :0, so there's only a 0.001% chance of two
|
|
||||||
// concurrent test runs to run into a conflict.
|
|
||||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
||||||
d.display = fmt.Sprintf(":%d", rnd.Intn(100000)+1)
|
|
||||||
|
|
||||||
var xprog string
|
|
||||||
xflags := []string{
|
|
||||||
"-wr", // we want a white background; the default is black
|
|
||||||
}
|
|
||||||
if *headless {
|
|
||||||
xprog = "Xvfb" // virtual X server
|
|
||||||
xflags = append(xflags, "-screen", "0", fmt.Sprintf("%dx%dx24", width, height))
|
|
||||||
} else {
|
|
||||||
xprog = "Xephyr" // nested X server as a window
|
|
||||||
xflags = append(xflags, "-screen", fmt.Sprintf("%dx%d", width, height))
|
|
||||||
}
|
|
||||||
xflags = append(xflags, d.display)
|
|
||||||
|
|
||||||
d.needPrograms(
|
|
||||||
xprog, // to run the X server
|
|
||||||
"scrot", // to take screenshots
|
|
||||||
"xdotool", // to send input
|
|
||||||
)
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
cmd := exec.CommandContext(ctx, xprog, xflags...)
|
|
||||||
combined := &bytes.Buffer{}
|
|
||||||
cmd.Stdout = combined
|
|
||||||
cmd.Stderr = combined
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
d.Cleanup(cancel)
|
|
||||||
d.Cleanup(func() {
|
|
||||||
// Give it a chance to exit gracefully, cleaning up
|
|
||||||
// after itself. After 10ms, the deferred cancel above
|
|
||||||
// will signal an os.Kill.
|
|
||||||
cmd.Process.Signal(os.Interrupt)
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait for the X server to be ready. The socket path isn't
|
|
||||||
// terribly portable, but that's okay for now.
|
|
||||||
withRetries(d.T, time.Second, func() error {
|
|
||||||
socket := fmt.Sprintf("/tmp/.X11-unix/X%s", d.display[1:])
|
|
||||||
_, err := os.Stat(socket)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
if err := cmd.Wait(); err != nil && ctx.Err() == nil {
|
|
||||||
// Print all output and error.
|
|
||||||
io.Copy(os.Stdout, combined)
|
|
||||||
d.Error(err)
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *X11TestDriver) Screenshot() image.Image {
|
|
||||||
cmd := exec.Command("scrot", "--silent", "--overwrite", "/dev/stdout")
|
|
||||||
cmd.Env = []string{"DISPLAY=" + d.display}
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
d.Errorf("%s", out)
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
img, err := png.Decode(bytes.NewReader(out))
|
|
||||||
if err != nil {
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *X11TestDriver) xdotool(args ...interface{}) string {
|
|
||||||
d.Helper()
|
|
||||||
strs := make([]string, len(args))
|
|
||||||
for i, arg := range args {
|
|
||||||
strs[i] = fmt.Sprint(arg)
|
|
||||||
}
|
|
||||||
cmd := exec.Command("xdotool", strs...)
|
|
||||||
cmd.Env = []string{"DISPLAY=" + d.display}
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
d.Errorf("%s", out)
|
|
||||||
d.Fatal(err)
|
|
||||||
}
|
|
||||||
return string(bytes.TrimSpace(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *X11TestDriver) Click(x, y int) {
|
|
||||||
d.xdotool("mousemove", "--sync", x, y)
|
|
||||||
d.xdotool("click", "1")
|
|
||||||
|
|
||||||
// Wait for the gio app to render after this click.
|
|
||||||
d.waitForFrame()
|
|
||||||
}
|
|
||||||
+41
-3
@@ -3,11 +3,11 @@
|
|||||||
package f32
|
package f32
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Affine2D represents an affine 2D transformation. The zero value if Affine2D
|
// Affine2D represents an affine 2D transformation. The zero value of Affine2D
|
||||||
// represents the identity transform.
|
// represents the identity transform.
|
||||||
type Affine2D struct {
|
type Affine2D struct {
|
||||||
// in order to make the zero value of Affine2D represent the identity
|
// in order to make the zero value of Affine2D represent the identity
|
||||||
@@ -30,6 +30,15 @@ func NewAffine2D(sx, hx, ox, hy, sy, oy float32) Affine2D {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AffineId returns an identity transformation matrix that represents no transformation
|
||||||
|
// when applied.
|
||||||
|
func AffineId() Affine2D {
|
||||||
|
return NewAffine2D(
|
||||||
|
1, 0, 0,
|
||||||
|
0, 1, 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Offset the transformation.
|
// Offset the transformation.
|
||||||
func (a Affine2D) Offset(offset Point) Affine2D {
|
func (a Affine2D) Offset(offset Point) Affine2D {
|
||||||
return Affine2D{
|
return Affine2D{
|
||||||
@@ -112,6 +121,15 @@ func (a Affine2D) Elems() (sx, hx, ox, hy, sy, oy float32) {
|
|||||||
return a.a + 1, a.b, a.c, a.d, a.e + 1, a.f
|
return a.a + 1, a.b, a.c, a.d, a.e + 1, a.f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split a transform into two parts, one which is pure offset and the
|
||||||
|
// other representing the scaling, shearing and rotation part.
|
||||||
|
func (a Affine2D) Split() (srs Affine2D, offset Point) {
|
||||||
|
return Affine2D{
|
||||||
|
a: a.a, b: a.b, c: 0,
|
||||||
|
d: a.d, e: a.e, f: 0,
|
||||||
|
}, Point{X: a.c, Y: a.f}
|
||||||
|
}
|
||||||
|
|
||||||
func (a Affine2D) scale(factor Point) Affine2D {
|
func (a Affine2D) scale(factor Point) Affine2D {
|
||||||
return Affine2D{
|
return Affine2D{
|
||||||
(a.a+1)*factor.X - 1, a.b * factor.X, a.c * factor.X,
|
(a.a+1)*factor.X - 1, a.b * factor.X, a.c * factor.X,
|
||||||
@@ -139,5 +157,25 @@ func (a Affine2D) shear(radiansX, radiansY float32) Affine2D {
|
|||||||
|
|
||||||
func (a Affine2D) String() string {
|
func (a Affine2D) String() string {
|
||||||
sx, hx, ox, hy, sy, oy := a.Elems()
|
sx, hx, ox, hy, sy, oy := a.Elems()
|
||||||
return fmt.Sprintf("[[%f %f %f] [%f %f %f]]", sx, hx, ox, hy, sy, oy)
|
|
||||||
|
// precision 6, one period, negative sign and space per number
|
||||||
|
const prec = 6
|
||||||
|
const charsPerFloat = prec + 2 + 1
|
||||||
|
s := make([]byte, 0, 6*charsPerFloat+6)
|
||||||
|
|
||||||
|
s = append(s, '[', '[')
|
||||||
|
s = strconv.AppendFloat(s, float64(sx), 'g', prec, 32)
|
||||||
|
s = append(s, ' ')
|
||||||
|
s = strconv.AppendFloat(s, float64(hx), 'g', prec, 32)
|
||||||
|
s = append(s, ' ')
|
||||||
|
s = strconv.AppendFloat(s, float64(ox), 'g', prec, 32)
|
||||||
|
s = append(s, ']', ' ', '[')
|
||||||
|
s = strconv.AppendFloat(s, float64(hy), 'g', prec, 32)
|
||||||
|
s = append(s, ' ')
|
||||||
|
s = strconv.AppendFloat(s, float64(sy), 'g', prec, 32)
|
||||||
|
s = append(s, ' ')
|
||||||
|
s = strconv.AppendFloat(s, float64(oy), 'g', prec, 32)
|
||||||
|
s = append(s, ']', ']')
|
||||||
|
|
||||||
|
return string(s)
|
||||||
}
|
}
|
||||||
|
|||||||
+183
-30
@@ -27,25 +27,51 @@ func TestTransformOffset(t *testing.T) {
|
|||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
o := Point{X: 2, Y: -3}
|
o := Point{X: 2, Y: -3}
|
||||||
|
|
||||||
r := Affine2D{}.Offset(o).Transform(p)
|
r := AffineId().Offset(o).Transform(p)
|
||||||
if !eq(r, Pt(3, -1)) {
|
if !eq(r, Pt(3, -1)) {
|
||||||
t.Errorf("offset transformation mismatch: have %v, want {3 -1}", r)
|
t.Errorf("offset transformation mismatch: have %v, want {3 -1}", r)
|
||||||
}
|
}
|
||||||
i := Affine2D{}.Offset(o).Invert().Transform(r)
|
i := AffineId().Offset(o).Invert().Transform(r)
|
||||||
if !eq(i, p) {
|
if !eq(i, p) {
|
||||||
t.Errorf("offset transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("offset transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in Affine2D
|
||||||
|
exp string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
in: NewAffine2D(9, 11, 13, 17, 19, 23),
|
||||||
|
exp: "[[9 11 13] [17 19 23]]",
|
||||||
|
}, {
|
||||||
|
in: NewAffine2D(29, 31, 37, 43, 47, 53),
|
||||||
|
exp: "[[29 31 37] [43 47 53]]",
|
||||||
|
}, {
|
||||||
|
in: NewAffine2D(29.142342, 31.4123412, 37.53152, 43.51324213, 47.123412, 53.14312342),
|
||||||
|
exp: "[[29.1423 31.4123 37.5315] [43.5132 47.1234 53.1431]]",
|
||||||
|
}, {
|
||||||
|
in: AffineId(),
|
||||||
|
exp: "[[1 0 0] [0 1 0]]",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
if test.in.String() != test.exp {
|
||||||
|
t.Errorf("string mismatch: have %q, want %q", test.in.String(), test.exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTransformScale(t *testing.T) {
|
func TestTransformScale(t *testing.T) {
|
||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
s := Point{X: -1, Y: 2}
|
s := Point{X: -1, Y: 2}
|
||||||
|
|
||||||
r := Affine2D{}.Scale(Point{}, s).Transform(p)
|
r := AffineId().Scale(Point{}, s).Transform(p)
|
||||||
if !eq(r, Pt(-1, 4)) {
|
if !eq(r, Pt(-1, 4)) {
|
||||||
t.Errorf("scale transformation mismatch: have %v, want {-1 4}", r)
|
t.Errorf("scale transformation mismatch: have %v, want {-1 4}", r)
|
||||||
}
|
}
|
||||||
i := Affine2D{}.Scale(Point{}, s).Invert().Transform(r)
|
i := AffineId().Scale(Point{}, s).Invert().Transform(r)
|
||||||
if !eq(i, p) {
|
if !eq(i, p) {
|
||||||
t.Errorf("scale transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("scale transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -55,11 +81,11 @@ func TestTransformRotate(t *testing.T) {
|
|||||||
p := Point{X: 1, Y: 0}
|
p := Point{X: 1, Y: 0}
|
||||||
a := float32(math.Pi / 2)
|
a := float32(math.Pi / 2)
|
||||||
|
|
||||||
r := Affine2D{}.Rotate(Point{}, a).Transform(p)
|
r := AffineId().Rotate(Point{}, a).Transform(p)
|
||||||
if !eq(r, Pt(0, 1)) {
|
if !eq(r, Pt(0, 1)) {
|
||||||
t.Errorf("rotate transformation mismatch: have %v, want {0 1}", r)
|
t.Errorf("rotate transformation mismatch: have %v, want {0 1}", r)
|
||||||
}
|
}
|
||||||
i := Affine2D{}.Rotate(Point{}, a).Invert().Transform(r)
|
i := AffineId().Rotate(Point{}, a).Invert().Transform(r)
|
||||||
if !eq(i, p) {
|
if !eq(i, p) {
|
||||||
t.Errorf("rotate transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("rotate transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -68,11 +94,11 @@ func TestTransformRotate(t *testing.T) {
|
|||||||
func TestTransformShear(t *testing.T) {
|
func TestTransformShear(t *testing.T) {
|
||||||
p := Point{X: 1, Y: 1}
|
p := Point{X: 1, Y: 1}
|
||||||
|
|
||||||
r := Affine2D{}.Shear(Point{}, math.Pi/4, 0).Transform(p)
|
r := AffineId().Shear(Point{}, math.Pi/4, 0).Transform(p)
|
||||||
if !eq(r, Pt(2, 1)) {
|
if !eq(r, Pt(2, 1)) {
|
||||||
t.Errorf("shear transformation mismatch: have %v, want {2 1}", r)
|
t.Errorf("shear transformation mismatch: have %v, want {2 1}", r)
|
||||||
}
|
}
|
||||||
i := Affine2D{}.Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
i := AffineId().Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
||||||
if !eq(i, p) {
|
if !eq(i, p) {
|
||||||
t.Errorf("shear transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("shear transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -84,11 +110,11 @@ func TestTransformMultiply(t *testing.T) {
|
|||||||
s := Point{X: -1, Y: 2}
|
s := Point{X: -1, Y: 2}
|
||||||
a := float32(-math.Pi / 2)
|
a := float32(-math.Pi / 2)
|
||||||
|
|
||||||
r := Affine2D{}.Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Transform(p)
|
r := AffineId().Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Transform(p)
|
||||||
if !eq(r, Pt(1, 3)) {
|
if !eq(r, Pt(1, 3)) {
|
||||||
t.Errorf("complex transformation mismatch: have %v, want {1 3}", r)
|
t.Errorf("complex transformation mismatch: have %v, want {1 3}", r)
|
||||||
}
|
}
|
||||||
i := Affine2D{}.Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
i := AffineId().Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
||||||
if !eq(i, p) {
|
if !eq(i, p) {
|
||||||
t.Errorf("complex transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("complex transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -140,7 +166,7 @@ func TestPrimes(t *testing.T) {
|
|||||||
func TestTransformScaleAround(t *testing.T) {
|
func TestTransformScaleAround(t *testing.T) {
|
||||||
p := Pt(-1, -1)
|
p := Pt(-1, -1)
|
||||||
target := Pt(-6, -13)
|
target := Pt(-6, -13)
|
||||||
pt := Affine2D{}.Scale(Pt(4, 5), Pt(2, 3)).Transform(p)
|
pt := AffineId().Scale(Pt(4, 5), Pt(2, 3)).Transform(p)
|
||||||
if !eq(pt, target) {
|
if !eq(pt, target) {
|
||||||
t.Log(pt, "!=", target)
|
t.Log(pt, "!=", target)
|
||||||
t.Error("Scale not as expected")
|
t.Error("Scale not as expected")
|
||||||
@@ -149,7 +175,7 @@ func TestTransformScaleAround(t *testing.T) {
|
|||||||
|
|
||||||
func TestTransformRotateAround(t *testing.T) {
|
func TestTransformRotateAround(t *testing.T) {
|
||||||
p := Pt(-1, -1)
|
p := Pt(-1, -1)
|
||||||
pt := Affine2D{}.Rotate(Pt(1, 1), -math.Pi/2).Transform(p)
|
pt := AffineId().Rotate(Pt(1, 1), -math.Pi/2).Transform(p)
|
||||||
target := Pt(-1, 3)
|
target := Pt(-1, 3)
|
||||||
if !eq(pt, target) {
|
if !eq(pt, target) {
|
||||||
t.Log(pt, "!=", target)
|
t.Log(pt, "!=", target)
|
||||||
@@ -158,12 +184,12 @@ func TestTransformRotateAround(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMulOrder(t *testing.T) {
|
func TestMulOrder(t *testing.T) {
|
||||||
A := Affine2D{}.Offset(Pt(100, 100))
|
A := AffineId().Offset(Pt(100, 100))
|
||||||
B := Affine2D{}.Scale(Point{}, Pt(2, 2))
|
B := AffineId().Scale(Point{}, Pt(2, 2))
|
||||||
_ = A
|
_ = A
|
||||||
_ = B
|
_ = B
|
||||||
|
|
||||||
T1 := Affine2D{}.Offset(Pt(100, 100)).Scale(Point{}, Pt(2, 2))
|
T1 := AffineId().Offset(Pt(100, 100)).Scale(Point{}, Pt(2, 2))
|
||||||
T2 := B.Mul(A)
|
T2 := B.Mul(A)
|
||||||
|
|
||||||
if T1 != T2 {
|
if T1 != T2 {
|
||||||
@@ -176,9 +202,9 @@ func TestMulOrder(t *testing.T) {
|
|||||||
func BenchmarkTransformOffset(b *testing.B) {
|
func BenchmarkTransformOffset(b *testing.B) {
|
||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
o := Point{X: 0.5, Y: 0.5}
|
o := Point{X: 0.5, Y: 0.5}
|
||||||
aff := Affine2D{}.Offset(o)
|
aff := AffineId().Offset(o)
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
p = aff.Transform(p)
|
p = aff.Transform(p)
|
||||||
}
|
}
|
||||||
_ = p
|
_ = p
|
||||||
@@ -187,8 +213,8 @@ func BenchmarkTransformOffset(b *testing.B) {
|
|||||||
func BenchmarkTransformScale(b *testing.B) {
|
func BenchmarkTransformScale(b *testing.B) {
|
||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
s := Point{X: 0.5, Y: 0.5}
|
s := Point{X: 0.5, Y: 0.5}
|
||||||
aff := Affine2D{}.Scale(Point{}, s)
|
aff := AffineId().Scale(Point{}, s)
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
p = aff.Transform(p)
|
p = aff.Transform(p)
|
||||||
}
|
}
|
||||||
_ = p
|
_ = p
|
||||||
@@ -197,36 +223,163 @@ func BenchmarkTransformScale(b *testing.B) {
|
|||||||
func BenchmarkTransformRotate(b *testing.B) {
|
func BenchmarkTransformRotate(b *testing.B) {
|
||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
a := float32(math.Pi / 2)
|
a := float32(math.Pi / 2)
|
||||||
aff := Affine2D{}.Rotate(Point{}, a)
|
aff := AffineId().Rotate(Point{}, a)
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
p = aff.Transform(p)
|
p = aff.Transform(p)
|
||||||
}
|
}
|
||||||
_ = p
|
_ = p
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkTransformTranslateMultiply(b *testing.B) {
|
func BenchmarkTransformTranslateMultiply(b *testing.B) {
|
||||||
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||||
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5})
|
t := AffineId().Offset(Point{X: 0.5, Y: 0.5})
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
a = a.Mul(t)
|
a = a.Mul(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkTransformScaleMultiply(b *testing.B) {
|
func BenchmarkTransformScaleMultiply(b *testing.B) {
|
||||||
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||||
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Scale(Point{}, Point{X: 0.4, Y: -0.5})
|
t := AffineId().Offset(Point{X: 0.5, Y: 0.5}).Scale(Point{}, Point{X: 0.4, Y: -0.5})
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
a = a.Mul(t)
|
a = a.Mul(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkTransformMultiply(b *testing.B) {
|
func BenchmarkTransformMultiply(b *testing.B) {
|
||||||
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||||
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Rotate(Point{}, math.Pi/7)
|
t := AffineId().Offset(Point{X: 0.5, Y: 0.5}).Rotate(Point{}, math.Pi/7)
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
a = a.Mul(t)
|
a = a.Mul(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewAffine2D(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
sx, hx, ox, hy, sy, oy float32
|
||||||
|
expected Affine2D
|
||||||
|
}{
|
||||||
|
{1, 0, 0, 0, 1, 0, AffineId()},
|
||||||
|
{2, 0, 5, 0, 3, 7, Affine2D{a: 1, b: 0, c: 5, d: 0, e: 2, f: 7}},
|
||||||
|
{-1, 2, 3, 4, -5, 6, Affine2D{a: -2, b: 2, c: 3, d: 4, e: -6, f: 6}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
got := NewAffine2D(test.sx, test.hx, test.ox, test.hy, test.sy, test.oy)
|
||||||
|
if !eqaff(got, test.expected) {
|
||||||
|
t.Errorf(
|
||||||
|
"Test %d: NewAffine2D(%v, %v, %v, %v, %v, %v) = %v, want %v",
|
||||||
|
i, test.sx, test.hx, test.ox, test.hy, test.sy, test.oy, got, test.expected,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAffineId(t *testing.T) {
|
||||||
|
id := AffineId()
|
||||||
|
|
||||||
|
testPoints := []Point{
|
||||||
|
{0, 0},
|
||||||
|
{1, 0},
|
||||||
|
{0, 1},
|
||||||
|
{-1, -1},
|
||||||
|
{10, 20},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range testPoints {
|
||||||
|
transformed := id.Transform(p)
|
||||||
|
if !eq(transformed, p) {
|
||||||
|
t.Errorf("Identity transform changed point: %v -> %v", p, transformed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestElems(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
aff Affine2D
|
||||||
|
sx, hx, ox, hy, sy, oy float32
|
||||||
|
}{
|
||||||
|
{AffineId(), 1, 0, 0, 0, 1, 0},
|
||||||
|
{Affine2D{a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}, 2, 2, 3, 4, 6, 6},
|
||||||
|
{NewAffine2D(7, 8, 9, 10, 11, 12), 7, 8, 9, 10, 11, 12},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
sx, hx, ox, hy, sy, oy := test.aff.Elems()
|
||||||
|
if sx != test.sx || hx != test.hx || ox != test.ox ||
|
||||||
|
hy != test.hy || sy != test.sy || oy != test.oy {
|
||||||
|
t.Errorf(
|
||||||
|
"Test %d: %v.Elems() = (%v, %v, %v, %v, %v, %v), want (%v, %v, %v, %v, %v, %v)",
|
||||||
|
i, test.aff, sx, hx, ox, hy, sy, oy, test.sx, test.hx, test.ox, test.hy, test.sy, test.oy,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplit(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
aff Affine2D
|
||||||
|
expectedSRS Affine2D
|
||||||
|
expectedOffset Point
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
AffineId(),
|
||||||
|
AffineId(),
|
||||||
|
Point{0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Affine2D{a: 1, b: 2, c: 3, d: 4, e: 5, f: 6},
|
||||||
|
Affine2D{a: 1, b: 2, c: 0, d: 4, e: 5, f: 0},
|
||||||
|
Point{3, 6},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NewAffine2D(2, 0, 10, 0, 3, 20),
|
||||||
|
NewAffine2D(2, 0, 0, 0, 3, 0),
|
||||||
|
Point{10, 20},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
srs, offset := test.aff.Split()
|
||||||
|
if !eqaff(srs, test.expectedSRS) || !eq(offset, test.expectedOffset) {
|
||||||
|
t.Errorf(
|
||||||
|
"Test %d: %v.Split() = (%v, %v), want (%v, %v)",
|
||||||
|
i, test.aff, srs, offset, test.expectedSRS, test.expectedOffset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShear(t *testing.T) {
|
||||||
|
p := Pt(2, 3)
|
||||||
|
origin := Pt(1, 1)
|
||||||
|
|
||||||
|
shearX := AffineId().Shear(origin, math.Pi/4, 0)
|
||||||
|
resultX := shearX.Transform(p)
|
||||||
|
expectedX := Pt(4, 3)
|
||||||
|
|
||||||
|
if !eq(resultX, expectedX) {
|
||||||
|
t.Errorf("Shear around origin in X: got %v, want %v", resultX, expectedX)
|
||||||
|
}
|
||||||
|
|
||||||
|
inverseX := shearX.Invert().Transform(resultX)
|
||||||
|
if !eq(inverseX, p) {
|
||||||
|
t.Errorf("Inverse shear X: got %v, want %v", inverseX, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
shearY := AffineId().Shear(origin, 0, math.Pi/4)
|
||||||
|
resultY := shearY.Transform(p)
|
||||||
|
expectedY := Pt(2, 4)
|
||||||
|
|
||||||
|
if !eq(resultY, expectedY) {
|
||||||
|
t.Errorf("Shear around origin in Y: got %v, want %v", resultY, expectedY)
|
||||||
|
}
|
||||||
|
|
||||||
|
inverseY := shearY.Invert().Transform(resultY)
|
||||||
|
if !eq(inverseY, p) {
|
||||||
|
t.Errorf("Inverse shear Y: got %v, want %v", inverseY, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+6
-142
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
Package f32 is a float32 implementation of package image's
|
Package f32 is a float32 implementation of package image's
|
||||||
Point and Rectangle.
|
Point and affine transformations.
|
||||||
|
|
||||||
The coordinate space has the origin in the top left
|
The coordinate space has the origin in the top left
|
||||||
corner with the axes extending right and down.
|
corner with the axes extending right and down.
|
||||||
@@ -26,30 +26,6 @@ func (p Point) String() string {
|
|||||||
"," + strconv.FormatFloat(float64(p.Y), 'f', -1, 32) + ")"
|
"," + strconv.FormatFloat(float64(p.Y), 'f', -1, 32) + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
|
|
||||||
// Min.Y <= Y < Max.Y.
|
|
||||||
type Rectangle struct {
|
|
||||||
Min, Max Point
|
|
||||||
}
|
|
||||||
|
|
||||||
// String return a string representation of r.
|
|
||||||
func (r Rectangle) String() string {
|
|
||||||
return r.Min.String() + "-" + r.Max.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rect is a shorthand for Rectangle{Point{x0, y0}, Point{x1, y1}}.
|
|
||||||
// The returned Rectangle has x0 and y0 swapped if necessary so that
|
|
||||||
// it's correctly formed.
|
|
||||||
func Rect(x0, y0, x1, y1 float32) Rectangle {
|
|
||||||
if x0 > x1 {
|
|
||||||
x0, x1 = x1, x0
|
|
||||||
}
|
|
||||||
if y0 > y1 {
|
|
||||||
y0, y1 = y1, y0
|
|
||||||
}
|
|
||||||
return Rectangle{Point{x0, y0}, Point{x1, y1}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pt is shorthand for Point{X: x, Y: y}.
|
// Pt is shorthand for Point{X: x, Y: y}.
|
||||||
func Pt(x, y float32) Point {
|
func Pt(x, y float32) Point {
|
||||||
return Point{X: x, Y: y}
|
return Point{X: x, Y: y}
|
||||||
@@ -75,122 +51,10 @@ func (p Point) Div(s float32) Point {
|
|||||||
return Point{X: p.X / s, Y: p.Y / s}
|
return Point{X: p.X / s, Y: p.Y / s}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In reports whether p is in r.
|
// Round returns the integer point closest to p.
|
||||||
func (p Point) In(r Rectangle) bool {
|
func (p Point) Round() image.Point {
|
||||||
return r.Min.X <= p.X && p.X < r.Max.X &&
|
return image.Point{
|
||||||
r.Min.Y <= p.Y && p.Y < r.Max.Y
|
X: int(math.Round(float64(p.X))),
|
||||||
}
|
Y: int(math.Round(float64(p.Y))),
|
||||||
|
|
||||||
// Size returns r's width and height.
|
|
||||||
func (r Rectangle) Size() Point {
|
|
||||||
return Point{X: r.Dx(), Y: r.Dy()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dx returns r's width.
|
|
||||||
func (r Rectangle) Dx() float32 {
|
|
||||||
return r.Max.X - r.Min.X
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dy returns r's Height.
|
|
||||||
func (r Rectangle) Dy() float32 {
|
|
||||||
return r.Max.Y - r.Min.Y
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intersect returns the intersection of r and s.
|
|
||||||
func (r Rectangle) Intersect(s Rectangle) Rectangle {
|
|
||||||
if r.Min.X < s.Min.X {
|
|
||||||
r.Min.X = s.Min.X
|
|
||||||
}
|
|
||||||
if r.Min.Y < s.Min.Y {
|
|
||||||
r.Min.Y = s.Min.Y
|
|
||||||
}
|
|
||||||
if r.Max.X > s.Max.X {
|
|
||||||
r.Max.X = s.Max.X
|
|
||||||
}
|
|
||||||
if r.Max.Y > s.Max.Y {
|
|
||||||
r.Max.Y = s.Max.Y
|
|
||||||
}
|
|
||||||
if r.Empty() {
|
|
||||||
return Rectangle{}
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Union returns the union of r and s.
|
|
||||||
func (r Rectangle) Union(s Rectangle) Rectangle {
|
|
||||||
if r.Empty() {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
if s.Empty() {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
if r.Min.X > s.Min.X {
|
|
||||||
r.Min.X = s.Min.X
|
|
||||||
}
|
|
||||||
if r.Min.Y > s.Min.Y {
|
|
||||||
r.Min.Y = s.Min.Y
|
|
||||||
}
|
|
||||||
if r.Max.X < s.Max.X {
|
|
||||||
r.Max.X = s.Max.X
|
|
||||||
}
|
|
||||||
if r.Max.Y < s.Max.Y {
|
|
||||||
r.Max.Y = s.Max.Y
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Canon returns the canonical version of r, where Min is to
|
|
||||||
// the upper left of Max.
|
|
||||||
func (r Rectangle) Canon() Rectangle {
|
|
||||||
if r.Max.X < r.Min.X {
|
|
||||||
r.Min.X, r.Max.X = r.Max.X, r.Min.X
|
|
||||||
}
|
|
||||||
if r.Max.Y < r.Min.Y {
|
|
||||||
r.Min.Y, r.Max.Y = r.Max.Y, r.Min.Y
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty reports whether r represents the empty area.
|
|
||||||
func (r Rectangle) Empty() bool {
|
|
||||||
return r.Min.X >= r.Max.X || r.Min.Y >= r.Max.Y
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add offsets r with the vector p.
|
|
||||||
func (r Rectangle) Add(p Point) Rectangle {
|
|
||||||
return Rectangle{
|
|
||||||
Point{r.Min.X + p.X, r.Min.Y + p.Y},
|
|
||||||
Point{r.Max.X + p.X, r.Max.Y + p.Y},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sub offsets r with the vector -p.
|
|
||||||
func (r Rectangle) Sub(p Point) Rectangle {
|
|
||||||
return Rectangle{
|
|
||||||
Point{r.Min.X - p.X, r.Min.Y - p.Y},
|
|
||||||
Point{r.Max.X - p.X, r.Max.Y - p.Y},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Round returns the smallest integer rectangle that
|
|
||||||
// contains r.
|
|
||||||
func (r Rectangle) Round() image.Rectangle {
|
|
||||||
return image.Rectangle{
|
|
||||||
Min: image.Point{
|
|
||||||
X: int(floor(r.Min.X)),
|
|
||||||
Y: int(floor(r.Min.Y)),
|
|
||||||
},
|
|
||||||
Max: image.Point{
|
|
||||||
X: int(ceil(r.Max.X)),
|
|
||||||
Y: int(ceil(r.Max.Y)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ceil(v float32) int {
|
|
||||||
return int(math.Ceil(float64(v)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func floor(v float32) int {
|
|
||||||
return int(math.Floor(float64(v)))
|
|
||||||
}
|
|
||||||
|
|||||||
Generated
+39
-92
@@ -1,111 +1,58 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"android": {
|
|
||||||
"inputs": {
|
|
||||||
"devshell": "devshell",
|
|
||||||
"flake-utils": "flake-utils_2",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1648412532,
|
|
||||||
"narHash": "sha256-zh0rLcppJ5i2Bh8oBWjBhDvgOrMhDGdXINbp3bhrs0U=",
|
|
||||||
"owner": "tadfisher",
|
|
||||||
"repo": "android-nixpkgs",
|
|
||||||
"rev": "b1318b23926260685dbb09dd127f38c917fc7441",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "tadfisher",
|
|
||||||
"repo": "android-nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"devshell": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1647857022,
|
|
||||||
"narHash": "sha256-Aw70NWLOIwKhT60MHDGjgWis3DP3faCzr6ap9CSayek=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "devshell",
|
|
||||||
"rev": "0a5ff74dacb9ea22614f64e61aeb3ca0bf0e7311",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "devshell",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1642700792,
|
|
||||||
"narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1648297722,
|
|
||||||
"narHash": "sha256-W+qlPsiZd8F3XkzXOzAoR+mpFqzm3ekQkJNa+PIh1BQ=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "0f8662f1319ad6abf89b3380dd2722369fc51ade",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1643381941,
|
"lastModified": 1747953325,
|
||||||
"narHash": "sha256-pHTwvnN4tTsEKkWlXQ8JMY423epos8wUOhthpwJjtpc=",
|
"narHash": "sha256-y2ZtlIlNTuVJUZCqzZAhIw5rrKP4DOSklev6c8PyCkQ=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "5efc8ca954272c4376ac929f4c5ffefcc20551d5",
|
"rev": "55d1f923c480dadce40f5231feb472e81b0bab48",
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1648469949,
|
|
||||||
"narHash": "sha256-ExCG9k36QNs0bNsi2NwnfL4w/kjb661rW43pf03ok/Y=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "a63a39e23873d5d72753ff12bb418cb7e470790c",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-25.05",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"android": "android",
|
"nixpkgs": "nixpkgs",
|
||||||
"nixpkgs": "nixpkgs_2"
|
"utils": "utils"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,42 +3,38 @@
|
|||||||
description = "Gio build environment";
|
description = "Gio build environment";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||||
android.url = "github:tadfisher/android-nixpkgs";
|
utils.url = "github:numtide/flake-utils";
|
||||||
android.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, android }:
|
outputs = { self, nixpkgs, utils }:
|
||||||
let
|
utils.lib.eachDefaultSystem (system:
|
||||||
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
let
|
||||||
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
|
pkgs = import nixpkgs {
|
||||||
in
|
inherit system;
|
||||||
{
|
|
||||||
devShells = forAllSystems
|
# allow unfree Android packages.
|
||||||
(system:
|
config.allowUnfree = true;
|
||||||
let
|
# accept the Android SDK license.
|
||||||
pkgs = import nixpkgs {
|
config.android_sdk.accept_license = true;
|
||||||
inherit system;
|
};
|
||||||
|
in {
|
||||||
|
devShells = let
|
||||||
|
android-sdk = let
|
||||||
|
androidComposition = pkgs.androidenv.composeAndroidPackages {
|
||||||
|
platformVersions = [ "latest" ];
|
||||||
|
abiVersions = [ "armeabi-v7a" "arm64-v8a" ];
|
||||||
|
# Omit the deprecated tools package.
|
||||||
|
toolsVersion = null;
|
||||||
|
includeNDK = true;
|
||||||
};
|
};
|
||||||
android-sdk = android.sdk.${system} (sdkPkgs: with sdkPkgs;
|
in androidComposition.androidsdk;
|
||||||
[
|
in {
|
||||||
build-tools-31-0-0
|
default = with pkgs;
|
||||||
cmdline-tools-latest
|
mkShell (rec {
|
||||||
platform-tools
|
ANDROID_HOME = "${android-sdk}/libexec/android-sdk";
|
||||||
platforms-android-31
|
packages = [ android-sdk jdk clang ]
|
||||||
ndk-bundle
|
++ (if stdenv.isLinux then [
|
||||||
]);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
default = with pkgs; mkShell
|
|
||||||
({
|
|
||||||
ANDROID_SDK_ROOT = "${android-sdk}/share/android-sdk";
|
|
||||||
JAVA_HOME = jdk8.home;
|
|
||||||
packages = [
|
|
||||||
android-sdk
|
|
||||||
jdk8
|
|
||||||
clang
|
|
||||||
] ++ (if stdenv.isLinux then [
|
|
||||||
vulkan-headers
|
vulkan-headers
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
wayland
|
wayland
|
||||||
@@ -46,12 +42,13 @@
|
|||||||
xorg.libXcursor
|
xorg.libXcursor
|
||||||
xorg.libXfixes
|
xorg.libXfixes
|
||||||
libGL
|
libGL
|
||||||
pkgconfig
|
pkg-config
|
||||||
] else [ ]);
|
] else
|
||||||
} // (if stdenv.isLinux then {
|
[ ]);
|
||||||
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
|
} // (if stdenv.isLinux then {
|
||||||
} else { }));
|
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
|
||||||
}
|
} else
|
||||||
);
|
{ }));
|
||||||
};
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+126
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
Package font provides type describing font faces attributes.
|
||||||
|
*/
|
||||||
|
package font
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-text/typesetting/font"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A FontFace is a Font and a matching Face.
|
||||||
|
type FontFace struct {
|
||||||
|
Font Font
|
||||||
|
Face Face
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style is the font style.
|
||||||
|
type Style int
|
||||||
|
|
||||||
|
// Weight is a font weight, in CSS units subtracted 400 so the zero value
|
||||||
|
// is normal text weight.
|
||||||
|
type Weight int
|
||||||
|
|
||||||
|
// Font specify a particular typeface variant, style and weight.
|
||||||
|
type Font struct {
|
||||||
|
// Typeface specifies the name(s) of the the font faces to try. See [Typeface]
|
||||||
|
// for details.
|
||||||
|
Typeface Typeface
|
||||||
|
// Style specifies the kind of text style.
|
||||||
|
Style Style
|
||||||
|
// Weight is the text weight.
|
||||||
|
Weight Weight
|
||||||
|
}
|
||||||
|
|
||||||
|
// Face is an opaque handle to a typeface. The concrete implementation depends
|
||||||
|
// upon the kind of font and shaper in use.
|
||||||
|
type Face interface {
|
||||||
|
Face() *font.Face
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typeface identifies a list of font families to attempt to use for displaying
|
||||||
|
// a string. The syntax is a comma-delimited list of family names. In order to
|
||||||
|
// allow for the remote possibility of needing to express a font family name
|
||||||
|
// containing a comma, name entries may be quoted using either single or double
|
||||||
|
// quotes. Within quotes, a literal quotation mark can be expressed by escaping
|
||||||
|
// it with `\`. A literal backslash may be expressed by escaping it with another
|
||||||
|
// `\`.
|
||||||
|
//
|
||||||
|
// Here's an example Typeface:
|
||||||
|
//
|
||||||
|
// Times New Roman, Georgia, serif
|
||||||
|
//
|
||||||
|
// This is equivalent to the above:
|
||||||
|
//
|
||||||
|
// "Times New Roman", 'Georgia', serif
|
||||||
|
//
|
||||||
|
// Here are some valid uses of escape sequences:
|
||||||
|
//
|
||||||
|
// "Contains a literal \" doublequote", 'Literal \' Singlequote', "\\ Literal backslash", '\\ another'
|
||||||
|
//
|
||||||
|
// This syntax has the happy side effect that most CSS "font-family" rules are
|
||||||
|
// valid Typefaces (without the trailing semicolon).
|
||||||
|
//
|
||||||
|
// Generic CSS font families are supported, and are automatically expanded to lists
|
||||||
|
// of known font families with a matching style. The supported generic families are:
|
||||||
|
//
|
||||||
|
// - fantasy
|
||||||
|
// - math
|
||||||
|
// - emoji
|
||||||
|
// - serif
|
||||||
|
// - sans-serif
|
||||||
|
// - cursive
|
||||||
|
// - monospace
|
||||||
|
type Typeface string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Regular Style = iota
|
||||||
|
Italic
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Thin Weight = -300
|
||||||
|
ExtraLight Weight = -200
|
||||||
|
Light Weight = -100
|
||||||
|
Normal Weight = 0
|
||||||
|
Medium Weight = 100
|
||||||
|
SemiBold Weight = 200
|
||||||
|
Bold Weight = 300
|
||||||
|
ExtraBold Weight = 400
|
||||||
|
Black Weight = 500
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s Style) String() string {
|
||||||
|
switch s {
|
||||||
|
case Regular:
|
||||||
|
return "Regular"
|
||||||
|
case Italic:
|
||||||
|
return "Italic"
|
||||||
|
default:
|
||||||
|
panic("invalid Style")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w Weight) String() string {
|
||||||
|
switch w {
|
||||||
|
case Thin:
|
||||||
|
return "Thin"
|
||||||
|
case ExtraLight:
|
||||||
|
return "ExtraLight"
|
||||||
|
case Light:
|
||||||
|
return "Light"
|
||||||
|
case Normal:
|
||||||
|
return "Normal"
|
||||||
|
case Medium:
|
||||||
|
return "Medium"
|
||||||
|
case SemiBold:
|
||||||
|
return "SemiBold"
|
||||||
|
case Bold:
|
||||||
|
return "Bold"
|
||||||
|
case ExtraBold:
|
||||||
|
return "ExtraBold"
|
||||||
|
case Black:
|
||||||
|
return "Black"
|
||||||
|
default:
|
||||||
|
panic("invalid Weight")
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
-19
@@ -24,29 +24,49 @@ import (
|
|||||||
"golang.org/x/image/font/gofont/gosmallcaps"
|
"golang.org/x/image/font/gofont/gosmallcaps"
|
||||||
"golang.org/x/image/font/gofont/gosmallcapsitalic"
|
"golang.org/x/image/font/gofont/gosmallcapsitalic"
|
||||||
|
|
||||||
|
"gioui.org/font"
|
||||||
"gioui.org/font/opentype"
|
"gioui.org/font/opentype"
|
||||||
"gioui.org/text"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
regOnce sync.Once
|
||||||
|
reg []font.FontFace
|
||||||
once sync.Once
|
once sync.Once
|
||||||
collection []text.FontFace
|
collection []font.FontFace
|
||||||
)
|
)
|
||||||
|
|
||||||
func Collection() []text.FontFace {
|
func loadRegular() {
|
||||||
|
regOnce.Do(func() {
|
||||||
|
faces, err := opentype.ParseCollection(goregular.TTF)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to parse font: %v", err))
|
||||||
|
}
|
||||||
|
reg = faces
|
||||||
|
collection = append(collection, reg[0])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular returns a collection of only the Go regular font face.
|
||||||
|
func Regular() []font.FontFace {
|
||||||
|
loadRegular()
|
||||||
|
return reg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular returns a collection of all available Go font faces.
|
||||||
|
func Collection() []font.FontFace {
|
||||||
|
loadRegular()
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
register(text.Font{}, goregular.TTF)
|
register(goitalic.TTF)
|
||||||
register(text.Font{Style: text.Italic}, goitalic.TTF)
|
register(gobold.TTF)
|
||||||
register(text.Font{Weight: text.Bold}, gobold.TTF)
|
register(gobolditalic.TTF)
|
||||||
register(text.Font{Style: text.Italic, Weight: text.Bold}, gobolditalic.TTF)
|
register(gomedium.TTF)
|
||||||
register(text.Font{Weight: text.Medium}, gomedium.TTF)
|
register(gomediumitalic.TTF)
|
||||||
register(text.Font{Weight: text.Medium, Style: text.Italic}, gomediumitalic.TTF)
|
register(gomono.TTF)
|
||||||
register(text.Font{Variant: "Mono"}, gomono.TTF)
|
register(gomonobold.TTF)
|
||||||
register(text.Font{Variant: "Mono", Weight: text.Bold}, gomonobold.TTF)
|
register(gomonobolditalic.TTF)
|
||||||
register(text.Font{Variant: "Mono", Weight: text.Bold, Style: text.Italic}, gomonobolditalic.TTF)
|
register(gomonoitalic.TTF)
|
||||||
register(text.Font{Variant: "Mono", Style: text.Italic}, gomonoitalic.TTF)
|
register(gosmallcaps.TTF)
|
||||||
register(text.Font{Variant: "Smallcaps"}, gosmallcaps.TTF)
|
register(gosmallcapsitalic.TTF)
|
||||||
register(text.Font{Variant: "Smallcaps", Style: text.Italic}, gosmallcapsitalic.TTF)
|
|
||||||
// Ensure that any outside appends will not reuse the backing store.
|
// Ensure that any outside appends will not reuse the backing store.
|
||||||
n := len(collection)
|
n := len(collection)
|
||||||
collection = collection[:n:n]
|
collection = collection[:n:n]
|
||||||
@@ -54,11 +74,10 @@ func Collection() []text.FontFace {
|
|||||||
return collection
|
return collection
|
||||||
}
|
}
|
||||||
|
|
||||||
func register(fnt text.Font, ttf []byte) {
|
func register(ttf []byte) {
|
||||||
face, err := opentype.Parse(ttf)
|
faces, err := opentype.ParseCollection(ttf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("failed to parse font: %v", err))
|
panic(fmt.Errorf("failed to parse font: %v", err))
|
||||||
}
|
}
|
||||||
fnt.Typeface = "Go"
|
collection = append(collection, faces[0])
|
||||||
collection = append(collection, text.FontFace{Font: fnt, Face: face})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,530 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"gioui.org/io/system"
|
|
||||||
"gioui.org/text"
|
|
||||||
"github.com/benoitkugler/textlayout/language"
|
|
||||||
"github.com/gioui/uax/segment"
|
|
||||||
"github.com/gioui/uax/uax14"
|
|
||||||
"github.com/go-text/typesetting/di"
|
|
||||||
"github.com/go-text/typesetting/font"
|
|
||||||
"github.com/go-text/typesetting/shaping"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
// computeGlyphClusters populates the Clusters field of a Layout.
|
|
||||||
// The order of the clusters is visual, meaning
|
|
||||||
// that the first cluster is the leftmost cluster displayed even when
|
|
||||||
// the cluster is part of RTL text.
|
|
||||||
func computeGlyphClusters(l *text.Layout) {
|
|
||||||
clusters := make([]text.GlyphCluster, 0, len(l.Glyphs)+1)
|
|
||||||
if len(l.Glyphs) < 1 {
|
|
||||||
if l.Runes.Count > 0 {
|
|
||||||
// Empty line corresponding to a newline character.
|
|
||||||
clusters = append(clusters, text.GlyphCluster{
|
|
||||||
Runes: text.Range{
|
|
||||||
Count: 1,
|
|
||||||
Offset: l.Runes.Offset,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
l.Clusters = clusters
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rtl := l.Direction == system.RTL
|
|
||||||
|
|
||||||
// Check for trailing whitespace characters and synthesize
|
|
||||||
// GlyphClusters to represent them.
|
|
||||||
lastGlyph := l.Glyphs[len(l.Glyphs)-1]
|
|
||||||
if rtl {
|
|
||||||
lastGlyph = l.Glyphs[0]
|
|
||||||
}
|
|
||||||
trailingNewline := lastGlyph.ClusterIndex+lastGlyph.RuneCount < l.Runes.Count+l.Runes.Offset
|
|
||||||
newlineCluster := text.GlyphCluster{
|
|
||||||
Runes: text.Range{
|
|
||||||
Count: 1,
|
|
||||||
Offset: l.Runes.Count + l.Runes.Offset - 1,
|
|
||||||
},
|
|
||||||
Glyphs: text.Range{
|
|
||||||
Offset: len(l.Glyphs),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
i int = 0
|
|
||||||
inc int = 1
|
|
||||||
runesProcessed int = 0
|
|
||||||
glyphsProcessed int = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
if rtl {
|
|
||||||
i = len(l.Glyphs) - 1
|
|
||||||
inc = -inc
|
|
||||||
glyphsProcessed = len(l.Glyphs) - 1
|
|
||||||
newlineCluster.Glyphs.Offset = 0
|
|
||||||
}
|
|
||||||
// Construct clusters from the line's glyphs.
|
|
||||||
for ; i < len(l.Glyphs) && i >= 0; i += inc {
|
|
||||||
g := l.Glyphs[i]
|
|
||||||
xAdv := g.XAdvance * fixed.Int26_6(inc)
|
|
||||||
for k := 0; k < g.GlyphCount-1 && k < len(l.Glyphs); k++ {
|
|
||||||
i += inc
|
|
||||||
xAdv += l.Glyphs[i].XAdvance * fixed.Int26_6(inc)
|
|
||||||
}
|
|
||||||
|
|
||||||
startRune := runesProcessed
|
|
||||||
runeIncrement := g.RuneCount
|
|
||||||
startGlyph := glyphsProcessed
|
|
||||||
glyphIncrement := g.GlyphCount * inc
|
|
||||||
if rtl {
|
|
||||||
startGlyph = glyphsProcessed + glyphIncrement + 1
|
|
||||||
}
|
|
||||||
clusters = append(clusters, text.GlyphCluster{
|
|
||||||
Advance: xAdv,
|
|
||||||
Runes: text.Range{
|
|
||||||
Count: g.RuneCount,
|
|
||||||
Offset: startRune + l.Runes.Offset,
|
|
||||||
},
|
|
||||||
Glyphs: text.Range{
|
|
||||||
Count: g.GlyphCount,
|
|
||||||
Offset: startGlyph,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
runesProcessed += runeIncrement
|
|
||||||
glyphsProcessed += glyphIncrement
|
|
||||||
}
|
|
||||||
// Insert synthetic clusters at the right edge of the line.
|
|
||||||
if trailingNewline {
|
|
||||||
clusters = append(clusters, newlineCluster)
|
|
||||||
}
|
|
||||||
l.Clusters = clusters
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// langConfig describes the language and writing system of a body of text.
|
|
||||||
type langConfig struct {
|
|
||||||
// Language the text is written in.
|
|
||||||
language.Language
|
|
||||||
// Writing system used to represent the text.
|
|
||||||
language.Script
|
|
||||||
// Direction of the text, usually driven by the writing system.
|
|
||||||
di.Direction
|
|
||||||
}
|
|
||||||
|
|
||||||
// mapRunesToClusterIndices returns a slice. Each index within that slice corresponds
|
|
||||||
// to an index within the runes input slice. The value stored at that index is the
|
|
||||||
// index of the glyph at the start of the corresponding glyph cluster shaped by
|
|
||||||
// harfbuzz.
|
|
||||||
func mapRunesToClusterIndices(runes []rune, glyphs []shaping.Glyph) []int {
|
|
||||||
mapping := make([]int, len(runes))
|
|
||||||
glyphCursor := 0
|
|
||||||
if len(runes) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// If the final cluster values are lower than the starting ones,
|
|
||||||
// the text is RTL.
|
|
||||||
rtl := len(glyphs) > 0 && glyphs[len(glyphs)-1].ClusterIndex < glyphs[0].ClusterIndex
|
|
||||||
if rtl {
|
|
||||||
glyphCursor = len(glyphs) - 1
|
|
||||||
}
|
|
||||||
for i := range runes {
|
|
||||||
for glyphCursor >= 0 && glyphCursor < len(glyphs) &&
|
|
||||||
((rtl && glyphs[glyphCursor].ClusterIndex <= i) ||
|
|
||||||
(!rtl && glyphs[glyphCursor].ClusterIndex < i)) {
|
|
||||||
if rtl {
|
|
||||||
glyphCursor--
|
|
||||||
} else {
|
|
||||||
glyphCursor++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rtl {
|
|
||||||
glyphCursor++
|
|
||||||
} else if (glyphCursor >= 0 && glyphCursor < len(glyphs) &&
|
|
||||||
glyphs[glyphCursor].ClusterIndex > i) ||
|
|
||||||
(glyphCursor == len(glyphs) && len(glyphs) > 1) {
|
|
||||||
glyphCursor--
|
|
||||||
targetClusterIndex := glyphs[glyphCursor].ClusterIndex
|
|
||||||
for glyphCursor-1 >= 0 && glyphs[glyphCursor-1].ClusterIndex == targetClusterIndex {
|
|
||||||
glyphCursor--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if glyphCursor < 0 {
|
|
||||||
glyphCursor = 0
|
|
||||||
} else if glyphCursor >= len(glyphs) {
|
|
||||||
glyphCursor = len(glyphs) - 1
|
|
||||||
}
|
|
||||||
mapping[i] = glyphCursor
|
|
||||||
}
|
|
||||||
return mapping
|
|
||||||
}
|
|
||||||
|
|
||||||
// inclusiveGlyphRange returns the inclusive range of runes and glyphs matching
|
|
||||||
// the provided start and breakAfter rune positions.
|
|
||||||
// runeToGlyph must be a valid mapping from the rune representation to the
|
|
||||||
// glyph reprsentation produced by mapRunesToClusterIndices.
|
|
||||||
// numGlyphs is the number of glyphs in the output representing the runes
|
|
||||||
// under consideration.
|
|
||||||
func inclusiveGlyphRange(start, breakAfter int, runeToGlyph []int, numGlyphs int) (glyphStart, glyphEnd int) {
|
|
||||||
rtl := runeToGlyph[len(runeToGlyph)-1] < runeToGlyph[0]
|
|
||||||
runeStart := start
|
|
||||||
runeEnd := breakAfter
|
|
||||||
if rtl {
|
|
||||||
glyphStart = runeToGlyph[runeEnd]
|
|
||||||
if runeStart-1 >= 0 {
|
|
||||||
glyphEnd = runeToGlyph[runeStart-1] - 1
|
|
||||||
} else {
|
|
||||||
glyphEnd = numGlyphs - 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
glyphStart = runeToGlyph[runeStart]
|
|
||||||
if runeEnd+1 < len(runeToGlyph) {
|
|
||||||
glyphEnd = runeToGlyph[runeEnd+1] - 1
|
|
||||||
} else {
|
|
||||||
glyphEnd = numGlyphs - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// breakOption represets a location within the rune slice at which
|
|
||||||
// it may be safe to break a line of text.
|
|
||||||
type breakOption struct {
|
|
||||||
// breakAtRune is the index at which it is safe to break.
|
|
||||||
breakAtRune int
|
|
||||||
// penalty is the cost of breaking at this index. Negative
|
|
||||||
// penalties mean that the break is beneficial, and a penalty
|
|
||||||
// of uax14.PenaltyForMustBreak means a required break.
|
|
||||||
penalty int
|
|
||||||
}
|
|
||||||
|
|
||||||
// getBreakOptions returns a slice of line break candidates for the
|
|
||||||
// text in the provided slice.
|
|
||||||
func getBreakOptions(text []rune) []breakOption {
|
|
||||||
// Collect options for breaking the lines in a slice.
|
|
||||||
var options []breakOption
|
|
||||||
const adjust = -1
|
|
||||||
breaker := uax14.NewLineWrap()
|
|
||||||
segmenter := segment.NewSegmenter(breaker)
|
|
||||||
segmenter.InitFromSlice(text)
|
|
||||||
runeOffset := 0
|
|
||||||
brokeAtEnd := false
|
|
||||||
for segmenter.Next() {
|
|
||||||
penalty, _ := segmenter.Penalties()
|
|
||||||
// Determine the indices of the breaking runes in the runes
|
|
||||||
// slice. Would be nice if the API provided this.
|
|
||||||
currentSegment := segmenter.Runes()
|
|
||||||
runeOffset += len(currentSegment)
|
|
||||||
|
|
||||||
// Collect all break options.
|
|
||||||
options = append(options, breakOption{
|
|
||||||
penalty: penalty,
|
|
||||||
breakAtRune: runeOffset + adjust,
|
|
||||||
})
|
|
||||||
if options[len(options)-1].breakAtRune == len(text)-1 {
|
|
||||||
brokeAtEnd = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(text) > 0 && !brokeAtEnd {
|
|
||||||
options = append(options, breakOption{
|
|
||||||
penalty: uax14.PenaltyForMustBreak,
|
|
||||||
breakAtRune: len(text) - 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
|
|
||||||
type Shaper func(shaping.Input) (shaping.Output, error)
|
|
||||||
|
|
||||||
// paragraph shapes a single paragraph of text, breaking it into multiple lines
|
|
||||||
// to fit within the provided maxWidth.
|
|
||||||
func paragraph(shaper Shaper, face font.Face, ppem fixed.Int26_6, maxWidth int, lc langConfig, paragraph []rune) ([]output, error) {
|
|
||||||
// TODO: handle splitting bidi text here
|
|
||||||
|
|
||||||
// Shape the text.
|
|
||||||
input := toInput(face, ppem, lc, paragraph)
|
|
||||||
out, err := shaper(input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Get a mapping from input runes to output glyphs.
|
|
||||||
runeToGlyph := mapRunesToClusterIndices(paragraph, out.Glyphs)
|
|
||||||
|
|
||||||
// Fetch line break candidates.
|
|
||||||
breaks := getBreakOptions(paragraph)
|
|
||||||
|
|
||||||
return lineWrap(out, input.Direction, paragraph, runeToGlyph, breaks, maxWidth), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldKeepSegmentOnLine decides whether the segment of text from the current
|
|
||||||
// end of the line to the provided breakOption should be kept on the current
|
|
||||||
// line. It should be called successively with each available breakOption,
|
|
||||||
// and the line should be broken (without keeping the current segment)
|
|
||||||
// whenever it returns false.
|
|
||||||
//
|
|
||||||
// The parameters require some explanation:
|
|
||||||
// out - the shaping.Output that is being line-broken.
|
|
||||||
// runeToGlyph - a mapping where accessing the slice at the index of a rune
|
|
||||||
// int out will yield the index of the first glyph corresponding to that rune.
|
|
||||||
// lineStartRune - the index of the first rune in the line.
|
|
||||||
// b - the line break candidate under consideration.
|
|
||||||
// curLineWidth - the amount of space total in the current line.
|
|
||||||
// curLineUsed - the amount of space in the current line that is already used.
|
|
||||||
// nextLineWidth - the amount of space available on the next line.
|
|
||||||
//
|
|
||||||
// This function returns both a valid shaping.Output broken at b and a boolean
|
|
||||||
// indicating whether the returned output should be used.
|
|
||||||
func shouldKeepSegmentOnLine(out shaping.Output, runeToGlyph []int, lineStartRune int, b breakOption, curLineWidth, curLineUsed, nextLineWidth int) (candidateLine shaping.Output, keep bool) {
|
|
||||||
// Convert the break target to an inclusive index.
|
|
||||||
glyphStart, glyphEnd := inclusiveGlyphRange(lineStartRune, b.breakAtRune, runeToGlyph, len(out.Glyphs))
|
|
||||||
|
|
||||||
// Construct a line out of the inclusive glyph range.
|
|
||||||
candidateLine = out
|
|
||||||
candidateLine.Glyphs = candidateLine.Glyphs[glyphStart : glyphEnd+1]
|
|
||||||
candidateLine.RecomputeAdvance()
|
|
||||||
candidateAdvance := candidateLine.Advance.Ceil()
|
|
||||||
if candidateAdvance > curLineWidth && candidateAdvance-curLineUsed <= nextLineWidth {
|
|
||||||
// If it fits on the next line, put it there.
|
|
||||||
return candidateLine, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidateLine, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// lineWrap wraps the shaped glyphs of a paragraph to a particular max width.
|
|
||||||
func lineWrap(out shaping.Output, dir di.Direction, paragraph []rune, runeToGlyph []int, breaks []breakOption, maxWidth int) []output {
|
|
||||||
var outputs []output
|
|
||||||
if len(breaks) == 0 {
|
|
||||||
// Pass empty lines through as empty.
|
|
||||||
outputs = append(outputs, output{
|
|
||||||
Shaped: out,
|
|
||||||
RuneRange: text.Range{
|
|
||||||
Count: len(paragraph),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return outputs
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(breaks); i++ {
|
|
||||||
b := breaks[i]
|
|
||||||
if b.breakAtRune+1 < len(runeToGlyph) {
|
|
||||||
// Check if this break is valid.
|
|
||||||
gIdx := runeToGlyph[b.breakAtRune]
|
|
||||||
g2Idx := runeToGlyph[b.breakAtRune+1]
|
|
||||||
cIdx := out.Glyphs[gIdx].ClusterIndex
|
|
||||||
c2Idx := out.Glyphs[g2Idx].ClusterIndex
|
|
||||||
if cIdx == c2Idx {
|
|
||||||
// This break is within a harfbuzz cluster, and is
|
|
||||||
// therefore invalid.
|
|
||||||
copy(breaks[i:], breaks[i+1:])
|
|
||||||
breaks = breaks[:len(breaks)-1]
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
start := 0
|
|
||||||
runesProcessed := 0
|
|
||||||
for i := 0; i < len(breaks); i++ {
|
|
||||||
b := breaks[i]
|
|
||||||
// Always keep the first segment on a line.
|
|
||||||
good, _ := shouldKeepSegmentOnLine(out, runeToGlyph, start, b, maxWidth, 0, maxWidth)
|
|
||||||
end := b.breakAtRune
|
|
||||||
innerLoop:
|
|
||||||
for k := i + 1; k < len(breaks); k++ {
|
|
||||||
bb := breaks[k]
|
|
||||||
candidate, ok := shouldKeepSegmentOnLine(out, runeToGlyph, start, bb, maxWidth, good.Advance.Ceil(), maxWidth)
|
|
||||||
if ok {
|
|
||||||
// Use this new, longer segment.
|
|
||||||
good = candidate
|
|
||||||
end = bb.breakAtRune
|
|
||||||
i++
|
|
||||||
} else {
|
|
||||||
break innerLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
numRunes := end - start + 1
|
|
||||||
outputs = append(outputs, output{
|
|
||||||
Shaped: good,
|
|
||||||
RuneRange: text.Range{
|
|
||||||
Count: numRunes,
|
|
||||||
Offset: runesProcessed,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
runesProcessed += numRunes
|
|
||||||
start = end + 1
|
|
||||||
}
|
|
||||||
return outputs
|
|
||||||
}
|
|
||||||
|
|
||||||
// output is a run of shaped text with metadata about its position
|
|
||||||
// within a text document.
|
|
||||||
type output struct {
|
|
||||||
Shaped shaping.Output
|
|
||||||
RuneRange text.Range
|
|
||||||
}
|
|
||||||
|
|
||||||
func toSystemDirection(d di.Direction) system.TextDirection {
|
|
||||||
switch d {
|
|
||||||
case di.DirectionLTR:
|
|
||||||
return system.LTR
|
|
||||||
case di.DirectionRTL:
|
|
||||||
return system.RTL
|
|
||||||
}
|
|
||||||
return system.LTR
|
|
||||||
}
|
|
||||||
|
|
||||||
// toGioGlyphs converts text shaper glyphs into the minimal representation
|
|
||||||
// that Gio needs.
|
|
||||||
func toGioGlyphs(in []shaping.Glyph) []text.Glyph {
|
|
||||||
out := make([]text.Glyph, 0, len(in))
|
|
||||||
for _, g := range in {
|
|
||||||
out = append(out, text.Glyph{
|
|
||||||
ID: g.GlyphID,
|
|
||||||
ClusterIndex: g.ClusterIndex,
|
|
||||||
RuneCount: g.RuneCount,
|
|
||||||
GlyphCount: g.GlyphCount,
|
|
||||||
XAdvance: g.XAdvance,
|
|
||||||
YAdvance: g.YAdvance,
|
|
||||||
XOffset: g.XOffset,
|
|
||||||
YOffset: g.YOffset,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToLine converts the output into a text.Line
|
|
||||||
func (o output) ToLine() text.Line {
|
|
||||||
advances := make([]fixed.Int26_6, 0, len(o.Shaped.Glyphs))
|
|
||||||
for _, glyph := range o.Shaped.Glyphs {
|
|
||||||
advances = append(advances, glyph.XAdvance)
|
|
||||||
}
|
|
||||||
layout := text.Layout{
|
|
||||||
Glyphs: toGioGlyphs(o.Shaped.Glyphs),
|
|
||||||
Runes: o.RuneRange,
|
|
||||||
Direction: toSystemDirection(o.Shaped.Direction),
|
|
||||||
}
|
|
||||||
return text.Line{
|
|
||||||
Layout: layout,
|
|
||||||
Bounds: fixed.Rectangle26_6{
|
|
||||||
Min: fixed.Point26_6{
|
|
||||||
Y: -o.Shaped.LineBounds.Ascent,
|
|
||||||
},
|
|
||||||
Max: fixed.Point26_6{
|
|
||||||
X: o.Shaped.Advance,
|
|
||||||
Y: -o.Shaped.LineBounds.Ascent + o.Shaped.LineBounds.LineHeight(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Width: o.Shaped.Advance,
|
|
||||||
Ascent: o.Shaped.LineBounds.Ascent,
|
|
||||||
Descent: -o.Shaped.LineBounds.Descent + o.Shaped.LineBounds.Gap,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapDirection(d system.TextDirection) di.Direction {
|
|
||||||
switch d {
|
|
||||||
case system.LTR:
|
|
||||||
return di.DirectionLTR
|
|
||||||
case system.RTL:
|
|
||||||
return di.DirectionRTL
|
|
||||||
}
|
|
||||||
return di.DirectionLTR
|
|
||||||
}
|
|
||||||
|
|
||||||
// Document shapes text using the given font, ppem, maximum line width, language,
|
|
||||||
// and sequence of runes. It returns a slice of lines corresponding to the txt,
|
|
||||||
// broken to fit within maxWidth and on paragraph boundaries.
|
|
||||||
func Document(shaper Shaper, face font.Face, ppem fixed.Int26_6, maxWidth int, lc system.Locale, txt io.RuneReader) []text.Line {
|
|
||||||
var (
|
|
||||||
outputs []text.Line
|
|
||||||
startByte int
|
|
||||||
startRune int
|
|
||||||
paragraphText []rune
|
|
||||||
done bool
|
|
||||||
langs = make(map[language.Script]int)
|
|
||||||
)
|
|
||||||
for !done {
|
|
||||||
var (
|
|
||||||
bytes int
|
|
||||||
runes int
|
|
||||||
)
|
|
||||||
newlineAdjust := 0
|
|
||||||
paragraphLoop:
|
|
||||||
for r, sz, re := txt.ReadRune(); !done; r, sz, re = txt.ReadRune() {
|
|
||||||
if re != nil {
|
|
||||||
done = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
paragraphText = append(paragraphText, r)
|
|
||||||
script := language.LookupScript(r)
|
|
||||||
if _, ok := langs[script]; ok {
|
|
||||||
langs[script]++
|
|
||||||
} else {
|
|
||||||
langs[script] = 1
|
|
||||||
}
|
|
||||||
bytes += sz
|
|
||||||
runes++
|
|
||||||
if r == '\n' {
|
|
||||||
newlineAdjust = 1
|
|
||||||
break paragraphLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
primary language.Script
|
|
||||||
primaryTotal int
|
|
||||||
)
|
|
||||||
for script, total := range langs {
|
|
||||||
if total > primaryTotal {
|
|
||||||
primary = script
|
|
||||||
primaryTotal = total
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if lc.Language == "" {
|
|
||||||
lc.Language = "EN"
|
|
||||||
}
|
|
||||||
lcfg := langConfig{
|
|
||||||
Language: language.NewLanguage(lc.Language),
|
|
||||||
Script: primary,
|
|
||||||
Direction: mapDirection(lc.Direction),
|
|
||||||
}
|
|
||||||
lines, _ := paragraph(shaper, face, ppem, maxWidth, lcfg, paragraphText[:len(paragraphText)-newlineAdjust])
|
|
||||||
for i := range lines {
|
|
||||||
// Update the offsets of each paragraph to be correct within the
|
|
||||||
// whole document.
|
|
||||||
lines[i].RuneRange.Offset += startRune
|
|
||||||
// Update the cluster values to be rune indices within the entire
|
|
||||||
// document.
|
|
||||||
for k := range lines[i].Shaped.Glyphs {
|
|
||||||
lines[i].Shaped.Glyphs[k].ClusterIndex += startRune
|
|
||||||
}
|
|
||||||
outputs = append(outputs, lines[i].ToLine())
|
|
||||||
}
|
|
||||||
// If there was a trailing newline update the byte counts to include
|
|
||||||
// it on the last line of the paragraph.
|
|
||||||
if newlineAdjust > 0 {
|
|
||||||
outputs[len(outputs)-1].Layout.Runes.Count += newlineAdjust
|
|
||||||
}
|
|
||||||
paragraphText = paragraphText[:0]
|
|
||||||
startByte += bytes
|
|
||||||
startRune += runes
|
|
||||||
}
|
|
||||||
for i := range outputs {
|
|
||||||
computeGlyphClusters(&outputs[i].Layout)
|
|
||||||
}
|
|
||||||
return outputs
|
|
||||||
}
|
|
||||||
|
|
||||||
// toInput converts its parameters into a shaping.Input.
|
|
||||||
func toInput(face font.Face, ppem fixed.Int26_6, lc langConfig, runes []rune) shaping.Input {
|
|
||||||
var input shaping.Input
|
|
||||||
input.Direction = lc.Direction
|
|
||||||
input.Text = runes
|
|
||||||
input.Size = ppem
|
|
||||||
input.Face = face
|
|
||||||
input.Language = lc.Language
|
|
||||||
input.Script = lc.Script
|
|
||||||
input.RunStart = 0
|
|
||||||
input.RunEnd = len(runes)
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
+169
-117
@@ -2,137 +2,189 @@
|
|||||||
|
|
||||||
// Package opentype implements text layout and shaping for OpenType
|
// Package opentype implements text layout and shaping for OpenType
|
||||||
// files.
|
// files.
|
||||||
|
//
|
||||||
|
// NOTE: the OpenType specification allows for fonts to include bitmap images
|
||||||
|
// in a variety of formats. In the interest of small binary sizes, the opentype
|
||||||
|
// package only automatically imports the PNG image decoder. If you have a font
|
||||||
|
// with glyphs in JPEG or TIFF formats, register those decoders with the image
|
||||||
|
// package in order to ensure those glyphs are visible in text.
|
||||||
package opentype
|
package opentype
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
_ "image/png"
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/benoitkugler/textlayout/fonts"
|
giofont "gioui.org/font"
|
||||||
"github.com/benoitkugler/textlayout/fonts/truetype"
|
fontapi "github.com/go-text/typesetting/font"
|
||||||
"github.com/benoitkugler/textlayout/harfbuzz"
|
"github.com/go-text/typesetting/font/opentype"
|
||||||
"github.com/go-text/typesetting/shaping"
|
|
||||||
"golang.org/x/image/font"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
|
|
||||||
"gioui.org/f32"
|
|
||||||
"gioui.org/font/opentype/internal"
|
|
||||||
"gioui.org/io/system"
|
|
||||||
"gioui.org/op"
|
|
||||||
"gioui.org/op/clip"
|
|
||||||
"gioui.org/text"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Font implements the text.Shaper interface using a rich text
|
// Face is a thread-safe representation of a loaded font. For efficiency, applications
|
||||||
// shaping engine.
|
// should construct a face for any given font file once, reusing it across different
|
||||||
type Font struct {
|
// text shapers.
|
||||||
font *truetype.Font
|
type Face struct {
|
||||||
|
face *fontapi.Font
|
||||||
|
font giofont.Font
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse constructs a Font from source bytes.
|
// Parse constructs a Face from source bytes.
|
||||||
func Parse(src []byte) (*Font, error) {
|
func Parse(src []byte) (Face, error) {
|
||||||
face, err := truetype.Parse(bytes.NewReader(src))
|
ld, err := opentype.NewLoader(bytes.NewReader(src))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed parsing truetype font: %w", err)
|
return Face{}, err
|
||||||
}
|
}
|
||||||
return &Font{
|
font, md, err := parseLoader(ld)
|
||||||
font: face,
|
if err != nil {
|
||||||
|
return Face{}, fmt.Errorf("failed parsing truetype font: %w", err)
|
||||||
|
}
|
||||||
|
return Face{
|
||||||
|
face: font,
|
||||||
|
font: md,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) Layout(ppem fixed.Int26_6, maxWidth int, lc system.Locale, txt io.RuneReader) ([]text.Line, error) {
|
// ParseCollection parse an Opentype font file, with support for collections.
|
||||||
return internal.Document(shaping.Shape, f.font, ppem, maxWidth, lc, txt), nil
|
// Single font files are supported, returning a slice with length 1.
|
||||||
}
|
// The returned fonts are automatically wrapped in a text.FontFace with
|
||||||
|
// inferred font font.
|
||||||
func (f *Font) Shape(ppem fixed.Int26_6, str text.Layout) clip.PathSpec {
|
// BUG(whereswaldon): the only Variant that can be detected automatically is
|
||||||
return textPath(ppem, f, str)
|
// "Mono".
|
||||||
}
|
func ParseCollection(src []byte) ([]giofont.FontFace, error) {
|
||||||
|
lds, err := opentype.NewLoaders(bytes.NewReader(src))
|
||||||
func (f *Font) Metrics(ppem fixed.Int26_6) font.Metrics {
|
if err != nil {
|
||||||
metrics := font.Metrics{}
|
return nil, err
|
||||||
font := harfbuzz.NewFont(f.font)
|
}
|
||||||
font.XScale = int32(ppem.Ceil()) << 6
|
out := make([]giofont.FontFace, len(lds))
|
||||||
font.YScale = font.XScale
|
for i, ld := range lds {
|
||||||
// Use any horizontal direction.
|
face, md, err := parseLoader(ld)
|
||||||
fontExtents := font.ExtentsForDirection(harfbuzz.LeftToRight)
|
if err != nil {
|
||||||
ascender := fixed.I(int(fontExtents.Ascender * 64))
|
return nil, fmt.Errorf("reading font %d of collection: %s", i, err)
|
||||||
descender := fixed.I(int(fontExtents.Descender * 64))
|
}
|
||||||
gap := fixed.I(int(fontExtents.LineGap * 64))
|
ff := Face{
|
||||||
metrics.Height = ascender + descender + gap
|
face: face,
|
||||||
metrics.Ascent = ascender
|
font: md,
|
||||||
metrics.Descent = descender
|
}
|
||||||
// These three are not readily available.
|
out[i] = giofont.FontFace{
|
||||||
// TODO(whereswaldon): figure out how to get these values.
|
Face: ff,
|
||||||
metrics.XHeight = ascender
|
Font: ff.Font(),
|
||||||
metrics.CapHeight = ascender
|
}
|
||||||
metrics.CaretSlope = image.Pt(0, 1)
|
}
|
||||||
|
|
||||||
return metrics
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func textPath(ppem fixed.Int26_6, font *Font, str text.Layout) clip.PathSpec {
|
func DescriptionToFont(md fontapi.Description) giofont.Font {
|
||||||
var lastPos f32.Point
|
return giofont.Font{
|
||||||
var builder clip.Path
|
Typeface: giofont.Typeface(md.Family),
|
||||||
ops := new(op.Ops)
|
Style: gioStyle(md.Aspect.Style),
|
||||||
var x fixed.Int26_6
|
Weight: gioWeight(md.Aspect.Weight),
|
||||||
builder.Begin(ops)
|
}
|
||||||
rune := 0
|
}
|
||||||
ppemInt := ppem.Round()
|
|
||||||
ppem16 := uint16(ppemInt)
|
func FontToDescription(font giofont.Font) fontapi.Description {
|
||||||
scaleFactor := float32(ppemInt) / float32(font.font.Upem())
|
return fontapi.Description{
|
||||||
for _, g := range str.Glyphs {
|
Family: string(font.Typeface),
|
||||||
advance := g.XAdvance
|
Aspect: fontapi.Aspect{
|
||||||
outline, ok := font.font.GlyphData(g.ID, ppem16, ppem16).(fonts.GlyphOutline)
|
Style: mdStyle(font.Style),
|
||||||
if !ok {
|
Weight: mdWeight(font.Weight),
|
||||||
continue
|
},
|
||||||
}
|
}
|
||||||
// Move to glyph position.
|
}
|
||||||
pos := f32.Point{
|
|
||||||
X: float32(x)/64 - float32(g.XOffset)/64,
|
// parseLoader parses the contents of the loader into a face and its font.
|
||||||
Y: -float32(g.YOffset) / 64,
|
func parseLoader(ld *opentype.Loader) (*fontapi.Font, giofont.Font, error) {
|
||||||
}
|
ft, err := fontapi.NewFont(ld)
|
||||||
builder.Move(pos.Sub(lastPos))
|
if err != nil {
|
||||||
lastPos = pos
|
return nil, giofont.Font{}, err
|
||||||
var lastArg f32.Point
|
}
|
||||||
|
data := DescriptionToFont(ft.Describe())
|
||||||
// Convert sfnt.Segments to relative segments.
|
return ft, data, nil
|
||||||
for _, fseg := range outline.Segments {
|
}
|
||||||
nargs := 1
|
|
||||||
switch fseg.Op {
|
// Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper.
|
||||||
case fonts.SegmentOpQuadTo:
|
// Face many be invoked any number of times and is safe so long as each return value is
|
||||||
nargs = 2
|
// only used by one goroutine.
|
||||||
case fonts.SegmentOpCubeTo:
|
func (f Face) Face() *fontapi.Face {
|
||||||
nargs = 3
|
return fontapi.NewFace(f.face)
|
||||||
}
|
}
|
||||||
var args [3]f32.Point
|
|
||||||
for i := 0; i < nargs; i++ {
|
// FontFace returns a text.Font with populated font metadata for the
|
||||||
a := f32.Point{
|
// font.
|
||||||
X: fseg.Args[i].X * scaleFactor,
|
// BUG(whereswaldon): the only Variant that can be detected automatically is
|
||||||
Y: -fseg.Args[i].Y * scaleFactor,
|
// "Mono".
|
||||||
}
|
func (f Face) Font() giofont.Font {
|
||||||
args[i] = a.Sub(lastArg)
|
return f.font
|
||||||
if i == nargs-1 {
|
}
|
||||||
lastArg = a
|
|
||||||
}
|
func gioStyle(s fontapi.Style) giofont.Style {
|
||||||
}
|
switch s {
|
||||||
switch fseg.Op {
|
case fontapi.StyleItalic:
|
||||||
case fonts.SegmentOpMoveTo:
|
return giofont.Italic
|
||||||
builder.Move(args[0])
|
case fontapi.StyleNormal:
|
||||||
case fonts.SegmentOpLineTo:
|
fallthrough
|
||||||
builder.Line(args[0])
|
default:
|
||||||
case fonts.SegmentOpQuadTo:
|
return giofont.Regular
|
||||||
builder.Quad(args[0], args[1])
|
}
|
||||||
case fonts.SegmentOpCubeTo:
|
}
|
||||||
builder.Cube(args[0], args[1], args[2])
|
|
||||||
default:
|
func mdStyle(g giofont.Style) fontapi.Style {
|
||||||
panic("unsupported segment op")
|
switch g {
|
||||||
}
|
case giofont.Italic:
|
||||||
}
|
return fontapi.StyleItalic
|
||||||
lastPos = lastPos.Add(lastArg)
|
case giofont.Regular:
|
||||||
x += advance
|
fallthrough
|
||||||
rune++
|
default:
|
||||||
|
return fontapi.StyleNormal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gioWeight(w fontapi.Weight) giofont.Weight {
|
||||||
|
switch w {
|
||||||
|
case fontapi.WeightThin:
|
||||||
|
return giofont.Thin
|
||||||
|
case fontapi.WeightExtraLight:
|
||||||
|
return giofont.ExtraLight
|
||||||
|
case fontapi.WeightLight:
|
||||||
|
return giofont.Light
|
||||||
|
case fontapi.WeightNormal:
|
||||||
|
return giofont.Normal
|
||||||
|
case fontapi.WeightMedium:
|
||||||
|
return giofont.Medium
|
||||||
|
case fontapi.WeightSemibold:
|
||||||
|
return giofont.SemiBold
|
||||||
|
case fontapi.WeightBold:
|
||||||
|
return giofont.Bold
|
||||||
|
case fontapi.WeightExtraBold:
|
||||||
|
return giofont.ExtraBold
|
||||||
|
case fontapi.WeightBlack:
|
||||||
|
return giofont.Black
|
||||||
|
default:
|
||||||
|
return giofont.Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mdWeight(g giofont.Weight) fontapi.Weight {
|
||||||
|
switch g {
|
||||||
|
case giofont.Thin:
|
||||||
|
return fontapi.WeightThin
|
||||||
|
case giofont.ExtraLight:
|
||||||
|
return fontapi.WeightExtraLight
|
||||||
|
case giofont.Light:
|
||||||
|
return fontapi.WeightLight
|
||||||
|
case giofont.Normal:
|
||||||
|
return fontapi.WeightNormal
|
||||||
|
case giofont.Medium:
|
||||||
|
return fontapi.WeightMedium
|
||||||
|
case giofont.SemiBold:
|
||||||
|
return fontapi.WeightSemibold
|
||||||
|
case giofont.Bold:
|
||||||
|
return fontapi.WeightBold
|
||||||
|
case giofont.ExtraBold:
|
||||||
|
return fontapi.WeightExtraBold
|
||||||
|
case giofont.Black:
|
||||||
|
return fontapi.WeightBlack
|
||||||
|
default:
|
||||||
|
return fontapi.WeightNormal
|
||||||
}
|
}
|
||||||
return builder.End()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
package opentype
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/image/font/gofont/goregular"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
|
|
||||||
"gioui.org/internal/ops"
|
|
||||||
"gioui.org/io/system"
|
|
||||||
"gioui.org/op"
|
|
||||||
"gioui.org/op/clip"
|
|
||||||
"gioui.org/text"
|
|
||||||
)
|
|
||||||
|
|
||||||
var english = system.Locale{
|
|
||||||
Language: "EN",
|
|
||||||
Direction: system.LTR,
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyString(t *testing.T) {
|
|
||||||
face, err := Parse(goregular.TTF)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ppem := fixed.I(200)
|
|
||||||
|
|
||||||
lines, err := face.Layout(ppem, 2000, english, strings.NewReader(""))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(lines) == 0 {
|
|
||||||
t.Fatalf("Layout returned no lines for empty string; expected 1")
|
|
||||||
}
|
|
||||||
l := lines[0]
|
|
||||||
exp := fixed.Rectangle26_6{
|
|
||||||
Min: fixed.Point26_6{
|
|
||||||
Y: fixed.Int26_6(-12094),
|
|
||||||
},
|
|
||||||
Max: fixed.Point26_6{
|
|
||||||
Y: fixed.Int26_6(2700),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if got := l.Bounds; got != exp {
|
|
||||||
t.Errorf("got bounds %+v for empty string; expected %+v", got, exp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decompressFontFile(name string) (*Font, []byte, error) {
|
|
||||||
f, err := os.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("could not open file for reading: %s: %v", name, err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
gz, err := gzip.NewReader(f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("font file contains invalid gzip data: %v", err)
|
|
||||||
}
|
|
||||||
src, err := ioutil.ReadAll(gz)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("failed to decompress font file: %v", err)
|
|
||||||
}
|
|
||||||
fnt, err := Parse(src)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("file did not contain a valid font: %v", err)
|
|
||||||
}
|
|
||||||
return fnt, src, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mergeFonts produces a trivial OpenType Collection (OTC) file for two source fonts.
|
|
||||||
// It makes many assumptions and is not meant for general use.
|
|
||||||
// For file format details, see https://docs.microsoft.com/en-us/typography/opentype/spec/otff
|
|
||||||
// For a robust tool to generate these files, see https://pypi.org/project/afdko/
|
|
||||||
func mergeFonts(ttf1, ttf2 []byte) []byte {
|
|
||||||
// Locations to place the two embedded fonts. All of the offsets to the fonts' internal tables will need to be
|
|
||||||
// shifted from the start of the file by the appropriate amount, and then everything will work as expected.
|
|
||||||
offset1 := uint32(20) // Length of OpenType collection headers
|
|
||||||
offset2 := offset1 + uint32(len(ttf1))
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_, _ = buf.Write([]byte("ttcf\x00\x01\x00\x00\x00\x00\x00\x02"))
|
|
||||||
_ = binary.Write(&buf, binary.BigEndian, offset1)
|
|
||||||
_ = binary.Write(&buf, binary.BigEndian, offset2)
|
|
||||||
|
|
||||||
// Inline function to copy a font into the collection verbatim, except for adding an offset to all of the font's
|
|
||||||
// table positions.
|
|
||||||
copyOffsetTTF := func(ttf []byte, offset uint32) {
|
|
||||||
_, _ = buf.Write(ttf[:12])
|
|
||||||
numTables := binary.BigEndian.Uint16(ttf[4:6])
|
|
||||||
for i := uint16(0); i < numTables; i++ {
|
|
||||||
p := 12 + 16*i
|
|
||||||
_, _ = buf.Write(ttf[p : p+8])
|
|
||||||
tblLoc := binary.BigEndian.Uint32(ttf[p+8:p+12]) + offset
|
|
||||||
_ = binary.Write(&buf, binary.BigEndian, tblLoc)
|
|
||||||
_, _ = buf.Write(ttf[p+12 : p+16])
|
|
||||||
}
|
|
||||||
_, _ = buf.Write(ttf[12+16*numTables:])
|
|
||||||
}
|
|
||||||
copyOffsetTTF(ttf1, offset1)
|
|
||||||
copyOffsetTTF(ttf2, offset2)
|
|
||||||
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// shapeRune uses a given Face to shape exactly one rune at a fixed size, then returns the resulting shape data.
|
|
||||||
func shapeRune(f text.Face, r rune) (clip.PathSpec, error) {
|
|
||||||
ppem := fixed.I(200)
|
|
||||||
lines, err := f.Layout(ppem, 2000, english, strings.NewReader(string(r)))
|
|
||||||
if err != nil {
|
|
||||||
return clip.PathSpec{}, err
|
|
||||||
}
|
|
||||||
if len(lines) != 1 {
|
|
||||||
return clip.PathSpec{}, fmt.Errorf("unexpected rendering for \"U+%08X\": got %d lines (expected: 1)", r, len(lines))
|
|
||||||
}
|
|
||||||
return f.Shape(ppem, lines[0].Layout), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// areShapesEqual returns true iff both given text shapes are produced with identical operations.
|
|
||||||
func areShapesEqual(shape1, shape2 clip.PathSpec) bool {
|
|
||||||
var ops1, ops2 op.Ops
|
|
||||||
clip.Outline{Path: shape1}.Op().Push(&ops1).Pop()
|
|
||||||
clip.Outline{Path: shape2}.Op().Push(&ops2).Pop()
|
|
||||||
var r1, r2 ops.Reader
|
|
||||||
r1.Reset(&ops1.Internal)
|
|
||||||
r2.Reset(&ops2.Internal)
|
|
||||||
for {
|
|
||||||
encOp1, ok1 := r1.Decode()
|
|
||||||
encOp2, ok2 := r2.Decode()
|
|
||||||
if ok1 != ok2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !ok1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if len(encOp1.Refs) > 0 || len(encOp2.Refs) > 0 {
|
|
||||||
panic("unexpected ops with refs in font shaping test")
|
|
||||||
}
|
|
||||||
if !bytes.Equal(encOp1.Data, encOp2.Data) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
+118
-110
@@ -18,6 +18,7 @@ import (
|
|||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/internal/fling"
|
"gioui.org/internal/fling"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/event"
|
||||||
|
"gioui.org/io/input"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
@@ -37,31 +38,31 @@ type Hover struct {
|
|||||||
|
|
||||||
// Add the gesture to detect hovering over the current pointer area.
|
// Add the gesture to detect hovering over the current pointer area.
|
||||||
func (h *Hover) Add(ops *op.Ops) {
|
func (h *Hover) Add(ops *op.Ops) {
|
||||||
pointer.InputOp{
|
event.Op(ops, h)
|
||||||
Tag: h,
|
|
||||||
Types: pointer.Enter | pointer.Leave,
|
|
||||||
}.Add(ops)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hovered returns whether a pointer is inside the area.
|
// Update state and report whether a pointer is inside the area.
|
||||||
func (h *Hover) Hovered(q event.Queue) bool {
|
func (h *Hover) Update(q input.Source) bool {
|
||||||
for _, ev := range q.Events(h) {
|
for {
|
||||||
|
ev, ok := q.Event(pointer.Filter{
|
||||||
|
Target: h,
|
||||||
|
Kinds: pointer.Enter | pointer.Leave | pointer.Cancel,
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
e, ok := ev.(pointer.Event)
|
e, ok := ev.(pointer.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch e.Type {
|
switch e.Kind {
|
||||||
case pointer.Leave, pointer.Cancel:
|
case pointer.Leave, pointer.Cancel:
|
||||||
if h.entered && h.pid == e.PointerID {
|
if h.entered && h.pid == e.PointerID {
|
||||||
h.entered = false
|
h.entered = false
|
||||||
}
|
}
|
||||||
case pointer.Enter:
|
case pointer.Enter:
|
||||||
if !h.entered {
|
h.pid = e.PointerID
|
||||||
h.pid = e.PointerID
|
h.entered = true
|
||||||
}
|
|
||||||
if h.pid == e.PointerID {
|
|
||||||
h.entered = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return h.entered
|
return h.entered
|
||||||
@@ -87,11 +88,11 @@ type Click struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ClickEvent represent a click action, either a
|
// ClickEvent represent a click action, either a
|
||||||
// TypePress for the beginning of a click or a
|
// KindPress for the beginning of a click or a
|
||||||
// TypeClick for a completed click.
|
// KindClick for a completed click.
|
||||||
type ClickEvent struct {
|
type ClickEvent struct {
|
||||||
Type ClickType
|
Kind ClickKind
|
||||||
Position f32.Point
|
Position image.Point
|
||||||
Source pointer.Source
|
Source pointer.Source
|
||||||
Modifiers key.Modifiers
|
Modifiers key.Modifiers
|
||||||
// NumClicks records successive clicks occurring
|
// NumClicks records successive clicks occurring
|
||||||
@@ -99,7 +100,7 @@ type ClickEvent struct {
|
|||||||
NumClicks int
|
NumClicks int
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClickType uint8
|
type ClickKind uint8
|
||||||
|
|
||||||
// Drag detects drag gestures in the form of pointer.Drag events.
|
// Drag detects drag gestures in the form of pointer.Drag events.
|
||||||
type Drag struct {
|
type Drag struct {
|
||||||
@@ -107,7 +108,6 @@ type Drag struct {
|
|||||||
pressed bool
|
pressed bool
|
||||||
pid pointer.ID
|
pid pointer.ID
|
||||||
start f32.Point
|
start f32.Point
|
||||||
grab bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll detects scroll gestures and reduces them to
|
// Scroll detects scroll gestures and reduces them to
|
||||||
@@ -115,11 +115,9 @@ type Drag struct {
|
|||||||
// movements as well as drag and fling touch gestures.
|
// movements as well as drag and fling touch gestures.
|
||||||
type Scroll struct {
|
type Scroll struct {
|
||||||
dragging bool
|
dragging bool
|
||||||
axis Axis
|
|
||||||
estimator fling.Extrapolation
|
estimator fling.Extrapolation
|
||||||
flinger fling.Animation
|
flinger fling.Animation
|
||||||
pid pointer.ID
|
pid pointer.ID
|
||||||
grab bool
|
|
||||||
last int
|
last int
|
||||||
// Leftover scroll.
|
// Leftover scroll.
|
||||||
scroll float32
|
scroll float32
|
||||||
@@ -136,15 +134,15 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TypePress is reported for the first pointer
|
// KindPress is reported for the first pointer
|
||||||
// press.
|
// press.
|
||||||
TypePress ClickType = iota
|
KindPress ClickKind = iota
|
||||||
// TypeClick is reported when a click action
|
// KindClick is reported when a click action
|
||||||
// is complete.
|
// is complete.
|
||||||
TypeClick
|
KindClick
|
||||||
// TypeCancel is reported when the gesture is
|
// KindCancel is reported when the gesture is
|
||||||
// cancelled.
|
// cancelled.
|
||||||
TypeCancel
|
KindCancel
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -157,14 +155,11 @@ const (
|
|||||||
StateFlinging
|
StateFlinging
|
||||||
)
|
)
|
||||||
|
|
||||||
var touchSlop = unit.Dp(3)
|
const touchSlop = unit.Dp(3)
|
||||||
|
|
||||||
// Add the handler to the operation list to receive click events.
|
// Add the handler to the operation list to receive click events.
|
||||||
func (c *Click) Add(ops *op.Ops) {
|
func (c *Click) Add(ops *op.Ops) {
|
||||||
pointer.InputOp{
|
event.Op(ops, c)
|
||||||
Tag: c,
|
|
||||||
Types: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave,
|
|
||||||
}.Add(ops)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hovered returns whether a pointer is inside the area.
|
// Hovered returns whether a pointer is inside the area.
|
||||||
@@ -177,30 +172,36 @@ func (c *Click) Pressed() bool {
|
|||||||
return c.pressed
|
return c.pressed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Events returns the next click events, if any.
|
// Update state and return the next click events, if any.
|
||||||
func (c *Click) Events(q event.Queue) []ClickEvent {
|
func (c *Click) Update(q input.Source) (ClickEvent, bool) {
|
||||||
var events []ClickEvent
|
for {
|
||||||
for _, evt := range q.Events(c) {
|
evt, ok := q.Event(pointer.Filter{
|
||||||
|
Target: c,
|
||||||
|
Kinds: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave | pointer.Cancel,
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
e, ok := evt.(pointer.Event)
|
e, ok := evt.(pointer.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch e.Type {
|
switch e.Kind {
|
||||||
case pointer.Release:
|
case pointer.Release:
|
||||||
if !c.pressed || c.pid != e.PointerID {
|
if !c.pressed || c.pid != e.PointerID {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
c.pressed = false
|
c.pressed = false
|
||||||
if !c.entered || c.hovered {
|
if !c.entered || c.hovered {
|
||||||
if e.Time-c.clickedAt < doubleClickDuration {
|
return ClickEvent{
|
||||||
c.clicks++
|
Kind: KindClick,
|
||||||
} else {
|
Position: e.Position.Round(),
|
||||||
c.clicks = 1
|
Source: e.Source,
|
||||||
}
|
Modifiers: e.Modifiers,
|
||||||
c.clickedAt = e.Time
|
NumClicks: c.clicks,
|
||||||
events = append(events, ClickEvent{Type: TypeClick, Position: e.Position, Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks})
|
}, true
|
||||||
} else {
|
} else {
|
||||||
events = append(events, ClickEvent{Type: TypeCancel})
|
return ClickEvent{Kind: KindCancel}, true
|
||||||
}
|
}
|
||||||
case pointer.Cancel:
|
case pointer.Cancel:
|
||||||
wasPressed := c.pressed
|
wasPressed := c.pressed
|
||||||
@@ -208,7 +209,7 @@ func (c *Click) Events(q event.Queue) []ClickEvent {
|
|||||||
c.hovered = false
|
c.hovered = false
|
||||||
c.entered = false
|
c.entered = false
|
||||||
if wasPressed {
|
if wasPressed {
|
||||||
events = append(events, ClickEvent{Type: TypeCancel})
|
return ClickEvent{Kind: KindCancel}, true
|
||||||
}
|
}
|
||||||
case pointer.Press:
|
case pointer.Press:
|
||||||
if c.pressed {
|
if c.pressed {
|
||||||
@@ -217,14 +218,15 @@ func (c *Click) Events(q event.Queue) []ClickEvent {
|
|||||||
if e.Source == pointer.Mouse && e.Buttons != pointer.ButtonPrimary {
|
if e.Source == pointer.Mouse && e.Buttons != pointer.ButtonPrimary {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !c.hovered {
|
c.pid = e.PointerID
|
||||||
c.pid = e.PointerID
|
|
||||||
}
|
|
||||||
if c.pid != e.PointerID {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
c.pressed = true
|
c.pressed = true
|
||||||
events = append(events, ClickEvent{Type: TypePress, Position: e.Position, Source: e.Source, Modifiers: e.Modifiers})
|
if e.Time-c.clickedAt < doubleClickDuration {
|
||||||
|
c.clicks++
|
||||||
|
} else {
|
||||||
|
c.clicks = 1
|
||||||
|
}
|
||||||
|
c.clickedAt = e.Time
|
||||||
|
return ClickEvent{Kind: KindPress, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks}, true
|
||||||
case pointer.Leave:
|
case pointer.Leave:
|
||||||
if !c.pressed {
|
if !c.pressed {
|
||||||
c.pid = e.PointerID
|
c.pid = e.PointerID
|
||||||
@@ -242,25 +244,16 @@ func (c *Click) Events(q event.Queue) []ClickEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return events
|
return ClickEvent{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ClickEvent) ImplementsEvent() {}
|
func (ClickEvent) ImplementsEvent() {}
|
||||||
|
|
||||||
// Add the handler to the operation list to receive scroll events.
|
// Add the handler to the operation list to receive scroll events.
|
||||||
// The bounds variable refers to the scrolling boundaries
|
// The bounds variable refers to the scrolling boundaries
|
||||||
// as defined in io/pointer.InputOp.
|
// as defined in [pointer.Filter].
|
||||||
func (s *Scroll) Add(ops *op.Ops, bounds image.Rectangle) {
|
func (s *Scroll) Add(ops *op.Ops) {
|
||||||
oph := pointer.InputOp{
|
event.Op(ops, s)
|
||||||
Tag: s,
|
|
||||||
Grab: s.grab,
|
|
||||||
Types: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll,
|
|
||||||
ScrollBounds: bounds,
|
|
||||||
}
|
|
||||||
oph.Add(ops)
|
|
||||||
if s.flinger.Active() {
|
|
||||||
op.InvalidateOp{}.Add(ops)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop any remaining fling movement.
|
// Stop any remaining fling movement.
|
||||||
@@ -268,20 +261,25 @@ func (s *Scroll) Stop() {
|
|||||||
s.flinger = fling.Animation{}
|
s.flinger = fling.Animation{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll detects the scrolling distance from the available events and
|
// Update state and report the scroll distance along axis.
|
||||||
// ongoing fling gestures.
|
func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, scrollx, scrolly pointer.ScrollRange) int {
|
||||||
func (s *Scroll) Scroll(cfg unit.Metric, q event.Queue, t time.Time, axis Axis) int {
|
|
||||||
if s.axis != axis {
|
|
||||||
s.axis = axis
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
total := 0
|
total := 0
|
||||||
for _, evt := range q.Events(s) {
|
f := pointer.Filter{
|
||||||
|
Target: s,
|
||||||
|
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
|
||||||
|
ScrollX: scrollx,
|
||||||
|
ScrollY: scrolly,
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
evt, ok := q.Event(f)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
e, ok := evt.(pointer.Event)
|
e, ok := evt.(pointer.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch e.Type {
|
switch e.Kind {
|
||||||
case pointer.Press:
|
case pointer.Press:
|
||||||
if s.dragging {
|
if s.dragging {
|
||||||
break
|
break
|
||||||
@@ -293,7 +291,7 @@ func (s *Scroll) Scroll(cfg unit.Metric, q event.Queue, t time.Time, axis Axis)
|
|||||||
}
|
}
|
||||||
s.Stop()
|
s.Stop()
|
||||||
s.estimator = fling.Extrapolation{}
|
s.estimator = fling.Extrapolation{}
|
||||||
v := s.val(e.Position)
|
v := s.val(axis, e.Position)
|
||||||
s.last = int(math.Round(float64(v)))
|
s.last = int(math.Round(float64(v)))
|
||||||
s.estimator.Sample(e.Time, v)
|
s.estimator.Sample(e.Time, v)
|
||||||
s.dragging = true
|
s.dragging = true
|
||||||
@@ -303,19 +301,20 @@ func (s *Scroll) Scroll(cfg unit.Metric, q event.Queue, t time.Time, axis Axis)
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
fling := s.estimator.Estimate()
|
fling := s.estimator.Estimate()
|
||||||
if slop, d := float32(cfg.Px(touchSlop)), fling.Distance; d < -slop || d > slop {
|
if slop, d := float32(cfg.Dp(touchSlop)), fling.Distance; d < -slop || d > slop {
|
||||||
s.flinger.Start(cfg, t, fling.Velocity)
|
s.flinger.Start(cfg, t, fling.Velocity)
|
||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
case pointer.Cancel:
|
case pointer.Cancel:
|
||||||
s.dragging = false
|
s.dragging = false
|
||||||
s.grab = false
|
|
||||||
case pointer.Scroll:
|
case pointer.Scroll:
|
||||||
switch s.axis {
|
switch axis {
|
||||||
case Horizontal:
|
case Horizontal:
|
||||||
s.scroll += e.Scroll.X
|
s.scroll += e.Scroll.X
|
||||||
case Vertical:
|
case Vertical:
|
||||||
s.scroll += e.Scroll.Y
|
s.scroll += e.Scroll.Y
|
||||||
|
case Both:
|
||||||
|
s.scroll += e.Scroll.X + e.Scroll.Y
|
||||||
}
|
}
|
||||||
iscroll := int(s.scroll)
|
iscroll := int(s.scroll)
|
||||||
s.scroll -= float32(iscroll)
|
s.scroll -= float32(iscroll)
|
||||||
@@ -324,14 +323,14 @@ func (s *Scroll) Scroll(cfg unit.Metric, q event.Queue, t time.Time, axis Axis)
|
|||||||
if !s.dragging || s.pid != e.PointerID {
|
if !s.dragging || s.pid != e.PointerID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val := s.val(e.Position)
|
val := s.val(axis, e.Position)
|
||||||
s.estimator.Sample(e.Time, val)
|
s.estimator.Sample(e.Time, val)
|
||||||
v := int(math.Round(float64(val)))
|
v := int(math.Round(float64(val)))
|
||||||
dist := s.last - v
|
dist := s.last - v
|
||||||
if e.Priority < pointer.Grabbed {
|
if e.Priority < pointer.Grabbed {
|
||||||
slop := cfg.Px(touchSlop)
|
slop := cfg.Dp(touchSlop)
|
||||||
if dist := dist; dist >= slop || -slop >= dist {
|
if dist := dist; dist >= slop || -slop >= dist {
|
||||||
s.grab = true
|
q.Execute(pointer.GrabCmd{Tag: s, ID: e.PointerID})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.last = v
|
s.last = v
|
||||||
@@ -340,14 +339,22 @@ func (s *Scroll) Scroll(cfg unit.Metric, q event.Queue, t time.Time, axis Axis)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
total += s.flinger.Tick(t)
|
total += s.flinger.Tick(t)
|
||||||
|
if s.flinger.Active() {
|
||||||
|
q.Execute(op.InvalidateCmd{})
|
||||||
|
}
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scroll) val(p f32.Point) float32 {
|
func (s *Scroll) val(axis Axis, p f32.Point) float32 {
|
||||||
if s.axis == Horizontal {
|
switch axis {
|
||||||
|
case Horizontal:
|
||||||
return p.X
|
return p.X
|
||||||
} else {
|
case Vertical:
|
||||||
return p.Y
|
return p.Y
|
||||||
|
case Both:
|
||||||
|
return p.X + p.Y
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,23 +372,25 @@ func (s *Scroll) State() ScrollState {
|
|||||||
|
|
||||||
// Add the handler to the operation list to receive drag events.
|
// Add the handler to the operation list to receive drag events.
|
||||||
func (d *Drag) Add(ops *op.Ops) {
|
func (d *Drag) Add(ops *op.Ops) {
|
||||||
pointer.InputOp{
|
event.Op(ops, d)
|
||||||
Tag: d,
|
|
||||||
Grab: d.grab,
|
|
||||||
Types: pointer.Press | pointer.Drag | pointer.Release,
|
|
||||||
}.Add(ops)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Events returns the next drag events, if any.
|
// Update state and return the next drag event, if any.
|
||||||
func (d *Drag) Events(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event {
|
func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) (pointer.Event, bool) {
|
||||||
var events []pointer.Event
|
for {
|
||||||
for _, e := range q.Events(d) {
|
ev, ok := q.Event(pointer.Filter{
|
||||||
e, ok := e.(pointer.Event)
|
Target: d,
|
||||||
|
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel,
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e, ok := ev.(pointer.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch e.Type {
|
switch e.Kind {
|
||||||
case pointer.Press:
|
case pointer.Press:
|
||||||
if !(e.Buttons == pointer.ButtonPrimary || e.Source == pointer.Touch) {
|
if !(e.Buttons == pointer.ButtonPrimary || e.Source == pointer.Touch) {
|
||||||
continue
|
continue
|
||||||
@@ -407,9 +416,9 @@ func (d *Drag) Events(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event
|
|||||||
}
|
}
|
||||||
if e.Priority < pointer.Grabbed {
|
if e.Priority < pointer.Grabbed {
|
||||||
diff := e.Position.Sub(d.start)
|
diff := e.Position.Sub(d.start)
|
||||||
slop := cfg.Px(touchSlop)
|
slop := cfg.Dp(touchSlop)
|
||||||
if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
|
if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
|
||||||
d.grab = true
|
q.Execute(pointer.GrabCmd{Tag: d, ID: e.PointerID})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case pointer.Release, pointer.Cancel:
|
case pointer.Release, pointer.Cancel:
|
||||||
@@ -418,13 +427,12 @@ func (d *Drag) Events(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
d.dragging = false
|
d.dragging = false
|
||||||
d.grab = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
events = append(events, e)
|
return e, true
|
||||||
}
|
}
|
||||||
|
|
||||||
return events
|
return pointer.Event{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dragging reports whether it is currently in use.
|
// Dragging reports whether it is currently in use.
|
||||||
@@ -444,16 +452,16 @@ func (a Axis) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ct ClickType) String() string {
|
func (ct ClickKind) String() string {
|
||||||
switch ct {
|
switch ct {
|
||||||
case TypePress:
|
case KindPress:
|
||||||
return "TypePress"
|
return "KindPress"
|
||||||
case TypeClick:
|
case KindClick:
|
||||||
return "TypeClick"
|
return "KindClick"
|
||||||
case TypeCancel:
|
case KindCancel:
|
||||||
return "TypeCancel"
|
return "KindCancel"
|
||||||
default:
|
default:
|
||||||
panic("invalid ClickType")
|
panic("invalid ClickKind")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+95
-22
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/event"
|
||||||
|
"gioui.org/io/input"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/router"
|
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
)
|
)
|
||||||
@@ -22,20 +22,21 @@ func TestHover(t *testing.T) {
|
|||||||
stack := clip.Rect(rect).Push(ops)
|
stack := clip.Rect(rect).Push(ops)
|
||||||
h.Add(ops)
|
h.Add(ops)
|
||||||
stack.Pop()
|
stack.Pop()
|
||||||
r := new(router.Router)
|
r := new(input.Router)
|
||||||
|
h.Update(r.Source())
|
||||||
r.Frame(ops)
|
r.Frame(ops)
|
||||||
|
|
||||||
r.Queue(
|
r.Queue(
|
||||||
pointer.Event{Type: pointer.Move, Position: f32.Pt(30, 30)},
|
pointer.Event{Kind: pointer.Move, Position: f32.Pt(30, 30)},
|
||||||
)
|
)
|
||||||
if !h.Hovered(r) {
|
if !h.Update(r.Source()) {
|
||||||
t.Fatal("expected hovered")
|
t.Fatal("expected hovered")
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Queue(
|
r.Queue(
|
||||||
pointer.Event{Type: pointer.Move, Position: f32.Pt(50, 50)},
|
pointer.Event{Kind: pointer.Move, Position: f32.Pt(50, 50)},
|
||||||
)
|
)
|
||||||
if h.Hovered(r) {
|
if h.Update(r.Source()) {
|
||||||
t.Fatal("expected not hovered")
|
t.Fatal("expected not hovered")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,12 +72,21 @@ func TestMouseClicks(t *testing.T) {
|
|||||||
var ops op.Ops
|
var ops op.Ops
|
||||||
click.Add(&ops)
|
click.Add(&ops)
|
||||||
|
|
||||||
var r router.Router
|
var r input.Router
|
||||||
|
click.Update(r.Source())
|
||||||
r.Frame(&ops)
|
r.Frame(&ops)
|
||||||
r.Queue(tc.events...)
|
r.Queue(tc.events...)
|
||||||
|
|
||||||
events := click.Events(&r)
|
var clicks []ClickEvent
|
||||||
clicks := filterMouseClicks(events)
|
for {
|
||||||
|
ev, ok := click.Update(r.Source())
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ev.Kind == KindClick {
|
||||||
|
clicks = append(clicks, ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
if got, want := len(clicks), len(tc.clicks); got != want {
|
if got, want := len(clicks), len(tc.clicks); got != want {
|
||||||
t.Fatalf("got %d mouse clicks, expected %d", got, want)
|
t.Fatalf("got %d mouse clicks, expected %d", got, want)
|
||||||
}
|
}
|
||||||
@@ -90,28 +100,91 @@ func TestMouseClicks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClickPointerIDReassignment(t *testing.T) {
|
||||||
|
// A Click must accept a Press from a PointerID that differs from the
|
||||||
|
// one its hovered state was previously associated with. Some backends
|
||||||
|
// reassign a single physical pointer's ID over its lifetime — e.g. the
|
||||||
|
// Windows pointer API across focus changes — and locking the gesture
|
||||||
|
// to the first observed ID would silently drop every subsequent press.
|
||||||
|
//
|
||||||
|
// The sequence below puts the gesture into the buggy state through
|
||||||
|
// public events alone: a press under PointerID 1 starts an active
|
||||||
|
// press cycle, a Move under PointerID 2 arrives mid-press (which the
|
||||||
|
// router routes as an Enter for PID 2 but the gesture's Enter handler
|
||||||
|
// is a no-op for pid while pressed), then PID 1 releases. After this,
|
||||||
|
// the router has the gesture entered for PID 2 (so the next event
|
||||||
|
// under PID 2 won't trigger another Enter) but the gesture itself
|
||||||
|
// still has pid=1.
|
||||||
|
var click Click
|
||||||
|
var ops op.Ops
|
||||||
|
rect := image.Rect(0, 0, 100, 100)
|
||||||
|
stack := clip.Rect(rect).Push(&ops)
|
||||||
|
click.Add(&ops)
|
||||||
|
stack.Pop()
|
||||||
|
|
||||||
|
var r input.Router
|
||||||
|
click.Update(r.Source())
|
||||||
|
r.Frame(&ops)
|
||||||
|
|
||||||
|
drain := func() {
|
||||||
|
for {
|
||||||
|
if _, ok := click.Update(r.Source()); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Press under PointerID 1.
|
||||||
|
r.Queue(
|
||||||
|
pointer.Event{Kind: pointer.Move, Source: pointer.Mouse, Position: f32.Pt(50, 50), PointerID: 1},
|
||||||
|
pointer.Event{Kind: pointer.Press, Source: pointer.Mouse, Buttons: pointer.ButtonPrimary, Position: f32.Pt(50, 50), PointerID: 1},
|
||||||
|
)
|
||||||
|
drain()
|
||||||
|
|
||||||
|
// Move under PointerID 2 while PointerID 1 is still pressed. The
|
||||||
|
// router records the gesture as entered for PointerID 2 but the
|
||||||
|
// gesture's Enter handler is a no-op for pid because c.pressed.
|
||||||
|
r.Queue(pointer.Event{Kind: pointer.Move, Source: pointer.Mouse, Position: f32.Pt(50, 50), PointerID: 2})
|
||||||
|
drain()
|
||||||
|
|
||||||
|
// Release PointerID 1. PointerID 1's press tracking ends; the
|
||||||
|
// gesture's recorded pid stays at 1.
|
||||||
|
r.Queue(pointer.Event{Kind: pointer.Release, Source: pointer.Mouse, Position: f32.Pt(50, 50), PointerID: 1})
|
||||||
|
drain()
|
||||||
|
|
||||||
|
// Press under PointerID 2. The router won't refire Enter for PID 2
|
||||||
|
// (the gesture is already in PID 2's entered set), so the gesture's
|
||||||
|
// only chance to refresh its pid is the Press handler itself.
|
||||||
|
r.Queue(pointer.Event{Kind: pointer.Press, Source: pointer.Mouse, Buttons: pointer.ButtonPrimary, Position: f32.Pt(50, 50), PointerID: 2})
|
||||||
|
|
||||||
|
var sawPress bool
|
||||||
|
for {
|
||||||
|
ev, ok := click.Update(r.Source())
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ev.Kind == KindPress {
|
||||||
|
sawPress = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !sawPress {
|
||||||
|
t.Fatal("expected KindPress for press under reassigned PointerID; gesture dropped the press because of stale recorded pid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func mouseClickEvents(times ...time.Duration) []event.Event {
|
func mouseClickEvents(times ...time.Duration) []event.Event {
|
||||||
press := pointer.Event{
|
press := pointer.Event{
|
||||||
Type: pointer.Press,
|
Kind: pointer.Press,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: pointer.ButtonPrimary,
|
Buttons: pointer.ButtonPrimary,
|
||||||
}
|
}
|
||||||
events := make([]event.Event, 0, 2*len(times))
|
events := make([]event.Event, 0, 2*len(times))
|
||||||
for _, t := range times {
|
for _, t := range times {
|
||||||
|
press := press
|
||||||
|
press.Time = t
|
||||||
release := press
|
release := press
|
||||||
release.Type = pointer.Release
|
release.Kind = pointer.Release
|
||||||
release.Time = t
|
|
||||||
events = append(events, press, release)
|
events = append(events, press, release)
|
||||||
}
|
}
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterMouseClicks(events []ClickEvent) []ClickEvent {
|
|
||||||
var clicks []ClickEvent
|
|
||||||
for _, ev := range events {
|
|
||||||
if ev.Type == TypeClick {
|
|
||||||
clicks = append(clicks, ev)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return clicks
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
module gioui.org
|
module gioui.org
|
||||||
|
|
||||||
go 1.17
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/exp v0.0.0-20210722180016-6781d3edade3
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
gioui.org/shader v1.0.8
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
github.com/go-text/typesetting v0.3.4
|
||||||
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||||
|
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0
|
||||||
|
golang.org/x/image v0.26.0
|
||||||
|
golang.org/x/sys v0.39.0
|
||||||
|
golang.org/x/text v0.32.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require golang.org/x/net v0.48.0
|
||||||
eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3
|
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
|
|
||||||
gioui.org/shader v1.0.6
|
|
||||||
github.com/benoitkugler/textlayout v0.0.10
|
|
||||||
github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12
|
|
||||||
github.com/go-text/typesetting v0.0.0-20220112121102-58fe93c84506
|
|
||||||
)
|
|
||||||
|
|
||||||
require golang.org/x/text v0.3.6 // indirect
|
|
||||||
|
|||||||
@@ -1,453 +1,21 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
|
||||||
eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3 h1:djFprmHZgrSepsHAIRMp5UJn3PzsoTg9drI+BDmif5Q=
|
|
||||||
eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
|
||||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
|
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||||
gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y=
|
github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU=
|
||||||
gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
github.com/go-text/typesetting v0.3.4/go.mod h1:4qZCQphq4KSgGTAeI0uMEkVbROgfah8BuyF5LRYr7XY=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3 h1:drBZzMgdYPbmyXqOto4YhhJGrFIQCX94FpR4MzTCsos=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
||||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
|
||||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
|
||||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
|
||||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
|
||||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
|
||||||
github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE=
|
|
||||||
github.com/benoitkugler/textlayout v0.0.5/go.mod h1:puH4v13Uz7uIhIH0XMk5jgc8U3MXcn5r3VlV9K8n0D8=
|
|
||||||
github.com/benoitkugler/textlayout v0.0.10 h1:uIaQgH4pBFw1LQ0tPkfjgxo94WYcckzzQaB41L2X84w=
|
|
||||||
github.com/benoitkugler/textlayout v0.0.10/go.mod h1:puH4v13Uz7uIhIH0XMk5jgc8U3MXcn5r3VlV9K8n0D8=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
|
||||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
|
|
||||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
|
||||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
||||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
|
||||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
|
||||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
|
||||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
|
||||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
|
||||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12 h1:1bjaB/5IIicfKpP4k0s30T2WEw//Kh00zULa8DQ0cxA=
|
|
||||||
github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12/go.mod h1:kDhBRTA/i3H46PVdhqcw26TdGSIj42TOKNWKY+Kipnw=
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
|
||||||
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
|
||||||
github.com/go-text/typesetting v0.0.0-20220112121102-58fe93c84506 h1:1TPz/Gn/MsXwJ6bEtI9wdkPcQYr2X3V9I+wz4wPYUdY=
|
|
||||||
github.com/go-text/typesetting v0.0.0-20220112121102-58fe93c84506/go.mod h1:R0mlTNeyszZ/tKQhbZA7SRGjx+OHsmNzgN2jTV7yZcs=
|
|
||||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
|
||||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
|
||||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
|
||||||
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
|
||||||
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
|
||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
||||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
|
||||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
|
||||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
|
||||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
|
||||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
|
||||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
|
||||||
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
|
||||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
|
||||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
|
||||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
|
||||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
|
||||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
|
||||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
|
||||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
|
||||||
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
|
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
|
||||||
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
|
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
|
||||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
|
||||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
|
||||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
|
||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
|
||||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
|
||||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
|
||||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
|
||||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
|
||||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
|
||||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
|
||||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
|
||||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
|
||||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
|
||||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
|
||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
|
||||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
|
||||||
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
|
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
|
||||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
|
||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
|
||||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
|
||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
|
||||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
|
||||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
|
||||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
|
||||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
|
||||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
|
||||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
|
||||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
|
||||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
|
||||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
|
|
||||||
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
|
|
||||||
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
|
|
||||||
go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
|
|
||||||
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
|
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
|
||||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
|
||||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
|
||||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
|
||||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
|
||||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|
||||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
|
||||||
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
|
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
|
||||||
golang.org/x/exp v0.0.0-20210722180016-6781d3edade3 h1:IlrJD2AM5p8JhN/wVny9jt6gJ9hut2VALhSeZ3SYluk=
|
|
||||||
golang.org/x/exp v0.0.0-20210722180016-6781d3edade3/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
|
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
||||||
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
|
|
||||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
|
||||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
|
||||||
|
|||||||
+39
-28
@@ -5,12 +5,21 @@ package gpu
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/internal/f32"
|
||||||
)
|
)
|
||||||
|
|
||||||
type resourceCache struct {
|
type textureCacheKey struct {
|
||||||
res map[interface{}]resource
|
filter byte
|
||||||
newRes map[interface{}]resource
|
handle any
|
||||||
|
}
|
||||||
|
|
||||||
|
type textureCache struct {
|
||||||
|
res map[textureCacheKey]resourceCacheValue
|
||||||
|
}
|
||||||
|
|
||||||
|
type resourceCacheValue struct {
|
||||||
|
used bool
|
||||||
|
resource resource
|
||||||
}
|
}
|
||||||
|
|
||||||
// opCache is like a resourceCache but using concrete types and a
|
// opCache is like a resourceCache but using concrete types and a
|
||||||
@@ -33,48 +42,50 @@ type opCacheValue struct {
|
|||||||
keep bool
|
keep bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newResourceCache() *resourceCache {
|
func newTextureCache() *textureCache {
|
||||||
return &resourceCache{
|
return &textureCache{
|
||||||
res: make(map[interface{}]resource),
|
res: make(map[textureCacheKey]resourceCacheValue),
|
||||||
newRes: make(map[interface{}]resource),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resourceCache) get(key interface{}) (resource, bool) {
|
func (r *textureCache) get(key textureCacheKey) (resource, bool) {
|
||||||
v, exists := r.res[key]
|
v, exists := r.res[key]
|
||||||
if exists {
|
if !exists {
|
||||||
r.newRes[key] = v
|
return nil, false
|
||||||
}
|
}
|
||||||
return v, exists
|
if !v.used {
|
||||||
|
v.used = true
|
||||||
|
r.res[key] = v
|
||||||
|
}
|
||||||
|
return v.resource, exists
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resourceCache) put(key interface{}, val resource) {
|
func (r *textureCache) put(key textureCacheKey, val resource) {
|
||||||
if _, exists := r.newRes[key]; exists {
|
v, exists := r.res[key]
|
||||||
panic(fmt.Errorf("key exists, %p", key))
|
if exists && v.used {
|
||||||
|
panic(fmt.Errorf("key exists, %v", key))
|
||||||
}
|
}
|
||||||
r.res[key] = val
|
v.used = true
|
||||||
r.newRes[key] = val
|
v.resource = val
|
||||||
|
r.res[key] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resourceCache) frame() {
|
func (r *textureCache) frame() {
|
||||||
for k, v := range r.res {
|
for k, v := range r.res {
|
||||||
if _, exists := r.newRes[k]; !exists {
|
if v.used {
|
||||||
|
v.used = false
|
||||||
|
r.res[k] = v
|
||||||
|
} else {
|
||||||
delete(r.res, k)
|
delete(r.res, k)
|
||||||
v.release()
|
v.resource.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for k, v := range r.newRes {
|
|
||||||
delete(r.newRes, k)
|
|
||||||
r.res[k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resourceCache) release() {
|
func (r *textureCache) release() {
|
||||||
r.frame()
|
|
||||||
for _, v := range r.res {
|
for _, v := range r.res {
|
||||||
v.release()
|
v.resource.release()
|
||||||
}
|
}
|
||||||
r.newRes = nil
|
|
||||||
r.res = nil
|
r.res = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+41
-9
@@ -1,7 +1,10 @@
|
|||||||
package gpu
|
package gpu
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gioui.org/f32"
|
"encoding/binary"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"gioui.org/internal/f32"
|
||||||
"gioui.org/internal/stroke"
|
"gioui.org/internal/stroke"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -9,19 +12,48 @@ type quadSplitter struct {
|
|||||||
bounds f32.Rectangle
|
bounds f32.Rectangle
|
||||||
contour uint32
|
contour uint32
|
||||||
d *drawOps
|
d *drawOps
|
||||||
|
|
||||||
|
// scratch space used by calls to stroke.SplitCubic
|
||||||
|
scratch []stroke.QuadSegment
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
|
func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
|
||||||
// NW.
|
// inlined code:
|
||||||
encodeVertex(data, meta, -1, 1, from, ctrl, to)
|
// encodeVertex(data, meta, 1, -1, from, ctrl, to)
|
||||||
// NE.
|
// encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
|
||||||
encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
|
// encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
|
||||||
// SW.
|
// encodeVertex(data[vertStride*3:], meta, -1, 1, from, ctrl, to)
|
||||||
encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
|
// this code needs to stay in sync with `vertex.encode`.
|
||||||
// SE.
|
|
||||||
encodeVertex(data[vertStride*3:], meta, 1, -1, from, ctrl, to)
|
bo := binary.LittleEndian
|
||||||
|
data = data[:vertStride*4]
|
||||||
|
|
||||||
|
// encode the main template
|
||||||
|
bo.PutUint32(data[4:8], meta)
|
||||||
|
bo.PutUint32(data[8:12], math.Float32bits(from.X))
|
||||||
|
bo.PutUint32(data[12:16], math.Float32bits(from.Y))
|
||||||
|
bo.PutUint32(data[16:20], math.Float32bits(ctrl.X))
|
||||||
|
bo.PutUint32(data[20:24], math.Float32bits(ctrl.Y))
|
||||||
|
bo.PutUint32(data[24:28], math.Float32bits(to.X))
|
||||||
|
bo.PutUint32(data[28:32], math.Float32bits(to.Y))
|
||||||
|
|
||||||
|
copy(data[vertStride*1:vertStride*2], data[vertStride*0:vertStride*1])
|
||||||
|
copy(data[vertStride*2:vertStride*3], data[vertStride*0:vertStride*1])
|
||||||
|
copy(data[vertStride*3:vertStride*4], data[vertStride*0:vertStride*1])
|
||||||
|
|
||||||
|
bo.PutUint32(data[vertStride*0:vertStride*0+4], math.Float32bits(nwCorner))
|
||||||
|
bo.PutUint32(data[vertStride*1:vertStride*1+4], math.Float32bits(neCorner))
|
||||||
|
bo.PutUint32(data[vertStride*2:vertStride*2+4], math.Float32bits(swCorner))
|
||||||
|
bo.PutUint32(data[vertStride*3:vertStride*3+4], math.Float32bits(seCorner))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
nwCorner = 1*0.5 + 0*0.25
|
||||||
|
neCorner = 1*0.5 + 1*0.25
|
||||||
|
swCorner = 0*0.5 + 0*0.25
|
||||||
|
seCorner = 0*0.5 + 1*0.25
|
||||||
|
)
|
||||||
|
|
||||||
func encodeVertex(data []byte, meta uint32, cornerx, cornery int16, from, ctrl, to f32.Point) {
|
func encodeVertex(data []byte, meta uint32, cornerx, cornery int16, from, ctrl, to f32.Point) {
|
||||||
var corner float32
|
var corner float32
|
||||||
if cornerx == 1 {
|
if cornerx == 1 {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
package gpu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gioui.org/internal/f32"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkEncodeQuadTo(b *testing.B) {
|
||||||
|
var data [vertStride * 4]byte
|
||||||
|
for i := 0; b.Loop(); i++ {
|
||||||
|
v := float32(i)
|
||||||
|
encodeQuadTo(data[:], 123,
|
||||||
|
f32.Point{X: v, Y: v},
|
||||||
|
f32.Point{X: v, Y: v},
|
||||||
|
f32.Point{X: v, Y: v},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
-2219
File diff suppressed because it is too large
Load Diff
-129
@@ -1,129 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package gpu
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"gioui.org/cpu"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This file contains code specific to running compute shaders on the CPU.
|
|
||||||
|
|
||||||
// dispatcher dispatches CPU compute programs across multiple goroutines.
|
|
||||||
type dispatcher struct {
|
|
||||||
// done is notified when a worker completes its work slice.
|
|
||||||
done chan struct{}
|
|
||||||
// work receives work slice indices. It is closed when the dispatcher is released.
|
|
||||||
work chan work
|
|
||||||
// dispatch receives compute jobs, which is then split among workers.
|
|
||||||
dispatch chan dispatch
|
|
||||||
// sync receives notification when a Sync completes.
|
|
||||||
sync chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type work struct {
|
|
||||||
ctx *cpu.DispatchContext
|
|
||||||
index int
|
|
||||||
}
|
|
||||||
|
|
||||||
type dispatch struct {
|
|
||||||
_type jobType
|
|
||||||
program *cpu.ProgramInfo
|
|
||||||
descSet unsafe.Pointer
|
|
||||||
x, y, z int
|
|
||||||
}
|
|
||||||
|
|
||||||
type jobType uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
jobDispatch jobType = iota
|
|
||||||
jobBarrier
|
|
||||||
jobSync
|
|
||||||
)
|
|
||||||
|
|
||||||
func newDispatcher(workers int) *dispatcher {
|
|
||||||
d := &dispatcher{
|
|
||||||
work: make(chan work, workers),
|
|
||||||
done: make(chan struct{}, workers),
|
|
||||||
// Leave some room to avoid blocking calls to Dispatch.
|
|
||||||
dispatch: make(chan dispatch, 20),
|
|
||||||
sync: make(chan struct{}),
|
|
||||||
}
|
|
||||||
for i := 0; i < workers; i++ {
|
|
||||||
go d.worker()
|
|
||||||
}
|
|
||||||
go d.dispatcher()
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dispatcher) dispatcher() {
|
|
||||||
defer close(d.work)
|
|
||||||
var free []*cpu.DispatchContext
|
|
||||||
defer func() {
|
|
||||||
for _, ctx := range free {
|
|
||||||
ctx.Free()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
var used []*cpu.DispatchContext
|
|
||||||
for job := range d.dispatch {
|
|
||||||
switch job._type {
|
|
||||||
case jobDispatch:
|
|
||||||
if len(free) == 0 {
|
|
||||||
free = append(free, cpu.NewDispatchContext())
|
|
||||||
}
|
|
||||||
ctx := free[len(free)-1]
|
|
||||||
free = free[:len(free)-1]
|
|
||||||
used = append(used, ctx)
|
|
||||||
ctx.Prepare(cap(d.work), job.program, job.descSet, job.x, job.y, job.z)
|
|
||||||
for i := 0; i < cap(d.work); i++ {
|
|
||||||
d.work <- work{
|
|
||||||
ctx: ctx,
|
|
||||||
index: i,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case jobBarrier:
|
|
||||||
// Wait for all outstanding dispatches to complete.
|
|
||||||
for i := 0; i < len(used)*cap(d.work); i++ {
|
|
||||||
<-d.done
|
|
||||||
}
|
|
||||||
free = append(free, used...)
|
|
||||||
used = used[:0]
|
|
||||||
case jobSync:
|
|
||||||
d.sync <- struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dispatcher) worker() {
|
|
||||||
thread := cpu.NewThreadContext()
|
|
||||||
defer thread.Free()
|
|
||||||
for w := range d.work {
|
|
||||||
w.ctx.Dispatch(w.index, thread)
|
|
||||||
d.done <- struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dispatcher) Barrier() {
|
|
||||||
d.dispatch <- dispatch{_type: jobBarrier}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dispatcher) Sync() {
|
|
||||||
d.dispatch <- dispatch{_type: jobSync}
|
|
||||||
<-d.sync
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dispatcher) Dispatch(program *cpu.ProgramInfo, descSet unsafe.Pointer, x, y, z int) {
|
|
||||||
d.dispatch <- dispatch{
|
|
||||||
_type: jobDispatch,
|
|
||||||
program: program,
|
|
||||||
descSet: descSet,
|
|
||||||
x: x,
|
|
||||||
y: y,
|
|
||||||
z: z,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dispatcher) Stop() {
|
|
||||||
close(d.dispatch)
|
|
||||||
}
|
|
||||||
+387
-192
@@ -9,18 +9,19 @@ package gpu
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/f32"
|
|
||||||
"gioui.org/gpu/internal/driver"
|
"gioui.org/gpu/internal/driver"
|
||||||
"gioui.org/internal/byteslice"
|
"gioui.org/internal/byteslice"
|
||||||
|
"gioui.org/internal/f32"
|
||||||
"gioui.org/internal/f32color"
|
"gioui.org/internal/f32color"
|
||||||
"gioui.org/internal/ops"
|
"gioui.org/internal/ops"
|
||||||
"gioui.org/internal/scene"
|
"gioui.org/internal/scene"
|
||||||
@@ -44,14 +45,10 @@ type GPU interface {
|
|||||||
Clear(color color.NRGBA)
|
Clear(color color.NRGBA)
|
||||||
// Frame draws the graphics operations from op into a viewport of target.
|
// Frame draws the graphics operations from op into a viewport of target.
|
||||||
Frame(frame *op.Ops, target RenderTarget, viewport image.Point) error
|
Frame(frame *op.Ops, target RenderTarget, viewport image.Point) error
|
||||||
// Profile returns the last available profiling information. Profiling
|
|
||||||
// information is requested when Frame sees an io/profile.Op, and the result
|
|
||||||
// is available through Profile at some later time.
|
|
||||||
Profile() string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type gpu struct {
|
type gpu struct {
|
||||||
cache *resourceCache
|
cache *textureCache
|
||||||
|
|
||||||
profile string
|
profile string
|
||||||
timers *timers
|
timers *timers
|
||||||
@@ -68,22 +65,39 @@ type renderer struct {
|
|||||||
pather *pather
|
pather *pather
|
||||||
packer packer
|
packer packer
|
||||||
intersections packer
|
intersections packer
|
||||||
|
layers packer
|
||||||
|
layerFBOs fboSet
|
||||||
}
|
}
|
||||||
|
|
||||||
type drawOps struct {
|
type drawOps struct {
|
||||||
profile bool
|
reader ops.Reader
|
||||||
reader ops.Reader
|
states []f32.Affine2D
|
||||||
states []f32.Affine2D
|
transStack []f32.Affine2D
|
||||||
transStack []f32.Affine2D
|
layers []opacityLayer
|
||||||
vertCache []byte
|
opacityStack []int
|
||||||
viewport image.Point
|
vertCache []byte
|
||||||
clear bool
|
viewport image.Point
|
||||||
clearColor f32color.RGBA
|
clear bool
|
||||||
imageOps []imageOp
|
clearColor f32color.RGBA
|
||||||
pathOps []*pathOp
|
imageOps []imageOp
|
||||||
pathOpCache []pathOp
|
pathOps []*pathOp
|
||||||
qs quadSplitter
|
pathOpCache []pathOp
|
||||||
pathCache *opCache
|
qs quadSplitter
|
||||||
|
pathCache *opCache
|
||||||
|
}
|
||||||
|
|
||||||
|
type opacityLayer struct {
|
||||||
|
opacity float32
|
||||||
|
parent int
|
||||||
|
// depth of the opacity stack. Layers of equal depth are
|
||||||
|
// independent and may be packed into one atlas.
|
||||||
|
depth int
|
||||||
|
// opStart and opEnd denote the range of drawOps.imageOps
|
||||||
|
// that belong to the layer.
|
||||||
|
opStart, opEnd int
|
||||||
|
// clip of the layer operations.
|
||||||
|
clip image.Rectangle
|
||||||
|
place placement
|
||||||
}
|
}
|
||||||
|
|
||||||
type drawState struct {
|
type drawState struct {
|
||||||
@@ -127,7 +141,12 @@ type imageOp struct {
|
|||||||
clip image.Rectangle
|
clip image.Rectangle
|
||||||
material material
|
material material
|
||||||
clipType clipType
|
clipType clipType
|
||||||
place placement
|
// place is either a placement in the path fbos or intersection fbos,
|
||||||
|
// depending on clipType.
|
||||||
|
place placement
|
||||||
|
// layerOps is the number of operations this
|
||||||
|
// operation replaces.
|
||||||
|
layerOps int
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeStrokeOp(data []byte) float32 {
|
func decodeStrokeOp(data []byte) float32 {
|
||||||
@@ -154,17 +173,25 @@ type material struct {
|
|||||||
// For materialTypeColor.
|
// For materialTypeColor.
|
||||||
color f32color.RGBA
|
color f32color.RGBA
|
||||||
// For materialTypeLinearGradient.
|
// For materialTypeLinearGradient.
|
||||||
color1 f32color.RGBA
|
color1 f32color.RGBA
|
||||||
color2 f32color.RGBA
|
color2 f32color.RGBA
|
||||||
|
opacity float32
|
||||||
// For materialTypeTexture.
|
// For materialTypeTexture.
|
||||||
data imageOpData
|
data imageOpData
|
||||||
|
tex driver.Texture
|
||||||
uvTrans f32.Affine2D
|
uvTrans f32.Affine2D
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
filterLinear = 0
|
||||||
|
filterNearest = 1
|
||||||
|
)
|
||||||
|
|
||||||
// imageOpData is the shadow of paint.ImageOp.
|
// imageOpData is the shadow of paint.ImageOp.
|
||||||
type imageOpData struct {
|
type imageOpData struct {
|
||||||
src *image.RGBA
|
src *image.RGBA
|
||||||
handle interface{}
|
handle any
|
||||||
|
filter byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type linearGradientOpData struct {
|
type linearGradientOpData struct {
|
||||||
@@ -174,7 +201,7 @@ type linearGradientOpData struct {
|
|||||||
color2 color.NRGBA
|
color2 color.NRGBA
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeImageOp(data []byte, refs []interface{}) imageOpData {
|
func decodeImageOp(data []byte, refs []any) imageOpData {
|
||||||
handle := refs[1]
|
handle := refs[1]
|
||||||
if handle == nil {
|
if handle == nil {
|
||||||
return imageOpData{}
|
return imageOpData{}
|
||||||
@@ -182,10 +209,12 @@ func decodeImageOp(data []byte, refs []interface{}) imageOpData {
|
|||||||
return imageOpData{
|
return imageOpData{
|
||||||
src: refs[0].(*image.RGBA),
|
src: refs[0].(*image.RGBA),
|
||||||
handle: handle,
|
handle: handle,
|
||||||
|
filter: data[1],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeColorOp(data []byte) color.NRGBA {
|
func decodeColorOp(data []byte) color.NRGBA {
|
||||||
|
data = data[:ops.TypeColorLen]
|
||||||
return color.NRGBA{
|
return color.NRGBA{
|
||||||
R: data[1],
|
R: data[1],
|
||||||
G: data[2],
|
G: data[2],
|
||||||
@@ -195,6 +224,7 @@ func decodeColorOp(data []byte) color.NRGBA {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func decodeLinearGradientOp(data []byte) linearGradientOpData {
|
func decodeLinearGradientOp(data []byte) linearGradientOpData {
|
||||||
|
data = data[:ops.TypeLinearGradientLen]
|
||||||
bo := binary.LittleEndian
|
bo := binary.LittleEndian
|
||||||
return linearGradientOpData{
|
return linearGradientOpData{
|
||||||
stop1: f32.Point{
|
stop1: f32.Point{
|
||||||
@@ -220,8 +250,6 @@ func decodeLinearGradientOp(data []byte) linearGradientOpData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type clipType uint8
|
|
||||||
|
|
||||||
type resource interface {
|
type resource interface {
|
||||||
release()
|
release()
|
||||||
}
|
}
|
||||||
@@ -234,7 +262,7 @@ type texture struct {
|
|||||||
type blitter struct {
|
type blitter struct {
|
||||||
ctx driver.Device
|
ctx driver.Device
|
||||||
viewport image.Point
|
viewport image.Point
|
||||||
pipelines [3]*pipeline
|
pipelines [2][3]*pipeline
|
||||||
colUniforms *blitColUniforms
|
colUniforms *blitColUniforms
|
||||||
texUniforms *blitTexUniforms
|
texUniforms *blitTexUniforms
|
||||||
linearGradientUniforms *blitLinearGradientUniforms
|
linearGradientUniforms *blitLinearGradientUniforms
|
||||||
@@ -271,6 +299,9 @@ type blitUniforms struct {
|
|||||||
transform [4]float32
|
transform [4]float32
|
||||||
uvTransformR1 [4]float32
|
uvTransformR1 [4]float32
|
||||||
uvTransformR2 [4]float32
|
uvTransformR2 [4]float32
|
||||||
|
opacity float32
|
||||||
|
fbo float32
|
||||||
|
_ [2]float32
|
||||||
}
|
}
|
||||||
|
|
||||||
type colorUniforms struct {
|
type colorUniforms struct {
|
||||||
@@ -282,7 +313,7 @@ type gradientUniforms struct {
|
|||||||
color2 f32color.RGBA
|
color2 f32color.RGBA
|
||||||
}
|
}
|
||||||
|
|
||||||
type materialType uint8
|
type clipType uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
clipTypeNone clipType = iota
|
clipTypeNone clipType = iota
|
||||||
@@ -290,6 +321,8 @@ const (
|
|||||||
clipTypeIntersection
|
clipTypeIntersection
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type materialType uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
materialColor materialType = iota
|
materialColor materialType = iota
|
||||||
materialLinearGradient
|
materialLinearGradient
|
||||||
@@ -311,18 +344,17 @@ func New(api API) (GPU, error) {
|
|||||||
func NewWithDevice(d driver.Device) (GPU, error) {
|
func NewWithDevice(d driver.Device) (GPU, error) {
|
||||||
d.BeginFrame(nil, false, image.Point{})
|
d.BeginFrame(nil, false, image.Point{})
|
||||||
defer d.EndFrame()
|
defer d.EndFrame()
|
||||||
forceCompute := os.Getenv("GIORENDERER") == "forcecompute"
|
|
||||||
feats := d.Caps().Features
|
feats := d.Caps().Features
|
||||||
switch {
|
switch {
|
||||||
case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
|
case feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
|
||||||
return newGPU(d)
|
return newGPU(d)
|
||||||
}
|
}
|
||||||
return newCompute(d)
|
return nil, errors.New("no available GPU driver")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGPU(ctx driver.Device) (*gpu, error) {
|
func newGPU(ctx driver.Device) (*gpu, error) {
|
||||||
g := &gpu{
|
g := &gpu{
|
||||||
cache: newResourceCache(),
|
cache: newTextureCache(),
|
||||||
}
|
}
|
||||||
g.drawOps.pathCache = newOpCache()
|
g.drawOps.pathCache = newOpCache()
|
||||||
if err := g.init(ctx); err != nil {
|
if err := g.init(ctx); err != nil {
|
||||||
@@ -362,7 +394,7 @@ func (g *gpu) collect(viewport image.Point, frameOps *op.Ops) {
|
|||||||
g.renderer.pather.viewport = viewport
|
g.renderer.pather.viewport = viewport
|
||||||
g.drawOps.reset(viewport)
|
g.drawOps.reset(viewport)
|
||||||
g.drawOps.collect(frameOps, viewport)
|
g.drawOps.collect(frameOps, viewport)
|
||||||
if g.drawOps.profile && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) {
|
if false && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) {
|
||||||
g.frameStart = time.Now()
|
g.frameStart = time.Now()
|
||||||
g.timers = newTimers(g.ctx)
|
g.timers = newTimers(g.ctx)
|
||||||
g.stencilTimer = g.timers.newTimer()
|
g.stencilTimer = g.timers.newTimer()
|
||||||
@@ -388,7 +420,9 @@ func (g *gpu) frame(target RenderTarget) error {
|
|||||||
g.stencilTimer.end()
|
g.stencilTimer.end()
|
||||||
g.coverTimer.begin()
|
g.coverTimer.begin()
|
||||||
g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
|
g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
|
||||||
g.renderer.prepareDrawOps(g.cache, g.drawOps.imageOps)
|
g.renderer.prepareDrawOps(g.drawOps.imageOps)
|
||||||
|
g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers)
|
||||||
|
g.renderer.drawLayers(g.drawOps.layers, g.drawOps.imageOps)
|
||||||
d := driver.LoadDesc{
|
d := driver.LoadDesc{
|
||||||
ClearColor: g.drawOps.clearColor,
|
ClearColor: g.drawOps.clearColor,
|
||||||
}
|
}
|
||||||
@@ -398,14 +432,14 @@ func (g *gpu) frame(target RenderTarget) error {
|
|||||||
}
|
}
|
||||||
g.ctx.BeginRenderPass(defFBO, d)
|
g.ctx.BeginRenderPass(defFBO, d)
|
||||||
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
||||||
g.renderer.drawOps(g.cache, g.drawOps.imageOps)
|
g.renderer.drawOps(false, image.Point{}, g.renderer.blitter.viewport, g.drawOps.imageOps)
|
||||||
g.coverTimer.end()
|
g.coverTimer.end()
|
||||||
g.ctx.EndRenderPass()
|
g.ctx.EndRenderPass()
|
||||||
g.cleanupTimer.begin()
|
g.cleanupTimer.begin()
|
||||||
g.cache.frame()
|
g.cache.frame()
|
||||||
g.drawOps.pathCache.frame()
|
g.drawOps.pathCache.frame()
|
||||||
g.cleanupTimer.end()
|
g.cleanupTimer.end()
|
||||||
if g.drawOps.profile && g.timers.ready() {
|
if false && g.timers.ready() {
|
||||||
st, covt, cleant := g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
|
st, covt, cleant := g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
|
||||||
ft := st + covt + cleant
|
ft := st + covt + cleant
|
||||||
q := 100 * time.Microsecond
|
q := 100 * time.Microsecond
|
||||||
@@ -421,20 +455,38 @@ func (g *gpu) Profile() string {
|
|||||||
return g.profile
|
return g.profile
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) texHandle(cache *resourceCache, data imageOpData) driver.Texture {
|
func (r *renderer) texHandle(cache *textureCache, data imageOpData) driver.Texture {
|
||||||
|
key := textureCacheKey{
|
||||||
|
filter: data.filter,
|
||||||
|
handle: data.handle,
|
||||||
|
}
|
||||||
|
|
||||||
var tex *texture
|
var tex *texture
|
||||||
t, exists := cache.get(data.handle)
|
t, exists := cache.get(key)
|
||||||
if !exists {
|
if !exists {
|
||||||
t = &texture{
|
t = &texture{
|
||||||
src: data.src,
|
src: data.src,
|
||||||
}
|
}
|
||||||
cache.put(data.handle, t)
|
cache.put(key, t)
|
||||||
}
|
}
|
||||||
tex = t.(*texture)
|
tex = t.(*texture)
|
||||||
if tex.tex != nil {
|
if tex.tex != nil {
|
||||||
return tex.tex
|
return tex.tex
|
||||||
}
|
}
|
||||||
handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA, data.src.Bounds().Dx(), data.src.Bounds().Dy(), driver.FilterLinear, driver.FilterLinear, driver.BufferBindingTexture)
|
|
||||||
|
var minFilter, magFilter driver.TextureFilter
|
||||||
|
switch data.filter {
|
||||||
|
case filterLinear:
|
||||||
|
minFilter, magFilter = driver.FilterLinearMipmapLinear, driver.FilterLinear
|
||||||
|
case filterNearest:
|
||||||
|
minFilter, magFilter = driver.FilterNearest, driver.FilterNearest
|
||||||
|
}
|
||||||
|
|
||||||
|
handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA,
|
||||||
|
data.src.Bounds().Dx(), data.src.Bounds().Dy(),
|
||||||
|
minFilter, magFilter,
|
||||||
|
driver.BufferBindingTexture,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -462,15 +514,18 @@ func newRenderer(ctx driver.Device) *renderer {
|
|||||||
if cap := 8192; maxDim > cap {
|
if cap := 8192; maxDim > cap {
|
||||||
maxDim = cap
|
maxDim = cap
|
||||||
}
|
}
|
||||||
|
d := image.Pt(maxDim, maxDim)
|
||||||
|
|
||||||
r.packer.maxDims = image.Pt(maxDim, maxDim)
|
r.packer.maxDims = d
|
||||||
r.intersections.maxDims = image.Pt(maxDim, maxDim)
|
r.intersections.maxDims = d
|
||||||
|
r.layers.maxDims = d
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) release() {
|
func (r *renderer) release() {
|
||||||
r.pather.release()
|
r.pather.release()
|
||||||
r.blitter.release()
|
r.blitter.release()
|
||||||
|
r.layerFBOs.delete(r.ctx, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBlitter(ctx driver.Device) *blitter {
|
func newBlitter(ctx driver.Device) *blitter {
|
||||||
@@ -493,7 +548,7 @@ func newBlitter(ctx driver.Device) *blitter {
|
|||||||
b.texUniforms = new(blitTexUniforms)
|
b.texUniforms = new(blitTexUniforms)
|
||||||
b.linearGradientUniforms = new(blitLinearGradientUniforms)
|
b.linearGradientUniforms = new(blitLinearGradientUniforms)
|
||||||
pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
|
pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
|
||||||
[3]interface{}{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
|
[3]any{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -505,12 +560,24 @@ func newBlitter(ctx driver.Device) *blitter {
|
|||||||
func (b *blitter) release() {
|
func (b *blitter) release() {
|
||||||
b.quadVerts.Release()
|
b.quadVerts.Release()
|
||||||
for _, p := range b.pipelines {
|
for _, p := range b.pipelines {
|
||||||
p.Release()
|
for _, p := range p {
|
||||||
|
p.Release()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) ([3]*pipeline, error) {
|
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]any) (pipelines [2][3]*pipeline, err error) {
|
||||||
var pipelines [3]*pipeline
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
for _, p := range pipelines {
|
||||||
|
for _, p := range p {
|
||||||
|
if p != nil {
|
||||||
|
p.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
blend := driver.BlendDesc{
|
blend := driver.BlendDesc{
|
||||||
Enable: true,
|
Enable: true,
|
||||||
SrcFactor: driver.BlendFactorOne,
|
SrcFactor: driver.BlendFactorOne,
|
||||||
@@ -528,86 +595,76 @@ func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.
|
|||||||
return pipelines, err
|
return pipelines, err
|
||||||
}
|
}
|
||||||
defer vsh.Release()
|
defer vsh.Release()
|
||||||
{
|
for i, format := range []driver.TextureFormat{driver.TextureFormatOutput, driver.TextureFormatSRGBA} {
|
||||||
fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
|
{
|
||||||
if err != nil {
|
fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
|
||||||
return pipelines, err
|
if err != nil {
|
||||||
|
return pipelines, err
|
||||||
|
}
|
||||||
|
defer fsh.Release()
|
||||||
|
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||||
|
VertexShader: vsh,
|
||||||
|
FragmentShader: fsh,
|
||||||
|
BlendDesc: blend,
|
||||||
|
VertexLayout: layout,
|
||||||
|
PixelFormat: format,
|
||||||
|
Topology: driver.TopologyTriangleStrip,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return pipelines, err
|
||||||
|
}
|
||||||
|
var vertBuffer *uniformBuffer
|
||||||
|
if u := uniforms[materialTexture]; u != nil {
|
||||||
|
vertBuffer = newUniformBuffer(b, u)
|
||||||
|
}
|
||||||
|
pipelines[i][materialTexture] = &pipeline{pipe, vertBuffer}
|
||||||
}
|
}
|
||||||
defer fsh.Release()
|
{
|
||||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
var vertBuffer *uniformBuffer
|
||||||
VertexShader: vsh,
|
fsh, err := b.NewFragmentShader(fsSrc[materialColor])
|
||||||
FragmentShader: fsh,
|
if err != nil {
|
||||||
BlendDesc: blend,
|
return pipelines, err
|
||||||
VertexLayout: layout,
|
}
|
||||||
PixelFormat: driver.TextureFormatOutput,
|
defer fsh.Release()
|
||||||
Topology: driver.TopologyTriangleStrip,
|
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||||
})
|
VertexShader: vsh,
|
||||||
if err != nil {
|
FragmentShader: fsh,
|
||||||
return pipelines, err
|
BlendDesc: blend,
|
||||||
|
VertexLayout: layout,
|
||||||
|
PixelFormat: format,
|
||||||
|
Topology: driver.TopologyTriangleStrip,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return pipelines, err
|
||||||
|
}
|
||||||
|
if u := uniforms[materialColor]; u != nil {
|
||||||
|
vertBuffer = newUniformBuffer(b, u)
|
||||||
|
}
|
||||||
|
pipelines[i][materialColor] = &pipeline{pipe, vertBuffer}
|
||||||
}
|
}
|
||||||
var vertBuffer *uniformBuffer
|
{
|
||||||
if u := uniforms[materialTexture]; u != nil {
|
var vertBuffer *uniformBuffer
|
||||||
vertBuffer = newUniformBuffer(b, u)
|
fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
|
||||||
|
if err != nil {
|
||||||
|
return pipelines, err
|
||||||
|
}
|
||||||
|
defer fsh.Release()
|
||||||
|
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||||
|
VertexShader: vsh,
|
||||||
|
FragmentShader: fsh,
|
||||||
|
BlendDesc: blend,
|
||||||
|
VertexLayout: layout,
|
||||||
|
PixelFormat: format,
|
||||||
|
Topology: driver.TopologyTriangleStrip,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return pipelines, err
|
||||||
|
}
|
||||||
|
if u := uniforms[materialLinearGradient]; u != nil {
|
||||||
|
vertBuffer = newUniformBuffer(b, u)
|
||||||
|
}
|
||||||
|
pipelines[i][materialLinearGradient] = &pipeline{pipe, vertBuffer}
|
||||||
}
|
}
|
||||||
pipelines[materialTexture] = &pipeline{pipe, vertBuffer}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var vertBuffer *uniformBuffer
|
|
||||||
fsh, err := b.NewFragmentShader(fsSrc[materialColor])
|
|
||||||
if err != nil {
|
|
||||||
pipelines[materialTexture].Release()
|
|
||||||
return pipelines, err
|
|
||||||
}
|
|
||||||
defer fsh.Release()
|
|
||||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
|
||||||
VertexShader: vsh,
|
|
||||||
FragmentShader: fsh,
|
|
||||||
BlendDesc: blend,
|
|
||||||
VertexLayout: layout,
|
|
||||||
PixelFormat: driver.TextureFormatOutput,
|
|
||||||
Topology: driver.TopologyTriangleStrip,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
pipelines[materialTexture].Release()
|
|
||||||
return pipelines, err
|
|
||||||
}
|
|
||||||
if u := uniforms[materialColor]; u != nil {
|
|
||||||
vertBuffer = newUniformBuffer(b, u)
|
|
||||||
}
|
|
||||||
pipelines[materialColor] = &pipeline{pipe, vertBuffer}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var vertBuffer *uniformBuffer
|
|
||||||
fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
|
|
||||||
if err != nil {
|
|
||||||
pipelines[materialTexture].Release()
|
|
||||||
pipelines[materialColor].Release()
|
|
||||||
return pipelines, err
|
|
||||||
}
|
|
||||||
defer fsh.Release()
|
|
||||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
|
||||||
VertexShader: vsh,
|
|
||||||
FragmentShader: fsh,
|
|
||||||
BlendDesc: blend,
|
|
||||||
VertexLayout: layout,
|
|
||||||
PixelFormat: driver.TextureFormatOutput,
|
|
||||||
Topology: driver.TopologyTriangleStrip,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
pipelines[materialTexture].Release()
|
|
||||||
pipelines[materialColor].Release()
|
|
||||||
return pipelines, err
|
|
||||||
}
|
|
||||||
if u := uniforms[materialLinearGradient]; u != nil {
|
|
||||||
vertBuffer = newUniformBuffer(b, u)
|
|
||||||
}
|
|
||||||
pipelines[materialLinearGradient] = &pipeline{pipe, vertBuffer}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
for _, p := range pipelines {
|
|
||||||
p.Release()
|
|
||||||
}
|
|
||||||
return pipelines, err
|
|
||||||
}
|
}
|
||||||
return pipelines, nil
|
return pipelines, nil
|
||||||
}
|
}
|
||||||
@@ -695,8 +752,8 @@ func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) {
|
|||||||
}
|
}
|
||||||
fbo := r.pather.stenciler.cover(p.place.Idx)
|
fbo := r.pather.stenciler.cover(p.place.Idx)
|
||||||
r.ctx.BindTexture(0, fbo.tex)
|
r.ctx.BindTexture(0, fbo.tex)
|
||||||
coverScale, coverOff := texSpaceTransform(layout.FRect(uv), fbo.size)
|
coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size)
|
||||||
subScale, subOff := texSpaceTransform(layout.FRect(sub), p.clip.Size())
|
subScale, subOff := texSpaceTransform(f32.FRect(sub), p.clip.Size())
|
||||||
r.pather.stenciler.ipipeline.uniforms.vert.uvTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
|
r.pather.stenciler.ipipeline.uniforms.vert.uvTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
|
||||||
r.pather.stenciler.ipipeline.uniforms.vert.subUVTransform = [4]float32{subScale.X, subScale.Y, subOff.X, subOff.Y}
|
r.pather.stenciler.ipipeline.uniforms.vert.subUVTransform = [4]float32{subScale.X, subScale.Y, subOff.X, subOff.Y}
|
||||||
r.pather.stenciler.ipipeline.pipeline.UploadUniforms(r.ctx)
|
r.pather.stenciler.ipipeline.pipeline.UploadUniforms(r.ctx)
|
||||||
@@ -745,8 +802,7 @@ func (r *renderer) packStencils(pops *[]*pathOp) {
|
|||||||
ops = ops[:len(ops)-1]
|
ops = ops[:len(ops)-1]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sz := image.Point{X: p.clip.Dx(), Y: p.clip.Dy()}
|
place, ok := r.packer.add(p.clip.Size())
|
||||||
place, ok := r.packer.add(sz)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
// The clip area is at most the entire screen. Hopefully no
|
// The clip area is at most the entire screen. Hopefully no
|
||||||
// screen is larger than GL_MAX_TEXTURE_SIZE.
|
// screen is larger than GL_MAX_TEXTURE_SIZE.
|
||||||
@@ -758,14 +814,92 @@ func (r *renderer) packStencils(pops *[]*pathOp) {
|
|||||||
*pops = ops
|
*pops = ops
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
|
||||||
|
// Make every layer bounds contain nested layers; cull empty layers.
|
||||||
|
for i := len(layers) - 1; i >= 0; i-- {
|
||||||
|
l := layers[i]
|
||||||
|
if l.parent != -1 {
|
||||||
|
b := layers[l.parent].clip
|
||||||
|
layers[l.parent].clip = b.Union(l.clip)
|
||||||
|
}
|
||||||
|
if l.clip.Empty() {
|
||||||
|
layers = slices.Delete(layers, i, i+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Pack layers.
|
||||||
|
r.layers.clear()
|
||||||
|
depth := 0
|
||||||
|
for i := range layers {
|
||||||
|
l := &layers[i]
|
||||||
|
// Only layers of the same depth may be packed together.
|
||||||
|
if l.depth != depth {
|
||||||
|
r.layers.newPage()
|
||||||
|
}
|
||||||
|
place, ok := r.layers.add(l.clip.Size())
|
||||||
|
if !ok {
|
||||||
|
// The layer area is at most the entire screen. Hopefully no
|
||||||
|
// screen is larger than GL_MAX_TEXTURE_SIZE.
|
||||||
|
panic(fmt.Errorf("layer size %v is larger than maximum texture size %v", l.clip.Size(), r.layers.maxDims))
|
||||||
|
}
|
||||||
|
l.place = place
|
||||||
|
}
|
||||||
|
return layers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) {
|
||||||
|
if len(r.layers.sizes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fbo := -1
|
||||||
|
r.layerFBOs.resize(r.ctx, driver.TextureFormatSRGBA, r.layers.sizes)
|
||||||
|
for i := len(layers) - 1; i >= 0; i-- {
|
||||||
|
l := layers[i]
|
||||||
|
if fbo != l.place.Idx {
|
||||||
|
if fbo != -1 {
|
||||||
|
r.ctx.EndRenderPass()
|
||||||
|
r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
|
||||||
|
}
|
||||||
|
fbo = l.place.Idx
|
||||||
|
f := r.layerFBOs.fbos[fbo]
|
||||||
|
r.ctx.BeginRenderPass(f.tex, driver.LoadDesc{Action: driver.LoadActionClear})
|
||||||
|
}
|
||||||
|
v := image.Rectangle{
|
||||||
|
Min: l.place.Pos,
|
||||||
|
Max: l.place.Pos.Add(l.clip.Size()),
|
||||||
|
}
|
||||||
|
r.ctx.Viewport(v.Min.X, v.Min.Y, v.Dx(), v.Dy())
|
||||||
|
f := r.layerFBOs.fbos[fbo]
|
||||||
|
r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
|
||||||
|
sr := f32.FRect(v)
|
||||||
|
uvScale, uvOffset := texSpaceTransform(sr, f.size)
|
||||||
|
uvTrans := f32.AffineId().Scale(f32.Point{}, uvScale).Offset(uvOffset)
|
||||||
|
// Replace layer ops with one textured op.
|
||||||
|
ops[l.opStart] = imageOp{
|
||||||
|
clip: l.clip,
|
||||||
|
material: material{
|
||||||
|
material: materialTexture,
|
||||||
|
tex: f.tex,
|
||||||
|
uvTrans: uvTrans,
|
||||||
|
opacity: l.opacity,
|
||||||
|
},
|
||||||
|
layerOps: l.opEnd - l.opStart - 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fbo != -1 {
|
||||||
|
r.ctx.EndRenderPass()
|
||||||
|
r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *drawOps) reset(viewport image.Point) {
|
func (d *drawOps) reset(viewport image.Point) {
|
||||||
d.profile = false
|
|
||||||
d.viewport = viewport
|
d.viewport = viewport
|
||||||
d.imageOps = d.imageOps[:0]
|
d.imageOps = d.imageOps[:0]
|
||||||
d.pathOps = d.pathOps[:0]
|
d.pathOps = d.pathOps[:0]
|
||||||
d.pathOpCache = d.pathOpCache[:0]
|
d.pathOpCache = d.pathOpCache[:0]
|
||||||
d.vertCache = d.vertCache[:0]
|
d.vertCache = d.vertCache[:0]
|
||||||
d.transStack = d.transStack[:0]
|
d.transStack = d.transStack[:0]
|
||||||
|
d.layers = d.layers[:0]
|
||||||
|
d.opacityStack = d.opacityStack[:0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
|
func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
|
||||||
@@ -798,7 +932,7 @@ func (d *drawOps) newPathOp() *pathOp {
|
|||||||
return &d.pathOpCache[len(d.pathOpCache)-1]
|
return &d.pathOpCache[len(d.pathOpCache)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point, push bool) {
|
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point) {
|
||||||
npath := d.newPathOp()
|
npath := d.newPathOp()
|
||||||
*npath = pathOp{
|
*npath = pathOp{
|
||||||
parent: state.cpath,
|
parent: state.cpath,
|
||||||
@@ -821,18 +955,11 @@ func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds
|
|||||||
state.cpath = npath
|
state.cpath = npath
|
||||||
}
|
}
|
||||||
|
|
||||||
// split a transform into two parts, one which is pure offset and the
|
|
||||||
// other representing the scaling, shearing and rotation part
|
|
||||||
func splitTransform(t f32.Affine2D) (srs f32.Affine2D, offset f32.Point) {
|
|
||||||
sx, hx, ox, hy, sy, oy := t.Elems()
|
|
||||||
offset = f32.Point{X: ox, Y: oy}
|
|
||||||
srs = f32.NewAffine2D(sx, hx, 0, hy, sy, 0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *drawOps) save(id int, state f32.Affine2D) {
|
func (d *drawOps) save(id int, state f32.Affine2D) {
|
||||||
if extra := id - len(d.states) + 1; extra > 0 {
|
if extra := id - len(d.states) + 1; extra > 0 {
|
||||||
d.states = append(d.states, make([]f32.Affine2D, extra)...)
|
for range extra {
|
||||||
|
d.states = append(d.states, f32.AffineId())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
d.states[id] = state
|
d.states[id] = state
|
||||||
}
|
}
|
||||||
@@ -847,12 +974,13 @@ func (k opKey) SetTransform(t f32.Affine2D) opKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
|
func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
|
||||||
var (
|
var quads quadsOp
|
||||||
quads quadsOp
|
state := drawState{
|
||||||
state drawState
|
t: f32.AffineId(),
|
||||||
)
|
}
|
||||||
reset := func() {
|
reset := func() {
|
||||||
state = drawState{
|
state = drawState{
|
||||||
|
t: f32.AffineId(),
|
||||||
color: color.NRGBA{A: 0xff},
|
color: color.NRGBA{A: 0xff},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -860,8 +988,6 @@ func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
|
|||||||
loop:
|
loop:
|
||||||
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
||||||
switch ops.OpType(encOp.Data[0]) {
|
switch ops.OpType(encOp.Data[0]) {
|
||||||
case ops.TypeProfile:
|
|
||||||
d.profile = true
|
|
||||||
case ops.TypeTransform:
|
case ops.TypeTransform:
|
||||||
dop, push := ops.DecodeTransform(encOp.Data)
|
dop, push := ops.DecodeTransform(encOp.Data)
|
||||||
if push {
|
if push {
|
||||||
@@ -873,6 +999,27 @@ loop:
|
|||||||
state.t = d.transStack[n-1]
|
state.t = d.transStack[n-1]
|
||||||
d.transStack = d.transStack[:n-1]
|
d.transStack = d.transStack[:n-1]
|
||||||
|
|
||||||
|
case ops.TypePushOpacity:
|
||||||
|
opacity := ops.DecodeOpacity(encOp.Data)
|
||||||
|
parent := -1
|
||||||
|
depth := len(d.opacityStack)
|
||||||
|
if depth > 0 {
|
||||||
|
parent = d.opacityStack[depth-1]
|
||||||
|
}
|
||||||
|
lidx := len(d.layers)
|
||||||
|
d.layers = append(d.layers, opacityLayer{
|
||||||
|
opacity: opacity,
|
||||||
|
parent: parent,
|
||||||
|
depth: depth,
|
||||||
|
opStart: len(d.imageOps),
|
||||||
|
})
|
||||||
|
d.opacityStack = append(d.opacityStack, lidx)
|
||||||
|
case ops.TypePopOpacity:
|
||||||
|
n := len(d.opacityStack)
|
||||||
|
idx := d.opacityStack[n-1]
|
||||||
|
d.layers[idx].opEnd = len(d.imageOps)
|
||||||
|
d.opacityStack = d.opacityStack[:n-1]
|
||||||
|
|
||||||
case ops.TypeStroke:
|
case ops.TypeStroke:
|
||||||
quads.key.strokeWidth = decodeStrokeOp(encOp.Data)
|
quads.key.strokeWidth = decodeStrokeOp(encOp.Data)
|
||||||
|
|
||||||
@@ -888,8 +1035,8 @@ loop:
|
|||||||
var op ops.ClipOp
|
var op ops.ClipOp
|
||||||
op.Decode(encOp.Data)
|
op.Decode(encOp.Data)
|
||||||
quads.key.outline = op.Outline
|
quads.key.outline = op.Outline
|
||||||
bounds := layout.FRect(op.Bounds)
|
bounds := f32.FRect(op.Bounds)
|
||||||
trans, off := splitTransform(state.t)
|
trans, off := transformOffset(state.t)
|
||||||
if len(quads.aux) > 0 {
|
if len(quads.aux) > 0 {
|
||||||
// There is a clipping path, build the gpu data and update the
|
// There is a clipping path, build the gpu data and update the
|
||||||
// cache key such that it will be equal only if the transform is the
|
// cache key such that it will be equal only if the transform is the
|
||||||
@@ -912,8 +1059,9 @@ loop:
|
|||||||
} else {
|
} else {
|
||||||
quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
|
quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
|
||||||
quads.key = opKey{Key: encOp.Key}
|
quads.key = opKey{Key: encOp.Key}
|
||||||
|
quads.key = quads.key.SetTransform(trans)
|
||||||
}
|
}
|
||||||
d.addClipPath(&state, quads.aux, quads.key, bounds, off, true)
|
d.addClipPath(&state, quads.aux, quads.key, bounds, off)
|
||||||
quads = quadsOp{}
|
quads = quadsOp{}
|
||||||
case ops.TypePopClip:
|
case ops.TypePopClip:
|
||||||
state.cpath = state.cpath.parent
|
state.cpath = state.cpath.parent
|
||||||
@@ -935,7 +1083,7 @@ loop:
|
|||||||
// Transform (if needed) the painting rectangle and if so generate a clip path,
|
// Transform (if needed) the painting rectangle and if so generate a clip path,
|
||||||
// for those cases also compute a partialTrans that maps texture coordinates between
|
// for those cases also compute a partialTrans that maps texture coordinates between
|
||||||
// the new bounding rectangle and the transformed original paint rectangle.
|
// the new bounding rectangle and the transformed original paint rectangle.
|
||||||
t, off := splitTransform(state.t)
|
t, off := transformOffset(state.t)
|
||||||
// Fill the clip area, unless the material is a (bounded) image.
|
// Fill the clip area, unless the material is a (bounded) image.
|
||||||
// TODO: Find a tighter bound.
|
// TODO: Find a tighter bound.
|
||||||
inf := float32(1e6)
|
inf := float32(1e6)
|
||||||
@@ -957,15 +1105,15 @@ loop:
|
|||||||
// The paint operation is sheared or rotated, add a clip path representing
|
// The paint operation is sheared or rotated, add a clip path representing
|
||||||
// this transformed rectangle.
|
// this transformed rectangle.
|
||||||
k := opKey{Key: encOp.Key}
|
k := opKey{Key: encOp.Key}
|
||||||
k.SetTransform(t) // TODO: This call has no effect.
|
k = k.SetTransform(t)
|
||||||
d.addClipPath(&state, clipData, k, bnd, off, false)
|
d.addClipPath(&state, clipData, k, bnd, off)
|
||||||
}
|
}
|
||||||
|
|
||||||
bounds := cl.Round()
|
bounds := cl.Round()
|
||||||
mat := state.materialFor(bnd, off, partialTrans, bounds)
|
mat := state.materialFor(bnd, off, partialTrans, bounds)
|
||||||
|
|
||||||
rect := state.cpath == nil || state.cpath.rect
|
rect := state.cpath == nil || state.cpath.rect
|
||||||
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) {
|
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) && len(d.opacityStack) == 0 {
|
||||||
// The image is a uniform opaque color and takes up the whole screen.
|
// The image is a uniform opaque color and takes up the whole screen.
|
||||||
// Scrap images up to and including this image and set clear color.
|
// Scrap images up to and including this image and set clear color.
|
||||||
d.imageOps = d.imageOps[:0]
|
d.imageOps = d.imageOps[:0]
|
||||||
@@ -978,6 +1126,15 @@ loop:
|
|||||||
clip: bounds,
|
clip: bounds,
|
||||||
material: mat,
|
material: mat,
|
||||||
}
|
}
|
||||||
|
if n := len(d.opacityStack); n > 0 {
|
||||||
|
idx := d.opacityStack[n-1]
|
||||||
|
lb := d.layers[idx].clip
|
||||||
|
if lb.Empty() {
|
||||||
|
d.layers[idx].clip = img.clip
|
||||||
|
} else {
|
||||||
|
d.layers[idx].clip = lb.Union(img.clip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
d.imageOps = append(d.imageOps, img)
|
d.imageOps = append(d.imageOps, img)
|
||||||
if clipData != nil {
|
if clipData != nil {
|
||||||
@@ -1007,7 +1164,10 @@ func expandPathOp(p *pathOp, clip image.Rectangle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
|
func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
|
||||||
var m material
|
m := material{
|
||||||
|
opacity: 1.,
|
||||||
|
uvTrans: f32.AffineId(),
|
||||||
|
}
|
||||||
switch d.matType {
|
switch d.matType {
|
||||||
case materialColor:
|
case materialColor:
|
||||||
m.material = materialColor
|
m.material = materialColor
|
||||||
@@ -1040,30 +1200,31 @@ func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32
|
|||||||
sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy
|
sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy
|
||||||
sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy
|
sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy
|
||||||
uvScale, uvOffset := texSpaceTransform(sr, sz)
|
uvScale, uvOffset := texSpaceTransform(sr, sz)
|
||||||
m.uvTrans = partTrans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset))
|
m.uvTrans = partTrans.Mul(f32.AffineId().Scale(f32.Point{}, uvScale).Offset(uvOffset))
|
||||||
m.data = d.image
|
m.data = d.image
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) {
|
func (r *renderer) uploadImages(cache *textureCache, ops []imageOp) {
|
||||||
for _, img := range ops {
|
for i := range ops {
|
||||||
|
img := &ops[i]
|
||||||
m := img.material
|
m := img.material
|
||||||
if m.material == materialTexture {
|
if m.material == materialTexture {
|
||||||
r.texHandle(cache, m.data)
|
img.material.tex = r.texHandle(cache, m.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) {
|
func (r *renderer) prepareDrawOps(ops []imageOp) {
|
||||||
for _, img := range ops {
|
for _, img := range ops {
|
||||||
m := img.material
|
m := img.material
|
||||||
switch m.material {
|
switch m.material {
|
||||||
case materialTexture:
|
case materialTexture:
|
||||||
r.ctx.PrepareTexture(r.texHandle(cache, m.data))
|
r.ctx.PrepareTexture(m.tex)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fbo stencilFBO
|
var fbo FBO
|
||||||
switch img.clipType {
|
switch img.clipType {
|
||||||
case clipTypeNone:
|
case clipTypeNone:
|
||||||
continue
|
continue
|
||||||
@@ -1076,24 +1237,30 @@ func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
|
func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageOp) {
|
||||||
var coverTex driver.Texture
|
var coverTex driver.Texture
|
||||||
for _, img := range ops {
|
for i := 0; i < len(ops); i++ {
|
||||||
|
img := ops[i]
|
||||||
|
i += img.layerOps
|
||||||
m := img.material
|
m := img.material
|
||||||
switch m.material {
|
switch m.material {
|
||||||
case materialTexture:
|
case materialTexture:
|
||||||
r.ctx.BindTexture(0, r.texHandle(cache, m.data))
|
r.ctx.BindTexture(0, m.tex)
|
||||||
}
|
}
|
||||||
drc := img.clip
|
drc := img.clip.Add(opOff)
|
||||||
|
|
||||||
scale, off := clipSpaceTransform(drc, r.blitter.viewport)
|
scale, off := clipSpaceTransform(drc, viewport)
|
||||||
var fbo stencilFBO
|
var fbo FBO
|
||||||
|
fboIdx := 0
|
||||||
|
if isFBO {
|
||||||
|
fboIdx = 1
|
||||||
|
}
|
||||||
switch img.clipType {
|
switch img.clipType {
|
||||||
case clipTypeNone:
|
case clipTypeNone:
|
||||||
p := r.blitter.pipelines[m.material]
|
p := r.blitter.pipelines[fboIdx][m.material]
|
||||||
r.ctx.BindPipeline(p.pipeline)
|
r.ctx.BindPipeline(p.pipeline)
|
||||||
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
||||||
r.blitter.blit(m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans)
|
r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans)
|
||||||
continue
|
continue
|
||||||
case clipTypePath:
|
case clipTypePath:
|
||||||
fbo = r.pather.stenciler.cover(img.place.Idx)
|
fbo = r.pather.stenciler.cover(img.place.Idx)
|
||||||
@@ -1108,16 +1275,20 @@ func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
|
|||||||
Min: img.place.Pos,
|
Min: img.place.Pos,
|
||||||
Max: img.place.Pos.Add(drc.Size()),
|
Max: img.place.Pos.Add(drc.Size()),
|
||||||
}
|
}
|
||||||
coverScale, coverOff := texSpaceTransform(layout.FRect(uv), fbo.size)
|
coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size)
|
||||||
p := r.pather.coverer.pipelines[m.material]
|
p := r.pather.coverer.pipelines[fboIdx][m.material]
|
||||||
r.ctx.BindPipeline(p.pipeline)
|
r.ctx.BindPipeline(p.pipeline)
|
||||||
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
||||||
r.pather.cover(m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
|
r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D) {
|
func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) {
|
||||||
p := b.pipelines[mat]
|
fboIdx := 0
|
||||||
|
if fbo {
|
||||||
|
fboIdx = 1
|
||||||
|
}
|
||||||
|
p := b.pipelines[fboIdx][mat]
|
||||||
b.ctx.BindPipeline(p.pipeline)
|
b.ctx.BindPipeline(p.pipeline)
|
||||||
var uniforms *blitUniforms
|
var uniforms *blitUniforms
|
||||||
switch mat {
|
switch mat {
|
||||||
@@ -1126,18 +1297,23 @@ func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.
|
|||||||
uniforms = &b.colUniforms.blitUniforms
|
uniforms = &b.colUniforms.blitUniforms
|
||||||
case materialTexture:
|
case materialTexture:
|
||||||
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
||||||
b.texUniforms.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
|
||||||
b.texUniforms.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
|
||||||
uniforms = &b.texUniforms.blitUniforms
|
uniforms = &b.texUniforms.blitUniforms
|
||||||
|
uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
||||||
|
uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||||
case materialLinearGradient:
|
case materialLinearGradient:
|
||||||
b.linearGradientUniforms.color1 = col1
|
b.linearGradientUniforms.color1 = col1
|
||||||
b.linearGradientUniforms.color2 = col2
|
b.linearGradientUniforms.color2 = col2
|
||||||
|
|
||||||
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
||||||
b.linearGradientUniforms.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
|
||||||
b.linearGradientUniforms.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
|
||||||
uniforms = &b.linearGradientUniforms.blitUniforms
|
uniforms = &b.linearGradientUniforms.blitUniforms
|
||||||
|
uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
||||||
|
uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||||
}
|
}
|
||||||
|
uniforms.fbo = 0
|
||||||
|
if fbo {
|
||||||
|
uniforms.fbo = 1
|
||||||
|
}
|
||||||
|
uniforms.opacity = opacity
|
||||||
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
|
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
|
||||||
p.UploadUniforms(b.ctx)
|
p.UploadUniforms(b.ctx)
|
||||||
b.ctx.DrawArrays(0, 4)
|
b.ctx.DrawArrays(0, 4)
|
||||||
@@ -1145,7 +1321,7 @@ func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.
|
|||||||
|
|
||||||
// newUniformBuffer creates a new GPU uniform buffer backed by the
|
// newUniformBuffer creates a new GPU uniform buffer backed by the
|
||||||
// structure uniformBlock points to.
|
// structure uniformBlock points to.
|
||||||
func newUniformBuffer(b driver.Device, uniformBlock interface{}) *uniformBuffer {
|
func newUniformBuffer(b driver.Device, uniformBlock any) *uniformBuffer {
|
||||||
ref := reflect.ValueOf(uniformBlock)
|
ref := reflect.ValueOf(uniformBlock)
|
||||||
// Determine the size of the uniforms structure, *uniforms.
|
// Determine the size of the uniforms structure, *uniforms.
|
||||||
size := ref.Elem().Type().Size()
|
size := ref.Elem().Type().Size()
|
||||||
@@ -1199,7 +1375,7 @@ func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f3
|
|||||||
|
|
||||||
// TODO: optimize
|
// TODO: optimize
|
||||||
zp := f32.Point{}
|
zp := f32.Point{}
|
||||||
return f32.Affine2D{}.
|
return f32.AffineId().
|
||||||
Scale(zp, layout.FPt(clip.Size())). // scale to pixel space
|
Scale(zp, layout.FPt(clip.Size())). // scale to pixel space
|
||||||
Offset(zp.Sub(off).Add(layout.FPt(clip.Min))). // offset to clip space
|
Offset(zp.Sub(off).Add(layout.FPt(clip.Min))). // offset to clip space
|
||||||
Offset(zp.Sub(stop1)). // offset to first stop point
|
Offset(zp.Sub(stop1)). // offset to first stop point
|
||||||
@@ -1313,7 +1489,7 @@ func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, str
|
|||||||
// as needed and feeds them to the supplied splitter.
|
// as needed and feeds them to the supplied splitter.
|
||||||
func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
||||||
for len(pathData) >= scene.CommandSize+4 {
|
for len(pathData) >= scene.CommandSize+4 {
|
||||||
qs.contour = bo.Uint32(pathData)
|
qs.contour = binary.LittleEndian.Uint32(pathData)
|
||||||
cmd := ops.DecodeCommand(pathData[4:])
|
cmd := ops.DecodeCommand(pathData[4:])
|
||||||
switch cmd.Op() {
|
switch cmd.Op() {
|
||||||
case scene.OpLine:
|
case scene.OpLine:
|
||||||
@@ -1334,7 +1510,9 @@ func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
|||||||
q = q.Transform(tr)
|
q = q.Transform(tr)
|
||||||
qs.splitAndEncode(q)
|
qs.splitAndEncode(q)
|
||||||
case scene.OpCubic:
|
case scene.OpCubic:
|
||||||
for _, q := range stroke.SplitCubic(scene.DecodeCubic(cmd)) {
|
from, ctrl0, ctrl1, to := scene.DecodeCubic(cmd)
|
||||||
|
qs.scratch = stroke.SplitCubic(from, ctrl0, ctrl1, to, qs.scratch[:0])
|
||||||
|
for _, q := range qs.scratch {
|
||||||
q = q.Transform(tr)
|
q = q.Transform(tr)
|
||||||
qs.splitAndEncode(q)
|
qs.splitAndEncode(q)
|
||||||
}
|
}
|
||||||
@@ -1347,12 +1525,10 @@ func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
|||||||
|
|
||||||
// create GPU vertices for transformed r, find the bounds and establish texture transform.
|
// create GPU vertices for transformed r, find the bounds and establish texture transform.
|
||||||
func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) {
|
func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) {
|
||||||
if isPureOffset(tr) {
|
ptr = f32.AffineId()
|
||||||
// fast-path to allow blitting of pure rectangles
|
if tr == f32.AffineId() {
|
||||||
_, _, ox, _, _, oy := tr.Elems()
|
// fast-path to allow blitting of pure rectangles.
|
||||||
off := f32.Pt(ox, oy)
|
bnd = r
|
||||||
bnd.Min = r.Min.Add(off)
|
|
||||||
bnd.Max = r.Max.Add(off)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1399,10 +1575,29 @@ func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (au
|
|||||||
sx, sy := P2.X-P3.X, P2.Y-P3.Y
|
sx, sy := P2.X-P3.X, P2.Y-P3.Y
|
||||||
ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert()
|
ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert()
|
||||||
|
|
||||||
return
|
return aux, bnd, ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPureOffset(t f32.Affine2D) bool {
|
// transformOffset a transform into two parts, one which is pure integer offset
|
||||||
a, b, _, d, e, _ := t.Elems()
|
// and the other representing the scaling, shearing and rotation and fractional
|
||||||
return a == 1 && b == 0 && d == 0 && e == 1
|
// offset.
|
||||||
|
func transformOffset(t f32.Affine2D) (f32.Affine2D, f32.Point) {
|
||||||
|
sx, hx, ox, hy, sy, oy := t.Elems()
|
||||||
|
iox, fox := math.Modf(float64(ox))
|
||||||
|
ioy, foy := math.Modf(float64(oy))
|
||||||
|
ft := f32.NewAffine2D(sx, hx, float32(fox), hy, sy, float32(foy))
|
||||||
|
ip := f32.Pt(float32(iox), float32(ioy))
|
||||||
|
return ft, ip
|
||||||
|
}
|
||||||
|
|
||||||
|
func newShaders(ctx driver.Device, vsrc, fsrc shader.Sources) (vert driver.VertexShader, frag driver.FragmentShader, err error) {
|
||||||
|
vert, err = ctx.NewVertexShader(vsrc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
frag, err = ctx.NewFragmentShader(fsrc)
|
||||||
|
if err != nil {
|
||||||
|
vert.Release()
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user