From 6868726844a0887c16437d0ee3389ed923fba89f Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sun, 27 Sep 2026 16:51:24 +0900 Subject: [PATCH] feat: show input history with frame timing --- Cargo.lock | 2979 ++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 5 + src/app.rs | 280 +++++ src/hid_source.rs | 187 +++ src/lib.rs | 7 +- src/main.rs | 26 +- src/timeline.rs | 92 ++ src/ui.rs | 307 +++++ tests/ui.rs | 235 ++++ 9 files changed, 4063 insertions(+), 55 deletions(-) create mode 100644 src/app.rs create mode 100644 src/hid_source.rs create mode 100644 src/ui.rs create mode 100644 tests/ui.rs diff --git a/Cargo.lock b/Cargo.lock index 78cdab1..3ca25a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,108 +2,2657 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "accesskit" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" +dependencies = [ + "uuid", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.13.2", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 2.0.21", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bit-set" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2f926cc3060f09db9ebc5b52823d85268d24bb917e472c0c4bea35780a7d" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.4", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.13.2", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.13.2", + "polling", + "rustix 1.1.5", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.4", + "rustix 1.1.5", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "color" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ec7c5eb7a16992b1904d76c517d170ab353b0e0b3d5a0c81a8a0cd1037893cf" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "ecolor" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0656a7530b9e2841ee7a5f983965bdd9840c32f4012f759cfd44b912cced6b34" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "eframe" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ad41fac6e3149abb4866e93df37dbd33baa95db525c3368c944584e95a5dc2" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "egui-wgpu", + "egui-winit", + "egui_glow", + "glow", + "glutin", + "glutin-winit", + "image", + "js-sys", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "profiling", + "raw-window-handle", + "static_assertions", + "wasm-bindgen", + "web-sys", + "web-time", + "windows-sys 0.61.2", + "winit", +] + +[[package]] +name = "egui" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc938cc27cd911415e1e4d151bc56ff9df063d8db081374e87aeb805ea74b2ba" +dependencies = [ + "accesskit", + "ahash", + "bitflags 2.13.2", + "emath", + "epaint", + "itertools", + "log", + "nohash-hasher", + "profiling", + "smallvec", + "unicode-segmentation", + "web-sys", +] + +[[package]] +name = "egui-wgpu" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cef1f6d1bba40b09a4d0d62cb822b81030922cfbf65c5373398c3a3caafc5bd" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "profiling", + "thiserror 2.0.21", + "type-map", + "web-time", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98466000559d66ba4db786a30448b01483946add4188e7f10f522a27b4fb89cc" +dependencies = [ + "arboard", + "bytemuck", + "egui", + "log", + "objc2 0.6.4", + "objc2-foundation 0.3.2", + "objc2-ui-kit 0.3.2", + "profiling", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "winit", +] + +[[package]] +name = "egui_glow" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "606ed4167daf341e5753d703285c4c1d562324b2c07aa8f405cd7d066fe58f3b" +dependencies = [ + "bytemuck", + "egui", + "glow", + "log", + "profiling", + "winit", +] + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "emath" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c8d4141cf0f60fb2f7aefaf352d3ffa5171ef0ab09731c64391801e95e48f1" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "epaint" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0ec0308dc23130fc1a7d4623a8534c2ed069c6dc84e258ee4f3de99615aa1a3" +dependencies = [ + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "font-types", + "harfrust", + "log", + "nohash-hasher", + "parking_lot", + "profiling", + "self_cell", + "skrifa", + "smallvec", + "unicode-general-category", + "unicode-segmentation", + "vello_cpu", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773fa9c96dd0dbef887e39d0ed6177f141cce4d68e6041df77570aa3702dfa13" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "error-code" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7" + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "fearless_simd" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97b65636e5b9ef369943878ac74335ba1c55c1cb6adbf1e2c293c624248d693" + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide 0.9.1", + "zlib-rs", +] + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-types" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8eb065f3251655b3c90e22e5e363f310fc5332fb3402e37bbc94752283248f6" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "frametap" +version = "0.1.0" +dependencies = [ + "bitflags 2.13.2", + "eframe", + "egui", + "hidapi", + "windows", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.5", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glifo" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed4a1bb24121291d27230c1b1b44e07d6a9b28cefdb32fe1581dfb84e14f940a" +dependencies = [ + "bytemuck", + "foldhash", + "hashbrown", + "log", + "peniko", + "skrifa", + "smallvec", + "vello_common", +] + +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" +dependencies = [ + "bitflags 2.13.2", + "cfg_aliases", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "once_cell", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" +dependencies = [ + "gl_generator", + "windows-sys 0.52.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "guillotiere" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b17e70c989c36bad147b27a58d148c0741c51448aa5653436547323e524d0ab" +dependencies = [ + "euclid", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "harfrust" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "read-fonts", + "smallvec", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "hidapi" +version = "2.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818c0e1d27887aaf76fe737042e27a66b796a7b099e6d2e1a72d106c2dff3fa6" +dependencies = [ + "cc", + "cfg-if", + "libc", + "pkg-config", + "windows-sys 0.61.2", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.21", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" +dependencies = [ + "arrayvec", + "euclid", + "polycool", + "smallvec", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "bitflags 2.13.2", + "libc", + "plain", + "redox_syscall 0.9.4", +] + +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "naga" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a616d2fb8c89516ac2723a581f69d6c18576046bed761bd6b305e5618e6ae130" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown", + "indexmap", + "libm", + "log", + "naga-types", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "thiserror 2.0.21", + "unicode-ident", +] + +[[package]] +name = "naga-types" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590afbf58a6f4f62873cd5cff4468061844bafa1cdf399cc954537c22d768d49" +dependencies = [ + "hashbrown", + "indexmap", + "rustc-hash 1.1.0", + "thiserror 2.0.21", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.2", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2 0.6.4", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.2", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "orbclient" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "peniko" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839c8299360d2e998bdb106dc0a6cd71dcc5f4df51df1b620361bf50e283cca6" +dependencies = [ + "bytemuck", + "color", + "kurbo", + "linebender_resource_handle", + "smallvec", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.5", + "windows-sys 0.61.2", +] + +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "read-fonts" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" +dependencies = [ + "bytemuck", + "font-types", + "once_cell", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "redox_syscall" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "skrifa" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.13.2", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.13.2", + "calloop 0.14.4", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.5", + "thiserror 2.0.21", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" +dependencies = [ + "libc", + "smithay-client-toolkit 0.20.0", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "pin-project-lite", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.15+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.3", +] + +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "uuid" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" + +[[package]] +name = "vello_common" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e9aed918117e8152c9eddfd8362d73c465c23f26c44786aa331707b8a64fa2" +dependencies = [ + "bytemuck", + "fearless_simd", + "guillotiere", + "log", + "peniko", + "smallvec", + "thiserror 2.0.21", +] + +[[package]] +name = "vello_cpu" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac7349e1f55f6b801c7c277958df4ea53e7f20f21e8014910ad888b2ecda93ea" +dependencies = [ + "bytemuck", + "glifo", + "hashbrown", + "vello_common", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" +dependencies = [ + "js-sys", + "tokio", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.6", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.5", + "scoped-tls", + "smallvec", + "wayland-sys", +] [[package]] -name = "cc" -version = "1.5.1" +name = "wayland-client" +version = "0.31.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040" +checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" dependencies = [ - "find-msvc-tools", - "shlex", + "bitflags 2.13.2", + "rustix 1.1.5", + "wayland-backend", + "wayland-scanner", ] [[package]] -name = "cfg-if" -version = "1.0.5" +name = "wayland-csd-frame" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.13.2", + "cursor-icon", + "wayland-backend", +] [[package]] -name = "find-msvc-tools" -version = "0.1.14" +name = "wayland-cursor" +version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.5", + "wayland-client", + "xcursor", +] [[package]] -name = "frametap" -version = "0.1.0" +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" dependencies = [ - "bitflags", - "hidapi", - "windows", + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-scanner", ] [[package]] -name = "hidapi" -version = "2.6.7" +name = "wayland-protocols-experimental" +version = "20250721.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818c0e1d27887aaf76fe737042e27a66b796a7b099e6d2e1a72d106c2dff3fa6" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" dependencies = [ - "cc", - "cfg-if", - "libc", - "pkg-config", - "windows-sys", + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] -name = "libc" -version = "0.2.189" +name = "wayland-protocols-misc" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] [[package]] -name = "pkg-config" -version = "0.3.34" +name = "wayland-protocols-plasma" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] [[package]] -name = "proc-macro2" -version = "1.0.107" +name = "wayland-protocols-wlr" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" dependencies = [ - "unicode-ident", + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] -name = "quote" -version = "1.0.47" +name = "wayland-scanner" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" dependencies = [ "proc-macro2", + "quick-xml", + "quote", ] [[package]] -name = "shlex" -version = "2.0.1" +name = "wayland-sys" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] [[package]] -name = "syn" -version = "2.0.119" +name = "web-sys" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "unicode-ident" -version = "1.0.26" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527ccdf43dd5b2e8676eed9984ce00e2bbb0a1b85b70c1969dcb6cd2eb55ab9e" +dependencies = [ + "arrayvec", + "bitflags 2.13.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown", + "js-sys", + "log", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c018fce9b6270aa203c2fdd56f3cce996713534bd757e4ea58c8560b121f14" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.13.2", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown", + "indexmap", + "log", + "naga", + "naga-types", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.21", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-naga-bridge", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7586165fd5f6d881cb9ce4bb71f40d6caab2c0f1837e3fc1d9788a197fb6004f" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b7fb58561a792bc237628ba0792e332de418fefe145f13b5ed8201e6d52f58" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "hashbrown", + "libloading", + "log", + "naga", + "naga-types", + "portable-atomic", + "portable-atomic-util", + "raw-window-handle", + "renderdoc-sys", + "static_assertions", + "thiserror 2.0.21", + "wgpu-naga-bridge", + "wgpu-types", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f62e73117bb7a62bfd9c5a5841438a823f6566c6442a808ee269d2d055c081" +dependencies = [ + "naga", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99dad6f1fbdbbdb4c278a6508b059d44688f5cebddf78d005a46a31340269286" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "js-sys", + "log", + "naga-types", + "raw-window-handle", + "static_assertions", + "web-sys", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] [[package]] name = "windows" @@ -158,7 +2707,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -169,7 +2718,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -206,6 +2755,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -215,6 +2791,39 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows-threading" version = "0.2.1" @@ -223,3 +2832,269 @@ checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ "windows-link", ] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.13.2", + "block2 0.5.1", + "bytemuck", + "calloop 0.13.0", + "cfg_aliases", + "concurrent-queue", + "core-foundation", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit 0.2.2", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "smithay-client-toolkit 0.19.2", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.5", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.13.2", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + +[[package]] +name = "zerocopy" +version = "0.8.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 4fae8fe..29c374f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,11 @@ edition = "2021" [dependencies] bitflags = "2.13.2" +# 描画は glow (OpenGL) で足りる。既定の wgpu は依存が重く、この用途に利点がない。 +# wayland と x11 は Windows では効かないが、Linux で cargo test と clippy を通すために要る。 +eframe = { version = "0.36.2", default-features = false, features = ["default_fonts", "glow", "wayland", "x11"] } +# ui.rs は eframe を知らずに済ませる。eframe に触るのは app.rs と main.rs だけである。 +egui = "0.36.2" [target."cfg(windows)".dependencies] hidapi = "2.6.7" diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..093843c --- /dev/null +++ b/src/app.rs @@ -0,0 +1,280 @@ +//! スレッドの起動と配線。 +//! +//! スレッドは 2 本ある。入力スレッドは [`crate::hid_source`] のブロッキング read から +//! [`History`] までを回し、UI スレッドは eframe のループで [`History`] を読む。 +//! 250Hz の書き込みと 60Hz の読み出しなので [`Mutex`] で足りる。ロックを持つのは +//! push と snapshot のコピーの間だけにする。 + +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; + +use eframe::egui; + +use crate::hid_source; +use crate::history::{History, Trial, DEFAULT_RETAIN_US, DEFAULT_TRIAL_GAP_US}; +use crate::report_decode::decode; +use crate::timeline::{EventKind, InputEvent, Target, Timeline}; +use crate::ui; + +/// 左スティックを方向入力と見なす閾値。設計 spec の既定値。 +const STICK_DEADZONE: f32 = 0.5; + +/// 方向を切り替える角度の閾値。セクタの半分にすると、隣のセクタに入った時点で切り替わる。 +const STICK_HYSTERESIS_DEG: f32 = 22.5; + +/// 抜き差しの再試行間隔。 +const RECONNECT_INTERVAL: Duration = Duration::from_secs(1); + +/// UI を描き直す間隔。押しっぱなしの長さは入力が無くても伸びるので、待たずに描き直す。 +const REPAINT_INTERVAL: Duration = Duration::from_millis(16); + +/// 入力スレッドと UI スレッドが共有する状態。 +#[derive(Debug)] +pub struct Shared { + history: Mutex, + status: Mutex, +} + +impl Shared { + pub fn new() -> Self { + Self { + history: Mutex::new(History::new(DEFAULT_RETAIN_US, DEFAULT_TRIAL_GAP_US)), + status: Mutex::new(ui::Status::default()), + } + } + + /// 表示に使う試行を複製する。何件読むかの判断は [`ui::recent_trials`] が持つ。 + /// ロックはこの複製の間だけで、描画の間は持たない。 + pub fn snapshot(&self, settings: &ui::Settings) -> Vec { + ui::recent_trials(&self.lock_history(), settings) + } + + pub fn status(&self) -> ui::Status { + self.lock_status().clone() + } + + fn push(&self, events: &[InputEvent], now_us: u64) { + self.lock_history().push(events, now_us); + } + + fn now_us(&self) -> u64 { + self.lock_history().now_us() + } + + /// 毒された Mutex でも中身を取り出して続ける。入力スレッドが panic したときに + /// UI まで落とすと、直前まで溜めた履歴を読めなくなる。[`Self::lock_status`] も同じ。 + fn lock_history(&self) -> std::sync::MutexGuard<'_, History> { + self.history.lock().unwrap_or_else(|err| err.into_inner()) + } + + fn lock_status(&self) -> std::sync::MutexGuard<'_, ui::Status> { + self.status.lock().unwrap_or_else(|err| err.into_inner()) + } +} + +impl Default for Shared { + fn default() -> Self { + Self::new() + } +} + +/// eframe に渡すアプリ。生成した時点で入力スレッドが走り出す。 +pub struct FrametapApp { + shared: Arc, + settings: ui::Settings, +} + +impl FrametapApp { + pub fn new(settings: ui::Settings) -> Self { + let shared = Arc::new(Shared::new()); + let input = Arc::clone(&shared); + thread::spawn(move || run_input(&input)); + + Self { shared, settings } + } +} + +impl eframe::App for FrametapApp { + fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { + let trials = self.shared.snapshot(&self.settings); + let status = self.shared.status(); + + // egui の慣習に合わせて引数を ui と呼ぶため、モジュールは絶対パスで書く。 + crate::ui::show(ui, &trials, &status, &self.settings); + + // 押しっぱなしの持続 F は入力が無くても伸びる。イベント待ちで止めない。 + ui.ctx().request_repaint_after(REPAINT_INTERVAL); + } +} + +/// 接続を張り直しながらレポートを読み続ける。戻らない。 +/// +/// 履歴は接続をまたいで残す。抜き差しの前に読んでいた試行が消えると、 +/// 何が起きたかを確かめる手段がなくなる。 +fn run_input(shared: &Shared) { + loop { + let reason = match hid_source::open() { + Ok(connection) => { + *shared.lock_status() = ui::Status::connected(connection.name().to_owned()); + pump(shared, &connection) + } + Err(reason) => reason, + }; + + // 代入の文で guard を捨てる。次の sleep の間ロックを持つと、その 1 秒は UI が状態を読めない。 + *shared.lock_status() = ui::Status::disconnected(reason); + + thread::sleep(RECONNECT_INTERVAL); + } +} + +/// 1 接続分のレポートを読み続ける。戻り値は読みが止まった理由。 +fn pump(shared: &Shared, connection: &hid_source::Connection) -> String { + let device = connection.device(); + let mut timeline = Timeline::new(device, STICK_DEADZONE, STICK_HYSTERESIS_DEG); + + // [`Timeline`] が出す時刻は接続ごとに 0 から始まる。履歴の時刻は戻せないので、 + // 前の接続の末尾から試行の区切り以上空けた位置に載せ直す。空けないと抜き差しの + // 前後が 1 試行に繋がる。 + let base_us = shared.now_us() + DEFAULT_TRIAL_GAP_US; + let mut held: Vec = Vec::new(); + + loop { + let report = match connection.read() { + Ok(report) => report, + Err(reason) => { + // 抜かれた時点で押されていたものは、離しが届かない。ここで離しておかないと + // 進行中の試行が押しっぱなしのまま残り、次の試行と繋がる。 + let now_us = base_us + timeline.now_us(); + shared.push(&releases(&held, now_us), now_us); + return reason; + } + }; + + // 壊れたレポートは捨てる。補間すると存在しない F 数が出る。 + let Some(state) = decode(device, report.bytes()) else { + continue; + }; + + let mut events = timeline.push(state, report.host_qpc_us()); + for event in &mut events { + event.at_us += base_us; + } + track_held(&mut held, &events); + + shared.push(&events, base_us + timeline.now_us()); + + let mut status = shared.lock_status(); + status.scale_us_per_tick = Some(timeline.scale_us_per_tick()); + status.scale_warning = timeline.scale_warning(); + } +} + +/// 押されているものを追う。[`History`] の中の状態は読めないので、ここで別に持つ。 +fn track_held(held: &mut Vec, events: &[InputEvent]) { + for event in events { + match event.kind { + EventKind::Press => { + if !held.contains(&event.target) { + held.push(event.target); + } + } + EventKind::Release => held.retain(|target| *target != event.target), + } + } +} + +fn releases(held: &[Target], at_us: u64) -> Vec { + held.iter() + .map(|target| InputEvent { + kind: EventKind::Release, + target: *target, + at_us, + // 離しを作ったのはデバイスではなく切断である。時間差は信用できない。 + gap_before: true, + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::report_decode::Buttons; + + fn press(target: Target) -> InputEvent { + InputEvent { + kind: EventKind::Press, + target, + at_us: 0, + gap_before: false, + } + } + + #[test] + fn track_held_follows_press_and_release() { + let mut held = Vec::new(); + track_held(&mut held, &[press(Target::Button(Buttons::L1))]); + track_held(&mut held, &[press(Target::Button(Buttons::R1))]); + // 同じ入力の押下が重なっても増えない。 + track_held(&mut held, &[press(Target::Button(Buttons::L1))]); + assert_eq!( + held, + vec![Target::Button(Buttons::L1), Target::Button(Buttons::R1)] + ); + + track_held( + &mut held, + &[InputEvent { + kind: EventKind::Release, + ..press(Target::Button(Buttons::L1)) + }], + ); + assert_eq!(held, vec![Target::Button(Buttons::R1)]); + } + + /// 切断で作る離しは、押されていたものを漏れなく閉じる。 + #[test] + fn releases_close_every_held_target() { + let held = vec![Target::Button(Buttons::L1), Target::Button(Buttons::R1)]; + let events = releases(&held, 1_234); + + assert_eq!(events.len(), 2); + assert!(events + .iter() + .all(|event| event.kind == EventKind::Release && event.at_us == 1_234)); + assert!(releases(&[], 0).is_empty()); + } + + /// 履歴は接続をまたいで残り、次の接続の入力は別の試行になる。 + /// 実機を繋がずに、入力スレッドが履歴へ書く手順だけを再現して確かめる。 + #[test] + fn a_reconnect_starts_a_new_trial_and_keeps_the_old_one() { + let shared = Shared::new(); + let first_us = 1_000; + shared.push(&[press(Target::Button(Buttons::L1))], 0); + shared.push( + &[InputEvent { + kind: EventKind::Release, + at_us: first_us, + ..press(Target::Button(Buttons::L1)) + }], + first_us, + ); + + // 再接続。デバイス時刻は 0 に戻るので、履歴の末尾から区切り分だけ空けて載せ直す。 + let base_us = shared.now_us() + DEFAULT_TRIAL_GAP_US; + shared.push( + &[InputEvent { + at_us: base_us, + ..press(Target::Button(Buttons::R1)) + }], + base_us, + ); + + let trials = shared.snapshot(&ui::Settings::default()); + assert_eq!(trials.len(), 2); + assert_eq!(trials[0].origin_us, base_us); + assert_eq!(trials[1].origin_us, 0); + } +} diff --git a/src/hid_source.rs b/src/hid_source.rs new file mode 100644 index 0000000..006dff1 --- /dev/null +++ b/src/hid_source.rs @@ -0,0 +1,187 @@ +//! hidapi でデバイスを開き、ブロッキング read で生レポートを 1 件ずつ返す。受信直後に QPC を打つ。 +//! +//! 書き込み系の API は呼ばない。output report と feature report を送ると Steam と取り合いになり、 +//! 別のレポートのデータが返る。 +//! +//! ブロッキング read はレポート到着で起床するので、ウィンドウが他のウィンドウに覆われても +//! 読みは止まらない。この前提は実機で確かめていない。 +//! +//! 実装は Windows だけにある。他の OS では [`open`] が必ず失敗し、UI は未接続を出す。 + +use crate::report_decode::Device; + +/// Sony Interactive Entertainment。 +pub const SONY_VENDOR_ID: u16 = 0x054C; + +/// 読み取り専用で扱う対象デバイス。`hid_probe` が列挙する PID と同じ。 +pub const TARGET_PRODUCTS: [(u16, &str, Device); 4] = [ + (0x0CE6, "DualSense", Device::DualSense), + (0x0DF2, "DualSense Edge", Device::DualSense), + (0x05C4, "DualShock 4 (1st gen)", Device::DualShock4), + (0x09CC, "DualShock 4 (2nd gen)", Device::DualShock4), +]; + +/// USB 接続の入力レポートは 64 バイトに収まる。 +const REPORT_BUFFER_LEN: usize = 64; + +/// 受信したレポート 1 件。 +/// +/// buffer は呼び出し側に持たせない。長さを間違えるとレポートが途中で切れ、 +/// 切れた先にあるデバイス時刻が読めなくなる。 +#[derive(Debug, Clone, Copy)] +pub struct Report { + buffer: [u8; REPORT_BUFFER_LEN], + len: usize, + host_qpc_us: u64, +} + +impl Report { + /// 受信した生バイト。 + pub fn bytes(&self) -> &[u8] { + &self.buffer[..self.len] + } + + /// 受信直後に打った QPC のµs 値。 + pub fn host_qpc_us(&self) -> u64 { + self.host_qpc_us + } +} + +pub use platform::{open, Connection}; + +#[cfg(windows)] +mod platform { + use hidapi::{HidApi, HidDevice}; + use std::sync::OnceLock; + use windows::Win32::System::Performance::{QueryPerformanceCounter, QueryPerformanceFrequency}; + + use super::{Report, REPORT_BUFFER_LEN, SONY_VENDOR_ID, TARGET_PRODUCTS}; + use crate::report_decode::Device; + + /// `read_timeout` に渡すミリ秒。負値はレポート到着までブロックする。 + const BLOCK_UNTIL_REPORT: i32 = -1; + + /// QPC の周波数は起動中変わらない。1 度だけ読む。 + static QPC_FREQUENCY: OnceLock = OnceLock::new(); + + /// 開いているデバイス。 + /// + /// フィールドは宣言順に drop される。[`HidApi`] の drop は `hid_exit` を呼び、 + /// 開いている handle を無効にするので、`device` を先に置いて先に閉じさせる。 + pub struct Connection { + device: HidDevice, + /// 接続の間 [`HidApi`] を生かすためだけに持つ。 + _api: HidApi, + kind: Device, + name: &'static str, + } + + /// 最初に見つかった対象デバイスを読み取り専用で開く。 + pub fn open() -> Result { + let api = HidApi::new().map_err(|err| format!("HidApi の初期化に失敗した: {err}"))?; + + let (product_id, name, kind) = api + .device_list() + .filter(|info| info.vendor_id() == SONY_VENDOR_ID) + .find_map(|info| { + TARGET_PRODUCTS + .iter() + .find(|(product_id, _, _)| *product_id == info.product_id()) + .copied() + }) + .ok_or_else(|| format!("対象デバイスが見つからない (VID {SONY_VENDOR_ID:#06X})"))?; + + let device = api + .open(SONY_VENDOR_ID, product_id) + .map_err(|err| format!("{name} を開けない: {err}"))?; + + Ok(Connection { + device, + _api: api, + kind, + name, + }) + } + + impl Connection { + /// 画面に出すデバイス名。 + pub fn name(&self) -> &'static str { + self.name + } + + /// レポートの構造を決めるデバイス種別。 + pub fn device(&self) -> Device { + self.kind + } + + /// レポートが 1 件届くまで待つ。 + pub fn read(&self) -> Result { + let mut buffer = [0u8; REPORT_BUFFER_LEN]; + let len = self + .device + .read_timeout(&mut buffer, BLOCK_UNTIL_REPORT) + .map_err(|err| format!("{} の読み取りに失敗した: {err}", self.name))?; + + Ok(Report { + buffer, + len, + host_qpc_us: host_qpc_us()?, + }) + } + } + + /// QPC を読んでµs に直す。i128 を経由して乗算の桁溢れを避ける。 + fn host_qpc_us() -> Result { + let frequency = qpc_frequency()?; + + let mut ticks = 0i64; + // SAFETY: ticks はスタック上の有効な i64。 + unsafe { QueryPerformanceCounter(&mut ticks) } + .map_err(|err| format!("QueryPerformanceCounter に失敗した: {err}"))?; + + Ok((i128::from(ticks) * 1_000_000 / i128::from(frequency)) as u64) + } + + fn qpc_frequency() -> Result { + if let Some(frequency) = QPC_FREQUENCY.get() { + return Ok(*frequency); + } + + let mut frequency = 0i64; + // SAFETY: frequency はスタック上の有効な i64。 + unsafe { QueryPerformanceFrequency(&mut frequency) } + .map_err(|err| format!("QueryPerformanceFrequency に失敗した: {err}"))?; + if frequency <= 0 { + return Err(format!("QPC の周波数が {frequency} で使えない")); + } + + Ok(*QPC_FREQUENCY.get_or_init(|| frequency)) + } +} + +#[cfg(not(windows))] +mod platform { + use super::Report; + use crate::report_decode::Device; + + /// Windows 以外では [`open`] が値を返さないので、この型の値は存在しない。 + pub enum Connection {} + + pub fn open() -> Result { + Err("HID の読み取りは Windows でのみ動く".to_owned()) + } + + impl Connection { + pub fn name(&self) -> &'static str { + match *self {} + } + + pub fn device(&self) -> Device { + match *self {} + } + + pub fn read(&self) -> Result { + match *self {} + } + } +} diff --git a/src/lib.rs b/src/lib.rs index dda32af..e9ed526 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,12 @@ //! DualSense / DualShock 4 の入力をフレーム単位で並べるための構成要素。 //! -//! OS と hidapi に接する層はバイナリ側に置き、ここには実機なしでテストできるものだけを入れる。 +//! データは `hid_source` → `report_decode` → `timeline` → `history` → `ui` の一方向に流れ、 +//! スレッドの配線だけを `app` が持つ。OS と hidapi に接するのは `hid_source` だけで、 +//! 他のモジュールは実機なしでテストできる。 +pub mod app; +pub mod hid_source; pub mod history; pub mod report_decode; pub mod timeline; +pub mod ui; diff --git a/src/main.rs b/src/main.rs index e7a11a9..a3f91b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,25 @@ -fn main() { - println!("Hello, world!"); +//! ウィンドウを開いて入力スレッドを起動する。 +//! +//! サブモニタに置いてゲームと並走させるため、always-on-top で開く。 + +use eframe::egui::ViewportBuilder; +use frametap::{app::FrametapApp, ui}; + +/// 初期のウィンドウサイズ。名前の列と F の 2 列が入り、試行が数件見える幅と高さ。 +const WINDOW_SIZE: [f32; 2] = [420.0, 640.0]; + +fn main() -> eframe::Result<()> { + let options = eframe::NativeOptions { + viewport: ViewportBuilder::default() + .with_title("frametap") + .with_always_on_top() + .with_inner_size(WINDOW_SIZE), + ..Default::default() + }; + + eframe::run_native( + "frametap", + options, + Box::new(|_cc| Ok(Box::new(FrametapApp::new(ui::Settings::default())))), + ) } diff --git a/src/timeline.rs b/src/timeline.rs index 67b02a1..c16fa0c 100644 --- a/src/timeline.rs +++ b/src/timeline.rs @@ -26,6 +26,20 @@ pub enum Target { Stick(Direction), } +impl Target { + /// 画面に出す名前。 + /// + /// 十字キーは `D`、左スティックは `L` を頭に付ける。同じ方向でもどちらで入れたかが + /// 読めないと、ずれの原因を切り分けられない。 + pub fn display_name(self) -> &'static str { + match self { + Target::Button(button) => button_name(button), + Target::Dpad(direction) => DPAD_NAMES[direction_index(direction)], + Target::Stick(direction) => STICK_NAMES[direction_index(direction)], + } + } +} + /// 押下または離しの 1 件。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct InputEvent { @@ -75,6 +89,33 @@ const STICK_DIRECTIONS: [Direction; 8] = [ Direction::SE, ]; +/// 十字キーの方向名。並びは [`direction_index`] に合わせる。 +const DPAD_NAMES: [&str; 8] = ["D↑", "D↗", "D→", "D↘", "D↓", "D↙", "D←", "D↖"]; + +/// 左スティックの方向名。並びは [`direction_index`] に合わせる。 +const STICK_NAMES: [&str; 8] = ["L↑", "L↗", "L→", "L↘", "L↓", "L↙", "L←", "L↖"]; + +/// ボタン名。単一ビットの値だけを引ける。 +const BUTTON_NAMES: [(Buttons, &str); 14] = [ + (Buttons::SQUARE, "Square"), + (Buttons::CROSS, "Cross"), + (Buttons::CIRCLE, "Circle"), + (Buttons::TRIANGLE, "Triangle"), + (Buttons::L1, "L1"), + (Buttons::R1, "R1"), + (Buttons::L2, "L2"), + (Buttons::R2, "R2"), + (Buttons::CREATE, "Create"), + (Buttons::OPTIONS, "Options"), + (Buttons::L3, "L3"), + (Buttons::R3, "R3"), + (Buttons::PS, "PS"), + (Buttons::TOUCHPAD, "Pad"), +]; + +/// 名前が引けなかったボタンに出す印。[`Target::Button`] に複数ビットの値を入れた場合に出る。 +const UNKNOWN_BUTTON_NAME: &str = "?"; + /// 方向を [`Target`] に包む構築子。十字キーとスティックで同じ差分処理を使うために型を揃える。 type DirectionTarget = fn(Direction) -> Target; @@ -323,6 +364,27 @@ impl Timeline { } } +/// 方向名の表を引く添字。 +fn direction_index(direction: Direction) -> usize { + match direction { + Direction::N => 0, + Direction::NE => 1, + Direction::E => 2, + Direction::SE => 3, + Direction::S => 4, + Direction::SW => 5, + Direction::W => 6, + Direction::NW => 7, + } +} + +fn button_name(button: Buttons) -> &'static str { + BUTTON_NAMES + .iter() + .find(|(flag, _)| *flag == button) + .map_or(UNKNOWN_BUTTON_NAME, |(_, name)| *name) +} + fn nominal_us_per_tick(device: Device) -> f64 { match device { Device::DualSense => DUALSENSE_NOMINAL_US_PER_TICK, @@ -974,4 +1036,34 @@ mod tests { assert_eq!(timeline.quantize_stick(raw, None), expected, "{raw:?}"); } } + + /// 表示名は入力ごとに違い、どの入力にも付いている。 + /// 十字キーとスティックが同じ名前になると、画面でどちらを入れたか読めない。 + #[test] + fn display_names_are_known_and_unique() { + let targets: Vec = STICK_DIRECTIONS + .iter() + .flat_map(|direction| [Target::Dpad(*direction), Target::Stick(*direction)]) + .chain(Buttons::all().iter().map(Target::Button)) + .collect(); + + for target in &targets { + assert_ne!(target.display_name(), UNKNOWN_BUTTON_NAME, "{target:?}"); + } + + let mut names: Vec<&str> = targets.iter().map(|target| target.display_name()).collect(); + let total = names.len(); + names.sort_unstable(); + names.dedup(); + assert_eq!(names.len(), total); + } + + /// 単一ビットでない値には名前が無い。 + #[test] + fn a_multi_bit_button_has_no_name() { + assert_eq!( + Target::Button(Buttons::L1 | Buttons::R1).display_name(), + UNKNOWN_BUTTON_NAME + ); + } } diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..a9f84b5 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,307 @@ +//! [`History`] の読み出し結果を egui で描く。 +//! +//! 直近の試行を 1 本の縦リストに並べる。古い試行が上、新しい行が下に増え、縦スクロールは +//! 底に追従する。プレイ中に横目で読むため、視線を動かす向きを一定にする。 +//! +//! このファイルは入力の取得も生バイトの復号も知らない。入力の表示名は +//! [`Target::display_name`] から受け取り、ボタンのビット表現をここに持ち込まない。 +//! +//! [`Target::display_name`]: crate::timeline::Target::display_name +//! +//! [`History`]: crate::history::History + +use egui::{Align, Color32, Frame, Layout, RichText, ScrollArea, Ui, Vec2}; + +use crate::history::{History, Trial}; + +/// F 換算の基準の既定値。対象は Dark Souls Remastered の 60fps。 +pub const DEFAULT_FPS: f64 = 60.0; + +/// 縦リストに積む試行数の既定値。 +pub const DEFAULT_TRIALS_SHOWN: usize = 5; + +/// 持続 F をそのまま出す上限。これを超えたら [`HOLD_OVERFLOW_TEXT`] にする。 +/// 押しっぱなしの行が桁を増やすと、右端の列幅が動いて他の行の数字が読みにくくなる。 +const HOLD_FRAMES_CAP: f64 = 99.0; + +/// 上限を超えた持続 F の代わりに出す文字。 +pub const HOLD_OVERFLOW_TEXT: &str = "99+"; + +/// 押下 F と持続 F を出さない行に置く文字。 +const BLANK_TEXT: &str = ""; + +/// USB 接続のレポート間隔。押下 F の量子化誤差はこの幅に収まる。 +const USB_REPORT_INTERVAL_US: f64 = 4_000.0; + +/// F の列の幅。3 桁と小数第 1 位が入る。 +const FRAMES_COLUMN_WIDTH: f32 = 52.0; + +/// 名前の欄の見出し。 +const NAMES_HEADER: &str = "入力"; + +/// 押下 F の欄の見出し。 +const PRESS_HEADER: &str = "押下F"; + +/// 持続 F の欄の見出し。 +const HOLD_HEADER: &str = "持続F"; + +/// 取りこぼしのあった行の背景。 +const GAP_FILL: Color32 = Color32::from_rgb(70, 70, 70); + +/// fps として受け付ける下限。0 を入れて 1F の長さが無限大になるのを防ぐ。 +const MIN_FPS: f64 = 1.0; + +/// 表示の設定。 +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Settings { + /// F 換算の基準。 + pub fps: f64, + /// 縦リストに積む試行数。 + pub trials_shown: usize, +} + +impl Default for Settings { + fn default() -> Self { + Self { + fps: DEFAULT_FPS, + trials_shown: DEFAULT_TRIALS_SHOWN, + } + } +} + +impl Settings { + /// 1F の長さ。 + pub fn frame_us(&self) -> f64 { + 1_000_000.0 / self.fps.max(MIN_FPS) + } +} + +/// 接続の状態。入力スレッドが書き、UI が読む。 +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Status { + /// 接続しているデバイスの表示名。未接続なら `None`。 + pub device_name: Option, + /// 未接続の理由。接続中は `None`。 + pub disconnected_reason: Option, + /// いま使っているデバイス時刻の単位。レポートを 1 件も受けていなければ `None`。 + pub scale_us_per_tick: Option, + /// 校正結果が公称単位から離れているときの警告文。 + pub scale_warning: Option, +} + +impl Status { + /// 接続を開いた直後の状態。 + /// + /// デバイス時刻の単位は接続ごとに校正し直す。前の接続の値を残すと、校正が終わる前の + /// 数秒間、いま使っていない単位を使っているように読める。[`Self::disconnected`] も同じ。 + pub fn connected(device_name: String) -> Self { + Self { + device_name: Some(device_name), + ..Self::default() + } + } + + /// 接続が切れた状態。 + pub fn disconnected(reason: String) -> Self { + Self { + disconnected_reason: Some(reason), + ..Self::default() + } + } +} + +/// 縦リストの 1 行。 +#[derive(Debug, Clone, PartialEq)] +pub struct Line { + /// この区間で押されている入力の名前。押した順に並ぶ。空の区間では空になる。 + pub names: Vec<&'static str>, + /// 試行の最初の押下からの F。空の区間では `None`。 + pub press_frames: Option, + /// この区間の長さ。 + pub hold_frames: f64, + /// 直前に取りこぼしがあったか。 + pub gap_before: bool, + /// この行が試行の先頭か。手前に区切り線を引く。 + pub starts_trial: bool, +} + +/// 新しい順に並んだ試行を、古い順の 1 本の行の列に畳む。 +/// +/// 押しっぱなしの間は [`Trial::rows`] が行を増やさず末尾の行だけが伸びるので、 +/// ここでも行数は変わらず持続 F だけが増える。 +pub fn lines(trials: &[Trial], settings: &Settings) -> Vec { + let frame_us = settings.frame_us(); + let mut lines = Vec::new(); + + for trial in trials.iter().rev() { + let mut starts_trial = true; + for row in trial.rows() { + lines.push(Line { + names: row + .held + .iter() + .map(|target| target.display_name()) + .collect(), + press_frames: (!row.held.is_empty()) + .then(|| row.at_us.saturating_sub(trial.origin_us) as f64 / frame_us), + hold_frames: row.end_us.saturating_sub(row.at_us) as f64 / frame_us, + gap_before: row.gap_before, + starts_trial, + }); + starts_trial = false; + } + } + + lines +} + +/// 表示に使う試行を [`History`] から読む。新しい順に最大 [`Settings::trials_shown`] 件。 +/// +/// 何件読むかは表示の設定なので、その判断をここに置く。呼び出し側は排他を短く握って +/// これを呼び、描画には排他の要らない [`Trial`] の複製を渡す。 +pub fn recent_trials(history: &History, settings: &Settings) -> Vec { + history.recent_trials(settings.trials_shown) +} + +/// ヘッダと縦リストを描く。`trials` は [`recent_trials`] の戻り値、 +/// つまり新しい順に並んだ試行である。 +pub fn show(ui: &mut Ui, trials: &[Trial], status: &Status, settings: &Settings) { + show_header(ui, status, settings); + ui.separator(); + + // 見出しはスクロール領域の外に置く。リストが流れても列の意味が画面から消えない。 + show_column_headers(ui); + ui.separator(); + + ScrollArea::vertical() + .stick_to_bottom(true) + .auto_shrink([false, false]) + .show(ui, |ui| { + for (index, line) in lines(trials, settings).iter().enumerate() { + // 試行の境界に線を引く。先頭の行の手前には引かない。 + if line.starts_trial && index > 0 { + ui.separator(); + } + show_line(ui, line); + } + }); +} + +/// 接続の状態と、F 数の読み方に必要な但し書きを出す。 +/// +/// 但し書きは条件付きで隠さない。1F ずれを読んでいる最中に、表示の幅が +/// どこから来ているかを思い出せる状態にしておく。 +fn show_header(ui: &mut Ui, status: &Status, settings: &Settings) { + match &status.device_name { + Some(name) => ui.label(format!("接続: {name}")), + None => ui.label( + RichText::new(match &status.disconnected_reason { + Some(reason) => format!("未接続: {reason}"), + None => "未接続".to_owned(), + }) + .color(Color32::LIGHT_RED), + ), + }; + + match status.scale_us_per_tick { + Some(scale) => ui.label(format!("デバイス時刻の単位: {scale:.4}µs/tick")), + None => ui.label("デバイス時刻の単位: 未取得"), + }; + + if let Some(warning) = &status.scale_warning { + ui.label(RichText::new(warning).color(Color32::LIGHT_RED)); + } + + let frame_us = settings.frame_us(); + ui.label(format!( + "USB のレポート間隔 4ms が量子化誤差になるため、F は ±{:.2}F の幅を持つ", + USB_REPORT_INTERVAL_US / frame_us + )); + ui.label(format!( + "{:.0}fps の独自グリッドで数えるので、ゲーム内のフレーム数とは最大 1F ずれる", + settings.fps.max(MIN_FPS) + )); +} + +/// どの欄が何かを示す見出し。行と同じ割り付けを使うので、列とずれない。 +fn show_column_headers(ui: &mut Ui) { + show_columns( + ui, + |ui| { + ui.label(header_text(NAMES_HEADER)); + }, + header_text(PRESS_HEADER), + header_text(HOLD_HEADER), + ); +} + +/// 見出しの文字。数字より小さく弱くして、行の数字から視線を奪わない。 +fn header_text(text: &str) -> RichText { + RichText::new(text).monospace().small().weak() +} + +fn show_line(ui: &mut Ui, line: &Line) { + let frame = if line.gap_before { + // 取りこぼした区間は時間差を信用できない。行ごと背景で潰して読み飛ばせるようにする。 + Frame::NONE.fill(GAP_FILL) + } else { + Frame::NONE + }; + + frame.show(ui, |ui| { + show_columns( + ui, + |ui| { + for name in &line.names { + ui.monospace(*name); + } + }, + RichText::new(press_text(line.press_frames)).monospace(), + RichText::new(hold_text(line.hold_frames)).monospace(), + ); + }); +} + +/// 名前の欄と F の 2 欄を割り付ける。 +fn show_columns(ui: &mut Ui, names: impl FnOnce(&mut Ui), press: RichText, hold: RichText) { + ui.horizontal(|ui| { + let row_height = ui.spacing().interact_size.y; + let names_width = (ui.available_width() - 2.0 * FRAMES_COLUMN_WIDTH).max(0.0); + + ui.allocate_ui_with_layout( + Vec2::new(names_width, row_height), + Layout::left_to_right(Align::Center), + names, + ); + + show_frames_column(ui, row_height, press); + show_frames_column(ui, row_height, hold); + }); +} + +fn show_frames_column(ui: &mut Ui, row_height: f32, text: RichText) { + ui.allocate_ui_with_layout( + Vec2::new(FRAMES_COLUMN_WIDTH, row_height), + Layout::right_to_left(Align::Center), + |ui| { + ui.label(text); + }, + ); +} + +/// 押下 F の表示。押されていない区間では空欄にする。 +pub fn press_text(frames: Option) -> String { + match frames { + Some(frames) => format!("{frames:.1}"), + None => BLANK_TEXT.to_owned(), + } +} + +/// 持続 F の表示。桁が増えると列幅が動くので、上限を超えたら丸める。 +pub fn hold_text(frames: f64) -> String { + if frames > HOLD_FRAMES_CAP { + HOLD_OVERFLOW_TEXT.to_owned() + } else { + format!("{frames:.1}") + } +} diff --git a/tests/ui.rs b/tests/ui.rs new file mode 100644 index 0000000..b8fc815 --- /dev/null +++ b/tests/ui.rs @@ -0,0 +1,235 @@ +//! [`frametap::ui`] の行の組み立てと表示文字列を、公開 API だけで確かめる。 +//! +//! 入力の組み立てには [`frametap::report_decode`] の値が要る。src/ui.rs をこの依存から +//! 切り離すため、テストをここに置く。 + +use frametap::history::{History, DEFAULT_RETAIN_US, DEFAULT_TRIAL_GAP_US}; +use frametap::report_decode::{Buttons, Direction}; +use frametap::timeline::{EventKind, InputEvent, Target}; +use frametap::ui::{ + hold_text, lines, press_text, recent_trials, show, Settings, Status, DEFAULT_TRIALS_SHOWN, + HOLD_OVERFLOW_TEXT, +}; + +/// 60fps の 1F。 +const FRAME_US: u64 = 16_667; + +fn history() -> History { + History::new(DEFAULT_RETAIN_US, DEFAULT_TRIAL_GAP_US) +} + +fn press(target: Target, at_us: u64) -> InputEvent { + InputEvent { + kind: EventKind::Press, + target, + at_us, + gap_before: false, + } +} + +fn release(target: Target, at_us: u64) -> InputEvent { + InputEvent { + kind: EventKind::Release, + ..press(target, at_us) + } +} + +/// 1F ずらしの `L1 + ↑` と `R1`。読み取りたい形をそのまま組む。 +fn wrong_warp_history() -> History { + let mut history = history(); + history.push( + &[ + press(Target::Button(Buttons::L1), 0), + press(Target::Dpad(Direction::N), 0), + ], + 0, + ); + history.push(&[press(Target::Button(Buttons::R1), FRAME_US)], FRAME_US); + history +} + +/// 押下 F は試行の起点からの差、持続 F は行の長さ。1F ずらしが 1.0 と読める。 +#[test] +fn frames_count_from_the_trial_origin() { + let mut history = wrong_warp_history(); + history.push(&[], 3 * FRAME_US); + + let lines = lines(&history.recent_trials(5), &Settings::default()); + assert_eq!(lines.len(), 2); + assert_eq!(lines[0].names, vec!["L1", "D↑"]); + assert_eq!(lines[0].press_frames, Some(0.0)); + assert_eq!(lines[1].names, vec!["L1", "D↑", "R1"]); + assert_eq!(press_text(lines[1].press_frames), "1.0"); + assert_eq!(hold_text(lines[1].hold_frames), "2.0"); +} + +/// 長押しの間は行が増えず、最後の行の持続 F だけが伸びる。 +#[test] +fn holding_grows_the_last_line_without_adding_lines() { + let mut history = wrong_warp_history(); + + let mut previous = Vec::new(); + for step in 2..=10u64 { + history.push(&[], step * FRAME_US); + let lines = lines(&history.recent_trials(5), &Settings::default()); + + assert_eq!(lines.len(), 2, "{step}F 目"); + if let Some(previous) = previous.first() { + // 増えるのは末尾の長さだけ。手前の行は動かない。 + assert_eq!(&lines[0], previous, "{step}F 目"); + } + // FRAME_US は 16666.7µs を丸めた値なので、表示に使う桁で比べる。 + assert_eq!( + hold_text(lines[1].hold_frames), + format!("{:.1}", (step - 1) as f64), + "{step}F 目" + ); + previous = lines; + } +} + +/// 試行は古い順に並び、先頭の行だけが区切りを持つ。 +#[test] +fn lines_run_oldest_first_and_mark_each_trial_head() { + let mut history = history(); + for index in 0..3u64 { + let at_us = index * 10 * DEFAULT_TRIAL_GAP_US; + history.push(&[press(Target::Button(Buttons::CROSS), at_us)], at_us); + history.push( + &[release(Target::Button(Buttons::CROSS), at_us + FRAME_US)], + at_us + FRAME_US, + ); + } + history.push(&[], 30 * DEFAULT_TRIAL_GAP_US); + + let trials = history.recent_trials(DEFAULT_TRIALS_SHOWN); + let lines = lines(&trials, &Settings::default()); + + // 各試行は押下の行と離した後の空の行を持つ。 + assert_eq!( + lines + .iter() + .map(|line| line.starts_trial) + .collect::>(), + vec![true, false, true, false, true, false], + ); + // 古い試行が先。試行内の押下 F は起点からの差なので、どの試行でも 0.0 から始まる。 + assert_eq!(lines[0].press_frames, Some(0.0)); + assert_eq!(lines[0].names, vec!["Cross"]); + // 空の区間は名前も押下 F も持たず、長さだけを出す。 + assert!(lines[1].names.is_empty()); + assert_eq!(press_text(lines[1].press_frames), ""); +} + +/// 試行数の上限は [`frametap::ui::recent_trials`] が [`Settings`] から読む。新しい方を残す。 +#[test] +fn trials_shown_caps_the_list() { + let mut history = history(); + for index in 0..4u64 { + let at_us = index * 10 * DEFAULT_TRIAL_GAP_US; + history.push(&[press(Target::Button(Buttons::CROSS), at_us)], at_us); + history.push( + &[release(Target::Button(Buttons::CROSS), at_us + FRAME_US)], + at_us + FRAME_US, + ); + } + history.push(&[], 40 * DEFAULT_TRIAL_GAP_US); + + let heads = |shown: usize| { + let settings = Settings { + trials_shown: shown, + ..Settings::default() + }; + lines(&recent_trials(&history, &settings), &settings) + .iter() + .filter(|line| line.starts_trial) + .count() + }; + assert_eq!(heads(4), 4); + assert_eq!(heads(2), 2); +} + +/// fps を変えると 1F の長さが変わる。30fps では同じ差が半分の F になる。 +#[test] +fn fps_changes_the_frame_length() { + let mut history = history(); + history.push(&[press(Target::Button(Buttons::L1), 0)], 0); + history.push( + &[press(Target::Button(Buttons::R1), 33_333)], + 33_333 + FRAME_US, + ); + + let frames = |fps: f64| { + let settings = Settings { + fps, + ..Settings::default() + }; + lines(&history.recent_trials(1), &settings)[1] + .press_frames + .expect("押下のある行") + }; + + assert_eq!(format!("{:.1}", frames(60.0)), "2.0"); + assert_eq!(format!("{:.1}", frames(30.0)), "1.0"); +} + +/// 取りこぼしの印は行に伝わる。 +#[test] +fn gap_before_reaches_the_line() { + let mut history = history(); + history.push(&[press(Target::Button(Buttons::L1), 0)], 0); + history.push( + &[InputEvent { + gap_before: true, + ..press(Target::Button(Buttons::R1), FRAME_US) + }], + FRAME_US, + ); + history.push(&[], 2 * FRAME_US); + + let lines = lines(&history.recent_trials(1), &Settings::default()); + assert!(!lines[0].gap_before); + assert!(lines[1].gap_before); +} + +/// 持続 F は 99.0 を超えたら丸める。境界の 99.0 はそのまま出す。 +#[test] +fn hold_text_caps_above_ninety_nine() { + assert_eq!(hold_text(0.0), "0.0"); + assert_eq!(hold_text(98.9), "98.9"); + assert_eq!(hold_text(99.0), "99.0"); + // 丸めた表示ではなく値で判定する。99.04 は "99.0" にならず丸めた印になる。 + assert_eq!(hold_text(99.04), HOLD_OVERFLOW_TEXT); +} + +/// 接続の状態は、その接続で校正した単位だけを持つ。前の接続の値は残さない。 +#[test] +fn a_new_connection_starts_without_a_scale() { + let connected = Status::connected("DualSense".to_owned()); + assert_eq!(connected.device_name.as_deref(), Some("DualSense")); + assert_eq!(connected.scale_us_per_tick, None); + assert_eq!(connected.scale_warning, None); + + let disconnected = Status::disconnected("読みが止まった".to_owned()); + assert_eq!(disconnected.device_name, None); + assert_eq!(disconnected.scale_us_per_tick, None); + assert_eq!(disconnected.scale_warning, None); +} + +/// 描画がウィンドウ無しで最後まで通る。列の確保と幅の計算が壊れていれば panic する。 +#[test] +fn show_runs_without_a_window() { + let mut history = wrong_warp_history(); + history.push(&[], 5 * FRAME_US); + let trials = history.recent_trials(DEFAULT_TRIALS_SHOWN); + + let connected = Status { + scale_us_per_tick: Some(0.333), + scale_warning: Some("単位が公称値から外れている".to_owned()), + ..Status::connected("DualSense".to_owned()) + }; + + for status in [connected, Status::default()] { + egui::__run_test_ui(|ui| show(ui, &trials, &status, &Settings::default())); + } +}