diff --git a/Cargo.lock b/Cargo.lock index e255f1fd..c4ee9265 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,94 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +[[package]] +name = "accesskit" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cf780eb737f2d4a49ffbd512324d53ad089070f813f7be7f99dbd5123a7f448" + +[[package]] +name = "accesskit_atspi_common" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3f1db0583df3e2f52501231d49001d17403bbdae4d14e359776cc87f591b618" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi-common", + "serde", + "thiserror", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "329bc2cce90b24356497badc4824c02986298f7b6c5ea208ceed59532448b6f6" +dependencies = [ + "accesskit", +] + +[[package]] +name = "accesskit_macos" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5abdf2e75371122f769baeee90ecfd3987b1b00d6ff596fd1eec5d778795e19a" +dependencies = [ + "accesskit", + "accesskit_consumer", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", +] + +[[package]] +name = "accesskit_unix" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64bb5d87ed1f38c0e905de65cfe39b8adea00ec00c842308d020ebeb8c5da7ed" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite 1.13.0", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f80369680515d6929059cd19a6d154800506afc36430aed3ddddb9f2ab153a" +dependencies = [ + "accesskit", + "accesskit_consumer", + "paste", + "static_assertions", + "windows 0.54.0", +] + +[[package]] +name = "accesskit_winit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585dba99dfbd169ce9ebfa8df51491c8fa283851476a7f96f23c269c730fe8db" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "raw-window-handle", + "winit", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -46,6 +134,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.18" @@ -121,6 +218,176 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" +dependencies = [ + "concurrent-queue", + "event-listener 5.3.0", + "event-listener-strategy 0.5.2", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.1.0", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.27", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +dependencies = [ + "async-lock 3.3.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.3.0", + "parking", + "polling 3.7.0", + "rustix 0.38.34", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy 0.4.0", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.34", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "async-signal" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afe66191c335039c7bb78f99dc7520b0cbb166b3a1cb33a03f53d8a1c6f2afda" +dependencies = [ + "async-io 2.3.2", + "async-lock 3.3.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.34", + "signal-hook-registry", + "slab", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -128,10 +395,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "autocfg" -version = "1.2.0" +name = "atspi" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "6059f350ab6f593ea00727b334265c4dfc7fd442ee32d264794bd9bdc68e87ca" +dependencies = [ + "atspi-common", + "atspi-connection", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92af95f966d2431f962bc632c2e68eda7777330158bf640c4af4249349b2cdf5" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-connection" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c65e7d70f86d4c0e3b2d585d9bf3f979f0b19d635a336725a88d279f76b939" +dependencies = [ + "atspi-common", + "atspi-proxies", + "futures-lite 1.13.0", + "zbus", +] + +[[package]] +name = "atspi-proxies" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52" +dependencies = [ + "atspi-common", + "serde", + "zbus", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" @@ -187,6 +502,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.5.0" @@ -196,6 +520,20 @@ dependencies = [ "objc2", ] +[[package]] +name = "blocking" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" +dependencies = [ + "async-channel", + "async-lock 3.3.0", + "async-task", + "futures-io", + "futures-lite 2.3.0", + "piper", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -242,8 +580,8 @@ checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ "bitflags 2.5.0", "log", - "polling", - "rustix", + "polling 3.7.0", + "rustix 0.38.34", "slab", "thiserror", ] @@ -255,7 +593,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" dependencies = [ "calloop", - "rustix", + "rustix 0.38.34", "wayland-backend", "wayland-client", ] @@ -455,6 +793,15 @@ dependencies = [ "xilem_web", ] +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -495,6 +842,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "cursor-icon" version = "1.1.0" @@ -512,6 +869,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -576,6 +954,27 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "enumflags2" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -601,6 +1000,65 @@ dependencies = [ "num-traits", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.0", + "pin-project-lite", +] + [[package]] name = "exr" version = "1.72.0" @@ -617,6 +1075,15 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.1.0" @@ -754,12 +1221,90 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.1.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + [[package]] name = "futures-task" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -877,7 +1422,7 @@ dependencies = [ "presser", "thiserror", "winapi", - "windows", + "windows 0.52.0", ] [[package]] @@ -957,6 +1502,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hexf-parse" version = "0.2.1" @@ -1109,6 +1660,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1246,6 +1808,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -1287,6 +1855,8 @@ dependencies = [ name = "masonry" version = "0.2.0" dependencies = [ + "accesskit", + "accesskit_winit", "assert_matches", "float-cmp", "fnv", @@ -1311,6 +1881,15 @@ dependencies = [ "xi-unicode", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "mathml_svg" version = "0.1.0" @@ -1336,6 +1915,24 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "metal" version = "0.27.0" @@ -1431,6 +2028,18 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1443,9 +2052,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1475,7 +2084,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 2.0.60", @@ -1580,6 +2189,16 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "overload" version = "0.1.1" @@ -1595,6 +2214,12 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.2" @@ -1678,6 +2303,23 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.1.0", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -1697,6 +2339,22 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "polling" version = "3.7.0" @@ -1707,7 +2365,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 0.38.34", "tracing", "windows-sys 0.52.0", ] @@ -1718,19 +2376,35 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "presser" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + [[package]] name = "proc-macro-crate" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit", + "toml_edit 0.21.1", ] [[package]] @@ -1775,6 +2449,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "range-alloc" version = "0.1.3" @@ -1844,6 +2548,50 @@ dependencies = [ "bitflags 2.5.0", ] +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.3", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -1868,6 +2616,20 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustix" version = "0.38.34" @@ -1877,7 +2639,7 @@ dependencies = [ "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.13", "windows-sys 0.52.0", ] @@ -1952,6 +2714,28 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2038,7 +2822,7 @@ dependencies = [ "libc", "log", "memmap2", - "rustix", + "rustix 0.38.34", "thiserror", "wayland-backend", "wayland-client", @@ -2059,6 +2843,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.7" @@ -2185,8 +2979,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand", - "rustix", + "fastrand 2.1.0", + "rustix 0.38.34", "windows-sys 0.52.0", ] @@ -2303,7 +3097,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.48.0", ] @@ -2325,6 +3119,17 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "toml_edit" version = "0.21.1" @@ -2385,10 +3190,14 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ + "matchers", "nu-ansi-term", + "once_cell", + "regex", "sharded-slab", "smallvec", "thread_local", + "tracing", "tracing-core", "tracing-log", ] @@ -2410,6 +3219,23 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "winapi", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -2473,6 +3299,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + [[package]] name = "walkdir" version = "2.5.0" @@ -2563,7 +3395,7 @@ checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" dependencies = [ "cc", "downcast-rs", - "rustix", + "rustix 0.38.34", "scoped-tls", "smallvec", "wayland-sys", @@ -2576,7 +3408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" dependencies = [ "bitflags 2.5.0", - "rustix", + "rustix 0.38.34", "wayland-backend", "wayland-scanner", ] @@ -2598,7 +3430,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba" dependencies = [ - "rustix", + "rustix 0.38.34", "wayland-client", "xcursor", ] @@ -2840,7 +3672,19 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "windows-core", + "windows-core 0.52.0", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-implement", + "windows-interface", "windows-targets 0.52.5", ] @@ -2853,6 +3697,47 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-implement" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "windows-interface" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "windows-result" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3088,7 +3973,7 @@ dependencies = [ "pin-project", "raw-window-handle", "redox_syscall 0.4.1", - "rustix", + "rustix 0.38.34", "sctk-adwaita", "smithay-client-toolkit", "smol_str", @@ -3154,7 +4039,7 @@ dependencies = [ "libc", "libloading 0.8.3", "once_cell", - "rustix", + "rustix 0.38.34", "x11rb-protocol", ] @@ -3170,6 +4055,16 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" +[[package]] +name = "xdg-home" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e5a325c3cb8398ad6cf859c1135b25dd29e186679cf2da7581d9679f63b38e" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "xi-unicode" version = "0.3.0" @@ -3180,6 +4075,8 @@ checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" name = "xilem" version = "0.1.0" dependencies = [ + "accesskit", + "accesskit_winit", "masonry", "smallvec", "tracing", @@ -3279,6 +4176,72 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener 2.5.3", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + [[package]] name = "zeno" version = "0.2.3" @@ -3356,3 +4319,41 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] + +[[package]] +name = "zvariant" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/Cargo.toml b/Cargo.toml index b7b910e3..c3e5df2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,8 @@ smallvec = "1.13.2" fnv = "1.0.7" instant = "0.1.6" bitflags = "2.0.0" +accesskit = "0.14.0" +accesskit_winit = "0.20.0" [package] name = "xilem_classic" diff --git a/crates/masonry/Cargo.toml b/crates/masonry/Cargo.toml index f4ab9c62..5ccd648c 100644 --- a/crates/masonry/Cargo.toml +++ b/crates/masonry/Cargo.toml @@ -36,7 +36,9 @@ pollster = "0.3.0" unicode-segmentation = "1.11.0" # TODO: Is this still the most up-to-date crate for this? xi-unicode = "0.3.0" -tracing-subscriber = "0.3.18" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +accesskit.workspace = true +accesskit_winit.workspace = true [dev-dependencies] float-cmp = { version = "0.8.0", features = ["std"], default-features = false } diff --git a/crates/masonry/examples/calc.rs b/crates/masonry/examples/calc.rs index a8a7e4e3..7af84ead 100644 --- a/crates/masonry/examples/calc.rs +++ b/crates/masonry/examples/calc.rs @@ -5,20 +5,22 @@ // On Windows platform, don't show a console when opening the app. #![windows_subsystem = "windows"] +#![allow(clippy::single_match)] use std::sync::Arc; +use accesskit::{DefaultActionVerb, Role}; use masonry::app_driver::{AppDriver, DriverCtx}; use masonry::widget::{Align, CrossAxisAlignment, Flex, Label, SizedBox, WidgetRef}; use masonry::{ - Action, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, - PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod, + AccessCtx, AccessEvent, Action, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle, + LifeCycleCtx, PaintCtx, Point, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId, + WidgetPod, }; use smallvec::{smallvec, SmallVec}; use tracing::{trace, trace_span, Span}; use vello::Scene; use winit::dpi::LogicalSize; -use winit::event_loop::EventLoop; use winit::window::Window; #[derive(Clone)] @@ -170,6 +172,19 @@ impl Widget for CalcButton { self.inner.on_text_event(ctx, event); } + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + if event.target == ctx.widget_id() { + match event.action { + accesskit::Action::Default => { + ctx.submit_action(Action::Other(Arc::new(self.action))); + ctx.request_paint(); + } + _ => {} + } + } + ctx.skip_child(&mut self.inner); + } + fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { match event { StatusChange::HotChanged(true) => { @@ -200,6 +215,23 @@ impl Widget for CalcButton { self.inner.paint(ctx, scene); } + fn accessibility_role(&self) -> Role { + Role::Button + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + let _name = match self.action { + CalcAction::Digit(digit) => digit.to_string(), + CalcAction::Op(op) => op.to_string(), + }; + // We may want to add a name if it doesn't interfere with the child label + // ctx.current_node().set_name(name); + ctx.current_node() + .set_default_action_verb(DefaultActionVerb::Click); + + self.inner.accessibility(ctx); + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { smallvec![self.inner.as_dyn()] } @@ -280,7 +312,7 @@ fn flex_row( } fn build_calc() -> impl Widget { - let display = Label::new("").with_text_size(32.0); + let display = Label::new(String::new()).with_text_size(32.0); Flex::column() .with_flex_spacer(0.2) .with_child(display) @@ -338,18 +370,12 @@ fn build_calc() -> impl Widget { } pub fn main() { - let event_loop = EventLoop::new().unwrap(); let window_size = LogicalSize::new(223., 300.); - #[allow(deprecated)] - let window = event_loop - .create_window( - Window::default_attributes() - .with_title("Simple Calculator") - .with_resizable(true) - .with_min_inner_size(window_size), - ) - .unwrap(); + let window_attributes = Window::default_attributes() + .with_title("Simple Calculator") + .with_resizable(true) + .with_min_inner_size(window_size); let calc_state = CalcState { value: "0".to_string(), @@ -358,5 +384,5 @@ pub fn main() { in_num: false, }; - masonry::event_loop_runner::run(build_calc(), window, event_loop, calc_state).unwrap(); + masonry::event_loop_runner::run(window_attributes, build_calc(), calc_state).unwrap(); } diff --git a/crates/masonry/examples/custom_widget.rs b/crates/masonry/examples/custom_widget.rs index 67d05be7..0647f313 100644 --- a/crates/masonry/examples/custom_widget.rs +++ b/crates/masonry/examples/custom_widget.rs @@ -7,13 +7,15 @@ // On Windows platform, don't show a console when opening the app. #![windows_subsystem = "windows"] +use accesskit::Role; use kurbo::Stroke; use masonry::app_driver::{AppDriver, DriverCtx}; use masonry::kurbo::BezPath; use masonry::widget::{FillStrat, WidgetRef}; use masonry::{ - Action, Affine, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, - Point, PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, WidgetId, + AccessCtx, AccessEvent, Action, Affine, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle, + LifeCycleCtx, PaintCtx, Point, PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, + WidgetId, }; use parley::layout::Alignment; use parley::style::{FontFamily, FontStack, StyleProperty}; @@ -21,7 +23,6 @@ use smallvec::SmallVec; use tracing::{trace_span, Span}; use vello::peniko::{Brush, Fill, Format, Image}; use vello::Scene; -use winit::event_loop::EventLoop; use winit::window::Window; struct Driver; @@ -40,6 +41,8 @@ impl Widget for CustomWidget { fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} + fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle) {} fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} @@ -126,6 +129,17 @@ impl Widget for CustomWidget { scene.draw_image(&image_data, transform); } + fn accessibility_role(&self) -> Role { + Role::Canvas + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + let text = &self.0; + ctx.current_node().set_name( + format!("This is a demo of the Masonry Widget trait. Masonry has accessibility tree support. The demo shows colored shapes with the text '{text}'."), + ); + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { SmallVec::new() } @@ -137,13 +151,9 @@ impl Widget for CustomWidget { pub fn main() { let my_string = "Masonry + Vello".to_string(); - let event_loop = EventLoop::new().unwrap(); - #[allow(deprecated)] - let window = event_loop - .create_window(Window::default_attributes().with_title("Fancy colots")) - .unwrap(); + let window_attributes = Window::default_attributes().with_title("Fancy colors"); - masonry::event_loop_runner::run(CustomWidget(my_string), window, event_loop, Driver).unwrap(); + masonry::event_loop_runner::run(window_attributes, CustomWidget(my_string), Driver).unwrap(); } fn make_image_data(width: usize, height: usize) -> Vec { diff --git a/crates/masonry/examples/hello_masonry.rs b/crates/masonry/examples/hello_masonry.rs index 87ec71e8..21e0d3eb 100644 --- a/crates/masonry/examples/hello_masonry.rs +++ b/crates/masonry/examples/hello_masonry.rs @@ -12,7 +12,6 @@ use masonry::widget::prelude::*; use masonry::widget::{Button, Flex, Label}; use masonry::Action; use winit::dpi::LogicalSize; -use winit::event_loop::EventLoop; use winit::window::Window; const VERTICAL_WIDGET_SPACING: f64 = 20.0; @@ -33,19 +32,13 @@ impl AppDriver for Driver { } pub fn main() { - let event_loop = EventLoop::new().unwrap(); let window_size = LogicalSize::new(400.0, 400.0); - #[allow(deprecated)] - let window = event_loop - .create_window( - Window::default_attributes() - .with_title("Hello World!") - .with_resizable(true) - .with_min_inner_size(window_size), - ) - .unwrap(); + let window_attributes = Window::default_attributes() + .with_title("Hello World!") + .with_resizable(true) + .with_min_inner_size(window_size); - masonry::event_loop_runner::run(build_root_widget(), window, event_loop, Driver).unwrap(); + masonry::event_loop_runner::run(window_attributes, build_root_widget(), Driver).unwrap(); } fn build_root_widget() -> impl Widget { diff --git a/crates/masonry/examples/simple_image.rs b/crates/masonry/examples/simple_image.rs index 4c5219b2..0988c63b 100644 --- a/crates/masonry/examples/simple_image.rs +++ b/crates/masonry/examples/simple_image.rs @@ -13,7 +13,6 @@ use masonry::widget::{FillStrat, Image}; use masonry::{Action, WidgetId}; use vello::peniko::{Format, Image as ImageBuf}; use winit::dpi::LogicalSize; -use winit::event_loop::EventLoop; use winit::window::Window; struct Driver; @@ -29,17 +28,11 @@ pub fn main() { let png_data = ImageBuf::new(image_data.to_vec().into(), Format::Rgba8, width, height); let image = Image::new(png_data).fill_mode(FillStrat::Contain); - let event_loop = EventLoop::new().unwrap(); let window_size = LogicalSize::new(650.0, 450.0); - #[allow(deprecated)] - let window = event_loop - .create_window( - Window::default_attributes() - .with_title("Simple image example") - .with_min_inner_size(window_size) - .with_max_inner_size(window_size), - ) - .unwrap(); + let window_attributes = Window::default_attributes() + .with_title("Simple image example") + .with_min_inner_size(window_size) + .with_max_inner_size(window_size); - masonry::event_loop_runner::run(image, window, event_loop, Driver).unwrap(); + masonry::event_loop_runner::run(window_attributes, image, Driver).unwrap(); } diff --git a/crates/masonry/src/contexts.rs b/crates/masonry/src/contexts.rs index f0eca3be..96a73339 100644 --- a/crates/masonry/src/contexts.rs +++ b/crates/masonry/src/contexts.rs @@ -6,6 +6,7 @@ use std::any::Any; use std::time::Duration; +use accesskit::{NodeBuilder, TreeUpdate}; use parley::FontContext; use tracing::{trace, warn}; use winit::dpi::LogicalPosition; @@ -86,6 +87,14 @@ pub struct PaintCtx<'a> { pub(crate) debug_widget: bool, } +pub struct AccessCtx<'a> { + pub(crate) global_state: &'a mut RenderRootState, + pub(crate) widget_state: &'a WidgetState, + pub(crate) tree_update: &'a mut TreeUpdate, + pub(crate) current_node: NodeBuilder, + pub(crate) rebuild_all: bool, +} + pub struct WorkerCtx<'a> { // TODO #[allow(dead_code)] @@ -100,6 +109,7 @@ impl_context_method!( LifeCycleCtx<'_>, PaintCtx<'_>, LayoutCtx<'_>, + AccessCtx<'_>, { /// get the `WidgetId` of the current widget. pub fn widget_id(&self) -> WidgetId { @@ -365,6 +375,12 @@ impl_context_method!(WidgetCtx<'_>, EventCtx<'_>, LifeCycleCtx<'_>, { self.widget_state.needs_layout = true; } + pub fn request_accessibility_update(&mut self) { + trace!("request_accessibility_update"); + self.widget_state.needs_accessibility_update = true; + self.widget_state.request_accessibility_update = true; + } + /// Request an animation frame. pub fn request_anim_frame(&mut self) { trace!("request_anim_frame"); @@ -662,3 +678,19 @@ impl PaintCtx<'_> { self.depth } } + +impl AccessCtx<'_> { + pub fn current_node(&mut self) -> &mut NodeBuilder { + &mut self.current_node + } + + /// Report whether accessibility was requested on this widget. + /// + /// This method is primarily intended for containers. The `accessibility` + /// method will be called on a widget when it or any of its descendants + /// have seen a request. However, in many cases a container need not push + /// a node for itself. + pub fn is_requested(&self) -> bool { + self.widget_state.needs_accessibility_update + } +} diff --git a/crates/masonry/src/event.rs b/crates/masonry/src/event.rs index 791c9796..9b75d669 100644 --- a/crates/masonry/src/event.rs +++ b/crates/masonry/src/event.rs @@ -9,6 +9,7 @@ use crate::WidgetId; use std::{collections::HashSet, path::PathBuf}; +use accesskit::{Action, ActionData}; use winit::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use winit::event::{Ime, KeyEvent, Modifiers, MouseButton}; use winit::keyboard::ModifiersState; @@ -56,6 +57,14 @@ pub enum TextEvent { FocusChange(bool), } +#[derive(Debug, Clone)] +pub struct AccessEvent { + // TODO - Split out widget id from AccessEvent + pub target: WidgetId, + pub action: Action, + pub data: Option, +} + #[derive(Debug, Clone)] pub struct PointerState { // TODO @@ -230,6 +239,20 @@ impl PointerEvent { PointerEvent::HoverFileCancel(_) => "HoverFileCancel", } } + + pub fn is_high_density(&self) -> bool { + match self { + PointerEvent::PointerDown(_, _) => false, + PointerEvent::PointerUp(_, _) => false, + PointerEvent::PointerMove(_) => true, + PointerEvent::PointerEnter(_) => false, + PointerEvent::PointerLeave(_) => false, + PointerEvent::MouseWheel(_, _) => true, + PointerEvent::HoverFile(_, _) => true, + PointerEvent::DropFile(_, _) => false, + PointerEvent::HoverFileCancel(_) => false, + } + } } impl TextEvent { @@ -241,6 +264,48 @@ impl TextEvent { TextEvent::FocusChange(_) => "FocusChange", } } + + pub fn is_high_density(&self) -> bool { + match self { + TextEvent::KeyboardKey(event, _) => event.repeat, + TextEvent::Ime(_) => false, + TextEvent::ModifierChange(_) => false, + TextEvent::FocusChange(_) => false, + } + } +} + +impl AccessEvent { + pub fn short_name(&self) -> &'static str { + match self.action { + accesskit::Action::Default => "Default", + accesskit::Action::Focus => "Focus", + accesskit::Action::Blur => "Blur", + accesskit::Action::Collapse => "Collapse", + accesskit::Action::Expand => "Expand", + accesskit::Action::CustomAction => "CustomAction", + accesskit::Action::Decrement => "Decrement", + accesskit::Action::Increment => "Increment", + accesskit::Action::HideTooltip => "HideTooltip", + accesskit::Action::ShowTooltip => "ShowTooltip", + accesskit::Action::ReplaceSelectedText => "ReplaceSelectedText", + accesskit::Action::ScrollBackward => "ScrollBackward", + accesskit::Action::ScrollDown => "ScrollDown", + accesskit::Action::ScrollForward => "ScrollForward", + accesskit::Action::ScrollLeft => "ScrollLeft", + accesskit::Action::ScrollRight => "ScrollRight", + accesskit::Action::ScrollUp => "ScrollUp", + accesskit::Action::ScrollIntoView => "ScrollIntoView", + accesskit::Action::ScrollToPoint => "ScrollToPoint", + accesskit::Action::SetScrollOffset => "SetScrollOffset", + accesskit::Action::SetTextSelection => "SetTextSelection", + accesskit::Action::SetSequentialFocusNavigationStartingPoint => { + "SetSequentialFocusNavigationStartingPoint" + } + accesskit::Action::SetValue => "SetValue", + accesskit::Action::ShowContextMenu => "ShowContextMenu", + } + } } impl PointerState { diff --git a/crates/masonry/src/event_loop_runner.rs b/crates/masonry/src/event_loop_runner.rs index ff45d8c0..65b693b3 100644 --- a/crates/masonry/src/event_loop_runner.rs +++ b/crates/masonry/src/event_loop_runner.rs @@ -4,8 +4,9 @@ use std::num::NonZeroUsize; use std::sync::Arc; +use accesskit_winit::Adapter; use tracing::subscriber::SetGlobalDefaultError; -use tracing::warn; +use tracing::{debug, warn}; use vello::kurbo::Affine; use vello::util::{RenderContext, RenderSurface}; use vello::{peniko::Color, AaSupport, RenderParams, Renderer, RendererOptions, Scene}; @@ -15,7 +16,7 @@ use winit::dpi::LogicalPosition; use winit::error::EventLoopError; use winit::event::WindowEvent as WinitWindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoop}; -use winit::window::{Window, WindowId}; +use winit::window::{Window, WindowAttributes, WindowId}; use crate::app_driver::{AppDriver, DriverCtx}; use crate::event::{PointerState, WindowEvent}; @@ -30,12 +31,33 @@ struct MainState<'a> { renderer: Option, pointer_state: PointerState, app_driver: Box, + accesskit_adapter: Adapter, } pub fn run( + window_attributes: WindowAttributes, root_widget: impl Widget, + app_driver: impl AppDriver + 'static, +) -> Result<(), EventLoopError> { + let visible = window_attributes.visible; + let window_attributes = window_attributes.with_visible(false); + + let event_loop = EventLoop::with_user_event().build()?; + #[allow(deprecated)] + let window = event_loop.create_window(window_attributes).unwrap(); + + let event_loop_proxy = event_loop.create_proxy(); + let adapter = Adapter::with_event_loop_proxy(&window, event_loop_proxy); + window.set_visible(visible); + + run_with(window, event_loop, adapter, root_widget, app_driver) +} + +pub fn run_with( window: Window, - event_loop: EventLoop<()>, + event_loop: EventLoop, + accesskit_adapter: Adapter, + root_widget: impl Widget, app_driver: impl AppDriver + 'static, ) -> Result<(), EventLoopError> { let window = Arc::new(window); @@ -57,6 +79,7 @@ pub fn run( renderer: None, pointer_state: PointerState::empty(), app_driver: Box::new(app_driver), + accesskit_adapter, }; // If there is no default tracing subscriber, we set our own. If one has @@ -68,12 +91,14 @@ pub fn run( event_loop.run_app(&mut main_state) } -impl ApplicationHandler for MainState<'_> { +impl ApplicationHandler for MainState<'_> { fn resumed(&mut self, _event_loop: &ActiveEventLoop) { // FIXME: initialize window in this handler because initializing it before running the event loop is deprecated } fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WinitWindowEvent) { + self.accesskit_adapter.process_event(&self.window, &event); + match event { WinitWindowEvent::RedrawRequested => { let scene = self.render_root.redraw(); @@ -149,10 +174,12 @@ impl ApplicationHandler for MainState<'_> { } _ => (), } + while let Some(signal) = self.render_root.pop_signal() { match signal { render_root::RenderRootSignal::Action(action, widget_id) => { self.render_root.edit_root_widget(|root| { + debug!("Action {:?} on widget {:?}", action, widget_id); let mut driver_ctx = DriverCtx { main_root_widget: root, }; @@ -203,6 +230,24 @@ impl ApplicationHandler for MainState<'_> { } } } + + self.accesskit_adapter + .update_if_active(|| self.render_root.root_accessibility(false)); + } + + fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: accesskit_winit::Event) { + match event.window_event { + // Note that this event can be called at any time, even multiple times if + // the user restarts their screen reader. + accesskit_winit::WindowEvent::InitialTreeRequested => { + self.accesskit_adapter + .update_if_active(|| self.render_root.root_accessibility(true)); + } + accesskit_winit::WindowEvent::ActionRequested(action_request) => { + self.render_root.root_on_access_event(action_request); + } + accesskit_winit::WindowEvent::AccessibilityDeactivated => {} + } } } @@ -264,17 +309,23 @@ pub(crate) fn try_init_tracing() -> Result<(), SetGlobalDefaultError> { { use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::prelude::*; - let filter_layer = if cfg!(debug_assertions) { + use tracing_subscriber::EnvFilter; + + let default_level = if cfg!(debug_assertions) { LevelFilter::DEBUG } else { LevelFilter::INFO }; + let env_filter = EnvFilter::builder() + .with_default_directive(default_level.into()) + .with_env_var("RUST_LOG") + .from_env_lossy(); let fmt_layer = tracing_subscriber::fmt::layer() // Display target (eg "my_crate::some_mod::submod") with logs .with_target(true); let registry = tracing_subscriber::registry() - .with(filter_layer) + .with(env_filter) .with(fmt_layer); tracing::dispatcher::set_global_default(registry.into()) } diff --git a/crates/masonry/src/lib.rs b/crates/masonry/src/lib.rs index 622299af..8be9f4fc 100644 --- a/crates/masonry/src/lib.rs +++ b/crates/masonry/src/lib.rs @@ -118,8 +118,10 @@ pub mod text2; pub use action::Action; pub use box_constraints::BoxConstraints; -pub use contexts::{EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx, WidgetCtx}; -pub use event::{InternalLifeCycle, LifeCycle, PointerEvent, StatusChange, TextEvent, WindowTheme}; +pub use contexts::{AccessCtx, EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx, WidgetCtx}; +pub use event::{ + AccessEvent, InternalLifeCycle, LifeCycle, PointerEvent, StatusChange, TextEvent, WindowTheme, +}; pub use kurbo::{Affine, Insets, Point, Rect, Size, Vec2}; pub use parley::layout::Alignment as TextAlignment; pub use util::{AsAny, Handled}; diff --git a/crates/masonry/src/render_root.rs b/crates/masonry/src/render_root.rs index 12b26554..1d824dc3 100644 --- a/crates/masonry/src/render_root.rs +++ b/crates/masonry/src/render_root.rs @@ -3,11 +3,12 @@ use std::collections::VecDeque; +use accesskit::{ActionRequest, NodeBuilder, Tree, TreeUpdate}; // Automatically defaults to std::time::Instant on non Wasm platforms use instant::Instant; use kurbo::Affine; use parley::FontContext; -use tracing::{info_span, warn}; +use tracing::{debug, info_span, warn}; use vello::peniko::{Color, Fill}; use vello::Scene; use winit::dpi::{LogicalPosition, LogicalSize, PhysicalSize}; @@ -20,7 +21,8 @@ use crate::event::{PointerEvent, TextEvent, WindowEvent}; use crate::kurbo::Point; use crate::widget::{WidgetMut, WidgetState}; use crate::{ - Action, BoxConstraints, Handled, InternalLifeCycle, LifeCycle, Widget, WidgetId, WidgetPod, + AccessCtx, AccessEvent, Action, BoxConstraints, Handled, InternalLifeCycle, LifeCycle, Widget, + WidgetId, WidgetPod, }; // TODO - Remove pub(crate) @@ -202,7 +204,10 @@ impl RenderRoot { widget: &mut self.root.inner, }; - let res = f(root_widget); + let res = { + let _span = info_span!("edit_root_widget").entered(); + f(root_widget) + }; self.post_event_processing(&mut fake_widget_state); res @@ -229,8 +234,11 @@ impl RenderRoot { let handled = { ctx.global_state .debug_logger - .push_important_span(&format!("¨POINTER_EVENT {}", event.short_name())); - let _span = info_span!("event").entered(); + .push_important_span(&format!("POINTER_EVENT {}", event.short_name())); + let _span = info_span!("pointer_event").entered(); + if !event.is_high_density() { + debug!("Running ON_POINTER_EVENT pass with {}", event.short_name()); + } self.root.on_pointer_event(&mut ctx, &event); ctx.global_state.debug_logger.pop_span(); Handled::from(ctx.is_handled) @@ -269,7 +277,10 @@ impl RenderRoot { ctx.global_state .debug_logger .push_important_span(&format!("TEXT_EVENT {}", event.short_name())); - let _span = info_span!("event").entered(); + let _span = info_span!("text_event").entered(); + if !event.is_high_density() { + debug!("Running ON_TEXT_EVENT pass with {}", event.short_name()); + } self.root.on_text_event(&mut ctx, &event); ctx.global_state.debug_logger.pop_span(); Handled::from(ctx.is_handled) @@ -292,6 +303,41 @@ impl RenderRoot { handled } + pub fn root_on_access_event(&mut self, event: ActionRequest) { + let mut widget_state = + WidgetState::new(self.root.id(), Some(self.get_kurbo_size()), ""); + + let mut ctx = EventCtx { + global_state: &mut self.state, + widget_state: &mut widget_state, + is_handled: false, + request_pan_to_child: None, + }; + + let Ok(id) = event.target.0.try_into() else { + warn!("Received ActionRequest with id 0. This shouldn't be possible."); + return; + }; + let event = AccessEvent { + target: WidgetId(id), + action: event.action, + data: event.data, + }; + + { + ctx.global_state + .debug_logger + .push_important_span(&format!("ACCESSS_EVENT {}", event.short_name())); + let _span = info_span!("access_event").entered(); + debug!("Running ON_ACCESS_EVENT pass with {}", event.short_name()); + self.root.on_access_event(&mut ctx, &event); + ctx.global_state.debug_logger.pop_span(); + } + + self.post_event_processing(&mut widget_state); + self.root.as_dyn().debug_validate(false); + } + fn root_lifecycle(&mut self, event: LifeCycle) { let mut widget_state = WidgetState::new(self.root.id(), Some(self.get_kurbo_size()), ""); @@ -369,7 +415,10 @@ impl RenderRoot { }; let mut scene = Scene::new(); - self.root.paint(&mut ctx, &mut scene); + { + let _span = info_span!("paint").entered(); + self.root.paint(&mut ctx, &mut scene); + } // FIXME - This is a workaround to Vello panicking when given an // empty scene @@ -386,6 +435,44 @@ impl RenderRoot { scene } + // TODO - Integrate in unit tests? + pub fn root_accessibility(&mut self, rebuild_all: bool) -> TreeUpdate { + let mut tree_update = TreeUpdate { + nodes: vec![], + tree: None, + focus: self.state.focused_widget.unwrap_or(self.root.id()).into(), + }; + let mut widget_state = + WidgetState::new(self.root.id(), Some(self.get_kurbo_size()), ""); + let mut ctx = AccessCtx { + global_state: &mut self.state, + widget_state: &mut widget_state, + tree_update: &mut tree_update, + current_node: NodeBuilder::default(), + rebuild_all, + }; + + // TODO - tree_update.tree + { + let _span = info_span!("accessibility").entered(); + if rebuild_all { + debug!("Running ACCESSIBILITY pass with rebuild_all"); + } + self.root.accessibility(&mut ctx); + } + + if true { + tree_update.tree = Some(Tree { + root: self.root.id().into(), + app_name: None, + toolkit_name: Some("Masonry".to_string()), + toolkit_version: Some(env!("CARGO_PKG_VERSION").to_string()), + }); + } + + tree_update + } + fn get_kurbo_size(&self) -> kurbo::Size { let size = self.size.to_logical(self.scale_factor); kurbo::Size::new(size.width, size.height) diff --git a/crates/masonry/src/testing/helper_widgets.rs b/crates/masonry/src/testing/helper_widgets.rs index 92bb87a7..c6132161 100644 --- a/crates/masonry/src/testing/helper_widgets.rs +++ b/crates/masonry/src/testing/helper_widgets.rs @@ -15,6 +15,8 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::rc::Rc; +use accesskit::Role; +use accesskit_winit::Event; use smallvec::SmallVec; use vello::Scene; @@ -24,10 +26,13 @@ use crate::*; pub type PointerEventFn = dyn FnMut(&mut S, &mut EventCtx, &PointerEvent); pub type TextEventFn = dyn FnMut(&mut S, &mut EventCtx, &TextEvent); +pub type AccessEventFn = dyn FnMut(&mut S, &mut EventCtx, &AccessEvent); pub type StatusChangeFn = dyn FnMut(&mut S, &mut LifeCycleCtx, &StatusChange); pub type LifeCycleFn = dyn FnMut(&mut S, &mut LifeCycleCtx, &LifeCycle); pub type LayoutFn = dyn FnMut(&mut S, &mut LayoutCtx, &BoxConstraints) -> Size; pub type PaintFn = dyn FnMut(&mut S, &mut PaintCtx, &mut Scene); +pub type RoleFn = dyn Fn(&S) -> Role; +pub type AccessFn = dyn FnMut(&mut S, &mut AccessCtx); pub type ChildrenFn = dyn Fn(&S) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]>; #[cfg(FALSE)] @@ -40,10 +45,13 @@ pub struct ModularWidget { state: S, on_pointer_event: Option>>, on_text_event: Option>>, + on_access_event: Option>>, on_status_change: Option>>, lifecycle: Option>>, layout: Option>>, paint: Option>>, + role: Option>>, + access: Option>>, children: Option>>, } @@ -84,10 +92,12 @@ pub struct Recording(Rc>>); pub enum Record { PE(PointerEvent), TE(TextEvent), + AE(AccessEvent), SC(StatusChange), L(LifeCycle), Layout(Size), Paint, + Access, } /// like `WidgetExt` but just for this one thing @@ -112,10 +122,13 @@ impl ModularWidget { state, on_pointer_event: None, on_text_event: None, + on_access_event: None, on_status_change: None, lifecycle: None, layout: None, paint: None, + role: None, + access: None, children: None, } } @@ -136,6 +149,14 @@ impl ModularWidget { self } + pub fn access_event_fn( + mut self, + f: impl FnMut(&mut S, &mut EventCtx, &AccessEvent) + 'static, + ) -> Self { + self.on_access_event = Some(Box::new(f)); + self + } + pub fn status_change_fn( mut self, f: impl FnMut(&mut S, &mut LifeCycleCtx, &StatusChange) + 'static, @@ -165,6 +186,16 @@ impl ModularWidget { self } + pub fn role_fn(mut self, f: impl Fn(&S) -> Role + 'static) -> Self { + self.role = Some(Box::new(f)); + self + } + + pub fn access_fn(mut self, f: impl FnMut(&mut S, &mut AccessCtx) + 'static) -> Self { + self.access = Some(Box::new(f)); + self + } + pub fn children_fn( mut self, children: impl Fn(&S) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> + 'static, @@ -187,6 +218,12 @@ impl Widget for ModularWidget { } } + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + if let Some(f) = self.on_access_event.as_mut() { + f(&mut self.state, ctx, event); + } + } + fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { if let Some(f) = self.on_status_change.as_mut() { f(&mut self.state, ctx, event); @@ -211,6 +248,18 @@ impl Widget for ModularWidget { .unwrap_or_else(|| Size::new(100., 100.)) } + fn accessibility_role(&self) -> Role { + if let Some(f) = self.role.as_ref() { + f(&self.state) + } else { + Role::Unknown + } + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + todo!() + } + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { if let Some(f) = self.paint.as_mut() { f(&mut self.state, ctx, scene); @@ -256,6 +305,10 @@ impl Widget for ReplaceChild { todo!() } + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + todo!() + } + fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, _event: &StatusChange) { ctx.request_layout(); } @@ -272,6 +325,14 @@ impl Widget for ReplaceChild { self.child.paint(ctx, scene); } + fn accessibility_role(&self) -> Role { + Role::GenericContainer + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + self.child.accessibility(ctx); + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { self.child.widget().children() } @@ -319,6 +380,11 @@ impl Widget for Recorder { self.child.on_text_event(ctx, event); } + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + self.recording.push(Record::AE(event.clone())); + self.child.on_access_event(ctx, event); + } + fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { self.recording.push(Record::SC(event.clone())); self.child.on_status_change(ctx, event); @@ -336,8 +402,17 @@ impl Widget for Recorder { } fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { - self.child.paint(ctx, scene); self.recording.push(Record::Paint); + self.child.paint(ctx, scene); + } + + fn accessibility_role(&self) -> Role { + self.child.accessibility_role() + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + self.recording.push(Record::Access); + self.child.accessibility(ctx); } fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { diff --git a/crates/masonry/src/widget/align.rs b/crates/masonry/src/widget/align.rs index a26637d2..c6504b75 100644 --- a/crates/masonry/src/widget/align.rs +++ b/crates/masonry/src/widget/align.rs @@ -8,15 +8,17 @@ // size constraints to its child means that "aligning" a widget may actually change // its computed size. See issue #3. +use accesskit::Role; use smallvec::{smallvec, SmallVec}; use tracing::{trace, trace_span, Span}; use vello::Scene; +use crate::contexts::AccessCtx; use crate::paint_scene_helpers::UnitPoint; use crate::widget::{WidgetPod, WidgetRef}; use crate::{ - BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, PointerEvent, Rect, - Size, StatusChange, TextEvent, Widget, + AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, }; // TODO - Have child widget type as generic argument @@ -89,6 +91,10 @@ impl Widget for Align { self.child.on_text_event(ctx, event); } + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + self.child.on_access_event(ctx, event); + } + fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { self.child.lifecycle(ctx, event); } @@ -146,6 +152,14 @@ impl Widget for Align { self.child.paint(ctx, scene); } + fn accessibility_role(&self) -> Role { + Role::GenericContainer + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + self.child.accessibility(ctx); + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { smallvec![self.child.as_dyn()] } diff --git a/crates/masonry/src/widget/button.rs b/crates/masonry/src/widget/button.rs index 6903d035..50633d1f 100644 --- a/crates/masonry/src/widget/button.rs +++ b/crates/masonry/src/widget/button.rs @@ -3,6 +3,7 @@ //! A button widget. +use accesskit::{DefaultActionVerb, Role}; use smallvec::SmallVec; use tracing::{trace, trace_span, Span}; use vello::Scene; @@ -12,8 +13,8 @@ use crate::paint_scene_helpers::{fill_lin_gradient, stroke, UnitPoint}; use crate::text2::TextStorage; use crate::widget::{Label, WidgetMut, WidgetPod, WidgetRef}; use crate::{ - theme, BoxConstraints, EventCtx, Insets, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, - PointerEvent, Size, StatusChange, TextEvent, Widget, + theme, AccessCtx, AccessEvent, BoxConstraints, EventCtx, Insets, LayoutCtx, LifeCycle, + LifeCycleCtx, PaintCtx, PointerEvent, Size, StatusChange, TextEvent, Widget, }; // the minimum padding added to a button. @@ -103,6 +104,19 @@ impl Widget for Button { self.label.on_text_event(ctx, event); } + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + if event.target == ctx.widget_id() { + match event.action { + accesskit::Action::Default => { + ctx.submit_action(Action::ButtonPressed); + ctx.request_paint(); + } + _ => {} + } + } + ctx.skip_child(&mut self.label); + } + fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, _event: &StatusChange) { ctx.request_paint(); } @@ -173,6 +187,20 @@ impl Widget for Button { self.label.paint(ctx, scene); } + fn accessibility_role(&self) -> Role { + Role::Button + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + let _name = self.label.widget().text().as_str().to_string(); + // We may want to add a name if it doesn't interfere with the child label + // ctx.current_node().set_name(name); + ctx.current_node() + .set_default_action_verb(DefaultActionVerb::Click); + + self.label.accessibility(ctx); + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { SmallVec::new() } diff --git a/crates/masonry/src/widget/checkbox.rs b/crates/masonry/src/widget/checkbox.rs index b55c1fd1..bb76d8de 100644 --- a/crates/masonry/src/widget/checkbox.rs +++ b/crates/masonry/src/widget/checkbox.rs @@ -3,6 +3,7 @@ //! A checkbox widget. +use accesskit::{DefaultActionVerb, Role, Toggled}; use kurbo::{Affine, Stroke}; use smallvec::SmallVec; use tracing::{trace, trace_span, Span}; @@ -14,8 +15,8 @@ use crate::paint_scene_helpers::{fill_lin_gradient, stroke, UnitPoint}; use crate::text2::TextStorage; use crate::widget::{Label, WidgetMut, WidgetRef}; use crate::{ - theme, ArcStr, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, - PointerEvent, StatusChange, TextEvent, Widget, WidgetPod, + theme, AccessCtx, AccessEvent, ArcStr, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, + LifeCycleCtx, PaintCtx, PointerEvent, StatusChange, TextEvent, Widget, WidgetPod, }; /// A checkbox that can be toggled. @@ -85,6 +86,19 @@ impl Widget for Checkbox { fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + if event.target == ctx.widget_id() { + match event.action { + accesskit::Action::Default => { + self.checked = !self.checked; + ctx.submit_action(Action::CheckboxChecked(self.checked)); + ctx.request_paint(); + } + _ => {} + } + } + } + fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, _event: &StatusChange) { ctx.request_paint(); } @@ -166,6 +180,27 @@ impl Widget for Checkbox { self.label.paint(ctx, scene); } + fn accessibility_role(&self) -> Role { + Role::CheckBox + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + let _name = self.label.widget().text().as_str().to_string(); + // We may want to add a name if it doesn't interfere with the child label + // ctx.current_node().set_name(name); + if self.checked { + ctx.current_node().set_toggled(Toggled::True); + ctx.current_node() + .set_default_action_verb(DefaultActionVerb::Uncheck); + } else { + ctx.current_node().set_toggled(Toggled::False); + ctx.current_node() + .set_default_action_verb(DefaultActionVerb::Check); + } + + self.label.accessibility(ctx); + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { SmallVec::new() } diff --git a/crates/masonry/src/widget/flex.rs b/crates/masonry/src/widget/flex.rs index 2f0922e3..8cd0f1e8 100644 --- a/crates/masonry/src/widget/flex.rs +++ b/crates/masonry/src/widget/flex.rs @@ -3,6 +3,7 @@ //! A widget that arranges its children in a one-dimensional array. +use accesskit::Role; use kurbo::{Affine, Stroke}; use smallvec::SmallVec; use tracing::{trace, trace_span, Span}; @@ -13,8 +14,8 @@ use crate::kurbo::Vec2; use crate::theme::get_debug_color; use crate::widget::{WidgetMut, WidgetRef}; use crate::{ - BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, PointerEvent, - Rect, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod, + AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + Point, PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod, }; /// A container with either horizontal or vertical layout. @@ -498,6 +499,12 @@ impl Widget for Flex { } } + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) { + child.on_access_event(ctx, event); + } + } + fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { @@ -719,6 +726,16 @@ impl Widget for Flex { } } + fn accessibility_role(&self) -> Role { + Role::GenericContainer + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) { + child.accessibility(ctx); + } + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { self.children .iter() diff --git a/crates/masonry/src/widget/image.rs b/crates/masonry/src/widget/image.rs index 54cc8284..16fbe7e2 100644 --- a/crates/masonry/src/widget/image.rs +++ b/crates/masonry/src/widget/image.rs @@ -4,6 +4,7 @@ //! An Image widget. //! Please consider using SVG and the SVG widget as it scales much better. +use accesskit::Role; use kurbo::Affine; use smallvec::SmallVec; use tracing::{trace, trace_span, Span}; @@ -12,8 +13,8 @@ use vello::Scene; use crate::widget::{FillStrat, WidgetMut, WidgetRef}; use crate::{ - BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, PointerEvent, Size, - StatusChange, TextEvent, Widget, + AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + PointerEvent, Size, StatusChange, TextEvent, Widget, }; // TODO - Resolve name collision between masonry::Image and peniko::Image @@ -68,6 +69,8 @@ impl Widget for Image { fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} + fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle) {} @@ -101,6 +104,14 @@ impl Widget for Image { scene.pop_layer(); } + fn accessibility_role(&self) -> Role { + Role::Image + } + + fn accessibility(&mut self, _ctx: &mut AccessCtx) { + // TODO - Handle alt text and such. + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { SmallVec::new() } diff --git a/crates/masonry/src/widget/label.rs b/crates/masonry/src/widget/label.rs index 01945c67..02a360bf 100644 --- a/crates/masonry/src/widget/label.rs +++ b/crates/masonry/src/widget/label.rs @@ -1,27 +1,24 @@ // Copyright 2019 the Xilem Authors and the Druid Authors // SPDX-License-Identifier: Apache-2.0 +//! A label widget. + +use accesskit::Role; use kurbo::{Affine, Point, Size}; -use parley::{ - layout::Alignment, - style::{FontFamily, FontStack}, -}; +use parley::layout::Alignment; +use parley::style::{FontFamily, FontStack}; use smallvec::SmallVec; use tracing::trace; -use vello::{ - peniko::{BlendMode, Color}, - Scene, -}; +use vello::peniko::BlendMode; +use vello::Scene; +use crate::text2::{TextBrush, TextLayout, TextStorage}; +use crate::widget::{WidgetMut, WidgetRef}; use crate::{ - text2::{TextBrush, TextLayout, TextStorage}, - widget::WidgetRef, - ArcStr, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, PointerEvent, - StatusChange, TextEvent, Widget, + AccessCtx, AccessEvent, ArcStr, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle, + LifeCycleCtx, PaintCtx, PointerEvent, StatusChange, TextEvent, Widget, }; -use super::WidgetMut; - // added padding between the edges of the widget and the text. pub(super) const LABEL_X_PADDING: f64 = 2.0; @@ -164,6 +161,8 @@ impl Widget for Label { // that the bounding boxes can go e.g. across line boundaries? } + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} + #[allow(missing_docs)] fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, event: &StatusChange) { match event { @@ -244,6 +243,15 @@ impl Widget for Label { } } + fn accessibility_role(&self) -> Role { + Role::StaticText + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + ctx.current_node() + .set_name(self.text().as_str().to_string()); + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { SmallVec::new() } diff --git a/crates/masonry/src/widget/portal.rs b/crates/masonry/src/widget/portal.rs index bb534198..997b88d2 100644 --- a/crates/masonry/src/widget/portal.rs +++ b/crates/masonry/src/widget/portal.rs @@ -5,6 +5,7 @@ use std::ops::Range; +use accesskit::Role; use kurbo::Affine; use smallvec::{smallvec, SmallVec}; use tracing::{trace_span, Span}; @@ -14,8 +15,8 @@ use vello::Scene; use crate::kurbo::{Point, Rect, Size, Vec2}; use crate::widget::{Axis, ScrollBar, WidgetMut, WidgetRef}; use crate::{ - BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, PointerEvent, - StatusChange, TextEvent, Widget, WidgetPod, + AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + PointerEvent, StatusChange, TextEvent, Widget, WidgetPod, }; // TODO - refactor - see issue #15 @@ -289,6 +290,14 @@ impl Widget for Portal { self.scrollbar_vertical.on_text_event(ctx, event); } + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + // TODO - Handle scroll-related events? + + self.child.on_access_event(ctx, event); + self.scrollbar_horizontal.on_access_event(ctx, event); + self.scrollbar_vertical.on_access_event(ctx, event); + } + fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { @@ -379,6 +388,31 @@ impl Widget for Portal { } } + fn accessibility_role(&self) -> Role { + Role::GenericContainer + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + // TODO - Double check this code + // Not sure about these values + if false { + ctx.current_node().set_scroll_x(self.viewport_pos.x); + ctx.current_node().set_scroll_y(self.viewport_pos.y); + ctx.current_node().set_scroll_x_min(0.0); + ctx.current_node() + .set_scroll_x_max(self.scrollbar_horizontal.widget().portal_size); + ctx.current_node().set_scroll_y_min(0.0); + ctx.current_node() + .set_scroll_y_max(self.scrollbar_vertical.widget().portal_size); + } + + ctx.current_node().set_clips_children(); + + self.child.accessibility(ctx); + self.scrollbar_horizontal.accessibility(ctx); + self.scrollbar_vertical.accessibility(ctx); + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { smallvec![self.child.as_dyn()] } diff --git a/crates/masonry/src/widget/prose.rs b/crates/masonry/src/widget/prose.rs index 00e03225..e0d0ae3f 100644 --- a/crates/masonry/src/widget/prose.rs +++ b/crates/masonry/src/widget/prose.rs @@ -1,6 +1,7 @@ // Copyright 2018 the Xilem Authors and the Druid Authors // SPDX-License-Identifier: Apache-2.0 +use accesskit::Role; use kurbo::{Affine, Point, Size}; use parley::{ layout::Alignment, @@ -13,8 +14,8 @@ use vello::{peniko::BlendMode, Scene}; use crate::{ text2::{Selectable, TextBrush, TextWithSelection}, widget::label::LABEL_X_PADDING, - BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, PointerEvent, - StatusChange, TextEvent, Widget, + AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + PointerEvent, StatusChange, TextEvent, Widget, }; use super::{LineBreaking, WidgetMut, WidgetRef}; @@ -175,6 +176,10 @@ impl Widget for Prose { } } + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) { + // TODO - Handle accesskit::Action::SetTextSelection + } + #[allow(missing_docs)] fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { match event { @@ -261,6 +266,15 @@ impl Widget for Prose { } } + fn accessibility_role(&self) -> Role { + Role::StaticText + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + ctx.current_node() + .set_name(self.text().as_str().to_string()); + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { SmallVec::new() } diff --git a/crates/masonry/src/widget/scroll_bar.rs b/crates/masonry/src/widget/scroll_bar.rs index 66d235b0..8750a2c8 100644 --- a/crates/masonry/src/widget/scroll_bar.rs +++ b/crates/masonry/src/widget/scroll_bar.rs @@ -3,6 +3,7 @@ #![allow(missing_docs)] +use accesskit::Role; use smallvec::SmallVec; use tracing::{trace_span, Span}; use vello::Scene; @@ -12,8 +13,8 @@ use crate::kurbo::Rect; use crate::paint_scene_helpers::{fill_color, stroke}; use crate::widget::{WidgetMut, WidgetRef}; use crate::{ - theme, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, - PointerEvent, Size, StatusChange, TextEvent, Widget, + theme, AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, + PaintCtx, Point, PointerEvent, Size, StatusChange, TextEvent, Widget, }; // RULES @@ -172,6 +173,10 @@ impl Widget for ScrollBar { fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) { + // TODO - Handle scroll-related events? + } + fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle) {} @@ -210,6 +215,15 @@ impl Widget for ScrollBar { ); } + fn accessibility_role(&self) -> Role { + Role::ScrollBar + } + + fn accessibility(&mut self, _ctx: &mut AccessCtx) { + // TODO + // Use set_scroll_x/y_min/max? + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { SmallVec::new() } diff --git a/crates/masonry/src/widget/sized_box.rs b/crates/masonry/src/widget/sized_box.rs index e1e64823..9a641557 100644 --- a/crates/masonry/src/widget/sized_box.rs +++ b/crates/masonry/src/widget/sized_box.rs @@ -3,6 +3,7 @@ //! A widget with predefined size. +use accesskit::Role; use kurbo::Affine; use smallvec::{smallvec, SmallVec}; use tracing::{trace, trace_span, warn, Span}; @@ -13,8 +14,8 @@ use crate::kurbo::RoundedRectRadii; use crate::paint_scene_helpers::{fill_color, stroke}; use crate::widget::{WidgetId, WidgetMut, WidgetPod, WidgetRef}; use crate::{ - BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, PointerEvent, - Size, StatusChange, TextEvent, Widget, + AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + Point, PointerEvent, Size, StatusChange, TextEvent, Widget, }; // FIXME - Improve all doc in this module ASAP. @@ -291,6 +292,8 @@ impl Widget for SizedBox { } } + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} + fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { @@ -366,6 +369,16 @@ impl Widget for SizedBox { } } + fn accessibility_role(&self) -> Role { + Role::GenericContainer + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + if let Some(child) = self.child.as_mut() { + child.accessibility(ctx); + } + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { if let Some(child) = &self.child { smallvec![child.as_dyn()] diff --git a/crates/masonry/src/widget/spinner.rs b/crates/masonry/src/widget/spinner.rs index bc86383b..82832cdb 100644 --- a/crates/masonry/src/widget/spinner.rs +++ b/crates/masonry/src/widget/spinner.rs @@ -5,6 +5,7 @@ use std::f64::consts::PI; +use accesskit::Role; use kurbo::{Affine, Cap, Stroke}; use smallvec::SmallVec; use tracing::trace; @@ -13,8 +14,8 @@ use vello::Scene; use crate::kurbo::Line; use crate::widget::{WidgetMut, WidgetRef}; use crate::{ - theme, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, - PointerEvent, Size, StatusChange, TextEvent, Vec2, Widget, + theme, AccessCtx, AccessEvent, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle, + LifeCycleCtx, PaintCtx, Point, PointerEvent, Size, StatusChange, TextEvent, Vec2, Widget, }; // TODO - Set color @@ -72,6 +73,8 @@ impl Widget for Spinner { fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} + fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { @@ -136,6 +139,14 @@ impl Widget for Spinner { } } + fn accessibility_role(&self) -> Role { + // Don't like to use that role, but I'm not seing + // anything that matches in accesskit::Role + Role::Unknown + } + + fn accessibility(&mut self, _ctx: &mut AccessCtx) {} + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { SmallVec::new() } diff --git a/crates/masonry/src/widget/split.rs b/crates/masonry/src/widget/split.rs index 1bcf50bc..7a9bf2e3 100644 --- a/crates/masonry/src/widget/split.rs +++ b/crates/masonry/src/widget/split.rs @@ -3,6 +3,7 @@ //! A widget which splits an area in two, with a settable ratio, and optional draggable resizing. +use accesskit::Role; use smallvec::{smallvec, SmallVec}; use tracing::{trace, trace_span, warn, Span}; use vello::Scene; @@ -15,8 +16,8 @@ use crate::paint_scene_helpers::{fill_color, stroke}; use crate::widget::flex::Axis; use crate::widget::{WidgetMut, WidgetPod, WidgetRef}; use crate::{ - theme, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, - PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, + theme, AccessCtx, AccessEvent, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle, + LifeCycleCtx, PaintCtx, Point, PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, }; // TODO - Have child widget type as generic argument @@ -443,6 +444,11 @@ impl Widget for Split { self.child2.on_text_event(ctx, event); } + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + self.child1.on_access_event(ctx, event); + self.child2.on_access_event(ctx, event); + } + fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { @@ -558,6 +564,15 @@ impl Widget for Split { self.child2.paint(ctx, scene); } + fn accessibility_role(&self) -> Role { + Role::Splitter + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + self.child1.accessibility(ctx); + self.child2.accessibility(ctx); + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { smallvec![self.child1.as_dyn(), self.child2.as_dyn()] } diff --git a/crates/masonry/src/widget/textbox.rs b/crates/masonry/src/widget/textbox.rs index c587663a..d6ed64b6 100644 --- a/crates/masonry/src/widget/textbox.rs +++ b/crates/masonry/src/widget/textbox.rs @@ -1,6 +1,7 @@ // Copyright 2018 the Xilem Authors and the Druid Authors // SPDX-License-Identifier: Apache-2.0 +use accesskit::Role; use kurbo::{Affine, Point, Size, Stroke}; use parley::{ layout::Alignment, @@ -15,8 +16,8 @@ use vello::{ use crate::{ text2::{EditableText, TextBrush, TextEditor, TextWithSelection}, - BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, PointerEvent, - StatusChange, TextEvent, Widget, + AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + PointerEvent, StatusChange, TextEvent, Widget, }; use super::{LineBreaking, WidgetMut, WidgetRef}; @@ -179,6 +180,12 @@ impl Widget for Textbox { } } + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) { + // TODO - Handle accesskit::Action::SetTextSelection + // TODO - Handle accesskit::Action::ReplaceSelectedText + // TODO - Handle accesskit::Action::SetValue + } + #[allow(missing_docs)] fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { match event { @@ -274,6 +281,14 @@ impl Widget for Textbox { } } + fn accessibility_role(&self) -> Role { + Role::TextInput + } + + fn accessibility(&mut self, _ctx: &mut AccessCtx) { + // TODO + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { SmallVec::new() } diff --git a/crates/masonry/src/widget/widget.rs b/crates/masonry/src/widget/widget.rs index d876b86f..a0c22320 100644 --- a/crates/masonry/src/widget/widget.rs +++ b/crates/masonry/src/widget/widget.rs @@ -6,15 +6,16 @@ use std::num::NonZeroU64; use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicU64, Ordering}; +use accesskit::Role; use smallvec::SmallVec; use tracing::{trace_span, Span}; use vello::Scene; -use crate::event::StatusChange; -use crate::event::{PointerEvent, TextEvent}; +use crate::event::{AccessEvent, PointerEvent, StatusChange, TextEvent}; use crate::widget::WidgetRef; use crate::{ - AsAny, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, Size, + AccessCtx, AsAny, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + Point, Size, }; /// A unique identifier for a single [`Widget`]. @@ -39,7 +40,7 @@ use crate::{ /// If you set a `WidgetId` directly, you are responsible for ensuring that it /// is unique. Two widgets must not be created with the same id. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -pub struct WidgetId(NonZeroU64); +pub struct WidgetId(pub(crate) NonZeroU64); // TODO - Add tutorial: implementing a widget - See issue #5 /// The trait implemented by all widgets. @@ -73,6 +74,9 @@ pub trait Widget: AsAny { fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent); fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent); + /// Handle an event from the platform's accessibility API. + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent); + #[allow(missing_docs)] fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange); @@ -110,6 +114,10 @@ pub trait Widget: AsAny { /// the render context, which is especially useful for scrolling. fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene); + fn accessibility_role(&self) -> Role; + + fn accessibility(&mut self, ctx: &mut AccessCtx); + /// Return references to this widget's children. /// /// Leaf widgets return an empty array. Container widgets return references to @@ -228,11 +236,17 @@ impl WidgetId { WidgetId(unsafe { std::num::NonZeroU64::new_unchecked(id) }) } - pub(crate) fn to_raw(self) -> u64 { + pub fn to_raw(self) -> u64 { self.0.into() } } +impl From for accesskit::NodeId { + fn from(id: WidgetId) -> accesskit::NodeId { + accesskit::NodeId(id.0.into()) + } +} + // TODO - remove impl Widget for Box { fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) { @@ -243,6 +257,10 @@ impl Widget for Box { self.deref_mut().on_text_event(ctx, event); } + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + self.deref_mut().on_access_event(ctx, event); + } + fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { self.deref_mut().on_status_change(ctx, event); } @@ -259,6 +277,14 @@ impl Widget for Box { self.deref_mut().paint(ctx, scene); } + fn accessibility_role(&self) -> Role { + self.deref().accessibility_role() + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + self.deref_mut().accessibility(ctx); + } + fn type_name(&self) -> &'static str { self.deref().type_name() } diff --git a/crates/masonry/src/widget/widget_pod.rs b/crates/masonry/src/widget/widget_pod.rs index 71083849..e3aa852d 100644 --- a/crates/masonry/src/widget/widget_pod.rs +++ b/crates/masonry/src/widget/widget_pod.rs @@ -1,19 +1,20 @@ // Copyright 2018 the Xilem Authors and the Druid Authors // SPDX-License-Identifier: Apache-2.0 +use accesskit::{NodeBuilder, NodeId}; use tracing::{info_span, trace, warn}; use vello::Scene; use winit::dpi::LogicalPosition; -use crate::event::{PointerEvent, TextEvent}; +use crate::event::{AccessEvent, PointerEvent, TextEvent}; use crate::kurbo::{Affine, Insets, Point, Rect, Shape, Size}; use crate::paint_scene_helpers::stroke; use crate::render_root::RenderRootState; use crate::theme::get_debug_color; use crate::widget::{WidgetRef, WidgetState}; use crate::{ - BoxConstraints, EventCtx, InternalLifeCycle, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, - StatusChange, Widget, WidgetId, + AccessCtx, BoxConstraints, EventCtx, InternalLifeCycle, LayoutCtx, LifeCycle, LifeCycleCtx, + PaintCtx, StatusChange, Widget, WidgetId, }; // TODO - rewrite links in doc @@ -497,6 +498,59 @@ impl WidgetPod { self.inner.lifecycle(&mut inner_ctx, &event); } + pub fn on_access_event(&mut self, parent_ctx: &mut EventCtx, event: &AccessEvent) { + let _span = self.inner.make_trace_span().entered(); + // TODO #11 + parent_ctx + .global_state + .debug_logger + .push_span(self.inner.short_type_name()); + + // TODO - explain this + self.mark_as_visited(); + self.check_initialized("on_text_event"); + + if parent_ctx.is_handled { + parent_ctx.global_state.debug_logger.pop_span(); + // If the event was already handled, we quit early. + return; + } + + if self.state.children.may_contain(&event.target) { + self.call_widget_method_with_checks("on_access_event", |widget_pod| { + // widget_pod is a reborrow of `self` + let mut inner_ctx = EventCtx { + global_state: parent_ctx.global_state, + widget_state: &mut widget_pod.state, + is_handled: false, + request_pan_to_child: None, + }; + + widget_pod.inner.on_access_event(&mut inner_ctx, event); + + inner_ctx.widget_state.has_active |= inner_ctx.widget_state.is_active; + parent_ctx.is_handled |= inner_ctx.is_handled; + + // TODO - request_pan_to_child + }); + } + + // Always merge even if not needed, because merging is idempotent and gives us simpler code. + // Doing this conditionally only makes sense when there's a measurable performance boost. + parent_ctx.widget_state.merge_up(&mut self.state); + + parent_ctx + .global_state + .debug_logger + .update_widget_state(self.as_dyn()); + parent_ctx + .global_state + .debug_logger + .push_log(false, "updated state"); + + parent_ctx.global_state.debug_logger.pop_span(); + } + // --- LIFECYCLE --- // TODO #5 - Some implicit invariants: @@ -629,6 +683,8 @@ impl WidgetPod { self.state.needs_layout = true; self.state.needs_paint = true; self.state.needs_window_origin = true; + self.state.needs_accessibility_update = true; + self.state.request_accessibility_update = true; true } @@ -791,6 +847,8 @@ impl WidgetPod { self.state.is_expecting_place_child_call = true; // TODO - Not everything that has been re-laid out needs to be repainted. self.state.needs_paint = true; + self.state.request_accessibility_update = false; + self.state.needs_accessibility_update = false; bc.debug_check(self.inner.short_type_name()); @@ -945,6 +1003,78 @@ impl WidgetPod { let scene = &mut self.fragment; stroke(scene, &rect, color, BORDER_WIDTH); } + + pub fn accessibility(&mut self, parent_ctx: &mut AccessCtx) { + let _span = self.inner.make_trace_span().entered(); + + // TODO + // if self.state.is_stashed {} + + // TODO - explain this + self.mark_as_visited(); + self.check_initialized("accessibility"); + + // If this widget or a child has requested an accessibility update, + // or if AccessKit has requested a full rebuild, + // we call the accessibility method on this widget. + if parent_ctx.rebuild_all || self.state.request_accessibility_update { + trace!( + "Building accessibility node for widget '{}' #{}", + self.inner.short_type_name(), + self.state.id.to_raw() + ); + + self.call_widget_method_with_checks("accessibility", |widget_pod| { + let current_node = widget_pod.build_access_node(); + let mut inner_ctx = AccessCtx { + global_state: parent_ctx.global_state, + widget_state: &mut widget_pod.state, + tree_update: parent_ctx.tree_update, + current_node, + rebuild_all: parent_ctx.rebuild_all, + }; + widget_pod.inner.accessibility(&mut inner_ctx); + + let id = inner_ctx.widget_state.id.into(); + inner_ctx + .tree_update + .nodes + .push((id, inner_ctx.current_node.build())); + }); + } + + self.state.request_accessibility_update = false; + self.state.needs_accessibility_update = false; + } + + fn build_access_node(&mut self) -> NodeBuilder { + let mut node = NodeBuilder::new(self.inner.accessibility_role()); + node.set_bounds(to_accesskit_rect(self.state.window_layout_rect())); + + node.set_children( + self.inner + .children() + .iter() + .map(|pod| pod.id().into()) + .collect::>(), + ); + + if self.state.is_hot { + node.set_hovered(); + } + if self.state.is_disabled() { + node.set_disabled(); + } + if self.state.is_stashed { + node.set_hidden(); + } + + node + } +} + +fn to_accesskit_rect(r: Rect) -> accesskit::Rect { + accesskit::Rect::new(r.x0, r.y0, r.x1, r.y1) } // TODO - negative rects? diff --git a/crates/masonry/src/widget/widget_state.rs b/crates/masonry/src/widget/widget_state.rs index eecd87c7..e6a4b7da 100644 --- a/crates/masonry/src/widget/widget_state.rs +++ b/crates/masonry/src/widget/widget_state.rs @@ -83,6 +83,7 @@ pub struct WidgetState { pub(crate) needs_layout: bool, pub(crate) needs_paint: bool, + pub(crate) needs_accessibility_update: bool, /// Because of some scrolling or something, `parent_window_origin` needs to be updated. pub(crate) needs_window_origin: bool, @@ -90,6 +91,9 @@ pub struct WidgetState { /// Any descendant has requested an animation frame. pub(crate) request_anim: bool, + /// Any descendant has requested an accessibility update. + pub(crate) request_accessibility_update: bool, + pub(crate) update_focus_chain: bool, pub(crate) focus_chain: Vec, @@ -161,11 +165,13 @@ impl WidgetState { is_hot: false, needs_layout: false, needs_paint: false, + needs_accessibility_update: false, needs_window_origin: false, is_active: false, has_active: false, has_focus: false, request_anim: false, + request_accessibility_update: false, focus_chain: Vec::new(), children: Bloom::new(), children_changed: false, @@ -214,6 +220,7 @@ impl WidgetState { self.needs_paint |= child_state.needs_paint; self.needs_window_origin |= child_state.needs_window_origin; self.request_anim |= child_state.request_anim; + self.request_accessibility_update |= child_state.request_accessibility_update; self.children_disabled_changed |= child_state.children_disabled_changed; self.children_disabled_changed |= child_state.is_explicitly_disabled_new != child_state.is_explicitly_disabled; diff --git a/crates/xilem_masonry/Cargo.toml b/crates/xilem_masonry/Cargo.toml index 17d7f0cd..9d32213d 100644 --- a/crates/xilem_masonry/Cargo.toml +++ b/crates/xilem_masonry/Cargo.toml @@ -23,3 +23,5 @@ winit.workspace = true tracing.workspace = true vello.workspace = true smallvec.workspace = true +accesskit.workspace = true +accesskit_winit.workspace = true diff --git a/crates/xilem_masonry/src/any_view.rs b/crates/xilem_masonry/src/any_view.rs index e659546d..1105575b 100644 --- a/crates/xilem_masonry/src/any_view.rs +++ b/crates/xilem_masonry/src/any_view.rs @@ -3,10 +3,11 @@ use std::{any::Any, ops::Deref}; +use accesskit::Role; use masonry::widget::{WidgetMut, WidgetRef}; use masonry::{ - BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, PointerEvent, - Size, StatusChange, TextEvent, Widget, WidgetPod, + AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + Point, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetPod, }; use smallvec::SmallVec; use vello::Scene; @@ -201,6 +202,9 @@ impl Widget for DynWidget { fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) { self.inner.on_text_event(ctx, event); } + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + self.inner.on_access_event(ctx, event); + } fn on_status_change(&mut self, _: &mut LifeCycleCtx, _: &StatusChange) { // Intentionally do nothing @@ -220,6 +224,14 @@ impl Widget for DynWidget { self.inner.paint(ctx, scene); } + fn accessibility_role(&self) -> Role { + self.inner.widget().accessibility_role() + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + self.inner.accessibility(ctx); + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { let mut vec = SmallVec::new(); vec.push(self.inner.as_dyn()); diff --git a/crates/xilem_masonry/src/lib.rs b/crates/xilem_masonry/src/lib.rs index 8f46e4c4..9ffc8fb2 100644 --- a/crates/xilem_masonry/src/lib.rs +++ b/crates/xilem_masonry/src/lib.rs @@ -4,17 +4,22 @@ #![allow(clippy::comparison_chain)] use std::{any::Any, collections::HashMap}; +use accesskit::Role; use masonry::{ app_driver::AppDriver, event_loop_runner, widget::{WidgetMut, WidgetRef}, - BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, PointerEvent, - Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod, + AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + Point, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod, }; pub use masonry::{widget::Axis, Color, TextAlignment}; use smallvec::SmallVec; use vello::Scene; -use winit::{dpi::LogicalSize, error::EventLoopError, event_loop::EventLoop, window::Window}; +use winit::{ + dpi::LogicalSize, + error::EventLoopError, + window::{Window, WindowAttributes}, +}; mod any_view; mod id; @@ -54,6 +59,9 @@ impl Widget for RootWidget { fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) { self.pod.on_text_event(ctx, event); } + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + self.pod.on_access_event(ctx, event); + } fn on_status_change(&mut self, _: &mut LifeCycleCtx, _: &StatusChange) { // Intentionally do nothing? @@ -73,6 +81,14 @@ impl Widget for RootWidget { self.pod.paint(ctx, scene); } + fn accessibility_role(&self) -> Role { + Role::Window + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + self.pod.accessibility(ctx); + } + fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { let mut vec = SmallVec::new(); vec.push(self.pod.as_dyn()); @@ -171,32 +187,22 @@ where Logic: 'static, View: 'static, { - let event_loop = EventLoop::new().unwrap(); let window_size = LogicalSize::new(600., 800.); - #[allow(deprecated)] - let window = event_loop - .create_window( - Window::default_attributes() - .with_title(window_title) - .with_resizable(true) - .with_min_inner_size(window_size), - ) - .unwrap(); - self.run_windowed_in(window, event_loop) + let window_attributes = Window::default_attributes() + .with_title(window_title) + .with_resizable(true) + .with_min_inner_size(window_size); + self.run_windowed_in(window_attributes) } // TODO: Make windows into a custom view - pub fn run_windowed_in( - self, - window: Window, - event_loop: EventLoop<()>, - ) -> Result<(), EventLoopError> + pub fn run_windowed_in(self, window_attributes: WindowAttributes) -> Result<(), EventLoopError> where State: 'static, Logic: 'static, View: 'static, { - event_loop_runner::run(self.root_widget, window, event_loop, self.driver) + event_loop_runner::run(window_attributes, self.root_widget, self.driver) } } pub trait MasonryView: Send + 'static {