diff --git a/.cargo/config.toml b/.cargo/config.toml index 04c7f4c3..390e90d4 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,4 +1,5 @@ [target.'cfg(all())'] +# TODO - Replace with Cargo.toml config rustflags = [ # Global lints/warnings. # We do this here instead of in the crate root because we want to apply diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9d480ef..711d2482 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,77 +58,14 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --all-targets --features=x11 --no-default-features + args: --all-targets --no-default-features # We use --all-targets to skip doc tests; we run them in a parallel task - name: cargo test uses: actions-rs/cargo@v1 with: command: test - args: --all-targets --no-default-features --features=svg,image,x11 - - # we test the gtk backend as a separate job because gtk install takes - # a long time. - test-stable-gtk: - runs-on: ubuntu-latest - name: cargo test (gtk) - steps: - - uses: actions/checkout@v2 - - - name: install libgtk-3-dev - run: | - sudo apt update - sudo apt install libgtk-3-dev - - - name: install stable toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - - name: restore cache - uses: Swatinem/rust-cache@v2 - - # We use --all-targets to skip doc tests; there are no gtk-specific - # doctests in masonry anyway - - name: cargo test - uses: actions-rs/cargo@v1 - with: - command: test - args: --all-targets --features=svg,image - - test-stable-wasm: - runs-on: macOS-latest - name: cargo test (wasm32) - steps: - - uses: actions/checkout@v2 - - - name: install wasm-pack - uses: jetli/wasm-pack-action@v0.3.0 - with: - version: latest - - - name: install stable toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-unknown - components: clippy - profile: minimal - override: true - - - name: restore cache - uses: Swatinem/rust-cache@v2 - - # We use --all-targets to skip doc tests; there are no wasm-specific - # doctests in masonry anyway - - name: cargo test - uses: actions-rs/cargo@v1 - with: - command: test - # TODO: Add svg feature when it's no longer broken with wasm - args: --all-targets --features=image --no-run --target wasm32-unknown-unknown + args: --all-targets --no-default-features doctest-stable: runs-on: macOS-latest @@ -150,7 +87,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --doc --no-default-features --features=svg,image + args: --doc --no-default-features # This tests the future rust compiler to catch errors ahead of time without # breaking CI @@ -178,12 +115,12 @@ jobs: uses: actions-rs/cargo@v1 with: command: check - args: --no-default-features --features=x11 + args: --no-default-features continue-on-error: true - name: cargo clippy uses: actions-rs/cargo@v1 with: command: clippy - args: --all-targets --no-default-features --features=x11,svg,image + args: --all-targets --no-default-features continue-on-error: true diff --git a/Cargo.lock b/Cargo.lock index de748e1c..3452d9c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,22 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ab_glyph" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f90148830dac590fac7ccfe78ec4a8ea404c60f75a24e16407a71f0f40de775" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + [[package]] name = "adler" version = "1.0.2" @@ -9,14 +25,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "aho-corasick" -version = "0.7.19" +name = "ahash" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ - "memchr", + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "android-activity" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" +dependencies = [ + "android-properties", + "bitflags 2.5.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror", ] +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -36,199 +89,124 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" - -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[package]] -name = "ashpd" -version = "0.3.2" +name = "anstream" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dcc8ed0b5211687437636d8c95f6a608f4281d142101b3b5d314b38bfadd40f" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ - "enumflags2", - "futures", - "rand 0.8.5", - "serde", - "serde_repr", - "zbus", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", ] [[package]] -name = "assert_matches" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" - -[[package]] -name = "associative-cache" -version = "1.0.1" +name = "anstyle" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46016233fc1bb55c23b856fe556b7db6ccd05119a0a392e04f0b3b7c79058f16" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] -name = "async-broadcast" -version = "0.4.1" +name = "anstyle-parse" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d26004fe83b2d1cd3a97609b21e39f9a31535822210fe83205d2ce48866ea61" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ - "event-listener", - "futures-core", - "parking_lot", + "utf8parse", ] [[package]] -name = "async-channel" -version = "1.7.1" +name = "anstyle-query" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", + "windows-sys 0.52.0", ] [[package]] -name = "async-executor" -version = "1.4.1" +name = "anstyle-wincon" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "once_cell", - "slab", + "anstyle", + "windows-sys 0.52.0", ] [[package]] -name = "async-io" -version = "1.9.0" +name = "anyhow" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7" -dependencies = [ - "autocfg", - "concurrent-queue", - "futures-lite", - "libc", - "log", - "once_cell", - "parking", - "polling", - "slab", - "socket2", - "waker-fn", - "winapi", -] +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] -name = "async-lock" -version = "2.5.0" +name = "arrayref" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" -dependencies = [ - "event-listener", -] +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] -name = "async-recursion" -version = "0.3.2" +name = "arrayvec" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] -name = "async-task" -version = "4.3.0" +name = "as-raw-xcb-connection" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" [[package]] -name = "async-trait" -version = "0.1.57" +name = "ash" +version = "0.37.3+1.3.251" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" dependencies = [ - "proc-macro2", - "quote", - "syn", + "libloading 0.7.4", ] [[package]] -name = "atk" -version = "0.16.0" +name = "assert_matches" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39991bc421ddf72f70159011b323ff49b0f783cc676a7287c59453da2e2531cf" -dependencies = [ - "atk-sys", - "bitflags", - "glib", - "libc", -] +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] -name = "atk-sys" -version = "0.16.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ad703eb64dc058024f0e57ccfa069e15a413b98dbd50a1a950e743b7f11148" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] -name = "base64" -version = "0.13.0" +name = "bit-set" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] [[package]] -name = "bindgen" -version = "0.61.0" +name = "bit-vec" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a022e58a142a46fea340d68012b9201c094e93ec3d033a944a24f8fd4a4f09a" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn", - "which", -] +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bit_field" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" [[package]] name = "bitflags" @@ -237,13 +215,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "bitmaps" -version = "2.1.0" +name = "bitflags" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -dependencies = [ - "typenum", -] +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "block" @@ -251,174 +226,239 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "block2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" +dependencies = [ + "block-sys", + "objc2", +] + [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.12.1" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "cache-padded" -version = "1.2.0" +name = "bytes" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] -name = "cairo-rs" -version = "0.16.7" +name = "calloop" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3125b15ec28b84c238f6f476c6034016a5f6cc0221cb514ca46c532139fc97d" +checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ - "bitflags", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", + "bitflags 2.5.0", + "log", + "polling", + "rustix", + "slab", "thiserror", ] [[package]] -name = "cairo-sys-rs" -version = "0.16.3" +name = "calloop-wayland-source" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +dependencies = [ + "calloop", + "rustix", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c48f4af05fabdcfa9658178e1326efa061853f040ce7d72e33af6885196f421" +checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" dependencies = [ - "glib-sys", + "jobserver", "libc", - "system-deps", ] [[package]] -name = "cc" -version = "1.0.73" +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] -name = "cexpr" -version = "0.6.0" +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ - "nom", + "clap_builder", + "clap_derive", ] [[package]] -name = "cfg-expr" -version = "0.11.0" +name = "clap_builder" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ - "smallvec", + "anstream", + "anstyle", + "clap_lex", + "strsim", ] [[package]] -name = "cfg-if" -version = "0.1.10" +name = "clap_derive" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.60", +] [[package]] -name = "cfg-if" -version = "1.0.0" +name = "clap_lex" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] -name = "chrono" -version = "0.4.22" +name = "codespan-reporting" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ - "iana-time-zone", - "js-sys", - "num-integer", - "num-traits", - "time 0.1.44", - "wasm-bindgen", - "winapi", + "termcolor", + "unicode-width", ] [[package]] -name = "clang-sys" -version = "1.4.0" +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "com" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" dependencies = [ - "glob", - "libc", - "libloading", + "com_macros", ] [[package]] -name = "cocoa" -version = "0.24.1" +name = "com_macros" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" dependencies = [ - "bitflags", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", + "com_macros_support", + "proc-macro2", + "syn 1.0.109", ] [[package]] -name = "cocoa-foundation" -version = "0.1.0" +name = "com_macros_support" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" dependencies = [ - "bitflags", - "block", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", - "objc", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "color_quant" -version = "1.1.0" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] [[package]] name = "concurrent-queue" -version = "1.2.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" dependencies = [ - "cache-padded", + "crossbeam-utils", ] [[package]] name = "console" -version = "0.15.2" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", - "terminal_size", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -427,15 +467,15 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen", ] [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -443,17 +483,17 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics" -version = "0.22.3" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-graphics-types", "foreign-types", @@ -462,21 +502,20 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", - "foreign-types", "libc", ] [[package]] name = "core-text" -version = "19.2.0" +version = "20.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" dependencies = [ "core-foundation", "core-graphics", @@ -485,138 +524,102 @@ dependencies = [ ] [[package]] -name = "crc32fast" -version = "1.3.2" +name = "core_maths" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" dependencies = [ - "cfg-if 1.0.0", + "libm", ] [[package]] -name = "crossbeam-channel" -version = "0.5.6" +name = "crc32fast" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils", + "cfg-if", ] [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if 1.0.0", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.11" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if 1.0.0", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" -dependencies = [ - "cfg-if 1.0.0", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] -name = "data-url" -version = "0.1.1" +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + +[[package]] +name = "d3d12" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" +checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" dependencies = [ - "matches", + "bitflags 2.5.0", + "libloading 0.8.3", + "winapi", ] [[package]] -name = "derivative" -version = "2.2.0" +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "displaydoc" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.60", ] [[package]] -name = "dirs" -version = "4.0.0" +name = "dlib" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "dirs-sys", + "libloading 0.8.3", ] [[package]] -name = "dirs-sys" -version = "0.3.7" +name = "downcast-rs" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "druid-shell" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a880d53c2651b4bd5527938c72b81114db5dea8ab8f978173f9ffda26dde066" -dependencies = [ - "anyhow", - "ashpd", - "bindgen", - "bitflags", - "block", - "cairo-rs", - "cairo-sys-rs", - "cfg-if 1.0.0", - "cocoa", - "core-graphics", - "foreign-types", - "futures", - "gdk-sys", - "glib-sys", - "gtk", - "gtk-sys", - "image 0.23.14", - "instant", - "js-sys", - "keyboard-types", - "kurbo 0.9.0", - "nix 0.24.3", - "objc", - "once_cell", - "piet-common", - "pkg-config", - "scopeguard", - "time 0.3.17", - "tracing", - "wasm-bindgen", - "web-sys", - "winapi", - "wio", - "x11rb", -] +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dwrote" @@ -626,15 +629,17 @@ checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" dependencies = [ "lazy_static", "libc", + "serde", + "serde_derive", "winapi", "wio", ] [[package]] name = "either" -version = "1.8.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "encode_unicode" @@ -643,132 +648,83 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] -name = "enumflags2" -version = "0.7.5" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" -dependencies = [ - "enumflags2_derive", - "serde", -] +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "enumflags2_derive" -version = "0.7.4" +name = "errno" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "proc-macro2", - "quote", - "syn", + "libc", + "windows-sys 0.52.0", ] [[package]] -name = "event-listener" -version = "2.5.3" +name = "euclid" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" +dependencies = [ + "num-traits", +] [[package]] name = "exr" -version = "1.5.1" +version = "1.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a7880199e74c6d3fe45579df2f436c5913a71405494cb89d59234d86b47dc5" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" dependencies = [ "bit_field", "flume", "half", "lebe", - "miniz_oxide 0.5.4", + "miniz_oxide", + "rayon-core", "smallvec", - "threadpool", + "zune-inflate", ] [[package]] name = "fastrand" -version = "1.8.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" -dependencies = [ - "instant", -] +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] -name = "field-offset" +name = "fdeflate" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" dependencies = [ - "memoffset", - "rustc_version", + "simd-adler32", ] [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", - "miniz_oxide 0.5.4", + "miniz_oxide", ] -[[package]] -name = "float-cmp" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e" - [[package]] name = "float-cmp" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" -[[package]] -name = "fluent-bundle" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd" -dependencies = [ - "fluent-langneg", - "fluent-syntax", - "intl-memoizer", - "intl_pluralrules", - "rustc-hash", - "self_cell", - "smallvec", - "unic-langid", -] - -[[package]] -name = "fluent-langneg" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" -dependencies = [ - "unic-langid", -] - -[[package]] -name = "fluent-syntax" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78" -dependencies = [ - "thiserror", -] - [[package]] name = "flume" -version = "0.10.14" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "pin-project", "spin", ] @@ -779,464 +735,358 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "fontdb" -version = "0.5.4" +name = "font-types" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58903f4f8d5b58c7d300908e4ebe5289c1bfdf5587964330f12023b8ff17fd1" +checksum = "bd6784a76a9c2b136ea3b8462391e9328252e938eb706eb44d752723b4c3a533" dependencies = [ - "log", - "memmap2", - "ttf-parser 0.12.3", + "bytemuck", ] [[package]] -name = "foreign-types" -version = "0.3.2" +name = "fontconfig-cache-parser" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "db0413877146e4ea3a9f56c12917a1754418b518ac7a6982838eabafa8a92260" dependencies = [ - "foreign-types-shared", + "anyhow", + "bytemuck", + "clap", + "thiserror", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "foreign-types" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] [[package]] -name = "futures" -version = "0.3.26" +name = "foreign-types-macros" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", + "proc-macro2", + "quote", + "syn 2.0.60", ] [[package]] -name = "futures-channel" -version = "0.3.26" +name = "foreign-types-shared" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" -dependencies = [ - "futures-core", - "futures-sink", -] +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] -name = "futures-executor" -version = "0.3.26" +name = "futures-intrusive" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", - "futures-task", - "futures-util", + "lock_api", + "parking_lot", ] [[package]] -name = "futures-io" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" - -[[package]] -name = "futures-lite" -version = "1.12.0" +name = "gethostname" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", + "libc", + "windows-targets 0.48.5", ] [[package]] -name = "futures-macro" -version = "0.3.26" +name = "getrandom" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ - "proc-macro2", - "quote", - "syn", + "cfg-if", + "libc", + "wasi", ] [[package]] -name = "futures-sink" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" - -[[package]] -name = "futures-task" -version = "0.3.26" +name = "gif" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] [[package]] -name = "futures-util" -version = "0.3.26" +name = "gl_generator" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", + "khronos_api", + "log", + "xml-rs", ] [[package]] -name = "gdk" -version = "0.16.2" +name = "glow" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9cb33da481c6c040404a11f8212d193889e9b435db2c14fd86987f630d3ce1" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" dependencies = [ - "bitflags", - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "gdk-pixbuf" -version = "0.16.7" +name = "glutin_wgl_sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3578c60dee9d029ad86593ed88cb40f35c1b83360e12498d055022385dd9a05" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" dependencies = [ - "bitflags", - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", + "gl_generator", ] [[package]] -name = "gdk-pixbuf-sys" -version = "0.16.3" +name = "gpu-alloc" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3092cf797a5f1210479ea38070d9ae8a5b8e9f8f1be9f32f4643c529c7d70016" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "bitflags 2.5.0", + "gpu-alloc-types", ] [[package]] -name = "gdk-sys" -version = "0.16.0" +name = "gpu-alloc-types" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76354f97a913e55b984759a997b693aa7dc71068c9e98bcce51aa167a0a5c5a" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", + "bitflags 2.5.0", ] [[package]] -name = "gethostname" -version = "0.2.3" +name = "gpu-allocator" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" dependencies = [ - "libc", + "log", + "presser", + "thiserror", "winapi", + "windows", ] [[package]] -name = "getrandom" -version = "0.1.16" +name = "gpu-descriptor" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "bitflags 2.5.0", + "gpu-descriptor-types", + "hashbrown", ] [[package]] -name = "getrandom" -version = "0.2.7" +name = "gpu-descriptor-types" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", + "bitflags 2.5.0", ] [[package]] -name = "gif" -version = "0.11.4" +name = "guillotiere" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" dependencies = [ - "color_quant", - "weezl", + "euclid", + "svg_fmt", ] [[package]] -name = "gio" -version = "0.16.7" +name = "half" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1c84b4534a290a29160ef5c6eff2a9c95833111472e824fc5cb78b513dd092" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ - "bitflags", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror", + "cfg-if", + "crunchy", ] [[package]] -name = "gio-sys" -version = "0.16.3" +name = "hashbrown" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", + "ahash", + "allocator-api2", ] [[package]] -name = "glib" -version = "0.16.7" +name = "hassle-rs" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd4df61a866ed7259d6189b8bcb1464989a77f1d85d25d002279bbe9dd38b2f" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", + "bitflags 2.5.0", + "com", "libc", - "once_cell", - "smallvec", + "libloading 0.8.3", "thiserror", + "widestring", + "winapi", ] [[package]] -name = "glib-macros" -version = "0.16.3" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e084807350b01348b6d9dbabb724d1a0bb987f47a2c85de200e98e12e30733bf" -dependencies = [ - "anyhow", - "heck", - "proc-macro-crate", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "glib-sys" -version = "0.16.3" +name = "hermit-abi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65" -dependencies = [ - "libc", - "system-deps", -] +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] -name = "glob" -version = "0.3.0" +name = "hexf-parse" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] -name = "gobject-sys" -version = "0.16.3" +name = "icrate" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1" +checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" dependencies = [ - "glib-sys", - "libc", - "system-deps", + "block2", + "dispatch", + "objc2", ] [[package]] -name = "gtk" -version = "0.16.2" +name = "icu_collections" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4d3507d43908c866c805f74c9dd593c0ce7ba5c38e576e41846639cdcd4bee6" +checksum = "137d96353afc8544d437e8a99eceb10ab291352699573b0de5b08bda38c78c60" dependencies = [ - "atk", - "bitflags", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "once_cell", - "pango", - "pkg-config", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "gtk-sys" -version = "0.16.0" +name = "icu_locid" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b5f8946685d5fe44497007786600c2f368ff6b1e61a16251c89f72a97520a3" +checksum = "5c0aa2536adc14c07e2a521e95512b75ed8ef832f0fdf9299d4a0a45d2be2a9d" dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "gtk3-macros" -version = "0.16.0" +name = "icu_locid_transform" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfd6557b1018b773e43c8de9d0d13581d6b36190d0501916cbec4731db5ccff" +checksum = "57c17d8f6524fdca4471101dd71f0a132eb6382b5d6d7f2970441cb25f6f435a" dependencies = [ - "anyhow", - "proc-macro-crate", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - -[[package]] -name = "heck" -version = "0.4.1" +name = "icu_locid_transform_data" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "545c6c3e8bf9580e2dafee8de6f9ec14826aaf359787789c7724f1f85f47d3dc" [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "icu_properties" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "976e296217453af983efa25f287a4c1da04b9a63bf1ed63719455068e4453eb5" dependencies = [ - "libc", + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "iana-time-zone" -version = "0.1.50" +name = "icu_properties_data" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "js-sys", - "wasm-bindgen", - "winapi", -] +checksum = "f6a86c0e384532b06b6c104814f9c1b13bcd5b64409001c0d05713a1f3529d99" [[package]] -name = "im" -version = "15.1.0" +name = "icu_provider" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +checksum = "ba58e782287eb6950247abbf11719f83f5d4e4a5c1f2cd490d30a334bc47c2f4" dependencies = [ - "bitmaps", - "rand_core 0.6.4", - "rand_xoshiro", - "serde", - "sized-chunks", - "typenum", - "version_check", + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "image" -version = "0.23.14" +name = "icu_provider_macros" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +checksum = "d2abdd3a62551e8337af119c5899e600ca0c88ec8f23a46c60ba216c803dcf1a" dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "num-iter", - "num-rational 0.3.2", - "num-traits", + "proc-macro2", + "quote", + "syn 2.0.60", ] [[package]] name = "image" -version = "0.24.5" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" dependencies = [ "bytemuck", "byteorder", @@ -1244,25 +1094,32 @@ dependencies = [ "exr", "gif", "jpeg-decoder", - "num-rational 0.4.1", "num-traits", "png", - "scoped_threadpool", + "qoi", "tiff", ] +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "insta" -version = "1.18.2" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a9aec10c9a062ef0454fd49ebaefa59239f836d1b30891d9cc2289978dd970" +checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc" dependencies = [ "console", + "lazy_static", "linked-hash-map", - "once_cell", - "serde", "similar", - "yaml-rust", ] [[package]] @@ -1271,82 +1128,93 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] -name = "intl-memoizer" -version = "0.5.1" +name = "itoa" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f" -dependencies = [ - "type-map", - "unic-langid", -] +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "intl_pluralrules" -version = "7.0.1" +name = "jni" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b18f988384267d7066cc2be425e6faf352900652c046b6971d2e228d3b1c5ecf" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ - "tinystr", - "unic-langid", + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", ] [[package]] -name = "itoa" -version = "1.0.3" +name = "jni-sys" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] -name = "jpeg-decoder" -version = "0.3.0" +name = "jobserver" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2" dependencies = [ - "rayon", + "libc", ] [[package]] -name = "js-sys" -version = "0.3.60" +name = "jpeg-decoder" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" dependencies = [ - "wasm-bindgen", + "rayon", ] [[package]] -name = "keyboard-types" -version = "0.6.2" +name = "js-sys" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7668b7cff6a51fe61cdde64cd27c8a220786f399501b57ebe36f7d8112fd68" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ - "bitflags", + "wasm-bindgen", ] [[package]] -name = "kurbo" -version = "0.8.3" +name = "khronos-egl" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ - "arrayvec", + "libc", + "libloading 0.8.3", + "pkg-config", ] +[[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.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e119590a03caff1f7a582e8ee8c2164ddcc975791701188132fd1d1b518d3871" +checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" dependencies = [ "arrayvec", - "serde", + "libm", + "smallvec", ] [[package]] @@ -1355,12 +1223,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "lebe" version = "0.5.2" @@ -1369,31 +1231,70 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.134" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "winapi", ] +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +dependencies = [ + "bitflags 2.5.0", + "libc", + "redox_syscall 0.4.1", +] + [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "litemap" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d642685b028806386b2b6e75685faadd3eb65a85fff7df711ce18446a422da" + [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -1401,12 +1302,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if 1.0.0", -] +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "malloc_buf" @@ -1422,209 +1320,207 @@ name = "masonry" version = "0.1.3" dependencies = [ "assert_matches", - "chrono", "console_error_panic_hook", - "druid-shell", - "float-cmp 0.8.0", - "fluent-bundle", - "fluent-langneg", - "fluent-syntax", + "float-cmp", "fnv", - "im", - "image 0.24.5", + "futures-intrusive", + "image", "insta", "instant", + "kurbo", "once_cell", "open", - "piet-common", + "parley", + "pollster", "pulldown-cmark", "serde", "serde_json", "smallvec", + "swash", "tempfile", "tracing", "tracing-subscriber 0.2.25", "tracing-wasm", - "unic-langid", - "unicode-segmentation", - "usvg", - "xi-unicode", + "vello", + "wgpu", + "winit", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memmap2" -version = "0.2.3" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" dependencies = [ "libc", ] [[package]] -name = "memoffset" -version = "0.6.5" +name = "memmap2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" dependencies = [ - "autocfg", + "libc", ] [[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.5.4" +name = "metal" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" dependencies = [ - "adler", + "bitflags 2.5.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", ] [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", + "simd-adler32", ] [[package]] -name = "nanorand" -version = "0.7.0" +name = "naga" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" dependencies = [ - "getrandom 0.2.7", + "bit-set", + "bitflags 2.5.0", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", ] [[package]] -name = "nix" -version = "0.23.1" +name = "ndk" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", - "memoffset", + "bitflags 2.5.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror", ] [[package]] -name = "nix" -version = "0.24.3" +name = "ndk-context" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags", - "cfg-if 1.0.0", - "libc", - "memoffset", -] +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] -name = "nom" -version = "7.1.3" +name = "ndk-sys" +version = "0.5.0+25.2.9519653" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" dependencies = [ - "memchr", - "minimal-lexical", + "jni-sys", ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "num-traits" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", - "num-traits", ] [[package]] -name = "num-iter" -version = "0.1.43" +name = "num_enum" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "num_enum_derive", ] [[package]] -name = "num-rational" -version = "0.3.2" +name = "num_enum_derive" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.60", ] [[package]] -name = "num-rational" -version = "0.4.1" +name = "objc" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "malloc_buf", + "objc_exception", ] [[package]] -name = "num-traits" -version = "0.2.15" +name = "objc-sys" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] +checksum = "da284c198fb9b7b0603f8635185e85fbd5b64ee154b1ed406d489077de2d6d60" [[package]] -name = "num_cpus" -version = "1.13.1" +name = "objc2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" dependencies = [ - "hermit-abi", - "libc", + "objc-sys", + "objc2-encode", ] [[package]] -name = "objc" -version = "0.2.7" +name = "objc2-encode" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" dependencies = [ - "malloc_buf", + "cc", ] [[package]] name = "once_cell" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "open" @@ -1637,1356 +1533,1465 @@ dependencies = [ ] [[package]] -name = "ordered-stream" -version = "0.0.1" +name = "orbclient" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44630c059eacfd6e08bdaa51b1db2ce33119caa4ddc1235e923109aa5f25ccb1" +checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" dependencies = [ - "futures-core", - "pin-project-lite", + "libredox", ] [[package]] -name = "pango" -version = "0.16.5" +name = "owned_ttf_parser" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94" +checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7" dependencies = [ - "bitflags", - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", + "ttf-parser", ] [[package]] -name = "pango-sys" -version = "0.16.3" +name = "parking_lot" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e134909a9a293e04d2cc31928aa95679c5e4df954d0b85483159bd20d8f047f" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "lock_api", + "parking_lot_core", ] [[package]] -name = "pangocairo" -version = "0.16.3" +name = "parking_lot_core" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ad2ec87789371b551fd2367c10aa37060412ffd3e60abd99491b21b93a3f9b" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ - "bitflags", - "cairo-rs", - "glib", + "cfg-if", "libc", - "pango", - "pangocairo-sys", + "redox_syscall 0.4.1", + "smallvec", + "windows-targets 0.48.5", ] [[package]] -name = "pangocairo-sys" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "848d2df9b7f1a8c7a19d994de443bcbe5d4382610ccb8e64247f932be74fcf76" +name = "parley" +version = "0.0.1" +source = "git+https://github.com/linebender/parley?rev=4f05e183be9b388c6748d3c531c9ac332672fb86#4f05e183be9b388c6748d3c531c9ac332672fb86" dependencies = [ - "cairo-sys-rs", - "glib-sys", - "libc", - "pango-sys", - "system-deps", + "anyhow", + "bytemuck", + "core-foundation", + "core-foundation-sys", + "core-text", + "dwrote", + "fontconfig-cache-parser", + "hashbrown", + "icu_locid", + "icu_properties", + "memmap2 0.5.10", + "peniko", + "roxmltree", + "skrifa", + "smallvec", + "swash", + "thiserror", + "winapi", + "wio", ] [[package]] -name = "parking" -version = "2.0.0" +name = "paste" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] -name = "parking_lot" -version = "0.12.1" +name = "pathdiff" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] -name = "parking_lot_core" -version = "0.9.3" +name = "peniko" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "caaf7fec601d640555d9a4cab7343eba1e1c7a5a71c9993ff63b4c26bc5d50c5" dependencies = [ - "cfg-if 1.0.0", - "libc", - "redox_syscall 0.2.16", + "kurbo", "smallvec", - "windows-sys", ] [[package]] -name = "pathdiff" -version = "0.2.1" +name = "percent-encoding" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "peeking_take_while" -version = "0.1.2" +name = "pin-project-lite" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] -name = "pest" -version = "2.3.1" +name = "pkg-config" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048" -dependencies = [ - "thiserror", - "ucd-trie", -] +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] -name = "pico-args" -version = "0.4.2" +name = "png" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] [[package]] -name = "piet" -version = "0.6.2" +name = "polling" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e381186490a3e2017a506d62b759ea8eaf4be14666b13ed53973e8ae193451b1" +checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" dependencies = [ - "image 0.24.5", - "kurbo 0.9.0", - "unic-bidi", + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.52.0", ] [[package]] -name = "piet-cairo" -version = "0.6.2" +name = "pollster" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc0b38ac300c79deb9bfc8c7f91a08f2b080338648f7202981094b22321bb9" -dependencies = [ - "cairo-rs", - "pango", - "pangocairo", - "piet", - "unicode-segmentation", - "xi-unicode", -] +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" [[package]] -name = "piet-common" -version = "0.6.2" +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd8497cc0bcfecb1e14e027428c5e3eaf9af6e14761176e1212006d8bdba387" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "cairo-rs", - "cairo-sys-rs", - "cfg-if 1.0.0", - "core-graphics", - "piet", - "piet-cairo", - "piet-coregraphics", - "piet-direct2d", - "piet-web", - "wasm-bindgen", - "web-sys", + "toml_edit", ] [[package]] -name = "piet-coregraphics" -version = "0.6.2" +name = "proc-macro2" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a819b41d2ddb1d8abf3e45e49422f866cba281b4abb5e2fb948bba06e2c3d3f7" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ - "associative-cache", - "core-foundation", - "core-foundation-sys", - "core-graphics", - "core-text", - "foreign-types", - "piet", + "unicode-ident", ] [[package]] -name = "piet-direct2d" -version = "0.6.2" +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" + +[[package]] +name = "pulldown-cmark" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd00e91df4f987be40eb13042afe6ee9e54468466bdb7486390b40d4fef0993e" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" dependencies = [ - "associative-cache", - "dwrote", - "piet", - "utf16_lit", - "winapi", - "wio", + "bitflags 1.3.2", + "memchr", + "unicase", ] [[package]] -name = "piet-web" -version = "0.6.2" +name = "qoi" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a560232a94e535979923d49062d1c6d5407b3804bcd0d0b4cb9e25a9b41db1e" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" dependencies = [ - "js-sys", - "piet", - "unicode-segmentation", - "wasm-bindgen", - "web-sys", - "xi-unicode", + "bytemuck", ] [[package]] -name = "pin-project" -version = "1.0.12" +name = "quick-xml" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ - "pin-project-internal", + "memchr", ] [[package]] -name = "pin-project-internal" -version = "1.0.12" +name = "quote" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", - "quote", - "syn", ] [[package]] -name = "pin-project-lite" -version = "0.2.9" +name = "range-alloc" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "raw-window-handle" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" [[package]] -name = "pkg-config" -version = "0.3.26" +name = "rayon" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] [[package]] -name = "png" -version = "0.17.7" +name = "rayon-core" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "bitflags", - "crc32fast", - "flate2", - "miniz_oxide 0.6.2", + "crossbeam-deque", + "crossbeam-utils", ] [[package]] -name = "polling" -version = "2.3.0" +name = "read-fonts" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011" +checksum = "ea75b5ec052843434d263ef7a4c31cf86db5908c729694afb1ad3c884252a1b6" dependencies = [ - "autocfg", - "cfg-if 1.0.0", - "libc", - "log", - "wepoll-ffi", - "winapi", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "proc-macro-crate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" -dependencies = [ - "once_cell", - "thiserror", - "toml", + "bytemuck", + "font-types", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "redox_syscall" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", + "bitflags 1.3.2", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "redox_syscall" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "bitflags 1.3.2", ] [[package]] -name = "proc-macro2" -version = "1.0.46" +name = "renderdoc-sys" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" -dependencies = [ - "unicode-ident", -] +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] -name = "pulldown-cmark" -version = "0.8.0" +name = "roxmltree" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +checksum = "862340e351ce1b271a378ec53f304a5558f7db87f3769dc655a8f6ecbb68b302" dependencies = [ - "bitflags", - "memchr", - "unicase", + "xmlparser", ] [[package]] -name = "quote" -version = "1.0.21" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" -dependencies = [ - "proc-macro2", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] -name = "rand" -version = "0.7.3" +name = "rustix" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "getrandom 0.1.16", + "bitflags 2.5.0", + "errno", "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", + "linux-raw-sys", + "windows-sys 0.52.0", ] [[package]] -name = "rand" -version = "0.8.5" +name = "ryu" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] -name = "rand_chacha" -version = "0.2.2" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "winapi-util", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "scoped-tls" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] -name = "rand_core" -version = "0.5.1" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "rand_core" -version = "0.6.4" +name = "sctk-adwaita" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "82b2eaf3a5b264a521b988b2e73042e742df700c4f962cde845d1541adb46550" dependencies = [ - "getrandom 0.2.7", + "ab_glyph", + "log", + "memmap2 0.9.4", + "smithay-client-toolkit", + "tiny-skia", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "serde" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ - "rand_core 0.5.1", + "serde_derive", ] [[package]] -name = "rand_xoshiro" -version = "0.6.0" +name = "serde_derive" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ - "rand_core 0.6.4", + "proc-macro2", + "quote", + "syn 2.0.60", ] [[package]] -name = "rayon" -version = "1.5.3" +name = "serde_json" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", + "itoa", + "ryu", + "serde", ] [[package]] -name = "rayon-core" -version = "1.9.3" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", + "lazy_static", ] [[package]] -name = "rctree" -version = "0.3.3" +name = "simd-adler32" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be9e29cb19c8fe84169fcb07f8f11e66bc9e6e0280efd4715c54818296f8a4a8" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] -name = "redox_syscall" -version = "0.1.57" +name = "similar" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] -name = "redox_syscall" -version = "0.2.16" +name = "skrifa" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "b3f54c6ecbad34b55ea1779389e334fe0d260f2f0c80964a926ffb47918c26e9" dependencies = [ - "bitflags", + "bytemuck", + "core_maths", + "read-fonts", ] [[package]] -name = "redox_users" -version = "0.4.3" +name = "slab" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ - "getrandom 0.2.7", - "redox_syscall 0.2.16", - "thiserror", + "autocfg", ] [[package]] -name = "regex" -version = "1.6.0" +name = "slotmap" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "version_check", ] [[package]] -name = "regex-syntax" -version = "0.6.27" +name = "smallvec" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "smithay-client-toolkit" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" dependencies = [ - "winapi", + "bitflags 2.5.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2 0.9.4", + "rustix", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", ] [[package]] -name = "roxmltree" -version = "0.14.1" +name = "smol_str" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" +checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49" dependencies = [ - "xmlparser", + "serde", ] [[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc_version" -version = "0.3.3" +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ - "semver", + "lock_api", ] [[package]] -name = "rustybuzz" -version = "0.3.0" +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab463a295d00f3692e0974a0bfd83c7a9bcd119e27e07c2beecdb1b44a09d10" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags", - "bytemuck", - "smallvec", - "ttf-parser 0.9.0", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-general-category", - "unicode-script", + "bitflags 2.5.0", ] [[package]] -name = "ryu" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "scoped_threadpool" -version = "0.1.9" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] -name = "scopeguard" +name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "self_cell" -version = "0.10.2" +name = "strict-num" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" [[package]] -name = "semver" -version = "0.11.0" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "semver-parser" -version = "0.10.2" +name = "svg_fmt" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] +checksum = "f83ba502a3265efb76efb89b0a2f7782ad6f2675015d4ce37e4b547dda42b499" [[package]] -name = "serde" -version = "1.0.145" +name = "swash" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +checksum = "06ec889a8e0a6fcb91041996c8f1f6be0fe1a09e94478785e07c32ce2bca2d2b" dependencies = [ - "serde_derive", + "read-fonts", + "yazi", + "zeno", ] [[package]] -name = "serde_derive" -version = "1.0.145" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", - "syn", + "unicode-ident", ] [[package]] -name = "serde_json" -version = "1.0.85" +name = "syn" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ - "itoa", - "ryu", - "serde", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "serde_repr" -version = "0.1.9" +name = "synstructure" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.60", ] [[package]] -name = "sha1" -version = "0.6.1" +name = "tempfile" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ - "sha1_smol", + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", ] [[package]] -name = "sha1_smol" -version = "1.0.0" +name = "termcolor" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] [[package]] -name = "sharded-slab" -version = "0.1.4" +name = "thiserror" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ - "lazy_static", + "thiserror-impl", ] [[package]] -name = "shlex" -version = "1.1.0" +name = "thiserror-impl" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] [[package]] -name = "similar" -version = "2.2.0" +name = "thread_local" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] [[package]] -name = "simplecss" -version = "0.2.1" +name = "tiff" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" dependencies = [ - "log", + "flate2", + "jpeg-decoder", + "weezl", ] [[package]] -name = "siphasher" -version = "0.2.3" +name = "tiny-skia" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] [[package]] -name = "sized-chunks" -version = "0.6.5" +name = "tiny-skia-path" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" dependencies = [ - "bitmaps", - "typenum", + "arrayref", + "bytemuck", + "strict-num", ] [[package]] -name = "slab" -version = "0.4.7" +name = "tinystr" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "83c02bf3c538ab32ba913408224323915f4ef9a6d61c0e85d493f355921c0ece" dependencies = [ - "autocfg", + "displaydoc", + "zerovec", ] [[package]] -name = "smallvec" -version = "1.9.0" +name = "toml_datetime" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] -name = "socket2" -version = "0.4.7" +name = "toml_edit" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "libc", - "winapi", + "indexmap", + "toml_datetime", + "winnow", ] [[package]] -name = "spin" -version = "0.9.4" +name = "tracing" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "lock_api", + "pin-project-lite", + "tracing-attributes", + "tracing-core", ] [[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "svgtypes" -version = "0.5.0" +name = "tracing-attributes" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "float-cmp 0.5.3", - "siphasher", + "proc-macro2", + "quote", + "syn 2.0.60", ] [[package]] -name = "syn" -version = "1.0.101" +name = "tracing-core" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "once_cell", + "valuable", ] [[package]] -name = "system-deps" -version = "6.0.3" +name = "tracing-subscriber" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml", - "version-compare", + "ansi_term", + "sharded-slab", + "thread_local", + "tracing-core", ] [[package]] -name = "tempfile" -version = "3.1.0" +name = "tracing-subscriber" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ - "cfg-if 0.1.10", - "libc", - "rand 0.7.3", - "redox_syscall 0.1.57", - "remove_dir_all", - "winapi", + "sharded-slab", + "thread_local", + "tracing-core", ] [[package]] -name = "terminal_size" -version = "0.1.17" +name = "tracing-wasm" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" dependencies = [ - "libc", - "winapi", + "tracing", + "tracing-subscriber 0.3.18", + "wasm-bindgen", ] [[package]] -name = "thiserror" -version = "1.0.37" +name = "ttf-parser" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" -dependencies = [ - "thiserror-impl", -] +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" [[package]] -name = "thiserror-impl" -version = "1.0.37" +name = "unicase" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ - "proc-macro2", - "quote", - "syn", + "version_check", ] [[package]] -name = "thread_local" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" -dependencies = [ - "once_cell", -] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "threadpool" -version = "1.8.1" +name = "unicode-segmentation" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] -name = "tiff" -version = "0.8.1" +name = "unicode-width" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] -name = "time" -version = "0.1.44" +name = "unicode-xid" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] -name = "time" -version = "0.3.17" +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vello" +version = "0.1.0" +source = "git+https://github.com/linebender/vello/?rev=b520a35addfa6bbb37d93491d2b8236528faf3b5#b520a35addfa6bbb37d93491d2b8236528faf3b5" dependencies = [ - "serde", - "time-core", + "bytemuck", + "futures-intrusive", + "log", + "peniko", + "raw-window-handle", + "skrifa", + "vello_encoding", + "wgpu", ] [[package]] -name = "time-core" +name = "vello_encoding" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +source = "git+https://github.com/linebender/vello/?rev=b520a35addfa6bbb37d93491d2b8236528faf3b5#b520a35addfa6bbb37d93491d2b8236528faf3b5" +dependencies = [ + "bytemuck", + "guillotiere", + "peniko", + "skrifa", +] [[package]] -name = "tinystr" -version = "0.3.4" +name = "version_check" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "toml" -version = "0.5.9" +name = "walkdir" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ - "serde", + "same-file", + "winapi-util", ] [[package]] -name = "tracing" -version = "0.1.37" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if 1.0.0", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "tracing-attributes" -version = "0.1.23" +name = "wasm-bindgen" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ - "proc-macro2", - "quote", - "syn", + "cfg-if", + "wasm-bindgen-macro", ] [[package]] -name = "tracing-core" -version = "0.1.30" +name = "wasm-bindgen-backend" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ + "bumpalo", + "log", "once_cell", - "valuable", + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-shared", ] [[package]] -name = "tracing-subscriber" -version = "0.2.25" +name = "wasm-bindgen-futures" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ - "ansi_term", - "sharded-slab", - "thread_local", - "tracing-core", + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "tracing-subscriber" -version = "0.3.15" +name = "wasm-bindgen-macro" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", + "quote", + "wasm-bindgen-macro-support", ] [[package]] -name = "tracing-wasm" -version = "0.2.1" +name = "wasm-bindgen-macro-support" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ - "tracing", - "tracing-subscriber 0.3.15", - "wasm-bindgen", + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-backend", + "wasm-bindgen-shared", ] [[package]] -name = "ttf-parser" -version = "0.9.0" +name = "wasm-bindgen-shared" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ddb402ac6c2af6f7a2844243887631c4e94b51585b229fcfddb43958cd55ca" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] -name = "ttf-parser" -version = "0.12.3" +name = "wayland-backend" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" +checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] [[package]] -name = "type-map" -version = "0.4.0" +name = "wayland-client" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46" +checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" dependencies = [ - "rustc-hash", + "bitflags 2.5.0", + "rustix", + "wayland-backend", + "wayland-scanner", ] [[package]] -name = "typenum" -version = "1.15.0" +name = "wayland-csd-frame" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.5.0", + "cursor-icon", + "wayland-backend", +] [[package]] -name = "ucd-trie" -version = "0.1.5" +name = "wayland-cursor" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba" +dependencies = [ + "rustix", + "wayland-client", + "xcursor", +] [[package]] -name = "uds_windows" -version = "1.0.2" +name = "wayland-protocols" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ - "tempfile", - "winapi", + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", ] [[package]] -name = "unic-bidi" -version = "0.9.0" +name = "wayland-protocols-plasma" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1356b759fb6a82050666f11dce4b6fe3571781f1449f3ef78074e408d468ec09" +checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "matches", - "unic-ucd-bidi", + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] -name = "unic-char-property" -version = "0.9.0" +name = "wayland-protocols-wlr" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "unic-char-range", + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] -name = "unic-char-range" -version = "0.9.0" +name = "wayland-scanner" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" +checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] [[package]] -name = "unic-common" -version = "0.9.0" +name = "wayland-sys" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" +checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] [[package]] -name = "unic-langid" -version = "0.9.0" +name = "web-sys" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ - "unic-langid-impl", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "unic-langid-impl" -version = "0.9.0" +name = "web-time" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" dependencies = [ - "tinystr", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "unic-ucd-bidi" -version = "0.9.0" +name = "weezl" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1d568b51222484e1f8209ce48caa6b430bf352962b877d592c29ab31fb53d8c" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "wgpu" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", + "arrayvec", + "cfg-if", + "cfg_aliases", + "js-sys", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", ] [[package]] -name = "unic-ucd-version" -version = "0.9.0" +name = "wgpu-core" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" dependencies = [ - "unic-common", + "arrayvec", + "bit-vec", + "bitflags 2.5.0", + "cfg_aliases", + "codespan-reporting", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror", + "web-sys", + "wgpu-hal", + "wgpu-types", ] [[package]] -name = "unicase" -version = "2.6.0" +name = "wgpu-hal" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "fc1a4924366df7ab41a5d8546d6534f1f33231aa5b3f72b9930e300f254e39c3" dependencies = [ - "version_check", + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.5.0", + "block", + "cfg_aliases", + "core-graphics-types", + "d3d12", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.3", + "log", + "metal", + "naga", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", ] [[package]] -name = "unicode-bidi" -version = "0.3.8" +name = "wgpu-types" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" +dependencies = [ + "bitflags 2.5.0", + "js-sys", + "web-sys", +] [[package]] -name = "unicode-bidi-mirroring" -version = "0.1.0" +name = "widestring" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] -name = "unicode-ccc" -version = "0.1.2" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] [[package]] -name = "unicode-general-category" -version = "0.2.0" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9af028e052a610d99e066b33304625dea9613170a2563314490a4e6ec5cf7f" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "unicode-ident" -version = "1.0.4" +name = "winapi-util" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] [[package]] -name = "unicode-script" -version = "0.5.5" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "unicode-segmentation" -version = "1.10.0" +name = "windows" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.5", +] [[package]] -name = "unicode-vo" -version = "0.1.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] [[package]] -name = "usvg" -version = "0.14.1" +name = "windows-sys" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8352f317d8f9a918ba5154797fb2a93e2730244041cf7d5be35148266adfa5" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "base64", - "data-url", - "flate2", - "fontdb", - "kurbo 0.8.3", - "log", - "memmap2", - "pico-args", - "rctree", - "roxmltree", - "rustybuzz", - "simplecss", - "siphasher", - "svgtypes", - "ttf-parser 0.12.3", - "unicode-bidi", - "unicode-script", - "unicode-vo", - "xmlwriter", + "windows-targets 0.42.2", ] [[package]] -name = "utf16_lit" -version = "2.0.2" +name = "windows-sys" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14706d2a800ee8ff38c1d3edb873cd616971ea59eb7c0d046bb44ef59b06a1ae" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] [[package]] -name = "valuable" -version = "0.1.0" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] [[package]] -name = "version-compare" -version = "0.1.1" +name = "windows-targets" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] [[package]] -name = "version_check" -version = "0.9.4" +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] [[package]] -name = "waker-fn" -version = "1.1.0" +name = "windows-targets" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] [[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +name = "windows_aarch64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +name = "windows_aarch64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "windows_aarch64_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] -name = "wasm-bindgen" -version = "0.2.83" +name = "windows_aarch64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" -dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen-macro", -] +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] -name = "wasm-bindgen-backend" -version = "0.2.83" +name = "windows_aarch64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] -name = "wasm-bindgen-macro" -version = "0.2.83" +name = "windows_aarch64_msvc" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.83" +name = "windows_i686_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] -name = "wasm-bindgen-shared" -version = "0.2.83" +name = "windows_i686_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] -name = "web-sys" -version = "0.3.60" +name = "windows_i686_gnu" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" -dependencies = [ - "js-sys", - "wasm-bindgen", -] +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] -name = "weezl" -version = "0.1.7" +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] -name = "wepoll-ffi" -version = "0.1.2" +name = "windows_i686_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] -name = "which" -version = "4.4.0" +name = "windows_i686_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -dependencies = [ - "either", - "libc", - "once_cell", -] +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] -name = "winapi" -version = "0.3.9" +name = "windows_i686_msvc" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows_x86_64_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] -name = "winapi-wsapoll" -version = "0.1.1" +name = "windows_x86_64_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" -dependencies = [ - "winapi", -] +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows_x86_64_gnu" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] -name = "windows-sys" -version = "0.36.1" +name = "windows_x86_64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" +name = "windows_x86_64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] -name = "windows_i686_gnu" -version = "0.36.1" +name = "windows_x86_64_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] -name = "windows_i686_msvc" -version = "0.36.1" +name = "windows_x86_64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" +name = "windows_x86_64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winit" +version = "0.29.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.5.0", + "bytemuck", + "calloop", + "cfg_aliases", + "core-foundation", + "core-graphics", + "cursor-icon", + "icrate", + "js-sys", + "libc", + "log", + "memmap2 0.9.4", + "ndk", + "ndk-sys", + "objc2", + "once_cell", + "orbclient", + "percent-encoding", + "raw-window-handle", + "redox_syscall 0.3.5", + "rustix", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.48.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] [[package]] name = "wio" @@ -2997,142 +3002,185 @@ dependencies = [ "winapi", ] +[[package]] +name = "writeable" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad7bb64b8ef9c0aa27b6da38b452b0ee9fd82beaf276a87dd796fb55cbae14e" + +[[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.10.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507" +checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" dependencies = [ + "as-raw-xcb-connection", "gethostname", "libc", - "nix 0.24.3", - "winapi", - "winapi-wsapoll", + "libloading 0.8.3", + "once_cell", + "rustix", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.10.0" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" + +[[package]] +name = "xcursor" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67" +checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "nix 0.24.3", + "bitflags 2.5.0", + "dlib", + "log", + "once_cell", + "xkeysym", ] [[package]] -name = "xi-unicode" -version = "0.3.0" +name = "xkeysym" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" +checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" + +[[package]] +name = "xml-rs" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" [[package]] name = "xmlparser" -version = "0.13.3" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] -name = "xmlwriter" -version = "0.1.0" +name = "yazi" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" [[package]] -name = "yaml-rust" -version = "0.4.5" +name = "yoke" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +checksum = "65e71b2e4f287f467794c671e2b8f8a5f3716b3c829079a1c44740148eff07e4" dependencies = [ - "linked-hash-map", + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", ] [[package]] -name = "zbus" -version = "2.3.2" +name = "yoke-derive" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8f1a037b2c4a67d9654dc7bdfa8ff2e80555bbefdd3c1833c1d1b27c963a6b" +checksum = "9e6936f0cce458098a201c245a11bef556c6a0181129c7034d10d76d1ec3a2b8" dependencies = [ - "async-broadcast", - "async-channel", - "async-executor", - "async-io", - "async-lock", - "async-recursion", - "async-task", - "async-trait", - "byteorder", - "derivative", - "dirs", - "enumflags2", - "event-listener", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "lazy_static", - "nix 0.23.1", - "once_cell", - "ordered-stream", - "rand 0.8.5", - "serde", - "serde_repr", - "sha1", - "static_assertions", - "tracing", - "uds_windows", - "winapi", - "zbus_macros", - "zbus_names", - "zvariant", + "proc-macro2", + "quote", + "syn 2.0.60", + "synstructure", ] [[package]] -name = "zbus_macros" -version = "2.3.2" +name = "zeno" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8fb5186d1c87ae88cf234974c240671238b4a679158ad3b94ec465237349a6" +checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ - "proc-macro-crate", "proc-macro2", "quote", - "regex", - "syn", + "syn 2.0.60", ] [[package]] -name = "zbus_names" -version = "2.2.0" +name = "zerofrom" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a408fd8a352695690f53906dc7fd036be924ec51ea5e05666ff42685ed0af5" +checksum = "655b0814c5c0b19ade497851070c640773304939a6c0fd5f5fb43da0696d05b7" dependencies = [ - "serde", - "static_assertions", - "zvariant", + "zerofrom-derive", ] [[package]] -name = "zvariant" -version = "3.7.1" +name = "zerofrom-derive" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794fb7f59af4105697b0449ba31731ee5dbb3e773a17dbdf3d36206ea1b1644" +checksum = "e6a647510471d372f2e6c2e6b7219e44d8c574d24fdc11c610a61455782f18c3" dependencies = [ - "byteorder", - "enumflags2", - "libc", - "serde", - "static_assertions", - "zvariant_derive", + "proc-macro2", + "quote", + "syn 2.0.60", + "synstructure", ] [[package]] -name = "zvariant_derive" -version = "3.7.1" +name = "zerovec" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd58d4b6c8e26d3dd2149c8c40c6613ef6451b9885ff1296d1ac86c388351a54" +checksum = "eff4439ae91fb5c72b8abc12f3f2dbf51bd27e6eadb9f8a5bc8898dddb0e27ea" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4e5997cbf58990550ef1f0e5124a05e47e1ebd33a84af25739be6031a62c20" dependencies = [ - "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.60", +] + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", ] diff --git a/Cargo.toml b/Cargo.toml index dd05170c..3ef48d55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,71 +11,28 @@ repository = "https://github.com/PoignardAzur/masonry-rs" rust-version = "1.65" version = "0.1.3" -[package.metadata.docs.rs] -# The "svg" and "image" features have doc clashes that cause undefined output in docs. -# See https://github.com/rust-lang/cargo/issues/6313 for more information. -# Once cargo doc becomes smart enough to handle multiple versions of the same crate, -# the "svg" and "image" features should be enabled for the docs.rs output. -default-target = "x86_64-pc-windows-msvc" -features = ["im"] -rustdoc-args = ["--cfg", "docsrs"] - -[features] -default = ["gtk", "image", "png", "jpeg"] -gtk = ["druid-shell/gtk"] -image = ["druid-shell/image", "piet-common/image"] -serde_deps = ["im/serde", "druid-shell/serde"] -svg = ["usvg"] -x11 = ["druid-shell/x11"] - -# passing on all the image features. AVIF is not supported because it does not -# support decoding, and that's all we use `Image` for. -bmp = ["druid-shell/bmp"] -dds = ["druid-shell/dds"] -dxt = ["druid-shell/dxt"] -farbfeld = ["druid-shell/farbfeld"] -gif = ["druid-shell/gif"] -hdr = ["druid-shell/hdr"] -ico = ["druid-shell/ico"] -jpeg = ["druid-shell/jpeg"] -jpeg_rayon = ["druid-shell/jpeg_rayon"] -png = ["druid-shell/image_png"] -pnm = ["druid-shell/pnm"] -tga = ["druid-shell/tga"] -tiff = ["druid-shell/tiff"] -webp = ["druid-shell/webp"] - -# Remember to update this when changing an image feature. -image-all = ["image", "svg", "png", "jpeg", "jpeg_rayon", "gif", "bmp", "ico", "tiff", "webp", "pnm", "dds", "tga", "farbfeld", "dxt", "hdr"] +[profile.dev.package."*"] +opt-level = 2 +# NOTE: Make sure to keep wgpu version in sync with the version badge in README.md [dependencies] -druid-shell = {version = "0.8.0", default-features = false} -# We defer to the version imported by druid-shell -piet-common = "*" - -# TODO - remove unused dependencies - See #10 -fluent-bundle = "0.15.1" -fluent-langneg = "0.13.0" -fluent-syntax = "0.11.0" fnv = "1.0.7" instant = {version = "0.1.6", features = ["wasm-bindgen"]} smallvec = "1.6.1" tracing = "0.1.29" tracing-subscriber = {version = "0.2.15", features = ["fmt", "ansi"], default-features = false} -unic-langid = "0.9.0" -unicode-segmentation = "1.6.0" -xi-unicode = "0.3.0" - -# Optional dependencies -chrono = {version = "0.4.19", optional = true} -im = {version = "15.0.0", optional = true} -usvg = {version = "0.14.1", optional = true} - -# TODO - make serde a dev dependency image = "0.24.0" once_cell = "1.9.0" serde = {version = "1.0.133", features = ["derive"]} serde_json = "1.0.74" +vello = { git = "https://github.com/linebender/vello/", rev = "b520a35addfa6bbb37d93491d2b8236528faf3b5" } +kurbo = "0.11.0" +futures-intrusive = "0.5.0" +pollster = "0.3.0" +parley = { git = "https://github.com/linebender/parley", rev = "4f05e183be9b388c6748d3c531c9ac332672fb86" } +wgpu = { version = "0.19.3" } +swash = "0.1.15" +winit = "0.29.15" [target.'cfg(target_arch="wasm32")'.dependencies] console_error_panic_hook = {version = "0.1.6"} @@ -84,14 +41,13 @@ tracing-wasm = {version = "0.2.0"} [dev-dependencies] float-cmp = {version = "0.8.0", features = ["std"], default-features = false} insta = {version = "1.8.0"} -# tempfile 3.2.0 broke wasm; I assume it will be yanked (Jan 12, 2021) assert_matches = "1.5.0" pulldown-cmark = {version = "0.8", default-features = false} -tempfile = "=3.1.0" +tempfile = "3.10.1" [target.'cfg(not(target_arch="wasm32"))'.dev-dependencies] open = "1.6" [[example]] name = "simple_image" -required-features = ["image", "png"] +#required-features = ["image", "png"] diff --git a/examples/blocking_function.rs b/examples/blocking_function.rs deleted file mode 100644 index 14c2ba66..00000000 --- a/examples/blocking_function.rs +++ /dev/null @@ -1,122 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! An example of a blocking function running in another thread. We give -//! the other thread some data and then we also pass some data back -//! to the main thread using commands. - -// On Windows platform, don't show a console when opening the app. -#![windows_subsystem = "windows"] - -use std::{thread, time}; - -use masonry::widget::prelude::*; -use masonry::widget::WidgetRef; -use masonry::widget::{Flex, Label, Spinner, WidgetPod}; -use masonry::{AppLauncher, Point, Selector, Target, WindowDescription}; -use smallvec::{smallvec, SmallVec}; - -const FINISH_SLOW_FUNCTION: Selector = Selector::new("finish_slow_function"); - -struct MainWidget { - pub content: WidgetPod, - pub loading: bool, - pub value: u32, -} - -impl MainWidget { - fn new(value: u32) -> Self { - MainWidget { - content: WidgetPod::new( - Flex::column().with_child(Label::new("Click to change value: 0")), - ), - loading: false, - value, - } - } -} - -impl Widget for MainWidget { - fn on_event(&mut self, ctx: &mut EventCtx, event: &Event) { - match event { - Event::MouseDown(_) => { - if !ctx.is_disabled() { - ctx.set_active(true); - ctx.request_paint(); - } - } - Event::MouseUp(_) => { - if ctx.is_active() && !ctx.is_disabled() { - ctx.set_active(false); - if !self.loading { - self.loading = true; - - let number = self.value + 1; - ctx.run_in_background(move |event_sink| { - // "sleep" stands in for a long computation, a download, etc. - thread::sleep(time::Duration::from_millis(2000)); - - event_sink - .submit_command(FINISH_SLOW_FUNCTION, number, Target::Auto) - .expect("command failed to submit"); - }); - - self.content.on_event(ctx, event); - let mut flex_mut = ctx.get_mut(&mut self.content); - flex_mut.clear(); - flex_mut.add_child(Spinner::new()); - - return; - } - } - } - Event::Command(cmd) if cmd.is(FINISH_SLOW_FUNCTION) => { - let value = *cmd.get(FINISH_SLOW_FUNCTION); - - self.content.on_event(ctx, event); - - let mut flex_mut = ctx.get_mut(&mut self.content); - flex_mut.clear(); - flex_mut.add_child(Label::new(format!("New value: {value}"))); - - self.loading = false; - self.value = value; - - return; - } - _ => (), - } - self.content.on_event(ctx, event); - } - - fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} - - fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { - self.content.lifecycle(ctx, event); - } - - fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { - let content_size = self.content.layout(ctx, bc); - ctx.place_child(&mut self.content, Point::ORIGIN); - content_size - } - - fn paint(&mut self, ctx: &mut PaintCtx) { - self.content.paint(ctx); - } - - fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { - smallvec![self.content.as_dyn()] - } -} - -// --- - -fn main() { - let main_window = WindowDescription::new(MainWidget::new(0)).title("Blocking functions"); - AppLauncher::with_window(main_window) - .log_to_console() - .launch() - .expect("launch failed"); -} diff --git a/examples/calc.rs b/examples/calc.rs index 72be4bc3..bf70d7d8 100644 --- a/examples/calc.rs +++ b/examples/calc.rs @@ -9,13 +9,21 @@ use std::sync::Arc; +use masonry::app_driver::{AppDriver, DriverCtx}; +use masonry::event_loop_runner::EventLoopRunner; +use masonry::testing::TestHarness; use masonry::widget::{Align, CrossAxisAlignment, Flex, Label, SizedBox, WidgetRef}; use masonry::{ - Action, AppDelegate, AppLauncher, BoxConstraints, Color, Event, EventCtx, LayoutCtx, LifeCycle, - LifeCycleCtx, PaintCtx, Point, Size, StatusChange, Widget, WidgetPod, WindowDescription, + assert_render_snapshot, Action, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle, + LifeCycleCtx, PaintCtx, Point, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId, + WidgetPod, }; use smallvec::{smallvec, SmallVec}; use tracing::{trace, trace_span, Span}; +use vello::Scene; +use winit::dpi::LogicalSize; +use winit::event_loop::EventLoop; +use winit::window::WindowBuilder; #[derive(Clone)] struct CalcState { @@ -137,9 +145,9 @@ impl CalcButton { } impl Widget for CalcButton { - fn on_event(&mut self, ctx: &mut EventCtx, event: &Event) { + fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) { match event { - Event::MouseDown(_) => { + PointerEvent::PointerDown(_, _) => { if !ctx.is_disabled() { ctx.get_mut(&mut self.inner) .set_background(self.active_color); @@ -148,7 +156,7 @@ impl Widget for CalcButton { trace!("CalcButton {:?} pressed", ctx.widget_id()); } } - Event::MouseUp(_) => { + PointerEvent::PointerUp(_, _) => { if ctx.is_active() && !ctx.is_disabled() { ctx.submit_action(Action::Other(Arc::new(self.action))); ctx.request_paint(); @@ -159,7 +167,11 @@ impl Widget for CalcButton { } _ => (), } - self.inner.on_event(ctx, event); + self.inner.on_pointer_event(ctx, event); + } + + fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) { + self.inner.on_text_event(ctx, event); } fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { @@ -188,8 +200,8 @@ impl Widget for CalcButton { size } - fn paint(&mut self, ctx: &mut PaintCtx) { - self.inner.paint(ctx); + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + self.inner.paint(ctx, scene); } fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { @@ -201,14 +213,8 @@ impl Widget for CalcButton { } } -impl AppDelegate for CalcState { - fn on_action( - &mut self, - ctx: &mut masonry::DelegateCtx, - _window_id: masonry::WindowId, - _widget_id: masonry::WidgetId, - action: Action, - ) { +impl AppDriver for CalcState { + fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) { match action { Action::Other(payload) => match payload.downcast_ref::().unwrap() { CalcAction::Digit(digit) => self.digit(*digit), @@ -336,19 +342,22 @@ fn build_calc() -> impl Widget { } pub fn main() { - let window = WindowDescription::new(build_calc()) - .window_size((223., 300.)) - .resizable(true) - .title("Simple Calculator"); + let event_loop = EventLoop::new().unwrap(); + let window_size = LogicalSize::new(223., 300.); + let window = WindowBuilder::new() + .with_title("Simple Calculator") + .with_resizable(true) + .with_min_inner_size(window_size) + .with_max_inner_size(window_size) + .build(&event_loop) + .unwrap(); let calc_state = CalcState { value: "0".to_string(), operand: 0.0, operator: 'C', in_num: false, }; - AppLauncher::with_window(window) - .with_delegate(calc_state) - .log_to_console() - .launch() - .expect("Cannot start application"); + + let runner = EventLoopRunner::new(build_calc(), window, event_loop, calc_state); + runner.run().unwrap(); } diff --git a/examples/custom_widget.rs b/examples/custom_widget.rs index 2e8e27ba..8581b507 100644 --- a/examples/custom_widget.rs +++ b/examples/custom_widget.rs @@ -8,17 +8,29 @@ // On Windows platform, don't show a console when opening the app. #![windows_subsystem = "windows"] +use kurbo::Stroke; +use masonry::app_driver::{AppDriver, DriverCtx}; +use masonry::event_loop_runner::EventLoopRunner; use masonry::kurbo::BezPath; -use masonry::piet::{FontFamily, ImageFormat, InterpolationMode, Text, TextLayoutBuilder}; -use masonry::text::{FontDescriptor, TextLayout}; -use masonry::widget::WidgetRef; +use masonry::widget::{FillStrat, WidgetRef}; use masonry::{ - Affine, AppLauncher, BoxConstraints, Color, Event, EventCtx, LayoutCtx, LifeCycle, - LifeCycleCtx, PaintCtx, Point, Rect, RenderContext, Size, StatusChange, Widget, - WindowDescription, + Action, Affine, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + Point, PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, WidgetId, }; +use parley::layout::Alignment; +use parley::style::{FontFamily, FontStack, StyleProperty}; use smallvec::SmallVec; use tracing::{trace_span, Span}; +use vello::peniko::{Brush, Fill, Format, Image}; +use vello::Scene; +use winit::event_loop::EventLoop; +use winit::window::WindowBuilder; + +struct Driver; + +impl AppDriver for Driver { + fn on_action(&mut self, _ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, _action: Action) {} +} struct CustomWidget(String); @@ -26,7 +38,9 @@ struct CustomWidget(String); // (and lifecycle) methods as well to make sure it works. Some things can be filtered, // but a general rule is to just pass it through unless you really know you don't want it. impl Widget for CustomWidget { - fn on_event(&mut self, _ctx: &mut EventCtx, _event: &Event) {} + fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {} + + fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle) {} @@ -55,82 +69,61 @@ impl Widget for CustomWidget { // The paint method gets called last, after an event flow. // It goes event -> update -> layout -> paint, and each method can influence the next. // Basically, anything that changes the appearance of a widget causes a paint. - fn paint(&mut self, ctx: &mut PaintCtx) { + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { // Clear the whole widget with the color of your choice // (ctx.size() returns the size of the layout rect we're painting in) // Note: ctx also has a `clear` method, but that clears the whole context, // and we only want to clear this widget's area. let size = ctx.size(); let rect = size.to_rect(); - ctx.fill(rect, &Color::WHITE); - - // We can paint with a Z index, this indicates that this code will be run - // after the rest of the painting. Painting with z-index is done in order, - // so first everything with z-index 1 is painted and then with z-index 2 etc. - // As you can see this(red) curve is drawn on top of the green curve - ctx.paint_with_z_index(1, move |ctx| { - let mut path = BezPath::new(); - path.move_to((0.0, size.height)); - path.quad_to((40.0, 50.0), (size.width, 0.0)); - // Create a color - let stroke_color = Color::rgb8(128, 0, 0); - // Stroke the path with thickness 1.0 - ctx.stroke(path, &stroke_color, 5.0); - }); + scene.fill(Fill::NonZero, Affine::IDENTITY, Color::WHITE, None, &rect); // Create an arbitrary bezier path let mut path = BezPath::new(); path.move_to(Point::ORIGIN); - path.quad_to((40.0, 50.0), (size.width, size.height)); + path.quad_to((60.0, 120.0), (size.width, size.height)); // Create a color let stroke_color = Color::rgb8(0, 128, 0); // Stroke the path with thickness 5.0 - ctx.stroke(path, &stroke_color, 5.0); + scene.stroke( + &Stroke::new(5.0), + Affine::IDENTITY, + stroke_color, + None, + &path, + ); // Rectangles: the path for practical people let rect = Rect::from_origin_size((10.0, 10.0), (100.0, 100.0)); // Note the Color:rgba8 which includes an alpha channel (7F in this case) let fill_color = Color::rgba8(0x00, 0x00, 0x00, 0x7F); - ctx.fill(rect, &fill_color); - - // Text is easy; in real use TextLayout should either be stored in the - // widget and reused, or a label child widget to manage it all. - // This is one way of doing it, you can also use a builder-style way. - let mut layout = TextLayout::::from_text(&self.0); - layout.set_font(FontDescriptor::new(FontFamily::SERIF).with_size(24.0)); - layout.set_text_color(fill_color); - layout.rebuild_if_needed(ctx.text()); - - // Let's rotate our text slightly. First we save our current (default) context: - ctx.with_save(|ctx| { - // Now we can rotate the context (or set a clip path, for instance): - // This makes it so that anything drawn after this (in the closure) is - // transformed. - // The transformation is in radians, but be aware it transforms the canvas, - // not just the part you are drawing. So we draw at (80.0, 40.0) on the rotated - // canvas, this is NOT the same position as (80.0, 40.0) on the original canvas. - ctx.transform(Affine::rotate(std::f64::consts::FRAC_PI_4)); - layout.draw(ctx, (80.0, 40.0)); - }); - // When we exit with_save, the original context's rotation is restored - - // This is the builder-style way of drawing text. - let text = ctx.text(); - let layout = text - .new_text_layout(self.0.clone()) - .font(FontFamily::SERIF, 24.0) - .text_color(Color::rgb8(128, 0, 0)) - .build() - .unwrap(); - ctx.draw_text(&layout, (100.0, 25.0)); + scene.fill(Fill::NonZero, Affine::IDENTITY, fill_color, None, &rect); + + // To render text, we first create a LayoutBuilder and set the text properties. + let mut lcx = parley::LayoutContext::new(); + let mut text_layout_builder = lcx.ranged_builder(ctx.font_ctx(), &self.0, 1.0); + + text_layout_builder.push_default(&StyleProperty::FontStack(FontStack::Single( + FontFamily::Generic(parley::style::GenericFamily::Serif), + ))); + text_layout_builder.push_default(&StyleProperty::FontSize(24.0)); + text_layout_builder.push_default(&StyleProperty::Brush(Brush::Solid(fill_color))); + + let mut text_layout = text_layout_builder.build(); + text_layout.break_all_lines(None, Alignment::Start); + + // We can pass a transform matrix to rotate the text we render + masonry::text_helpers::render_text( + scene, + Affine::rotate(std::f64::consts::FRAC_PI_4).then_translate((80.0, 40.0).into()), + &text_layout, + ); // Let's burn some CPU to make a (partially transparent) image buffer let image_data = make_image_data(256, 256); - let image = ctx - .make_image(256, 256, &image_data, ImageFormat::RgbaSeparate) - .unwrap(); - // The image is automatically scaled to fit the rect you pass to draw_image - ctx.draw_image(&image, size.to_rect(), InterpolationMode::Bilinear); + let image_data = Image::new(image_data.into(), Format::Rgba8, 256, 256); + let transform = FillStrat::Fill.affine_to_fill(ctx.size(), size); + scene.draw_image(&image_data, transform); } fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { @@ -143,12 +136,15 @@ impl Widget for CustomWidget { } pub fn main() { - let my_string = "Masonry + Piet".to_string(); - let window = WindowDescription::new(CustomWidget(my_string)).title("Fancy Colors"); - AppLauncher::with_window(window) - .log_to_console() - .launch() - .expect("launch failed"); + let my_string = "Masonry + Vello".to_string(); + let event_loop = EventLoop::new().unwrap(); + let window = WindowBuilder::new() + .with_title("Fancy colots") + .build(&event_loop) + .unwrap(); + + let runner = EventLoopRunner::new(CustomWidget(my_string), window, event_loop, Driver); + runner.run().unwrap(); } fn make_image_data(width: usize, height: usize) -> Vec { diff --git a/examples/hello.rs b/examples/hello.rs index f2941526..939846bc 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -8,42 +8,42 @@ // On Windows platform, don't show a console when opening the app. #![windows_subsystem = "windows"] +use masonry::app_driver::{AppDriver, DriverCtx}; +use masonry::event_loop_runner::EventLoopRunner; use masonry::widget::prelude::*; use masonry::widget::{Button, Flex, Label}; use masonry::Action; -use masonry::{AppDelegate, DelegateCtx}; -use masonry::{AppLauncher, WindowDescription, WindowId}; +use winit::dpi::LogicalSize; +use winit::event_loop::EventLoop; +use winit::window::WindowBuilder; const VERTICAL_WIDGET_SPACING: f64 = 20.0; -struct Delegate; - -impl AppDelegate for Delegate { - fn on_action( - &mut self, - _ctx: &mut DelegateCtx, - _window_id: WindowId, - _widget_id: WidgetId, - action: Action, - ) { - if let Action::ButtonPressed = action { - println!("Hello"); +struct Driver; + +impl AppDriver for Driver { + fn on_action(&mut self, _ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) { + match action { + Action::ButtonPressed => { + println!("Hello"); + } + _ => {} } } } pub fn main() { - // describe the main window - let main_window = WindowDescription::new(build_root_widget()) - .title("Hello World!") - .window_size((400.0, 400.0)); - - // start the application. Here we pass in the application state. - AppLauncher::with_window(main_window) - .with_delegate(Delegate) - .log_to_console() - .launch() - .expect("Failed to launch application"); + let event_loop = EventLoop::new().unwrap(); + let window_size = LogicalSize::new(400.0, 400.0); + let window = WindowBuilder::new() + .with_title("Hello World!") + .with_resizable(true) + .with_min_inner_size(window_size) + .build(&event_loop) + .unwrap(); + + let runner = EventLoopRunner::new(build_root_widget(), window, event_loop, Driver); + runner.run().unwrap(); } fn build_root_widget() -> impl Widget { diff --git a/examples/promise_button.rs b/examples/promise_button.rs deleted file mode 100644 index c7a51475..00000000 --- a/examples/promise_button.rs +++ /dev/null @@ -1,164 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! A label widget. - -#![allow(clippy::single_match)] - -use std::{thread, time}; - -use druid_shell::Cursor; -use masonry::kurbo::Vec2; -use masonry::promise::PromiseToken; -use masonry::text::TextLayout; -use masonry::widget::prelude::*; -use masonry::widget::WidgetRef; -use masonry::{AppLauncher, WindowDescription}; -use masonry::{ArcStr, Color, Point}; -use smallvec::SmallVec; -use tracing::{trace, trace_span, Span}; - -// added padding between the edges of the widget and the text. -const LABEL_X_PADDING: f64 = 2.0; - -pub struct PromiseButton { - value: u32, - text_layout: TextLayout, - line_break_mode: LineBreaking, - promise_token: PromiseToken, - - default_text_color: Color, -} - -/// Options for handling lines that are too wide for the label. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum LineBreaking { - /// Lines are broken at word boundaries. - WordWrap, - /// Lines are truncated to the width of the label. - Clip, - /// Lines overflow the label. - Overflow, -} - -// --- METHODS --- - -impl PromiseButton { - /// Create a new `PromiseButton`. - pub fn new(text: impl Into) -> Self { - let mut text_layout = TextLayout::new(); - text_layout.set_text(text.into()); - - Self { - value: 0, - text_layout, - line_break_mode: LineBreaking::Overflow, - promise_token: PromiseToken::empty(), - default_text_color: masonry::theme::TEXT_COLOR, - } - } -} - -// --- TRAIT IMPLS --- - -impl Widget for PromiseButton { - fn on_event(&mut self, ctx: &mut EventCtx, event: &Event) { - match event { - Event::MouseUp(_event) => { - let value = self.value; - self.promise_token = ctx.compute_in_background(move |_| { - // "sleep" stands in for a long computation, a download, etc. - thread::sleep(time::Duration::from_millis(2000)); - value + 1 - }); - self.text_layout.set_text("Loading ...".into()); - ctx.request_layout(); - } - Event::MouseMove(event) => { - // Account for the padding - let pos = event.pos - Vec2::new(LABEL_X_PADDING, 0.0); - - if self.text_layout.link_for_pos(pos).is_some() { - ctx.set_cursor(&Cursor::Pointer); - } else { - ctx.clear_cursor(); - } - } - Event::PromiseResult(result) => { - if let Some(new_value) = result.try_get(self.promise_token) { - self.text_layout - .set_text(format!("New value: {new_value}").into()); - self.value = new_value; - ctx.request_layout(); - } - } - _ => {} - } - } - - fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} - - fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { - match event { - LifeCycle::DisabledChanged(disabled) => { - let color = if *disabled { - masonry::theme::DISABLED_TEXT_COLOR - } else { - self.default_text_color - }; - self.text_layout.set_text_color(color); - ctx.request_layout(); - } - _ => {} - } - } - - fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { - let width = match self.line_break_mode { - LineBreaking::WordWrap => bc.max().width - LABEL_X_PADDING * 2.0, - _ => f64::INFINITY, - }; - - self.text_layout.set_wrap_width(width); - self.text_layout.rebuild_if_needed(ctx.text()); - - let text_metrics = self.text_layout.layout_metrics(); - ctx.set_baseline_offset(text_metrics.size.height - text_metrics.first_baseline); - let size = bc.constrain(Size::new( - text_metrics.size.width + 2. * LABEL_X_PADDING, - text_metrics.size.height, - )); - trace!("Computed size: {}", size); - size - } - - fn paint(&mut self, ctx: &mut PaintCtx) { - let origin = Point::new(LABEL_X_PADDING, 0.0); - let label_size = ctx.size(); - - if self.line_break_mode == LineBreaking::Clip { - ctx.clip(label_size.to_rect()); - } - self.text_layout.draw(ctx, origin) - } - - fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { - SmallVec::new() - } - - fn make_trace_span(&self) -> Span { - trace_span!("PromiseButton") - } -} - -// --- - -fn main() { - let main_window = - WindowDescription::new(PromiseButton::new("Hello")).title("Blocking functions"); - AppLauncher::with_window(main_window) - .log_to_console() - .launch() - .expect("launch failed"); -} diff --git a/examples/simple_image.rs b/examples/simple_image.rs index f50ded9d..566b305f 100644 --- a/examples/simple_image.rs +++ b/examples/simple_image.rs @@ -9,19 +9,37 @@ // On Windows platform, don't show a console when opening the app. #![windows_subsystem = "windows"] +use masonry::app_driver::{AppDriver, DriverCtx}; +use masonry::event_loop_runner::EventLoopRunner; use masonry::widget::{FillStrat, Image}; -use masonry::{AppLauncher, ImageBuf, WindowDescription}; +use masonry::{Action, WidgetId}; +use vello::peniko::{Format, Image as ImageBuf}; +use winit::dpi::LogicalSize; +use winit::event_loop::EventLoop; +use winit::window::WindowBuilder; + +struct Driver; + +impl AppDriver for Driver { + fn on_action(&mut self, _ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, _action: Action) {} +} pub fn main() { - let png_data = ImageBuf::from_data(include_bytes!("./assets/PicWithAlpha.png")).unwrap(); + let image_bytes = include_bytes!("./assets/PicWithAlpha.png"); + let image_data = image::load_from_memory(image_bytes).unwrap().to_rgba8(); + let (width, height) = image_data.dimensions(); + let png_data = ImageBuf::new(image_data.to_vec().into(), Format::Rgba8, width, height); let image = Image::new(png_data).fill_mode(FillStrat::Contain); - let main_window = WindowDescription::new(image) - .window_size((650., 450.)) - .title("Flex Container Options"); + let event_loop = EventLoop::new().unwrap(); + let window_size = LogicalSize::new(650.0, 450.0); + let window = WindowBuilder::new() + .with_title("Simple image example") + .with_min_inner_size(window_size) + .with_max_inner_size(window_size) + .build(&event_loop) + .unwrap(); - AppLauncher::with_window(main_window) - .log_to_console() - .launch() - .expect("Failed to launch application"); + let runner = EventLoopRunner::new(image, window, event_loop, Driver); + runner.run().unwrap(); } diff --git a/examples/textbox.rs b/examples/textbox.rs deleted file mode 100644 index df4ba1f7..00000000 --- a/examples/textbox.rs +++ /dev/null @@ -1,60 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -// On Windows platform, don't show a console when opening the app. -#![windows_subsystem = "windows"] - -// TODO - rework imports - See #14 -use masonry::widget::prelude::*; -use masonry::widget::{Button, Flex, TextBox}; -use masonry::{Action, AppDelegate, AppLauncher, DelegateCtx, WindowDescription, WindowId}; - -const VERTICAL_WIDGET_SPACING: f64 = 20.0; - -struct Delegate; - -impl AppDelegate for Delegate { - fn on_action( - &mut self, - _ctx: &mut DelegateCtx, - _window_id: WindowId, - _widget_id: WidgetId, - action: Action, - ) { - match action { - Action::ButtonPressed => { - // TODO - Print textbox contents - println!("Hello"); - } - _ => {} - } - } -} - -pub fn main() { - // describe the main window - let main_window = WindowDescription::new(build_root_widget()) - .title("Hello World!") - .window_size((400.0, 400.0)); - - // start the application. Here we pass in the application state. - AppLauncher::with_window(main_window) - .with_delegate(Delegate) - .log_to_console() - .launch() - .expect("Failed to launch application"); -} - -fn build_root_widget() -> impl Widget { - let label = TextBox::new("").with_placeholder("Some text"); - - // a button that says "hello" - let button = Button::new("Say hello"); - - // arrange the two widgets vertically, with some padding - Flex::column() - .with_child(label) - .with_spacer(VERTICAL_WIDGET_SPACING) - .with_child(button) -} diff --git a/examples/to_do_list.rs b/examples/to_do_list.rs deleted file mode 100644 index 95b85683..00000000 --- a/examples/to_do_list.rs +++ /dev/null @@ -1,89 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -#![windows_subsystem = "windows"] - -use masonry::widget::{ - Button, CrossAxisAlignment, Flex, Label, Portal, SizedBox, TextBox, WidgetMut, -}; -use masonry::{ - Action, AppDelegate, AppLauncher, Color, DelegateCtx, WidgetId, WindowDescription, WindowId, -}; - -struct Delegate { - next_task: String, -} - -impl AppDelegate for Delegate { - fn on_action( - &mut self, - ctx: &mut DelegateCtx, - _window_id: WindowId, - _widget_id: WidgetId, - action: Action, - ) { - match action { - Action::ButtonPressed | Action::TextEntered(_) => { - let mut root: WidgetMut> = ctx.get_root(); - if !self.next_task.is_empty() { - let mut flex = root.child_mut(); - flex.child_mut(2) - .unwrap() - .downcast::() - .unwrap() - .child_mut() - .unwrap() - .downcast::() - .unwrap() - .add_child(Label::new(self.next_task.clone())); - } - } - Action::TextChanged(new_text) => { - self.next_task = new_text; - } - _ => {} - } - } -} - -fn main() { - const GAP_SIZE: f64 = 4.0; - const LIGHT_GRAY: Color = Color::rgb8(0x71, 0x71, 0x71); - // The main button with some space below, all inside a scrollable area. - let root_widget = Portal::new( - Flex::column() - .with_child( - SizedBox::new( - Flex::row() - .with_child(Button::new("Add task")) - .with_spacer(5.0) - .with_flex_child(TextBox::new(""), 1.0), - ) - .border(LIGHT_GRAY, GAP_SIZE), - ) - .with_spacer(GAP_SIZE) - .with_child( - SizedBox::new( - Flex::column() - .cross_axis_alignment(CrossAxisAlignment::Start) - .with_child(Label::new("List items:")), - ) - .expand_width() - .border(LIGHT_GRAY, GAP_SIZE), - ), - ) - .constrain_horizontal(true); - - let main_window = WindowDescription::new(root_widget) - .title("To-do list") - .window_size((400.0, 400.0)); - - AppLauncher::with_window(main_window) - .with_delegate(Delegate { - next_task: String::new(), - }) - .log_to_console() - .launch() - .expect("Failed to launch application"); -} diff --git a/src/action.rs b/src/action.rs index e23437d2..5c8573bc 100644 --- a/src/action.rs +++ b/src/action.rs @@ -3,11 +3,8 @@ // details. use std::any::Any; -use std::collections::VecDeque; use std::sync::Arc; -use crate::{WidgetId, WindowId}; - // TODO - Refactor - See issue #1 // TODO - TextCursor changed, ImeChanged, EnterKey, MouseEnter @@ -51,6 +48,3 @@ impl std::fmt::Debug for Action { } } } - -/// Our queue type -pub(crate) type ActionQueue = VecDeque<(Action, WidgetId, WindowId)>; diff --git a/src/app_delegate.rs b/src/app_delegate.rs deleted file mode 100644 index 6eb99ccf..00000000 --- a/src/app_delegate.rs +++ /dev/null @@ -1,127 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -#![allow(unused)] -use std::collections::HashMap; - -use tracing::trace; - -use crate::action::Action; -use crate::command::{Command, CommandQueue}; -use crate::ext_event::{ExtEventQueue, ExtEventSink}; -use crate::widget::{StoreInWidgetMut, WidgetMut, WidgetRef}; -use crate::{ - Event, Handled, SingleUse, Target, Widget, WidgetId, WindowDescription, WindowId, WindowRoot, -}; - -/// A context provided to [`AppDelegate`] methods. -pub struct DelegateCtx<'a, 'b> { - //pub(crate) command_queue: &'a mut CommandQueue, - pub(crate) ext_event_queue: &'a ExtEventQueue, - // FIXME - Ideally, we'd like to get a hashmap of all root widgets, - // but that creates "aliasing mutable references" problems - // See issue #17 - pub(crate) main_root_widget: WidgetMut<'a, 'b, Box>, - //pub(crate) active_windows: &'a mut HashMap, -} - -impl<'a, 'b> DelegateCtx<'a, 'b> { - #[cfg(FALSE)] - pub fn submit_command(&mut self, command: impl Into) { - self.command_queue - .push_back(command.into().default_to(Target::Global)) - } - - /// Return an [`ExtEventSink`] that can be moved between threads, - /// and can be used to submit commands back to the application. - pub fn get_external_handle(&self) -> ExtEventSink { - self.ext_event_queue.make_sink() - } - - #[cfg(FALSE)] - pub fn new_window(&mut self, desc: WindowDescription) { - trace!("new_window"); - self.submit_command( - crate::command::NEW_WINDOW - .with(SingleUse::new(Box::new(desc))) - .to(Target::Global), - ); - } - - // TODO - Use static typing to guarantee proper return type - See issue #17 - /// Try to return a [`WidgetMut`] to the root widget. - /// - /// Returns null if the returned type doesn't match the root widget type. - pub fn try_get_root(&mut self) -> Option> { - self.main_root_widget.downcast() - } - - /// Return a [`WidgetMut`] to the root widget. - /// - /// ## Panics - /// - /// Panics if the returned type doesn't match the root widget type. - pub fn get_root(&mut self) -> WidgetMut<'_, 'b, W> { - self.main_root_widget.downcast().expect("wrong widget type") - } -} - -/// A type that provides hooks for handling top-level events. -/// -/// The `AppDelegate` is a trait that is allowed to handle and filter events before -/// they are passed down the widget tree. -pub trait AppDelegate { - /// The handler for non-command [`Event`]s. - /// - /// This function receives all non-command events, before they are passed down - /// the tree. If it returns [`Handled::Yes`], events are short-circuited. - fn on_event(&mut self, ctx: &mut DelegateCtx, window_id: WindowId, event: &Event) -> Handled { - #![allow(unused)] - Handled::No - } - - /// The handler for [`Command`]s. - /// - /// This function receives all command events, before they are passed down - /// the tree. If it returns [`Handled::Yes`], commands are short-circuited. - fn on_command(&mut self, ctx: &mut DelegateCtx, cmd: &Command) -> Handled { - #![allow(unused)] - Handled::No - } - - /// The handler for [`Action`]s. - /// - /// Note: Actions are still a WIP part of masonry. - fn on_action( - &mut self, - ctx: &mut DelegateCtx, - window_id: WindowId, - widget_id: WidgetId, - action: Action, - ) { - #![allow(unused)] - } - - /// The handler for window creation events. - /// - /// This function is called after a window has been added, - /// allowing you to customize the window creation behavior of your app. - fn on_window_added(&mut self, ctx: &mut DelegateCtx, id: WindowId) { - #![allow(unused)] - } - - /// The handler for window deletion events. - /// - /// This function is called after a window has been removed. - fn on_window_removed(&mut self, ctx: &mut DelegateCtx, id: WindowId) { - #![allow(unused)] - } -} - -// TODO - impl AppDelegate for FnMut - -// TODO - document -pub(crate) struct NullDelegate; - -impl AppDelegate for NullDelegate {} diff --git a/src/app_driver.rs b/src/app_driver.rs new file mode 100644 index 00000000..ec889302 --- /dev/null +++ b/src/app_driver.rs @@ -0,0 +1,20 @@ +use crate::widget::{StoreInWidgetMut, WidgetMut}; +use crate::{Action, Widget, WidgetId}; + +// xilem::App will implement AppDriver + +pub struct DriverCtx<'a> { + // TODO + pub(crate) main_root_widget: WidgetMut<'a, Box>, +} + +pub trait AppDriver { + fn on_action(&mut self, ctx: &mut DriverCtx<'_>, widget_id: WidgetId, action: Action); +} + +impl<'a> DriverCtx<'a> { + /// Return a [`WidgetMut`] to the root widget. + pub fn get_root(&mut self) -> WidgetMut<'_, W> { + self.main_root_widget.downcast().expect("wrong widget type") + } +} diff --git a/src/app_launcher.rs b/src/app_launcher.rs deleted file mode 100644 index f56626de..00000000 --- a/src/app_launcher.rs +++ /dev/null @@ -1,96 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -use druid_shell::{Application as AppHandle, Error as PlatformError}; - -use crate::app_delegate::AppDelegate; -use crate::app_root::AppRoot; -use crate::ext_event::{ExtEventQueue, ExtEventSink}; -use crate::platform::{MasonryAppHandler, WindowDescription}; - -/// Handles initial setup of an application, and starts the runloop. -pub struct AppLauncher { - windows: Vec, - app_delegate: Option>, - ext_event_queue: ExtEventQueue, -} - -impl AppLauncher { - /// Create a new `AppLauncher` with the provided window. - pub fn with_window(window: WindowDescription) -> Self { - AppLauncher { - windows: vec![window], - app_delegate: None, - ext_event_queue: ExtEventQueue::new(), - } - } - - /// Set the [`AppDelegate`]. - /// - /// [`AppDelegate`]: trait.AppDelegate.html - pub fn with_delegate(mut self, delegate: impl AppDelegate + 'static) -> Self { - self.app_delegate = Some(Box::new(delegate)); - self - } - - /// Initialize a minimal tracing subscriber with DEBUG max level for printing logs out to - /// stderr. - /// - /// This is meant for quick-and-dirty debugging. If you want more serious trace handling, - /// it's probably better to implement it yourself. - /// - /// # Panics - /// - /// Panics if the subscriber fails to initialize. - pub fn log_to_console(self) -> Self { - #[cfg(not(target_arch = "wasm32"))] - { - use tracing_subscriber::prelude::*; - let filter_layer = tracing_subscriber::filter::LevelFilter::DEBUG; - let fmt_layer = tracing_subscriber::fmt::layer() - // Display target (eg "my_crate::some_mod::submod") with logs - .with_target(true); - - tracing_subscriber::registry() - .with(filter_layer) - .with(fmt_layer) - .init(); - } - // Note - tracing-wasm might not work in headless Node.js. Probably doesn't matter anyway, - // because this is a GUI framework, so wasm targets will virtually always be browsers. - #[cfg(target_arch = "wasm32")] - { - console_error_panic_hook::set_once(); - let config = tracing_wasm::WASMLayerConfigBuilder::new() - .set_max_level(tracing::Level::DEBUG) - .build(); - tracing_wasm::set_as_global_default_with_config(config) - } - self - } - - /// Returns an [`ExtEventSink`] that can be moved between threads, - /// and can be used to submit commands back to the application. - pub fn get_external_handle(&self) -> ExtEventSink { - self.ext_event_queue.make_sink() - } - - /// Build the windows and start the runloop. - /// - /// Returns an error if a window cannot be instantiated. This is usually - /// a fatal error. - pub fn launch(self) -> Result<(), PlatformError> { - let app = AppHandle::new()?; - let state = AppRoot::create( - app.clone(), - self.windows, - self.app_delegate, - self.ext_event_queue, - )?; - let handler = MasonryAppHandler::new(state); - - app.run(Some(Box::new(handler))); - Ok(()) - } -} diff --git a/src/app_root.rs b/src/app_root.rs deleted file mode 100644 index cba86a25..00000000 --- a/src/app_root.rs +++ /dev/null @@ -1,1494 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -// FIXME -#![allow(unused_imports)] -#![allow(unused_variables)] -#![allow(unused_mut)] -#![allow(dead_code)] - -use std::cell::{RefCell, RefMut}; -use std::collections::{HashMap, VecDeque}; -use std::ops::DerefMut; -use std::rc::Rc; - -use druid_shell::text::InputHandler; -// TODO - rename Application to AppHandle in glazier -// See https://github.com/linebender/glazier/issues/44 -use druid_shell::{Application as AppHandle, WindowHandle}; -use druid_shell::{ - Cursor, FileDialogToken, FileInfo, Region, TextFieldToken, TimerToken, WindowBuilder, -}; -// Automatically defaults to std::time::Instant on non Wasm platforms -use instant::Instant; -use tracing::{error, info, info_span}; - -use crate::action::ActionQueue; -use crate::app_delegate::{AppDelegate, DelegateCtx, NullDelegate}; -use crate::command::CommandQueue; -use crate::contexts::GlobalPassCtx; -use crate::debug_logger::DebugLogger; -use crate::ext_event::{ExtEventQueue, ExtEventSink, ExtMessage}; -use crate::kurbo::{Point, Size}; -use crate::piet::{Color, Piet, RenderContext}; -use crate::platform::{ - DialogInfo, WindowConfig, WindowSizePolicy, EXT_EVENT_IDLE_TOKEN, RUN_COMMANDS_TOKEN, -}; -use crate::testing::MockTimerQueue; -use crate::text::TextFieldRegistration; -use crate::widget::{FocusChange, StoreInWidgetMut, WidgetMut, WidgetRef, WidgetState}; -use crate::{ - command as sys_cmd, ArcStr, BoxConstraints, Command, Event, EventCtx, Handled, InternalEvent, - InternalLifeCycle, LayoutCtx, LifeCycle, LifeCycleCtx, MasonryWinHandler, PaintCtx, - PlatformError, Target, Widget, WidgetCtx, WidgetId, WidgetPod, WindowDescription, WindowId, -}; - -/// The type of a function that will be called once an IME field is updated. -pub type ImeUpdateFn = dyn FnOnce(druid_shell::text::Event); - -// TODO - Add AppRootEvent type - -// TODO - Explain and document re-entrancy and when locks should be used - See issue #16 - -// TODO - Delegate callbacks are shared between AppRoot and AppRootInner methods -// This muddles what part of the code has the responsibility of maintaining invariants - -/// State shared by all windows in the UI. -/// -/// This is an internal object that shouldn't be manipulated directly by the user. -#[derive(Clone)] -pub struct AppRoot { - inner: Rc>, -} - -struct AppRootInner { - app_handle: AppHandle, - debug_logger: DebugLogger, - app_delegate: Box, - command_queue: CommandQueue, - action_queue: ActionQueue, - ext_event_queue: ExtEventQueue, - file_dialogs: HashMap, - window_requests: VecDeque, - pending_windows: HashMap, - active_windows: HashMap, - // FIXME - remove - main_window_id: WindowId, - /// The id of the most-recently-focused window that has a menu. On macOS, this - /// is the window that's currently in charge of the app menu. - #[allow(unused)] - menu_window: Option, -} - -/// The parts of a window, pending construction, that are dependent on top level app state -/// or are not part of druid-shell's windowing abstraction. -struct PendingWindow { - root: Box, - title: ArcStr, - transparent: bool, - size_policy: WindowSizePolicy, -} - -// TODO - refactor out again -/// Per-window state not owned by user code. -/// -/// This is an internal object that shouldn't be manipulated directly by the user. -pub struct WindowRoot { - pub(crate) id: WindowId, - pub(crate) root: WidgetPod>, - pub(crate) title: ArcStr, - size_policy: WindowSizePolicy, - size: Size, - invalid: Region, - // Is `Some` if the most recently displayed frame was an animation frame. - pub(crate) last_anim: Option, - pub(crate) last_mouse_pos: Option, - pub(crate) focus: Option, - pub(crate) ext_event_sink: ExtEventSink, - pub(crate) handle: WindowHandle, - pub(crate) timers: HashMap, - // Used in unit tests - see `src/testing/mock_timer_queue.rs` - pub(crate) mock_timer_queue: Option, - pub(crate) transparent: bool, - pub(crate) ime_handlers: Vec<(TextFieldToken, TextFieldRegistration)>, - pub(crate) ime_focus_change: Option>, -} - -// --- - -// Public methods -// -// Each of these methods should handle post-event cleanup -// (eg invalidation regions, opening new windows, etc) -impl AppRoot { - /// Create new application. - pub(crate) fn create( - app: AppHandle, - windows: Vec, - app_delegate: Option>, - ext_event_queue: ExtEventQueue, - ) -> Result { - let inner = Rc::new(RefCell::new(AppRootInner { - app_handle: app, - debug_logger: DebugLogger::new(false), - app_delegate: app_delegate.unwrap_or_else(|| Box::new(NullDelegate)), - command_queue: VecDeque::new(), - action_queue: VecDeque::new(), - ext_event_queue, - file_dialogs: HashMap::new(), - // FIXME - this is awful - main_window_id: windows.first().unwrap().id, - menu_window: None, - window_requests: VecDeque::new(), - pending_windows: Default::default(), - active_windows: Default::default(), - })); - let mut app_root = AppRoot { inner }; - - for desc in windows { - let window = app_root.build_native_window(desc)?; - window.show(); - } - - Ok(app_root) - } - - /// Notify the app that a window was added and is now running in the platform. - /// - /// This should be called by the platform after processing from - /// [`druid_shell::WindowBuilder`] finishes. - pub fn window_connected(&mut self, window_id: WindowId, handle: WindowHandle) { - { - let mut inner = self.inner.borrow_mut(); - let inner = inner.deref_mut(); - - if let Some(pending) = inner.pending_windows.remove(&window_id) { - let win = WindowRoot::new( - window_id, - handle, - inner.ext_event_queue.make_sink(), - pending.root, - pending.title, - pending.transparent, - pending.size_policy, - None, - ); - let existing = inner.active_windows.insert(window_id, win); - debug_assert!(existing.is_none(), "duplicate window"); - } else { - tracing::error!("no window for connecting handle {:?}", window_id); - } - - // If the external event host has no handle, it cannot wake us - // when an event arrives. - if inner.ext_event_queue.handle_window_id.is_none() { - inner.set_ext_event_idle_handler(window_id); - } - } - - self.with_delegate(|delegate, ctx| delegate.on_window_added(ctx, window_id)); - - let event = Event::WindowConnected; - self.do_window_event(window_id, event); - - self.process_commands_and_actions(); - self.inner().invalidate_paint_regions(); - self.process_ime_changes(); - self.process_window_requests(); - } - - /// Notify the app that a window has been closed by the platform. - /// - /// AppRoot then cleans up resources. - pub fn window_removed(&mut self, window_id: WindowId) { - self.with_delegate(|delegate, ctx| delegate.on_window_removed(ctx, window_id)); - - let mut inner = self.inner.borrow_mut(); - inner.active_windows.remove(&window_id); - - // If there are no active or pending windows, we quit the run loop. - if inner.active_windows.is_empty() && inner.pending_windows.is_empty() { - #[cfg(any(target_os = "windows", feature = "x11"))] - inner.app_handle.quit(); - } - - // If we are closing the window that is currently responsible - // for waking us when external events arrive, we want to pass - // that responsibility to another window. - if inner.ext_event_queue.handle_window_id == Some(window_id) { - inner.ext_event_queue.handle_window_id = None; - // find any other live window - let win_id = inner.active_windows.keys().next(); - if let Some(any_other_window) = win_id.cloned() { - inner.set_ext_event_idle_handler(any_other_window); - } - } - } - - /// Notify the app that a window has acquired focus (eg the user clicked on it). - pub fn window_got_focus(&mut self, _window_id: WindowId) { - // TODO - menu stuff - } - - /// Send an event to the widget hierarchy. - /// - /// Returns [`Handled::Yes`] if the event produced an action. - /// - /// This is principally because in certain cases (such as keydown on Windows) - /// the OS needs to know if an event was handled. - pub fn handle_event(&mut self, event: Event, window_id: WindowId) -> Handled { - let result; - { - if let Event::Command(command) - | Event::Internal(InternalEvent::TargetedCommand(command)) = event - { - self.do_cmd(command); - result = Handled::Yes; - } else { - result = self.do_window_event(window_id, event); - }; - } - - self.process_commands_and_actions(); - self.inner().invalidate_paint_regions(); - self.process_ime_changes(); - self.process_window_requests(); - - result - } - - /// Handle a 'command' message from druid-shell. These map to an item - /// in an application, window, or context (right-click) menu. - /// - /// If the menu is associated with a window (the general case) then - /// the `window_id` will be `Some(_)`, otherwise (such as if no window - /// is open but a menu exists, as on macOS) it will be `None`. - pub fn handle_system_cmd(&mut self, cmd_id: u32, window_id: Option) { - #![allow(unused_variables)] - todo!(); - } - - // TODO - Promises - /// Notify the app that the user has closed a given dialog popup. - /// - /// This gives the user both a token referring to the given dialog and - /// the [`FileInfo`] representing which file(s) the user chose. - pub fn handle_dialog_response(&mut self, token: FileDialogToken, file_info: Option) { - let dialog_info = self.inner().file_dialogs.remove(&token); - if let Some(dialog_info) = dialog_info { - let cmd = if let Some(info) = file_info { - dialog_info.accept_cmd.with(info).to(dialog_info.id) - } else { - dialog_info.cancel_cmd.to(dialog_info.id) - }; - self.do_cmd(cmd); - self.process_commands_and_actions(); - self.process_ime_changes(); - self.inner().invalidate_paint_regions(); - } else { - tracing::error!("unknown dialog token"); - } - } - - /// Run some computations before painting a given window. - /// - /// Must be called once per frame for each window. - /// - /// Currently, this computes layout and runs an animation frame. - pub fn prepare_paint(&mut self, window_id: WindowId) { - { - let mut inner = self.inner.borrow_mut(); - let inner = inner.deref_mut(); - if let Some(win) = inner.active_windows.get_mut(&window_id) { - win.prepare_paint( - &mut inner.debug_logger, - &mut inner.command_queue, - &mut inner.action_queue, - ); - } - inner.invalidate_paint_regions(); - } - self.process_window_requests(); - } - - /// Paint a given window's contents. - /// - /// Currently, this computes layout if needed and calls paint methods in the - /// widget hierarchy. - pub fn paint(&mut self, window_id: WindowId, piet: &mut Piet, invalid: &Region) { - let mut inner = self.inner.borrow_mut(); - let inner = inner.deref_mut(); - if let Some(win) = inner.active_windows.get_mut(&window_id) { - win.do_paint( - piet, - invalid, - &mut inner.debug_logger, - &mut inner.command_queue, - &mut inner.action_queue, - ); - } - } - - /// Run any leftover commands from previous events. - pub fn run_commands(&mut self) { - self.process_commands_and_actions(); - self.inner().invalidate_paint_regions(); - self.process_ime_changes(); - self.process_window_requests(); - } - - /// Run any events in the background event queue, usually sent by a background thread. - pub fn run_ext_events(&mut self) { - self.process_ext_events(); - self.process_commands_and_actions(); - self.inner().invalidate_paint_regions(); - self.process_ime_changes(); - self.process_window_requests(); - } - - #[allow(missing_docs)] - pub fn ime_update_fn( - &self, - window_id: WindowId, - widget_id: WidgetId, - ) -> Option> { - let mut inner = self.inner.borrow_mut(); - let window = inner.active_windows.get(&window_id)?; - window.ime_invalidation_fn(widget_id) - } - - #[allow(missing_docs)] - pub fn get_ime_lock( - &mut self, - window_id: WindowId, - token: TextFieldToken, - mutable: bool, - ) -> Box { - let mut inner = self.inner.borrow_mut(); - inner - .active_windows - .get_mut(&window_id) - .unwrap() - .get_ime_handler(token, mutable) - } - - /// Returns a `WidgetId` if the lock was mutable; the widget should be updated. - pub fn release_ime_lock( - &mut self, - window_id: WindowId, - token: TextFieldToken, - ) -> Option { - let mut inner = self.inner.borrow_mut(); - inner - .active_windows - .get_mut(&window_id) - .unwrap() - .release_ime_lock(token) - } -} - -// Internal functions -impl AppRoot { - fn inner(&self) -> RefMut<'_, AppRootInner> { - self.inner.borrow_mut() - } - - // TODO - rename? - fn process_commands_and_actions(&mut self) { - loop { - let next_cmd = self.inner().command_queue.pop_front(); - if let Some(cmd) = next_cmd { - self.do_cmd(cmd); - continue; - } - - let next_action = self.inner().action_queue.pop_front(); - if let Some((action, widget_id, window_id)) = next_action { - self.with_delegate(|delegate, ctx| { - delegate.on_action(ctx, window_id, widget_id, action) - }); - continue; - } - - // else - no more commands or actions - break; - } - } - - fn process_ext_events(&mut self) { - loop { - let ext_cmd = self.inner().ext_event_queue.recv(); - match ext_cmd { - Some(ExtMessage::Command(selector, payload, target)) => { - self.do_cmd(Command::from_ext(selector, payload, target)) - } - Some(ExtMessage::Promise(promise_result, widget_id, window_id)) => { - // TODO - self.do_window_event( - window_id, - Event::Internal(InternalEvent::RoutePromiseResult( - promise_result, - widget_id, - )), - ); - } - None => break, - } - } - } - - fn process_ime_changes(&mut self) { - let mut ime_focus_change_fns: Vec> = vec![]; - - for window in self.inner().active_windows.values_mut() { - if let Some(focus_change) = window.ime_focus_change.take() { - // The handle.set_focused_text_field method may call WindowHandle - // methods which may be reentrant (depending on the platform). - // So we clone the window handle and defer calling set_focused_text_field. - let handle = window.handle.clone(); - let f = Box::new(move || handle.set_focused_text_field(focus_change)); - ime_focus_change_fns.push(f); - } - } - - for ime_focus_change_fn in ime_focus_change_fns { - (ime_focus_change_fn)() - } - } - - /// Handle a command. Top level commands (e.g. for creating and destroying - /// windows) have their logic here; other commands are passed to the window. - fn do_cmd(&mut self, cmd: Command) { - if self.with_delegate(|delegate, ctx| delegate.on_command(ctx, &cmd)) == Handled::Yes { - return; - } - - use Target as T; - match cmd.target() { - // these are handled the same no matter where they come from - _ if cmd.is(sys_cmd::QUIT_APP) => self.inner().app_handle.quit(), - #[cfg(target_os = "macos")] - _ if cmd.is(sys_cmd::HIDE_APPLICATION) => self.inner().hide_app(), - #[cfg(target_os = "macos")] - _ if cmd.is(sys_cmd::HIDE_OTHERS) => self.inner().hide_others(), - _ if cmd.is(sys_cmd::NEW_WINDOW) => { - self.inner().request_new_window(cmd); - } - _ if cmd.is(sys_cmd::CLOSE_ALL_WINDOWS) => self.inner().request_close_all_windows(), - //T::Window(id) if cmd.is(sys_cmd::INVALIDATE_IME) => self.inner().invalidate_ime(cmd, id), - // these should come from a window - // FIXME: we need to be able to open a file without a window handle - // TODO - uncomment - //T::Window(id) if cmd.is(sys_cmd::SHOW_OPEN_PANEL) => self.inner().show_open_panel(cmd, id), - //T::Window(id) if cmd.is(sys_cmd::SHOW_SAVE_PANEL) => self.inner().show_save_panel(cmd, id), - //T::Window(id) if cmd.is(sys_cmd::CONFIGURE_WINDOW) => self.inner().request_configure_window(cmd, id), - T::Window(id) if cmd.is(sys_cmd::CLOSE_WINDOW) => { - self.inner().request_close_window(id); - } - T::Window(id) if cmd.is(sys_cmd::SHOW_WINDOW) => self.inner().request_show_window(id), - //T::Window(id) if cmd.is(sys_cmd::PASTE) => self.inner().do_paste(id), - _ if cmd.is(sys_cmd::CLOSE_WINDOW) => { - tracing::warn!("CLOSE_WINDOW command must target a window.") - } - _ if cmd.is(sys_cmd::SHOW_WINDOW) => { - tracing::warn!("SHOW_WINDOW command must target a window.") - } - // TODO - uncomment - /* - _ if cmd.is(sys_cmd::SHOW_OPEN_PANEL) => { - tracing::warn!("SHOW_OPEN_PANEL command must target a window.") - } - */ - _ => { - self.inner().dispatch_cmd(cmd); - } - } - } - - fn do_window_event(&mut self, source_id: WindowId, event: Event) -> Handled { - if matches!( - event, - Event::Command(..) | Event::Internal(InternalEvent::TargetedCommand(..)) - ) { - unreachable!("commands should be dispatched via dispatch_cmd"); - } - - if self.with_delegate(|delegate, ctx| delegate.on_event(ctx, source_id, &event)) - == Handled::Yes - { - return Handled::Yes; - } - - let mut inner = self.inner.borrow_mut(); - let inner = inner.deref_mut(); - - if let Some(win) = inner.active_windows.get_mut(&source_id) { - win.event( - event, - &mut inner.debug_logger, - &mut inner.command_queue, - &mut inner.action_queue, - ) - } else { - // TODO - error message? - Handled::No - } - } - - /// A helper fn for setting up the `DelegateCtx`. Takes a closure with - /// an arbitrary return type `R`, and returns `Some(R)` if an `AppDelegate` - /// is configured. - fn with_delegate( - &mut self, - f: impl FnOnce(&mut dyn AppDelegate, &mut DelegateCtx) -> R, - ) -> R { - let mut inner = self.inner.borrow_mut(); - let inner = inner.deref_mut(); - - let mut window = inner.active_windows.get_mut(&inner.main_window_id).unwrap(); - let mut fake_widget_state; - let res = { - let mut global_state = GlobalPassCtx::new( - window.ext_event_sink.clone(), - &mut inner.debug_logger, - &mut inner.command_queue, - &mut inner.action_queue, - &mut window.timers, - window.mock_timer_queue.as_mut(), - &window.handle, - inner.main_window_id, - window.focus, - ); - fake_widget_state = window.root.state.clone(); - - let main_root_ctx = WidgetCtx { - global_state: &mut global_state, - widget_state: &mut window.root.state, - }; - let main_root_widget = WidgetMut { - parent_widget_state: &mut fake_widget_state, - inner: Box::from_widget_and_ctx(&mut window.root.inner, main_root_ctx), - }; - - let mut ctx = DelegateCtx { - //command_queue: &mut inner.command_queue, - ext_event_queue: &mut inner.ext_event_queue, - main_root_widget, - }; - - f(&mut *inner.app_delegate, &mut ctx) - }; - - // TODO - handle cursor and validation - - window.post_event_processing( - &mut fake_widget_state, - &mut inner.debug_logger, - &mut inner.command_queue, - &mut inner.action_queue, - false, - ); - - res - } - - // -- Handle "new window" requests -- - - fn process_window_requests(&mut self) { - let window_requests = std::mem::take(&mut self.inner.borrow_mut().window_requests); - for window_desc in window_requests.into_iter() { - match self.build_native_window(window_desc) { - Ok(window) => window.show(), - Err(err) => tracing::error!("failed to create window: '{err}'"), - }; - } - } - - // TODO - document why process_window_requests/build_native_window - fn build_native_window( - &mut self, - desc: WindowDescription, - ) -> Result { - let root = desc.root; - let title = desc.title; - let config = desc.config; - let id = desc.id; - - let mut builder = WindowBuilder::new(self.inner.borrow().app_handle.clone()); - config.apply_to_builder(&mut builder); - builder.set_title(title.to_string()); - - let handler = MasonryWinHandler::new_shared(self.clone(), id); - builder.set_handler(Box::new(handler)); - - let pending = PendingWindow { - root, - title, - transparent: config.transparent.unwrap_or(false), - size_policy: config.size_policy, - }; - - let existing = self.inner.borrow_mut().pending_windows.insert(id, pending); - assert!(existing.is_none(), "duplicate pending window {id:?}"); - - builder.build() - } -} - -impl AppRootInner { - /// invalidate any window handles that need it. - /// - /// This should always be called at the end of an event update cycle, - /// including for lifecycle events. - fn invalidate_paint_regions(&mut self) { - for win in self.active_windows.values_mut() { - win.invalidate_paint_region(); - } - } - - /// Set the idle handle that will be used to wake us when external events arrive. - fn set_ext_event_idle_handler(&mut self, id: WindowId) { - if let Some(mut idle) = self - .active_windows - .get_mut(&id) - .and_then(|win| win.handle.get_idle_handle()) - { - if self.ext_event_queue.has_pending_items() { - idle.schedule_idle(EXT_EVENT_IDLE_TOKEN); - } - self.ext_event_queue.set_idle(idle, id); - } - } - - fn request_new_window(&mut self, cmd: Command) { - let desc = cmd.get(sys_cmd::NEW_WINDOW); - // The NEW_WINDOW command is private and only masonry should be able to send it, - // so we can use .unwrap() here - let desc = *desc - .take() - .unwrap() - .downcast::() - .unwrap(); - self.window_requests.push_back(desc); - } - - /// triggered by a menu item or other command. - /// - /// This doesn't close the window; it calls the close method on the platform - /// window handle; the platform should close the window, and then call - /// our handlers `destroy()` method, at which point we can do our cleanup. - fn request_close_window(&mut self, window_id: WindowId) { - if let Some(window) = self.active_windows.get_mut(&window_id) { - let handled = window.event( - Event::WindowCloseRequested, - &mut self.debug_logger, - &mut self.command_queue, - &mut self.action_queue, - ); - if !handled.is_handled() { - window.event( - Event::WindowDisconnected, - &mut self.debug_logger, - &mut self.command_queue, - &mut self.action_queue, - ); - window.handle.close(); - } - } else { - tracing::warn!("Failed to close {window_id:?}: no active window with this id"); - } - } - - // TODO - same confirmation code as request_close_window? - /// Requests the platform to close all windows. - fn request_close_all_windows(&mut self) { - for win in self.active_windows.values_mut() { - win.handle.close(); - } - } - - fn request_show_window(&mut self, id: WindowId) { - if let Some(win) = self.active_windows.get_mut(&id) { - win.handle.bring_to_front_and_focus(); - } - } - - fn request_configure_window(&mut self, config: &WindowConfig, id: WindowId) { - if let Some(win) = self.active_windows.get_mut(&id) { - config.apply_to_handle(&mut win.handle); - } - } - - fn dispatch_cmd(&mut self, cmd: Command) -> Handled { - self.invalidate_paint_regions(); - match cmd.target() { - Target::Global => { - for w in self.active_windows.values_mut() { - if w.event( - Event::Command(cmd.clone()), - &mut self.debug_logger, - &mut self.command_queue, - &mut self.action_queue, - ) - .is_handled() - { - return Handled::Yes; - } - } - return Handled::No; - } - Target::Window(id) => { - if let Some(w) = self.active_windows.get_mut(&id) { - return w.event( - Event::Command(cmd), - &mut self.debug_logger, - &mut self.command_queue, - &mut self.action_queue, - ); - } - } - // in this case we send it to every window that might contain - // this widget, breaking if the event is handled. - Target::Widget(id) => { - for w in self - .active_windows - .values_mut() - .filter(|w| w.may_contain_widget(id)) - { - let event = Event::Internal(InternalEvent::TargetedCommand(cmd.clone())); - if w.event( - event, - &mut self.debug_logger, - &mut self.command_queue, - &mut self.action_queue, - ) - .is_handled() - { - return Handled::Yes; - } - } - } - Target::Auto => { - tracing::error!("{:?} reached window handler with `Target::Auto`", cmd); - } - } - Handled::No - } - - #[cfg(FALSE)] - fn show_open_panel(&mut self, cmd: Command, window_id: WindowId) { - let options = cmd.get(sys_cmd::SHOW_OPEN_PANEL).to_owned(); - let handle = self - .inner - .borrow_mut() - .active_windows - .get_mut(&window_id) - .map(|w| w.handle.clone()); - - let accept_cmd = options.accept_cmd.unwrap_or(sys_cmd::OPEN_FILE); - let cancel_cmd = options.cancel_cmd.unwrap_or(sys_cmd::OPEN_PANEL_CANCELLED); - let token = handle.and_then(|mut handle| handle.open_file(options.opt)); - if let Some(token) = token { - self.root().file_dialogs.insert( - token, - DialogInfo { - id: window_id, - accept_cmd, - cancel_cmd, - }, - ); - } - } - - #[cfg(FALSE)] - fn show_save_panel(&mut self, cmd: Command, window_id: WindowId) { - let options = cmd.get(sys_cmd::SHOW_SAVE_PANEL).to_owned(); - let handle = self - .inner - .borrow_mut() - .active_windows - .get_mut(&window_id) - .map(|w| w.handle.clone()); - let accept_cmd = options.accept_cmd.unwrap_or(sys_cmd::SAVE_FILE_AS); - let cancel_cmd = options.cancel_cmd.unwrap_or(sys_cmd::SAVE_PANEL_CANCELLED); - let token = handle.and_then(|mut handle| handle.save_as(options.opt)); - if let Some(token) = token { - self.root().file_dialogs.insert( - token, - DialogInfo { - id: window_id, - accept_cmd, - cancel_cmd, - }, - ); - } - } - - #[cfg(target_os = "macos")] - fn hide_app(&self) { - use druid_shell::platform::mac::ApplicationExt as _; - self.app_handle.hide(); - } - - #[cfg(target_os = "macos")] - fn hide_others(&mut self) { - use druid_shell::platform::mac::ApplicationExt as _; - self.app_handle.hide_others(); - } -} - -// --- - -impl WindowRoot { - #[allow(clippy::too_many_arguments)] - pub(crate) fn new( - id: WindowId, - handle: WindowHandle, - ext_event_sink: ExtEventSink, - root: Box, - title: ArcStr, - transparent: bool, - size_policy: WindowSizePolicy, - mock_timer_queue: Option, - ) -> WindowRoot { - WindowRoot { - id, - root: WidgetPod::new(root), - size_policy, - size: Size::ZERO, - invalid: Region::EMPTY, - title, - transparent, - last_anim: None, - last_mouse_pos: None, - focus: None, - ext_event_sink, - handle, - timers: HashMap::new(), - mock_timer_queue, - ime_handlers: Vec::new(), - ime_focus_change: None, - } - } - - // TODO - Add 'get_global_ctx() -> GlobalPassCtx' method - - /// `true` iff any child requested an animation frame since the last `AnimFrame` event. - pub(crate) fn wants_animation_frame(&self) -> bool { - self.root.state().request_anim - } - - pub(crate) fn focus_chain(&self) -> &[WidgetId] { - &self.root.state().focus_chain - } - - /// Returns `true` if the provided widget may be in this window, - /// but it may also be a false positive. - /// However when this returns `false` the widget is definitely not in this window. - pub(crate) fn may_contain_widget(&self, widget_id: WidgetId) -> bool { - // The bloom filter we're checking can return false positives. - widget_id == self.root.id() || self.root.state().children.may_contain(&widget_id) - } - - pub(crate) fn post_event_processing( - &mut self, - widget_state: &mut WidgetState, - debug_logger: &mut DebugLogger, - command_queue: &mut CommandQueue, - action_queue: &mut ActionQueue, - process_commands: bool, - ) { - // If children are changed during the handling of an event, - // we need to send RouteWidgetAdded now, so that they are ready for update/layout. - if widget_state.children_changed { - // Anytime widgets are removed we check and see if any of those - // widgets had IME sessions and unregister them if so. - let WindowRoot { - ime_handlers, - handle, - .. - } = self; - ime_handlers.retain(|(token, v)| { - let will_retain = v.is_alive(); - if !will_retain { - tracing::debug!("{:?} removed", token); - handle.remove_text_field(*token); - } - will_retain - }); - - self.lifecycle( - &LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded), - debug_logger, - command_queue, - action_queue, - false, - ); - } - - if debug_logger.layout_tree.root.is_none() { - debug_logger.layout_tree.root = Some(self.root.id().to_raw() as u32); - } - - if self.root.state().needs_window_origin && !self.root.state().needs_layout { - let event = LifeCycle::Internal(InternalLifeCycle::ParentWindowOrigin); - self.lifecycle(&event, debug_logger, command_queue, action_queue, false); - } - - // Update the disabled state if necessary - // Always do this before updating the focus-chain - if self.root.state().tree_disabled_changed() { - let event = LifeCycle::Internal(InternalLifeCycle::RouteDisabledChanged); - self.lifecycle(&event, debug_logger, command_queue, action_queue, false); - } - - // Update the focus-chain if necessary - // Always do this before sending focus change, since this event updates the focus chain. - if self.root.state().update_focus_chain { - let event = LifeCycle::BuildFocusChain; - self.lifecycle(&event, debug_logger, command_queue, action_queue, false); - } - - self.update_focus(widget_state, debug_logger, command_queue, action_queue); - - // If we need a new paint pass, make sure druid-shell knows it. - if self.wants_animation_frame() { - self.handle.request_anim_frame(); - } - self.invalid.union_with(&widget_state.invalid); - for ime_field in widget_state.text_registrations.drain(..) { - let token = self.handle.add_text_field(); - tracing::debug!("{:?} added", token); - self.ime_handlers.push((token, ime_field)); - } - - // If there are any commands and they should be processed - if process_commands && !command_queue.is_empty() { - // Ask the handler to call us back on idle - // so we can process them in a new event/update pass. - if let Some(mut handle) = self.handle.get_idle_handle() { - handle.schedule_idle(RUN_COMMANDS_TOKEN); - } else { - // FIXME - probably messes with tests - error!("failed to get idle handle"); - } - } - } - - pub(crate) fn event( - &mut self, - event: Event, - debug_logger: &mut DebugLogger, - command_queue: &mut CommandQueue, - action_queue: &mut ActionQueue, - ) -> Handled { - match &event { - Event::WindowSize(size) => self.size = *size, - Event::MouseDown(e) | Event::MouseUp(e) | Event::MouseMove(e) | Event::Wheel(e) => { - self.last_mouse_pos = Some(e.pos) - } - Event::Internal(InternalEvent::MouseLeave) => self.last_mouse_pos = None, - _ => (), - } - - let event = match event { - Event::Timer(token) => { - if let Some(widget_id) = self.timers.get(&token) { - Event::Internal(InternalEvent::RouteTimer(token, *widget_id)) - } else { - error!("No widget found for timer {:?}", token); - return Handled::No; - } - } - other => other, - }; - - if let Event::WindowConnected = event { - self.lifecycle( - &LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded), - debug_logger, - command_queue, - action_queue, - false, - ); - } - - let mut widget_state = WidgetState::new(self.root.id(), Some(self.size), ""); - let is_handled = { - let mut global_state = GlobalPassCtx::new( - self.ext_event_sink.clone(), - debug_logger, - command_queue, - action_queue, - &mut self.timers, - self.mock_timer_queue.as_mut(), - &self.handle, - self.id, - self.focus, - ); - let mut notifications = VecDeque::new(); - - let mut ctx = EventCtx { - global_state: &mut global_state, - widget_state: &mut widget_state, - notifications: &mut notifications, - is_handled: false, - is_root: true, - request_pan_to_child: None, - }; - - { - ctx.global_state - .debug_logger - .push_important_span(&format!("EVENT {}", event.short_name())); - let _span = info_span!("event").entered(); - self.root.on_event(&mut ctx, &event); - ctx.global_state.debug_logger.pop_span(); - } - - if !ctx.notifications.is_empty() { - info!("{} unhandled notifications:", ctx.notifications.len()); - for (i, n) in ctx.notifications.iter().enumerate() { - info!("{}: {:?}", i, n); - } - } - - Handled::from(ctx.is_handled) - }; - - // Clean up the timer token and do it immediately after the event handling - // because the token may be reused and re-added in a lifecycle pass below. - if let Event::Internal(InternalEvent::RouteTimer(token, _)) = event { - self.timers.remove(&token); - } - - if let Some(cursor) = &widget_state.cursor { - self.handle.set_cursor(cursor); - } else if matches!( - event, - Event::MouseMove(..) | Event::Internal(InternalEvent::MouseLeave) - ) { - self.handle.set_cursor(&Cursor::Arrow); - } - - if matches!( - (event, self.size_policy), - (Event::WindowSize(_), WindowSizePolicy::Content) - ) { - // Because our initial size can be zero, the window system won't ask us to paint. - // So layout ourselves and hopefully we resize - self.layout(debug_logger, command_queue, action_queue); - } - - self.post_event_processing( - &mut widget_state, - debug_logger, - command_queue, - action_queue, - false, - ); - - self.root.as_dyn().debug_validate(false); - - is_handled - } - - pub(crate) fn lifecycle( - &mut self, - event: &LifeCycle, - debug_logger: &mut DebugLogger, - command_queue: &mut CommandQueue, - action_queue: &mut ActionQueue, - process_commands: bool, - ) { - let mut widget_state = WidgetState::new(self.root.id(), Some(self.size), ""); - let mut global_state = GlobalPassCtx::new( - self.ext_event_sink.clone(), - debug_logger, - command_queue, - action_queue, - &mut self.timers, - self.mock_timer_queue.as_mut(), - &self.handle, - self.id, - self.focus, - ); - let mut ctx = LifeCycleCtx { - global_state: &mut global_state, - widget_state: &mut widget_state, - }; - - { - ctx.global_state - .debug_logger - .push_important_span(&format!("LIFECYCLE {}", event.short_name())); - let _span = info_span!("lifecycle").entered(); - self.root.lifecycle(&mut ctx, event); - ctx.global_state.debug_logger.pop_span(); - } - - self.post_event_processing( - &mut widget_state, - debug_logger, - command_queue, - action_queue, - process_commands, - ); - } - - pub(crate) fn invalidate_paint_region(&mut self) { - if self.root.state().needs_layout { - // TODO - this might be too coarse - self.handle.invalidate(); - } else { - for rect in self.invalid.rects() { - self.handle.invalidate_rect(*rect); - } - } - self.invalid.clear(); - } - - #[allow(dead_code)] - pub(crate) fn invalid(&self) -> &Region { - &self.invalid - } - - #[allow(dead_code)] - pub(crate) fn invalid_mut(&mut self) -> &mut Region { - &mut self.invalid - } - - /// Get ready for painting, by doing layout and sending an `AnimFrame` event. - pub(crate) fn prepare_paint( - &mut self, - debug_logger: &mut DebugLogger, - command_queue: &mut CommandQueue, - action_queue: &mut ActionQueue, - ) { - let now = Instant::now(); - // TODO: this calculation uses wall-clock time of the paint call, which - // potentially has jitter. - // - // See https://github.com/linebender/druid/issues/85 for discussion. - let last = self.last_anim.take(); - let elapsed_ns = last.map(|t| now.duration_since(t).as_nanos()).unwrap_or(0) as u64; - - if self.wants_animation_frame() { - self.event( - Event::AnimFrame(elapsed_ns), - debug_logger, - command_queue, - action_queue, - ); - self.last_anim = Some(now); - } - } - - pub(crate) fn do_paint( - &mut self, - piet: &mut Piet, - invalid: &Region, - debug_logger: &mut DebugLogger, - command_queue: &mut CommandQueue, - action_queue: &mut ActionQueue, - ) { - if self.root.state().needs_layout { - self.layout(debug_logger, command_queue, action_queue); - } - - for &r in invalid.rects() { - piet.clear( - Some(r), - if self.transparent { - Color::TRANSPARENT - } else { - crate::theme::WINDOW_BACKGROUND_COLOR - }, - ); - } - self.paint(piet, invalid, debug_logger, command_queue, action_queue); - } - - pub(crate) fn layout( - &mut self, - debug_logger: &mut DebugLogger, - command_queue: &mut CommandQueue, - action_queue: &mut ActionQueue, - ) { - let mut widget_state = WidgetState::new(self.root.id(), Some(self.size), ""); - let mut global_state = GlobalPassCtx::new( - self.ext_event_sink.clone(), - debug_logger, - command_queue, - action_queue, - &mut self.timers, - self.mock_timer_queue.as_mut(), - &self.handle, - self.id, - self.focus, - ); - let mut layout_ctx = LayoutCtx { - global_state: &mut global_state, - widget_state: &mut widget_state, - mouse_pos: self.last_mouse_pos, - }; - let bc = match self.size_policy { - WindowSizePolicy::User => BoxConstraints::tight(self.size), - WindowSizePolicy::Content => BoxConstraints::UNBOUNDED, - }; - - let content_size = { - layout_ctx - .global_state - .debug_logger - .push_important_span("LAYOUT"); - let _span = info_span!("layout").entered(); - self.root.layout(&mut layout_ctx, &bc) - }; - layout_ctx.global_state.debug_logger.pop_span(); - - if let WindowSizePolicy::Content = self.size_policy { - let insets = self.handle.content_insets(); - let full_size = (content_size.to_rect() + insets).size(); - if self.size != full_size { - self.size = full_size; - self.handle.set_size(full_size) - } - } - layout_ctx.place_child(&mut self.root, Point::ORIGIN); - self.lifecycle( - &LifeCycle::Internal(InternalLifeCycle::ParentWindowOrigin), - debug_logger, - command_queue, - action_queue, - false, - ); - self.post_event_processing( - &mut widget_state, - debug_logger, - command_queue, - action_queue, - true, - ); - } - - fn paint( - &mut self, - piet: &mut Piet, - invalid: &Region, - debug_logger: &mut DebugLogger, - command_queue: &mut CommandQueue, - action_queue: &mut ActionQueue, - ) { - let widget_state = WidgetState::new(self.root.id(), Some(self.size), ""); - let mut global_state = GlobalPassCtx::new( - self.ext_event_sink.clone(), - debug_logger, - command_queue, - action_queue, - &mut self.timers, - self.mock_timer_queue.as_mut(), - &self.handle, - self.id, - self.focus, - ); - let mut ctx = PaintCtx { - render_ctx: piet, - global_state: &mut global_state, - widget_state: &widget_state, - z_ops: Vec::new(), - region: invalid.clone(), - depth: 0, - debug_paint: false, - debug_widget: false, - debug_widget_id: false, - }; - - let root = &mut self.root; - info_span!("paint").in_scope(|| { - ctx.with_child_ctx(invalid.clone(), |ctx| root.paint_raw(ctx)); - }); - - let mut z_ops = std::mem::take(&mut ctx.z_ops); - z_ops.sort_by_key(|k| k.z_index); - - for z_op in z_ops.into_iter() { - ctx.with_child_ctx(invalid.clone(), |ctx| { - ctx.with_save(|ctx| { - ctx.render_ctx.transform(z_op.transform); - (z_op.paint_func)(ctx); - }); - }); - } - - if self.wants_animation_frame() { - self.handle.request_anim_frame(); - } - } - - pub(crate) fn get_ime_handler( - &mut self, - req_token: TextFieldToken, - mutable: bool, - ) -> Box { - self.ime_handlers - .iter() - .find(|(token, _)| req_token == *token) - .and_then(|(_, reg)| reg.document.acquire(mutable)) - .unwrap() - } - - pub(crate) fn get_focused_ime_handler( - &mut self, - mutable: bool, - ) -> Option> { - let focused_widget_id = self.focus?; - self.ime_handlers - .iter() - .find(|(_, reg)| reg.widget_id == focused_widget_id) - .and_then(|(_, reg)| reg.document.acquire(mutable)) - } - - fn update_focus( - &mut self, - widget_state: &mut WidgetState, - debug_logger: &mut DebugLogger, - command_queue: &mut CommandQueue, - action_queue: &mut ActionQueue, - ) { - if let Some(focus_req) = widget_state.request_focus.take() { - let old = self.focus; - let new = self.widget_for_focus_request(focus_req); - - // TODO - // Skip change if requested widget is disabled - - // Only send RouteFocusChanged in case there's actual change - if old != new { - let event = LifeCycle::Internal(InternalLifeCycle::RouteFocusChanged { old, new }); - self.lifecycle(&event, debug_logger, command_queue, action_queue, false); - self.focus = new; - // check if the newly focused widget has an IME session, and - // notify the system if so. - // - // If you're here because a profiler sent you: I guess I should've - // used a hashmap? - let old_was_ime = old - .map(|old| { - self.ime_handlers - .iter() - .any(|(_, sesh)| sesh.widget_id == old) - }) - .unwrap_or(false); - let maybe_active_text_field = self - .ime_handlers - .iter() - .find(|(_, sesh)| Some(sesh.widget_id) == self.focus) - .map(|(token, _)| *token); - // we call this on every focus change; we could call it less but does it matter? - self.ime_focus_change = if maybe_active_text_field.is_some() { - Some(maybe_active_text_field) - } else if old_was_ime { - Some(None) - } else { - None - }; - } - } - } - - /// Create a function that can invalidate the provided widget's text state. - /// - /// This will be called from outside the main app state in order to avoid - /// reentrancy problems. - pub(crate) fn ime_invalidation_fn(&self, widget: WidgetId) -> Option> { - let token = self - .ime_handlers - .iter() - .find(|(_, reg)| reg.widget_id == widget) - .map(|(t, _)| *t)?; - let window_handle = self.handle.clone(); - Some(Box::new(move |event| { - window_handle.update_text_field(token, event) - })) - } - - /// Release a lock on an IME session, returning a `WidgetId` if the lock was mutable. - /// - /// After a mutable lock is released, the widget needs to be notified so that - /// it can update any internal state. - pub(crate) fn release_ime_lock(&mut self, req_token: TextFieldToken) -> Option { - let (_, reg) = self - .ime_handlers - .iter() - .find(|(token, _)| req_token == *token)?; - reg.document.release().then_some(reg.widget_id) - } - - pub(crate) fn release_focused_ime_handler(&mut self) -> Option { - let focused_widget_id = self.focus?; - let (_, reg) = self - .ime_handlers - .iter() - .find(|(_, reg)| reg.widget_id == focused_widget_id)?; - reg.document.release().then_some(reg.widget_id) - } - - fn widget_for_focus_request(&self, focus: FocusChange) -> Option { - match focus { - FocusChange::Resign => None, - FocusChange::Focus(id) => Some(id), - FocusChange::Next => self.widget_from_focus_chain(true), - FocusChange::Previous => self.widget_from_focus_chain(false), - } - } - - fn widget_from_focus_chain(&self, forward: bool) -> Option { - self.focus.and_then(|focus| { - self.focus_chain() - .iter() - // Find where the focused widget is in the focus chain - .position(|id| id == &focus) - .map(|idx| { - // Return the id that's next to it in the focus chain - let len = self.focus_chain().len(); - let new_idx = if forward { - (idx + 1) % len - } else { - (idx + len - 1) % len - }; - self.focus_chain()[new_idx] - }) - .or_else(|| { - // If the currently focused widget isn't in the focus chain, - // then we'll just return the first/last entry of the chain, if any. - if forward { - self.focus_chain().first().copied() - } else { - self.focus_chain().last().copied() - } - }) - }) - } - - /// Return the root widget. - pub fn root_widget(&self, window_id: WindowId) -> WidgetRef { - self.root.as_dyn() - } - - /// Try to return the widget with the given id. - pub fn find_widget_by_id(&self, id: WidgetId) -> Option> { - self.root.as_dyn().find_widget_by_id(id) - } - - /// Recursively find innermost widget at given position. - pub fn find_widget_at_pos(&self, pos: Point) -> Option> { - self.root.as_dyn().find_widget_at_pos(pos) - } - - /// Return the widget that receives keyboard events. - pub fn focused_widget(&self) -> Option> { - self.find_widget_by_id(self.focus?) - } -} diff --git a/src/bloom.rs b/src/bloom.rs index cfdc5a44..5e45157c 100644 --- a/src/bloom.rs +++ b/src/bloom.rs @@ -7,6 +7,7 @@ use std::hash::{Hash, Hasher}; use std::marker::PhantomData; +// TODO - Once the bloom filter goess away, remove the fnv dependency. use fnv::FnvHasher; const NUM_BITS: u64 = 64; @@ -26,6 +27,7 @@ pub(crate) struct Bloom { entry_count: usize, } +#[allow(dead_code)] impl Bloom { /// Create a new filter. pub fn new() -> Self { diff --git a/src/command.rs b/src/command.rs deleted file mode 100644 index 15811464..00000000 --- a/src/command.rs +++ /dev/null @@ -1,570 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! Custom commands. - -use std::any::Any; -use std::collections::VecDeque; -use std::marker::PhantomData; -use std::sync::{Arc, Mutex}; - -use crate::{WidgetId, WindowId}; - -/// The identity of a [`Selector`]. -/// -/// [`Selector`]: struct.Selector.html -pub(crate) type SelectorSymbol = &'static str; - -/// An identifier for a particular command. -/// -/// This should be a unique string identifier. -/// Having multiple selectors with the same identifier but different payload -/// types is not allowed and can cause [`Command::try_get`] and [`Command::get`] to panic. -/// -/// The type parameter `T` specifies the command's payload type. -/// See [`Command`] for more information. -/// -/// Certain `Selector`s are defined by masonry, and have special meaning -/// to the framework; these are listed in the [`command`](crate::command) module. -#[derive(Debug, PartialEq, Eq)] -pub struct Selector(SelectorSymbol, PhantomData); - -/// An arbitrary command. -/// -/// A `Command` consists of a [`Selector`], that indicates what the command is -/// and what type of payload it carries, as well as the actual payload. -/// -/// If the payload can't or shouldn't be cloned, -/// wrapping it with [`SingleUse`] allows you to `take` the payload. -/// The [`SingleUse`] docs give an example on how to do this. -/// -/// ## Sources -/// -/// Commands can come from multiple sources: -/// - Widgets can send custom commands via methods such as -/// [`EventCtx::submit_command`](crate::EventCtx::submit_command). -/// - If you are doing work in a background thread, your main way of sending -/// data back to the main thread is to use -/// [`ExtEventSink::submit_command`](crate::ext_event::ExtEventSink::submit_command). -/// - In a future version, when MenuItems are implemented, they will work by sending commands when selected. -/// -/// ## Example -/// ``` -/// use masonry::{Command, Selector, Target}; -/// -/// let selector = Selector::new("process_rows"); -/// let rows = vec![1, 3, 10, 12]; -/// let command = selector.with(rows); -/// -/// assert_eq!(command.get(selector), &vec![1, 3, 10, 12]); -/// ``` -// TODO - The [`AppDelegate`](crate::AppDelegate) can send commands through[`DelegateCtx::submit_command`](crate::DelegateCtx::submit_command) - See issue #17 -#[derive(Debug, Clone)] -pub struct Command { - symbol: SelectorSymbol, - payload: Arc, - target: Target, -} - -/// A message passed up the tree from a [`Widget`] to its ancestors. -/// -/// In the course of handling an event, a [`Widget`] may change some internal -/// state that is of interest to one of its ancestors. In this case, the widget -/// may submit a `Notification`. -/// -/// In practice, a `Notification` is very similar to a [`Command`]; the -/// main distinction relates to delivery. [`Command`]s are delivered from the -/// root of the tree down towards the target, and this delivery occurs after -/// the originating event call has returned. `Notification`s are delivered *up* -/// the tree, and this occurs *during* event handling; immediately after the -/// child widget's [`on_event`] method returns, the notification will be delivered -/// to the child's parent, and then the parent's parent, until the notification -/// is handled. -/// -/// [`Widget`]: crate::Widget -/// [`on_event`]: crate::Widget::on_event -#[derive(Clone)] -pub struct Notification { - symbol: SelectorSymbol, - payload: Arc, - source: WidgetId, -} - -/// A wrapper type for [`Command`] payloads that should only be used once. -/// -/// This is useful if you have some resource that cannot be -/// cloned, and you wish to send it to another widget. -/// -/// # Examples -/// ``` -/// use masonry::{Command, Selector, SingleUse, Target}; -/// -/// struct CantClone(u8); -/// -/// let selector = Selector::new("use-once"); -/// let num = CantClone(42); -/// let command = selector.with(SingleUse::new(num)); -/// -/// let payload: &SingleUse = command.get(selector); -/// if let Some(num) = payload.take() { -/// // now you own the data -/// assert_eq!(num.0, 42); -/// } -/// -/// // subsequent calls will return `None` -/// assert!(payload.take().is_none()); -/// ``` -// TODO replace - See issue #1 -pub struct SingleUse(Mutex>); - -/// Our queue type -pub(crate) type CommandQueue = VecDeque; - -/// The target of a [`Command`]. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Target { - /// The target is the top-level application. - /// - /// The `Command` will be delivered to all open windows, and all widgets - /// in each window. Delivery will stop if the event is [`handled`](crate::EventCtx::set_handled). - Global, - /// The target is a specific window. - /// - /// The `Command` will be delivered to all widgets in that window. - /// Delivery will stop if the event is [`handled`](crate::EventCtx::set_handled). - Window(WindowId), - /// The target is a specific widget. - Widget(WidgetId), - // FIXME - remove this variant - /// The target will be determined automatically. - /// - /// How this behaves depends on the context used to submit the command. - /// If the command is submitted within a `Widget` method, then it will be sent to the host - /// window for that widget. If it is from outside the application, via [`ExtEventSink`](crate::ext_event::ExtEventSink), - /// or from the root [`AppDelegate`](crate::AppDelegate) then it will be sent to [`Target::Global`]. - Auto, -} - -pub use sys::*; - -// FIXME -#[allow(dead_code)] -mod sys { - use std::any::Any; - - use druid_shell::FileInfo; - - use super::{Selector, SingleUse}; - use crate::platform::WindowConfig; - use crate::WidgetId; - - /// Quit the running application. This command is handled by the Masonry library. - pub const QUIT_APP: Selector = Selector::new("masonry-builtin.quit-app"); - - /// Hide the application. (mac only) - #[cfg_attr( - not(target_os = "macos"), - deprecated = "HIDE_APPLICATION is only supported on macOS" - )] - pub const HIDE_APPLICATION: Selector = Selector::new("masonry-builtin.menu-hide-application"); - - /// Hide all other applications. (mac only) - #[cfg_attr( - not(target_os = "macos"), - deprecated = "HIDE_OTHERS is only supported on macOS" - )] - pub const HIDE_OTHERS: Selector = Selector::new("masonry-builtin.menu-hide-others"); - - /// The selector for a command to create a new window. - pub(crate) const NEW_WINDOW: Selector>> = - Selector::new("masonry-builtin.new-window"); - - /// The selector for a command to close a window. - /// - /// The command must target a specific window. - /// When calling `submit_command` on a `Widget`s context, passing `None` as target - /// will automatically target the window containing the widget. - pub const CLOSE_WINDOW: Selector = Selector::new("masonry-builtin.close-window"); - - /// Close all windows. - pub const CLOSE_ALL_WINDOWS: Selector = Selector::new("masonry-builtin.close-all-windows"); - - /// The selector for a command to bring a window to the front, and give it focus. - /// - /// The command must target a specific window. - /// When calling `submit_command` on a `Widget`s context, passing `None` as target - /// will automatically target the window containing the widget. - pub const SHOW_WINDOW: Selector = Selector::new("masonry-builtin.show-window"); - - /// Apply the configuration payload to an existing window. The target should be a WindowId. - pub const CONFIGURE_WINDOW: Selector = - Selector::new("masonry-builtin.configure-window"); - - /// Show the application preferences. - pub const SHOW_PREFERENCES: Selector = Selector::new("masonry-builtin.menu-show-preferences"); - - /// Show the application about window. - pub const SHOW_ABOUT: Selector = Selector::new("masonry-builtin.menu-show-about"); - - /// Show all applications. - pub const SHOW_ALL: Selector = Selector::new("masonry-builtin.menu-show-all"); - - /// Show the new file dialog. - pub const NEW_FILE: Selector = Selector::new("masonry-builtin.menu-file-new"); - - /// Sent when the user cancels an open file panel. - pub const OPEN_PANEL_CANCELLED: Selector = - Selector::new("masonry-builtin.open-panel-cancelled"); - - /// Open a path, must be handled by the application. - /// - /// [`FileInfo`]: ../struct.FileInfo.html - pub const OPEN_FILE: Selector = Selector::new("masonry-builtin.open-file-path"); - - /// Sent when the user cancels a save file panel. - pub const SAVE_PANEL_CANCELLED: Selector = - Selector::new("masonry-builtin.save-panel-cancelled"); - - /// Save the current path. - /// - /// The application should save its data, to a path that should be determined by the - /// application. Usually, this will be the most recent path provided by a [`SAVE_FILE_AS`] - /// or [`OPEN_FILE`] command. - pub const SAVE_FILE: Selector<()> = Selector::new("masonry-builtin.save-file"); - - /// Save to a given location. - /// - /// This command is emitted by Masonry whenever a save file dialog successfully completes. The - /// application should save its data to the path proved, and should store the path in order to - /// handle [`SAVE_FILE`] commands in the future. - /// - /// The path might be a file or a directory, so always check whether it matches your - /// expectations. - pub const SAVE_FILE_AS: Selector = Selector::new("masonry-builtin.save-file-as"); - - /// Show the print-setup window. - pub const PRINT_SETUP: Selector = Selector::new("masonry-builtin.menu-file-print-setup"); - - /// Show the print dialog. - pub const PRINT: Selector = Selector::new("masonry-builtin.menu-file-print"); - - /// Show the print preview. - pub const PRINT_PREVIEW: Selector = Selector::new("masonry-builtin.menu-file-print"); - - /// Cut the current selection. - pub const CUT: Selector = Selector::new("masonry-builtin.menu-cut"); - - /// Copy the current selection. - pub const COPY: Selector = Selector::new("masonry-builtin.menu-copy"); - - /// Paste. - pub const PASTE: Selector = Selector::new("masonry-builtin.menu-paste"); - - /// Undo. - pub const UNDO: Selector = Selector::new("masonry-builtin.menu-undo"); - - /// Redo. - pub const REDO: Selector = Selector::new("masonry-builtin.menu-redo"); - - /// Select all. - pub const SELECT_ALL: Selector = Selector::new("masonry-builtin.menu-select-all"); - - /// Text input state has changed, and we need to notify the platform. - pub(crate) const INVALIDATE_IME: Selector = - Selector::new("masonry-builtin.invalidate-ime"); - - /// A change that has occured to text state, and needs to be - /// communicated to the platform. - pub(crate) struct ImeInvalidation { - pub widget: WidgetId, - pub event: druid_shell::text::Event, - } -} - -impl Selector<()> { - /// A selector that does nothing. - pub const NOOP: Selector = Selector::new(""); - - /// Turns this into a command with the specified [`Target`]. - /// - /// [`Target`]: enum.Target.html - pub fn to(self, target: impl Into) -> Command { - Command::from(self).to(target.into()) - } -} - -impl Selector { - /// Create a new `Selector` with the given string. - pub const fn new(s: &'static str) -> Selector { - Selector(s, PhantomData) - } - - /// Returns the `SelectorSymbol` identifying this `Selector`. - pub(crate) const fn symbol(self) -> SelectorSymbol { - self.0 - } -} - -impl Selector { - /// Convenience method for [`Command::new`] with this selector. - /// - /// If the payload is `()` there is no need to call this, - /// as `Selector<()>` implements `Into`. - /// - /// By default, the command will have [`Target::Auto`]. - /// The [`Selector::to`] method can be used to override this. - pub fn with(self, payload: T) -> Command { - Command::new(self, payload, Target::Auto) - } -} - -impl Command { - /// Create a new `Command` with a payload and a [`Target`]. - /// - /// [`Selector::with`] should be used to create `Command`s more conveniently. - /// - /// If you do not need a payload, [`Selector`] implements `Into`. - pub fn new(selector: Selector, payload: T, target: impl Into) -> Self { - Command { - symbol: selector.symbol(), - payload: Arc::new(payload), - target: target.into(), - } - } - - /// Used to create a `Command` from the types sent via an `ExtEventSink`. - pub(crate) fn from_ext(symbol: SelectorSymbol, payload: Box, target: Target) -> Self { - Command { - symbol, - payload: payload.into(), - target, - } - .default_to(Target::Global) - } - - /// A helper method for creating a `Notification` from a `Command`. - /// - /// This is slightly icky; it lets us do `SOME_SELECTOR.with(SOME_PAYLOAD)` - /// (which generates a command) and then privately convert it to a - /// notification. - pub(crate) fn into_notification(self, source: WidgetId) -> Notification { - Notification { - symbol: self.symbol, - payload: self.payload, - source, - } - } - - /// Set the `Command`'s [`Target`]. - /// - /// [`Command::target`] can be used to get the current [`Target`]. - /// - /// [`Command::target`]: #method.target - /// [`Target`]: enum.Target.html - pub fn to(mut self, target: impl Into) -> Self { - self.target = target.into(); - self - } - - /// Set the correct default target when target is `Auto`. - pub(crate) fn default_to(mut self, target: Target) -> Self { - self.target.default(target); - self - } - - /// Returns the `Command`'s [`Target`]. - /// - /// [`Command::to`] can be used to change the [`Target`]. - pub fn target(&self) -> Target { - self.target - } - - /// Returns `true` if `self` matches this `selector`. - pub fn is(&self, selector: Selector) -> bool { - self.symbol == selector.symbol() - } - - /// Returns `Some(&T)` (this `Command`'s payload) if the selector matches. - /// - /// Returns `None` when `self.is(selector) == false`. - /// - /// Alternatively you can check the selector with [`is`](Self::is) and then use [`get`](Self::get). - /// - /// # Panics - /// - /// Panics when the payload has a different type, than what the selector is supposed to carry. - /// This can happen when two selectors with different types but the same key are used. - pub fn try_get(&self, selector: Selector) -> Option<&T> { - if self.symbol == selector.symbol() { - Some(self.payload.downcast_ref().unwrap_or_else(|| { - panic!( - "The selector \"{}\" exists twice with different types. See the masonry::Command::get documentation for more information", - selector.symbol() - ); - })) - } else { - None - } - } - - /// Returns a reference to this `Command`'s payload. - /// - /// If the selector has already been checked with [`is`](Self::is), then `get` can be used safely. - /// Otherwise you should use [`try_get`](Self::try_get) instead. - /// - /// # Panics - /// - /// Panics when `self.is(selector) == false`. - /// - /// Panics when the payload has a different type, than what the selector is supposed to carry. - /// This can happen when two selectors with different types but the same key are used. - pub fn get(&self, selector: Selector) -> &T { - self.try_get(selector).unwrap_or_else(|| { - panic!( - "Expected selector \"{}\" but the command was \"{}\".", - selector.symbol(), - self.symbol - ) - }) - } -} - -impl Notification { - /// Returns `true` if `self` matches this [`Selector`]. - pub fn is(&self, selector: Selector) -> bool { - self.symbol == selector.symbol() - } - - /// Returns the payload for this [`Selector`], if the selector matches. - /// - /// # Panics - /// - /// Panics when the payload has a different type, than what the selector - /// is supposed to carry. This can happen when two selectors with different - /// types but the same key are used. - pub fn try_get(&self, selector: Selector) -> Option<&T> { - if self.symbol == selector.symbol() { - Some(self.payload.downcast_ref().unwrap_or_else(|| { - panic!( - "The selector \"{}\" exists twice with different types. See the masonry::Command::try_get documentation for more information", - selector.symbol() - ); - })) - } else { - None - } - } - - /// The [`WidgetId`] of the [`Widget`] that sent this [`Notification`]. - /// - /// [`Widget`]: crate::Widget - pub fn source(&self) -> WidgetId { - self.source - } -} - -impl SingleUse { - /// Create a new single-use payload. - pub fn new(data: T) -> Self { - SingleUse(Mutex::new(Some(data))) - } - - /// Takes the value, leaving a None in its place. - pub fn take(&self) -> Option { - self.0.lock().unwrap().take() - } -} - -impl From for Command { - fn from(selector: Selector) -> Command { - Command { - symbol: selector.symbol(), - payload: Arc::new(()), - target: Target::Auto, - } - } -} - -impl std::fmt::Display for Selector { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "Selector(\"{}\", {})", - self.0, - std::any::type_name::() - ) - } -} - -// This has do be done explicitly, to avoid the Copy bound on `T`. -// See https://doc.rust-lang.org/std/marker/trait.Copy.html#how-can-i-implement-copy . -impl Copy for Selector {} -impl Clone for Selector { - fn clone(&self) -> Self { - *self - } -} - -impl Target { - /// If `self` is `Auto` it will be replaced with `target`. - pub(crate) fn default(&mut self, target: Target) { - if self == &Target::Auto { - *self = target; - } - } -} - -impl From for Target { - fn from(id: WindowId) -> Target { - Target::Window(id) - } -} - -impl From for Target { - fn from(id: WidgetId) -> Target { - Target::Widget(id) - } -} - -impl From for Option { - fn from(id: WindowId) -> Self { - Some(Target::Window(id)) - } -} - -impl From for Option { - fn from(id: WidgetId) -> Self { - Some(Target::Widget(id)) - } -} - -impl std::fmt::Debug for Notification { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "Notification: Selector {} from {:?}", - self.symbol, self.source - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn get_payload() { - let sel = Selector::new("my-selector"); - let payload = vec![0, 1, 2]; - let command = sel.with(payload); - assert_eq!(command.try_get(sel), Some(&vec![0, 1, 2])); - } - - #[test] - fn selector_is_send_and_sync() { - fn assert_send_sync() {} - - assert_send_sync::(); - } -} diff --git a/src/contexts.rs b/src/contexts.rs index 07ef6609..028dc767 100644 --- a/src/contexts.rs +++ b/src/contexts.rs @@ -5,28 +5,19 @@ //! The context types that are passed into various widget methods. use std::any::Any; -use std::collections::{HashMap, VecDeque}; -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; use std::time::Duration; -use druid_shell::text::Event as ImeInvalidation; -use druid_shell::{Cursor, Region, TimerToken, WindowHandle}; -use tracing::{error, trace, warn}; +use parley::FontContext; +use tracing::{trace, warn}; +use winit::dpi::PhysicalPosition; +use winit::window::CursorIcon; -use crate::action::{Action, ActionQueue}; -use crate::command::{Command, CommandQueue, Notification, SingleUse}; -use crate::debug_logger::DebugLogger; -use crate::ext_event::ExtEventSink; -use crate::piet::{Piet, PietText, RenderContext}; -use crate::platform::WindowDescription; +use crate::action::Action; use crate::promise::PromiseToken; -use crate::testing::MockTimerQueue; -use crate::text::{ImeHandlerRef, TextFieldRegistration}; +use crate::render_root::{RenderRootSignal, RenderRootState}; +use crate::text_helpers::{ImeChangeSignal, TextFieldRegistration}; use crate::widget::{CursorChange, FocusChange, StoreInWidgetMut, WidgetMut, WidgetState}; -use crate::{ - Affine, Insets, Point, Rect, Size, Target, Vec2, Widget, WidgetId, WidgetPod, WindowId, -}; +use crate::{Insets, Point, Rect, Size, Widget, WidgetId, WidgetPod}; /// A macro for implementing methods on multiple contexts. /// @@ -42,26 +33,6 @@ macro_rules! impl_context_method { }; } -// TODO - remove second lifetime, only keep queues and Rc -// TODO - rename lifetimes -/// Static state that is shared between most contexts. -pub(crate) struct GlobalPassCtx<'a> { - pub(crate) ext_event_sink: ExtEventSink, - pub(crate) debug_logger: &'a mut DebugLogger, - pub(crate) command_queue: &'a mut CommandQueue, - pub(crate) action_queue: &'a mut ActionQueue, - // TODO - merge queues - // Associate timers with widgets that requested them. - pub(crate) timers: &'a mut HashMap, - // Used in Harness for unit tests - see `src/testing/mock_timer_queue.rs` - pub(crate) mock_timer_queue: Option<&'a mut MockTimerQueue>, - pub(crate) window_id: WindowId, - pub(crate) window: &'a WindowHandle, - pub(crate) text: PietText, - /// The id of the widget that currently has focus. - pub(crate) focus_widget: Option, -} - /// A context provided to implementors of [`StoreInWidgetMut`]. /// /// When you declare a mutable reference type for your widget, methods of this type @@ -70,8 +41,8 @@ pub(crate) struct GlobalPassCtx<'a> { /// you will need to signal that change in the pass (eg `requrest_paint`). /// // TODO add tutorial - See issue #5 -pub struct WidgetCtx<'a, 'b> { - pub(crate) global_state: &'a mut GlobalPassCtx<'b>, +pub struct WidgetCtx<'a> { + pub(crate) global_state: &'a mut RenderRootState, pub(crate) widget_state: &'a mut WidgetState, } @@ -79,20 +50,18 @@ pub struct WidgetCtx<'a, 'b> { /// /// Widgets should call [`request_paint`](Self::request_paint) whenever an event causes a change /// in the widget's appearance, to schedule a repaint. -pub struct EventCtx<'a, 'b> { - pub(crate) global_state: &'a mut GlobalPassCtx<'b>, +pub struct EventCtx<'a> { + pub(crate) global_state: &'a mut RenderRootState, pub(crate) widget_state: &'a mut WidgetState, - pub(crate) notifications: &'a mut VecDeque, pub(crate) is_handled: bool, - pub(crate) is_root: bool, pub(crate) request_pan_to_child: Option, } /// A context provided to the [`lifecycle`] method on widgets. /// /// [`lifecycle`]: trait.Widget.html#tymethod.lifecycle -pub struct LifeCycleCtx<'a, 'b> { - pub(crate) global_state: &'a mut GlobalPassCtx<'b>, +pub struct LifeCycleCtx<'a> { + pub(crate) global_state: &'a mut RenderRootState, pub(crate) widget_state: &'a mut WidgetState, } @@ -101,67 +70,42 @@ pub struct LifeCycleCtx<'a, 'b> { /// As of now, the main service provided is access to a factory for /// creating text layout objects, which are likely to be useful /// during widget layout. -pub struct LayoutCtx<'a, 'b> { - pub(crate) global_state: &'a mut GlobalPassCtx<'b>, +pub struct LayoutCtx<'a> { + pub(crate) global_state: &'a mut RenderRootState, pub(crate) widget_state: &'a mut WidgetState, pub(crate) mouse_pos: Option, } -/// Z-order paint operations with transformations. -pub(crate) struct ZOrderPaintOp { - pub z_index: u32, - pub paint_func: Box, - pub transform: Affine, -} - /// A context passed to paint methods of widgets. -/// -/// In addition to the API below, [`PaintCtx`] derefs to an implemention of -/// the [`RenderContext`] trait, which defines the basic available drawing -/// commands. -pub struct PaintCtx<'a, 'b, 'c> { - pub(crate) global_state: &'a mut GlobalPassCtx<'b>, +pub struct PaintCtx<'a> { + pub(crate) global_state: &'a mut RenderRootState, pub(crate) widget_state: &'a WidgetState, - /// The render context for actually painting. - pub render_ctx: &'a mut Piet<'c>, - /// The z-order paint operations. - pub(crate) z_ops: Vec, - /// The currently visible region. - pub(crate) region: Region, /// The approximate depth in the tree at the time of painting. pub(crate) depth: u32, pub(crate) debug_paint: bool, pub(crate) debug_widget: bool, - pub(crate) debug_widget_id: bool, } +pub struct WorkerCtx<'a> { + // TODO + #[allow(dead_code)] + pub(crate) global_state: &'a mut RenderRootState, +} + +pub struct WorkerFn(pub Box); + impl_context_method!( - WidgetCtx<'_, '_>, - EventCtx<'_, '_>, - LifeCycleCtx<'_, '_>, - PaintCtx<'_, '_, '_>, - LayoutCtx<'_, '_>, + WidgetCtx<'_>, + EventCtx<'_>, + LifeCycleCtx<'_>, + PaintCtx<'_>, + LayoutCtx<'_>, { /// get the `WidgetId` of the current widget. pub fn widget_id(&self) -> WidgetId { self.widget_state.id } - /// Returns a reference to the current `WindowHandle`. - pub fn window(&self) -> &WindowHandle { - self.global_state.window - } - - /// Get the `WindowId` of the current window. - pub fn window_id(&self) -> WindowId { - self.global_state.window_id - } - - /// Get an object which can create text layouts. - pub fn text(&mut self) -> &mut PietText { - &mut self.global_state.text - } - /// Skip iterating over the given child. /// /// Normally, container widgets are supposed to iterate over each of their @@ -178,10 +122,10 @@ impl_context_method!( // methods on everyone but layoutctx impl_context_method!( - WidgetCtx<'_, '_>, - EventCtx<'_, '_>, - LifeCycleCtx<'_, '_>, - PaintCtx<'_, '_, '_>, + WidgetCtx<'_>, + EventCtx<'_>, + LifeCycleCtx<'_>, + PaintCtx<'_>, { /// The layout size. /// @@ -209,16 +153,6 @@ impl_context_method!( self.window_origin() + widget_point.to_vec2() } - /// Convert a point from the widget's coordinate space to the screen's. - /// See the [`Screen`] module - /// - /// [`Screen`]: druid_shell::Screen - pub fn to_screen(&self, widget_point: Point) -> Point { - let insets = self.window().content_insets(); - let content_origin = self.window().get_position() + Vec2::new(insets.x0, insets.y0); - content_origin + self.to_window(widget_point).to_vec2() - } - /// The "hot" (aka hover) status of a widget. /// /// A widget is "hot" when the mouse is hovered over it. Widgets will @@ -269,7 +203,7 @@ impl_context_method!( /// [`LifeCycle::FocusChanged`]: enum.LifeCycle.html#variant.FocusChanged /// [`has_focus`]: #method.has_focus pub fn is_focused(&self) -> bool { - self.global_state.focus_widget == Some(self.widget_id()) + self.global_state.focused_widget == Some(self.widget_id()) } /// The (tree) focus status of a widget. @@ -307,7 +241,7 @@ impl_context_method!( } ); -impl_context_method!(EventCtx<'_, '_>, { +impl_context_method!(EventCtx<'_>, { /// Set the cursor icon. /// /// This setting will be retained until [`clear_cursor`] is called, but it will only take @@ -319,9 +253,9 @@ impl_context_method!(EventCtx<'_, '_>, { /// [`override_cursor`]: EventCtx::override_cursor /// [`hot`]: EventCtx::is_hot /// [`active`]: EventCtx::is_active - pub fn set_cursor(&mut self, cursor: &Cursor) { + pub fn set_cursor(&mut self, cursor: &CursorIcon) { trace!("set_cursor {:?}", cursor); - self.widget_state.cursor_change = CursorChange::Set(cursor.clone()); + self.widget_state.cursor_change = CursorChange::Set(*cursor); } /// Override the cursor icon. @@ -334,9 +268,9 @@ impl_context_method!(EventCtx<'_, '_>, { /// [`set_cursor`]: EventCtx::override_cursor /// [`hot`]: EventCtx::is_hot /// [`active`]: EventCtx::is_active - pub fn override_cursor(&mut self, cursor: &Cursor) { + pub fn override_cursor(&mut self, cursor: &CursorIcon) { trace!("override_cursor {:?}", cursor); - self.widget_state.cursor_change = CursorChange::Override(cursor.clone()); + self.widget_state.cursor_change = CursorChange::Override(*cursor); } /// Clear the cursor icon. @@ -351,13 +285,13 @@ impl_context_method!(EventCtx<'_, '_>, { } }); -impl<'a, 'b> WidgetCtx<'a, 'b> { +impl<'a> WidgetCtx<'a> { // FIXME - Assert that child's parent is self /// Return a [`WidgetMut`] to a child widget. pub fn get_mut<'c, Child: Widget + StoreInWidgetMut>( &'c mut self, child: &'c mut WidgetPod, - ) -> WidgetMut<'c, 'b, Child> { + ) -> WidgetMut<'c, Child> { let child_ctx = WidgetCtx { global_state: self.global_state, widget_state: &mut child.state, @@ -369,13 +303,13 @@ impl<'a, 'b> WidgetCtx<'a, 'b> { } } -impl<'a, 'b> EventCtx<'a, 'b> { +impl<'a> EventCtx<'a> { /// Return a [`WidgetMut`] to a child widget. // FIXME - Assert that child's parent is self pub fn get_mut<'c, Child: Widget + StoreInWidgetMut>( &'c mut self, child: &'c mut WidgetPod, - ) -> WidgetMut<'c, 'b, Child> { + ) -> WidgetMut<'c, Child> { let child_ctx = WidgetCtx { global_state: self.global_state, widget_state: &mut child.state, @@ -387,13 +321,13 @@ impl<'a, 'b> EventCtx<'a, 'b> { } } -impl<'a, 'b> LifeCycleCtx<'a, 'b> { +impl<'a> LifeCycleCtx<'a> { /// Return a [`WidgetMut`] to a child widget. // FIXME - Assert that child's parent is self pub fn get_mut<'c, Child: Widget + StoreInWidgetMut>( &'c mut self, child: &'c mut WidgetPod, - ) -> WidgetMut<'c, 'b, Child> { + ) -> WidgetMut<'c, Child> { let child_ctx = WidgetCtx { global_state: self.global_state, widget_state: &mut child.state, @@ -406,26 +340,12 @@ impl<'a, 'b> LifeCycleCtx<'a, 'b> { } // methods on event and lifecycle -impl_context_method!(WidgetCtx<'_, '_>, EventCtx<'_, '_>, LifeCycleCtx<'_, '_>, { - /// Request a [`paint`] pass. This is equivalent to calling - /// [`request_paint_rect`](Self::request_paint_rect) for the widget's [`paint_rect`]. - /// +impl_context_method!(WidgetCtx<'_>, EventCtx<'_>, LifeCycleCtx<'_>, { + /// Request a [`paint`] pass. /// [`paint`]: trait.Widget.html#tymethod.paint - /// [`paint_rect`]: struct.WidgetPod.html#method.paint_rect pub fn request_paint(&mut self) { trace!("request_paint"); - self.widget_state.invalid.set_rect( - self.widget_state.paint_rect() - self.widget_state.layout_rect().origin().to_vec2(), - ); - } - - /// Request a [`paint`] pass for redrawing a rectangle, which is given - /// relative to our layout rectangle. - /// - /// [`paint`]: trait.Widget.html#tymethod.paint - pub fn request_paint_rect(&mut self, rect: Rect) { - trace!("request_paint_rect {}", rect); - self.widget_state.invalid.add_rect(rect); + self.widget_state.needs_paint = true; } /// Request a layout pass. @@ -482,151 +402,75 @@ impl_context_method!(WidgetCtx<'_, '_>, EventCtx<'_, '_>, LifeCycleCtx<'_, '_>, self.children_changed(); } + #[allow(unused)] /// Indicate that text input state has changed. /// /// A widget that accepts text input should call this anytime input state /// (such as the text or the selection) changes as a result of a non text-input /// event. - pub fn invalidate_text_input(&mut self, event: ImeInvalidation) { - let payload = crate::command::ImeInvalidation { - widget: self.widget_id(), - event, - }; - let cmd = crate::command::INVALIDATE_IME - .with(payload) - .to(Target::Window(self.window_id())); - self.submit_command(cmd); + pub fn invalidate_text_input(&mut self, event: ImeChangeSignal) { + todo!("invalidate_text_input"); } }); // methods on everyone but paintctx impl_context_method!( - WidgetCtx<'_, '_>, - EventCtx<'_, '_>, - LifeCycleCtx<'_, '_>, - LayoutCtx<'_, '_>, + WidgetCtx<'_>, + EventCtx<'_>, + LifeCycleCtx<'_>, + LayoutCtx<'_>, { - /// Submit a [`Command`] to be run after this event is handled. - /// - /// Commands are run in the order they are submitted; all commands - /// submitted during the handling of an event are executed before - /// the [`update`] method is called; events submitted during [`update`] - /// are handled after painting. - /// - /// [`Target::Auto`] commands will be sent to the window containing the widget. - /// - /// [`Command`]: struct.Command.html - /// [`update`]: trait.Widget.html#tymethod.update - pub fn submit_command(&mut self, cmd: impl Into) { - trace!("submit_command"); - self.global_state.submit_command(cmd.into()) - } - /// Submit an [`Action`]. /// /// Note: Actions are still a WIP feature. pub fn submit_action(&mut self, action: Action) { - trace!("submit_command"); + trace!("submit_action"); self.global_state - .submit_action(action, self.widget_state.id) + .signal_queue + .push_back(RenderRootSignal::Action(action, self.widget_state.id)); } /// Run the provided function in the background. /// - /// The function takes an [`ExtEventSink`] which it can use to send - /// [`Command`]s back to the main thread. + /// The function takes a [`WorkerCtx`] which it can use to + /// communicate with the main thread. pub fn run_in_background( &mut self, - background_task: impl FnOnce(ExtEventSink) + Send + 'static, + _background_task: impl FnOnce(WorkerCtx) + Send + 'static, ) { - use std::thread; - - let ext_event_sink = self.global_state.ext_event_sink.clone(); - thread::spawn(move || { - background_task(ext_event_sink); - }); + // TODO - Use RenderRootSignal::SpawnWorker + todo!("run_in_background"); } /// Run the provided function in the background, and send its result once it's done. /// - /// The function takes an [`ExtEventSink`] which it can use to send - /// [`Command`]s back to the main thread. + /// The function takes a [`WorkerCtx`] which it can use to + /// communicate with the main thread. /// /// Once the function returns, an [`Event::PromiseResult`](crate::Event::PromiseResult) /// is emitted with the return value. pub fn compute_in_background( &mut self, - background_task: impl FnOnce(ExtEventSink) -> T + Send + 'static, + _background_task: impl FnOnce(WorkerCtx) -> T + Send + 'static, ) -> PromiseToken { - let token = PromiseToken::::new(); - - use std::thread; - - let ext_event_sink = self.global_state.ext_event_sink.clone(); - let widget_id = self.widget_state.id; - let window_id = self.global_state.window_id; - thread::spawn(move || { - let result = background_task(ext_event_sink.clone()); - // TODO unwrap_or - let _ = - ext_event_sink.resolve_promise(token.make_result(result), widget_id, window_id); - }); - - token + // TODO - Use RenderRootSignal::SpawnWorker + todo!("compute_in_background"); } /// Request a timer event. /// /// The return value is a token, which can be used to associate the /// request with the event. - pub fn request_timer(&mut self, deadline: Duration) -> TimerToken { - trace!("request_timer deadline={:?}", deadline); - self.global_state - .request_timer(deadline, self.widget_state.id) + pub fn request_timer(&mut self, _deadline: Duration) -> TimerToken { + todo!("request_timer"); } } ); -impl EventCtx<'_, '_> { - /// Submit a [`Notification`]. - /// - /// The provided argument can be a [`Selector`] or a [`Command`]; this lets - /// us work with the existing API for addding a payload to a [`Selector`]. - /// - /// If the argument is a `Command`, the command's target will be ignored. - /// - /// # Examples - /// - /// ``` - /// # use masonry::{Event, EventCtx, Selector}; - /// const IMPORTANT_EVENT: Selector = Selector::new("masonry-example.important-event"); - /// - /// fn check_event(ctx: &mut EventCtx, event: &Event) { - /// if is_this_the_event_we_were_looking_for(event) { - /// ctx.submit_notification(IMPORTANT_EVENT.with("That's the one".to_string())) - /// } - /// } - /// - /// # fn is_this_the_event_we_were_looking_for(event: &Event) -> bool { true } - /// ``` - /// - /// [`Selector`]: crate::Selector - pub fn submit_notification(&mut self, note: impl Into) { - trace!("submit_notification"); - let note = note.into().into_notification(self.widget_state.id); - self.notifications.push_back(note); - } - - /// Create a new window. - pub fn new_window(&mut self, desc: WindowDescription) { - trace!("new_window"); - self.submit_command( - crate::command::NEW_WINDOW - .with(SingleUse::new(Box::new(desc))) - .to(Target::Global), - ); - } +// FIXME - Remove +pub struct TimerToken; +impl EventCtx<'_> { /// Send a signal to parent widgets to scroll this widget into view. pub fn request_pan_to_this(&mut self) { self.request_pan_to_child = Some(self.widget_state.layout_rect()); @@ -731,7 +575,7 @@ impl EventCtx<'_, '_> { } } -impl LifeCycleCtx<'_, '_> { +impl LifeCycleCtx<'_> { /// Registers a child widget. /// /// This should only be called in response to a `LifeCycle::WidgetAdded` event. @@ -757,9 +601,8 @@ impl LifeCycleCtx<'_, '_> { } /// Register this widget as accepting text input. - pub fn register_text_input(&mut self, document: impl ImeHandlerRef + 'static) { + pub fn register_as_text_input(&mut self) { let registration = TextFieldRegistration { - document: Rc::new(document), widget_id: self.widget_id(), }; self.widget_state.text_registrations.push(registration); @@ -774,7 +617,7 @@ impl LifeCycleCtx<'_, '_> { } } -impl LayoutCtx<'_, '_> { +impl LayoutCtx<'_> { /// Set explicit paint [`Insets`] for this widget. /// /// You are not required to set explicit paint bounds unless you need @@ -814,26 +657,33 @@ impl LayoutCtx<'_, '_> { pub fn place_child(&mut self, child: &mut WidgetPod, origin: Point) { child.state.origin = origin; child.state.is_expecting_place_child_call = false; - let layout_rect = child.layout_rect(); self.widget_state.local_paint_rect = self.widget_state.local_paint_rect.union(child.paint_rect()); + let mouse_pos = self + .mouse_pos + .map(|pos| PhysicalPosition::new(pos.x, pos.y)); // if the widget has moved, it may have moved under the mouse, in which // case we need to handle that. if WidgetPod::update_hot_state( &mut child.inner, &mut child.state, self.global_state, - layout_rect, - self.mouse_pos, + mouse_pos, ) { self.widget_state.merge_up(&mut child.state); } } } -impl PaintCtx<'_, '_, '_> { +impl_context_method!(LayoutCtx<'_>, PaintCtx<'_>, { + pub fn font_ctx(&mut self) -> &mut FontContext { + &mut self.global_state.font_context + } +}); + +impl PaintCtx<'_> { /// The depth in the tree of the currently painting widget. /// /// This may be used in combination with [`paint_with_z_index`](Self::paint_with_z_index) in order @@ -845,150 +695,4 @@ impl PaintCtx<'_, '_, '_> { pub fn depth(&self) -> u32 { self.depth } - - /// Returns the region that needs to be repainted. - #[inline] - pub fn region(&self) -> &Region { - &self.region - } - - /// Creates a temporary `PaintCtx` with a new visible region, and calls - /// the provided function with that `PaintCtx`. - /// - /// This is used by containers to ensure that their children have the correct - /// visible region given their layout. - pub fn with_child_ctx(&mut self, region: impl Into, f: impl FnOnce(&mut PaintCtx)) { - let mut child_ctx = PaintCtx { - render_ctx: self.render_ctx, - global_state: self.global_state, - widget_state: self.widget_state, - z_ops: Vec::new(), - region: region.into(), - depth: self.depth + 1, - debug_paint: self.debug_paint, - debug_widget: self.debug_widget, - debug_widget_id: self.debug_widget_id, - }; - f(&mut child_ctx); - self.z_ops.append(&mut child_ctx.z_ops); - } - - /// Saves the current context, executes the closures, and restores the context. - /// - /// This is useful if you would like to transform or clip or otherwise - /// modify the drawing context but do not want that modification to - /// effect other widgets. - /// - /// # Examples - /// - /// ``` - /// # use masonry::{PaintCtx, RenderContext, theme}; - /// # struct T; - /// # impl T { - /// fn paint(&mut self, ctx: &mut PaintCtx) { - /// let clip_rect = ctx.size().to_rect().inset(5.0); - /// ctx.with_save(|ctx| { - /// ctx.clip(clip_rect); - /// ctx.stroke(clip_rect, &theme::PRIMARY_DARK, 5.0); - /// }); - /// } - /// # } - /// ``` - pub fn with_save(&mut self, f: impl FnOnce(&mut PaintCtx)) { - if let Err(e) = self.render_ctx.save() { - error!("Failed to save RenderContext: '{}'", e); - return; - } - - f(self); - - if let Err(e) = self.render_ctx.restore() { - error!("Failed to restore RenderContext: '{}'", e); - } - } - - /// Allows to specify order for paint operations. - /// - /// Larger `z_index` indicate that an operation will be executed later. - pub fn paint_with_z_index( - &mut self, - z_index: u32, - paint_func: impl FnOnce(&mut PaintCtx) + 'static, - ) { - let current_transform = self.render_ctx.current_transform(); - self.z_ops.push(ZOrderPaintOp { - z_index, - paint_func: Box::new(paint_func), - transform: current_transform, - }) - } -} - -impl<'a> GlobalPassCtx<'a> { - #[allow(clippy::too_many_arguments)] - pub(crate) fn new( - ext_event_sink: ExtEventSink, - debug_logger: &'a mut DebugLogger, - command_queue: &'a mut CommandQueue, - action_queue: &'a mut ActionQueue, - timers: &'a mut HashMap, - mock_timer_queue: Option<&'a mut MockTimerQueue>, - window: &'a WindowHandle, - window_id: WindowId, - focus_widget: Option, - ) -> Self { - GlobalPassCtx { - ext_event_sink, - debug_logger, - command_queue, - action_queue, - timers, - mock_timer_queue, - window, - window_id, - focus_widget, - text: window.text(), - } - } - - pub(crate) fn submit_command(&mut self, command: Command) { - trace!("submit_command"); - self.command_queue - .push_back(command.default_to(self.window_id.into())); - } - - pub(crate) fn submit_action(&mut self, action: Action, widget_id: WidgetId) { - trace!("submit_action"); - self.action_queue - .push_back((action, widget_id, self.window_id)); - } - - pub(crate) fn request_timer(&mut self, duration: Duration, widget_id: WidgetId) -> TimerToken { - trace!("request_timer duration={:?}", duration); - - let timer_token = if let Some(timer_queue) = self.mock_timer_queue.as_mut() { - // Path taken in unit tests, because we don't want to use platform timers - timer_queue.add_timer(duration) - } else { - // Normal path - self.window.request_timer(duration) - }; - - self.timers.insert(timer_token, widget_id); - timer_token - } -} - -impl<'c> Deref for PaintCtx<'_, '_, 'c> { - type Target = Piet<'c>; - - fn deref(&self) -> &Self::Target { - self.render_ctx - } -} - -impl<'c> DerefMut for PaintCtx<'_, '_, 'c> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.render_ctx - } } diff --git a/src/event.rs b/src/event.rs index be4fc123..92bfe9e8 100644 --- a/src/event.rs +++ b/src/event.rs @@ -4,200 +4,74 @@ //! Events. -use druid_shell::{Clipboard, KeyEvent, TimerToken}; - -use crate::kurbo::{Rect, Size}; -use crate::mouse::MouseEvent; +use crate::kurbo::Rect; // TODO - See issue #14 -use crate::promise::PromiseResult; -use crate::{Command, Notification, WidgetId}; - -/// An event, propagated downwards during event flow. -/// -/// Events are things that happen that the UI can be expected to react to: -/// -/// - Conventional platform interactions (eg [`MouseEvent`], [`KeyEvent`]). -/// - Messages sent from other widgets or background threads ([`Command`] and -/// [`Notification`]). -/// - Responses to requests send by the widget ([`Event::Timer`] and [`PromiseResult`]). -/// -/// Events are propagated through "event flow": they are passed down the -/// widget tree through [`Widget::on_event`](crate::Widget::on_event) methods. -/// Container widgets will generally pass each event to their children through -/// [`WidgetPod::on_event`](crate::WidgetPod::on_event), which internally takes -/// care of most of the event flow logic (in particular whether or not to recurse). -/// -/// This enum is expected to grow considerably, as there are many, many -/// different kinds of events that are relevant in a GUI. -// TODO - Add tutorial about event flow - See issue #5 -// TODO - Normalize variant decriptions -// TODO - Migrate bulk of descriptions to other types -#[non_exhaustive] +use crate::WidgetId; + +use std::{collections::HashSet, path::PathBuf}; + +use winit::dpi::{PhysicalPosition, PhysicalSize}; +use winit::event::{Ime, KeyEvent, Modifiers, MouseButton}; +use winit::keyboard::ModifiersState; + +// TODO - Occluded(bool) event +// TODO - winit ActivationTokenDone thing +// TODO - Suspended/Resume/NewEvents/MemoryWarning +// TODO - wtf is InnerSizeWriter? +// TODO - Move AnimFrame to Lifecycle +// TODO - switch anim frames to being about age / an absolute timestamp +// instead of time elapsed. +// (this will help in cases where we want to skip anim frames) #[derive(Debug, Clone)] -pub enum Event { - /// Sent to all widgets in a given window when that window is first instantiated. - /// - /// This should always be the first `Event` received, although widgets will - /// receive [`LifeCycle::WidgetAdded`] first. - /// - /// Widgets should handle this event if they need to do some addition setup - /// when a window is first created. - WindowConnected, - - /// Sent to all widgets in a given window when the system requests to close the window. - /// - /// If the event is handled (with [`EventCtx::set_handled`](crate::EventCtx::set_handled)), the window will not be closed. - /// All widgets are given an opportunity to handle this event; your widget should not assume - /// that the window *will* close just because this event is received; for instance, you should - /// avoid destructive side effects such as cleaning up resources. - WindowCloseRequested, - - /// Sent to all widgets in a given window when the system is going to close that window. - /// - /// This event means the window *will* go away; it is safe to dispose of resources and - /// do any other cleanup. - WindowDisconnected, - - /// Called on the root widget when the window size changes. - /// - /// **Note:** it's not obvious this should be propagated to user - /// widgets. It might be better to just handle it in `layout`. - WindowSize(Size), - - /// Called when a mouse button is pressed. - MouseDown(MouseEvent), - - /// Called when a mouse button is released. - MouseUp(MouseEvent), - - /// Called when the mouse is moved. - /// - /// The `MouseMove` event is propagated to the active widget, if - /// there is one, otherwise to hot widgets (see `HotChanged`). - /// If a widget loses its hot status due to `MouseMove` then that specific - /// `MouseMove` event is also still sent to that widget. - /// - /// The `MouseMove` event is also the primary mechanism for widgets - /// to set a cursor, for example to an I-bar inside a text widget. A - /// simple tactic is for the widget to unconditionally call - /// [`set_cursor`] in the MouseMove handler, as `MouseMove` is only - /// propagated to active or hot widgets. - /// - /// [`set_cursor`]: struct.EventCtx.html#method.set_cursor - MouseMove(MouseEvent), - - // TODO - What about trackpad scrolling? Touchscreens? - /// Called when the mouse wheel or trackpad is scrolled. - Wheel(MouseEvent), - - /// Called when a key is pressed. - KeyDown(KeyEvent), - - /// Called when a key is released. - /// - /// Because of repeat, there may be a number `KeyDown` events before - /// a corresponding `KeyUp` is sent. - KeyUp(KeyEvent), - - /// Called when a paste command is received. - Paste(Clipboard), - - // TODO - Rename to "TextChange" or something similar? - /// Sent to a widget when the platform may have mutated shared IME state. - /// - /// This is sent to a widget that has an attached IME session anytime the - /// platform has released a mutable lock on shared state. - /// - /// This does not *mean* that any state has changed, but the widget - /// should check the shared state, perform invalidation, and update `Data` - /// as necessary. - ImeStateChange, - - /// Called when the trackpad is pinched. - /// - /// The value is a delta. - Zoom(f64), - - /// Called at the beginning of a new animation frame. - /// - /// On the first frame when transitioning from idle to animating, `interval` - /// will be 0. (This logic is presently per-window but might change to - /// per-widget to make it more consistent). Otherwise it is in nanoseconds. - /// - /// The `paint` method will be called shortly after this event is finished. - /// As a result, you should try to avoid doing anything computationally - /// intensive in response to an `AnimFrame` event: it might make the app miss - /// the monitor's refresh, causing lag or jerky animations. - AnimFrame(u64), - - /// Called on a timer event. - /// - /// When the user creates a timer through - /// [`EventCtx::request_timer`](crate::EventCtx::request_timer), - /// a `Timer` event is sent when the time is up. - /// - /// Note that timer events from other widgets may be delivered as well. Use - /// the token returned from the `request_timer()` call to filter events more - /// precisely. - Timer(TimerToken), - - /// Called when a promise returns. - /// - /// When the user creates a promise through - /// [`EventCtx::compute_in_background`](crate::EventCtx::compute_in_background), - /// a`PromiseResult` event is sent when the computation completes. - PromiseResult(PromiseResult), - - /// An event containing a [`Command`] to be handled by the widget. - /// - /// Commands are messages, optionally with attached data, from other - /// widgets or other sources. See [`Command`] for details. - Command(Command), - - /// A [`Notification`] from one of this widget's descendants. - /// - /// Notifications are messages, optionally with attached data, from child - /// widgets. See [`Notification`] for details. - /// - /// If you handle a [`Notification`], you should call - /// [`EventCtx::set_handled`](crate::EventCtx::set_handled) - /// to stop the notification from being delivered to further ancestors. - Notification(Notification), - - /// Internal Masonry event. - /// - /// This should always be passed down to descendant [`WidgetPod`]s. - /// - /// [`WidgetPod`]: struct.WidgetPod.html - Internal(InternalEvent), +pub enum WindowEvent { + Rescale(f64), + Resize(PhysicalSize), + AnimFrame, } -/// Internal events used by Masonry inside [`WidgetPod`]. -/// -/// These events are translated into regular [`Event`]s -/// and should not be used directly. -/// -/// [`WidgetPod`]: struct.WidgetPod.html -/// [`Event`]: enum.Event.html +// TODO - How can RenderRoot express "I started a drag-and-drop op"? +// TODO - Touchpad, Touch, AxisMotion +// TODO - How to handle CursorEntered? +// Note to self: Events like "pointerenter", "pointerleave" are handled differently at the Widget level. But that's weird because WidgetPod can distribute them. Need to think about this again. #[derive(Debug, Clone)] -pub enum InternalEvent { - /// Sent in some cases when the mouse has left the window. - /// - /// This is used in cases when the platform no longer sends mouse events, - /// but we know that we've stopped receiving the mouse events. - MouseLeave, - - /// A command still in the process of being dispatched. - TargetedCommand(Command), +pub enum PointerEvent { + PointerDown(MouseButton, PointerState), + PointerUp(MouseButton, PointerState), + PointerMove(PointerState), + PointerEnter(PointerState), + PointerLeave(PointerState), + MouseWheel(PhysicalPosition, PointerState), + HoverFile(PathBuf, PointerState), + DropFile(PathBuf, PointerState), + HoverFileCancel(PointerState), +} - /// Used for routing timer events. - RouteTimer(TimerToken, WidgetId), +// TODO - Clipboard Paste? +// TODO skip is_synthetic=true events +#[derive(Debug, Clone)] +pub enum TextEvent { + KeyboardKey(KeyEvent, ModifiersState), + Ime(Ime), + ModifierChange(ModifiersState), + // TODO - Document difference with Lifecycle focus change + FocusChange(bool), +} - /// Used for routing promise results. - RoutePromiseResult(PromiseResult, WidgetId), +#[derive(Debug, Clone)] +pub struct PointerState { + // TODO + // pub device_id: DeviceId, + pub position: PhysicalPosition, + pub buttons: HashSet, + pub mods: Modifiers, + pub count: u8, + pub focus: bool, +} - /// Route an IME change event. - RouteImeStateChange(WidgetId), +#[derive(Debug, Clone)] +pub enum WindowTheme { + Light, + Dark, } /// Application life cycle events. @@ -228,6 +102,18 @@ pub enum LifeCycle { /// of events. WidgetAdded, + /// Called at the beginning of a new animation frame. + /// + /// On the first frame when transitioning from idle to animating, `interval` + /// will be 0. (This logic is presently per-window but might change to + /// per-widget to make it more consistent). Otherwise it is in nanoseconds. + /// + /// The `paint` method will be called shortly after this event is finished. + /// As a result, you should try to avoid doing anything computationally + /// intensive in response to an `AnimFrame` event: it might make the app miss + /// the monitor's refresh, causing lag or jerky animations. + AnimFrame(u64), + // TODO - Put in StatusChange /// Called when the Disabled state of the widgets is changed. /// @@ -315,78 +201,64 @@ pub enum StatusChange { FocusChanged(bool), } -impl Event { - /// Whether this event should be sent to widgets which are currently not visible and not - /// accessible. - /// - /// For example: the hidden tabs in a tabs widget are `hidden` whereas the non-visible - /// widgets in a scroll are not, since you can bring them into view by scrolling. - /// - /// This distinction between scroll and tabs is due to one of the main purposes of - /// this method: determining which widgets are allowed to receive focus. As a rule - /// of thumb a widget counts as `hidden` if it makes no sense for it to receive focus - /// when the user presses thee 'tab' key. - /// - /// If a widget changes which children are hidden it must call [`children_changed`]. - /// - /// See also [`LifeCycle::should_propagate_to_hidden`]. - /// - /// [`children_changed`]: crate::EventCtx::children_changed - /// [`LifeCycle::should_propagate_to_hidden`]: LifeCycle::should_propagate_to_hidden - pub fn should_propagate_to_hidden(&self) -> bool { +impl PointerEvent { + pub fn pointer_state(&self) -> &PointerState { match self { - Event::WindowConnected - | Event::WindowCloseRequested - | Event::WindowDisconnected - | Event::WindowSize(_) - | Event::Timer(_) - | Event::AnimFrame(_) - | Event::Command(_) - | Event::PromiseResult(_) - | Event::Notification(_) - | Event::Internal(_) => true, - Event::MouseDown(_) - | Event::MouseUp(_) - | Event::MouseMove(_) - | Event::Wheel(_) - | Event::KeyDown(_) - | Event::KeyUp(_) - | Event::Paste(_) - | Event::ImeStateChange - | Event::Zoom(_) => false, + PointerEvent::PointerDown(_, state) + | PointerEvent::PointerUp(_, state) + | PointerEvent::PointerMove(state) + | PointerEvent::PointerEnter(state) + | PointerEvent::PointerLeave(state) + | PointerEvent::MouseWheel(_, state) + | PointerEvent::HoverFile(_, state) + | PointerEvent::DropFile(_, state) + | PointerEvent::HoverFileCancel(state) => state, } } - /// Short name, for debug logging. - /// - /// Essentially returns the enum variant name. pub fn short_name(&self) -> &'static str { match self { - Event::Internal(internal) => match internal { - InternalEvent::MouseLeave => "MouseLeave", - InternalEvent::TargetedCommand(_) => "TargetedCommand", - InternalEvent::RouteTimer(_, _) => "RouteTimer", - InternalEvent::RoutePromiseResult(_, _) => "RoutePromiseResult", - InternalEvent::RouteImeStateChange(_) => "RouteImeStateChange", - }, - Event::WindowConnected => "WindowConnected", - Event::WindowCloseRequested => "WindowCloseRequested", - Event::WindowDisconnected => "WindowDisconnected", - Event::WindowSize(_) => "WindowSize", - Event::Timer(_) => "Timer", - Event::AnimFrame(_) => "AnimFrame", - Event::Command(_) => "Command", - Event::PromiseResult(_) => "PromiseResult", - Event::Notification(_) => "Notification", - Event::MouseDown(_) => "MouseDown", - Event::MouseUp(_) => "MouseUp", - Event::MouseMove(_) => "MouseMove", - Event::Wheel(_) => "Wheel", - Event::KeyDown(_) => "KeyDown", - Event::KeyUp(_) => "KeyUp", - Event::Paste(_) => "Paste", - Event::ImeStateChange => "ImeStateChange", - Event::Zoom(_) => "Zoom", + PointerEvent::PointerDown(_, _) => "PointerDown", + PointerEvent::PointerUp(_, _) => "PointerUp", + PointerEvent::PointerMove(_) => "PointerMove", + PointerEvent::PointerEnter(_) => "PointerEnter", + PointerEvent::PointerLeave(_) => "PointerLeave", + PointerEvent::MouseWheel(_, _) => "MouseWheel", + PointerEvent::HoverFile(_, _) => "HoverFile", + PointerEvent::DropFile(_, _) => "DropFile", + PointerEvent::HoverFileCancel(_) => "HoverFileCancel", + } + } +} + +impl TextEvent { + pub fn short_name(&self) -> &'static str { + match self { + TextEvent::KeyboardKey(_, _) => "KeyboardKey", + TextEvent::Ime(_) => "Ime", + TextEvent::ModifierChange(_) => "ModifierChange", + TextEvent::FocusChange(_) => "FocusChange", + } + } +} + +impl PointerState { + pub fn empty() -> Self { + #[cfg(FALSE)] + #[allow(unsafe_code)] + // SAFETY: Uuuuh, unclear. Winit says the dummy id should only be used in + // tests and should never be passed to winit. In principle, we're never + // passing this id to winit, but it's still visible to custom widgets which + // might do so if they tried really hard. + // It would be a lot better if winit could just make this constructor safe. + let device_id = unsafe { DeviceId::dummy() }; + + PointerState { + position: PhysicalPosition::new(0.0, 0.0), + buttons: Default::default(), + mods: Default::default(), + count: 0, + focus: false, } } } @@ -405,6 +277,7 @@ impl LifeCycle { match self { LifeCycle::Internal(internal) => internal.should_propagate_to_hidden(), LifeCycle::WidgetAdded => true, + LifeCycle::AnimFrame(_) => true, LifeCycle::DisabledChanged(_) => true, LifeCycle::BuildFocusChain => false, LifeCycle::RequestPanToChild(_) => false, @@ -423,6 +296,7 @@ impl LifeCycle { InternalLifeCycle::ParentWindowOrigin => "ParentWindowOrigin", }, LifeCycle::WidgetAdded => "WidgetAdded", + LifeCycle::AnimFrame(_) => "AnimFrame", LifeCycle::DisabledChanged(_) => "DisabledChanged", LifeCycle::BuildFocusChain => "BuildFocusChain", LifeCycle::RequestPanToChild(_) => "RequestPanToChild", diff --git a/src/event_loop_runner.rs b/src/event_loop_runner.rs new file mode 100644 index 00000000..dcaa4c11 --- /dev/null +++ b/src/event_loop_runner.rs @@ -0,0 +1,234 @@ +use std::num::NonZeroUsize; +use std::sync::Arc; + +use tracing::warn; +use vello::util::{RenderContext, RenderSurface}; +use vello::{peniko::Color, AaSupport, RenderParams, Renderer, RendererOptions, Scene}; +use wgpu::PresentMode; +use winit::dpi::PhysicalPosition; +use winit::error::EventLoopError; +use winit::event::WindowEvent as WinitWindowEvent; +use winit::event_loop::EventLoop; +use winit::window::Window; + +use crate::app_driver::{AppDriver, DriverCtx}; +use crate::event::{PointerState, WindowEvent}; +use crate::render_root::{self, RenderRoot, WindowSizePolicy}; +use crate::{PointerEvent, TextEvent, Widget}; + +pub struct EventLoopRunner { + window: Arc, + event_loop: EventLoop<()>, + render_root: RenderRoot, + app_driver: Box, +} + +struct MainState<'a> { + window: Arc, + render_cx: RenderContext, + surface: RenderSurface<'a>, + renderer: Option, + pointer_state: PointerState, + app_driver: Box, +} + +impl EventLoopRunner { + pub fn new( + root_widget: impl Widget, + window: Window, + event_loop: EventLoop<()>, + app_driver: impl AppDriver + 'static, + ) -> Self { + Self { + window: Arc::new(window), + event_loop, + render_root: RenderRoot::new(root_widget, WindowSizePolicy::User), + app_driver: Box::new(app_driver), + } + } + + pub fn run(self) -> Result<(), EventLoopError> { + let mut render_cx = RenderContext::new().unwrap(); + let size = self.window.inner_size(); + let surface = pollster::block_on(render_cx.create_surface( + self.window.clone(), + size.width, + size.height, + PresentMode::AutoVsync, + )) + .unwrap(); + let mut render_root = self.render_root; + let mut main_state = MainState { + window: self.window, + render_cx, + surface, + renderer: None, + pointer_state: PointerState::empty(), + app_driver: self.app_driver, + }; + + self.event_loop.run(move |event, window_target| { + if let winit::event::Event::WindowEvent { event: e, .. } = event { + match e { + WinitWindowEvent::RedrawRequested => { + let scene = render_root.redraw(); + main_state.render(scene); + } + WinitWindowEvent::CloseRequested => window_target.exit(), + WinitWindowEvent::Resized(size) => { + render_root.handle_window_event(WindowEvent::Resize(size)); + } + WinitWindowEvent::ModifiersChanged(modifiers) => { + render_root.handle_text_event(TextEvent::ModifierChange(modifiers.state())); + } + WinitWindowEvent::CursorMoved { position, .. } => { + main_state.pointer_state.position = position; + render_root.handle_pointer_event(PointerEvent::PointerMove( + main_state.pointer_state.clone(), + )); + } + WinitWindowEvent::CursorLeft { .. } => { + render_root.handle_pointer_event(PointerEvent::PointerLeave( + main_state.pointer_state.clone(), + )); + } + WinitWindowEvent::MouseInput { state, button, .. } => match state { + winit::event::ElementState::Pressed => { + render_root.handle_pointer_event(PointerEvent::PointerDown( + button, + main_state.pointer_state.clone(), + )); + } + winit::event::ElementState::Released => { + render_root.handle_pointer_event(PointerEvent::PointerUp( + button, + main_state.pointer_state.clone(), + )); + } + }, + WinitWindowEvent::MouseWheel { delta, .. } => { + let delta = match delta { + winit::event::MouseScrollDelta::LineDelta(x, y) => (x as f64, y as f64), + winit::event::MouseScrollDelta::PixelDelta(delta) => (delta.x, delta.y), + }; + let delta = PhysicalPosition::new(delta.0, delta.1); + render_root.handle_pointer_event(PointerEvent::MouseWheel( + delta, + main_state.pointer_state.clone(), + )); + } + _ => (), + } + main_state.process_signals(&mut render_root); + } + }) + } +} + +impl MainState<'_> { + fn render(&mut self, scene: Scene) { + //let scale = self.window.scale_factor(); + let size = self.window.inner_size(); + let width = size.width; + let height = size.height; + + if self.surface.config.width != width || self.surface.config.height != height { + self.render_cx + .resize_surface(&mut self.surface, width, height); + } + + #[cfg(FALSE)] + let transform = if scale != 1.0 { + Some(Affine::scale(scale)) + } else { + None + }; + + let Ok(surface_texture) = self.surface.surface.get_current_texture() else { + warn!("failed to acquire next swapchain texture"); + return; + }; + let dev_id = self.surface.dev_id; + let device = &self.render_cx.devices[dev_id].device; + let queue = &self.render_cx.devices[dev_id].queue; + let renderer_options = RendererOptions { + surface_format: Some(self.surface.format), + use_cpu: false, + antialiasing_support: AaSupport { + area: true, + msaa8: false, + msaa16: false, + }, + num_init_threads: NonZeroUsize::new(1), + }; + let render_params = RenderParams { + base_color: Color::BLACK, + width, + height, + antialiasing_method: vello::AaConfig::Area, + }; + self.renderer + .get_or_insert_with(|| Renderer::new(device, renderer_options).unwrap()) + .render_to_surface(device, queue, &scene, &surface_texture, &render_params) + .expect("failed to render to surface"); + surface_texture.present(); + device.poll(wgpu::Maintain::Wait); + } + + fn process_signals(&mut self, render_root: &mut RenderRoot) { + while let Some(signal) = render_root.pop_signal() { + match signal { + render_root::RenderRootSignal::Action(action, widget_id) => { + render_root.edit_root_widget(|root| { + let mut driver_ctx = DriverCtx { + main_root_widget: root, + }; + self.app_driver + .on_action(&mut driver_ctx, widget_id, action); + }); + } + render_root::RenderRootSignal::TextFieldAdded => { + // TODO + } + render_root::RenderRootSignal::TextFieldRemoved => { + // TODO + } + render_root::RenderRootSignal::TextFieldFocused => { + // TODO + } + render_root::RenderRootSignal::ImeStarted => { + // TODO + } + render_root::RenderRootSignal::ImeMoved => { + // TODO + } + render_root::RenderRootSignal::ImeInvalidated => { + // TODO + } + render_root::RenderRootSignal::RequestRedraw => { + self.window.request_redraw(); + } + render_root::RenderRootSignal::RequestAnimFrame => { + // TODO + self.window.request_redraw(); + } + render_root::RenderRootSignal::SpawnWorker(_worker_fn) => { + // TODO + } + render_root::RenderRootSignal::TakeFocus => { + self.window.focus_window(); + } + render_root::RenderRootSignal::SetCursor(cursor_icon) => { + self.window.set_cursor_icon(cursor_icon); + } + render_root::RenderRootSignal::SetSize(size) => { + // TODO - Handle return value? + let _ = self.window.request_inner_size(size); + } + render_root::RenderRootSignal::SetTitle(title) => { + self.window.set_title(&title); + } + } + } + } +} diff --git a/src/ext_event.rs b/src/ext_event.rs index 53b9af2d..84543eb3 100644 --- a/src/ext_event.rs +++ b/src/ext_event.rs @@ -3,6 +3,7 @@ // details. //! Simple handle for submitting external events. +#![allow(unused)] use std::any::Any; use std::collections::VecDeque; @@ -10,14 +11,13 @@ use std::sync::{Arc, Mutex}; use druid_shell::IdleHandle; -use crate::command::SelectorSymbol; -use crate::platform::EXT_EVENT_IDLE_TOKEN; use crate::promise::PromiseResult; use crate::widget::WidgetId; -use crate::{Selector, Target, WindowId}; + +// FIXME - Remove +pub struct WindowId; pub(crate) enum ExtMessage { - Command(SelectorSymbol, Box, Target), Promise(PromiseResult, WidgetId, WindowId), } @@ -78,35 +78,6 @@ impl ExtEventQueue { } impl ExtEventSink { - /// Submit a [`Command`] to the running application. - /// - /// [`Command`] is not thread safe, so you cannot submit it directly; - /// instead you have to pass the [`Selector`] and the payload - /// separately, and it will be turned into a [`Command`] when it is received. - /// - /// For the **target** argument, [`Target::Auto`] is equivalent to [`Target::Global`]. - /// - /// [`Command`]: struct.Command.html - /// [`Target::Auto`]: enum.Target.html#variant.Auto - /// [`Target::Global`]: enum.Target.html#variant.Global - pub fn submit_command( - &self, - selector: Selector, - payload: impl Into>, - target: impl Into, - ) -> Result<(), ExtEventError> { - let target = target.into(); - let payload = payload.into(); - if let Some(handle) = self.handle.lock().unwrap().as_mut() { - handle.schedule_idle(EXT_EVENT_IDLE_TOKEN); - } - self.queue - .lock() - .map_err(|_| ExtEventError)? - .push_back(ExtMessage::Command(selector.symbol(), payload, target)); - Ok(()) - } - #[allow(missing_docs)] pub fn resolve_promise( &self, @@ -115,7 +86,7 @@ impl ExtEventSink { target_window: WindowId, ) -> Result<(), ExtEventError> { if let Some(handle) = self.handle.lock().unwrap().as_mut() { - handle.schedule_idle(EXT_EVENT_IDLE_TOKEN); + todo!() } self.queue .lock() diff --git a/src/lib.rs b/src/lib.rs index 4253e322..ab1f107a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,9 +12,10 @@ //! //! ## Example //! +//! **(TODO: FIX THIS EXAMPLE)** //! The todo-list example looks like this: //! -//! ```no_run +//! ```ignore //! use masonry::widget::{prelude::*, TextBox}; //! use masonry::widget::{Button, Flex, Label, Portal, WidgetMut}; //! use masonry::Action; @@ -91,50 +92,38 @@ // TODO - Add logo -pub use druid_shell as shell; #[doc(inline)] -pub use druid_shell::{kurbo, piet}; +pub use kurbo; #[macro_use] mod util; mod action; -mod app_delegate; -mod app_launcher; -mod app_root; mod bloom; mod box_constraints; -pub mod command; mod contexts; mod event; -pub mod ext_event; -mod mouse; -mod platform; +pub mod paint_scene_helpers; pub mod promise; +pub mod render_root; pub mod testing; -pub mod text; +pub mod text_helpers; pub mod theme; pub mod widget; // TODO +pub mod app_driver; pub mod debug_logger; pub mod debug_values; +pub mod event_loop_runner; pub use action::Action; -pub use app_delegate::{AppDelegate, DelegateCtx}; -pub use app_launcher::AppLauncher; -pub use app_root::{AppRoot, WindowRoot}; pub use box_constraints::BoxConstraints; -pub use command::{Command, Notification, Selector, SingleUse, Target}; pub use contexts::{EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx, WidgetCtx}; -pub use druid_shell::Error as PlatformError; -pub use event::{Event, InternalEvent, InternalLifeCycle, LifeCycle, StatusChange}; +pub use event::{InternalLifeCycle, LifeCycle, PointerEvent, StatusChange, TextEvent, WindowTheme}; pub use kurbo::{Affine, Insets, Point, Rect, Size, Vec2}; -pub use mouse::MouseEvent; -pub use piet::{Color, ImageBuf, LinearGradient, RadialGradient, RenderContext, UnitPoint}; -pub use platform::{ - MasonryWinHandler, WindowConfig, WindowDescription, WindowId, WindowSizePolicy, -}; -pub use text::ArcStr; pub use util::{AsAny, Handled}; +pub use vello::peniko::{Color, Gradient}; pub use widget::{BackgroundBrush, Widget, WidgetId, WidgetPod, WidgetState}; + +pub use text_helpers::ArcStr; diff --git a/src/mouse.rs b/src/mouse.rs deleted file mode 100644 index 26104e3d..00000000 --- a/src/mouse.rs +++ /dev/null @@ -1,89 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -#![cfg(not(tarpaulin_include))] - -//! The mousey bits - -use druid_shell::{Modifiers, MouseButton, MouseButtons}; - -use crate::kurbo::{Point, Vec2}; - -/// The state of the mouse for a click, mouse-up, move, or wheel event. -/// -/// In Masonry, unlike in `druid_shell`, we treat the widget's coordinate -/// space and the window's coordinate space separately. -/// -/// Every mouse event can have a new position. There is no guarantee of -/// receiving an [`Event::MouseMove`] before another mouse event. -/// -/// When comparing to the position that was reported by [`Event::MouseMove`], -/// the position in relation to the window might have changed because -/// the window moved or the platform just didn't inform us of the move. -/// The position may also have changed in relation to the receiver, -/// because the receiver's location changed without the mouse moving. -/// -/// [`Event::MouseMove`]: enum.Event.html#variant.MouseMove -#[derive(Debug, Clone)] -pub struct MouseEvent { - /// The position of the mouse in the coordinate space of the receiver. - pub pos: Point, - /// The position of the mouse in the coordinate space of the window. - pub window_pos: Point, - /// Mouse buttons being held down during a move or after a click event. - /// Thus it will contain the `button` that triggered a mouse-down event, - /// and it will not contain the `button` that triggered a mouse-up event. - pub buttons: MouseButtons, - /// Keyboard modifiers at the time of the event. - pub mods: Modifiers, - /// The number of mouse clicks associated with this event. This will always - /// be `0` for a mouse-up and mouse-move events. - pub count: u8, - /// Focus is `true` on macOS when the mouse-down event (or its companion mouse-up event) - /// with `MouseButton::Left` was the event that caused the window to gain focus. - /// - /// This is primarily used in relation to text selection. - /// If there is some text selected in some text widget and it receives a click - /// with `focus` set to `true` then the widget should gain focus (i.e. start blinking a cursor) - /// but it should not change the text selection. Text selection should only be changed - /// when the click has `focus` set to `false`. - pub focus: bool, - /// The button that was pressed down in the case of mouse-down, - /// or the button that was released in the case of mouse-up. - /// This will always be `MouseButton::None` in the case of mouse-move. - pub button: MouseButton, - /// The wheel movement. - /// - /// The polarity is the amount to be added to the scroll position, - /// in other words the opposite of the direction the content should - /// move on scrolling. This polarity is consistent with the - /// deltaX and deltaY values in a web [WheelEvent]. - /// - /// [WheelEvent]: https://w3c.github.io/uievents/#event-type-wheel - pub wheel_delta: Vec2, -} - -impl From for MouseEvent { - fn from(src: druid_shell::MouseEvent) -> MouseEvent { - let druid_shell::MouseEvent { - pos, - buttons, - mods, - count, - focus, - button, - wheel_delta, - } = src; - MouseEvent { - pos, - window_pos: pos, - buttons, - mods, - count, - focus, - button, - wheel_delta, - } - } -} diff --git a/src/paint_scene_helpers.rs b/src/paint_scene_helpers.rs new file mode 100644 index 00000000..ad4e9f88 --- /dev/null +++ b/src/paint_scene_helpers.rs @@ -0,0 +1,84 @@ +#![allow(missing_docs)] + +use vello::{ + kurbo::{self, Affine, Rect, Shape, Stroke}, + peniko::{BrushRef, Color, ColorStopsSource, Fill, Gradient}, + Scene, +}; + +// TODO - Remove this file + +#[derive(Debug, Clone, Copy)] +pub struct UnitPoint { + u: f64, + v: f64, +} + +pub fn stroke<'b>( + scene: &mut Scene, + path: &impl Shape, + brush: impl Into>, + stroke_width: f64, +) { + scene.stroke( + &Stroke::new(stroke_width), + Affine::IDENTITY, + brush, + None, + path, + ); +} + +#[allow(unused)] +impl UnitPoint { + /// `(0.0, 0.0)` + pub const TOP_LEFT: UnitPoint = UnitPoint::new(0.0, 0.0); + /// `(0.5, 0.0)` + pub const TOP: UnitPoint = UnitPoint::new(0.5, 0.0); + /// `(1.0, 0.0)` + pub const TOP_RIGHT: UnitPoint = UnitPoint::new(1.0, 0.0); + /// `(0.0, 0.5)` + pub const LEFT: UnitPoint = UnitPoint::new(0.0, 0.5); + /// `(0.5, 0.5)` + pub const CENTER: UnitPoint = UnitPoint::new(0.5, 0.5); + /// `(1.0, 0.5)` + pub const RIGHT: UnitPoint = UnitPoint::new(1.0, 0.5); + /// `(0.0, 1.0)` + pub const BOTTOM_LEFT: UnitPoint = UnitPoint::new(0.0, 1.0); + /// `(0.5, 1.0)` + pub const BOTTOM: UnitPoint = UnitPoint::new(0.5, 1.0); + /// `(1.0, 1.0)` + pub const BOTTOM_RIGHT: UnitPoint = UnitPoint::new(1.0, 1.0); + + /// Create a new UnitPoint. + /// + /// The `u` and `v` coordinates describe the point, with (0.0, 0.0) being + /// the top-left, and (1.0, 1.0) being the bottom-right. + pub const fn new(u: f64, v: f64) -> UnitPoint { + UnitPoint { u, v } + } + + /// Given a rectangle, resolve the point within the rectangle. + pub fn resolve(self, rect: Rect) -> kurbo::Point { + kurbo::Point::new( + rect.x0 + self.u * (rect.x1 - rect.x0), + rect.y0 + self.v * (rect.y1 - rect.y0), + ) + } +} + +pub fn fill_lin_gradient( + scene: &mut Scene, + path: &impl Shape, + stops: impl ColorStopsSource, + start: UnitPoint, + end: UnitPoint, +) { + let rect = path.bounding_box(); + let brush = Gradient::new_linear(start.resolve(rect), end.resolve(rect)).with_stops(stops); + scene.fill(Fill::NonZero, Affine::IDENTITY, &brush, None, path); +} + +pub fn fill_color(scene: &mut Scene, path: &impl Shape, color: Color) { + scene.fill(Fill::NonZero, Affine::IDENTITY, color, None, path); +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs deleted file mode 100644 index e5ed404a..00000000 --- a/src/platform/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -#[cfg(not(tarpaulin_include))] -mod win_handler; -#[cfg(not(tarpaulin_include))] -mod window_description; - -pub use win_handler::{DialogInfo, MasonryAppHandler, MasonryWinHandler}; -pub(crate) use win_handler::{EXT_EVENT_IDLE_TOKEN, RUN_COMMANDS_TOKEN}; -pub use window_description::{WindowConfig, WindowDescription, WindowId, WindowSizePolicy}; diff --git a/src/platform/win_handler.rs b/src/platform/win_handler.rs deleted file mode 100644 index 7012f3b1..00000000 --- a/src/platform/win_handler.rs +++ /dev/null @@ -1,219 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! The implementation of the WinHandler trait (druid-shell integration). - -use std::any::Any; - -use druid_shell::text::InputHandler; -use druid_shell::{ - AppHandler, FileDialogToken, FileInfo, IdleToken, KeyEvent, MouseEvent, Region, Scale, - TextFieldToken, TimerToken, WinHandler, WindowHandle, -}; - -use crate::app_root::AppRoot; -use crate::kurbo::Size; -use crate::piet::Piet; -use crate::{command as sys_cmd, Event, InternalEvent, Selector, WindowId}; - -pub(crate) const RUN_COMMANDS_TOKEN: IdleToken = IdleToken::new(1); - -/// A token we are called back with if an external event was submitted. -pub(crate) const EXT_EVENT_IDLE_TOKEN: IdleToken = IdleToken::new(2); - -/// The top-level handler for a window's events. -/// -/// This struct implements the druid-shell `WinHandler` trait. One `MasonryWinHandler` -/// exists per window. -/// -/// This is only an internal detail for now, but it might be exposed for unit tests in the future. -pub struct MasonryWinHandler { - /// The shared app state. - pub(crate) app_state: AppRoot, - /// The id for the current window. - pub(crate) window_id: WindowId, -} - -/// The top-level handler for window-less events. -/// -/// This struct implements the druid-shell `AppHandler` trait. One `MasonryAppHandler` -/// exists per application. -/// -/// It handles events that are not associated with a window. Currently, this means only -/// menu items on macOS when no window is open. -/// -/// This is only an internal detail for now, but it might be exposed for unit tests in the future. -pub struct MasonryAppHandler { - /// The shared app state. - pub(crate) app_state: AppRoot, -} - -// TODO - Move to separate file -/// The information for forwarding druid-shell's file dialog reply to the right place. -pub struct DialogInfo { - /// The window to send the command to. - pub id: WindowId, - /// The command to send if the dialog is accepted. - pub accept_cmd: Selector, - /// The command to send if the dialog is cancelled. - pub cancel_cmd: Selector<()>, -} - -impl MasonryAppHandler { - pub(crate) fn new(app_state: AppRoot) -> Self { - Self { app_state } - } -} - -impl MasonryWinHandler { - /// Note: the root widget doesn't go in here, because it gets added to the - /// app state. - pub(crate) fn new_shared(app_state: AppRoot, window_id: WindowId) -> MasonryWinHandler { - MasonryWinHandler { - app_state, - window_id, - } - } -} - -impl AppHandler for MasonryAppHandler { - fn command(&mut self, id: u32) { - self.app_state.handle_system_cmd(id, None) - } -} - -// Every WinHandler method is triggered by some sort of platform event. -// -// The method implementations should be short (two lines at most, usually), and call -// AppRoot methods directly. MasonryWinHandler methods should never break or check -// invariants: that's AppRoot's job. -impl WinHandler for MasonryWinHandler { - fn connect(&mut self, handle: &WindowHandle) { - self.app_state - .window_connected(self.window_id, handle.clone()); - } - - fn request_close(&mut self) { - let event = Event::Command(sys_cmd::CLOSE_WINDOW.to(self.window_id)); - self.app_state.handle_event(event, self.window_id); - } - - fn destroy(&mut self) { - self.app_state.window_removed(self.window_id); - } - - fn got_focus(&mut self) { - self.app_state.window_got_focus(self.window_id); - } - - fn prepare_paint(&mut self) { - self.app_state.prepare_paint(self.window_id); - } - - fn paint(&mut self, piet: &mut Piet, region: &Region) { - self.app_state.paint(self.window_id, piet, region); - } - - fn size(&mut self, size: Size) { - let event = Event::WindowSize(size); - self.app_state.handle_event(event, self.window_id); - } - - fn scale(&mut self, _scale: Scale) { - // TODO: Do something with the scale - } - - fn command(&mut self, id: u32) { - self.app_state.handle_system_cmd(id, Some(self.window_id)); - } - - fn save_as(&mut self, token: FileDialogToken, file_info: Option) { - self.app_state.handle_dialog_response(token, file_info); - } - - fn open_file(&mut self, token: FileDialogToken, file_info: Option) { - self.app_state.handle_dialog_response(token, file_info); - } - - fn mouse_down(&mut self, event: &MouseEvent) { - // Note that double-click detection is done in druid-shell. - let event = Event::MouseDown(event.clone().into()); - self.app_state.handle_event(event, self.window_id); - } - - fn mouse_up(&mut self, event: &MouseEvent) { - let event = Event::MouseUp(event.clone().into()); - self.app_state.handle_event(event, self.window_id); - } - - fn mouse_move(&mut self, event: &MouseEvent) { - let event = Event::MouseMove(event.clone().into()); - self.app_state.handle_event(event, self.window_id); - } - - fn mouse_leave(&mut self) { - self.app_state - .handle_event(Event::Internal(InternalEvent::MouseLeave), self.window_id); - } - - fn key_down(&mut self, event: KeyEvent) -> bool { - self.app_state - .handle_event(Event::KeyDown(event), self.window_id) - .is_handled() - } - - fn key_up(&mut self, event: KeyEvent) { - self.app_state - .handle_event(Event::KeyUp(event), self.window_id); - } - - fn wheel(&mut self, event: &MouseEvent) { - self.app_state - .handle_event(Event::Wheel(event.clone().into()), self.window_id); - } - - fn zoom(&mut self, delta: f64) { - let event = Event::Zoom(delta); - self.app_state.handle_event(event, self.window_id); - } - - fn timer(&mut self, token: TimerToken) { - self.app_state - .handle_event(Event::Timer(token), self.window_id); - } - - fn idle(&mut self, token: IdleToken) { - match token { - RUN_COMMANDS_TOKEN => { - self.app_state.run_commands(); - } - EXT_EVENT_IDLE_TOKEN => { - self.app_state.run_ext_events(); - } - other => { - tracing::warn!("unexpected idle token {:?}", other); - } - } - } - - fn as_any(&mut self) -> &mut dyn Any { - self - } - - fn acquire_input_lock( - &mut self, - token: TextFieldToken, - mutable: bool, - ) -> Box { - self.app_state.get_ime_lock(self.window_id, token, mutable) - } - - fn release_input_lock(&mut self, token: TextFieldToken) { - let needs_update = self.app_state.release_ime_lock(self.window_id, token); - if let Some(widget) = needs_update { - let event = Event::Internal(InternalEvent::RouteImeStateChange(widget)); - self.app_state.handle_event(event, self.window_id); - } - } -} diff --git a/src/platform/window_description.rs b/src/platform/window_description.rs deleted file mode 100644 index f86938c1..00000000 --- a/src/platform/window_description.rs +++ /dev/null @@ -1,366 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -use druid_shell::{Counter, WindowBuilder, WindowHandle, WindowLevel, WindowState}; - -use crate::kurbo::{Point, Size}; -use crate::{ArcStr, Widget}; - -/// A unique identifier for a window. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct WindowId(u64); - -/// A description of a window to be instantiated. -/// -/// This object is paramaterized with builder-style methods, eg: -/// -/// ```no_run -/// # use masonry::WindowDescription; -/// # let some_widget = masonry::widget::Label::new("hello"); -/// let main_window = WindowDescription::new(some_widget) -/// .title("My window") -/// .window_size((400.0, 400.0)); -/// ``` -pub struct WindowDescription { - pub(crate) root: Box, - pub(crate) title: ArcStr, - pub(crate) config: WindowConfig, - /// The `WindowId` that will be assigned to this window. - /// - /// This can be used to track a window from when it is launched to when - /// it actually connects. - pub id: WindowId, -} - -/// Defines how a windows size should be determined -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub enum WindowSizePolicy { - /// Use the content of the window to determine the size. - /// - /// If you use this option, your root widget will be passed infinite constraints; - /// you are responsible for ensuring that your content picks an appropriate size. - Content, - /// Use the provided window size. - #[default] - User, -} - -/// Window configuration that can be applied to a [WindowBuilder], or to an existing [WindowHandle]. -/// -/// It does not include anything related to app data. -#[derive(Default, PartialEq)] -pub struct WindowConfig { - pub(crate) size_policy: WindowSizePolicy, - pub(crate) size: Option, - pub(crate) min_size: Option, - pub(crate) position: Option, - pub(crate) resizable: Option, - pub(crate) transparent: Option, - pub(crate) show_titlebar: Option, - pub(crate) level: Option, - // TODO - Remove? - pub(crate) state: Option, -} - -// --- - -impl WindowId { - /// Allocate a new, unique window id. - pub fn next() -> WindowId { - static WINDOW_COUNTER: Counter = Counter::new(); - WindowId(WINDOW_COUNTER.next()) - } -} - -impl WindowDescription { - /// Create a new `WindowDescription`, taking the root [`Widget`] for this window. - pub fn new(root: W) -> WindowDescription - where - W: Widget + 'static, - { - WindowDescription { - root: Box::new(root), - // FIXME - add argument instead - title: "Masonry application".into(), - config: WindowConfig::default(), - id: WindowId::next(), - } - } - - /// Set the window title - pub fn title(mut self, title: impl Into) -> Self { - self.title = title.into(); - self - } - - /// Set the window size policy - pub fn window_size_policy(mut self, size_policy: WindowSizePolicy) -> Self { - #[cfg(windows)] - { - // On Windows content_insets doesn't work on window with no initial size - // so the window size can't be adapted to the content, to fix this a - // non null initial size is set here. - if size_policy == WindowSizePolicy::Content { - self.config.size = Some(Size::new(1., 1.)) - } - } - self.config.size_policy = size_policy; - self - } - - /// Set the window's initial drawing area size in [display points](druid_shell::Scale). - /// - /// You can pass in a tuple `(width, height)` or a [`Size`], - /// e.g. to create a window with a drawing area 1000dp wide and 500dp high: - /// - /// ```ignore - /// window.window_size((1000.0, 500.0)); - /// ``` - /// - /// The actual window size in pixels will depend on the platform DPI settings. - /// - /// This should be considered a request to the platform to set the size of the window. - /// The platform might increase the size a tiny bit due to DPI. - pub fn window_size(mut self, size: impl Into) -> Self { - self.config.size = Some(size.into()); - self - } - - /// Set the window's minimum drawing area size in [display points](druid_shell::Scale). - /// - /// The actual minimum window size in pixels will depend on the platform DPI settings. - /// - /// This should be considered a request to the platform to set the minimum size of the window. - /// The platform might increase the size a tiny bit due to DPI. - /// - /// To set the window's initial drawing area size use [`window_size`](Self::window_size). - pub fn min_size(mut self, size: impl Into) -> Self { - self.config = self.config.min_size(size); - self - } - - /// Set whether this window can be resized. - pub fn resizable(mut self, resizable: bool) -> Self { - self.config = self.config.resizable(resizable); - self - } - - /// Set whether this window's titlebar is visible. - pub fn show_titlebar(mut self, show_titlebar: bool) -> Self { - self.config = self.config.show_titlebar(show_titlebar); - self - } - - /// Set whether this window's background should be transparent. - pub fn transparent(mut self, transparent: bool) -> Self { - self.config = self.config.transparent(transparent); - self - } - - /// Set the initial window position in [display points](druid_shell::Scale), relative to the origin - /// of the [virtual screen](druid_shell::Screen). - pub fn set_position(mut self, position: impl Into) -> Self { - self.config = self.config.set_position(position.into()); - self - } - - /// Set the [`WindowLevel`] of the window. - pub fn set_level(mut self, level: WindowLevel) -> Self { - self.config = self.config.set_level(level); - self - } - - /// Set initial [`WindowState`] of the window (eg minimized/maximized). - pub fn set_window_state(mut self, state: WindowState) -> Self { - self.config = self.config.set_window_state(state); - self - } - - /// Set the [`WindowConfig`] of the window. - pub fn with_config(mut self, config: WindowConfig) -> Self { - self.config = config; - self - } -} - -impl WindowConfig { - /// Set the window size policy. - pub fn window_size_policy(mut self, size_policy: WindowSizePolicy) -> Self { - #[cfg(windows)] - { - // On Windows content_insets doesn't work on window with no initial size - // so the window size can't be adapted to the content, to fix this a - // non null initial size is set here. - if size_policy == WindowSizePolicy::Content { - self.size = Some(Size::new(1., 1.)) - } - } - self.size_policy = size_policy; - self - } - - /// Set the window's initial drawing area size in [display points](druid_shell::Scale). - /// - /// You can pass in a tuple `(width, height)` or a [`Size`], - /// e.g. to create a window with a drawing area 1000dp wide and 500dp high: - /// - /// ```ignore - /// window.window_size((1000.0, 500.0)); - /// ``` - /// - /// The actual window size in pixels will depend on the platform DPI settings. - /// - /// This should be considered a request to the platform to set the size of the window. - /// The platform might increase the size a tiny bit due to DPI. - pub fn window_size(mut self, size: impl Into) -> Self { - self.size = Some(size.into()); - self - } - - /// Set the window's minimum drawing area size in [display points](druid_shell::Scale). - /// - /// The actual minimum window size in pixels will depend on the platform DPI settings. - /// - /// This should be considered a request to the platform to set the minimum size of the window. - /// The platform might increase the size a tiny bit due to DPI. - /// - /// To set the window's initial drawing area size use [`window_size`](WindowConfig::window_size). - pub fn min_size(mut self, size: impl Into) -> Self { - self.min_size = Some(size.into()); - self - } - - /// Set whether the window should be resizable. - pub fn resizable(mut self, resizable: bool) -> Self { - self.resizable = Some(resizable); - self - } - - /// Set whether the window should have a titlebar and decorations. - pub fn show_titlebar(mut self, show_titlebar: bool) -> Self { - self.show_titlebar = Some(show_titlebar); - self - } - - /// Set the window position in virtual screen coordinates. - /// - /// Position is in pixels. - pub fn set_position(mut self, position: Point) -> Self { - self.position = Some(position); - self - } - - /// Set the [`WindowLevel`] of the window - /// - /// [`WindowLevel`]: enum.WindowLevel.html - pub fn set_level(mut self, level: WindowLevel) -> Self { - self.level = Some(level); - self - } - - /// Set the [`WindowState`] of the window. - /// - /// [`WindowState`]: enum.WindowState.html - pub fn set_window_state(mut self, state: WindowState) -> Self { - self.state = Some(state); - self - } - - /// Set whether the window background should be transparent - pub fn transparent(mut self, transparent: bool) -> Self { - self.transparent = Some(transparent); - self - } - - /// Apply this window configuration to the given WindowBuilder - pub fn apply_to_builder(&self, builder: &mut WindowBuilder) { - if let Some(resizable) = self.resizable { - builder.resizable(resizable); - } - - if let Some(show_titlebar) = self.show_titlebar { - builder.show_titlebar(show_titlebar); - } - - if let Some(size) = self.size { - builder.set_size(size); - } else if let WindowSizePolicy::Content = self.size_policy { - builder.set_size(Size::new(0., 0.)); - } - - if let Some(position) = self.position { - builder.set_position(position); - } - - if let Some(transparent) = self.transparent { - builder.set_transparent(transparent); - } - - if let Some(level) = &self.level { - builder.set_level(level.clone()) - } - - if let Some(state) = self.state { - builder.set_window_state(state); - } - - if let Some(min_size) = self.min_size { - builder.set_min_size(min_size); - } - } - - /// Apply this window configuration to the given WindowHandle - pub fn apply_to_handle(&self, win_handle: &mut WindowHandle) { - if let Some(resizable) = self.resizable { - win_handle.resizable(resizable); - } - - if let Some(show_titlebar) = self.show_titlebar { - win_handle.show_titlebar(show_titlebar); - } - - if let Some(size) = self.size { - win_handle.set_size(size); - } - - // Can't apply min size currently as window handle - // does not support it. - - if let Some(position) = self.position { - win_handle.set_position(position); - } - - // TODO - set_level ? - // See https://github.com/linebender/druid/issues/1824 - - if let Some(state) = self.state { - win_handle.set_window_state(state); - } - } -} - -impl std::fmt::Debug for WindowConfig { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("WindowConfig") - .field("size_policy", &self.size_policy) - .field("size", &self.size) - .field("min_size", &self.min_size) - .field("position", &self.position) - .field("resizable", &self.resizable) - .field("transparent", &self.transparent) - .field("show_titlebar", &self.show_titlebar) - .field( - "level", - match &self.level { - Some(WindowLevel::AppWindow) => &"Some(AppWindow)", - Some(WindowLevel::Tooltip(_)) => &"Some(ToolTip)", - Some(WindowLevel::DropDown(_)) => &"Some(DropDown)", - Some(WindowLevel::Modal(_)) => &"Some(Modal)", - None => &"None", - }, - ) - .field("state", &self.state) - .finish() - } -} diff --git a/src/promise.rs b/src/promise.rs index 6780dcbe..fba0a906 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -6,6 +6,7 @@ use std::any::Any; use std::num::NonZeroU64; +use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] @@ -24,9 +25,9 @@ pub struct PromiseResult { impl PromiseTokenId { pub fn next() -> PromiseTokenId { - use druid_shell::Counter; - static WIDGET_ID_COUNTER: Counter = Counter::new(); - PromiseTokenId(WIDGET_ID_COUNTER.next_nonzero()) + static WIDGET_ID_COUNTER: AtomicU64 = AtomicU64::new(1); + let id = WIDGET_ID_COUNTER.fetch_add(1, Ordering::Relaxed); + PromiseTokenId(id.try_into().unwrap()) } pub fn to_raw(self) -> u64 { diff --git a/src/render_root.rs b/src/render_root.rs new file mode 100644 index 00000000..0b84d7e8 --- /dev/null +++ b/src/render_root.rs @@ -0,0 +1,507 @@ +use std::collections::VecDeque; + +// Automatically defaults to std::time::Instant on non Wasm platforms +use instant::Instant; +use kurbo::Affine; +use parley::FontContext; +use tracing::{info_span, warn}; +use vello::peniko::{Color, Fill}; +use vello::Scene; +use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; +use winit::window::CursorIcon; + +use crate::contexts::{EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx, WidgetCtx, WorkerFn}; +use crate::debug_logger::DebugLogger; +use crate::event::{PointerEvent, TextEvent, WindowEvent}; +use crate::kurbo::Point; +use crate::widget::{FocusChange, StoreInWidgetMut, WidgetMut, WidgetState}; +use crate::{ + Action, BoxConstraints, Handled, InternalLifeCycle, LifeCycle, Widget, WidgetId, WidgetPod, +}; + +// TODO - Remove pub(crate) +pub struct RenderRoot { + pub(crate) root: WidgetPod>, + pub(crate) size_policy: WindowSizePolicy, + pub(crate) size: PhysicalSize, + // TODO - Currently this is always 1.0 + // kurbo coordinates are assumed to be in logical pixels + pub(crate) scale_factor: f64, + /// Is `Some` if the most recently displayed frame was an animation frame. + pub(crate) last_anim: Option, + pub(crate) last_mouse_pos: Option>, + pub(crate) cursor_icon: CursorIcon, + pub(crate) state: RenderRootState, +} + +pub(crate) struct RenderRootState { + pub(crate) debug_logger: DebugLogger, + pub(crate) signal_queue: VecDeque, + pub(crate) focused_widget: Option, + pub(crate) font_context: FontContext, +} + +/// Defines how a windows size should be determined +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub enum WindowSizePolicy { + /// Use the content of the window to determine the size. + /// + /// If you use this option, your root widget will be passed infinite constraints; + /// you are responsible for ensuring that your content picks an appropriate size. + Content, + /// Use the provided window size. + #[default] + User, +} + +// TODO - Handle custom cursors? +// TODO - handling timers +// TODO - Text fields +pub enum RenderRootSignal { + Action(Action, WidgetId), + TextFieldAdded, + TextFieldRemoved, + TextFieldFocused, + ImeStarted, + ImeMoved, + ImeInvalidated, + RequestRedraw, + RequestAnimFrame, + SpawnWorker(WorkerFn), + TakeFocus, + SetCursor(CursorIcon), + SetSize(PhysicalSize), + SetTitle(String), +} + +impl RenderRoot { + pub fn new(root_widget: impl Widget, size_policy: WindowSizePolicy) -> Self { + let mut root = RenderRoot { + root: WidgetPod::new(root_widget).boxed(), + size_policy, + size: PhysicalSize::new(0, 0), + scale_factor: 1.0, + last_anim: None, + last_mouse_pos: None, + cursor_icon: CursorIcon::Default, + state: RenderRootState { + debug_logger: DebugLogger::new(false), + signal_queue: VecDeque::new(), + focused_widget: None, + font_context: FontContext::default(), + }, + }; + + // We send WidgetAdded to all widgets right away + root.root_lifecycle(LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded)); + + // We run a layout pass right away to have a SetSize signal ready + if size_policy == WindowSizePolicy::Content { + root.root_layout(); + } + + root + } + + pub fn handle_window_event(&mut self, event: WindowEvent) -> Handled { + match event { + WindowEvent::Rescale(_scale) => { + // TODO - Handle dpi scaling + // For now we're assuming the scale factor is always 1.0 + Handled::No + } + WindowEvent::Resize(size) => { + self.size = size; + self.root.state.needs_layout = true; + self.state + .signal_queue + .push_back(RenderRootSignal::RequestRedraw); + Handled::No + } + WindowEvent::AnimFrame => { + let now = Instant::now(); + // TODO: this calculation uses wall-clock time of the paint call, which + // potentially has jitter. + // + // See https://github.com/linebender/druid/issues/85 for discussion. + let last = self.last_anim.take(); + let elapsed_ns = last.map(|t| now.duration_since(t).as_nanos()).unwrap_or(0) as u64; + + if self.wants_animation_frame() { + self.root_lifecycle(LifeCycle::AnimFrame(elapsed_ns)); + self.last_anim = Some(now); + } + Handled::Yes + } + } + } + + pub fn handle_pointer_event(&mut self, event: PointerEvent) -> Handled { + self.root_on_pointer_event(event) + } + + pub fn handle_text_event(&mut self, event: TextEvent) -> Handled { + self.root_on_text_event(event) + } + + pub fn redraw(&mut self) -> Scene { + // TODO - Xilem's reconciliation logic will have to be called + // by the function that calls this + + // TODO - if root widget's request_anim is still set by the + // time this is called, emit a warning + if self.root.state().needs_layout { + self.root_layout(); + } + if self.root.state().needs_layout { + warn!("Widget requested layout during layout pass"); + self.state + .signal_queue + .push_back(RenderRootSignal::RequestRedraw); + } + + // TODO - Improve caching of scenes. + self.root_paint() + } + + pub fn pop_signal(&mut self) -> Option { + self.state.signal_queue.pop_front() + } + + pub fn pop_signal_matching( + &mut self, + predicate: impl Fn(&RenderRootSignal) -> bool, + ) -> Option { + let idx = self.state.signal_queue.iter().position(predicate)?; + self.state.signal_queue.remove(idx) + } + + pub fn cursor_icon(&self) -> CursorIcon { + self.cursor_icon + } + + pub fn edit_root_widget( + &mut self, + f: impl FnOnce(WidgetMut<'_, Box>) -> R, + ) -> R { + let mut fake_widget_state = + WidgetState::new(self.root.id(), Some(self.get_kurbo_size()), ""); + let root_widget = WidgetMut { + inner: Box::::from_widget_and_ctx( + &mut self.root.inner, + WidgetCtx { + global_state: &mut self.state, + widget_state: &mut self.root.state, + }, + ), + parent_widget_state: &mut fake_widget_state, + }; + + let res = f(root_widget); + self.post_event_processing(&mut fake_widget_state); + + res + } + + fn root_on_pointer_event(&mut self, event: PointerEvent) -> Handled { + let mut widget_state = + WidgetState::new(self.root.id(), Some(self.get_kurbo_size()), ""); + + let mut ctx = EventCtx { + global_state: &mut self.state, + widget_state: &mut widget_state, + is_handled: false, + request_pan_to_child: None, + }; + + // TODO - Only for primary pointer + self.last_mouse_pos = match event { + PointerEvent::PointerLeave(_) | PointerEvent::HoverFile(_, _) => None, + _ => Some(event.pointer_state().position), + }; + + let handled = { + ctx.global_state + .debug_logger + .push_important_span(&format!("¨POINTER_EVENT {}", event.short_name())); + let _span = info_span!("event").entered(); + self.root.on_pointer_event(&mut ctx, &event); + ctx.global_state.debug_logger.pop_span(); + Handled::from(ctx.is_handled) + }; + + if let Some(cursor) = &ctx.widget_state.cursor { + // TODO - Add methods and `into()` impl to make this more concise. + ctx.global_state + .signal_queue + .push_back(RenderRootSignal::SetCursor(*cursor)); + } else { + ctx.global_state + .signal_queue + .push_back(RenderRootSignal::SetCursor(CursorIcon::Default)); + } + + self.post_event_processing(&mut widget_state); + self.root.as_dyn().debug_validate(false); + + handled + } + + fn root_on_text_event(&mut self, event: TextEvent) -> Handled { + let mut widget_state = + WidgetState::new(self.root.id(), Some(self.get_kurbo_size()), ""); + + let mut ctx = EventCtx { + global_state: &mut self.state, + widget_state: &mut widget_state, + is_handled: false, + request_pan_to_child: None, + }; + + let handled = { + ctx.global_state + .debug_logger + .push_important_span(&format!("TEXT_EVENT {}", event.short_name())); + let _span = info_span!("event").entered(); + self.root.on_text_event(&mut ctx, &event); + ctx.global_state.debug_logger.pop_span(); + Handled::from(ctx.is_handled) + }; + + self.post_event_processing(&mut widget_state); + self.root.as_dyn().debug_validate(false); + + handled + } + + fn root_lifecycle(&mut self, event: LifeCycle) { + let mut widget_state = + WidgetState::new(self.root.id(), Some(self.get_kurbo_size()), ""); + let mut ctx = LifeCycleCtx { + global_state: &mut self.state, + widget_state: &mut widget_state, + }; + + { + ctx.global_state + .debug_logger + .push_important_span(&format!("LIFECYCLE {}", event.short_name())); + let _span = info_span!("lifecycle").entered(); + self.root.lifecycle(&mut ctx, &event); + self.state.debug_logger.pop_span(); + } + + // TODO - Remove this line + // post_event_processing can recursively call root_lifecycle, which + // makes the execution model more complex and unpredictable. + self.post_event_processing(&mut widget_state); + } + + pub(crate) fn root_layout(&mut self) { + let mut widget_state = + WidgetState::new(self.root.id(), Some(self.get_kurbo_size()), ""); + let size = self.get_kurbo_size(); + let mouse_pos = self.last_mouse_pos.map(|pos| (pos.x, pos.y).into()); + let mut layout_ctx = LayoutCtx { + global_state: &mut self.state, + widget_state: &mut widget_state, + mouse_pos, + }; + + let bc = match self.size_policy { + WindowSizePolicy::User => BoxConstraints::tight(size), + WindowSizePolicy::Content => BoxConstraints::UNBOUNDED, + }; + + let size = { + layout_ctx + .global_state + .debug_logger + .push_important_span("LAYOUT"); + let _span = info_span!("layout").entered(); + self.root.layout(&mut layout_ctx, &bc) + }; + layout_ctx.global_state.debug_logger.pop_span(); + + if let WindowSizePolicy::Content = self.size_policy { + let new_size = LogicalSize::new(size.width, size.height).to_physical(self.scale_factor); + if self.size != new_size { + self.size = new_size; + layout_ctx + .global_state + .signal_queue + .push_back(RenderRootSignal::SetSize(new_size)); + } + } + + layout_ctx.place_child(&mut self.root, Point::ORIGIN); + self.root_lifecycle(LifeCycle::Internal(InternalLifeCycle::ParentWindowOrigin)); + self.post_event_processing(&mut widget_state); + } + + fn root_paint(&mut self) -> Scene { + // TODO - Handle Xilem's VIEW_CONTEXT_CHANGED + + let widget_state = WidgetState::new(self.root.id(), Some(self.get_kurbo_size()), ""); + let mut ctx = PaintCtx { + global_state: &mut self.state, + widget_state: &widget_state, + depth: 0, + debug_paint: false, + debug_widget: false, + }; + + let mut scene = Scene::new(); + self.root.paint(&mut ctx, &mut scene); + + // FIXME - This is a workaround to Vello panicking when given an + // empty scene + // See https://github.com/linebender/vello/issues/291 + let empty_path = kurbo::Rect::ZERO; + scene.fill( + Fill::NonZero, + Affine::IDENTITY, + Color::TRANSPARENT, + None, + &empty_path, + ); + + scene + } + + fn get_kurbo_size(&self) -> kurbo::Size { + let size = self.size.to_logical(self.scale_factor); + kurbo::Size::new(size.width, size.height) + } + + fn post_event_processing(&mut self, widget_state: &mut WidgetState) { + // If children are changed during the handling of an event, + // we need to send RouteWidgetAdded now, so that they are ready for update/layout. + if widget_state.children_changed { + // TODO - Update IME handlers + // Send TextFieldRemoved signal + + self.root_lifecycle(LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded)); + } + + if self.state.debug_logger.layout_tree.root.is_none() { + self.state.debug_logger.layout_tree.root = Some(self.root.id().to_raw() as u32); + } + + if self.root.state().needs_window_origin && !self.root.state().needs_layout { + let event = LifeCycle::Internal(InternalLifeCycle::ParentWindowOrigin); + self.root_lifecycle(event); + } + + // Update the disabled state if necessary + // Always do this before updating the focus-chain + if self.root.state().tree_disabled_changed() { + let event = LifeCycle::Internal(InternalLifeCycle::RouteDisabledChanged); + self.root_lifecycle(event); + } + + // Update the focus-chain if necessary + // Always do this before sending focus change, since this event updates the focus chain. + if self.root.state().update_focus_chain { + let event = LifeCycle::BuildFocusChain; + self.root_lifecycle(event); + } + + self.update_focus(widget_state); + + // If we need a new paint pass, make sure winit knows it. + if self.wants_animation_frame() { + self.state + .signal_queue + .push_back(RenderRootSignal::RequestAnimFrame); + } + + if self.root.state().needs_paint { + self.state + .signal_queue + .push_back(RenderRootSignal::RequestRedraw); + } + + #[cfg(FALSE)] + for ime_field in widget_state.text_registrations.drain(..) { + let token = self.handle.add_text_field(); + tracing::debug!("{:?} added", token); + self.ime_handlers.push((token, ime_field)); + } + } + + /// `true` iff any child requested an animation frame since the last `AnimFrame` event. + fn wants_animation_frame(&self) -> bool { + self.root.state().request_anim + } + + fn update_focus(&mut self, widget_state: &mut WidgetState) { + if let Some(focus_req) = widget_state.request_focus.take() { + let old = self.state.focused_widget; + let new = self.widget_for_focus_request(focus_req); + + // TODO + // Skip change if requested widget is disabled + + // Only send RouteFocusChanged in case there's actual change + if old != new { + let event = LifeCycle::Internal(InternalLifeCycle::RouteFocusChanged { old, new }); + self.root_lifecycle(event); + self.state.focused_widget = new; + + // TODO - Handle IME + // Send TextFieldFocused(focused_widget) signal + } + } + } + + fn widget_for_focus_request(&self, focus: FocusChange) -> Option { + match focus { + FocusChange::Resign => None, + FocusChange::Focus(id) => Some(id), + FocusChange::Next => self.widget_from_focus_chain(true), + FocusChange::Previous => self.widget_from_focus_chain(false), + } + } + + fn widget_from_focus_chain(&self, forward: bool) -> Option { + self.state.focused_widget.and_then(|focus| { + self.focus_chain() + .iter() + // Find where the focused widget is in the focus chain + .position(|id| id == &focus) + .map(|idx| { + // Return the id that's next to it in the focus chain + let len = self.focus_chain().len(); + let new_idx = if forward { + (idx + 1) % len + } else { + (idx + len - 1) % len + }; + self.focus_chain()[new_idx] + }) + .or_else(|| { + // If the currently focused widget isn't in the focus chain, + // then we'll just return the first/last entry of the chain, if any. + if forward { + self.focus_chain().first().copied() + } else { + self.focus_chain().last().copied() + } + }) + }) + } + + // TODO - Store in RenderRootState + pub(crate) fn focus_chain(&self) -> &[WidgetId] { + &self.root.state().focus_chain + } +} + +/* +TODO: +- Invalidation regions +- Timer handling +- prepare_paint +- Focus-related stuff +*/ diff --git a/src/testing/harness.rs b/src/testing/harness.rs index f3eb008b..bfa90788 100644 --- a/src/testing/harness.rs +++ b/src/testing/harness.rs @@ -4,31 +4,35 @@ //! Tools and infrastructure for testing widgets. -use std::collections::{HashMap, VecDeque}; -use std::sync::Arc; +use std::num::NonZeroUsize; -use druid_shell::{KeyEvent, Modifiers, MouseButton, MouseButtons}; -pub use druid_shell::{RawMods, Region}; use image::io::Reader as ImageReader; -use instant::Duration; -use shell::text::Selection; - -use super::screenshots::{get_image_diff, get_rgba_image}; +use image::RgbaImage; +use vello::util::RenderContext; +use vello::{block_on_wgpu, RendererOptions}; +use wgpu::{ + BufferDescriptor, BufferUsages, CommandEncoderDescriptor, Extent3d, ImageCopyBuffer, + TextureDescriptor, TextureFormat, TextureUsages, +}; +use winit::dpi::{PhysicalPosition, PhysicalSize}; +use winit::event::{Ime, MouseButton}; + +use super::screenshots::get_image_diff; use super::snapshot_utils::get_cargo_workspace; -use super::MockTimerQueue; -use crate::action::{Action, ActionQueue}; -//use crate::ext_event::ExtEventHost; -use crate::command::CommandQueue; -use crate::contexts::GlobalPassCtx; -use crate::debug_logger::DebugLogger; -use crate::ext_event::ExtEventQueue; -use crate::piet::{BitmapTarget, Device, ImageFormat, Piet}; -use crate::widget::{StoreInWidgetMut, WidgetMut, WidgetRef}; -use crate::*; - -/// Default screen size for tests. +use crate::action::Action; +use crate::event::{PointerEvent, PointerState, TextEvent, WindowEvent}; +use crate::render_root::{RenderRoot, RenderRootSignal, WindowSizePolicy}; +use crate::widget::{WidgetMut, WidgetRef}; +use crate::{Color, Handled, Point, Size, Vec2, Widget, WidgetId}; + +// TODO - Get shorter names +// TODO - Make them associated consts +/// Default canvas size for tests. pub const HARNESS_DEFAULT_SIZE: Size = Size::new(400., 400.); +/// Default background color for tests. +pub const HARNESS_DEFAULT_BACKGROUND_COLOR: Color = Color::rgb8(0x29, 0x29, 0x29); + /// A safe headless environment to test widgets in. /// /// `TestHarness` is a type that simulates an [`AppRoot`](crate::AppRoot) @@ -113,11 +117,11 @@ pub const HARNESS_DEFAULT_SIZE: Size = Size::new(400., 400.); /// /// # simple_button(); /// ``` -// TODO - Fix examples pub struct TestHarness { - mock_app: MockAppRoot, - mouse_state: MouseEvent, - window_size: Size, + render_root: RenderRoot, + mouse_state: PointerState, + window_size: PhysicalSize, + background_color: Color, } /// Assert a snapshot of a rendered frame of your app. @@ -142,73 +146,56 @@ macro_rules! assert_render_snapshot { }; } -// TODO - merge -/// All of the state except for the `Piet` (render context). We need to pass -/// that in to get around some lifetime issues. -struct MockAppRoot { - window: WindowRoot, - command_queue: CommandQueue, - action_queue: ActionQueue, - debug_logger: DebugLogger, -} - impl TestHarness { /// Builds harness with given root widget. /// /// Window size will be [`HARNESS_DEFAULT_SIZE`]. - pub fn create(root: impl Widget) -> Self { - Self::create_with_size(root, HARNESS_DEFAULT_SIZE) + /// Background color will be [`HARNESS_DEFAULT_BACKGROUND_COLOR`]. + pub fn create(root_widget: impl Widget) -> Self { + Self::create_with( + root_widget, + HARNESS_DEFAULT_SIZE, + HARNESS_DEFAULT_BACKGROUND_COLOR, + ) } + // TODO - Remove /// Builds harness with given root widget and window size. - pub fn create_with_size(root: impl Widget, window_size: Size) -> Self { - //let ext_host = ExtEventHost::default(); - //let ext_handle = ext_host.make_sink(); - - // FIXME - let event_queue = ExtEventQueue::new(); - - let window = WindowRoot::new( - WindowId::next(), - Default::default(), - event_queue.make_sink(), - Box::new(root), - "Masonry test app".into(), - false, - WindowSizePolicy::User, - Some(MockTimerQueue::new()), - ); + pub fn create_with_size(root_widget: impl Widget, window_size: Size) -> Self { + Self::create_with(root_widget, window_size, HARNESS_DEFAULT_BACKGROUND_COLOR) + } - let mouse_state = MouseEvent { - pos: Point::ZERO, - window_pos: Point::ZERO, - buttons: MouseButtons::default(), - mods: Modifiers::default(), - count: 0, - focus: false, - button: MouseButton::None, - wheel_delta: Vec2::ZERO, - }; + /// Builds harness with given root widget, canvas size and background color. + pub fn create_with( + root_widget: impl Widget, + window_size: Size, + background_color: Color, + ) -> Self { + let mouse_state = PointerState::empty(); + let window_size = PhysicalSize::new(window_size.width as _, window_size.height as _); let mut harness = TestHarness { - mock_app: MockAppRoot { - window, - command_queue: VecDeque::new(), - action_queue: VecDeque::new(), - debug_logger: DebugLogger::new(false), - }, + render_root: RenderRoot::new(root_widget, WindowSizePolicy::User), mouse_state, window_size, + background_color, }; + harness.process_window_event(WindowEvent::Resize(window_size)); - // verify that all widgets are marked as having children_changed - // (this should always be true for a new widget) - harness.inspect_widgets(|widget| assert!(widget.state().children_changed)); + harness + } - harness.process_event(Event::WindowConnected); - harness.process_event(Event::WindowSize(window_size)); + // FIXME - The docs for these three functions are copy-pasted. Rewrite them. - harness + /// Send an event to the widget. + /// + /// If this event triggers lifecycle events, they will also be dispatched, + /// as will any resulting commands. Commands created as a result of this event + /// will also be dispatched. + pub fn process_window_event(&mut self, event: WindowEvent) -> Handled { + let handled = self.render_root.handle_window_event(event); + self.process_state_after_event(); + handled } /// Send an event to the widget. @@ -216,108 +203,158 @@ impl TestHarness { /// If this event triggers lifecycle events, they will also be dispatched, /// as will any resulting commands. Commands created as a result of this event /// will also be dispatched. - pub fn process_event(&mut self, event: Event) { - self.mock_app.event(event); + pub fn process_pointer_event(&mut self, event: PointerEvent) -> Handled { + let handled = self.render_root.handle_pointer_event(event); + self.process_state_after_event(); + handled + } + /// Send an event to the widget. + /// + /// If this event triggers lifecycle events, they will also be dispatched, + /// as will any resulting commands. Commands created as a result of this event + /// will also be dispatched. + pub fn process_text_event(&mut self, event: TextEvent) -> Handled { + let handled = self.render_root.handle_text_event(event); self.process_state_after_event(); + handled } fn process_state_after_event(&mut self) { - loop { - let cmd = self.mock_app.command_queue.pop_front(); - match cmd { - Some(cmd) => self - .mock_app - .event(Event::Internal(InternalEvent::TargetedCommand(cmd))), - None => break, - }; - } - - // TODO - this might be too coarse if self.root_widget().state().needs_layout { - self.mock_app.layout(); - *self.window_mut().invalid_mut() = Region::from(self.window_size.to_rect()); + self.render_root.root_layout(); } } - fn render_to(&mut self, render_target: &mut BitmapTarget) { - /// A way to clean up resources when our render context goes out of - /// scope, even during a panic. - pub struct RenderContextGuard<'a>(Piet<'a>); - - impl Drop for RenderContextGuard<'_> { - fn drop(&mut self) { - // We need to call finish even if a test assert failed - if let Err(err) = self.0.finish() { - // We can't panic, because we might already be panicking - tracing::error!("piet finish failed: {}", err); - } - } - } + // TODO - We add way too many dependencies in this code + // TODO - Should be async? + /// Create a bitmap (an array of pixels), paint the window and return the bitmap as an 8-bits-per-channel RGB image. + pub fn render(&mut self) -> RgbaImage { + let mut context = + RenderContext::new().expect("Got non-Send/Sync error from creating render context"); + let device_id = + pollster::block_on(context.device(None)).expect("No compatible device found"); + let device_handle = &mut context.devices[device_id]; + let device = &device_handle.device; + let queue = &device_handle.queue; + let mut renderer = vello::Renderer::new( + device, + RendererOptions { + surface_format: None, + // TODO - Examine this value + use_cpu: true, + num_init_threads: NonZeroUsize::new(1), + // TODO - Examine this value + antialiasing_support: vello::AaSupport::area_only(), + }, + ) + .expect("Got non-Send/Sync error from creating renderer"); + + let scene = self.render_root.redraw(); + + // TODO - fix window_size + let (width, height) = (self.window_size.width, self.window_size.height); + let render_params = vello::RenderParams { + // TODO - Parameterize + base_color: self.background_color, + width, + height, + antialiasing_method: vello::AaConfig::Area, + }; - let mut piet = RenderContextGuard(render_target.render_context()); + let size = Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + let target = device.create_texture(&TextureDescriptor { + label: Some("Target texture"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::STORAGE_BINDING | TextureUsages::COPY_SRC, + view_formats: &[], + }); + let view = target.create_view(&wgpu::TextureViewDescriptor::default()); + renderer + .render_to_texture(device, queue, &scene, &view, &render_params) + .expect("Got non-Send/Sync error from rendering"); + let padded_byte_width = (width * 4).next_multiple_of(256); + let buffer_size = padded_byte_width as u64 * height as u64; + let buffer = device.create_buffer(&BufferDescriptor { + label: Some("val"), + size: buffer_size, + usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor { + label: Some("Copy out buffer"), + }); + encoder.copy_texture_to_buffer( + target.as_image_copy(), + ImageCopyBuffer { + buffer: &buffer, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(padded_byte_width), + rows_per_image: None, + }, + }, + size, + ); - // FIXME - this doesn't make sense given we might render to a fresh surface - let invalid = std::mem::replace(self.window_mut().invalid_mut(), Region::EMPTY); - self.mock_app.paint_region(&mut piet.0, &invalid); - } + queue.submit([encoder.finish()]); + let buf_slice = buffer.slice(..); - /// Create a Piet bitmap render context (an array of pixels), paint the - /// window and return the bitmap. - pub fn render(&mut self) -> Arc<[u8]> { - let mut device = Device::new().expect("harness failed to get device"); - let mut render_target = device - .bitmap_target( - self.window_size.width as usize, - self.window_size.height as usize, - 1.0, - ) - .expect("failed to create bitmap_target"); - - self.render_to(&mut render_target); - - render_target - .to_image_buf(ImageFormat::RgbaPremul) - .unwrap() - .raw_pixels_shared() + let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel(); + buf_slice.map_async(wgpu::MapMode::Read, move |v| sender.send(v).unwrap()); + let recv_result = block_on_wgpu(device, receiver.receive()).expect("channel was closed"); + recv_result.expect("failed to map buffer"); + + let data = buf_slice.get_mapped_range(); + let mut result_unpadded = + Vec::::with_capacity((width * height * 4).try_into().unwrap()); + for row in 0..height { + let start = (row * padded_byte_width).try_into().unwrap(); + result_unpadded.extend(&data[start..start + (width * 4) as usize]); + } + + RgbaImage::from_vec(width, height, result_unpadded).expect("failed to create image") } // --- Event helpers --- /// Move an internal mouse state, and send a MouseMove event to the window. pub fn mouse_move(&mut self, pos: impl Into) { + // FIXME - Account for scaling let pos = pos.into(); - // FIXME - not actually the same - self.mouse_state.pos = pos; - self.mouse_state.window_pos = pos; - self.mouse_state.button = MouseButton::None; + let pos = PhysicalPosition::new(pos.x, pos.y); + self.mouse_state.position = dbg!(pos); - self.process_event(Event::MouseMove(self.mouse_state.clone())); + self.process_pointer_event(PointerEvent::PointerMove(self.mouse_state.clone())); } /// Send a MouseDown event to the window. pub fn mouse_button_press(&mut self, button: MouseButton) { self.mouse_state.buttons.insert(button); - self.mouse_state.button = button; - - self.process_event(Event::MouseDown(self.mouse_state.clone())); + self.process_pointer_event(PointerEvent::PointerDown(button, self.mouse_state.clone())); } /// Send a MouseUp event to the window. pub fn mouse_button_release(&mut self, button: MouseButton) { - self.mouse_state.buttons.remove(button); - self.mouse_state.button = button; - - self.process_event(Event::MouseUp(self.mouse_state.clone())); + self.mouse_state.buttons.remove(&button); + self.process_pointer_event(PointerEvent::PointerUp(button, self.mouse_state.clone())); } /// Send a Wheel event to the window pub fn mouse_wheel(&mut self, wheel_delta: Vec2) { - self.mouse_state.button = MouseButton::None; - self.mouse_state.wheel_delta = wheel_delta; - - self.process_event(Event::Wheel(self.mouse_state.clone())); - self.mouse_state.wheel_delta = Vec2::ZERO; + let pixel_delta = PhysicalPosition::new(wheel_delta.x, wheel_delta.y); + self.process_pointer_event(PointerEvent::MouseWheel( + pixel_delta, + self.mouse_state.clone(), + )); } /// Send events that lead to a given widget being clicked. @@ -343,51 +380,17 @@ impl TestHarness { } // TODO - Handle complicated IME - - /// Simulate typing the given text. - /// - /// For every character in the input string (more specifically, - /// for every Unicode Scalar Value), this sends a KeyDown and a - /// KeyUp event to the window. - /// - /// Obviously this works better with ASCII text. - /// - /// **(Note: IME mocking is a future feature)** + // TODO - Mock Winit keyboard events pub fn keyboard_type_chars(&mut self, text: &str) { // For each character for c in text.split("").filter(|s| !s.is_empty()) { - let event = KeyEvent::for_test(RawMods::None, c); - - if self.mock_app.event(Event::KeyDown(event.clone())) == Handled::No { - if let Some(mut input_handler) = self.mock_app.window.get_focused_ime_handler(true) - { - // This is copy-pasted from druid-shell's simulate_input function - let selection = input_handler.selection(); - input_handler.replace_range(selection.range(), c); - let new_caret_index = selection.min() + c.len(); - input_handler.set_selection(Selection::caret(new_caret_index)); - - let modified_widget = self.mock_app.window.release_focused_ime_handler(); - - if let Some(widget_id) = modified_widget { - let event = Event::Internal(InternalEvent::RouteImeStateChange(widget_id)); - self.mock_app.event(event); - } - } - } - self.mock_app.event(Event::KeyUp(event.clone())); + let event = TextEvent::Ime(Ime::Commit(c.to_string())); + self.render_root.handle_text_event(event); } self.process_state_after_event(); } - #[doc(alias = "send_command")] - /// Send a command to a target. - pub fn submit_command(&mut self, command: impl Into) { - let command = command.into().default_to(self.mock_app.window.id.into()); - let event = Event::Internal(InternalEvent::TargetedCommand(command)); - self.process_event(event); - } - + #[cfg(FALSE)] /// Simulate the passage of time. /// /// If you create any timer in a widget, this method is the only way to trigger @@ -411,19 +414,9 @@ impl TestHarness { // --- Getters --- - /// Return the mocked window. - pub fn window(&self) -> &WindowRoot { - &self.mock_app.window - } - - /// Return the mocked window. - pub fn window_mut(&mut self) -> &mut WindowRoot { - &mut self.mock_app.window - } - /// Return the root widget. pub fn root_widget(&self) -> WidgetRef<'_, dyn Widget> { - self.mock_app.window.root.as_dyn() + self.render_root.root.as_dyn() } /// Return the widget with the given id. @@ -432,21 +425,21 @@ impl TestHarness { /// /// Panics if no Widget with this id can be found. pub fn get_widget(&self, id: WidgetId) -> WidgetRef<'_, dyn Widget> { - self.mock_app - .window + self.root_widget() .find_widget_by_id(id) .expect("could not find widget") } /// Try to return the widget with the given id. pub fn try_get_widget(&self, id: WidgetId) -> Option> { - self.mock_app.window.find_widget_by_id(id) + self.root_widget().find_widget_by_id(id) } // TODO - link to focus documentation. /// Return the widget that receives keyboard events. pub fn focused_widget(&self) -> Option> { - self.mock_app.window.focused_widget() + self.root_widget() + .find_widget_by_id(self.render_root.state.focused_widget?) } /// Call the provided visitor on every widget in the widget tree. @@ -461,7 +454,7 @@ impl TestHarness { } } - inspect(self.mock_app.window.root.as_dyn(), &f); + inspect(self.root_widget(), &f); } /// Get a [`WidgetMut`] to the root widget. @@ -469,54 +462,10 @@ impl TestHarness { /// Because of how WidgetMut works, it can only be passed to a user-provided callback. pub fn edit_root_widget( &mut self, - f: impl FnOnce(WidgetMut<'_, '_, Box>) -> R, + f: impl FnOnce(WidgetMut<'_, Box>) -> R, ) -> R { - // TODO - Move to MockAppRoot? - let window = &mut self.mock_app.window; - let mut fake_widget_state; - let mut timers = HashMap::new(); - let res = { - let mut global_state = GlobalPassCtx::new( - window.ext_event_sink.clone(), - &mut self.mock_app.debug_logger, - &mut self.mock_app.command_queue, - &mut self.mock_app.action_queue, - &mut timers, - window.mock_timer_queue.as_mut(), - &window.handle, - window.id, - window.focus, - ); - fake_widget_state = window.root.state.clone(); - - let root_widget = WidgetMut { - inner: Box::::from_widget_and_ctx( - &mut window.root.inner, - WidgetCtx { - global_state: &mut global_state, - widget_state: &mut window.root.state, - }, - ), - parent_widget_state: &mut fake_widget_state, - }; - - f(root_widget) - }; - - // Timer creation should use mock_timer_queue instead - assert!(timers.is_empty()); - - // TODO - handle cursor and validation - - window.post_event_processing( - &mut fake_widget_state, - &mut self.mock_app.debug_logger, - &mut self.mock_app.command_queue, - &mut self.mock_app.action_queue, - false, - ); + let res = self.render_root.edit_root_widget(f); self.process_state_after_event(); - res } @@ -524,8 +473,14 @@ impl TestHarness { /// /// Note: Actions are still a WIP feature. pub fn pop_action(&mut self) -> Option<(Action, WidgetId)> { - let (action, widget_id, _) = self.mock_app.action_queue.pop_front()?; - Some((action, widget_id)) + let signal = self + .render_root + .pop_signal_matching(|signal| matches!(signal, RenderRootSignal::Action(..))); + match signal { + Some(RenderRootSignal::Action(action, id)) => Some((action, id)), + Some(_) => unreachable!(), + _ => None, + } } // --- Screenshots --- @@ -551,21 +506,14 @@ impl TestHarness { // We need a way to skip render snapshots on CI and locally // until we can make sure the snapshots render the same on // different platforms. - return; - } - let mut device = Device::new().expect("harness failed to get device"); - let mut render_target = device - .bitmap_target( - self.window_size.width as usize, - self.window_size.height as usize, - 1.0, - ) - .expect("failed to create bitmap_target"); + // We still redraw to get some coverage in the paint code. + let _ = self.render_root.redraw(); - self.render_to(&mut render_target); + return; + } - let new_image = get_rgba_image(&mut render_target, self.window_size); + let new_image = self.render(); let workspace_path = get_cargo_workspace(manifest_dir); let test_file_path_abs = workspace_path.join(test_file_path); @@ -601,58 +549,9 @@ impl TestHarness { // --- Debug logger --- - // TODO - remove, see ROADMAP.md - #[allow(missing_docs)] - pub fn push_log(&mut self, message: &str) { - self.mock_app - .debug_logger - .update_widget_state(self.mock_app.window.root.as_dyn()); - self.mock_app.debug_logger.push_log(false, message); - } - // ex: harness.write_debug_logs("test_log.json"); #[allow(missing_docs)] pub fn write_debug_logs(&mut self, path: &str) { - self.mock_app.debug_logger.write_to_file(path); - } -} - -#[allow(dead_code)] -impl MockAppRoot { - fn event(&mut self, event: Event) -> Handled { - self.window.event( - event, - &mut self.debug_logger, - &mut self.command_queue, - &mut self.action_queue, - ) - } - - fn lifecycle(&mut self, event: LifeCycle) { - self.window.lifecycle( - &event, - &mut self.debug_logger, - &mut self.command_queue, - &mut self.action_queue, - false, - ); - } - - fn layout(&mut self) { - self.window.layout( - &mut self.debug_logger, - &mut self.command_queue, - &mut self.action_queue, - ); - } - - fn paint_region(&mut self, piet: &mut Piet, invalid: &Region) { - self.window.do_paint( - piet, - invalid, - &mut self.debug_logger, - &mut self.command_queue, - &mut self.action_queue, - ); + self.render_root.state.debug_logger.write_to_file(path); } } diff --git a/src/testing/helper_widgets.rs b/src/testing/helper_widgets.rs index d59738a8..74aa29d6 100644 --- a/src/testing/helper_widgets.rs +++ b/src/testing/helper_widgets.rs @@ -10,23 +10,28 @@ //! Masonry, not to be user-facing. #![allow(missing_docs)] +#![allow(unused)] use std::cell::RefCell; use std::collections::VecDeque; use std::rc::Rc; use smallvec::SmallVec; +use vello::Scene; +use crate::event::{PointerEvent, TextEvent}; use crate::widget::{SizedBox, WidgetRef}; use crate::*; -pub type EventFn = dyn FnMut(&mut S, &mut EventCtx, &Event); +pub type PointerEventFn = dyn FnMut(&mut S, &mut EventCtx, &PointerEvent); +pub type TextEventFn = dyn FnMut(&mut S, &mut EventCtx, &TextEvent); pub type StatusChangeFn = dyn FnMut(&mut S, &mut LifeCycleCtx, &StatusChange); pub type LifeCycleFn = dyn FnMut(&mut S, &mut LifeCycleCtx, &LifeCycle); pub type LayoutFn = dyn FnMut(&mut S, &mut LayoutCtx, &BoxConstraints) -> Size; -pub type PaintFn = dyn FnMut(&mut S, &mut PaintCtx); +pub type PaintFn = dyn FnMut(&mut S, &mut PaintCtx, &mut Scene); pub type ChildrenFn = dyn Fn(&S) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]>; +#[cfg(FALSE)] pub const REPLACE_CHILD: Selector = Selector::new("masonry-test.replace-child"); /// A widget that can be constructed from individual functions, builder-style. @@ -34,7 +39,8 @@ pub const REPLACE_CHILD: Selector = Selector::new("masonry-test.replace-child"); /// This widget is generic over its state, which is passed in at construction time. pub struct ModularWidget { state: S, - on_event: Option>>, + on_pointer_event: Option>>, + on_text_event: Option>>, on_status_change: Option>>, lifecycle: Option>>, layout: Option>>, @@ -61,7 +67,7 @@ pub struct ReplaceChild { /// let widget = Label::new("Hello").record(&recording); /// /// TestHarness::create(widget); -/// assert!(matches!(recording.next(), Record::L(LifeCycle::WidgetAdded))); +/// assert!(matches!(recording.next().unwrap(), Record::L(LifeCycle::WidgetAdded))); /// ``` pub struct Recorder { recording: Recording, @@ -77,17 +83,12 @@ pub struct Recording(Rc>>); /// Each member of the enum corresponds to one of the methods on `Widget`. #[derive(Debug, Clone)] pub enum Record { - /// An `Event`. - E(Event), + PE(PointerEvent), + TE(TextEvent), SC(StatusChange), - /// A `LifeCycle` event. L(LifeCycle), Layout(Size), Paint, - // instead of always returning an Option, we have a none variant; - // this would be code smell elsewhere but here I think it makes the tests - // easier to read. - None, } /// like WidgetExt but just for this one thing @@ -110,7 +111,8 @@ impl ModularWidget { pub fn new(state: S) -> Self { ModularWidget { state, - on_event: None, + on_pointer_event: None, + on_text_event: None, on_status_change: None, lifecycle: None, layout: None, @@ -119,8 +121,19 @@ impl ModularWidget { } } - pub fn event_fn(mut self, f: impl FnMut(&mut S, &mut EventCtx, &Event) + 'static) -> Self { - self.on_event = Some(Box::new(f)); + pub fn pointer_event_fn( + mut self, + f: impl FnMut(&mut S, &mut EventCtx, &PointerEvent) + 'static, + ) -> Self { + self.on_pointer_event = Some(Box::new(f)); + self + } + + pub fn text_event_fn( + mut self, + f: impl FnMut(&mut S, &mut EventCtx, &TextEvent) + 'static, + ) -> Self { + self.on_text_event = Some(Box::new(f)); self } @@ -148,7 +161,7 @@ impl ModularWidget { self } - pub fn paint_fn(mut self, f: impl FnMut(&mut S, &mut PaintCtx) + 'static) -> Self { + pub fn paint_fn(mut self, f: impl FnMut(&mut S, &mut PaintCtx, &mut Scene) + 'static) -> Self { self.paint = Some(Box::new(f)); self } @@ -163,8 +176,14 @@ impl ModularWidget { } impl Widget for ModularWidget { - fn on_event(&mut self, ctx: &mut EventCtx, event: &Event) { - if let Some(f) = self.on_event.as_mut() { + fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &event::PointerEvent) { + if let Some(f) = self.on_pointer_event.as_mut() { + f(&mut self.state, ctx, event) + } + } + + fn on_text_event(&mut self, ctx: &mut EventCtx, event: &event::TextEvent) { + if let Some(f) = self.on_text_event.as_mut() { f(&mut self.state, ctx, event) } } @@ -193,9 +212,9 @@ impl Widget for ModularWidget { .unwrap_or_else(|| Size::new(100., 100.)) } - fn paint(&mut self, ctx: &mut PaintCtx) { + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { if let Some(f) = self.paint.as_mut() { - f(&mut self.state, ctx) + f(&mut self.state, ctx, scene) } } @@ -217,7 +236,9 @@ impl ReplaceChild { } impl Widget for ReplaceChild { + #[cfg(FALSE)] fn on_event(&mut self, ctx: &mut EventCtx, event: &Event) { + #[cfg(FALSE)] if let Event::Command(cmd) = event { if cmd.is(REPLACE_CHILD) { self.child = (self.replacer)(); @@ -228,6 +249,14 @@ impl Widget for ReplaceChild { self.child.on_event(ctx, event) } + fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &event::PointerEvent) { + todo!() + } + + fn on_text_event(&mut self, ctx: &mut EventCtx, event: &event::TextEvent) { + todo!() + } + fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, _event: &StatusChange) { ctx.request_layout(); } @@ -240,8 +269,8 @@ impl Widget for ReplaceChild { self.child.layout(ctx, bc) } - fn paint(&mut self, ctx: &mut PaintCtx) { - self.child.paint_raw(ctx) + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + self.child.paint(ctx, scene) } fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { @@ -266,8 +295,8 @@ impl Recording { /// Returns the next event in the recording, if one exists. /// /// This consumes the event. - pub fn next(&self) -> Record { - self.0.borrow_mut().pop_front().unwrap_or(Record::None) + pub fn next(&self) -> Option { + self.0.borrow_mut().pop_front() } /// Returns a vec of events drained from the recording. @@ -281,9 +310,14 @@ impl Recording { } impl Widget for Recorder { - fn on_event(&mut self, ctx: &mut EventCtx, event: &Event) { - self.recording.push(Record::E(event.clone())); - self.child.on_event(ctx, event) + fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &event::PointerEvent) { + self.recording.push(Record::PE(event.clone())); + self.child.on_pointer_event(ctx, event) + } + + fn on_text_event(&mut self, ctx: &mut EventCtx, event: &event::TextEvent) { + self.recording.push(Record::TE(event.clone())); + self.child.on_text_event(ctx, event) } fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { @@ -302,8 +336,8 @@ impl Widget for Recorder { size } - fn paint(&mut self, ctx: &mut PaintCtx) { - self.child.paint(ctx); + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + self.child.paint(ctx, scene); self.recording.push(Record::Paint) } diff --git a/src/testing/mock_timer_queue.rs b/src/testing/mock_timer_queue.rs deleted file mode 100644 index 5bb3def1..00000000 --- a/src/testing/mock_timer_queue.rs +++ /dev/null @@ -1,61 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -use std::collections::VecDeque; - -use druid_shell::TimerToken; -use instant::Duration; - -/// Handles timers for unit tests. -/// -/// In normal app execution, timers are submitted to the platform handle, which immediately -/// returns a token. The token is stored in a HashMap with a WidgetId and, when the timer -/// fires, the platform passes us the token again so we can plumb the event to the right -/// widget. -/// -/// In unit tests, we can't submit timers to the platform. Instead, we store a list of -/// timer tokens and durations, and when the user calls [`TestHarness::move_timers_forward`], -/// the timers are "manually" mutated and checked, and the matching events fired. -/// -/// To avoid polluting the code with `#[cfg(test)]` annotations, MockTimerQueue is also -/// present in non-test code, but it's always empty. -pub(crate) struct MockTimerQueue { - pub current_time: Duration, - pub queue: VecDeque<(Duration, TimerToken)>, -} - -impl MockTimerQueue { - pub(crate) fn new() -> Self { - MockTimerQueue { - current_time: Duration::ZERO, - queue: VecDeque::new(), - } - } - - #[must_use] - pub(crate) fn add_timer(&mut self, duration: Duration) -> TimerToken { - let deadline = self.current_time + duration; - let token = TimerToken::next(); - let idx = self - .queue - .binary_search_by_key(&deadline, |(d, _t)| *d) - .unwrap_or_else(|x| x); - self.queue.insert(idx, (deadline, token)); - - token - } - - #[must_use] - pub(crate) fn move_forward(&mut self, duration: Duration) -> Vec { - self.current_time += duration; - let idx = self - .queue - .partition_point(|(deadline, _token)| *deadline <= self.current_time); - - self.queue - .drain(0..idx) - .map(|(_deadline, token)| token) - .collect() - } -} diff --git a/src/testing/mod.rs b/src/testing/mod.rs index 286d5d00..4c76658d 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -11,51 +11,14 @@ mod harness; #[cfg(not(tarpaulin_include))] mod helper_widgets; #[cfg(not(tarpaulin_include))] -mod mock_timer_queue; -#[cfg(not(tarpaulin_include))] mod screenshots; #[cfg(not(tarpaulin_include))] mod snapshot_utils; -use druid_shell::{Modifiers, MouseButton, MouseButtons}; pub use harness::{TestHarness, HARNESS_DEFAULT_SIZE}; -pub use helper_widgets::{ - ModularWidget, Record, Recorder, Recording, ReplaceChild, TestWidgetExt, REPLACE_CHILD, -}; -pub(crate) use mock_timer_queue::MockTimerQueue; - -use crate::kurbo::{Point, Vec2}; -use crate::{MouseEvent, WidgetId}; +pub use helper_widgets::{ModularWidget, Record, Recorder, Recording, ReplaceChild, TestWidgetExt}; -/// Helper function to construct a "move to this position" mouse event. -pub fn mouse_move(p: impl Into) -> MouseEvent { - let pos = p.into(); - MouseEvent { - pos, - window_pos: pos, - buttons: MouseButtons::default(), - mods: Modifiers::default(), - count: 0, - focus: false, - button: MouseButton::None, - wheel_delta: Vec2::ZERO, - } -} - -/// Helper function to construct a "scroll by n ticks" mouse event. -pub fn mouse_scroll(p: impl Into, delta: impl Into) -> MouseEvent { - let pos = p.into(); - MouseEvent { - pos, - window_pos: pos, - buttons: MouseButtons::default(), - mods: Modifiers::default(), - count: 0, - focus: false, - button: MouseButton::None, - wheel_delta: delta.into(), - } -} +use crate::WidgetId; /// Convenience function to return an arrays of unique widget ids. pub fn widget_ids() -> [WidgetId; N] { diff --git a/src/testing/screenshots.rs b/src/testing/screenshots.rs index 2bcbdc40..55658b36 100644 --- a/src/testing/screenshots.rs +++ b/src/testing/screenshots.rs @@ -6,23 +6,6 @@ use image::{GenericImageView as _, RgbaImage}; -use crate::piet::{BitmapTarget, ImageFormat}; -use crate::Size; - -pub(crate) fn get_rgba_image(render_target: &mut BitmapTarget, window_size: Size) -> RgbaImage { - let pixels = render_target - .to_image_buf(ImageFormat::RgbaPremul) - .unwrap() - .raw_pixels_shared(); - - RgbaImage::from_raw( - window_size.width as u32, - window_size.height as u32, - Vec::from(pixels.as_ref()), - ) - .unwrap() -} - pub(crate) fn get_image_diff(ref_image: &RgbaImage, new_image: &RgbaImage) -> Option { let mut is_changed = false; diff --git a/src/text/attribute.rs b/src/text/attribute.rs deleted file mode 100644 index 9dfef727..00000000 --- a/src/text/attribute.rs +++ /dev/null @@ -1,381 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! Text attributes and spans. - -use std::ops::Range; - -use super::FontDescriptor; -use crate::piet::{Color, FontFamily, FontStyle, FontWeight, TextAttribute as PietAttr}; - -// TODO - Should also hold an associated Command, maybe. -/// A clickable range of text -#[derive(Debug, Clone)] -pub struct Link { - /// The range of text for the link. - pub range: Range, -} - -/// A collection of spans of attributes of various kinds. -#[derive(Debug, Clone, Default)] -pub struct AttributeSpans { - family: SpanSet, - size: SpanSet, - weight: SpanSet, - fg_color: SpanSet, - style: SpanSet, - underline: SpanSet, - font_descriptor: SpanSet, -} - -/// A set of spans for a given attribute. -/// -/// Invariant: the spans are sorted and non-overlapping. -#[derive(Debug, Clone)] -struct SpanSet { - spans: Vec>, -} - -/// An attribute and a range. -/// -/// This is used to represent text attributes of various kinds, -/// with the range representing a region of some text buffer. -#[derive(Debug, Clone, PartialEq)] -struct Span { - range: Range, - attr: T, -} - -/// Attributes that can be applied to text. -/// -/// Where possible, attributes are [`KeyOrValue`] types; this means you -/// can use items defined in the [`theme`] *or* concrete types, where appropriate. -/// -/// The easiest way to construct these attributes is via the various constructor -/// methods, such as [`Attribute::size`] or [`Attribute::text_color`]. -/// -/// # Examples -/// -/// ```no_run -/// use masonry::text::Attribute; -/// use masonry::{theme, Color}; -/// -/// let font = Attribute::font_descriptor(theme::UI_FONT); -/// let font_size = Attribute::size(32.0); -/// let explicit_color = Attribute::text_color(Color::BLACK); -/// let theme_color = Attribute::text_color(theme::SELECTION_TEXT_COLOR); -/// ``` -/// -/// [`KeyOrValue`]: ../enum.KeyOrValue.html -/// [`theme`]: ../theme -/// [`Attribute::size`]: #method.size -/// [`Attribute::text_color`]: #method.text_color -#[derive(Debug, Clone)] -pub enum Attribute { - /// The font family. - FontFamily(FontFamily), - /// The font size, in points. - FontSize(f64), - /// The [`FontWeight`](struct.FontWeight.html). - Weight(FontWeight), - /// The foreground color of the text. - TextColor(Color), - /// The [`FontStyle`]; either regular or italic. - /// - /// [`FontStyle`]: enum.FontStyle.html - Style(FontStyle), - /// Underline. - Underline(bool), - /// A [`FontDescriptor`](struct.FontDescriptor.html). - Descriptor(FontDescriptor), -} - -impl Link { - /// Create a new `Link`. - pub fn new(range: Range) -> Self { - Self { range } - } - - /// Get this `Link`'s range. - pub fn range(&self) -> Range { - self.range.clone() - } -} - -impl AttributeSpans { - /// Create a new, empty `AttributeSpans`. - pub fn new() -> Self { - Default::default() - } - - /// Add a new [`Attribute`] over the provided [`Range`]. - pub fn add(&mut self, range: Range, attr: Attribute) { - match attr { - Attribute::FontFamily(attr) => self.family.add(Span::new(range, attr)), - Attribute::FontSize(attr) => self.size.add(Span::new(range, attr)), - Attribute::Weight(attr) => self.weight.add(Span::new(range, attr)), - Attribute::TextColor(attr) => self.fg_color.add(Span::new(range, attr)), - Attribute::Style(attr) => self.style.add(Span::new(range, attr)), - Attribute::Underline(attr) => self.underline.add(Span::new(range, attr)), - Attribute::Descriptor(attr) => self.font_descriptor.add(Span::new(range, attr)), - } - } - - pub(crate) fn to_piet_attrs(&self) -> Vec<(Range, PietAttr)> { - let mut items = Vec::new(); - for Span { range, attr } in self.font_descriptor.iter() { - items.push((range.clone(), PietAttr::FontFamily(attr.family.clone()))); - items.push((range.clone(), PietAttr::FontSize(attr.size))); - items.push((range.clone(), PietAttr::Weight(attr.weight))); - items.push((range.clone(), PietAttr::Style(attr.style))); - } - - items.extend( - self.family - .iter() - .map(|s| (s.range.clone(), PietAttr::FontFamily(s.attr.clone()))), - ); - items.extend( - self.size - .iter() - .map(|s| (s.range.clone(), PietAttr::FontSize(s.attr))), - ); - items.extend( - self.weight - .iter() - .map(|s| (s.range.clone(), PietAttr::Weight(s.attr))), - ); - items.extend( - self.fg_color - .iter() - .map(|s| (s.range.clone(), PietAttr::TextColor(s.attr))), - ); - items.extend( - self.style - .iter() - .map(|s| (s.range.clone(), PietAttr::Style(s.attr))), - ); - items.extend( - self.underline - .iter() - .map(|s| (s.range.clone(), PietAttr::Underline(s.attr))), - ); - - // sort by ascending start order; this is a stable sort - // so items that come from FontDescriptor will stay at the front - items.sort_by(|a, b| a.0.start.cmp(&b.0.start)); - items - } -} - -impl SpanSet { - fn iter(&self) -> impl Iterator> { - self.spans.iter() - } - - /// Add a `Span` to this `SpanSet`. - /// - /// Spans can be added in any order. existing spans will be updated - /// as required. - fn add(&mut self, span: Span) { - let span_start = span.range.start; - let span_end = span.range.end; - let insert_idx = self - .spans - .iter() - .position(|x| x.range.start >= span.range.start) - .unwrap_or_else(|| self.spans.len()); - - // if we are inserting into the middle of an existing span we need - // to add the trailing portion back afterwards. - let mut prev_remainder = None; - - if insert_idx > 0 { - // truncate the preceding item, if necessary - let before = self.spans.get_mut(insert_idx - 1).unwrap(); - if before.range.end > span_end { - let mut remainder = before.clone(); - remainder.range.start = span_end; - prev_remainder = Some(remainder); - } - before.range.end = before.range.end.min(span_start); - } - - self.spans.insert(insert_idx, span); - if let Some(remainder) = prev_remainder.take() { - self.spans.insert(insert_idx + 1, remainder); - } - - // clip any existing spans as needed - for after in self.spans.iter_mut().skip(insert_idx + 1) { - after.range.start = after.range.start.max(span_end); - after.range.end = after.range.end.max(span_end); - } - - // remove any spans that have been overwritten - self.spans.retain(|span| !span.is_empty()); - } - - /// Edit the spans, inserting empty space into the changed region if needed. - /// - /// This is used to keep the spans up to date as edits occur in the buffer. - /// - /// `changed` is the range of the string that has been replaced; this can - /// be an empty range (eg, 10..10) for the insertion case. - /// - /// `new_len` is the length of the inserted text. - //TODO: we could be smarter here about just extending the existing spans - //as requred for insertions in the interior of a span. - //TODO: this isn't currently used; it should be used if we use spans with - //some editable type. - // the branches are much more readable without sharing code - #[allow(dead_code, clippy::branches_sharing_code)] - fn edit(&mut self, changed: Range, new_len: usize) { - let old_len = changed.len(); - let mut to_insert = None; - - for (idx, Span { range, attr }) in self.spans.iter_mut().enumerate() { - if range.end <= changed.start { - continue; - } else if range.start < changed.start { - // we start before but end inside; truncate end - if range.end <= changed.end { - range.end = changed.start; - // we start before and end after; this is a special case, - // we'll need to add a new span - } else { - let new_start = changed.start + new_len; - let new_end = range.end - old_len + new_len; - let new_span = Span::new(new_start..new_end, attr.clone()); - to_insert = Some((idx + 1, new_span)); - range.end = changed.start; - } - // start inside - } else if range.start < changed.end { - range.start = changed.start + new_len; - // end inside; collapse - if range.end <= changed.end { - range.end = changed.start + new_len; - // end outside: adjust by length delta - } else { - range.end -= old_len; - range.end += new_len; - } - // whole range is after: - } else { - range.start -= old_len; - range.start += new_len; - range.end -= old_len; - range.end += new_len; - } - } - if let Some((idx, span)) = to_insert.take() { - self.spans.insert(idx, span); - } - - self.spans.retain(|span| !span.is_empty()); - } -} - -impl Span { - fn new(range: Range, attr: T) -> Self { - Span { range, attr } - } - - fn is_empty(&self) -> bool { - self.range.end <= self.range.start - } -} - -impl Attribute { - /// Create a new font size attribute. - pub fn size(size: impl Into) -> Self { - Attribute::FontSize(size.into()) - } - - /// Create a new forground color attribute. - pub fn text_color(color: impl Into) -> Self { - Attribute::TextColor(color.into()) - } - - /// Create a new font family attribute. - pub fn font_family(family: FontFamily) -> Self { - Attribute::FontFamily(family) - } - - /// Create a new `FontWeight` attribute. - pub fn weight(weight: FontWeight) -> Self { - Attribute::Weight(weight) - } - - /// Create a new `FontStyle` attribute. - pub fn style(style: FontStyle) -> Self { - Attribute::Style(style) - } - - /// Create a new underline attribute. - pub fn underline(underline: bool) -> Self { - Attribute::Underline(underline) - } - - /// Create a new `FontDescriptor` attribute. - pub fn font_descriptor(font: impl Into) -> Self { - Attribute::Descriptor(font.into()) - } -} - -impl Default for SpanSet { - fn default() -> Self { - SpanSet { spans: Vec::new() } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn smoke_test_spans() { - let mut spans = SpanSet::::default(); - spans.add(Span::new(2..10, 1)); - spans.add(Span::new(3..6, 2)); - assert_eq!( - &spans.spans, - &vec![Span::new(2..3, 1), Span::new(3..6, 2), Span::new(6..10, 1)] - ); - - spans.add(Span::new(0..12, 3)); - assert_eq!(&spans.spans, &vec![Span::new(0..12, 3)]); - spans.add(Span::new(5..20, 4)); - assert_eq!(&spans.spans, &vec![Span::new(0..5, 3), Span::new(5..20, 4)]); - } - - #[test] - fn edit_spans() { - let mut spans = SpanSet::::default(); - spans.add(Span::new(0..2, 1)); - spans.add(Span::new(8..12, 2)); - spans.add(Span::new(13..16, 3)); - spans.add(Span::new(20..22, 4)); - - let mut deletion = spans.clone(); - deletion.edit(6..14, 0); - assert_eq!( - &deletion.spans, - &vec![Span::new(0..2, 1), Span::new(6..8, 3), Span::new(12..14, 4)] - ); - - spans.edit(10..10, 2); - assert_eq!( - &spans.spans, - &vec![ - Span::new(0..2, 1), - Span::new(8..10, 2), - Span::new(12..14, 2), - Span::new(15..18, 3), - Span::new(22..24, 4), - ] - ); - } -} diff --git a/src/text/backspace.rs b/src/text/backspace.rs deleted file mode 100644 index d688a430..00000000 --- a/src/text/backspace.rs +++ /dev/null @@ -1,196 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! Calc start of a backspace delete interval - -use xi_unicode::*; - -use crate::text::{EditableText, EditableTextCursor, Selection}; - -#[allow(clippy::cognitive_complexity)] -fn backspace_offset(text: &impl EditableText, start: usize) -> usize { - #[derive(PartialEq)] - enum State { - Start, - Lf, - BeforeKeycap, - BeforeVsAndKeycap, - BeforeEmojiModifier, - BeforeVsAndEmojiModifier, - BeforeVs, - BeforeEmoji, - BeforeZwj, - BeforeVsAndZwj, - OddNumberedRis, - EvenNumberedRis, - InTagSequence, - Finished, - } - let mut state = State::Start; - - let mut delete_code_point_count = 0; - let mut last_seen_vs_code_point_count = 0; - let mut cursor = text - .cursor(start) - .expect("Backspace must begin at a valid codepoint boundary."); - - while state != State::Finished && cursor.pos() > 0 { - let code_point = cursor.prev_codepoint().unwrap_or('0'); - - match state { - State::Start => { - delete_code_point_count = 1; - if code_point == '\n' { - state = State::Lf; - } else if is_variation_selector(code_point) { - state = State::BeforeVs; - } else if code_point.is_regional_indicator_symbol() { - state = State::OddNumberedRis; - } else if code_point.is_emoji_modifier() { - state = State::BeforeEmojiModifier; - } else if code_point.is_emoji_combining_enclosing_keycap() { - state = State::BeforeKeycap; - } else if code_point.is_emoji() { - state = State::BeforeEmoji; - } else if code_point.is_emoji_cancel_tag() { - state = State::InTagSequence; - } else { - state = State::Finished; - } - } - State::Lf => { - if code_point == '\r' { - delete_code_point_count += 1; - } - state = State::Finished; - } - State::OddNumberedRis => { - if code_point.is_regional_indicator_symbol() { - delete_code_point_count += 1; - state = State::EvenNumberedRis - } else { - state = State::Finished - } - } - State::EvenNumberedRis => { - if code_point.is_regional_indicator_symbol() { - delete_code_point_count -= 1; - state = State::OddNumberedRis; - } else { - state = State::Finished; - } - } - State::BeforeKeycap => { - if is_variation_selector(code_point) { - last_seen_vs_code_point_count = 1; - state = State::BeforeVsAndKeycap; - } else { - if is_keycap_base(code_point) { - delete_code_point_count += 1; - } - state = State::Finished; - } - } - State::BeforeVsAndKeycap => { - if is_keycap_base(code_point) { - delete_code_point_count += last_seen_vs_code_point_count + 1; - } - state = State::Finished; - } - State::BeforeEmojiModifier => { - if is_variation_selector(code_point) { - last_seen_vs_code_point_count = 1; - state = State::BeforeVsAndEmojiModifier; - } else { - if code_point.is_emoji_modifier_base() { - delete_code_point_count += 1; - } - state = State::Finished; - } - } - State::BeforeVsAndEmojiModifier => { - if code_point.is_emoji_modifier_base() { - delete_code_point_count += last_seen_vs_code_point_count + 1; - } - state = State::Finished; - } - State::BeforeVs => { - if code_point.is_emoji() { - delete_code_point_count += 1; - state = State::BeforeEmoji; - } else { - if !is_variation_selector(code_point) { - //TODO: UCharacter.getCombiningClass(codePoint) == 0 - delete_code_point_count += 1; - } - state = State::Finished; - } - } - State::BeforeEmoji => { - if code_point.is_zwj() { - state = State::BeforeZwj; - } else { - state = State::Finished; - } - } - State::BeforeZwj => { - if code_point.is_emoji() { - delete_code_point_count += 2; - state = if code_point.is_emoji_modifier() { - State::BeforeEmojiModifier - } else { - State::BeforeEmoji - }; - } else if is_variation_selector(code_point) { - last_seen_vs_code_point_count = 1; - state = State::BeforeVsAndZwj; - } else { - state = State::Finished; - } - } - State::BeforeVsAndZwj => { - if code_point.is_emoji() { - delete_code_point_count += last_seen_vs_code_point_count + 2; - last_seen_vs_code_point_count = 0; - state = State::BeforeEmoji; - } else { - state = State::Finished; - } - } - State::InTagSequence => { - if code_point.is_tag_spec_char() { - delete_code_point_count += 1; - } else if code_point.is_emoji() { - delete_code_point_count += 1; - state = State::Finished; - } else { - delete_code_point_count = 1; - state = State::Finished; - } - } - State::Finished => { - break; - } - } - } - - cursor.set(start); - for _ in 0..delete_code_point_count { - let _ = cursor.prev_codepoint(); - } - cursor.pos() -} - -/// Calculate resulting offset for a backwards delete. -/// -/// This involves complicated logic to handle various special cases that -/// are unique to backspace. -#[allow(clippy::trivially_copy_pass_by_ref)] -pub fn offset_for_delete_backwards(region: &Selection, text: &impl EditableText) -> usize { - if !region.is_caret() { - region.min() - } else { - backspace_offset(text, region.active) - } -} diff --git a/src/text/editable_text.rs b/src/text/editable_text.rs deleted file mode 100644 index 98088615..00000000 --- a/src/text/editable_text.rs +++ /dev/null @@ -1,522 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! Traits for text editing and a basic String implementation. - -use std::borrow::Cow; -use std::ops::{Deref, Range}; -use std::sync::Arc; - -use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation}; - -/// An EditableText trait. -pub trait EditableText: Sized { - // TODO: would be nice to have something like - // type Cursor: EditableTextCursor; - - /// Create a cursor with a reference to the text and a offset position. - /// - /// Returns None if the position isn't a codepoint boundary. - fn cursor(&self, position: usize) -> Option; - - /// Replace range with new text. - /// Can panic if supplied an invalid range. - // TODO: make this generic over Self - fn edit(&mut self, range: Range, new: impl Into); - - /// Get slice of text at range. - fn slice(&self, range: Range) -> Option>; - - /// Get length of text (in bytes). - fn len(&self) -> usize; - - /// Get the previous word offset from the given offset, if it exists. - fn prev_word_offset(&self, offset: usize) -> Option; - - /// Get the next word offset from the given offset, if it exists. - fn next_word_offset(&self, offset: usize) -> Option; - - /// Get the next grapheme offset from the given offset, if it exists. - fn prev_grapheme_offset(&self, offset: usize) -> Option; - - /// Get the next grapheme offset from the given offset, if it exists. - fn next_grapheme_offset(&self, offset: usize) -> Option; - - /// Get the previous codepoint offset from the given offset, if it exists. - fn prev_codepoint_offset(&self, offset: usize) -> Option; - - /// Get the next codepoint offset from the given offset, if it exists. - fn next_codepoint_offset(&self, offset: usize) -> Option; - - /// Get the preceding line break offset from the given offset - fn preceding_line_break(&self, offset: usize) -> usize; - - /// Get the next line break offset from the given offset - fn next_line_break(&self, offset: usize) -> usize; - - /// Returns `true` if this text has 0 length. - fn is_empty(&self) -> bool; - - /// Construct an instance of this type from a `&str`. - fn from_str(s: &str) -> Self; -} - -impl EditableText for String { - fn cursor<'a>(&self, position: usize) -> Option { - let new_cursor = StringCursor { - text: self, - position, - }; - - if new_cursor.is_boundary() { - Some(new_cursor) - } else { - None - } - } - - fn edit(&mut self, range: Range, new: impl Into) { - self.replace_range(range, &new.into()); - } - - fn slice(&self, range: Range) -> Option> { - self.get(range).map(Cow::from) - } - - fn len(&self) -> usize { - self.len() - } - - fn prev_grapheme_offset(&self, from: usize) -> Option { - let mut c = GraphemeCursor::new(from, self.len(), true); - c.prev_boundary(self, 0).unwrap() - } - - fn next_grapheme_offset(&self, from: usize) -> Option { - let mut c = GraphemeCursor::new(from, self.len(), true); - c.next_boundary(self, 0).unwrap() - } - - fn prev_codepoint_offset(&self, from: usize) -> Option { - let mut c = self.cursor(from).unwrap(); - c.prev() - } - - fn next_codepoint_offset(&self, from: usize) -> Option { - let mut c = self.cursor(from).unwrap(); - if c.next().is_some() { - Some(c.pos()) - } else { - None - } - } - - fn prev_word_offset(&self, from: usize) -> Option { - let mut offset = from; - let mut passed_alphanumeric = false; - for prev_grapheme in self.get(0..from)?.graphemes(true).rev() { - let is_alphanumeric = prev_grapheme.chars().next()?.is_alphanumeric(); - if is_alphanumeric { - passed_alphanumeric = true; - } else if passed_alphanumeric { - return Some(offset); - } - offset -= prev_grapheme.len(); - } - None - } - - fn next_word_offset(&self, from: usize) -> Option { - let mut offset = from; - let mut passed_alphanumeric = false; - for next_grapheme in self.get(from..)?.graphemes(true) { - let is_alphanumeric = next_grapheme.chars().next()?.is_alphanumeric(); - if is_alphanumeric { - passed_alphanumeric = true; - } else if passed_alphanumeric { - return Some(offset); - } - offset += next_grapheme.len(); - } - Some(self.len()) - } - - fn is_empty(&self) -> bool { - self.is_empty() - } - - fn from_str(s: &str) -> Self { - s.to_string() - } - - fn preceding_line_break(&self, from: usize) -> usize { - let mut offset = from; - - for byte in self.get(0..from).unwrap_or("").bytes().rev() { - if byte == 0x0a { - return offset; - } - offset -= 1; - } - - 0 - } - - fn next_line_break(&self, from: usize) -> usize { - let mut offset = from; - - for char in self.get(from..).unwrap_or("").bytes() { - if char == 0x0a { - return offset; - } - offset += 1; - } - - self.len() - } -} - -impl EditableText for Arc { - fn cursor(&self, position: usize) -> Option { - ::cursor(self, position) - } - fn edit(&mut self, range: Range, new: impl Into) { - let new = new.into(); - if !range.is_empty() || !new.is_empty() { - Arc::make_mut(self).edit(range, new) - } - } - fn slice(&self, range: Range) -> Option> { - Some(Cow::Borrowed(&self[range])) - } - fn len(&self) -> usize { - self.deref().len() - } - fn prev_word_offset(&self, offset: usize) -> Option { - self.deref().prev_word_offset(offset) - } - fn next_word_offset(&self, offset: usize) -> Option { - self.deref().next_word_offset(offset) - } - fn prev_grapheme_offset(&self, offset: usize) -> Option { - self.deref().prev_grapheme_offset(offset) - } - fn next_grapheme_offset(&self, offset: usize) -> Option { - self.deref().next_grapheme_offset(offset) - } - fn prev_codepoint_offset(&self, offset: usize) -> Option { - self.deref().prev_codepoint_offset(offset) - } - fn next_codepoint_offset(&self, offset: usize) -> Option { - self.deref().next_codepoint_offset(offset) - } - fn preceding_line_break(&self, offset: usize) -> usize { - self.deref().preceding_line_break(offset) - } - fn next_line_break(&self, offset: usize) -> usize { - self.deref().next_line_break(offset) - } - fn is_empty(&self) -> bool { - self.deref().is_empty() - } - fn from_str(s: &str) -> Self { - Arc::new(s.to_owned()) - } -} - -/// A cursor with convenience functions for moving through EditableText. -pub trait EditableTextCursor { - /// Set cursor position. - fn set(&mut self, position: usize); - - /// Get cursor position. - fn pos(&self) -> usize; - - /// Check if cursor position is at a codepoint boundary. - fn is_boundary(&self) -> bool; - - /// Move cursor to previous codepoint boundary, if it exists. - /// Returns previous codepoint as usize offset. - fn prev(&mut self) -> Option; - - /// Move cursor to next codepoint boundary, if it exists. - /// Returns current codepoint as usize offset. - fn next(&mut self) -> Option; - - /// Get the next codepoint after the cursor position, without advancing - /// the cursor. - fn peek_next_codepoint(&self) -> Option; - - /// Return codepoint preceding cursor offset and move cursor backward. - fn prev_codepoint(&mut self) -> Option; - - /// Return codepoint at cursor offset and move cursor forward. - fn next_codepoint(&mut self) -> Option; - - /// Return current offset if it's a boundary, else next. - fn at_or_next(&mut self) -> Option; - - /// Return current offset if it's a boundary, else previous. - fn at_or_prev(&mut self) -> Option; -} - -/// A cursor type that implements EditableTextCursor for String -#[derive(Debug)] -pub struct StringCursor<'a> { - text: &'a str, - position: usize, -} - -impl<'a> EditableTextCursor<&'a String> for StringCursor<'a> { - fn set(&mut self, position: usize) { - self.position = position; - } - - fn pos(&self) -> usize { - self.position - } - - fn is_boundary(&self) -> bool { - self.text.is_char_boundary(self.position) - } - - fn prev(&mut self) -> Option { - let current_pos = self.pos(); - - if current_pos == 0 { - None - } else { - let mut len = 1; - while !self.text.is_char_boundary(current_pos - len) { - len += 1; - } - self.set(self.pos() - len); - Some(self.pos()) - } - } - - fn next(&mut self) -> Option { - let current_pos = self.pos(); - - if current_pos == self.text.len() { - None - } else { - let b = self.text.as_bytes()[current_pos]; - self.set(current_pos + len_utf8_from_first_byte(b)); - Some(current_pos) - } - } - - fn peek_next_codepoint(&self) -> Option { - self.text[self.pos()..].chars().next() - } - - fn prev_codepoint(&mut self) -> Option { - if let Some(prev) = self.prev() { - self.text[prev..].chars().next() - } else { - None - } - } - - fn next_codepoint(&mut self) -> Option { - let current_index = self.pos(); - if self.next().is_some() { - self.text[current_index..].chars().next() - } else { - None - } - } - - fn at_or_next(&mut self) -> Option { - if self.is_boundary() { - Some(self.pos()) - } else { - self.next() - } - } - - fn at_or_prev(&mut self) -> Option { - if self.is_boundary() { - Some(self.pos()) - } else { - self.prev() - } - } -} - -pub fn len_utf8_from_first_byte(b: u8) -> usize { - match b { - b if b < 0x80 => 1, - b if b < 0xe0 => 2, - b if b < 0xf0 => 3, - _ => 4, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn replace() { - let mut a = String::from("hello world"); - a.edit(1..9, "era"); - assert_eq!("herald", a); - } - - #[test] - fn prev_codepoint_offset() { - let a = String::from("a\u{00A1}\u{4E00}\u{1F4A9}"); - assert_eq!(Some(6), a.prev_codepoint_offset(10)); - assert_eq!(Some(3), a.prev_codepoint_offset(6)); - assert_eq!(Some(1), a.prev_codepoint_offset(3)); - assert_eq!(Some(0), a.prev_codepoint_offset(1)); - assert_eq!(None, a.prev_codepoint_offset(0)); - let b = a.slice(1..10).unwrap().to_string(); - assert_eq!(Some(5), b.prev_codepoint_offset(9)); - assert_eq!(Some(2), b.prev_codepoint_offset(5)); - assert_eq!(Some(0), b.prev_codepoint_offset(2)); - assert_eq!(None, b.prev_codepoint_offset(0)); - } - - #[test] - fn next_codepoint_offset() { - let a = String::from("a\u{00A1}\u{4E00}\u{1F4A9}"); - assert_eq!(Some(10), a.next_codepoint_offset(6)); - assert_eq!(Some(6), a.next_codepoint_offset(3)); - assert_eq!(Some(3), a.next_codepoint_offset(1)); - assert_eq!(Some(1), a.next_codepoint_offset(0)); - assert_eq!(None, a.next_codepoint_offset(10)); - let b = a.slice(1..10).unwrap().to_string(); - assert_eq!(Some(9), b.next_codepoint_offset(5)); - assert_eq!(Some(5), b.next_codepoint_offset(2)); - assert_eq!(Some(2), b.next_codepoint_offset(0)); - assert_eq!(None, b.next_codepoint_offset(9)); - } - - #[test] - fn prev_next() { - let input = String::from("abc"); - let mut cursor = input.cursor(0).unwrap(); - assert_eq!(cursor.next(), Some(0)); - assert_eq!(cursor.next(), Some(1)); - assert_eq!(cursor.prev(), Some(1)); - assert_eq!(cursor.next(), Some(1)); - assert_eq!(cursor.next(), Some(2)); - } - - #[test] - fn peek_next_codepoint() { - let inp = String::from("$¢€£💶"); - let mut cursor = inp.cursor(0).unwrap(); - assert_eq!(cursor.peek_next_codepoint(), Some('$')); - assert_eq!(cursor.peek_next_codepoint(), Some('$')); - assert_eq!(cursor.next_codepoint(), Some('$')); - assert_eq!(cursor.peek_next_codepoint(), Some('¢')); - assert_eq!(cursor.prev_codepoint(), Some('$')); - assert_eq!(cursor.peek_next_codepoint(), Some('$')); - assert_eq!(cursor.next_codepoint(), Some('$')); - assert_eq!(cursor.next_codepoint(), Some('¢')); - assert_eq!(cursor.peek_next_codepoint(), Some('€')); - assert_eq!(cursor.next_codepoint(), Some('€')); - assert_eq!(cursor.peek_next_codepoint(), Some('£')); - assert_eq!(cursor.next_codepoint(), Some('£')); - assert_eq!(cursor.peek_next_codepoint(), Some('💶')); - assert_eq!(cursor.next_codepoint(), Some('💶')); - assert_eq!(cursor.peek_next_codepoint(), None); - assert_eq!(cursor.next_codepoint(), None); - assert_eq!(cursor.peek_next_codepoint(), None); - } - - #[test] - fn prev_grapheme_offset() { - // A with ring, hangul, regional indicator "US" - let a = String::from("A\u{030a}\u{110b}\u{1161}\u{1f1fa}\u{1f1f8}"); - assert_eq!(Some(9), a.prev_grapheme_offset(17)); - assert_eq!(Some(3), a.prev_grapheme_offset(9)); - assert_eq!(Some(0), a.prev_grapheme_offset(3)); - assert_eq!(None, a.prev_grapheme_offset(0)); - } - - #[test] - fn next_grapheme_offset() { - // A with ring, hangul, regional indicator "US" - let a = String::from("A\u{030a}\u{110b}\u{1161}\u{1f1fa}\u{1f1f8}"); - assert_eq!(Some(3), a.next_grapheme_offset(0)); - assert_eq!(Some(9), a.next_grapheme_offset(3)); - assert_eq!(Some(17), a.next_grapheme_offset(9)); - assert_eq!(None, a.next_grapheme_offset(17)); - } - - #[test] - fn prev_word_offset() { - let a = String::from("Technically a word: ৬藏A\u{030a}\u{110b}\u{1161}"); - assert_eq!(Some(20), a.prev_word_offset(35)); - assert_eq!(Some(20), a.prev_word_offset(27)); - assert_eq!(Some(20), a.prev_word_offset(23)); - assert_eq!(Some(14), a.prev_word_offset(20)); - assert_eq!(Some(14), a.prev_word_offset(19)); - assert_eq!(Some(12), a.prev_word_offset(13)); - assert_eq!(None, a.prev_word_offset(12)); - assert_eq!(None, a.prev_word_offset(11)); - assert_eq!(None, a.prev_word_offset(0)); - } - - #[test] - fn next_word_offset() { - let a = String::from("Technically a word: ৬藏A\u{030a}\u{110b}\u{1161}"); - assert_eq!(Some(11), a.next_word_offset(0)); - assert_eq!(Some(11), a.next_word_offset(7)); - assert_eq!(Some(13), a.next_word_offset(11)); - assert_eq!(Some(18), a.next_word_offset(14)); - assert_eq!(Some(35), a.next_word_offset(18)); - assert_eq!(Some(35), a.next_word_offset(19)); - assert_eq!(Some(35), a.next_word_offset(20)); - assert_eq!(Some(35), a.next_word_offset(26)); - assert_eq!(Some(35), a.next_word_offset(35)); - } - - #[test] - fn preceding_line_break() { - let a = String::from("Technically\na word:\n ৬藏A\u{030a}\n\u{110b}\u{1161}"); - assert_eq!(0, a.preceding_line_break(0)); - assert_eq!(0, a.preceding_line_break(11)); - assert_eq!(12, a.preceding_line_break(12)); - assert_eq!(12, a.preceding_line_break(13)); - assert_eq!(20, a.preceding_line_break(21)); - assert_eq!(31, a.preceding_line_break(31)); - assert_eq!(31, a.preceding_line_break(34)); - - let b = String::from("Technically a word: ৬藏A\u{030a}\u{110b}\u{1161}"); - assert_eq!(0, b.preceding_line_break(0)); - assert_eq!(0, b.preceding_line_break(11)); - assert_eq!(0, b.preceding_line_break(13)); - assert_eq!(0, b.preceding_line_break(21)); - } - - #[test] - fn next_line_break() { - let a = String::from("Technically\na word:\n ৬藏A\u{030a}\n\u{110b}\u{1161}"); - assert_eq!(11, a.next_line_break(0)); - assert_eq!(11, a.next_line_break(11)); - assert_eq!(19, a.next_line_break(13)); - assert_eq!(30, a.next_line_break(21)); - assert_eq!(a.len(), a.next_line_break(31)); - - let b = String::from("Technically a word: ৬藏A\u{030a}\u{110b}\u{1161}"); - assert_eq!(b.len(), b.next_line_break(0)); - assert_eq!(b.len(), b.next_line_break(11)); - assert_eq!(b.len(), b.next_line_break(13)); - assert_eq!(b.len(), b.next_line_break(19)); - } - - #[test] - fn arcstring_empty_edit() { - let a = Arc::new("hello".to_owned()); - let mut b = a.clone(); - b.edit(5..5, ""); - assert!(Arc::ptr_eq(&a, &b)); - } -} diff --git a/src/text/font_descriptor.rs b/src/text/font_descriptor.rs deleted file mode 100644 index 19dad581..00000000 --- a/src/text/font_descriptor.rs +++ /dev/null @@ -1,70 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! Font attributes - -use crate::piet::{FontFamily, FontStyle, FontWeight}; - -/// A collection of attributes that describe a font. -/// -/// This is provided as a convenience; library consumers may wish to have -/// a single type that represents a specific font face at a specific size. -#[derive(Debug, Clone, PartialEq)] -pub struct FontDescriptor { - /// The font's [`FontFamily`](struct.FontFamily.html). - pub family: FontFamily, - /// The font's size. - pub size: f64, - /// The font's [`FontWeight`](struct.FontWeight.html). - pub weight: FontWeight, - /// The font's [`FontStyle`](struct.FontStyle.html). - pub style: FontStyle, -} - -impl FontDescriptor { - /// Create a new descriptor with the provided [`FontFamily`]. - /// - /// [`FontFamily`]: struct.FontFamily.html - pub const fn new(family: FontFamily) -> Self { - FontDescriptor { - family, - size: crate::piet::util::DEFAULT_FONT_SIZE, - weight: FontWeight::REGULAR, - style: FontStyle::Regular, - } - } - - /// Buider-style method to set the descriptor's font size. - pub const fn with_size(mut self, size: f64) -> Self { - self.size = size; - self - } - - /// Buider-style method to set the descriptor's [`FontWeight`]. - /// - /// [`FontWeight`]: struct.FontWeight.html - pub const fn with_weight(mut self, weight: FontWeight) -> Self { - self.weight = weight; - self - } - - /// Buider-style method to set the descriptor's [`FontStyle`]. - /// - /// [`FontStyle`]: enum.FontStyle.html - pub const fn with_style(mut self, style: FontStyle) -> Self { - self.style = style; - self - } -} - -impl Default for FontDescriptor { - fn default() -> Self { - FontDescriptor { - family: Default::default(), - weight: Default::default(), - style: Default::default(), - size: crate::piet::util::DEFAULT_FONT_SIZE, - } - } -} diff --git a/src/text/input_component.rs b/src/text/input_component.rs deleted file mode 100644 index fe5fcaba..00000000 --- a/src/text/input_component.rs +++ /dev/null @@ -1,954 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! A widget component that integrates with the platform text system. - -use std::cell::{Cell, Ref, RefCell, RefMut}; -use std::ops::Range; -use std::sync::{Arc, Weak}; - -use druid_shell::{Cursor, Modifiers}; -use smallvec::SmallVec; -use tracing::{trace_span, Span}; - -use super::{ - EditableText, ImeHandlerRef, ImeInvalidation, InputHandler, Movement, Selection, TextAction, - TextAlignment, TextLayout, TextStorage, -}; -use crate::kurbo::{Line, Point, Rect, Vec2}; -use crate::piet::TextLayout as _; -use crate::widget::WidgetRef; -use crate::{ - text, theme, BoxConstraints, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, - RenderContext, Selector, Size, StatusChange, Widget, -}; - -/// A widget that accepts text input. -/// -/// This is intended to be used as a component of other widgets. -/// -/// Text input is more complicated than you think, probably. For a good -/// overview, see [`druid_shell::text`]. -/// -/// This type manages an inner [`EditSession`] that is shared with the platform. -/// Unlike other aspects of Masonry, the platform interacts with this session, not -/// through discrete events. -/// -/// This is managed through a simple 'locking' mechanism; the platform asks for -/// a lock on a particular text session that it wishes to interact with, calls -/// methods on the locked session, and then later releases the lock. -/// -/// Importantly, *other events may be received while the lock is held*. -/// -/// It is the responsibility of the user of this widget to ensure that the -/// session is not locked before it is accessed. This can be done by checking -/// [`TextComponent::can_read`] and [`TextComponent::can_write`]; -/// after checking these methods the inner session can be accessed via -/// [`TextComponent::borrow`] and [`TextComponent::borrow_mut`]. -/// -/// Sementically, this functions like a `RefCell`; attempting to borrow while -/// a lock is held will result in a panic. -#[derive(Debug, Clone)] -pub struct TextComponent { - edit_session: Arc>>, - lock: Arc>, - // HACK: because of the way focus works (it is managed higher up, in - // whatever widget is controlling this) we can't rely on `is_focused` in - // the PaintCtx. - /// A manual flag set by the parent to control drawing behaviour. - /// - /// The parent should update this when handling [`StatusChange::FocusChanged`]. - pub has_focus: bool, -} - -crate::declare_widget!( - TextComponentMut, - TextComponent -); - -/// Editable text state. -/// -/// This is the inner state of a [`TextComponent`]. It should only be accessed -/// through its containing [`TextComponent`], or by the platform through an -/// [`ImeHandlerRef`] created by [`TextComponent::input_handler`]. -#[derive(Debug, Clone)] -pub struct EditSession { - /// The inner [`TextLayout`] object. - /// - /// This is exposed so that users can do things like set text properties; - /// you should avoid doing things like rebuilding this layout manually, or - /// setting the text directly. - pub layout: TextLayout, - /// If the platform modifies the text, this contains the new text; - /// we update the app `Data` with this text on the next update pass. - external_text_change: Option, - external_selection_change: Option, - external_scroll_to: Option, - external_action: Option, - /// A flag set in `update` if the text has changed from a non-IME source. - pending_ime_invalidation: Option, - /// If `true`, the component will send the [`TextComponent::RETURN`] - /// notification when the user enters a newline. - pub send_notification_on_return: bool, - /// If `true`, the component will send the [`TextComponent::CANCEL`] - /// notification when the user cancels editing. - pub send_notification_on_cancel: bool, - selection: Selection, - accepts_newlines: bool, - accepts_tabs: bool, - alignment: TextAlignment, - /// The y-position of the text when it does not fill our width. - alignment_offset: f64, - /// The portion of the text that is currently marked by the IME. - composition_range: Option>, - drag_granularity: DragGranularity, - /// The origin of the textbox, relative to the origin of the window. - pub origin: Point, -} - -/// An object that can be used to acquire an `ImeHandler`. -/// -/// This does not own the session; when the widget that owns the session -/// is dropped, this will become invalid. -#[derive(Debug, Clone)] -struct EditSessionRef { - inner: Weak>>, - lock: Arc>, -} - -/// A locked handle to an [`EditSession`]. -/// -/// This type implements [`InputHandler`]; it is the type that we pass to the -/// platform. -struct EditSessionHandle { - text: T, - inner: Arc>>, -} - -/// When a drag follows a double- or triple-click, the behaviour of -/// drag changes to only select whole words or whole paragraphs. -#[derive(Debug, Clone, Copy, PartialEq)] -enum DragGranularity { - Grapheme, - /// Start and end are the start/end bounds of the initial selection. - Word { - start: usize, - end: usize, - }, - /// Start and end are the start/end bounds of the initial selection. - Paragraph { - start: usize, - end: usize, - }, -} - -/// An informal lock. -#[derive(Debug, Clone, Copy, PartialEq)] -enum ImeLock { - None, - ReadWrite, - Read, -} - -impl ImeHandlerRef for EditSessionRef { - fn is_alive(&self) -> bool { - Weak::strong_count(&self.inner) > 0 - } - - fn acquire(&self, mutable: bool) -> Option> { - let lock = if mutable { - ImeLock::ReadWrite - } else { - ImeLock::Read - }; - assert_eq!( - self.lock.replace(lock), - ImeLock::None, - "Ime session is already locked" - ); - Weak::upgrade(&self.inner) - .map(EditSessionHandle::new) - .map(|doc| Box::new(doc) as Box) - } - - fn release(&self) -> bool { - self.lock.replace(ImeLock::None) == ImeLock::ReadWrite - } -} - -impl TextComponent<()> { - /// A notification sent by the component when the cursor has moved. - /// - /// If the payload is true, this follows an edit, and the view will need - /// layout before scrolling. - pub const SCROLL_TO: Selector = Selector::new("masonry-builtin.textbox-scroll-to"); - - /// A notification sent by the component when the user hits return. - /// - /// This is only sent when `send_notification_on_return` is `true`. - pub const RETURN: Selector = Selector::new("masonry-builtin.textbox-return"); - - /// A notification sent by the component when the user edits contents. - pub const TEXT_CHANGED: Selector = Selector::new("masonry-builtin.textbox-changed"); - - /// A notification sent when the user cancels editing. - /// - /// This is only sent when `send_notification_on_cancel` is `true`. - pub const CANCEL: Selector = Selector::new("masonry-builtin.textbox-cancel"); - - /// A notification sent by the component when the user presses the tab key. - /// - /// This is not sent if `accepts_tabs` is true. - /// - /// An ancestor can handle this event in order to do things like request - /// a focus change. - pub const TAB: Selector = Selector::new("masonry-builtin.textbox-tab"); - - /// A notification sent by the component when the user inserts a backtab. - /// - /// This is not sent if `accepts_tabs` is true. - /// - /// An ancestor can handle this event in order to do things like request - /// a focus change. - pub const BACKTAB: Selector = Selector::new("masonry-builtin.textbox-backtab"); -} - -impl TextComponent { - /// Returns `true` if the inner [`EditSession`] can be read. - pub fn can_read(&self) -> bool { - self.lock.get() != ImeLock::ReadWrite - } - - /// Returns `true` if the inner [`EditSession`] can be mutated. - pub fn can_write(&self) -> bool { - self.lock.get() == ImeLock::None - } - - /// Returns `true` if the IME is actively composing (or the text is locked.) - /// - /// When text is composing, you should avoid doing things like modifying the - /// selection or copy/pasting text. - pub fn is_composing(&self) -> bool { - self.can_read() && self.borrow().composition_range.is_some() - } - - /// Attempt to mutably borrow the inner [`EditSession`]. - /// - /// # Panics - /// - /// This method panics if there is an outstanding lock on the session. - pub fn borrow_mut(&self) -> RefMut<'_, EditSession> { - assert!(self.can_write()); - self.edit_session.borrow_mut() - } - - /// Attempt to borrow the inner [`EditSession`]. - /// - /// # Panics - /// - /// This method panics if there is an outstanding write lock on the session. - pub fn borrow(&self) -> Ref<'_, EditSession> { - assert!(self.can_read()); - self.edit_session.borrow() - } -} - -impl TextComponent { - /// Returns an [`ImeHandlerRef`] that can accept platform text input. - /// - /// The widget managing this component should call [`LifeCycleCtx::register_text_input`] - /// during [`LifeCycle::WidgetAdded`], and pass it this object. - pub fn input_handler(&self) -> impl ImeHandlerRef { - EditSessionRef { - inner: Arc::downgrade(&self.edit_session), - lock: self.lock.clone(), - } - } -} - -impl TextComponentMut<'_, '_, T> { - pub fn set_text(&mut self, new_text: impl Into) { - let new_text = new_text.into(); - // TODO - use '==' instead - let needs_rebuild = self - .widget - .borrow() - .layout - .text() - .map(|old| !old.maybe_eq(&new_text)) - .unwrap_or(true); - if needs_rebuild { - self.widget.borrow_mut().layout.set_text(new_text.clone()); - self.widget - .borrow_mut() - .update_pending_invalidation(ImeInvalidation::Reset); - self.ctx.request_layout(); - } - } - - pub fn set_focused(&mut self, focused: bool) { - self.widget.has_focus = focused; - self.ctx.request_paint(); - } -} - -impl Widget for TextComponent { - fn on_event(&mut self, ctx: &mut EventCtx, event: &Event) { - match event { - Event::MouseDown(mouse) if self.can_write() && !ctx.is_disabled() => { - ctx.set_active(true); - self.borrow_mut() - .do_mouse_down(mouse.pos, mouse.mods, mouse.count); - self.borrow_mut() - .update_pending_invalidation(ImeInvalidation::SelectionChanged); - ctx.request_layout(); - ctx.request_paint(); - } - Event::MouseMove(mouse) if self.can_write() => { - if !ctx.is_disabled() { - ctx.set_cursor(&Cursor::IBeam); - if ctx.is_active() { - let pre_sel = self.borrow().selection(); - self.borrow_mut().do_drag(mouse.pos); - if self.borrow().selection() != pre_sel { - self.borrow_mut() - .update_pending_invalidation(ImeInvalidation::SelectionChanged); - ctx.request_layout(); - ctx.request_paint(); - } - } - } else { - ctx.set_disabled(false); - ctx.clear_cursor(); - } - } - Event::MouseUp(_) if ctx.is_active() => { - ctx.set_active(false); - ctx.request_paint(); - } - Event::ImeStateChange => { - assert!( - self.can_write(), - "lock release should be cause of ImeStateChange event" - ); - - let scroll_to = self.borrow_mut().take_scroll_to(); - if let Some(scroll_to) = scroll_to { - ctx.submit_notification(TextComponent::SCROLL_TO.with(scroll_to)); - } - - let text = self.borrow_mut().take_external_text_change(); - if let Some(text) = text { - self.borrow_mut().layout.set_text(text.clone()); - let new_text = self - .borrow() - .layout - .text() - .map(|txt| txt.as_str()) - .unwrap_or("") - .to_string(); - ctx.submit_notification(TextComponent::TEXT_CHANGED.with(new_text)); - } - - let action = self.borrow_mut().take_external_action(); - if let Some(action) = action { - match action { - TextAction::Cancel => ctx.submit_notification(TextComponent::CANCEL), - TextAction::InsertNewLine { .. } => { - let text = self - .borrow() - .layout - .text() - .map(|txt| txt.as_str()) - .unwrap_or("") - .to_string(); - ctx.submit_notification(TextComponent::RETURN.with(text)); - } - TextAction::InsertTab { .. } => ctx.submit_notification(TextComponent::TAB), - TextAction::InsertBacktab => { - ctx.submit_notification(TextComponent::BACKTAB) - } - _ => tracing::warn!("unexepcted external action '{:?}'", action), - }; - } - - let selection = self.borrow_mut().take_external_selection_change(); - if let Some(selection) = selection { - self.borrow_mut().selection = selection; - ctx.request_paint(); - } - ctx.request_layout(); - } - _ => (), - } - } - - fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} - - fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { - match event { - LifeCycle::WidgetAdded => { - assert!( - self.can_write(), - "ime should never be locked at WidgetAdded" - ); - self.borrow_mut().layout.rebuild_if_needed(ctx.text()); - } - LifeCycle::DisabledChanged(disabled) => { - if self.can_write() { - let color = if *disabled { - theme::DISABLED_TEXT_COLOR - } else { - theme::TEXT_COLOR - }; - self.borrow_mut().layout.set_text_color(color); - } - ctx.request_layout(); - } - //FIXME: this should happen in the parent too? - LifeCycle::Internal(crate::InternalLifeCycle::ParentWindowOrigin) => { - if self.can_write() { - let prev_origin = self.borrow().origin; - let new_origin = ctx.window_origin(); - if prev_origin != new_origin { - self.borrow_mut().origin = ctx.window_origin(); - ctx.invalidate_text_input(ImeInvalidation::LayoutChanged); - } - } - } - _ => (), - } - } - - fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { - if !self.can_write() { - tracing::warn!("Text layout called with IME lock held."); - return Size::ZERO; - } - - self.borrow_mut().layout.set_wrap_width(bc.max().width); - self.borrow_mut().layout.rebuild_if_needed(ctx.text()); - let metrics = self.borrow().layout.layout_metrics(); - let width = if bc.max().width.is_infinite() || bc.max().width < f64::MAX { - metrics.trailing_whitespace_width - } else { - metrics.size.width - }; - let size = bc.constrain((width, metrics.size.height)); - let extra_width = if self.borrow().accepts_newlines { - 0.0 - } else { - (size.width - width).max(0.0) - }; - self.borrow_mut().update_alignment_offset(extra_width); - let baseline_off = metrics.size.height - metrics.first_baseline; - ctx.set_baseline_offset(baseline_off); - size - } - - fn paint(&mut self, ctx: &mut PaintCtx) { - if !self.can_read() { - tracing::warn!("Text paint called with IME lock held."); - } - - let selection_color = if self.has_focus { - theme::SELECTED_TEXT_BACKGROUND_COLOR - } else { - theme::SELECTED_TEXT_INACTIVE_BACKGROUND_COLOR - }; - - let cursor_color = theme::CURSOR_COLOR; - let text_offset = Vec2::new(self.borrow().alignment_offset, 0.0); - - let selection = self.borrow().selection(); - let composition = self.borrow().composition_range(); - let sel_rects = self.borrow().layout.rects_for_range(selection.range()); - if let Some(composition) = composition { - // I believe selection should always be contained in composition range while composing? - assert!(composition.start <= selection.anchor && composition.end >= selection.active); - let comp_rects = self.borrow().layout.rects_for_range(composition); - for region in comp_rects { - let y = region.max_y().floor(); - let line = Line::new((region.min_x(), y), (region.max_x(), y)) + text_offset; - ctx.stroke(line, &cursor_color, 1.0); - } - for region in sel_rects { - let y = region.max_y().floor(); - let line = Line::new((region.min_x(), y), (region.max_x(), y)) + text_offset; - ctx.stroke(line, &cursor_color, 2.0); - } - } else { - for region in sel_rects { - let rounded = (region + text_offset).to_rounded_rect(1.0); - ctx.fill(rounded, &selection_color); - } - } - self.borrow().layout.draw(ctx, text_offset.to_point()); - } - - fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { - SmallVec::new() - } - - fn make_trace_span(&self) -> Span { - trace_span!("TextComponent") - } -} - -impl EditSession { - /// The current [`Selection`]. - pub fn selection(&self) -> Selection { - self.selection - } - - /// Manually set the selection. - /// - /// If the new selection is different from the current selection, this - /// will return an ime event that the controlling widget should use to - /// invalidte the platform's IME state, by passing it to - /// [`EventCtx::invalidate_text_input`]. - #[must_use] - pub fn set_selection(&mut self, selection: Selection) -> Option { - if selection != self.selection { - self.selection = selection; - self.update_pending_invalidation(ImeInvalidation::SelectionChanged); - Some(ImeInvalidation::SelectionChanged) - } else { - None - } - } - - /// The range of text currently being modified by an IME. - pub fn composition_range(&self) -> Option> { - self.composition_range.clone() - } - - /// Sets whether or not this session will allow the insertion of newlines. - pub fn set_accepts_newlines(&mut self, accepts_newlines: bool) { - self.accepts_newlines = accepts_newlines; - } - - /// Set the text alignment. - /// - /// This is only meaningful for single-line text that does not fill - /// the minimum layout size. - pub fn set_text_alignment(&mut self, alignment: TextAlignment) { - self.alignment = alignment; - } - - /// The text alignment. - pub fn text_alignment(&self) -> TextAlignment { - self.alignment - } - - /// Returns any invalidation action that should be passed to the platform. - /// - /// The user of this component *must* check this after calling `update`. - pub fn pending_ime_invalidation(&mut self) -> Option { - self.pending_ime_invalidation.take() - } - - fn take_external_text_change(&mut self) -> Option { - self.external_text_change.take() - } - - fn take_external_selection_change(&mut self) -> Option { - self.external_selection_change.take() - } - - fn take_scroll_to(&mut self) -> Option { - self.external_scroll_to.take() - } - - fn take_external_action(&mut self) -> Option { - self.external_action.take() - } - - // we don't want to replace a more aggressive invalidation with a less aggressive one. - fn update_pending_invalidation(&mut self, new_invalidation: ImeInvalidation) { - self.pending_ime_invalidation = match self.pending_ime_invalidation.take() { - None => Some(new_invalidation), - Some(prev) => match (prev, new_invalidation) { - (ImeInvalidation::SelectionChanged, ImeInvalidation::SelectionChanged) => { - ImeInvalidation::SelectionChanged - } - (ImeInvalidation::LayoutChanged, ImeInvalidation::LayoutChanged) => { - ImeInvalidation::LayoutChanged - } - _ => ImeInvalidation::Reset, - } - .into(), - } - } - - fn update_alignment_offset(&mut self, extra_width: f64) { - self.alignment_offset = match self.alignment { - TextAlignment::Start | TextAlignment::Justified => 0.0, - TextAlignment::End => extra_width, - TextAlignment::Center => extra_width / 2.0, - }; - } -} - -impl EditSession { - /// Insert text *not* from the IME, replacing the current selection. - /// - /// The caller is responsible for notifying the platform of the change in - /// text state, by calling [`EventCtx::invalidate_text_input`]. - #[must_use] - pub fn insert_text(&mut self, data: &mut T, new_text: &str) -> ImeInvalidation { - let new_cursor_pos = self.selection.min() + new_text.len(); - data.edit(self.selection.range(), new_text); - self.selection = Selection::caret(new_cursor_pos); - self.scroll_to_selection_end(true); - ImeInvalidation::Reset - } - - /// Sets the clipboard to the contents of the current selection. - /// - /// Returns `true` if the clipboard was set, and `false` if not (indicating) - /// that the selection was empty.) - pub fn set_clipboard(&self) -> bool { - if let Some(text) = self - .layout - .text() - .and_then(|txt| txt.slice(self.selection.range())) - { - if !text.is_empty() { - druid_shell::Application::global() - .clipboard() - .put_string(text); - return true; - } - } - false - } - - fn scroll_to_selection_end(&mut self, after_edit: bool) { - self.external_scroll_to = Some(after_edit); - } - - fn do_action(&mut self, buffer: &mut T, action: TextAction) { - match action { - TextAction::Move(movement) => { - let sel = text::movement(movement, self.selection, &self.layout, false); - self.external_selection_change = Some(sel); - self.scroll_to_selection_end(false); - } - TextAction::MoveSelecting(movement) => { - let sel = text::movement(movement, self.selection, &self.layout, true); - self.external_selection_change = Some(sel); - self.scroll_to_selection_end(false); - } - TextAction::SelectAll => { - let len = buffer.len(); - self.external_selection_change = Some(Selection::new(0, len)); - } - TextAction::SelectWord => { - if self.selection.is_caret() { - let range = - text::movement::word_range_for_pos(buffer.as_str(), self.selection.active); - self.external_selection_change = Some(Selection::new(range.start, range.end)); - } - - // it is unclear what the behaviour should be if the selection - // is not a caret (and may span multiple words) - } - // This requires us to have access to the layout, which might be stale? - TextAction::SelectLine => (), - // this assumes our internal selection is consistent with the buffer? - TextAction::SelectParagraph => { - if !self.selection.is_caret() || buffer.len() < self.selection.active { - return; - } - let prev = buffer.preceding_line_break(self.selection.active); - let next = buffer.next_line_break(self.selection.active); - self.external_selection_change = Some(Selection::new(prev, next)); - } - TextAction::Delete(movement) if self.selection.is_caret() => { - if movement == Movement::Grapheme(druid_shell::text::Direction::Upstream) { - self.backspace(buffer); - } else { - let to_delete = text::movement(movement, self.selection, &self.layout, true); - self.selection = to_delete; - self.ime_insert_text(buffer, "") - } - } - TextAction::Delete(_) => self.ime_insert_text(buffer, ""), - TextAction::DecomposingBackspace => { - tracing::warn!("Decomposing Backspace is not implemented"); - self.backspace(buffer); - } - //TextAction::UppercaseSelection - //| TextAction::LowercaseSelection - //| TextAction::TitlecaseSelection => { - //tracing::warn!("IME transformations are not implemented"); - //} - TextAction::InsertNewLine { - newline_type, - ignore_hotkey, - } => { - if self.send_notification_on_return && !ignore_hotkey { - self.external_action = Some(action); - } else if self.accepts_newlines { - self.ime_insert_text(buffer, &newline_type.to_string()); - } - } - TextAction::InsertTab { ignore_hotkey } => { - if ignore_hotkey || self.accepts_tabs { - self.ime_insert_text(buffer, "\t"); - } else if !ignore_hotkey { - self.external_action = Some(action); - } - } - TextAction::InsertBacktab => { - if !self.accepts_tabs { - self.external_action = Some(action); - } - } - TextAction::InsertSingleQuoteIgnoringSmartQuotes => self.ime_insert_text(buffer, "'"), - TextAction::InsertDoubleQuoteIgnoringSmartQuotes => self.ime_insert_text(buffer, "\""), - TextAction::Cancel if self.send_notification_on_cancel => { - self.external_action = Some(action) - } - other => tracing::warn!("unhandled IME action {:?}", other), - } - } - - /// Replace the current selection with `text`, and advance the cursor. - /// - /// This should only be called from the IME. - fn ime_insert_text(&mut self, buffer: &mut T, text: &str) { - let new_cursor_pos = self.selection.min() + text.len(); - buffer.edit(self.selection.range(), text); - self.external_selection_change = Some(Selection::caret(new_cursor_pos)); - self.scroll_to_selection_end(true); - } - - fn backspace(&mut self, buffer: &mut T) { - let to_del = if self.selection.is_caret() { - let del_start = text::offset_for_delete_backwards(&self.selection, buffer); - del_start..self.selection.anchor - } else { - self.selection.range() - }; - self.external_selection_change = Some(Selection::caret(to_del.start)); - buffer.edit(to_del, ""); - self.scroll_to_selection_end(true); - } - - fn do_mouse_down(&mut self, point: Point, mods: Modifiers, count: u8) { - let point = point - Vec2::new(self.alignment_offset, 0.0); - let pos = self.layout.text_position_for_point(point); - if mods.shift() { - self.selection.active = pos; - } else { - let Range { start, end } = self.sel_region_for_pos(pos, count); - self.selection = Selection::new(start, end); - self.drag_granularity = match count { - 2 => DragGranularity::Word { start, end }, - 3 => DragGranularity::Paragraph { start, end }, - _ => DragGranularity::Grapheme, - }; - } - } - - fn do_drag(&mut self, point: Point) { - let point = point - Vec2::new(self.alignment_offset, 0.0); - //FIXME: this should behave differently if we were double or triple clicked - let pos = self.layout.text_position_for_point(point); - let text = match self.layout.text() { - Some(text) => text, - None => return, - }; - - let (start, end) = match self.drag_granularity { - DragGranularity::Grapheme => (self.selection.anchor, pos), - DragGranularity::Word { start, end } => { - let word_range = self.word_for_pos(pos); - if pos <= start { - (end, word_range.start) - } else { - (start, word_range.end) - } - } - DragGranularity::Paragraph { start, end } => { - let par_start = text.preceding_line_break(pos); - let par_end = text.next_line_break(pos); - - if pos <= start { - (end, par_start) - } else { - (start, par_end) - } - } - }; - self.selection = Selection::new(start, end); - self.scroll_to_selection_end(false); - } - - /// Returns a line suitable for drawing a standard cursor. - pub fn cursor_line_for_text_position(&self, pos: usize) -> Line { - let line = self.layout.cursor_line_for_text_position(pos); - line + Vec2::new(self.alignment_offset, 0.0) - } - - fn sel_region_for_pos(&mut self, pos: usize, click_count: u8) -> Range { - match click_count { - 1 => pos..pos, - 2 => self.word_for_pos(pos), - _ => { - let text = match self.layout.text() { - Some(text) => text, - None => return pos..pos, - }; - let line_min = text.preceding_line_break(pos); - let line_max = text.next_line_break(pos); - line_min..line_max - } - } - } - - fn word_for_pos(&self, pos: usize) -> Range { - let layout = match self.layout.layout() { - Some(layout) => layout, - None => return pos..pos, - }; - - let line_n = layout.hit_test_text_position(pos).line; - let lm = layout.line_metric(line_n).unwrap(); - let text = layout.line_text(line_n).unwrap(); - let rel_pos = pos - lm.start_offset; - let mut range = text::movement::word_range_for_pos(text, rel_pos); - range.start += lm.start_offset; - range.end += lm.start_offset; - range - } -} - -impl EditSessionHandle { - fn new(inner: Arc>>) -> Self { - let text = inner.borrow().layout.text().cloned().unwrap(); - EditSessionHandle { text, inner } - } -} - -impl InputHandler for EditSessionHandle { - fn selection(&self) -> Selection { - self.inner.borrow().selection - } - - fn set_selection(&mut self, selection: Selection) { - self.inner.borrow_mut().external_selection_change = Some(selection); - self.inner.borrow_mut().external_scroll_to = Some(true); - } - - fn composition_range(&self) -> Option> { - self.inner.borrow().composition_range.clone() - } - - fn set_composition_range(&mut self, range: Option>) { - self.inner.borrow_mut().composition_range = range; - } - - fn is_char_boundary(&self, i: usize) -> bool { - self.text.cursor(i).is_some() - } - - fn len(&self) -> usize { - self.text.len() - } - - fn slice(&self, range: Range) -> std::borrow::Cow { - self.text.slice(range).unwrap() - } - - fn replace_range(&mut self, range: Range, text: &str) { - self.text.edit(range, text); - self.inner.borrow_mut().external_text_change = Some(self.text.clone()); - } - - fn hit_test_point(&self, point: Point) -> crate::piet::HitTestPoint { - self.inner - .borrow() - .layout - .layout() - .map(|layout| layout.hit_test_point(point)) - .unwrap_or_default() - } - - fn line_range(&self, index: usize, _affinity: druid_shell::text::Affinity) -> Range { - let inner = self.inner.borrow(); - let layout = inner.layout.layout().unwrap(); - let hit = layout.hit_test_text_position(index); - let metric = layout.line_metric(hit.line).unwrap(); - metric.range() - } - - fn bounding_box(&self) -> Option { - let size = self.inner.borrow().layout.size(); - Some(Rect::from_origin_size(self.inner.borrow().origin, size)) - } - - fn slice_bounding_box(&self, range: Range) -> Option { - let origin = self.inner.borrow().origin; - let layout = &self.inner.borrow().layout; - if range.is_empty() { - let hit = layout - .layout() - .map(|l| l.hit_test_text_position(range.start))?; - let line = layout.layout().and_then(|l| l.line_metric(hit.line))?; - let x = hit.point.x; - Some(Rect::new(x, line.y_offset, x, line.y_offset + line.height)) - } else { - layout.rects_for_range(range).first().copied() - } - .map(|rect| rect + origin.to_vec2()) - } - - fn handle_action(&mut self, action: TextAction) { - self.inner.borrow_mut().do_action(&mut self.text, action); - let text_changed = self - .inner - .borrow() - .layout - .text() - .map(|old| !old.maybe_eq(&self.text)) - .unwrap_or(true); - if text_changed { - self.inner.borrow_mut().external_text_change = Some(self.text.clone()); - } - } -} - -impl TextComponent { - /// Create new `TextComponent`. - pub fn new(text: T) -> Self { - let mut inner = EditSession { - layout: TextLayout::new(), - external_scroll_to: None, - external_text_change: None, - external_selection_change: None, - external_action: None, - pending_ime_invalidation: None, - selection: Selection::caret(0), - composition_range: None, - send_notification_on_return: false, - send_notification_on_cancel: false, - accepts_newlines: false, - accepts_tabs: false, - alignment: TextAlignment::Start, - alignment_offset: 0.0, - drag_granularity: DragGranularity::Grapheme, - origin: Point::ZERO, - }; - inner.layout.set_text(text); - - TextComponent { - edit_session: Arc::new(RefCell::new(inner)), - lock: Arc::new(Cell::new(ImeLock::None)), - has_focus: false, - } - } -} diff --git a/src/text/input_methods.rs b/src/text/input_methods.rs deleted file mode 100644 index fd0b1484..00000000 --- a/src/text/input_methods.rs +++ /dev/null @@ -1,65 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! Types related to input method editing. -//! -//! Most IME-related code is in druid-shell; these are helper types used -//! exclusively in Masonry. - -use std::rc::Rc; - -use druid_shell::text::InputHandler; - -use crate::WidgetId; - -/// A trait for input handlers registered by widgets. -/// -/// A widget registers itself as accepting text input by calling -/// [`LifeCycleCtx::register_text_input`] while handling the -/// [`LifeCycle::WidgetAdded`] event. -/// -/// The widget does not explicitly *deregister* afterwards; rather anytime -/// the widget tree changes, Masonry will call [`is_alive`] on each registered -/// `ImeHandlerRef`, and deregister those that return `false`. -/// -/// [`LifeCycle::WidgetAdded`]: crate::LifeCycle::WidgetAdded -/// [`LifeCycleCtx::register_text_input`]: crate::LifeCycleCtx::register_text_input -/// [`is_alive`]: ImeHandlerRef::is_alive -pub trait ImeHandlerRef { - /// Returns `true` if this handler is still active. - fn is_alive(&self) -> bool; - /// Mark the session as locked, and return a handle. - /// - /// The lock can be read-write or read-only, indicated by the `mutable` flag. - /// - /// if [`is_alive`] is `true`, this should always return `Some(_)`. - /// - /// [`is_alive`]: ImeHandlerRef::is_alive - fn acquire(&self, mutable: bool) -> Option>; - /// Mark the session as released. - fn release(&self) -> bool; -} - -/// A type we use to keep track of which widgets are responsible for which -/// ime sessions. -#[derive(Clone)] -pub(crate) struct TextFieldRegistration { - pub widget_id: WidgetId, - pub document: Rc, -} - -impl TextFieldRegistration { - pub fn is_alive(&self) -> bool { - self.document.is_alive() - } -} - -impl std::fmt::Debug for TextFieldRegistration { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.debug_struct("TextFieldRegistration") - .field("widget_id", &self.widget_id) - .field("is_alive", &self.document.is_alive()) - .finish() - } -} diff --git a/src/text/layout.rs b/src/text/layout.rs deleted file mode 100644 index 7f9da02b..00000000 --- a/src/text/layout.rs +++ /dev/null @@ -1,424 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! A type for laying out, drawing, and interacting with text. - -use std::ops::Range; -use std::rc::Rc; - -use super::{FontDescriptor, Link, TextStorage}; -use crate::kurbo::{Line, Point, Rect, Size}; -use crate::piet::{ - Color, PietText, PietTextLayout, Text as _, TextAlignment, TextAttribute, TextLayout as _, - TextLayoutBuilder as _, -}; -use crate::{PaintCtx, RenderContext}; - -/// A component for displaying text on screen. -/// -/// This is a type intended to be used by other widgets that display text. -/// It allows for the text itself as well as font and other styling information -/// to be set and modified. It wraps an inner layout object, and handles -/// invalidating and rebuilding it as required. -/// -/// This object is not valid until the [`rebuild_if_needed`] method has been -/// called. You should generally do this in your widget's [`layout`] method. -/// Additionally, you should call [`needs_rebuild_after_update`] -/// as part of your widget's [`update`] method; if this returns `true`, you will need -/// to call [`rebuild_if_needed`] again, generally by scheduling another [`layout`] -/// pass. -/// -/// [`layout`]: trait.Widget.html#tymethod.layout -/// [`update`]: trait.Widget.html#tymethod.update -/// [`needs_rebuild_after_update`]: #method.needs_rebuild_after_update -/// [`rebuild_if_needed`]: #method.rebuild_if_needed -#[derive(Clone)] -pub struct TextLayout { - // TODO - remove Option - text: Option, - font: FontDescriptor, - // when set, this will be used to override the size in he font descriptor. - // This provides an easy way to change only the font size, while still - // using a `FontDescriptor` in the `Env`. - text_size_override: Option, - text_color: Color, - layout: Option, - wrap_width: f64, - alignment: TextAlignment, - links: Rc<[(Rect, usize)]>, - text_is_rtl: bool, -} - -/// Metrics describing the layout text. -#[derive(Debug, Clone, Copy, Default)] -pub struct LayoutMetrics { - /// The nominal size of the layout. - pub size: Size, - /// The distance from the nominal top of the layout to the first baseline. - pub first_baseline: f64, - /// The width of the layout, inclusive of trailing whitespace. - pub trailing_whitespace_width: f64, - //TODO: add inking_rect -} - -impl TextLayout { - /// Create a new `TextLayout` object. - /// - /// You must set the text ([`set_text`]) before using this object. - /// - /// [`set_text`]: #method.set_text - pub fn new() -> Self { - TextLayout { - text: None, - font: crate::theme::UI_FONT, - text_color: crate::theme::TEXT_COLOR, - text_size_override: None, - layout: None, - wrap_width: f64::INFINITY, - alignment: Default::default(), - links: Rc::new([]), - text_is_rtl: false, - } - } - - /// Set the default text color for this layout. - pub fn set_text_color(&mut self, color: Color) { - if color != self.text_color { - self.text_color = color; - self.layout = None; - } - } - - /// Set the default font. - /// - /// The argument is a [`FontDescriptor`]. - /// - /// [`FontDescriptor`]: struct.FontDescriptor.html - pub fn set_font(&mut self, font: FontDescriptor) { - if font != self.font { - self.font = font; - self.layout = None; - self.text_size_override = None; - } - } - - /// Set the font size. - /// - /// This overrides the size in the [`FontDescriptor`] provided to [`set_font`]. - /// - /// [`set_font`]: #method.set_font.html - /// [`FontDescriptor`]: struct.FontDescriptor.html - pub fn set_text_size(&mut self, size: f64) { - if Some(&size) != self.text_size_override.as_ref() { - self.text_size_override = Some(size); - self.layout = None; - } - } - - /// Set the width at which to wrap words. - /// - /// You may pass `f64::INFINITY` to disable word wrapping - /// (the default behaviour). - pub fn set_wrap_width(&mut self, width: f64) { - let width = width.max(0.0); - // 1e-4 is an arbitrary small-enough value that we don't care to rewrap - if (width - self.wrap_width).abs() > 1e-4 { - self.wrap_width = width; - self.layout = None; - } - } - - /// Set the [`TextAlignment`] for this layout. - /// - /// [`TextAlignment`]: enum.TextAlignment.html - pub fn set_text_alignment(&mut self, alignment: TextAlignment) { - if self.alignment != alignment { - self.alignment = alignment; - self.layout = None; - } - } - - /// Returns `true` if this layout's text appears to be right-to-left. - /// - /// See [`piet::util::first_strong_rtl`] for more information. - /// - /// [`piet::util::first_strong_rtl`]: crate::piet::util::first_strong_rtl - pub fn text_is_rtl(&self) -> bool { - self.text_is_rtl - } -} - -impl TextLayout { - /// Create a new `TextLayout` with the provided text. - /// - /// This is useful when the text is not tied to application data. - pub fn from_text(text: impl Into) -> Self { - let mut this = TextLayout::new(); - this.set_text(text.into()); - this - } - - /// Returns `true` if this layout needs to be rebuilt. - /// - /// This happens (for instance) after style attributes are modified. - /// - /// This does not account for things like the text changing, handling that - /// is the responsibility of the user. - pub fn needs_rebuild(&self) -> bool { - self.layout.is_none() - } - - /// Set the text to display. - pub fn set_text(&mut self, text: T) { - if self.text.is_none() || !self.text.as_ref().unwrap().maybe_eq(&text) { - self.text_is_rtl = crate::piet::util::first_strong_rtl(text.as_str()); - self.text = Some(text); - self.layout = None; - } - } - - /// Returns the [`TextStorage`] backing this layout, if it exists. - pub fn text(&self) -> Option<&T> { - self.text.as_ref() - } - - /// Returns the length of the [`TextStorage`] backing this layout, if it exists. - pub fn text_len(&self) -> usize { - if let Some(text) = &self.text { - text.as_str().len() - } else { - 0 - } - } - - /// Returns the inner Piet [`TextLayout`] type. - /// - /// [`TextLayout`]: ./piet/trait.TextLayout.html - pub fn layout(&self) -> Option<&PietTextLayout> { - self.layout.as_ref() - } - - /// The size of the laid-out text. - /// - /// This is not meaningful until [`rebuild_if_needed`] has been called. - /// - /// [`rebuild_if_needed`]: #method.rebuild_if_needed - pub fn size(&self) -> Size { - self.layout - .as_ref() - .map(|layout| layout.size()) - .unwrap_or_default() - } - - /// Return the text's [`LayoutMetrics`]. - /// - /// This is not meaningful until [`rebuild_if_needed`] has been called. - /// - /// [`rebuild_if_needed`]: #method.rebuild_if_needed - /// [`LayoutMetrics`]: struct.LayoutMetrics.html - pub fn layout_metrics(&self) -> LayoutMetrics { - debug_assert!( - self.layout.is_some(), - "TextLayout::layout_metrics called without rebuilding layout object. Text was '{}'", - self.text().as_ref().map(|s| s.as_str()).unwrap_or_default() - ); - - if let Some(layout) = self.layout.as_ref() { - let first_baseline = layout.line_metric(0).unwrap().baseline; - let size = layout.size(); - LayoutMetrics { - size, - first_baseline, - trailing_whitespace_width: layout.trailing_whitespace_width(), - } - } else { - LayoutMetrics::default() - } - } - - /// For a given `Point` (relative to this object's origin), returns index - /// into the underlying text of the nearest grapheme boundary. - pub fn text_position_for_point(&self, point: Point) -> usize { - self.layout - .as_ref() - .map(|layout| layout.hit_test_point(point).idx) - .unwrap_or_default() - } - - /// Given the utf-8 position of a character boundary in the underlying text, - /// return the `Point` (relative to this object's origin) representing the - /// boundary of the containing grapheme. - /// - /// # Panics - /// - /// Panics if `text_pos` is not a character boundary. - pub fn point_for_text_position(&self, text_pos: usize) -> Point { - self.layout - .as_ref() - .map(|layout| layout.hit_test_text_position(text_pos).point) - .unwrap_or_default() - } - - /// Given a utf-8 range in the underlying text, return a `Vec` of `Rect`s - /// representing the nominal bounding boxes of the text in that range. - /// - /// # Panics - /// - /// Panics if the range start or end is not a character boundary. - pub fn rects_for_range(&self, range: Range) -> Vec { - self.layout - .as_ref() - .map(|layout| layout.rects_for_range(range)) - .unwrap_or_default() - } - - /// Return a line suitable for underlining a range of text. - /// - /// This is really only intended to be used to indicate the composition - /// range while IME is active. - /// - /// range is expected to be on a single visual line. - pub fn underline_for_range(&self, range: Range) -> Line { - self.layout - .as_ref() - .map(|layout| { - let p1 = layout.hit_test_text_position(range.start); - let p2 = layout.hit_test_text_position(range.end); - let line_metric = layout.line_metric(p1.line).unwrap(); - // heuristic; 1/5 of height is a rough guess at the descender pos? - let y_pos = line_metric.baseline + (line_metric.height / 5.0); - Line::new((p1.point.x, y_pos), (p2.point.x, y_pos)) - }) - .unwrap_or_else(|| Line::new(Point::ZERO, Point::ZERO)) - } - - /// Given the utf-8 position of a character boundary in the underlying text, - /// return a `Line` suitable for drawing a vertical cursor at that boundary. - pub fn cursor_line_for_text_position(&self, text_pos: usize) -> Line { - self.layout - .as_ref() - .map(|layout| { - let pos = layout.hit_test_text_position(text_pos); - let line_metrics = layout.line_metric(pos.line).unwrap(); - let p1 = (pos.point.x, line_metrics.y_offset); - let p2 = (pos.point.x, (line_metrics.y_offset + line_metrics.height)); - Line::new(p1, p2) - }) - .unwrap_or_else(|| Line::new(Point::ZERO, Point::ZERO)) - } - - /// Returns the [`Link`] at the provided point (relative to the layout's origin) if one exists. - /// - /// This can be used both for hit-testing (deciding whether to change the mouse cursor, - /// or performing some other action when hovering) as well as for retrieving a [`Link`] - /// on click. - /// - /// [`Link`]: super::attribute::Link - pub fn link_for_pos(&self, pos: Point) -> Option<&Link> { - let (_, i) = self - .links - .iter() - .rfind(|(hit_box, _)| hit_box.contains(pos))?; - - let text = self.text()?; - text.links().get(*i) - } - - /// Rebuild the inner layout as needed. - /// - /// This `TextLayout` object manages a lower-level layout object that may - /// need to be rebuilt in response to changes to the text or attributes - /// like the font. - /// - /// This method should be called whenever any of these things may have changed. - /// A simple way to ensure this is correct is to always call this method - /// as part of your widget's [`layout`] method. - /// - /// [`layout`]: trait.Widget.html#method.layout - pub fn rebuild_if_needed(&mut self, factory: &mut PietText) { - if let Some(text) = &self.text { - if self.layout.is_none() { - let font = self.font.clone(); - let color = self.text_color; - let size_override = self.text_size_override; - - let descriptor = if let Some(size) = size_override { - font.with_size(size) - } else { - font - }; - - let builder = factory - .new_text_layout(text.clone()) - .max_width(self.wrap_width) - .alignment(self.alignment) - .font(descriptor.family.clone(), descriptor.size) - .default_attribute(descriptor.weight) - .default_attribute(descriptor.style) - .default_attribute(TextAttribute::TextColor(color)); - let layout = text.add_attributes(builder).build().unwrap(); - - self.links = text - .links() - .iter() - .enumerate() - .flat_map(|(i, link)| { - layout - .rects_for_range(link.range()) - .into_iter() - .map(move |rect| (rect, i)) - }) - .collect(); - - self.layout = Some(layout); - } - } - } - - /// Draw the layout at the provided `Point`. - /// - /// The origin of the layout is the top-left corner. - /// - /// You must call [`rebuild_if_needed`] at some point before you first - /// call this method. - /// - /// [`rebuild_if_needed`]: #method.rebuild_if_needed - pub fn draw(&self, ctx: &mut PaintCtx, point: impl Into) { - debug_assert!( - self.layout.is_some(), - "TextLayout::draw called without rebuilding layout object. Text was '{}'", - self.text - .as_ref() - .map(|t| t.as_str()) - .unwrap_or("layout is missing text") - ); - if let Some(layout) = self.layout.as_ref() { - ctx.draw_text(layout, point); - } - } -} - -impl std::fmt::Debug for TextLayout { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.debug_struct("TextLayout") - .field("font", &self.font) - .field("text_size_override", &self.text_size_override) - .field("text_color", &self.text_color) - .field( - "layout", - if self.layout.is_some() { - &"Some" - } else { - &"None" - }, - ) - .finish() - } -} - -impl Default for TextLayout { - fn default() -> Self { - Self::new() - } -} diff --git a/src/text/mod.rs b/src/text/mod.rs deleted file mode 100644 index 1962a276..00000000 --- a/src/text/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! Editing and displaying text. - -// TODO -#![allow(clippy::all)] - -mod attribute; -mod backspace; -mod editable_text; -mod font_descriptor; - -mod input_component; -mod input_methods; -mod layout; -mod movement; -mod rich_text; -mod storage; - -pub use druid_shell::text::{ - Action as TextAction, Affinity, Direction, Event as ImeInvalidation, InputHandler, Movement, - Selection, VerticalMovement, WritingDirection, -}; -pub use input_component::{EditSession, TextComponent}; -pub use input_methods::ImeHandlerRef; -pub(crate) use input_methods::TextFieldRegistration; -pub use rich_text::{AttributesAdder, RichText, RichTextBuilder}; -pub use storage::{ArcStr, TextStorage}; - -pub use self::attribute::{Attribute, AttributeSpans, Link}; -pub use self::backspace::offset_for_delete_backwards; -pub use self::editable_text::{EditableText, EditableTextCursor, StringCursor}; -pub use self::font_descriptor::FontDescriptor; -pub use self::layout::{LayoutMetrics, TextLayout}; -pub use self::movement::movement; -pub use crate::piet::{FontFamily, FontStyle, FontWeight, TextAlignment}; diff --git a/src/text/movement.rs b/src/text/movement.rs deleted file mode 100644 index a7a278c1..00000000 --- a/src/text/movement.rs +++ /dev/null @@ -1,195 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! Text editing movements. - -use std::ops::Range; - -use unicode_segmentation::UnicodeSegmentation; - -use crate::kurbo::Point; -use crate::piet::TextLayout as _; -use crate::text::{ - EditableText, Movement, Selection, TextLayout, TextStorage, VerticalMovement, WritingDirection, -}; - -/// Compute the result of a [`Movement`] on a [`Selection`]. -/// -/// returns a new selection representing the state after the movement. -/// -/// If `modify` is true, only the 'active' edge (the `end`) of the selection -/// should be changed; this is the case when the user moves with the shift -/// key pressed. -pub fn movement( - m: Movement, - s: Selection, - layout: &TextLayout, - modify: bool, -) -> Selection { - let (text, layout) = match (layout.text(), layout.layout()) { - (Some(text), Some(layout)) => (text, layout), - _ => { - debug_assert!(false, "movement() called before layout rebuild"); - return s; - } - }; - - let writing_direction = if crate::piet::util::first_strong_rtl(text.as_str()) { - WritingDirection::RightToLeft - } else { - WritingDirection::LeftToRight - }; - - let (offset, h_pos) = match m { - Movement::Grapheme(d) if d.is_upstream_for_direction(writing_direction) => { - if s.is_caret() || modify { - text.prev_grapheme_offset(s.active) - .map(|off| (off, None)) - .unwrap_or((0, s.h_pos)) - } else { - (s.min(), None) - } - } - Movement::Grapheme(_) => { - if s.is_caret() || modify { - text.next_grapheme_offset(s.active) - .map(|off| (off, None)) - .unwrap_or((s.active, s.h_pos)) - } else { - (s.max(), None) - } - } - Movement::Vertical(VerticalMovement::LineUp) => { - let cur_pos = layout.hit_test_text_position(s.active); - let h_pos = s.h_pos.unwrap_or(cur_pos.point.x); - if cur_pos.line == 0 { - (0, Some(h_pos)) - } else { - let lm = layout.line_metric(cur_pos.line).unwrap(); - let point_above = Point::new(h_pos, cur_pos.point.y - lm.height); - let up_pos = layout.hit_test_point(point_above); - if up_pos.is_inside { - (up_pos.idx, Some(h_pos)) - } else { - // because we can't specify affinity, moving up when h_pos - // is wider than both the current line and the previous line - // can result in a cursor position at the visual start of the - // current line; so we handle this as a special-case. - let lm_prev = layout.line_metric(cur_pos.line.saturating_sub(1)).unwrap(); - let up_pos = lm_prev.end_offset - lm_prev.trailing_whitespace; - (up_pos, Some(h_pos)) - } - } - } - Movement::Vertical(VerticalMovement::LineDown) => { - let cur_pos = layout.hit_test_text_position(s.active); - let h_pos = s.h_pos.unwrap_or(cur_pos.point.x); - if cur_pos.line == layout.line_count() - 1 { - (text.len(), Some(h_pos)) - } else { - let lm = layout.line_metric(cur_pos.line).unwrap(); - // may not work correctly for point sizes below 1.0 - let y_below = lm.y_offset + lm.height + 1.0; - let point_below = Point::new(h_pos, y_below); - let up_pos = layout.hit_test_point(point_below); - (up_pos.idx, Some(point_below.x)) - } - } - Movement::Vertical(VerticalMovement::DocumentStart) => (0, None), - Movement::Vertical(VerticalMovement::DocumentEnd) => (text.len(), None), - - Movement::ParagraphStart => (text.preceding_line_break(s.active), None), - Movement::ParagraphEnd => (text.next_line_break(s.active), None), - - Movement::Line(d) => { - let hit = layout.hit_test_text_position(s.active); - let lm = layout.line_metric(hit.line).unwrap(); - let offset = if d.is_upstream_for_direction(writing_direction) { - lm.start_offset - } else { - lm.end_offset - lm.trailing_whitespace - }; - (offset, None) - } - Movement::Word(d) if d.is_upstream_for_direction(writing_direction) => { - let offset = if s.is_caret() || modify { - text.prev_word_offset(s.active).unwrap_or(0) - } else { - s.min() - }; - (offset, None) - } - Movement::Word(_) => { - let offset = if s.is_caret() || modify { - text.next_word_offset(s.active).unwrap_or(s.active) - } else { - s.max() - }; - (offset, None) - } - - // These two are not handled; they require knowledge of the size - // of the viewport. - Movement::Vertical(VerticalMovement::PageDown) - | Movement::Vertical(VerticalMovement::PageUp) => (s.active, s.h_pos), - other => { - tracing::warn!("unhandled movement {:?}", other); - (s.anchor, s.h_pos) - } - }; - - let start = if modify { s.anchor } else { offset }; - Selection::new(start, offset).with_h_pos(h_pos) -} - -/// Given a position in some text, return the containing word boundaries. -/// -/// The returned range may not necessary be a 'word'; for instance it could be -/// the sequence of whitespace between two words. -/// -/// If the position is on a word boundary, that will be considered the start -/// of the range. -/// -/// This uses Unicode word boundaries, as defined in [UAX#29]. -/// -/// [UAX#29]: http://www.unicode.org/reports/tr29/ -pub(crate) fn word_range_for_pos(text: &str, pos: usize) -> Range { - text.split_word_bound_indices() - .map(|(ix, word)| ix..(ix + word.len())) - .find(|range| range.contains(&pos)) - .unwrap_or(pos..pos) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn word_range_simple() { - assert_eq!(word_range_for_pos("hello world", 3), 0..5); - assert_eq!(word_range_for_pos("hello world", 8), 6..11); - } - - #[test] - fn word_range_whitespace() { - assert_eq!(word_range_for_pos("hello world", 5), 5..6); - } - - #[test] - fn word_range_rtl() { - let rtl = "مرحبا بالعالم"; - assert_eq!(word_range_for_pos(rtl, 5), 0..10); - assert_eq!(word_range_for_pos(rtl, 16), 11..25); - assert_eq!(word_range_for_pos(rtl, 10), 10..11); - } - - #[test] - fn word_range_mixed() { - let mixed = "hello مرحبا بالعالم world"; - assert_eq!(word_range_for_pos(mixed, 3), 0..5); - assert_eq!(word_range_for_pos(mixed, 8), 6..16); - assert_eq!(word_range_for_pos(mixed, 19), 17..31); - assert_eq!(word_range_for_pos(mixed, 36), 32..37); - } -} diff --git a/src/text/rich_text.rs b/src/text/rich_text.rs deleted file mode 100644 index 3f48915c..00000000 --- a/src/text/rich_text.rs +++ /dev/null @@ -1,231 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! Rich text with style spans. - -use std::ops::{Range, RangeBounds}; -use std::sync::Arc; - -use super::attribute::Link; -use super::{Attribute, AttributeSpans, FontDescriptor, TextStorage}; -use crate::piet::{ - util, Color, FontFamily, FontStyle, FontWeight, PietTextLayoutBuilder, TextLayoutBuilder, - TextStorage as PietTextStorage, -}; -use crate::ArcStr; - -/// Text with optional style spans. -#[derive(Clone, Debug)] -pub struct RichText { - buffer: ArcStr, - attrs: Arc, - links: Arc<[Link]>, -} - -impl RichText { - /// Create a new `RichText` object with the provided text. - pub fn new(buffer: ArcStr) -> Self { - RichText::new_with_attributes(buffer, Default::default()) - } - - /// Create a new `RichText`, providing explicit attributes. - pub fn new_with_attributes(buffer: ArcStr, attributes: AttributeSpans) -> Self { - RichText { - buffer, - attrs: Arc::new(attributes), - links: Arc::new([]), - } - } - - /// Builder-style method for adding an [`Attribute`] to a range of text. - /// - /// [`Attribute`]: enum.Attribute.html - pub fn with_attribute(mut self, range: impl RangeBounds, attr: Attribute) -> Self { - self.add_attribute(range, attr); - self - } - - /// The length of the buffer, in utf8 code units. - pub fn len(&self) -> usize { - self.buffer.len() - } - - /// Returns `true` if the underlying buffer is empty. - pub fn is_empty(&self) -> bool { - self.buffer.is_empty() - } - - /// Add an [`Attribute`] to the provided range of text. - /// - /// [`Attribute`]: enum.Attribute.html - pub fn add_attribute(&mut self, range: impl RangeBounds, attr: Attribute) { - let range = util::resolve_range(range, self.buffer.len()); - Arc::make_mut(&mut self.attrs).add(range, attr); - } -} - -impl PietTextStorage for RichText { - fn as_str(&self) -> &str { - self.buffer.as_str() - } -} - -impl TextStorage for RichText { - fn add_attributes(&self, mut builder: PietTextLayoutBuilder) -> PietTextLayoutBuilder { - for (range, attr) in self.attrs.to_piet_attrs() { - builder = builder.range_attribute(range, attr); - } - builder - } - - fn links(&self) -> &[Link] { - &self.links - } - - fn maybe_eq(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.buffer, &other.buffer) - && Arc::ptr_eq(&self.attrs, &other.attrs) - && Arc::ptr_eq(&self.links, &other.links) - } -} - -/// A builder for creating [`RichText`] objects. -/// -/// This builder allows you to construct a [`RichText`] object by building up a sequence -/// of styled sub-strings; first you [`push`](RichTextBuilder::push) a `&str` onto the string, -/// and then you can optionally add styles to that text via the returned [`AttributesAdder`] -/// object. -/// -/// # Example -/// ``` -/// # use masonry::text::{Attribute, RichTextBuilder}; -/// # use masonry::Color; -/// # use masonry::piet::FontWeight; -/// let mut builder = RichTextBuilder::new(); -/// builder.push("Hello "); -/// builder.push("World!").weight(FontWeight::BOLD); -/// -/// // Can also use write! -/// write!(builder, "Here is your number: {}", 1).underline(true).text_color(Color::RED); -/// -/// let rich_text = builder.build(); -/// ``` -/// -/// [`RichText`]: RichText -#[derive(Default)] -pub struct RichTextBuilder { - buffer: String, - attrs: AttributeSpans, - links: Vec, -} - -impl RichTextBuilder { - /// Create a new `RichTextBuilder`. - pub fn new() -> Self { - Self::default() - } - - /// Append a `&str` to the end of the text. - /// - /// This method returns a [`AttributesAdder`] that can be used to style the newly - /// added string slice. - pub fn push(&mut self, string: &str) -> AttributesAdder { - let range = self.buffer.len()..(self.buffer.len() + string.len()); - self.buffer.push_str(string); - self.add_attributes_for_range(range) - } - - /// Glue for usage of the write! macro. - /// - /// This method should generally not be invoked manually, but rather through the write! macro itself. - #[doc(hidden)] - pub fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> AttributesAdder { - use std::fmt::Write; - let start = self.buffer.len(); - self.buffer - .write_fmt(fmt) - .expect("a formatting trait implementation returned an error"); - self.add_attributes_for_range(start..self.buffer.len()) - } - - /// Get an [`AttributesAdder`] for the given range. - /// - /// This can be used to modify styles for a given range after it has been added. - pub fn add_attributes_for_range(&mut self, range: impl RangeBounds) -> AttributesAdder { - let range = util::resolve_range(range, self.buffer.len()); - AttributesAdder { - rich_text_builder: self, - range, - } - } - - /// Build the `RichText`. - pub fn build(self) -> RichText { - RichText { - buffer: self.buffer.into(), - attrs: self.attrs.into(), - links: self.links.into(), - } - } -} - -/// Adds Attributes to the text. -/// -/// See also: [`RichTextBuilder`](RichTextBuilder) -pub struct AttributesAdder<'a> { - rich_text_builder: &'a mut RichTextBuilder, - range: Range, -} - -impl AttributesAdder<'_> { - /// Add the given attribute. - pub fn add_attr(&mut self, attr: Attribute) -> &mut Self { - self.rich_text_builder.attrs.add(self.range.clone(), attr); - self - } - - /// Add a font size attribute. - pub fn size(&mut self, size: impl Into) -> &mut Self { - self.add_attr(Attribute::size(size)); - self - } - - /// Add a forground color attribute. - pub fn text_color(&mut self, color: impl Into) -> &mut Self { - self.add_attr(Attribute::text_color(color)); - self - } - - /// Add a font family attribute. - pub fn font_family(&mut self, family: FontFamily) -> &mut Self { - self.add_attr(Attribute::font_family(family)); - self - } - - /// Add a `FontWeight` attribute. - pub fn weight(&mut self, weight: FontWeight) -> &mut Self { - self.add_attr(Attribute::weight(weight)); - self - } - - /// Add a `FontStyle` attribute. - pub fn style(&mut self, style: FontStyle) -> &mut Self { - self.add_attr(Attribute::style(style)); - self - } - - /// Add a underline attribute. - pub fn underline(&mut self, underline: bool) -> &mut Self { - self.add_attr(Attribute::underline(underline)); - self - } - - /// Add a `FontDescriptor` attribute. - pub fn font_descriptor(&mut self, font: impl Into) -> &mut Self { - self.add_attr(Attribute::font_descriptor(font)); - self - } - - //pub fn link(&mut self, command: impl Into) -> &mut Self; -} diff --git a/src/text/storage.rs b/src/text/storage.rs deleted file mode 100644 index d63d3812..00000000 --- a/src/text/storage.rs +++ /dev/null @@ -1,63 +0,0 @@ -// This software is licensed under Apache License 2.0 and distributed on an -// "as-is" basis without warranties of any kind. See the LICENSE file for -// details. - -//! Storing text. - -use std::sync::Arc; - -use super::attribute::Link; -use crate::piet::{PietTextLayoutBuilder, TextStorage as PietTextStorage}; - -/// A type that represents text that can be displayed. -pub trait TextStorage: PietTextStorage + Clone { - /// If this TextStorage object manages style spans, it should implement - /// this method and update the provided builder with its spans, as required. - #[allow(unused_variables)] - fn add_attributes(&self, builder: PietTextLayoutBuilder) -> PietTextLayoutBuilder { - builder - } - - /// Any additional [`Link`] attributes on this text. - /// - /// If this `TextStorage` object manages link attributes, it should implement this - /// method and return any attached [`Link`]s. - /// - /// Unlike other attributes, links are managed in Masonry, not in [`piet`]; as such they - /// require a separate API. - /// - /// [`Link`]: super::attribute::Link - /// [`piet`]: https://docs.rs/piet - fn links(&self) -> &[Link] { - &[] - } - - /// Determines quickly whether two text objects have the same content. - /// - /// To allow for faster checks, this method is allowed to return false negatives. - fn maybe_eq(&self, other: &Self) -> bool; -} - -/// A reference counted string slice. -/// -/// This is a data-friendly way to represent strings in Masonry. Unlike `String` -/// it cannot be mutated, but unlike `String` it can be cheaply cloned. -pub type ArcStr = Arc; - -impl TextStorage for ArcStr { - fn maybe_eq(&self, other: &Self) -> bool { - self == other - } -} - -impl TextStorage for String { - fn maybe_eq(&self, other: &Self) -> bool { - self == other - } -} - -impl TextStorage for Arc { - fn maybe_eq(&self, other: &Self) -> bool { - self == other - } -} diff --git a/src/text_helpers.rs b/src/text_helpers.rs new file mode 100644 index 00000000..c16e6529 --- /dev/null +++ b/src/text_helpers.rs @@ -0,0 +1,91 @@ +//! Helper functions for working with text in Masonry. + +use parley::Layout; +use vello::{ + kurbo::Affine, + peniko::{Brush, Fill}, + Scene, +}; + +use crate::WidgetId; + +/// A reference counted string slice. +/// +/// This is a data-friendly way to represent strings in Masonry. Unlike `String` +/// it cannot be mutated, but unlike `String` it can be cheaply cloned. +pub type ArcStr = std::sync::Arc; + +/// A type we use to keep track of which widgets are responsible for which +/// ime sessions. +#[derive(Clone, Debug)] +#[allow(unused)] +pub(crate) struct TextFieldRegistration { + pub widget_id: WidgetId, +} + +// Copy-pasted from druid_shell +/// An event representing an application-initiated change in [`InputHandler`] +/// state. +/// +/// When we change state that may have previously been retrieved from an +/// [`InputHandler`], we notify the platform so that it can invalidate any +/// data if necessary. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum ImeChangeSignal { + /// Indicates the value returned by `InputHandler::selection` may have changed. + SelectionChanged, + + /// Indicates the values returned by one or more of these methods may have changed: + /// - `InputHandler::hit_test_point` + /// - `InputHandler::line_range` + /// - `InputHandler::bounding_box` + /// - `InputHandler::slice_bounding_box` + LayoutChanged, + + /// Indicates any value returned from any `InputHandler` method may have changed. + Reset, +} + +/// A function that renders laid out glyphs to a [Scene]. +pub fn render_text(scene: &mut Scene, transform: Affine, layout: &Layout) { + for line in layout.lines() { + for glyph_run in line.glyph_runs() { + let mut x = glyph_run.offset(); + let y = glyph_run.baseline(); + let run = glyph_run.run(); + let font = run.font(); + let font_size = run.font_size(); + let synthesis = run.synthesis(); + let glyph_xform = synthesis + .skew() + .map(|angle| Affine::skew(angle.to_radians().tan() as f64, 0.0)); + let style = glyph_run.style(); + let coords = run + .normalized_coords() + .iter() + .map(|coord| vello::skrifa::instance::NormalizedCoord::from_bits(*coord)) + .collect::>(); + scene + .draw_glyphs(font) + .brush(&style.brush) + .transform(transform) + .glyph_transform(glyph_xform) + .font_size(font_size) + .normalized_coords(&coords) + .draw( + Fill::NonZero, + glyph_run.glyphs().map(|glyph| { + let gx = x + glyph.x; + let gy = y - glyph.y; + x += glyph.advance; + vello::glyph::Glyph { + id: glyph.id as _, + x: gx, + y: gy, + } + }), + ); + } + } +} diff --git a/src/theme.rs b/src/theme.rs index e6dc9ab7..67bbfb56 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -6,8 +6,8 @@ #![allow(missing_docs)] -use crate::piet::{Color, FontFamily, FontStyle, FontWeight}; -use crate::text::FontDescriptor; +use vello::peniko::Color; + use crate::Insets; // Colors are from https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors/ @@ -28,14 +28,14 @@ pub const DISABLED_FOREGROUND_LIGHT: Color = Color::rgb8(0x89, 0x89, 0x89); pub const DISABLED_FOREGROUND_DARK: Color = Color::rgb8(0x6f, 0x6f, 0x6f); pub const BUTTON_DARK: Color = Color::BLACK; pub const BUTTON_LIGHT: Color = Color::rgb8(0x21, 0x21, 0x21); -pub const DISABLED_BUTTON_DARK: Color = Color::grey8(0x28); -pub const DISABLED_BUTTON_LIGHT: Color = Color::grey8(0x38); +pub const DISABLED_BUTTON_DARK: Color = Color::rgb8(0x28, 0x28, 0x28); +pub const DISABLED_BUTTON_LIGHT: Color = Color::rgb8(0x38, 0x38, 0x38); pub const BUTTON_BORDER_RADIUS: f64 = 4.; pub const BUTTON_BORDER_WIDTH: f64 = 2.; pub const BORDER_DARK: Color = Color::rgb8(0x3a, 0x3a, 0x3a); pub const BORDER_LIGHT: Color = Color::rgb8(0xa1, 0xa1, 0xa1); pub const SELECTED_TEXT_BACKGROUND_COLOR: Color = Color::rgb8(0x43, 0x70, 0xA8); -pub const SELECTED_TEXT_INACTIVE_BACKGROUND_COLOR: Color = Color::grey8(0x74); +pub const SELECTED_TEXT_INACTIVE_BACKGROUND_COLOR: Color = Color::rgb8(0x74, 0x74, 0x74); pub const SELECTION_TEXT_COLOR: Color = Color::rgb8(0x00, 0x00, 0x00); pub const CURSOR_COLOR: Color = Color::WHITE; pub const TEXT_SIZE_NORMAL: f64 = 15.0; @@ -58,13 +58,6 @@ pub const SCROLLBAR_EDGE_WIDTH: f64 = 1.; pub const WIDGET_PADDING_VERTICAL: f64 = 10.0; pub const WIDGET_PADDING_HORIZONTAL: f64 = 8.0; pub const WIDGET_CONTROL_COMPONENT_PADDING: f64 = 4.0; -pub const UI_FONT: FontDescriptor = FontDescriptor::new(FontFamily::SYSTEM_UI).with_size(15.0); -pub const UI_FONT_BOLD: FontDescriptor = FontDescriptor::new(FontFamily::SYSTEM_UI) - .with_weight(FontWeight::BOLD) - .with_size(15.0); -pub const UI_FONT_ITALIC: FontDescriptor = FontDescriptor::new(FontFamily::SYSTEM_UI) - .with_style(FontStyle::Italic) - .with_size(15.0); static DEBUG_COLOR: &[Color] = &[ Color::rgb8(230, 25, 75), diff --git a/src/widget/align.rs b/src/widget/align.rs index c0a27ef4..3b86692a 100644 --- a/src/widget/align.rs +++ b/src/widget/align.rs @@ -11,11 +11,13 @@ use smallvec::{smallvec, SmallVec}; use tracing::{trace, trace_span, Span}; +use vello::Scene; +use crate::paint_scene_helpers::UnitPoint; use crate::widget::{WidgetPod, WidgetRef}; use crate::{ - BoxConstraints, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Rect, Size, - StatusChange, UnitPoint, Widget, + BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, PointerEvent, Rect, + Size, StatusChange, TextEvent, Widget, }; // TODO - Have child widget type as generic argument @@ -80,8 +82,12 @@ impl Align { } impl Widget for Align { - fn on_event(&mut self, ctx: &mut EventCtx, event: &Event) { - self.child.on_event(ctx, event) + fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) { + self.child.on_pointer_event(ctx, event) + } + + fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) { + self.child.on_text_event(ctx, event) } fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { @@ -137,8 +143,8 @@ impl Widget for Align { my_size } - fn paint(&mut self, ctx: &mut PaintCtx) { - self.child.paint(ctx); + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + self.child.paint(ctx, scene); } fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { diff --git a/src/widget/button.rs b/src/widget/button.rs index 8a0e3217..70d679b4 100644 --- a/src/widget/button.rs +++ b/src/widget/button.rs @@ -6,12 +6,14 @@ use smallvec::SmallVec; use tracing::{trace, trace_span, Span}; +use vello::Scene; use crate::action::Action; +use crate::paint_scene_helpers::{fill_lin_gradient, stroke, UnitPoint}; use crate::widget::{Label, WidgetMut, WidgetPod, WidgetRef}; use crate::{ - theme, ArcStr, BoxConstraints, Event, EventCtx, Insets, LayoutCtx, LifeCycle, LifeCycleCtx, - LinearGradient, PaintCtx, RenderContext, Size, StatusChange, UnitPoint, Widget, + theme, ArcStr, BoxConstraints, EventCtx, Insets, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + PointerEvent, Size, StatusChange, TextEvent, Widget, }; // the minimum padding added to a button. @@ -50,7 +52,7 @@ impl Button { /// use masonry::Color; /// use masonry::widget::{Button, Label}; /// - /// let label = Label::new("Increment").with_text_color(Color::grey(0.5)); + /// let label = Label::new("Increment").with_text_color(Color::rgb(0.5, 0.5, 0.5)); /// let button = Button::from_label(label); /// ``` pub fn from_label(label: Label) -> Button { @@ -60,28 +62,28 @@ impl Button { } } -impl<'a, 'b> ButtonMut<'a, 'b> { +impl<'a> ButtonMut<'a> { /// Set the text. pub fn set_text(&mut self, new_text: impl Into) { self.label_mut().set_text(new_text.into()); } - pub fn label_mut(&mut self) -> WidgetMut<'_, 'b, Label> { + pub fn label_mut(&mut self) -> WidgetMut<'_, Label> { self.ctx.get_mut(&mut self.widget.label) } } impl Widget for Button { - fn on_event(&mut self, ctx: &mut EventCtx, event: &Event) { + fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) { match event { - Event::MouseDown(_) => { + PointerEvent::PointerDown(_, _) => { if !ctx.is_disabled() { ctx.set_active(true); ctx.request_paint(); trace!("Button {:?} pressed", ctx.widget_id()); } } - Event::MouseUp(_) => { + PointerEvent::PointerUp(_, _) => { if ctx.is_active() && !ctx.is_disabled() { ctx.submit_action(Action::ButtonPressed); ctx.request_paint(); @@ -91,6 +93,11 @@ impl Widget for Button { } _ => (), } + self.label.on_pointer_event(ctx, event); + } + + fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) { + self.label.on_text_event(ctx, event); } fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, _event: &StatusChange) { @@ -126,7 +133,7 @@ impl Widget for Button { button_size } - fn paint(&mut self, ctx: &mut PaintCtx) { + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { let is_active = ctx.is_active() && !ctx.is_disabled(); let is_hot = ctx.is_hot(); let size = ctx.size(); @@ -138,23 +145,11 @@ impl Widget for Button { .to_rounded_rect(theme::BUTTON_BORDER_RADIUS); let bg_gradient = if ctx.is_disabled() { - LinearGradient::new( - UnitPoint::TOP, - UnitPoint::BOTTOM, - (theme::DISABLED_BUTTON_LIGHT, theme::DISABLED_BUTTON_DARK), - ) + [theme::DISABLED_BUTTON_LIGHT, theme::DISABLED_BUTTON_DARK] } else if is_active { - LinearGradient::new( - UnitPoint::TOP, - UnitPoint::BOTTOM, - (theme::BUTTON_DARK, theme::BUTTON_LIGHT), - ) + [theme::BUTTON_DARK, theme::BUTTON_LIGHT] } else { - LinearGradient::new( - UnitPoint::TOP, - UnitPoint::BOTTOM, - (theme::BUTTON_LIGHT, theme::BUTTON_DARK), - ) + [theme::BUTTON_LIGHT, theme::BUTTON_DARK] }; let border_color = if is_hot && !ctx.is_disabled() { @@ -163,10 +158,16 @@ impl Widget for Button { theme::BORDER_DARK }; - ctx.stroke(rounded_rect, &border_color, stroke_width); - ctx.fill(rounded_rect, &bg_gradient); + stroke(scene, &rounded_rect, border_color, stroke_width); + fill_lin_gradient( + scene, + &rounded_rect, + bg_gradient, + UnitPoint::TOP, + UnitPoint::BOTTOM, + ); - self.label.paint(ctx); + self.label.paint(ctx, scene); } fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { diff --git a/src/widget/checkbox.rs b/src/widget/checkbox.rs index 53555eba..97f41487 100644 --- a/src/widget/checkbox.rs +++ b/src/widget/checkbox.rs @@ -4,16 +4,18 @@ //! A checkbox widget. +use kurbo::{Affine, Stroke}; use smallvec::SmallVec; use tracing::{trace, trace_span, Span}; +use vello::Scene; use crate::action::Action; -use crate::kurbo::{BezPath, Size}; -use crate::piet::{LineCap, LineJoin, LinearGradient, RenderContext, StrokeStyle, UnitPoint}; +use crate::kurbo::{BezPath, Cap, Join, Size}; +use crate::paint_scene_helpers::{fill_lin_gradient, stroke, UnitPoint}; use crate::widget::{Label, WidgetMut, WidgetRef}; use crate::{ - theme, ArcStr, BoxConstraints, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, - StatusChange, Widget, WidgetPod, + theme, ArcStr, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + PointerEvent, StatusChange, TextEvent, Widget, WidgetPod, }; /// A checkbox that can be toggled. @@ -42,7 +44,7 @@ impl Checkbox { } } -impl<'a, 'b> CheckboxMut<'a, 'b> { +impl<'a> CheckboxMut<'a> { pub fn set_checked(&mut self, checked: bool) { self.widget.checked = checked; self.ctx.request_paint(); @@ -53,22 +55,22 @@ impl<'a, 'b> CheckboxMut<'a, 'b> { self.label_mut().set_text(new_text.into()); } - pub fn label_mut(&mut self) -> WidgetMut<'_, 'b, Label> { + pub fn label_mut(&mut self) -> WidgetMut<'_, Label> { self.ctx.get_mut(&mut self.widget.label) } } impl Widget for Checkbox { - fn on_event(&mut self, ctx: &mut EventCtx, event: &Event) { + fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) { match event { - Event::MouseDown(_) => { + PointerEvent::PointerDown(_, _) => { if !ctx.is_disabled() { ctx.set_active(true); ctx.request_paint(); trace!("Checkbox {:?} pressed", ctx.widget_id()); } } - Event::MouseUp(_) => { + PointerEvent::PointerUp(_, _) => { if ctx.is_active() && !ctx.is_disabled() { if ctx.is_hot() { self.checked = !self.checked; @@ -83,6 +85,8 @@ impl Widget for Checkbox { } } + fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} + fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, _event: &StatusChange) { ctx.request_paint(); } @@ -109,7 +113,7 @@ impl Widget for Checkbox { our_size } - fn paint(&mut self, ctx: &mut PaintCtx) { + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { let check_size = theme::BASIC_WIDGET_HEIGHT; let border_width = 1.; @@ -118,22 +122,21 @@ impl Widget for Checkbox { .inset(-border_width / 2.) .to_rounded_rect(2.); - //Paint the background - let background_gradient = LinearGradient::new( + fill_lin_gradient( + scene, + &rect, + [theme::BACKGROUND_LIGHT, theme::BACKGROUND_DARK], UnitPoint::TOP, UnitPoint::BOTTOM, - (theme::BACKGROUND_LIGHT, theme::BACKGROUND_DARK), ); - ctx.fill(rect, &background_gradient); - let border_color = if ctx.is_hot() && !ctx.is_disabled() { theme::BORDER_LIGHT } else { theme::BORDER_DARK }; - ctx.stroke(rect, &border_color, border_width); + stroke(scene, &rect, border_color, border_width); if self.checked { // Paint the checkmark @@ -142,9 +145,15 @@ impl Widget for Checkbox { path.line_to((8.0, 13.0)); path.line_to((14.0, 5.0)); - let style = StrokeStyle::new() - .line_cap(LineCap::Round) - .line_join(LineJoin::Round); + let style = Stroke { + width: 2.0, + join: Join::Round, + miter_limit: 10.0, + start_cap: Cap::Round, + end_cap: Cap::Round, + dash_pattern: Default::default(), + dash_offset: 0.0, + }; let brush = if ctx.is_disabled() { theme::DISABLED_TEXT_COLOR @@ -152,11 +161,11 @@ impl Widget for Checkbox { theme::TEXT_COLOR }; - ctx.stroke_styled(path, &brush, 2., &style); + scene.stroke(&style, Affine::IDENTITY, brush, None, &path); } // Paint the text label - self.label.paint(ctx); + self.label.paint(ctx, scene); } fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { diff --git a/src/widget/flex.rs b/src/widget/flex.rs index eeb1803d..ad009a10 100644 --- a/src/widget/flex.rs +++ b/src/widget/flex.rs @@ -4,17 +4,18 @@ //! A widget that arranges its children in a one-dimensional array. -use piet_common::RenderContext; +use kurbo::{Affine, Stroke}; use smallvec::SmallVec; use tracing::{trace, trace_span, Span}; +use vello::Scene; use crate::kurbo::common::FloatExt; use crate::kurbo::Vec2; use crate::theme::get_debug_color; use crate::widget::{WidgetMut, WidgetRef}; use crate::{ - BoxConstraints, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, Rect, - Size, StatusChange, Widget, WidgetId, WidgetPod, + BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, PointerEvent, + Rect, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod, }; /// A container with either horizontal or vertical layout. @@ -237,7 +238,7 @@ impl Flex { // --- Mutate live Flex - WidgetMut --- -impl<'a, 'b> FlexMut<'a, 'b> { +impl<'a> FlexMut<'a> { /// Set the childrens' [`CrossAxisAlignment`]. /// /// [`CrossAxisAlignment`]: enum.CrossAxisAlignment.html @@ -456,7 +457,7 @@ impl<'a, 'b> FlexMut<'a, 'b> { } // FIXME - Remove Box - pub fn child_mut(&mut self, idx: usize) -> Option>> { + pub fn child_mut(&mut self, idx: usize) -> Option>> { let child = match &mut self.widget.children[idx] { Child::Fixed { widget, .. } | Child::Flex { widget, .. } => widget, Child::FixedSpacer(..) => return None, @@ -473,12 +474,14 @@ impl<'a, 'b> FlexMut<'a, 'b> { } impl Widget for Flex { - fn on_event(&mut self, ctx: &mut EventCtx, event: &Event) { + fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) { for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) { - child.on_event(ctx, event); + child.on_pointer_event(ctx, event); } } + fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} + fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { @@ -686,9 +689,9 @@ impl Widget for Flex { my_size } - fn paint(&mut self, ctx: &mut PaintCtx) { + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) { - child.paint(ctx); + child.paint(ctx, scene); } // paint the baseline if we're debugging layout @@ -696,8 +699,9 @@ impl Widget for Flex { let color = get_debug_color(ctx.widget_id().to_raw()); let my_baseline = ctx.size().height - ctx.widget_state.baseline_offset; let line = crate::kurbo::Line::new((0.0, my_baseline), (ctx.size().width, my_baseline)); - let stroke_style = crate::piet::StrokeStyle::new().dash_pattern(&[4.0, 4.0]); - ctx.stroke_styled(line, &color, 1.0, &stroke_style); + + let stroke_style = Stroke::new(1.0).with_dashes(0., [4.0, 4.0]); + scene.stroke(&stroke_style, Affine::IDENTITY, color, None, &line); } } diff --git a/src/widget/image.rs b/src/widget/image.rs index 11b8e786..a0c73b98 100644 --- a/src/widget/image.rs +++ b/src/widget/image.rs @@ -5,24 +5,26 @@ //! An Image widget. //! Please consider using SVG and the SVG widget as it scales much better. +use kurbo::Affine; use smallvec::SmallVec; use tracing::{trace, trace_span, Span}; +use vello::peniko::{BlendMode, Image as ImageBuf}; +use vello::Scene; -use crate::kurbo::Rect; -use crate::piet::{Image as _, ImageBuf, InterpolationMode, PietImage}; use crate::widget::{FillStrat, WidgetRef}; use crate::{ - BoxConstraints, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, RenderContext, - Size, StatusChange, Widget, + BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, PointerEvent, Size, + StatusChange, TextEvent, Widget, }; +// TODO - Resolve name collision between masonry::Image and peniko::Image + /// A widget that renders a bitmap Image. +/// +/// The underlying image uses `Arc` for buffer data, making it cheap to clone. pub struct Image { image_data: ImageBuf, - paint_data: Option, fill: FillStrat, - interpolation: InterpolationMode, - clip_area: Option, } crate::declare_widget!(ImageMut, Image); @@ -30,18 +32,13 @@ crate::declare_widget!(ImageMut, Image); impl Image { /// Create an image drawing widget from an image buffer. /// - /// By default, the Image will scale to fit its box constraints ([`FillStrat::Fill`]) - /// and will be scaled bilinearly ([`InterpolationMode::Bilinear`]) - /// - /// The underlying `ImageBuf` uses `Arc` for buffer data, making it cheap to clone. + /// By default, the Image will scale to fit its box constraints ([`FillStrat::Fill`]). + #[inline] pub fn new(image_data: ImageBuf) -> Self { Image { image_data, - paint_data: None, fill: FillStrat::default(), - interpolation: InterpolationMode::Bilinear, - clip_area: None, } } @@ -51,25 +48,9 @@ impl Image { self.fill = mode; self } - - /// Builder-style method for specifying the interpolation strategy. - #[inline] - pub fn interpolation_mode(mut self, interpolation: InterpolationMode) -> Self { - self.interpolation = interpolation; - self - } - - /// Builder-style method for setting the area of the image that will be displayed. - /// - /// If `None`, then the whole image will be displayed. - #[inline] - pub fn clip_area(mut self, clip_area: Option) -> Self { - self.clip_area = clip_area; - self - } } -impl<'a, 'b> ImageMut<'a, 'b> { +impl<'a> ImageMut<'a> { /// Modify the widget's fill strategy. #[inline] pub fn set_fill_mode(&mut self, newfil: FillStrat) { @@ -77,33 +58,18 @@ impl<'a, 'b> ImageMut<'a, 'b> { self.ctx.request_paint(); } - /// Modify the widget's interpolation mode. - #[inline] - pub fn set_interpolation_mode(&mut self, interpolation: InterpolationMode) { - self.widget.interpolation = interpolation; - self.ctx.request_paint(); - } - - /// Set the area of the image that will be displayed. - /// - /// If `None`, then the whole image will be displayed. - #[inline] - pub fn set_clip_area(&mut self, clip_area: Option) { - self.widget.clip_area = clip_area; - self.ctx.request_paint(); - } - /// Set new `ImageBuf`. #[inline] pub fn set_image_data(&mut self, image_data: ImageBuf) { self.widget.image_data = image_data; - self.widget.paint_data = None; self.ctx.request_layout(); } } impl Widget for Image { - fn on_event(&mut self, _ctx: &mut EventCtx, _event: &Event) {} + fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {} + + fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} @@ -114,7 +80,7 @@ impl Widget for Image { // in the size exactly. If it is unconstrained by both width and height take the size of // the image. let max = bc.max(); - let image_size = self.image_data.size(); + let image_size = Size::new(self.image_data.width as f64, self.image_data.height as f64); let size = if bc.is_width_bounded() && !bc.is_height_bounded() { let ratio = max.width / image_size.width; Size::new(max.width, ratio * image_size.height) @@ -122,54 +88,20 @@ impl Widget for Image { let ratio = max.height / image_size.height; Size::new(ratio * image_size.width, max.height) } else { - bc.constrain(self.image_data.size()) + bc.constrain(image_size) }; trace!("Computed size: {}", size); size } - fn paint(&mut self, ctx: &mut PaintCtx) { - let offset_matrix = self.fill.affine_to_fill(ctx.size(), self.image_data.size()); - - // The ImageData's to_piet function does not clip to the image's size - // CairoRenderContext is very like Masonry's but with some extra goodies like clip - if self.fill != FillStrat::Contain { - let clip_rect = ctx.size().to_rect(); - ctx.clip(clip_rect); - } + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + let image_size = Size::new(self.image_data.width as f64, self.image_data.height as f64); + let transform = self.fill.affine_to_fill(ctx.size(), image_size); - let piet_image = { - let image_data = &self.image_data; - self.paint_data - .get_or_insert_with(|| image_data.to_image(ctx.render_ctx)) - }; - if piet_image.size().is_empty() { - // zero-sized image = nothing to draw - return; - } - ctx.with_save(|ctx| { - // we have to re-do this because the whole struct is moved into the closure. - let piet_image = { - let image_data = &self.image_data; - self.paint_data - .get_or_insert_with(|| image_data.to_image(ctx.render_ctx)) - }; - ctx.transform(offset_matrix); - if let Some(area) = self.clip_area { - ctx.draw_image_area( - piet_image, - area, - self.image_data.size().to_rect(), - self.interpolation, - ); - } else { - ctx.draw_image( - piet_image, - self.image_data.size().to_rect(), - self.interpolation, - ); - } - }); + let clip_rect = ctx.size().to_rect(); + scene.push_layer(BlendMode::default(), 1., Affine::IDENTITY, &clip_rect); + scene.draw_image(&self.image_data, transform); + scene.pop_layer(); } fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { @@ -181,95 +113,50 @@ impl Widget for Image { } } -#[allow(unused)] // FIXME - remove cfg? #[cfg(not(target_arch = "wasm32"))] #[cfg(test)] mod tests { - use insta::assert_debug_snapshot; + use vello::peniko::Format; use super::*; use crate::assert_render_snapshot; - use crate::piet::ImageFormat; - use crate::testing::{widget_ids, TestHarness, TestWidgetExt}; - use crate::theme::PRIMARY_LIGHT; + use crate::testing::TestHarness; /// Painting an empty image shouldn't crash. #[test] fn empty_paint() { - let image_data = ImageBuf::empty(); - - let image_widget = - Image::new(image_data).interpolation_mode(InterpolationMode::NearestNeighbor); + // TODO - Blob::empty() function? + let image_data = ImageBuf::new(Vec::new().into(), vello::peniko::Format::Rgba8, 0, 0); + let image_widget = Image::new(image_data); let mut harness = TestHarness::create(image_widget); let _ = harness.render(); } #[test] fn tall_paint() { - let image_data = ImageBuf::from_raw( - vec![255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255], - ImageFormat::Rgb, + #[rustfmt::skip] + let image_data = ImageBuf::new( + vec![ + 255, 255, 255, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 255, 255, 255, 255, + ].into(), + Format::Rgba8, 2, 2, ); - - let image_widget = - Image::new(image_data).interpolation_mode(InterpolationMode::NearestNeighbor); + let image_widget = Image::new(image_data); let mut harness = TestHarness::create_with_size(image_widget, Size::new(40., 60.)); assert_render_snapshot!(harness, "tall_paint"); } - #[test] - fn edit_image_attributes() { - let image_data = ImageBuf::from_raw( - vec![ - 255, 255, 255, 244, 244, 244, 255, 255, 255, // row 0 - 0, 0, 0, 1, 1, 1, 0, 0, 0, // row 1 - 255, 255, 255, 244, 244, 244, 255, 255, 255, // row 2 - ], - ImageFormat::Rgb, - 3, - 3, - ); - - let render_1 = { - let image_widget = Image::new(image_data.clone()) - .fill_mode(FillStrat::Cover) - .interpolation_mode(InterpolationMode::NearestNeighbor) - .clip_area(Some(Rect::new(0.0, 0.0, 1.0, 1.0))); - - let mut harness = TestHarness::create_with_size(image_widget, Size::new(40.0, 60.0)); - - harness.render() - }; - - let render_2 = { - let image_widget = Image::new(image_data); - - let mut harness = TestHarness::create_with_size(image_widget, Size::new(40.0, 60.0)); - - harness.edit_root_widget(|mut image| { - let mut image = image.downcast::().unwrap(); - image.set_fill_mode(FillStrat::Cover); - image.set_interpolation_mode(InterpolationMode::NearestNeighbor); - image.set_clip_area(Some(Rect::new(0.0, 0.0, 1.0, 1.0))); - }); - - harness.render() - }; - - // TODO - write comparison function that creates rich diff - // and saves it in /tmp folder - See issue #18 - // We don't use assert_eq because we don't want rich assert - assert!(render_1 == render_2); - } - #[test] fn edit_image() { - let image_data = ImageBuf::from_raw(vec![255; 3 * 8 * 8], ImageFormat::Rgb, 8, 8); + let image_data = ImageBuf::new(vec![255; 4 * 8 * 8].into(), Format::Rgba8, 8, 8); let render_1 = { let image_widget = Image::new(image_data.clone()); @@ -280,7 +167,7 @@ mod tests { }; let render_2 = { - let other_image_data = ImageBuf::from_raw(vec![10; 3 * 8 * 8], ImageFormat::Rgb, 8, 8); + let other_image_data = ImageBuf::new(vec![10; 4 * 8 * 8].into(), Format::Rgba8, 8, 8); let image_widget = Image::new(other_image_data); let mut harness = TestHarness::create_with_size(image_widget, Size::new(40.0, 60.0)); diff --git a/src/widget/label.rs b/src/widget/label.rs index 49ddd230..517bb2da 100644 --- a/src/widget/label.rs +++ b/src/widget/label.rs @@ -4,20 +4,19 @@ //! A label widget. -// TODO -// - set text -// - set text attributes - -use druid_shell::Cursor; +use kurbo::Affine; +use parley::layout::Alignment; +use parley::style::{FontFamily, FontStack, GenericFamily, StyleProperty}; +use parley::{FontContext, Layout}; use smallvec::SmallVec; use tracing::{trace, trace_span, Span}; +use vello::peniko::{BlendMode, Brush}; +use vello::Scene; -use crate::kurbo::Vec2; -use crate::text::{FontDescriptor, TextAlignment, TextLayout}; use crate::widget::WidgetRef; use crate::{ - ArcStr, BoxConstraints, Color, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, - Point, RenderContext, Size, StatusChange, Widget, + ArcStr, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + PointerEvent, Size, StatusChange, TextEvent, Widget, }; // added padding between the edges of the widget and the text. @@ -26,11 +25,13 @@ const LABEL_X_PADDING: f64 = 2.0; /// A widget displaying non-editable text. pub struct Label { current_text: ArcStr, - text_layout: TextLayout, + text_layout: Option>, + text_size: f32, + font_family: FontFamily<'static>, line_break_mode: LineBreaking, - disabled: bool, - default_text_color: Color, + text_color: Color, + alignment: Alignment, } crate::declare_widget!(LabelMut, Label); @@ -52,64 +53,47 @@ impl Label { /// Create a new label. pub fn new(text: impl Into) -> Self { let current_text = text.into(); - let mut text_layout = TextLayout::new(); - text_layout.set_text(current_text.clone()); - Self { current_text, - text_layout, + text_layout: None, + text_color: crate::theme::TEXT_COLOR, + text_size: crate::theme::TEXT_SIZE_NORMAL as f32, + font_family: FontFamily::Generic(GenericFamily::SystemUi), line_break_mode: LineBreaking::Overflow, disabled: false, - default_text_color: crate::theme::TEXT_COLOR, + alignment: Alignment::Start, } } /// Create a label with empty text. pub fn empty() -> Self { - Self { - current_text: "".into(), - text_layout: TextLayout::new(), - line_break_mode: LineBreaking::Overflow, - disabled: false, - default_text_color: crate::theme::TEXT_COLOR, - } + Self::new("") } + // TODO - Rename methods /// Builder-style method for setting the text string. pub fn with_text(mut self, new_text: impl Into) -> Self { - self.text_layout.set_text(new_text.into()); + self.current_text = new_text.into(); + // TODO - Rethink how layout caching works during the builder phase + self.text_layout = None; self } /// Builder-style method for setting the text color. - /// - /// The argument can be either a `Color` or a [`Key`]. - /// - /// [`Key`]: ../struct.Key.html pub fn with_text_color(mut self, color: impl Into) -> Self { - let color = color.into(); - if !self.disabled { - self.text_layout.set_text_color(color); - } - self.default_text_color = color; + self.text_color = color.into(); self } /// Builder-style method for setting the text size. - /// - /// The argument can be either an `f64` or a [`Key`]. - /// - /// [`Key`]: ../struct.Key.html - pub fn with_text_size(mut self, size: impl Into) -> Self { - self.text_layout.set_text_size(size.into()); + pub fn with_text_size(mut self, size: impl Into) -> Self { + self.text_size = size.into(); self } - // FIXME - with_font cancels with_text_size - // TODO - write failing test for this case /// Builder-style method for setting the font. - pub fn with_font(mut self, font: impl Into) -> Self { - self.text_layout.set_font(font.into()); + pub fn with_font_family(mut self, font_family: impl Into>) -> Self { + self.font_family = font_family.into(); self } @@ -119,9 +103,9 @@ impl Label { self } - /// Builder-style method to set the [`TextAlignment`]. - pub fn with_text_alignment(mut self, alignment: TextAlignment) -> Self { - self.text_layout.set_text_alignment(alignment); + /// Builder-style method to set the [`Alignment`]. + pub fn with_text_alignment(mut self, alignment: Alignment) -> Self { + self.alignment = alignment; self } @@ -130,55 +114,58 @@ impl Label { self.current_text.clone() } + #[cfg(FALSE)] /// Return the offset of the first baseline relative to the bottom of the widget. pub fn baseline_offset(&self) -> f64 { let text_metrics = self.text_layout.layout_metrics(); text_metrics.size.height - text_metrics.first_baseline } - /// Draw this label's text at the provided `Point`, without internal padding. - /// - /// This is a convenience for widgets that want to use Label as a way - /// of managing a dynamic or localized string, but want finer control - /// over where the text is drawn. - pub fn draw_at(&self, ctx: &mut PaintCtx, origin: impl Into) { - self.text_layout.draw(ctx, origin) + fn get_layout_mut(&mut self, font_cx: &mut FontContext) -> &mut Layout { + let color = if self.disabled { + crate::theme::DISABLED_TEXT_COLOR + } else { + self.text_color + }; + let mut lcx = parley::LayoutContext::new(); + let mut layout_builder = lcx.ranged_builder(font_cx, &self.current_text, 1.0); + + layout_builder.push_default(&StyleProperty::FontStack(FontStack::Single( + self.font_family, + ))); + layout_builder.push_default(&StyleProperty::FontSize(self.text_size)); + layout_builder.push_default(&StyleProperty::Brush(Brush::Solid(color))); + + // TODO - Refactor. This code is mostly copy-pasted from Xilem's text widget + // Not super elegant. + self.text_layout = Some(layout_builder.build()); + self.text_layout.as_mut().unwrap() } } -impl LabelMut<'_, '_> { +impl LabelMut<'_> { /// Set the text. pub fn set_text(&mut self, new_text: impl Into) { - self.widget.text_layout.set_text(new_text.into()); + self.widget.current_text = new_text.into(); + self.widget.text_layout = None; self.ctx.request_layout(); } /// Set the text color. - /// - /// The argument can be either a `Color` or a [`Key`]. - /// [`Key`]: ../struct.Key.html pub fn set_text_color(&mut self, color: impl Into) { - let color = color.into(); - if !self.widget.disabled { - self.widget.text_layout.set_text_color(color); - } - self.widget.default_text_color = color; + self.widget.text_color = color.into(); self.ctx.request_layout(); } /// Set the text size. - /// - /// The argument can be either an `f64` or a [`Key`]. - /// - /// [`Key`]: ../struct.Key.html - pub fn set_text_size(&mut self, size: impl Into) { - self.widget.text_layout.set_text_size(size.into()); + pub fn set_text_size(&mut self, size: impl Into) { + self.widget.text_size = size.into(); self.ctx.request_layout(); } /// Set the font. - pub fn set_font(&mut self, font: impl Into) { - self.widget.text_layout.set_font(font.into()); + pub fn set_font_family(&mut self, font_family: impl Into>) { + self.widget.font_family = font_family.into(); self.ctx.request_layout(); } @@ -188,9 +175,9 @@ impl LabelMut<'_, '_> { self.ctx.request_layout(); } - /// Set the [`TextAlignment`] for this layout. - pub fn set_text_alignment(&mut self, alignment: TextAlignment) { - self.widget.text_layout.set_text_alignment(alignment); + /// Set the [`Alignment`] for this layout. + pub fn set_text_alignment(&mut self, alignment: Alignment) { + self.widget.alignment = alignment; self.ctx.request_layout(); } } @@ -198,42 +185,16 @@ impl LabelMut<'_, '_> { // --- TRAIT IMPLS --- impl Widget for Label { - fn on_event(&mut self, ctx: &mut EventCtx, event: &Event) { - match event { - Event::MouseUp(event) => { - // Account for the padding - let pos = event.pos - Vec2::new(LABEL_X_PADDING, 0.0); - if let Some(_link) = self.text_layout.link_for_pos(pos) { - todo!(); - //ctx.submit_command(link.command.clone()); - // See issue #21 - } - } - Event::MouseMove(event) => { - // Account for the padding - let pos = event.pos - Vec2::new(LABEL_X_PADDING, 0.0); - - if self.text_layout.link_for_pos(pos).is_some() { - ctx.set_cursor(&Cursor::Pointer); - } else { - ctx.clear_cursor(); - } - } - _ => {} - } - } + fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {} + + fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { match event { - LifeCycle::DisabledChanged(disabled) => { - let color = if *disabled { - crate::theme::DISABLED_TEXT_COLOR - } else { - self.default_text_color - }; - self.text_layout.set_text_color(color); + LifeCycle::DisabledChanged(_) => { + // TODO - only request paint ctx.request_layout(); } _ => {} @@ -241,32 +202,54 @@ impl Widget for Label { } fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { - let width = match self.line_break_mode { - LineBreaking::WordWrap => bc.max().width - LABEL_X_PADDING * 2.0, - _ => f64::INFINITY, + // Compute max_advance from box constraints + let max_advance = if self.line_break_mode != LineBreaking::WordWrap { + None + } else if bc.max().width.is_finite() { + Some(bc.max().width as f32 - 2. * LABEL_X_PADDING as f32) + } else if bc.min().width.is_sign_negative() { + Some(0.0) + } else { + None }; - self.text_layout.set_wrap_width(width); - self.text_layout.rebuild_if_needed(ctx.text()); + // TODO - Handle baseline - let text_metrics = self.text_layout.layout_metrics(); - ctx.set_baseline_offset(text_metrics.size.height - text_metrics.first_baseline); - let size = bc.constrain(Size::new( - text_metrics.size.width + 2. * LABEL_X_PADDING, - text_metrics.size.height, - )); - trace!("Computed size: {}", size); + // Lay text out + let alignment = self.alignment; + let layout = self.get_layout_mut(ctx.font_ctx()); + layout.break_all_lines(max_advance, alignment); + let size = Size { + width: layout.width() as f64 + 2. * LABEL_X_PADDING, + height: layout.height() as f64, + }; + let size = bc.constrain(size); + trace!( + "Computed layout: max={:?}. w={}, h={}", + max_advance, + size.width, + size.height, + ); size } - fn paint(&mut self, ctx: &mut PaintCtx) { - let origin = Point::new(LABEL_X_PADDING, 0.0); - let label_size = ctx.size(); + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + if let Some(text_layout) = &self.text_layout { + if self.line_break_mode == LineBreaking::Clip { + let clip_rect = ctx.size().to_rect(); + scene.push_layer(BlendMode::default(), 1., Affine::IDENTITY, &clip_rect); + } - if self.line_break_mode == LineBreaking::Clip { - ctx.clip(label_size.to_rect()); + crate::text_helpers::render_text( + scene, + Affine::translate((LABEL_X_PADDING, 0.)), + text_layout, + ); + + if self.line_break_mode == LineBreaking::Clip { + scene.pop_layer(); + } } - self.draw_at(ctx, origin) } fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> { @@ -282,9 +265,9 @@ impl Widget for Label { } } +// TODO - reenable tests #[cfg(test)] mod tests { - use crate::piet::FontFamily; use insta::assert_debug_snapshot; use super::*; @@ -307,10 +290,10 @@ mod tests { fn styled_label() { let label = Label::new("The quick brown fox jumps over the lazy dog") .with_text_color(PRIMARY_LIGHT) - .with_font(FontDescriptor::new(FontFamily::MONOSPACE)) + .with_font_family(FontFamily::Generic(GenericFamily::Monospace)) .with_text_size(20.0) .with_line_break_mode(LineBreaking::WordWrap) - .with_text_alignment(TextAlignment::Center); + .with_text_alignment(Alignment::Middle); let mut harness = TestHarness::create_with_size(label, Size::new(200.0, 200.0)); @@ -356,10 +339,10 @@ mod tests { let image_1 = { let label = Label::new("The quick brown fox jumps over the lazy dog") .with_text_color(PRIMARY_LIGHT) - .with_font(FontDescriptor::new(FontFamily::MONOSPACE)) + .with_font_family(FontFamily::Generic(GenericFamily::Monospace)) .with_text_size(20.0) .with_line_break_mode(LineBreaking::WordWrap) - .with_text_alignment(TextAlignment::Center); + .with_text_alignment(Alignment::Middle); let mut harness = TestHarness::create_with_size(label, Size::new(50.0, 50.0)); @@ -377,10 +360,10 @@ mod tests { let mut label = label.downcast::