diff options
151 files changed, 12246 insertions, 2648 deletions
diff --git a/.cargo/config.toml b/.cargo/config.toml index 49ca3252..df979396 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,3 @@ [alias] lint = "clippy --workspace --benches --all-features --no-deps -- -D warnings" +lint-fix = "clippy --fix --allow-dirty --workspace --benches --all-features --no-deps -- -D warnings" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ea941509..517bd23f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - rust: [stable, beta, "1.80"] + rust: [stable, beta, "1.81"] steps: - uses: hecrj/setup-rust-action@v2 with: @@ -1,6 +1,5 @@ target/ pkg/ **/*.rs.bk -Cargo.lock dist/ traces/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..7f4c6d91 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7419 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +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 = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.7.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + +[[package]] +name = "arc" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "ashpd" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c39d707614dbcc6bed00015539f488d8e3fe3e66ed60961efc0c90f4b380b3" +dependencies = [ + "async-fs 2.1.2", + "async-net 2.0.0", + "enumflags2", + "futures-channel", + "futures-util", + "rand", + "serde", + "serde_repr", + "url", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener 5.4.0", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.3.0", + "futures-lite 2.6.0", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock 3.4.0", + "blocking", + "futures-lite 2.6.0", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io 2.4.0", + "async-lock 3.4.0", + "blocking", + "futures-lite 2.6.0", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +dependencies = [ + "async-lock 3.4.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.0", + "parking", + "polling 3.7.4", + "rustix 0.38.43", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.4.0", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f" +dependencies = [ + "async-io 1.13.0", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io 2.4.0", + "blocking", + "futures-lite 2.6.0", +] + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.43", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel 2.3.1", + "async-io 2.4.0", + "async-lock 3.4.0", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.4.0", + "futures-lite 2.6.0", + "rustix 0.38.43", + "tracing", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io 2.4.0", + "async-lock 3.4.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.43", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-std" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io 2.4.0", + "async-lock 3.4.0", + "async-process 2.3.0", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 2.6.0", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-tungstenite" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" +dependencies = [ + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tungstenite", + "webpki-roots", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bezier_tool" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" + +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite 2.6.0", + "piper", +] + +[[package]] +name = "built" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "bytesize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "calloop" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" +dependencies = [ + "bitflags 2.7.0", + "log", + "polling 3.7.4", + "rustix 0.38.43", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.7.0", + "log", + "polling 3.7.4", + "rustix 0.38.43", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +dependencies = [ + "calloop 0.12.4", + "rustix 0.38.43", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix 0.38.43", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +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 = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "changelog" +version = "0.1.0" +dependencies = [ + "iced", + "log", + "reqwest", + "serde", + "thiserror 1.0.69", + "tokio", + "tracing-subscriber", + "webbrowser", +] + +[[package]] +name = "checkbox" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + +[[package]] +name = "clipboard_macos" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7f4aaa047ba3c3630b080bb9860894732ff23e2aee290a418909aa6d5df38f" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "clipboard_wayland" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003f886bc4e2987729d10c1db3424e7f80809f3fc22dbc16c685738887cb37b8" +dependencies = [ + "smithay-clipboard", +] + +[[package]] +name = "clipboard_x11" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4274ea815e013e0f9f04a2633423e14194e408a0576c943ce3d14ca56c50031c" +dependencies = [ + "thiserror 1.0.69", + "x11rb", +] + +[[package]] +name = "clock" +version = "0.1.0" +dependencies = [ + "chrono", + "iced", + "tracing-subscriber", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_palette" +version = "0.1.0" +dependencies = [ + "iced", + "palette", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "combo_box" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f" +dependencies = [ + "log", + "web-sys", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.7.0", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.7.0", + "core-foundation 0.10.0", + "libc", +] + +[[package]] +name = "cosmic-text" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fd57d82eb4bfe7ffa9b1cec0c05e2fd378155b47f255a67983cb4afe0e80c2" +dependencies = [ + "bitflags 2.7.0", + "fontdb 0.16.2", + "log", + "rangemap", + "rayon", + "rustc-hash 1.1.0", + "rustybuzz", + "self_cell", + "swash", + "sys-locale", + "ttf-parser 0.21.1", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "counter" +version = "0.1.0" +dependencies = [ + "iced", + "iced_test", +] + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctor-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" + +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + +[[package]] +name = "custom_quad" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "custom_shader" +version = "0.1.0" +dependencies = [ + "bytemuck", + "glam", + "iced", + "image", + "rand", +] + +[[package]] +name = "custom_widget" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "dark-light" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e1a09f280e29a8b00bc7e81eca5ac87dca0575639c9422a5fa25a07bb884b8" +dependencies = [ + "ashpd", + "async-std", + "objc2", + "objc2-foundation", + "web-sys", + "winreg", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "download_progress" +version = "0.1.0" +dependencies = [ + "iced", + "reqwest", +] + +[[package]] +name = "dpi" +version = "0.1.1" +source = "git+https://github.com/iced-rs/winit.git?rev=254d6b3420ce4e674f516f7a2bd440665e05484d#254d6b3420ce4e674f516f7a2bd440665e05484d" + +[[package]] +name = "drm" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" +dependencies = [ + "bitflags 2.7.0", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "rustix 0.38.43", +] + +[[package]] +name = "drm-ffi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" +dependencies = [ + "drm-sys", + "rustix 0.38.43", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" +dependencies = [ + "libc", + "linux-raw-sys 0.6.5", +] + +[[package]] +name = "editor" +version = "0.1.0" +dependencies = [ + "iced", + "rfd", + "tokio", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + +[[package]] +name = "etagere" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e2f1e3be19fb10f549be8c1bf013e8675b4066c445e36eb76d2ebb2f54ee495" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +dependencies = [ + "event-listener 5.4.0", + "pin-project-lite", +] + +[[package]] +name = "events" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "exit" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "ferris" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "font-types" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3971f9a5ca983419cdc386941ba3b9e1feba01a0ab888adf78739feb2798492" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.20.0", +] + +[[package]] +name = "fontdb" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.21.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand 2.3.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "game_of_life" +version = "0.1.0" +dependencies = [ + "iced", + "itertools 0.12.1", + "rustc-hash 2.1.0", + "tokio", + "tracing-subscriber", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "geometry" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "glow" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "glyphon" +version = "0.5.0" +source = "git+https://github.com/hecrj/glyphon.git?rev=09712a70df7431e9a3b1ac1bbd4fb634096cb3b4#09712a70df7431e9a3b1ac1bbd4fb634096cb3b4" +dependencies = [ + "cosmic-text", + "etagere", + "lru", + "rustc-hash 2.1.0", + "wgpu", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.7.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.7.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "windows 0.58.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" +dependencies = [ + "bitflags 2.7.0", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.7.0", +] + +[[package]] +name = "gradient" +version = "0.1.0" +dependencies = [ + "iced", + "tracing-subscriber", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 0.2.12", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.2.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.8", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.2.0", + "hyper 1.5.2", + "hyper-util", + "rustls 0.23.21", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.1", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "hyper 1.5.2", + "pin-project-lite", + "socket2 0.5.8", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core 0.52.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "iced" +version = "0.14.0-dev" +dependencies = [ + "criterion", + "iced_core", + "iced_futures", + "iced_highlighter", + "iced_renderer", + "iced_wgpu", + "iced_widget", + "iced_winit", + "image", + "thiserror 1.0.69", +] + +[[package]] +name = "iced_core" +version = "0.14.0-dev" +dependencies = [ + "approx", + "bitflags 2.7.0", + "bytes", + "dark-light", + "glam", + "log", + "num-traits", + "palette", + "rustc-hash 2.1.0", + "smol_str", + "thiserror 1.0.69", + "web-time", +] + +[[package]] +name = "iced_futures" +version = "0.14.0-dev" +dependencies = [ + "async-std", + "futures", + "iced_core", + "log", + "rustc-hash 2.1.0", + "smol", + "tokio", + "wasm-bindgen-futures", + "wasm-timer", +] + +[[package]] +name = "iced_graphics" +version = "0.14.0-dev" +dependencies = [ + "bitflags 2.7.0", + "bytemuck", + "cosmic-text", + "half", + "iced_core", + "iced_futures", + "image", + "kamadak-exif", + "log", + "lyon_path", + "raw-window-handle 0.6.2", + "rustc-hash 2.1.0", + "thiserror 1.0.69", + "unicode-segmentation", +] + +[[package]] +name = "iced_highlighter" +version = "0.14.0-dev" +dependencies = [ + "iced_core", + "syntect", +] + +[[package]] +name = "iced_renderer" +version = "0.14.0-dev" +dependencies = [ + "iced_graphics", + "iced_tiny_skia", + "iced_wgpu", + "log", + "thiserror 1.0.69", +] + +[[package]] +name = "iced_runtime" +version = "0.14.0-dev" +dependencies = [ + "bytes", + "iced_core", + "iced_futures", + "raw-window-handle 0.6.2", + "thiserror 1.0.69", +] + +[[package]] +name = "iced_test" +version = "0.14.0-dev" +dependencies = [ + "iced_renderer", + "iced_runtime", + "png", + "sha2", + "thiserror 1.0.69", +] + +[[package]] +name = "iced_tiny_skia" +version = "0.14.0-dev" +dependencies = [ + "bytemuck", + "cosmic-text", + "iced_graphics", + "kurbo 0.10.4", + "log", + "resvg", + "rustc-hash 2.1.0", + "softbuffer", + "tiny-skia", +] + +[[package]] +name = "iced_wgpu" +version = "0.14.0-dev" +dependencies = [ + "bitflags 2.7.0", + "bytemuck", + "futures", + "glam", + "glyphon", + "guillotiere", + "iced_graphics", + "log", + "lyon", + "resvg", + "rustc-hash 2.1.0", + "thiserror 1.0.69", + "wgpu", +] + +[[package]] +name = "iced_widget" +version = "0.14.0-dev" +dependencies = [ + "iced_highlighter", + "iced_renderer", + "iced_runtime", + "num-traits", + "ouroboros", + "pulldown-cmark", + "qrcode", + "rustc-hash 2.1.0", + "thiserror 1.0.69", + "unicode-segmentation", + "url", +] + +[[package]] +name = "iced_winit" +version = "0.14.0-dev" +dependencies = [ + "iced_futures", + "iced_graphics", + "iced_runtime", + "log", + "rustc-hash 2.1.0", + "sysinfo", + "thiserror 1.0.69", + "tracing", + "wasm-bindgen-futures", + "web-sys", + "winapi", + "window_clipboard", + "winit", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + +[[package]] +name = "imgref" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "integration" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "console_log", + "iced_wgpu", + "iced_widget", + "iced_winit", + "tracing-subscriber", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kamadak-exif" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077" +dependencies = [ + "mutate_once", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "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.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" +dependencies = [ + "arrayvec", + "smallvec", +] + +[[package]] +name = "kurbo" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" +dependencies = [ + "arrayvec", + "smallvec", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "layout" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "lazy" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.7.0", + "libc", + "redox_syscall 0.5.8", +] + +[[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.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "loading_spinners" +version = "0.1.0" +dependencies = [ + "iced", + "lyon_algorithms", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +dependencies = [ + "value-bag", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "loupe" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" + +[[package]] +name = "lyon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f13c9be19d257c7d37e70608ed858e8eab4b2afcea2e3c9a622e892acbf43c08" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e0b8aec2f58586f6eef237985b9a9b7cb3a3aff4417c575075cf95bf925252e" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markdown" +version = "0.1.0" +dependencies = [ + "iced", + "open", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "maybe_parallel_iterator" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5069b219d51d2ba2d9388623bd7eead5d4e7974bc8ff3ec9edbe36b09c0ef477" +dependencies = [ + "rayon", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.7.0", + "block", + "core-graphics-types 0.1.3", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[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.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "modal" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http 0.2.12", + "httparse", + "log", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "multi_window" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "multitouch" +version = "0.1.0" +dependencies = [ + "iced", + "tracing-subscriber", + "voronator", +] + +[[package]] +name = "mutate_once" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" + +[[package]] +name = "naga" +version = "23.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.7.0", + "cfg_aliases 0.1.1", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "rustc-hash 1.1.0", + "spirv", + "termcolor", + "thiserror 1.0.69", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.7.0", + "jni-sys", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle 0.6.2", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.7.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.7.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.7.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.7.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.7.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.7.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.7.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.7.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.7.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "onig" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +dependencies = [ + "bitflags 1.3.2", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "orbclient" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +dependencies = [ + "libredox", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owned_ttf_parser" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +dependencies = [ + "ttf-parser 0.25.1", +] + +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "palette_derive", + "phf", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pane_grid" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.10", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.8", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pick_list" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand 2.3.0", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "plist" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" +dependencies = [ + "base64 0.22.1", + "indexmap", + "quick-xml 0.32.0", + "serde", + "time", +] + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "pokedex" +version = "0.1.0" +dependencies = [ + "getrandom", + "iced", + "rand", + "reqwest", + "serde", + "serde_json", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix 0.38.43", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + +[[package]] +name = "profiling" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "progress_bar" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "pulldown-cmark" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625" +dependencies = [ + "bitflags 2.7.0", + "getopts", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "qr_code" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "qrcode" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "166f136dfdb199f98186f3649cf7a0536534a61417a1a30221b492b4fb60ce3f" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.0", + "rustls 0.23.21", + "socket2 0.5.8", + "thiserror 2.0.11", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes", + "getrandom", + "rand", + "ring", + "rustc-hash 2.1.0", + "rustls 0.23.21", + "rustls-pki-types", + "slab", + "thiserror 2.0.11", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" +dependencies = [ + "cfg_aliases 0.2.1", + "libc", + "once_cell", + "socket2 0.5.8", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "range-alloc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" + +[[package]] +name = "rangemap" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.12.1", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror 1.0.69", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.22.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69aacb76b5c29acfb7f90155d39759a29496aebb49395830e928a9703d2eec2f" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.7.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.2", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.21", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls 0.26.1", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "resvg" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944d052815156ac8fa77eaac055220e95ba0b01fa8887108ca710c03805d9051" +dependencies = [ + "gif", + "jpeg-decoder", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", +] + +[[package]] +name = "rfd" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d8ab342bcc5436e04d3a4c1e09e17d74958bfaddf8d5fad6f85607df0f994f" +dependencies = [ + "block", + "dispatch", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle 0.5.2", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +dependencies = [ + "bitflags 2.7.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +dependencies = [ + "web-time", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.7.0", + "bytemuck", + "libm", + "smallvec", + "ttf-parser 0.21.1", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "screenshot" +version = "0.1.0" +dependencies = [ + "iced", + "image", + "tokio", + "tracing-subscriber", +] + +[[package]] +name = "scrollable" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "sctk-adwaita" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7555fcb4f753d095d734fdefebb0ad8c98478a21db500492d87c55913d3b0086" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit 0.18.1", + "tiny-skia", +] + +[[package]] +name = "self_cell" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "sierpinski_triangle" +version = "0.1.0" +dependencies = [ + "iced", + "rand", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "skrifa" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slider" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smithay-client-toolkit" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" +dependencies = [ + "bitflags 2.7.0", + "calloop 0.12.4", + "calloop-wayland-source 0.2.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.43", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols 0.31.2", + "wayland-protocols-wlr 0.2.0", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.7.0", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.43", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols 0.32.5", + "wayland-protocols-wlr 0.3.5", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit 0.19.2", + "wayland-backend", +] + +[[package]] +name = "smol" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" +dependencies = [ + "async-channel 1.9.0", + "async-executor", + "async-fs 1.6.0", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-net 1.8.0", + "async-process 1.8.1", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "as-raw-xcb-connection", + "bytemuck", + "cfg_aliases 0.2.1", + "core-graphics 0.24.0", + "drm", + "fastrand 2.3.0", + "foreign-types", + "js-sys", + "log", + "memmap2", + "objc2", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle 0.6.2", + "redox_syscall 0.5.8", + "rustix 0.38.43", + "tiny-xlib", + "wasm-bindgen", + "wayland-backend", + "wayland-client", + "wayland-sys", + "web-sys", + "windows-sys 0.59.0", + "x11rb", +] + +[[package]] +name = "solar_system" +version = "0.1.0" +dependencies = [ + "iced", + "rand", + "tracing-subscriber", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.7.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stopwatch" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "styling" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "svg" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "svg_fmt" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" + +[[package]] +name = "svgtypes" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794de53cc48eaabeed0ab6a3404a65f40b3e38c067e4435883a65d2aa4ca000e" +dependencies = [ + "kurbo 0.11.1", + "siphasher", +] + +[[package]] +name = "swash" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2" +dependencies = [ + "skrifa", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syntect" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" +dependencies = [ + "bincode", + "bitflags 1.3.2", + "flate2", + "fnv", + "once_cell", + "onig", + "plist", + "regex-syntax", + "serde", + "serde_derive", + "serde_json", + "thiserror 1.0.69", + "walkdir", + "yaml-rust", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "sysinfo" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows 0.52.0", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "system_information" +version = "0.1.0" +dependencies = [ + "bytesize", + "iced", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +dependencies = [ + "cfg-if", + "fastrand 2.3.0", + "getrandom", + "once_cell", + "rustix 0.38.43", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "the_matrix" +version = "0.1.0" +dependencies = [ + "iced", + "rand", + "tracing-subscriber", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tiny-xlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" +dependencies = [ + "as-raw-xcb-connection", + "ctor-lite", + "libloading", + "pkg-config", + "tracing", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toast" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "todos" +version = "0.1.0" +dependencies = [ + "async-std", + "directories-next", + "iced", + "iced_test", + "serde", + "serde_json", + "tracing-subscriber", + "uuid", + "wasm-timer", + "web-sys", +] + +[[package]] +name = "tokio" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.8", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls 0.23.21", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tooltip" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "tour" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "console_log", + "iced", + "tracing-subscriber", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.2.0", + "httparse", + "log", + "rand", + "rustls 0.22.4", + "rustls-pki-types", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + +[[package]] +name = "unicode-ccc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-script" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "url_handler" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "usvg" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb 0.18.0", + "imagesize", + "kurbo 0.11.1", + "log", + "pico-args", + "roxmltree", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" +dependencies = [ + "getrandom", + "rand", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "value-bag" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" + +[[package]] +name = "vectorial_text" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "visible_bounds" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "voronator" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07d8e6fe4b9b3b443f2e4ada327cf4b08ab5cdd27a42cd5c9f38dd29092c10ee" +dependencies = [ + "maybe_parallel_iterator", +] + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http 0.2.12", + "hyper 0.14.32", + "log", + "mime", + "mime_guess", + "multer", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +dependencies = [ + "cc", + "downcast-rs", + "rustix 0.38.43", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" +dependencies = [ + "bitflags 2.7.0", + "rustix 0.38.43", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.7.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" +dependencies = [ + "rustix 0.38.43", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +dependencies = [ + "bitflags 2.7.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" +dependencies = [ + "bitflags 2.7.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" +dependencies = [ + "bitflags 2.7.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.31.2", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +dependencies = [ + "bitflags 2.7.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.31.2", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" +dependencies = [ + "bitflags 2.7.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.5", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +dependencies = [ + "proc-macro2", + "quick-xml 0.36.2", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea9fe1ebb156110ff855242c1101df158b822487e4957b0556d9ffce9db0f535" +dependencies = [ + "block2", + "core-foundation 0.10.0", + "home", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", +] + +[[package]] +name = "webpki-roots" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "websocket" +version = "1.0.0" +dependencies = [ + "async-tungstenite", + "iced", + "tokio", + "warp", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "wgpu" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a" +dependencies = [ + "arrayvec", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "naga", + "parking_lot 0.12.3", + "profiling", + "raw-window-handle 0.6.2", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.7.0", + "cfg_aliases 0.1.1", + "document-features", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot 0.12.3", + "profiling", + "raw-window-handle 0.6.2", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.7.0", + "block", + "bytemuck", + "cfg_aliases 0.1.1", + "core-graphics-types 0.1.3", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot 0.12.3", + "profiling", + "range-alloc", + "raw-window-handle 0.6.2", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "wgpu-types" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" +dependencies = [ + "bitflags 2.7.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window_clipboard" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d692d46038c433f9daee7ad8757e002a4248c20b0a3fbc991d99521d3bcb6d" +dependencies = [ + "clipboard-win", + "clipboard_macos", + "clipboard_wayland", + "clipboard_x11", + "raw-window-handle 0.6.2", + "thiserror 1.0.69", +] + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.30.1" +source = "git+https://github.com/iced-rs/winit.git?rev=254d6b3420ce4e674f516f7a2bd440665e05484d#254d6b3420ce4e674f516f7a2bd440665e05484d" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.7.0", + "block2", + "bytemuck", + "calloop 0.12.4", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle 0.6.2", + "redox_syscall 0.4.1", + "rustix 0.38.43", + "sctk-adwaita", + "smithay-client-toolkit 0.18.1", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.31.2", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 0.38.43", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xcursor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.7.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yazi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "192a0d989036cd60a1e91a54c9851fb9ad5bd96125d41803eed79d2e2ef74bd7" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs 2.1.2", + "async-io 2.4.0", + "async-lock 3.4.0", + "async-process 2.3.0", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener 5.4.0", + "futures-core", + "futures-util", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3685b5c81fce630efc3e143a4ded235b107f1b1cdf186c3f115529e5e5ae4265" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519629a3f80976d89c575895b05677cbc45eaf9f70d62a364d819ba646409cc8" +dependencies = [ + "serde", + "static_assertions", + "winnow", + "zvariant", +] + +[[package]] +name = "zeno" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" +dependencies = [ + "zune-core", +] + +[[package]] +name = "zvariant" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e6b9b5f1361de2d5e7d9fd1ee5f6f7fcb6060618a1f82f3472f58f2b8d4be9" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "url", + "winnow", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573a8dd76961957108b10f7a45bac6ab1ea3e9b7fe01aff88325dc57bb8f5c8b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd46446ea2a1f353bfda53e35f17633afa79f4fe290a611c94645c69fe96a50" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn", + "winnow", +] @@ -22,20 +22,20 @@ all-features = true maintenance = { status = "actively-developed" } [features] -default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"] -# Enable the `wgpu` GPU-accelerated renderer backend +default = ["wgpu", "tiny-skia", "auto-detect-theme"] +# Enables the `wgpu` GPU-accelerated renderer backend wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"] -# Enable the `tiny-skia` software renderer backend +# Enables the `tiny-skia` software renderer backend tiny-skia = ["iced_renderer/tiny-skia"] -# Enables the `Image` widget +# Enables the `image` widget image = ["image-without-codecs", "image/default"] -# Enables the `Image` widget, without any built-in codecs of the `image` crate +# Enables the `image` widget, without any built-in codecs of the `image` crate image-without-codecs = ["iced_widget/image", "dep:image"] -# Enables the `Svg` widget +# Enables the `svg` widget svg = ["iced_widget/svg"] -# Enables the `Canvas` widget +# Enables the `canvas` widget canvas = ["iced_widget/canvas"] -# Enables the `QRCode` widget +# Enables the `qr_code` widget qr_code = ["iced_widget/qr_code"] # Enables the `markdown` widget markdown = ["iced_widget/markdown"] @@ -53,20 +53,20 @@ smol = ["iced_futures/smol"] system = ["iced_winit/system"] # Enables broken "sRGB linear" blending to reproduce color management of the Web web-colors = ["iced_renderer/web-colors"] -# Enables the WebGL backend, replacing WebGPU +# Enables the WebGL backend webgl = ["iced_renderer/webgl"] -# Enables the syntax `highlighter` module +# Enables syntax highligthing highlighter = ["iced_highlighter", "iced_widget/highlighter"] -# Enables experimental multi-window support. -multi-window = ["iced_winit/multi-window"] # Enables the advanced module advanced = ["iced_core/advanced", "iced_widget/advanced"] -# Enables embedding Fira Sans as the default font on Wasm builds +# Embeds Fira Sans into the final application; useful for testing and Wasm builds fira-sans = ["iced_renderer/fira-sans"] -# Enables auto-detecting light/dark mode for the built-in theme +# Auto-detects light/dark mode for the built-in theme auto-detect-theme = ["iced_core/auto-detect-theme"] # Enables strict assertions for debugging purposes at the expense of performance strict-assertions = ["iced_renderer/strict-assertions"] +# Redraws on every runtime event, and not only when a widget requests it +unconditional-rendering = ["iced_winit/unconditional-rendering"] [dependencies] iced_core.workspace = true @@ -111,6 +111,7 @@ members = [ "highlighter", "renderer", "runtime", + "test", "tiny_skia", "wgpu", "widget", @@ -127,7 +128,7 @@ repository = "https://github.com/iced-rs/iced" homepage = "https://iced.rs" categories = ["gui"] keywords = ["gui", "ui", "graphics", "interface", "widgets"] -rust-version = "1.80" +rust-version = "1.81" [workspace.dependencies] iced = { version = "0.14.0-dev", path = "." } @@ -137,6 +138,7 @@ iced_graphics = { version = "0.14.0-dev", path = "graphics" } iced_highlighter = { version = "0.14.0-dev", path = "highlighter" } iced_renderer = { version = "0.14.0-dev", path = "renderer" } iced_runtime = { version = "0.14.0-dev", path = "runtime" } +iced_test = { version = "0.14.0-dev", path = "test" } iced_tiny_skia = { version = "0.14.0-dev", path = "tiny_skia" } iced_wgpu = { version = "0.14.0-dev", path = "wgpu" } iced_widget = { version = "0.14.0-dev", path = "widget" } @@ -147,27 +149,28 @@ bitflags = "2.0" bytemuck = { version = "1.0", features = ["derive"] } bytes = "1.6" cosmic-text = "0.12" -dark-light = "1.0" +dark-light = "2.0" futures = "0.3" glam = "0.25" -glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "0d7ba1bba4dd71eb88d2cface5ce649db2413cb7" } +glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "09712a70df7431e9a3b1ac1bbd4fb634096cb3b4" } guillotiere = "0.6" half = "2.2" -image = { version = "0.24", default-features = false } +image = { version = "0.25", default-features = false } kamadak-exif = "0.5" kurbo = "0.10" log = "0.4" lyon = "1.0" lyon_path = "1.0" num-traits = "0.2" -once_cell = "1.0" ouroboros = "0.18" palette = "0.7" +png = "0.17" pulldown-cmark = "0.11" qrcode = { version = "0.13", default-features = false } raw-window-handle = "0.6" resvg = "0.42" rustc-hash = "2.0" +sha2 = "0.10" smol = "1.0" smol_str = "0.2" softbuffer = "0.4" @@ -183,7 +186,7 @@ wasm-bindgen-futures = "0.4" wasm-timer = "0.2" web-sys = "0.3.69" web-time = "1.1" -wgpu = "22.0" +wgpu = "23.0" winapi = "0.3" window_clipboard = "0.4.1" winit = { git = "https://github.com/iced-rs/winit.git", rev = "254d6b3420ce4e674f516f7a2bd440665e05484d" } diff --git a/core/Cargo.toml b/core/Cargo.toml index a1228909..a3bc6745 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -23,7 +23,6 @@ bytes.workspace = true glam.workspace = true log.workspace = true num-traits.workspace = true -once_cell.workspace = true palette.workspace = true rustc-hash.workspace = true smol_str.workspace = true diff --git a/core/src/element.rs b/core/src/element.rs index 6ebb8a15..82ba753b 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -1,4 +1,3 @@ -use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::overlay; @@ -6,8 +5,8 @@ use crate::renderer; use crate::widget; use crate::widget::tree::{self, Tree}; use crate::{ - Border, Clipboard, Color, Layout, Length, Rectangle, Shell, Size, Vector, - Widget, + Border, Clipboard, Color, Event, Layout, Length, Rectangle, Shell, Size, + Vector, Widget, }; use std::borrow::Borrow; @@ -309,7 +308,7 @@ where self.widget.operate(tree, layout, renderer, operation); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -319,11 +318,11 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, B>, viewport: &Rectangle, - ) -> event::Status { + ) { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); - let status = self.widget.on_event( + self.widget.update( tree, event, layout, @@ -335,8 +334,6 @@ where ); shell.merge(local_shell, &self.mapper); - - status } fn draw( @@ -397,8 +394,8 @@ where } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Explain<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Explain<'_, Message, Theme, Renderer> where Renderer: crate::Renderer, { @@ -447,7 +444,7 @@ where .operate(state, layout, renderer, operation); } - fn on_event( + fn update( &mut self, state: &mut Tree, event: Event, @@ -457,10 +454,10 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.element.widget.on_event( + ) { + self.element.widget.update( state, event, layout, cursor, renderer, clipboard, shell, viewport, - ) + ); } fn draw( diff --git a/core/src/keyboard/key.rs b/core/src/keyboard/key.rs index 69a91902..47169d9a 100644 --- a/core/src/keyboard/key.rs +++ b/core/src/keyboard/key.rs @@ -32,6 +32,12 @@ impl Key { } } +impl From<Named> for Key { + fn from(named: Named) -> Self { + Self::Named(named) + } +} + /// A named key. /// /// This is mostly the `NamedKey` type found in [`winit`]. diff --git a/core/src/layout/flex.rs b/core/src/layout/flex.rs index ac80d393..2cff5bfd 100644 --- a/core/src/layout/flex.rs +++ b/core/src/layout/flex.rs @@ -79,6 +79,7 @@ where let max_cross = axis.cross(limits.max()); let mut fill_main_sum = 0; + let mut some_fill_cross = false; let (mut cross, cross_compress) = match axis { Axis::Vertical if width == Length::Shrink => (0.0, true), Axis::Horizontal if height == Length::Shrink => (0.0, true), @@ -90,6 +91,10 @@ where let mut nodes: Vec<Node> = Vec::with_capacity(items.len()); nodes.resize(items.len(), Node::default()); + // FIRST PASS + // We lay out non-fluid elements in the main axis. + // If we need to compress the cross axis, then we skip any of these elements + // that are also fluid in the cross axis. for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() { let (fill_main_factor, fill_cross_factor) = { let size = child.as_widget().size(); @@ -121,6 +126,41 @@ where nodes[i] = layout; } else { fill_main_sum += fill_main_factor; + some_fill_cross = some_fill_cross || fill_cross_factor != 0; + } + } + + // SECOND PASS (conditional) + // If we must compress the cross axis and there are fluid elements in the + // cross axis, we lay out any of these elements that are also non-fluid in + // the main axis (i.e. the ones we deliberately skipped in the first pass). + // + // We use the maximum cross length obtained in the first pass as the maximum + // cross limit. + if cross_compress && some_fill_cross { + for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() + { + let (fill_main_factor, fill_cross_factor) = { + let size = child.as_widget().size(); + + axis.pack(size.width.fill_factor(), size.height.fill_factor()) + }; + + if fill_main_factor == 0 && fill_cross_factor != 0 { + let (max_width, max_height) = axis.pack(available, cross); + + let child_limits = + Limits::new(Size::ZERO, Size::new(max_width, max_height)); + + let layout = + child.as_widget().layout(tree, renderer, &child_limits); + let size = layout.size(); + + available -= axis.main(size); + cross = cross.max(axis.cross(size)); + + nodes[i] = layout; + } } } @@ -135,6 +175,9 @@ where }, }; + // THIRD PASS + // We only have the elements that are fluid in the main axis left. + // We use the remaining space to evenly allocate space based on fill factors. for (i, (child, tree)) in items.iter().zip(trees).enumerate() { let (fill_main_factor, fill_cross_factor) = { let size = child.as_widget().size(); @@ -142,10 +185,16 @@ where axis.pack(size.width.fill_factor(), size.height.fill_factor()) }; - if fill_main_factor != 0 || (cross_compress && fill_cross_factor != 0) { + if fill_main_factor != 0 { let max_main = remaining * fill_main_factor as f32 / fill_main_sum as f32; + let max_main = if max_main.is_nan() { + f32::INFINITY + } else { + max_main + }; + let min_main = if max_main.is_infinite() { 0.0 } else { @@ -178,6 +227,8 @@ where let pad = axis.pack(padding.left, padding.top); let mut main = pad.0; + // FOURTH PASS + // We align all the laid out nodes in the cross axis, if needed. for (i, node) in nodes.iter_mut().enumerate() { if i > 0 { main += spacing; diff --git a/core/src/lib.rs b/core/src/lib.rs index df599f45..645f7a90 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -40,6 +40,7 @@ mod pixels; mod point; mod rectangle; mod rotation; +mod settings; mod shadow; mod shell; mod size; @@ -67,6 +68,7 @@ pub use point::Point; pub use rectangle::Rectangle; pub use renderer::Renderer; pub use rotation::Rotation; +pub use settings::Settings; pub use shadow::Shadow; pub use shell::Shell; pub use size::Size; diff --git a/core/src/overlay.rs b/core/src/overlay.rs index f09de831..383663af 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -5,13 +5,12 @@ mod group; pub use element::Element; pub use group::Group; -use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; use crate::widget::Tree; -use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; +use crate::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size, Vector}; /// An interactive component that can be displayed on top of other widgets. pub trait Overlay<Message, Theme, Renderer> @@ -57,7 +56,7 @@ where /// * a [`Clipboard`], if available /// /// By default, it does nothing. - fn on_event( + fn update( &mut self, _event: Event, _layout: Layout<'_>, @@ -65,8 +64,7 @@ where _renderer: &Renderer, _clipboard: &mut dyn Clipboard, _shell: &mut Shell<'_, Message>, - ) -> event::Status { - event::Status::Ignored + ) { } /// Returns the current [`mouse::Interaction`] of the [`Overlay`]. diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 32e987a3..7a179663 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -1,11 +1,10 @@ pub use crate::Overlay; -use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; -use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size}; +use crate::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size}; /// A generic [`Overlay`]. #[allow(missing_debug_implementations)] @@ -50,7 +49,7 @@ where } /// Processes a runtime [`Event`]. - pub fn on_event( + pub fn update( &mut self, event: Event, layout: Layout<'_>, @@ -58,9 +57,9 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { self.overlay - .on_event(event, layout, cursor, renderer, clipboard, shell) + .update(event, layout, cursor, renderer, clipboard, shell); } /// Returns the current [`mouse::Interaction`] of the [`Element`]. @@ -131,8 +130,8 @@ impl<'a, A, B, Theme, Renderer> Map<'a, A, B, Theme, Renderer> { } } -impl<'a, A, B, Theme, Renderer> Overlay<B, Theme, Renderer> - for Map<'a, A, B, Theme, Renderer> +impl<A, B, Theme, Renderer> Overlay<B, Theme, Renderer> + for Map<'_, A, B, Theme, Renderer> where Renderer: crate::Renderer, { @@ -149,7 +148,7 @@ where self.content.operate(layout, renderer, operation); } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -157,11 +156,11 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, B>, - ) -> event::Status { + ) { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); - let event_status = self.content.on_event( + self.content.update( event, layout, cursor, @@ -171,8 +170,6 @@ where ); shell.merge(local_shell, self.mapper); - - event_status } fn mouse_interaction( @@ -206,11 +203,11 @@ where self.content.is_over(layout, renderer, cursor_position) } - fn overlay<'b>( - &'b mut self, + fn overlay<'a>( + &'a mut self, layout: Layout<'_>, renderer: &Renderer, - ) -> Option<Element<'b, B, Theme, Renderer>> { + ) -> Option<Element<'a, B, Theme, Renderer>> { self.content .overlay(layout, renderer) .map(|overlay| overlay.map(self.mapper)) diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index 6541d311..e07744e3 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -1,4 +1,3 @@ -use crate::event; use crate::layout; use crate::mouse; use crate::overlay; @@ -58,8 +57,8 @@ where } } -impl<'a, Message, Theme, Renderer> Overlay<Message, Theme, Renderer> - for Group<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Overlay<Message, Theme, Renderer> + for Group<'_, Message, Theme, Renderer> where Renderer: crate::Renderer, { @@ -73,7 +72,7 @@ where ) } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -81,21 +80,17 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.children - .iter_mut() - .zip(layout.children()) - .map(|(child, layout)| { - child.on_event( - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - ) - }) - .fold(event::Status::Ignored, event::Status::merge) + ) { + for (child, layout) in self.children.iter_mut().zip(layout.children()) { + child.update( + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + ); + } } fn draw( @@ -157,11 +152,11 @@ where }) } - fn overlay<'b>( - &'b mut self, + fn overlay<'a>( + &'a mut self, layout: Layout<'_>, renderer: &Renderer, - ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { + ) -> Option<overlay::Element<'a, Message, Theme, Renderer>> { let children = self .children .iter_mut() diff --git a/core/src/renderer.rs b/core/src/renderer.rs index 6684517f..68e070e8 100644 --- a/core/src/renderer.rs +++ b/core/src/renderer.rs @@ -3,7 +3,8 @@ mod null; use crate::{ - Background, Border, Color, Rectangle, Shadow, Size, Transformation, Vector, + Background, Border, Color, Font, Pixels, Rectangle, Shadow, Size, + Transformation, Vector, }; /// A component that can be used by widgets to draw themselves on a screen. @@ -100,3 +101,19 @@ impl Default for Style { } } } + +/// A headless renderer is a renderer that can render offscreen without +/// a window nor a compositor. +pub trait Headless { + /// Creates a new [`Headless`] renderer; + fn new(default_font: Font, default_text_size: Pixels) -> Self; + + /// Draws offscreen into a screenshot, returning a collection of + /// bytes representing the rendered pixels in RGBA order. + fn screenshot( + &mut self, + size: Size<u32>, + scale_factor: f32, + background_color: Color, + ) -> Vec<u8>; +} diff --git a/src/settings.rs b/core/src/settings.rs index ebac7a86..3189c8d1 100644 --- a/src/settings.rs +++ b/core/src/settings.rs @@ -29,11 +29,9 @@ pub struct Settings { /// primitives. /// /// Enabling it can produce a smoother result in some widgets, like the - /// [`Canvas`], at a performance cost. + /// `canvas` widget, at a performance cost. /// /// By default, it is disabled. - /// - /// [`Canvas`]: crate::widget::Canvas pub antialiasing: bool, } @@ -48,12 +46,3 @@ impl Default for Settings { } } } - -impl From<Settings> for iced_winit::Settings { - fn from(settings: Settings) -> iced_winit::Settings { - iced_winit::Settings { - id: settings.id, - fonts: settings.fonts, - } - } -} diff --git a/core/src/shell.rs b/core/src/shell.rs index 2952ceff..12ebbaa8 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -1,3 +1,5 @@ +use crate::event; +use crate::time::Instant; use crate::window; /// A connection to the state of a shell. @@ -9,6 +11,7 @@ use crate::window; #[derive(Debug)] pub struct Shell<'a, Message> { messages: &'a mut Vec<Message>, + event_status: event::Status, redraw_request: Option<window::RedrawRequest>, is_layout_invalid: bool, are_widgets_invalid: bool, @@ -19,6 +22,7 @@ impl<'a, Message> Shell<'a, Message> { pub fn new(messages: &'a mut Vec<Message>) -> Self { Self { messages, + event_status: event::Status::Ignored, redraw_request: None, is_layout_invalid: false, are_widgets_invalid: false, @@ -35,14 +39,37 @@ impl<'a, Message> Shell<'a, Message> { self.messages.push(message); } - /// Requests a new frame to be drawn. - pub fn request_redraw(&mut self, request: window::RedrawRequest) { + /// Marks the current event as captured. Prevents "event bubbling". + /// + /// A widget should capture an event when no ancestor should + /// handle it. + pub fn capture_event(&mut self) { + self.event_status = event::Status::Captured; + } + + /// Returns the current [`event::Status`] of the [`Shell`]. + pub fn event_status(&self) -> event::Status { + self.event_status + } + + /// Returns whether the current event has been captured. + pub fn is_event_captured(&self) -> bool { + self.event_status == event::Status::Captured + } + + /// Requests a new frame to be drawn as soon as possible. + pub fn request_redraw(&mut self) { + self.redraw_request = Some(window::RedrawRequest::NextFrame); + } + + /// Requests a new frame to be drawn at the given [`Instant`]. + pub fn request_redraw_at(&mut self, at: Instant) { match self.redraw_request { None => { - self.redraw_request = Some(request); + self.redraw_request = Some(window::RedrawRequest::At(at)); } - Some(current) if request < current => { - self.redraw_request = Some(request); + Some(window::RedrawRequest::At(current)) if at < current => { + self.redraw_request = Some(window::RedrawRequest::At(at)); } _ => {} } @@ -95,8 +122,12 @@ impl<'a, Message> Shell<'a, Message> { pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) { self.messages.extend(other.messages.drain(..).map(f)); - if let Some(at) = other.redraw_request { - self.request_redraw(at); + if let Some(new) = other.redraw_request { + self.redraw_request = Some( + self.redraw_request + .map(|current| if current < new { current } else { new }) + .unwrap_or(new), + ); } self.is_layout_invalid = @@ -104,5 +135,7 @@ impl<'a, Message> Shell<'a, Message> { self.are_widgets_invalid = self.are_widgets_invalid || other.are_widgets_invalid; + + self.event_status = self.event_status.merge(other.event_status); } } diff --git a/core/src/text.rs b/core/src/text.rs index a9e3dce5..c144fd24 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -446,7 +446,7 @@ impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> { } } -impl<'a, Link, Font: PartialEq> PartialEq for Span<'a, Link, Font> { +impl<Link, Font: PartialEq> PartialEq for Span<'_, Link, Font> { fn eq(&self, other: &Self) -> bool { self.text == other.text && self.size == other.size @@ -474,7 +474,7 @@ impl<'a> IntoFragment<'a> for Fragment<'a> { } } -impl<'a, 'b> IntoFragment<'a> for &'a Fragment<'b> { +impl<'a> IntoFragment<'a> for &'a Fragment<'_> { fn into_fragment(self) -> Fragment<'a> { Fragment::Borrowed(self) } diff --git a/core/src/theme.rs b/core/src/theme.rs index 6b2c04da..cc5b77df 100644 --- a/core/src/theme.rs +++ b/core/src/theme.rs @@ -3,6 +3,8 @@ pub mod palette; pub use palette::Palette; +use crate::Color; + use std::fmt; use std::sync::Arc; @@ -164,15 +166,18 @@ impl Default for Theme { fn default() -> Self { #[cfg(feature = "auto-detect-theme")] { - use once_cell::sync::Lazy; + use std::sync::LazyLock; - static DEFAULT: Lazy<Theme> = - Lazy::new(|| match dark_light::detect() { + static DEFAULT: LazyLock<Theme> = LazyLock::new(|| { + match dark_light::detect() + .unwrap_or(dark_light::Mode::Unspecified) + { dark_light::Mode::Dark => Theme::Dark, - dark_light::Mode::Light | dark_light::Mode::Default => { + dark_light::Mode::Light | dark_light::Mode::Unspecified => { Theme::Light } - }); + } + }); DEFAULT.clone() } @@ -246,3 +251,35 @@ impl fmt::Display for Custom { write!(f, "{}", self.name) } } + +/// The base style of a [`Theme`]. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Style { + /// The background [`Color`] of the application. + pub background_color: Color, + + /// The default text [`Color`] of the application. + pub text_color: Color, +} + +/// The default blank style of a [`Theme`]. +pub trait Base { + /// Returns the default base [`Style`] of a [`Theme`]. + fn base(&self) -> Style; +} + +impl Base for Theme { + fn base(&self) -> Style { + default(self) + } +} + +/// The default [`Style`] of a built-in [`Theme`]. +pub fn default(theme: &Theme) -> Style { + let palette = theme.extended_palette(); + + Style { + background_color: palette.background.base.color, + text_color: palette.background.base.text, + } +} diff --git a/core/src/theme/palette.rs b/core/src/theme/palette.rs index e0ff397a..696c01d0 100644 --- a/core/src/theme/palette.rs +++ b/core/src/theme/palette.rs @@ -1,11 +1,12 @@ //! Define the colors of a theme. use crate::{color, Color}; -use once_cell::sync::Lazy; use palette::color_difference::Wcag21RelativeContrast; use palette::rgb::Rgb; use palette::{FromColor, Hsl, Mix}; +use std::sync::LazyLock; + /// A color palette. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Palette { @@ -17,6 +18,8 @@ pub struct Palette { pub primary: Color, /// The success [`Color`] of the [`Palette`]. pub success: Color, + /// The warning [`Color`] of the [`Palette`]. + pub warning: Color, /// The danger [`Color`] of the [`Palette`]. pub danger: Color, } @@ -36,6 +39,11 @@ impl Palette { 0x66 as f32 / 255.0, 0x4F as f32 / 255.0, ), + warning: Color::from_rgb( + 0xFF as f32 / 255.0, + 0xC1 as f32 / 255.0, + 0x4E as f32 / 255.0, + ), danger: Color::from_rgb( 0xC3 as f32 / 255.0, 0x42 as f32 / 255.0, @@ -61,6 +69,11 @@ impl Palette { 0x66 as f32 / 255.0, 0x4F as f32 / 255.0, ), + warning: Color::from_rgb( + 0xFF as f32 / 255.0, + 0xC1 as f32 / 255.0, + 0x4E as f32 / 255.0, + ), danger: Color::from_rgb( 0xC3 as f32 / 255.0, 0x42 as f32 / 255.0, @@ -76,6 +89,7 @@ impl Palette { text: color!(0xf8f8f2), // FOREGROUND primary: color!(0xbd93f9), // PURPLE success: color!(0x50fa7b), // GREEN + warning: color!(0xf1fa8c), // YELLOW danger: color!(0xff5555), // RED }; @@ -87,6 +101,7 @@ impl Palette { text: color!(0xeceff4), // nord6 primary: color!(0x8fbcbb), // nord7 success: color!(0xa3be8c), // nord14 + warning: color!(0xebcb8b), // nord13 danger: color!(0xbf616a), // nord11 }; @@ -98,6 +113,7 @@ impl Palette { text: color!(0x657b83), // base00 primary: color!(0x2aa198), // cyan success: color!(0x859900), // green + warning: color!(0xb58900), // yellow danger: color!(0xdc322f), // red }; @@ -109,6 +125,7 @@ impl Palette { text: color!(0x839496), // base0 primary: color!(0x2aa198), // cyan success: color!(0x859900), // green + warning: color!(0xb58900), // yellow danger: color!(0xdc322f), // red }; @@ -120,6 +137,7 @@ impl Palette { text: color!(0x282828), // light FG0_29 primary: color!(0x458588), // light BLUE_4 success: color!(0x98971a), // light GREEN_2 + warning: color!(0xd79921), // light YELLOW_3 danger: color!(0xcc241d), // light RED_1 }; @@ -131,6 +149,7 @@ impl Palette { text: color!(0xfbf1c7), // dark FG0_29 primary: color!(0x458588), // dark BLUE_4 success: color!(0x98971a), // dark GREEN_2 + warning: color!(0xd79921), // dark YELLOW_3 danger: color!(0xcc241d), // dark RED_1 }; @@ -142,6 +161,7 @@ impl Palette { text: color!(0x4c4f69), // Text primary: color!(0x1e66f5), // Blue success: color!(0x40a02b), // Green + warning: color!(0xdf8e1d), // Yellow danger: color!(0xd20f39), // Red }; @@ -153,6 +173,7 @@ impl Palette { text: color!(0xc6d0f5), // Text primary: color!(0x8caaee), // Blue success: color!(0xa6d189), // Green + warning: color!(0xe5c890), // Yellow danger: color!(0xe78284), // Red }; @@ -164,6 +185,7 @@ impl Palette { text: color!(0xcad3f5), // Text primary: color!(0x8aadf4), // Blue success: color!(0xa6da95), // Green + warning: color!(0xeed49f), // Yellow danger: color!(0xed8796), // Red }; @@ -175,6 +197,7 @@ impl Palette { text: color!(0xcdd6f4), // Text primary: color!(0x89b4fa), // Blue success: color!(0xa6e3a1), // Green + warning: color!(0xf9e2af), // Yellow danger: color!(0xf38ba8), // Red }; @@ -186,6 +209,7 @@ impl Palette { text: color!(0x9aa5ce), // Text primary: color!(0x2ac3de), // Blue success: color!(0x9ece6a), // Green + warning: color!(0xe0af68), // Yellow danger: color!(0xf7768e), // Red }; @@ -197,6 +221,7 @@ impl Palette { text: color!(0x9aa5ce), // Text primary: color!(0x2ac3de), // Blue success: color!(0x9ece6a), // Green + warning: color!(0xe0af68), // Yellow danger: color!(0xf7768e), // Red }; @@ -208,6 +233,7 @@ impl Palette { text: color!(0x565a6e), // Text primary: color!(0x166775), // Blue success: color!(0x485e30), // Green + warning: color!(0x8f5e15), // Yellow danger: color!(0x8c4351), // Red }; @@ -219,6 +245,7 @@ impl Palette { text: color!(0xCD7BA), // Fuji White primary: color!(0x2D4F67), // Wave Blue 2 success: color!(0x76946A), // Autumn Green + warning: color!(0xff9e3b), // Ronin Yellow danger: color!(0xC34043), // Autumn Red }; @@ -230,6 +257,7 @@ impl Palette { text: color!(0xc5c9c5), // Dragon White primary: color!(0x223249), // Wave Blue 1 success: color!(0x8a9a7b), // Dragon Green 2 + warning: color!(0xff9e3b), // Ronin Yellow danger: color!(0xc4746e), // Dragon Red }; @@ -241,6 +269,7 @@ impl Palette { text: color!(0x545464), // Lotus Ink 1 primary: color!(0xc9cbd1), // Lotus Violet 3 success: color!(0x6f894e), // Lotus Green + warning: color!(0xe98a00), // Lotus Orange 2 danger: color!(0xc84053), // Lotus Red }; @@ -252,6 +281,7 @@ impl Palette { text: color!(0xbdbdbd), // Foreground primary: color!(0x80a0ff), // Blue (normal) success: color!(0x8cc85f), // Green (normal) + warning: color!(0xe3c78a), // Yellow (normal) danger: color!(0xff5454), // Red (normal) }; @@ -263,6 +293,7 @@ impl Palette { text: color!(0xbdc1c6), // Foreground primary: color!(0x82aaff), // Blue (normal) success: color!(0xa1cd5e), // Green (normal) + warning: color!(0xe3d18a), // Yellow (normal) danger: color!(0xfc514e), // Red (normal) }; @@ -274,6 +305,7 @@ impl Palette { text: color!(0xd0d0d0), primary: color!(0x00b4ff), success: color!(0x00c15a), + warning: color!(0xbe95ff), // Base 14 danger: color!(0xf62d0f), }; @@ -285,6 +317,7 @@ impl Palette { text: color!(0xfecdb2), primary: color!(0xd1d1e0), success: color!(0xb1b695), + warning: color!(0xf5d76e), // Honey danger: color!(0xe06b75), }; } @@ -300,6 +333,8 @@ pub struct Extended { pub secondary: Secondary, /// The set of success colors. pub success: Success, + /// The set of warning colors. + pub warning: Warning, /// The set of danger colors. pub danger: Danger, /// Whether the palette is dark or not. @@ -307,92 +342,92 @@ pub struct Extended { } /// The built-in light variant of an [`Extended`] palette. -pub static EXTENDED_LIGHT: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::LIGHT)); +pub static EXTENDED_LIGHT: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::LIGHT)); /// The built-in dark variant of an [`Extended`] palette. -pub static EXTENDED_DARK: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::DARK)); +pub static EXTENDED_DARK: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::DARK)); /// The built-in Dracula variant of an [`Extended`] palette. -pub static EXTENDED_DRACULA: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::DRACULA)); +pub static EXTENDED_DRACULA: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::DRACULA)); /// The built-in Nord variant of an [`Extended`] palette. -pub static EXTENDED_NORD: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::NORD)); +pub static EXTENDED_NORD: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::NORD)); /// The built-in Solarized Light variant of an [`Extended`] palette. -pub static EXTENDED_SOLARIZED_LIGHT: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::SOLARIZED_LIGHT)); +pub static EXTENDED_SOLARIZED_LIGHT: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::SOLARIZED_LIGHT)); /// The built-in Solarized Dark variant of an [`Extended`] palette. -pub static EXTENDED_SOLARIZED_DARK: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::SOLARIZED_DARK)); +pub static EXTENDED_SOLARIZED_DARK: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::SOLARIZED_DARK)); /// The built-in Gruvbox Light variant of an [`Extended`] palette. -pub static EXTENDED_GRUVBOX_LIGHT: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::GRUVBOX_LIGHT)); +pub static EXTENDED_GRUVBOX_LIGHT: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::GRUVBOX_LIGHT)); /// The built-in Gruvbox Dark variant of an [`Extended`] palette. -pub static EXTENDED_GRUVBOX_DARK: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::GRUVBOX_DARK)); +pub static EXTENDED_GRUVBOX_DARK: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::GRUVBOX_DARK)); /// The built-in Catppuccin Latte variant of an [`Extended`] palette. -pub static EXTENDED_CATPPUCCIN_LATTE: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::CATPPUCCIN_LATTE)); +pub static EXTENDED_CATPPUCCIN_LATTE: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_LATTE)); /// The built-in Catppuccin Frappé variant of an [`Extended`] palette. -pub static EXTENDED_CATPPUCCIN_FRAPPE: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::CATPPUCCIN_FRAPPE)); +pub static EXTENDED_CATPPUCCIN_FRAPPE: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_FRAPPE)); /// The built-in Catppuccin Macchiato variant of an [`Extended`] palette. -pub static EXTENDED_CATPPUCCIN_MACCHIATO: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::CATPPUCCIN_MACCHIATO)); +pub static EXTENDED_CATPPUCCIN_MACCHIATO: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_MACCHIATO)); /// The built-in Catppuccin Mocha variant of an [`Extended`] palette. -pub static EXTENDED_CATPPUCCIN_MOCHA: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::CATPPUCCIN_MOCHA)); +pub static EXTENDED_CATPPUCCIN_MOCHA: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_MOCHA)); /// The built-in Tokyo Night variant of an [`Extended`] palette. -pub static EXTENDED_TOKYO_NIGHT: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::TOKYO_NIGHT)); +pub static EXTENDED_TOKYO_NIGHT: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT)); /// The built-in Tokyo Night Storm variant of an [`Extended`] palette. -pub static EXTENDED_TOKYO_NIGHT_STORM: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::TOKYO_NIGHT_STORM)); +pub static EXTENDED_TOKYO_NIGHT_STORM: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT_STORM)); /// The built-in Tokyo Night variant of an [`Extended`] palette. -pub static EXTENDED_TOKYO_NIGHT_LIGHT: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::TOKYO_NIGHT_LIGHT)); +pub static EXTENDED_TOKYO_NIGHT_LIGHT: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT_LIGHT)); /// The built-in Kanagawa Wave variant of an [`Extended`] palette. -pub static EXTENDED_KANAGAWA_WAVE: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::KANAGAWA_WAVE)); +pub static EXTENDED_KANAGAWA_WAVE: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::KANAGAWA_WAVE)); /// The built-in Kanagawa Dragon variant of an [`Extended`] palette. -pub static EXTENDED_KANAGAWA_DRAGON: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::KANAGAWA_DRAGON)); +pub static EXTENDED_KANAGAWA_DRAGON: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::KANAGAWA_DRAGON)); /// The built-in Kanagawa Lotus variant of an [`Extended`] palette. -pub static EXTENDED_KANAGAWA_LOTUS: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::KANAGAWA_LOTUS)); +pub static EXTENDED_KANAGAWA_LOTUS: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::KANAGAWA_LOTUS)); /// The built-in Moonfly variant of an [`Extended`] palette. -pub static EXTENDED_MOONFLY: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::MOONFLY)); +pub static EXTENDED_MOONFLY: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::MOONFLY)); /// The built-in Nightfly variant of an [`Extended`] palette. -pub static EXTENDED_NIGHTFLY: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::NIGHTFLY)); +pub static EXTENDED_NIGHTFLY: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::NIGHTFLY)); /// The built-in Oxocarbon variant of an [`Extended`] palette. -pub static EXTENDED_OXOCARBON: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::OXOCARBON)); +pub static EXTENDED_OXOCARBON: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::OXOCARBON)); /// The built-in Ferra variant of an [`Extended`] palette. -pub static EXTENDED_FERRA: Lazy<Extended> = - Lazy::new(|| Extended::generate(Palette::FERRA)); +pub static EXTENDED_FERRA: LazyLock<Extended> = + LazyLock::new(|| Extended::generate(Palette::FERRA)); impl Extended { /// Generates an [`Extended`] palette from a simple [`Palette`]. @@ -410,6 +445,11 @@ impl Extended { palette.background, palette.text, ), + warning: Warning::generate( + palette.warning, + palette.background, + palette.text, + ), danger: Danger::generate( palette.danger, palette.background, @@ -545,6 +585,31 @@ impl Success { } } +/// A set of warning colors. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Warning { + /// The base warning color. + pub base: Pair, + /// A weaker version of the base warning color. + pub weak: Pair, + /// A stronger version of the base warning color. + pub strong: Pair, +} + +impl Warning { + /// Generates a set of [`Warning`] colors from the base, background, and text colors. + pub fn generate(base: Color, background: Color, text: Color) -> Self { + let weak = mix(base, background, 0.4); + let strong = deviate(base, 0.1); + + Self { + base: Pair::new(base, text), + weak: Pair::new(weak, text), + strong: Pair::new(strong, text), + } + } +} + /// A set of danger colors. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Danger { diff --git a/core/src/widget.rs b/core/src/widget.rs index 9cfff83d..2a40f823 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -10,12 +10,11 @@ pub use operation::Operation; pub use text::Text; pub use tree::Tree; -use crate::event::{self, Event}; use crate::layout::{self, Layout}; use crate::mouse; use crate::overlay; use crate::renderer; -use crate::{Clipboard, Length, Rectangle, Shell, Size, Vector}; +use crate::{Clipboard, Event, Length, Rectangle, Shell, Size, Vector}; /// A component that displays information and allows interaction. /// @@ -112,7 +111,7 @@ where /// Processes a runtime [`Event`]. /// /// By default, it does nothing. - fn on_event( + fn update( &mut self, _state: &mut Tree, _event: Event, @@ -122,8 +121,7 @@ where _clipboard: &mut dyn Clipboard, _shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { - event::Status::Ignored + ) { } /// Returns the current [`mouse::Interaction`] of the [`Widget`]. diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs index 097c3601..8fc627bf 100644 --- a/core/src/widget/operation.rs +++ b/core/src/widget/operation.rs @@ -30,24 +30,45 @@ pub trait Operation<T = ()>: Send { ); /// Operates on a widget that can be focused. - fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {} + fn focusable( + &mut self, + _id: Option<&Id>, + _bounds: Rectangle, + _state: &mut dyn Focusable, + ) { + } /// Operates on a widget that can be scrolled. fn scrollable( &mut self, - _state: &mut dyn Scrollable, _id: Option<&Id>, _bounds: Rectangle, _content_bounds: Rectangle, _translation: Vector, + _state: &mut dyn Scrollable, ) { } /// Operates on a widget that has text input. - fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {} + fn text_input( + &mut self, + _id: Option<&Id>, + _bounds: Rectangle, + _state: &mut dyn TextInput, + ) { + } + + /// Operates on a widget that contains some text. + fn text(&mut self, _id: Option<&Id>, _bounds: Rectangle, _text: &str) {} /// Operates on a custom widget with some state. - fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {} + fn custom( + &mut self, + _id: Option<&Id>, + _bounds: Rectangle, + _state: &mut dyn Any, + ) { + } /// Finishes the [`Operation`] and returns its [`Outcome`]. fn finish(&self) -> Outcome<T> { @@ -68,33 +89,52 @@ where self.as_mut().container(id, bounds, operate_on_children); } - fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { - self.as_mut().focusable(state, id); + fn focusable( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn Focusable, + ) { + self.as_mut().focusable(id, bounds, state); } fn scrollable( &mut self, - state: &mut dyn Scrollable, id: Option<&Id>, bounds: Rectangle, content_bounds: Rectangle, translation: Vector, + state: &mut dyn Scrollable, ) { self.as_mut().scrollable( - state, id, bounds, content_bounds, translation, + state, ); } - fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { - self.as_mut().text_input(state, id); + fn text_input( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn TextInput, + ) { + self.as_mut().text_input(id, bounds, state); + } + + fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) { + self.as_mut().text(id, bounds, text); } - fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { - self.as_mut().custom(state, id); + fn custom( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn Any, + ) { + self.as_mut().custom(id, bounds, state); } fn finish(&self) -> Outcome<O> { @@ -138,7 +178,7 @@ where operation: &'a mut dyn Operation<T>, } - impl<'a, T, O> Operation<O> for BlackBox<'a, T> { + impl<T, O> Operation<O> for BlackBox<'_, T> { fn container( &mut self, id: Option<&Id>, @@ -150,33 +190,52 @@ where }); } - fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { - self.operation.focusable(state, id); + fn focusable( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn Focusable, + ) { + self.operation.focusable(id, bounds, state); } fn scrollable( &mut self, - state: &mut dyn Scrollable, id: Option<&Id>, bounds: Rectangle, content_bounds: Rectangle, translation: Vector, + state: &mut dyn Scrollable, ) { self.operation.scrollable( - state, id, bounds, content_bounds, translation, + state, ); } - fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { - self.operation.text_input(state, id); + fn text_input( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn TextInput, + ) { + self.operation.text_input(id, bounds, state); } - fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { - self.operation.custom(state, id); + fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) { + self.operation.text(id, bounds, text); + } + + fn custom( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn Any, + ) { + self.operation.custom(id, bounds, state); } fn finish(&self) -> Outcome<O> { @@ -218,7 +277,7 @@ where operation: &'a mut dyn Operation<A>, } - impl<'a, A, B> Operation<B> for MapRef<'a, A> { + impl<A, B> Operation<B> for MapRef<'_, A> { fn container( &mut self, id: Option<&Id>, @@ -234,39 +293,55 @@ where fn scrollable( &mut self, - state: &mut dyn Scrollable, id: Option<&Id>, bounds: Rectangle, content_bounds: Rectangle, translation: Vector, + state: &mut dyn Scrollable, ) { self.operation.scrollable( - state, id, bounds, content_bounds, translation, + state, ); } fn focusable( &mut self, - state: &mut dyn Focusable, id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn Focusable, ) { - self.operation.focusable(state, id); + self.operation.focusable(id, bounds, state); } fn text_input( &mut self, + id: Option<&Id>, + bounds: Rectangle, state: &mut dyn TextInput, + ) { + self.operation.text_input(id, bounds, state); + } + + fn text( + &mut self, id: Option<&Id>, + bounds: Rectangle, + text: &str, ) { - self.operation.text_input(state, id); + self.operation.text(id, bounds, text); } - fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { - self.operation.custom(state, id); + fn custom( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn Any, + ) { + self.operation.custom(id, bounds, state); } } @@ -275,33 +350,52 @@ where MapRef { operation }.container(id, bounds, operate_on_children); } - fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { - self.operation.focusable(state, id); + fn focusable( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn Focusable, + ) { + self.operation.focusable(id, bounds, state); } fn scrollable( &mut self, - state: &mut dyn Scrollable, id: Option<&Id>, bounds: Rectangle, content_bounds: Rectangle, translation: Vector, + state: &mut dyn Scrollable, ) { self.operation.scrollable( - state, id, bounds, content_bounds, translation, + state, ); } - fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { - self.operation.text_input(state, id); + fn text_input( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn TextInput, + ) { + self.operation.text_input(id, bounds, state); + } + + fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) { + self.operation.text(id, bounds, text); } - fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { - self.operation.custom(state, id); + fn custom( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn Any, + ) { + self.operation.custom(id, bounds, state); } fn finish(&self) -> Outcome<B> { @@ -361,33 +455,52 @@ where }); } - fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { - self.operation.focusable(state, id); + fn focusable( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn Focusable, + ) { + self.operation.focusable(id, bounds, state); } fn scrollable( &mut self, - state: &mut dyn Scrollable, id: Option<&Id>, bounds: Rectangle, content_bounds: Rectangle, translation: crate::Vector, + state: &mut dyn Scrollable, ) { self.operation.scrollable( - state, id, bounds, content_bounds, translation, + state, ); } - fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { - self.operation.text_input(state, id); + fn text_input( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn TextInput, + ) { + self.operation.text_input(id, bounds, state); } - fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) { - self.operation.custom(state, id); + fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) { + self.operation.text(id, bounds, text); + } + + fn custom( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + state: &mut dyn Any, + ) { + self.operation.custom(id, bounds, state); } fn finish(&self) -> Outcome<B> { diff --git a/core/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs index 867c682e..8f66e575 100644 --- a/core/src/widget/operation/focusable.rs +++ b/core/src/widget/operation/focusable.rs @@ -32,7 +32,12 @@ pub fn focus<T>(target: Id) -> impl Operation<T> { } impl<T> Operation<T> for Focus { - fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { + fn focusable( + &mut self, + id: Option<&Id>, + _bounds: Rectangle, + state: &mut dyn Focusable, + ) { match id { Some(id) if id == &self.target => { state.focus(); @@ -64,7 +69,12 @@ pub fn count() -> impl Operation<Count> { } impl Operation<Count> for CountFocusable { - fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { + fn focusable( + &mut self, + _id: Option<&Id>, + _bounds: Rectangle, + state: &mut dyn Focusable, + ) { if state.is_focused() { self.count.focused = Some(self.count.total); } @@ -104,7 +114,12 @@ where } impl<T> Operation<T> for FocusPrevious { - fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { + fn focusable( + &mut self, + _id: Option<&Id>, + _bounds: Rectangle, + state: &mut dyn Focusable, + ) { if self.count.total == 0 { return; } @@ -147,7 +162,12 @@ where } impl<T> Operation<T> for FocusNext { - fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { + fn focusable( + &mut self, + _id: Option<&Id>, + _bounds: Rectangle, + state: &mut dyn Focusable, + ) { match self.count.focused { None if self.current == 0 => state.focus(), Some(focused) if focused == self.current => state.unfocus(), @@ -179,7 +199,12 @@ pub fn find_focused() -> impl Operation<Id> { } impl Operation<Id> for FindFocused { - fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { + fn focusable( + &mut self, + id: Option<&Id>, + _bounds: Rectangle, + state: &mut dyn Focusable, + ) { if state.is_focused() && id.is_some() { self.focused = id.cloned(); } diff --git a/core/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs index c2fecf56..7c78c087 100644 --- a/core/src/widget/operation/scrollable.rs +++ b/core/src/widget/operation/scrollable.rs @@ -39,11 +39,11 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> { fn scrollable( &mut self, - state: &mut dyn Scrollable, id: Option<&Id>, _bounds: Rectangle, _content_bounds: Rectangle, _translation: Vector, + state: &mut dyn Scrollable, ) { if Some(&self.target) == id { state.snap_to(self.offset); @@ -74,11 +74,11 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> { fn scrollable( &mut self, - state: &mut dyn Scrollable, id: Option<&Id>, _bounds: Rectangle, _content_bounds: Rectangle, _translation: Vector, + state: &mut dyn Scrollable, ) { if Some(&self.target) == id { state.scroll_to(self.offset); @@ -109,11 +109,11 @@ pub fn scroll_by<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> { fn scrollable( &mut self, - state: &mut dyn Scrollable, id: Option<&Id>, bounds: Rectangle, content_bounds: Rectangle, _translation: Vector, + state: &mut dyn Scrollable, ) { if Some(&self.target) == id { state.scroll_by(self.offset, bounds, content_bounds); diff --git a/core/src/widget/operation/text_input.rs b/core/src/widget/operation/text_input.rs index 41731d4c..a46f1a2d 100644 --- a/core/src/widget/operation/text_input.rs +++ b/core/src/widget/operation/text_input.rs @@ -23,7 +23,12 @@ pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> { } impl<T> Operation<T> for MoveCursor { - fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + fn text_input( + &mut self, + id: Option<&Id>, + _bounds: Rectangle, + state: &mut dyn TextInput, + ) { match id { Some(id) if id == &self.target => { state.move_cursor_to_front(); @@ -53,7 +58,12 @@ pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> { } impl<T> Operation<T> for MoveCursor { - fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + fn text_input( + &mut self, + id: Option<&Id>, + _bounds: Rectangle, + state: &mut dyn TextInput, + ) { match id { Some(id) if id == &self.target => { state.move_cursor_to_end(); @@ -84,7 +94,12 @@ pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> { } impl<T> Operation<T> for MoveCursor { - fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + fn text_input( + &mut self, + id: Option<&Id>, + _bounds: Rectangle, + state: &mut dyn TextInput, + ) { match id { Some(id) if id == &self.target => { state.move_cursor_to(self.position); @@ -113,7 +128,12 @@ pub fn select_all<T>(target: Id) -> impl Operation<T> { } impl<T> Operation<T> for MoveCursor { - fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + fn text_input( + &mut self, + id: Option<&Id>, + _bounds: Rectangle, + state: &mut dyn TextInput, + ) { match id { Some(id) if id == &self.target => { state.select_all(); diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index b34c5632..c7ec3c8b 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -206,8 +206,8 @@ where #[derive(Debug, Default)] pub struct State<P: Paragraph>(pub paragraph::Plain<P>); -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Text<'a, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Text<'_, Theme, Renderer> where Theme: Catalog, Renderer: text::Renderer, @@ -267,6 +267,16 @@ where draw(renderer, defaults, layout, state.0.raw(), style, viewport); } + + fn operate( + &self, + _state: &mut Tree, + layout: Layout<'_>, + _renderer: &Renderer, + operation: &mut dyn super::Operation, + ) { + operation.text(None, layout.bounds(), &self.fragment); + } } /// Produces the [`layout::Node`] of a [`Text`] widget. diff --git a/core/src/window.rs b/core/src/window.rs index 448ffc45..d0e741d8 100644 --- a/core/src/window.rs +++ b/core/src/window.rs @@ -1,7 +1,9 @@ //! Build window-based GUI applications. pub mod icon; +pub mod screenshot; pub mod settings; +mod direction; mod event; mod id; mod level; @@ -10,6 +12,7 @@ mod position; mod redraw_request; mod user_attention; +pub use direction::Direction; pub use event::Event; pub use icon::Icon; pub use id::Id; @@ -17,5 +20,6 @@ pub use level::Level; pub use mode::Mode; pub use position::Position; pub use redraw_request::RedrawRequest; +pub use screenshot::Screenshot; pub use settings::Settings; pub use user_attention::UserAttention; diff --git a/core/src/window/direction.rs b/core/src/window/direction.rs new file mode 100644 index 00000000..b757961e --- /dev/null +++ b/core/src/window/direction.rs @@ -0,0 +1,27 @@ +/// The cardinal directions relative to the center of a window. +#[derive(Debug, Clone, Copy)] +pub enum Direction { + /// Points to the top edge of a window. + North, + + /// Points to the bottom edge of a window. + South, + + /// Points to the right edge of a window. + East, + + /// Points to the left edge of a window. + West, + + /// Points to the top-right corner of a window. + NorthEast, + + /// Points to the top-left corner of a window. + NorthWest, + + /// Points to the bottom-right corner of a window. + SouthEast, + + /// Points to the bottom-left corner of a window. + SouthWest, +} diff --git a/core/src/window/event.rs b/core/src/window/event.rs index 4e2751ee..45d29179 100644 --- a/core/src/window/event.rs +++ b/core/src/window/event.rs @@ -9,8 +9,8 @@ pub enum Event { /// A window was opened. Opened { /// The position of the opened window. This is relative to the top-left corner of the desktop - /// the window is on, including virtual desktops. Refers to window's "inner" position, - /// or the client area, in logical pixels. + /// the window is on, including virtual desktops. Refers to window's "outer" position, + /// or the window area, in logical pixels. /// /// **Note**: Not available in Wayland. position: Option<Point>, diff --git a/runtime/src/window/screenshot.rs b/core/src/window/screenshot.rs index d9adbc01..424168bb 100644 --- a/runtime/src/window/screenshot.rs +++ b/core/src/window/screenshot.rs @@ -1,5 +1,5 @@ //! Take screenshots of a window. -use crate::core::{Rectangle, Size}; +use crate::{Rectangle, Size}; use bytes::Bytes; use std::fmt::{Debug, Formatter}; diff --git a/core/src/window/settings.rs b/core/src/window/settings.rs index fbbf86ab..9432eaaa 100644 --- a/core/src/window/settings.rs +++ b/core/src/window/settings.rs @@ -28,12 +28,19 @@ use crate::window::{Icon, Level, Position}; use crate::Size; pub use platform::PlatformSpecific; + /// The window settings of an application. #[derive(Debug, Clone)] pub struct Settings { /// The initial logical dimensions of the window. pub size: Size, + /// Whether the window should start maximized. + pub maximized: bool, + + /// Whether the window should start fullscreen. + pub fullscreen: bool, + /// The initial position of the window. pub position: Position, @@ -79,6 +86,8 @@ impl Default for Settings { fn default() -> Self { Self { size: Size::new(1024.0, 768.0), + maximized: false, + fullscreen: false, position: Position::default(), min_size: None, max_size: None, diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 949bfad7..e8f0efc9 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -57,8 +57,9 @@ impl Example { mod bezier { use iced::mouse; - use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke}; + use iced::widget::canvas::{ + self, Canvas, Event, Frame, Geometry, Path, Stroke, + }; use iced::{Element, Fill, Point, Rectangle, Renderer, Theme}; #[derive(Default)] @@ -87,7 +88,7 @@ mod bezier { curves: &'a [Curve], } - impl<'a> canvas::Program<Curve> for Bezier<'a> { + impl canvas::Program<Curve> for Bezier<'_> { type State = Option<Pending>; fn update( @@ -96,48 +97,47 @@ mod bezier { event: Event, bounds: Rectangle, cursor: mouse::Cursor, - ) -> (event::Status, Option<Curve>) { - let Some(cursor_position) = cursor.position_in(bounds) else { - return (event::Status::Ignored, None); - }; + ) -> Option<canvas::Action<Curve>> { + let cursor_position = cursor.position_in(bounds)?; match event { - Event::Mouse(mouse_event) => { - let message = match mouse_event { - mouse::Event::ButtonPressed(mouse::Button::Left) => { - match *state { - None => { - *state = Some(Pending::One { - from: cursor_position, - }); - - None - } - Some(Pending::One { from }) => { - *state = Some(Pending::Two { - from, - to: cursor_position, - }); - - None - } - Some(Pending::Two { from, to }) => { - *state = None; - - Some(Curve { - from, - to, - control: cursor_position, - }) - } - } + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) => Some( + match *state { + None => { + *state = Some(Pending::One { + from: cursor_position, + }); + + canvas::Action::request_redraw() } - _ => None, - }; + Some(Pending::One { from }) => { + *state = Some(Pending::Two { + from, + to: cursor_position, + }); - (event::Status::Captured, message) + canvas::Action::request_redraw() + } + Some(Pending::Two { from, to }) => { + *state = None; + + canvas::Action::publish(Curve { + from, + to, + control: cursor_position, + }) + } + } + .and_capture(), + ), + Event::Mouse(mouse::Event::CursorMoved { .. }) + if state.is_some() => + { + Some(canvas::Action::request_redraw()) } - _ => (event::Status::Ignored, None), + _ => None, } } diff --git a/examples/changelog/Cargo.toml b/examples/changelog/Cargo.toml index eb942235..eeb7b526 100644 --- a/examples/changelog/Cargo.toml +++ b/examples/changelog/Cargo.toml @@ -5,6 +5,9 @@ authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" publish = false +[lints.clippy] +large_enum_variant = "allow" + [dependencies] iced.workspace = true iced.features = ["tokio", "markdown", "highlighter", "debug"] diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 7f21003b..1a86b168 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -89,6 +89,7 @@ impl ColorPalette { primary: *self.theme.lower.first().unwrap(), text: *self.theme.higher.last().unwrap(), success: *self.theme.lower.last().unwrap(), + warning: *self.theme.higher.last().unwrap(), danger: *self.theme.higher.last().unwrap(), }, ) diff --git a/examples/counter/Cargo.toml b/examples/counter/Cargo.toml index 22f86064..02eac329 100644 --- a/examples/counter/Cargo.toml +++ b/examples/counter/Cargo.toml @@ -10,4 +10,7 @@ iced.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] iced.workspace = true -iced.features = ["webgl"] +iced.features = ["webgl", "fira-sans"] + +[dev-dependencies] +iced_test.workspace = true diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs index 81684c1c..18bb8cfe 100644 --- a/examples/counter/src/main.rs +++ b/examples/counter/src/main.rs @@ -38,3 +38,31 @@ impl Counter { .align_x(Center) } } + +#[cfg(test)] +mod tests { + use super::*; + use iced_test::selector::text; + use iced_test::{simulator, Error}; + + #[test] + fn it_counts() -> Result<(), Error> { + let mut counter = Counter { value: 0 }; + let mut ui = simulator(counter.view()); + + let _ = ui.click(text("Increment"))?; + let _ = ui.click(text("Increment"))?; + let _ = ui.click(text("Decrement"))?; + + for message in ui.into_messages() { + counter.update(message); + } + + assert_eq!(counter.value, 1); + + let mut ui = simulator(counter.view()); + assert!(ui.find(text("1")).is_ok(), "Counter should display 1!"); + + Ok(()) + } +} diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs index dc425cc6..f9c07da9 100644 --- a/examples/custom_quad/src/main.rs +++ b/examples/custom_quad/src/main.rs @@ -75,7 +75,7 @@ mod quad { } } - impl<'a, Message> From<CustomQuad> for Element<'a, Message> { + impl<Message> From<CustomQuad> for Element<'_, Message> { fn from(circle: CustomQuad) -> Self { Self::new(circle) } diff --git a/examples/custom_shader/src/scene/pipeline.rs b/examples/custom_shader/src/scene/pipeline.rs index 84a3e5e2..567ab00b 100644 --- a/examples/custom_shader/src/scene/pipeline.rs +++ b/examples/custom_shader/src/scene/pipeline.rs @@ -241,7 +241,7 @@ impl Pipeline { layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, - entry_point: "vs_main", + entry_point: Some("vs_main"), buffers: &[Vertex::desc(), cube::Raw::desc()], compilation_options: wgpu::PipelineCompilationOptions::default(), @@ -261,7 +261,7 @@ impl Pipeline { }, fragment: Some(wgpu::FragmentState { module: &shader, - entry_point: "fs_main", + entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format, blend: Some(wgpu::BlendState { @@ -493,7 +493,7 @@ impl DepthPipeline { layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, - entry_point: "vs_main", + entry_point: Some("vs_main"), buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default(), @@ -509,7 +509,7 @@ impl DepthPipeline { multisample: wgpu::MultisampleState::default(), fragment: Some(wgpu::FragmentState { module: &shader, - entry_point: "fs_main", + entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format, blend: Some(wgpu::BlendState::REPLACE), diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index 58f3c54a..d561c2e0 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -62,8 +62,8 @@ mod circle { } } - impl<'a, Message, Theme, Renderer> From<Circle> - for Element<'a, Message, Theme, Renderer> + impl<Message, Theme, Renderer> From<Circle> + for Element<'_, Message, Theme, Renderer> where Renderer: renderer::Renderer, { diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs index a8e7b404..d63fb906 100644 --- a/examples/download_progress/src/download.rs +++ b/examples/download_progress/src/download.rs @@ -1,24 +1,13 @@ use iced::futures::{SinkExt, Stream, StreamExt}; use iced::stream::try_channel; -use iced::Subscription; -use std::hash::Hash; use std::sync::Arc; -// Just a little utility function -pub fn file<I: 'static + Hash + Copy + Send + Sync, T: ToString>( - id: I, - url: T, -) -> iced::Subscription<(I, Result<Progress, Error>)> { - Subscription::run_with_id( - id, - download(url.to_string()).map(move |progress| (id, progress)), - ) -} - -fn download(url: String) -> impl Stream<Item = Result<Progress, Error>> { +pub fn download( + url: impl AsRef<str>, +) -> impl Stream<Item = Result<Progress, Error>> { try_channel(1, move |mut output| async move { - let response = reqwest::get(&url).await?; + let response = reqwest::get(url.as_ref()).await?; let total = response.content_length().ok_or(Error::NoContentLength)?; let _ = output.send(Progress::Downloading { percent: 0.0 }).await; diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index bcc01606..f4b07203 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -1,7 +1,10 @@ mod download; +use download::download; + +use iced::task; use iced::widget::{button, center, column, progress_bar, text, Column}; -use iced::{Center, Element, Right, Subscription}; +use iced::{Center, Element, Right, Task}; pub fn main() -> iced::Result { iced::application( @@ -9,7 +12,6 @@ pub fn main() -> iced::Result { Example::update, Example::view, ) - .subscription(Example::subscription) .run() } @@ -23,7 +25,7 @@ struct Example { pub enum Message { Add, Download(usize), - DownloadProgressed((usize, Result<download::Progress, download::Error>)), + DownloadProgressed(usize, Result<download::Progress, download::Error>), } impl Example { @@ -34,32 +36,38 @@ impl Example { } } - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::Add => { self.last_id += 1; self.downloads.push(Download::new(self.last_id)); + + Task::none() } Message::Download(index) => { - if let Some(download) = self.downloads.get_mut(index) { - download.start(); - } + let Some(download) = self.downloads.get_mut(index) else { + return Task::none(); + }; + + let task = download.start(); + + task.map(move |progress| { + Message::DownloadProgressed(index, progress) + }) } - Message::DownloadProgressed((id, progress)) => { + Message::DownloadProgressed(id, progress) => { if let Some(download) = self.downloads.iter_mut().find(|download| download.id == id) { download.progress(progress); } + + Task::none() } } } - fn subscription(&self) -> Subscription<Message> { - Subscription::batch(self.downloads.iter().map(Download::subscription)) - } - fn view(&self) -> Element<Message> { let downloads = Column::with_children(self.downloads.iter().map(Download::view)) @@ -90,7 +98,7 @@ struct Download { #[derive(Debug)] enum State { Idle, - Downloading { progress: f32 }, + Downloading { progress: f32, _task: task::Handle }, Finished, Errored, } @@ -103,14 +111,28 @@ impl Download { } } - pub fn start(&mut self) { + pub fn start( + &mut self, + ) -> Task<Result<download::Progress, download::Error>> { match self.state { State::Idle { .. } | State::Finished { .. } | State::Errored { .. } => { - self.state = State::Downloading { progress: 0.0 }; + let (task, handle) = Task::stream(download( + "https://huggingface.co/\ + mattshumer/Reflection-Llama-3.1-70B/\ + resolve/main/model-00001-of-00162.safetensors", + )) + .abortable(); + + self.state = State::Downloading { + progress: 0.0, + _task: handle.abort_on_drop(), + }; + + task } - State::Downloading { .. } => {} + State::Downloading { .. } => Task::none(), } } @@ -118,7 +140,7 @@ impl Download { &mut self, new_progress: Result<download::Progress, download::Error>, ) { - if let State::Downloading { progress } = &mut self.state { + if let State::Downloading { progress, .. } = &mut self.state { match new_progress { Ok(download::Progress::Downloading { percent }) => { *progress = percent; @@ -133,20 +155,10 @@ impl Download { } } - pub fn subscription(&self) -> Subscription<Message> { - match self.state { - State::Downloading { .. } => { - download::file(self.id, "https://huggingface.co/mattshumer/Reflection-Llama-3.1-70B/resolve/main/model-00001-of-00162.safetensors") - .map(Message::DownloadProgressed) - } - _ => Subscription::none(), - } - } - pub fn view(&self) -> Element<Message> { let current_progress = match &self.state { State::Idle { .. } => 0.0, - State::Downloading { progress } => *progress, + State::Downloading { progress, .. } => *progress, State::Finished { .. } => 100.0, State::Errored { .. } => 0.0, }; diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 9dcebecc..7a7224d5 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -193,8 +193,9 @@ mod grid { use iced::mouse; use iced::touch; use iced::widget::canvas; - use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text}; + use iced::widget::canvas::{ + Cache, Canvas, Event, Frame, Geometry, Path, Text, + }; use iced::{ Color, Element, Fill, Point, Rectangle, Renderer, Size, Theme, Vector, }; @@ -383,14 +384,12 @@ mod grid { event: Event, bounds: Rectangle, cursor: mouse::Cursor, - ) -> (event::Status, Option<Message>) { + ) -> Option<canvas::Action<Message>> { if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event { *interaction = Interaction::None; } - let Some(cursor_position) = cursor.position_in(bounds) else { - return (event::Status::Ignored, None); - }; + let cursor_position = cursor.position_in(bounds)?; let cell = Cell::at(self.project(cursor_position, bounds.size())); let is_populated = self.state.contains(&cell); @@ -413,7 +412,12 @@ mod grid { populate.or(unpopulate) }; - (event::Status::Captured, message) + Some( + message + .map(canvas::Action::publish) + .unwrap_or(canvas::Action::request_redraw()) + .and_capture(), + ) } Event::Mouse(mouse_event) => match mouse_event { mouse::Event::ButtonPressed(button) => { @@ -438,7 +442,12 @@ mod grid { _ => None, }; - (event::Status::Captured, message) + Some( + message + .map(canvas::Action::publish) + .unwrap_or(canvas::Action::request_redraw()) + .and_capture(), + ) } mouse::Event::CursorMoved { .. } => { let message = match *interaction { @@ -454,12 +463,14 @@ mod grid { Interaction::None => None, }; - let event_status = match interaction { - Interaction::None => event::Status::Ignored, - _ => event::Status::Captured, - }; + let action = message + .map(canvas::Action::publish) + .unwrap_or(canvas::Action::request_redraw()); - (event_status, message) + Some(match interaction { + Interaction::None => action, + _ => action.and_capture(), + }) } mouse::Event::WheelScrolled { delta } => match delta { mouse::ScrollDelta::Lines { y, .. } @@ -496,18 +507,21 @@ mod grid { None }; - ( - event::Status::Captured, - Some(Message::Scaled(scaling, translation)), + Some( + canvas::Action::publish(Message::Scaled( + scaling, + translation, + )) + .and_capture(), ) } else { - (event::Status::Captured, None) + Some(canvas::Action::capture()) } } }, - _ => (event::Status::Ignored, None), + _ => None, }, - _ => (event::Status::Ignored, None), + _ => None, } } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 3c7969c5..d53ae6a5 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -145,7 +145,7 @@ mod rainbow { } } - impl<'a, Message> From<Rainbow> for Element<'a, Message> { + impl<Message> From<Rainbow> for Element<'_, Message> { fn from(rainbow: Rainbow) -> Self { Self::new(rainbow) } diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs index b2de069f..910ea9fc 100644 --- a/examples/gradient/src/main.rs +++ b/examples/gradient/src/main.rs @@ -1,5 +1,5 @@ -use iced::application; use iced::gradient; +use iced::theme; use iced::widget::{ checkbox, column, container, horizontal_space, row, slider, text, }; @@ -95,16 +95,14 @@ impl Gradient { .into() } - fn style(&self, theme: &Theme) -> application::Appearance { - use application::DefaultStyle; - + fn style(&self, theme: &Theme) -> theme::Style { if self.transparent { - application::Appearance { + theme::Style { background_color: Color::TRANSPARENT, text_color: theme.palette().text, } } else { - Theme::default_style(theme) + theme::default(theme) } } } diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs index 15f97e08..7ba551aa 100644 --- a/examples/integration/src/scene.rs +++ b/examples/integration/src/scene.rs @@ -72,13 +72,13 @@ fn build_pipeline( layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &vs_module, - entry_point: "main", + entry_point: Some("main"), buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { module: &fs_module, - entry_point: "main", + entry_point: Some("main"), targets: &[Some(wgpu::ColorTargetState { format: texture_format, blend: Some(wgpu::BlendState { diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 4280a003..e83a1f7d 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -3,7 +3,8 @@ use iced::keyboard; use iced::mouse; use iced::widget::{ button, canvas, center, checkbox, column, container, horizontal_rule, - horizontal_space, pick_list, row, scrollable, text, vertical_rule, + horizontal_space, pick_list, pin, row, scrollable, stack, text, + vertical_rule, }; use iced::{ color, Center, Element, Fill, Font, Length, Point, Rectangle, Renderer, @@ -151,6 +152,10 @@ impl Example { title: "Quotes", view: quotes, }, + Self { + title: "Pinning", + view: pinning, + }, ]; fn is_first(self) -> bool { @@ -309,6 +314,23 @@ fn quotes<'a>() -> Element<'a, Message> { .into() } +fn pinning<'a>() -> Element<'a, Message> { + column![ + "The pin widget can be used to position a widget \ + at some fixed coordinates inside some other widget.", + stack![ + container(pin("• (50, 50)").x(50).y(50)) + .width(500) + .height(500) + .style(container::bordered_box), + pin("• (300, 300)").x(300).y(300), + ] + ] + .align_x(Center) + .spacing(10) + .into() +} + fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> { struct Square; diff --git a/examples/loading_spinners/Cargo.toml b/examples/loading_spinners/Cargo.toml index a32da386..abd28aec 100644 --- a/examples/loading_spinners/Cargo.toml +++ b/examples/loading_spinners/Cargo.toml @@ -10,4 +10,3 @@ iced.workspace = true iced.features = ["advanced", "canvas"] lyon_algorithms = "1.0" -once_cell.workspace = true
\ No newline at end of file diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index 9239f01f..33232fac 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -3,11 +3,10 @@ use iced::advanced::layout; use iced::advanced::renderer; use iced::advanced::widget::tree::{self, Tree}; use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; -use iced::event; use iced::mouse; use iced::time::Instant; use iced::widget::canvas; -use iced::window::{self, RedrawRequest}; +use iced::window; use iced::{ Background, Color, Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector, @@ -89,7 +88,7 @@ where } } -impl<'a, Theme> Default for Circular<'a, Theme> +impl<Theme> Default for Circular<'_, Theme> where Theme: StyleSheet, { @@ -262,7 +261,7 @@ where layout::atomic(limits, self.size, self.size) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -272,7 +271,7 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::<State>(); if let Event::Window(window::Event::RedrawRequested(now)) = event { @@ -283,10 +282,8 @@ where ); state.cache.clear(); - shell.request_redraw(RedrawRequest::NextFrame); + shell.request_redraw(); } - - event::Status::Ignored } fn draw( diff --git a/examples/loading_spinners/src/easing.rs b/examples/loading_spinners/src/easing.rs index 45089ef6..8caf282d 100644 --- a/examples/loading_spinners/src/easing.rs +++ b/examples/loading_spinners/src/easing.rs @@ -2,40 +2,41 @@ use iced::Point; use lyon_algorithms::measure::PathMeasurements; use lyon_algorithms::path::{builder::NoAttributes, path::BuilderImpl, Path}; -use once_cell::sync::Lazy; -pub static EMPHASIZED: Lazy<Easing> = Lazy::new(|| { +use std::sync::LazyLock; + +pub static EMPHASIZED: LazyLock<Easing> = LazyLock::new(|| { Easing::builder() .cubic_bezier_to([0.05, 0.0], [0.133333, 0.06], [0.166666, 0.4]) .cubic_bezier_to([0.208333, 0.82], [0.25, 1.0], [1.0, 1.0]) .build() }); -pub static EMPHASIZED_DECELERATE: Lazy<Easing> = Lazy::new(|| { +pub static EMPHASIZED_DECELERATE: LazyLock<Easing> = LazyLock::new(|| { Easing::builder() .cubic_bezier_to([0.05, 0.7], [0.1, 1.0], [1.0, 1.0]) .build() }); -pub static EMPHASIZED_ACCELERATE: Lazy<Easing> = Lazy::new(|| { +pub static EMPHASIZED_ACCELERATE: LazyLock<Easing> = LazyLock::new(|| { Easing::builder() .cubic_bezier_to([0.3, 0.0], [0.8, 0.15], [1.0, 1.0]) .build() }); -pub static STANDARD: Lazy<Easing> = Lazy::new(|| { +pub static STANDARD: LazyLock<Easing> = LazyLock::new(|| { Easing::builder() .cubic_bezier_to([0.2, 0.0], [0.0, 1.0], [1.0, 1.0]) .build() }); -pub static STANDARD_DECELERATE: Lazy<Easing> = Lazy::new(|| { +pub static STANDARD_DECELERATE: LazyLock<Easing> = LazyLock::new(|| { Easing::builder() .cubic_bezier_to([0.0, 0.0], [0.0, 1.0], [1.0, 1.0]) .build() }); -pub static STANDARD_ACCELERATE: Lazy<Easing> = Lazy::new(|| { +pub static STANDARD_ACCELERATE: LazyLock<Easing> = LazyLock::new(|| { Easing::builder() .cubic_bezier_to([0.3, 0.0], [1.0, 1.0], [1.0, 1.0]) .build() diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs index 164993c6..a10b64f0 100644 --- a/examples/loading_spinners/src/linear.rs +++ b/examples/loading_spinners/src/linear.rs @@ -3,10 +3,9 @@ use iced::advanced::layout; use iced::advanced::renderer::{self, Quad}; use iced::advanced::widget::tree::{self, Tree}; use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; -use iced::event; use iced::mouse; use iced::time::Instant; -use iced::window::{self, RedrawRequest}; +use iced::window; use iced::{Background, Color, Element, Event, Length, Rectangle, Size}; use super::easing::{self, Easing}; @@ -71,7 +70,7 @@ where } } -impl<'a, Theme> Default for Linear<'a, Theme> +impl<Theme> Default for Linear<'_, Theme> where Theme: StyleSheet, { @@ -176,7 +175,7 @@ where layout::atomic(limits, self.width, self.height) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -186,16 +185,14 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::<State>(); if let Event::Window(window::Event::RedrawRequested(now)) = event { *state = state.timed_transition(self.cycle_duration, now); - shell.request_redraw(RedrawRequest::NextFrame); + shell.request_redraw(); } - - event::Status::Ignored } fn draw( diff --git a/examples/loupe/src/main.rs b/examples/loupe/src/main.rs index 1c748d42..6b7d053a 100644 --- a/examples/loupe/src/main.rs +++ b/examples/loupe/src/main.rs @@ -74,7 +74,7 @@ mod loupe { content: Element<'a, Message>, } - impl<'a, Message> Widget<Message, Theme, Renderer> for Loupe<'a, Message> { + impl<Message> Widget<Message, Theme, Renderer> for Loupe<'_, Message> { fn tag(&self) -> widget::tree::Tag { self.content.as_widget().tag() } diff --git a/examples/multi_window/Cargo.toml b/examples/multi_window/Cargo.toml index 2e222dfb..3f89417f 100644 --- a/examples/multi_window/Cargo.toml +++ b/examples/multi_window/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug", "multi-window"] } +iced = { path = "../..", features = ["debug"] } diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index d5e5dffa..5f4a5c90 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -3,9 +3,8 @@ //! computers like Microsoft Surface. use iced::mouse; use iced::touch; -use iced::widget::canvas::event; use iced::widget::canvas::stroke::{self, Stroke}; -use iced::widget::canvas::{self, Canvas, Geometry}; +use iced::widget::canvas::{self, Canvas, Event, Geometry}; use iced::{Color, Element, Fill, Point, Rectangle, Renderer, Theme}; use std::collections::HashMap; @@ -56,25 +55,25 @@ impl canvas::Program<Message> for Multitouch { fn update( &self, _state: &mut Self::State, - event: event::Event, + event: Event, _bounds: Rectangle, _cursor: mouse::Cursor, - ) -> (event::Status, Option<Message>) { - match event { - event::Event::Touch(touch_event) => match touch_event { + ) -> Option<canvas::Action<Message>> { + let message = match event { + Event::Touch( touch::Event::FingerPressed { id, position } - | touch::Event::FingerMoved { id, position } => ( - event::Status::Captured, - Some(Message::FingerPressed { id, position }), - ), + | touch::Event::FingerMoved { id, position }, + ) => Some(Message::FingerPressed { id, position }), + Event::Touch( touch::Event::FingerLifted { id, .. } - | touch::Event::FingerLost { id, .. } => ( - event::Status::Captured, - Some(Message::FingerLifted { id }), - ), - }, - _ => (event::Status::Ignored, None), - } + | touch::Event::FingerLost { id, .. }, + ) => Some(Message::FingerLifted { id }), + _ => None, + }; + + message + .map(canvas::Action::publish) + .map(canvas::Action::and_capture) } fn draw( diff --git a/examples/scrollable/Cargo.toml b/examples/scrollable/Cargo.toml index f8c735c0..ba291520 100644 --- a/examples/scrollable/Cargo.toml +++ b/examples/scrollable/Cargo.toml @@ -8,5 +8,3 @@ publish = false [dependencies] iced.workspace = true iced.features = ["debug"] - -once_cell.workspace = true diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index de4f2f9a..6359fb5a 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -4,9 +4,10 @@ use iced::widget::{ }; use iced::{Border, Center, Color, Element, Fill, Task, Theme}; -use once_cell::sync::Lazy; +use std::sync::LazyLock; -static SCROLLABLE_ID: Lazy<scrollable::Id> = Lazy::new(scrollable::Id::unique); +static SCROLLABLE_ID: LazyLock<scrollable::Id> = + LazyLock::new(scrollable::Id::unique); pub fn main() -> iced::Result { iced::application( diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index 99e7900a..d4d483f5 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -1,6 +1,5 @@ use iced::mouse; -use iced::widget::canvas::event::{self, Event}; -use iced::widget::canvas::{self, Canvas, Geometry}; +use iced::widget::canvas::{self, Canvas, Event, Geometry}; use iced::widget::{column, row, slider, text}; use iced::{Center, Color, Fill, Point, Rectangle, Renderer, Size, Theme}; @@ -80,26 +79,22 @@ impl canvas::Program<Message> for SierpinskiGraph { event: Event, bounds: Rectangle, cursor: mouse::Cursor, - ) -> (event::Status, Option<Message>) { - let Some(cursor_position) = cursor.position_in(bounds) else { - return (event::Status::Ignored, None); - }; + ) -> Option<canvas::Action<Message>> { + let cursor_position = cursor.position_in(bounds)?; match event { - Event::Mouse(mouse_event) => { - let message = match mouse_event { - iced::mouse::Event::ButtonPressed( - iced::mouse::Button::Left, - ) => Some(Message::PointAdded(cursor_position)), - iced::mouse::Event::ButtonPressed( - iced::mouse::Button::Right, - ) => Some(Message::PointRemoved), - _ => None, - }; - (event::Status::Captured, message) - } - _ => (event::Status::Ignored, None), + Event::Mouse(mouse::Event::ButtonPressed(button)) => match button { + mouse::Button::Left => Some(canvas::Action::publish( + Message::PointAdded(cursor_position), + )), + mouse::Button::Right => { + Some(canvas::Action::publish(Message::PointRemoved)) + } + _ => None, + }, + _ => None, } + .map(canvas::Action::and_capture) } fn draw( diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index 534f5e32..594be4a7 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -1,12 +1,14 @@ +use iced::keyboard; use iced::widget::{ button, center, checkbox, column, horizontal_rule, pick_list, progress_bar, row, scrollable, slider, text, text_input, toggler, vertical_rule, vertical_space, }; -use iced::{Center, Element, Fill, Theme}; +use iced::{Center, Element, Fill, Subscription, Theme}; pub fn main() -> iced::Result { iced::application("Styling - Iced", Styling::update, Styling::view) + .subscription(Styling::subscription) .theme(Styling::theme) .run() } @@ -28,6 +30,8 @@ enum Message { SliderChanged(f32), CheckboxToggled(bool), TogglerToggled(bool), + PreviousTheme, + NextTheme, } impl Styling { @@ -41,6 +45,23 @@ impl Styling { Message::SliderChanged(value) => self.slider_value = value, Message::CheckboxToggled(value) => self.checkbox_value = value, Message::TogglerToggled(value) => self.toggler_value = value, + Message::PreviousTheme | Message::NextTheme => { + if let Some(current) = Theme::ALL + .iter() + .position(|candidate| &self.theme == candidate) + { + self.theme = if matches!(message, Message::NextTheme) { + Theme::ALL[(current + 1) % Theme::ALL.len()].clone() + } else if current == 0 { + Theme::ALL + .last() + .expect("Theme::ALL must not be empty") + .clone() + } else { + Theme::ALL[current - 1].clone() + }; + } + } } } @@ -57,9 +78,16 @@ impl Styling { .padding(10) .size(20); - let button = button("Submit") - .padding(10) - .on_press(Message::ButtonPressed); + let styled_button = |label| { + button(text(label).width(Fill).center()) + .padding(10) + .on_press(Message::ButtonPressed) + }; + + let primary = styled_button("Primary"); + let success = styled_button("Success").style(button::success); + let warning = styled_button("Warning").style(button::warning); + let danger = styled_button("Danger").style(button::danger); let slider = slider(0.0..=100.0, self.slider_value, Message::SliderChanged); @@ -85,7 +113,10 @@ impl Styling { let content = column![ choose_theme, horizontal_rule(38), - row![text_input, button].spacing(10).align_y(Center), + text_input, + row![primary, success, warning, danger] + .spacing(10) + .align_y(Center), slider, progress_bar, row![ @@ -104,6 +135,19 @@ impl Styling { center(content).into() } + fn subscription(&self) -> Subscription<Message> { + keyboard::on_key_press(|key, _modifiers| match key { + keyboard::Key::Named( + keyboard::key::Named::ArrowUp | keyboard::key::Named::ArrowLeft, + ) => Some(Message::PreviousTheme), + keyboard::Key::Named( + keyboard::key::Named::ArrowDown + | keyboard::key::Named::ArrowRight, + ) => Some(Message::NextTheme), + _ => None, + }) + } + fn theme(&self) -> Theme { self.theme.clone() } diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs index 363df590..afa657d8 100644 --- a/examples/system_information/src/main.rs +++ b/examples/system_information/src/main.rs @@ -7,7 +7,7 @@ pub fn main() -> iced::Result { Example::update, Example::view, ) - .run() + .run_with(Example::new) } #[derive(Default)] @@ -28,20 +28,28 @@ enum Message { } impl Example { + fn new() -> (Self, Task<Message>) { + ( + Self::Loading, + system::fetch_information().map(Message::InformationReceived), + ) + } + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::Refresh => { - *self = Self::Loading; + let (state, refresh) = Self::new(); + + *self = state; - return system::fetch_information() - .map(Message::InformationReceived); + refresh } Message::InformationReceived(information) => { *self = Self::Loaded { information }; + + Task::none() } } - - Task::none() } fn view(&self) -> Element<Message> { diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 8f6a836e..a1b5886f 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -169,7 +169,6 @@ mod toast { use iced::advanced::renderer; use iced::advanced::widget::{self, Operation, Tree}; use iced::advanced::{Clipboard, Shell, Widget}; - use iced::event::{self, Event}; use iced::mouse; use iced::theme; use iced::widget::{ @@ -177,8 +176,8 @@ mod toast { }; use iced::window; use iced::{ - Alignment, Center, Element, Fill, Length, Point, Rectangle, Renderer, - Size, Theme, Vector, + Alignment, Center, Element, Event, Fill, Length, Point, Rectangle, + Renderer, Size, Theme, Vector, }; pub const DEFAULT_TIMEOUT: u64 = 5; @@ -282,7 +281,7 @@ mod toast { } } - impl<'a, Message> Widget<Message, Theme, Renderer> for Manager<'a, Message> { + impl<Message> Widget<Message, Theme, Renderer> for Manager<'_, Message> { fn size(&self) -> Size<Length> { self.content.as_widget().size() } @@ -359,7 +358,7 @@ mod toast { }); } - fn on_event( + fn update( &mut self, state: &mut Tree, event: Event, @@ -369,8 +368,8 @@ mod toast { clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.content.as_widget_mut().on_event( + ) { + self.content.as_widget_mut().update( &mut state.children[0], event, layout, @@ -379,7 +378,7 @@ mod toast { clipboard, shell, viewport, - ) + ); } fn draw( @@ -465,8 +464,8 @@ mod toast { timeout_secs: u64, } - impl<'a, 'b, Message> overlay::Overlay<Message, Theme, Renderer> - for Overlay<'a, 'b, Message> + impl<Message> overlay::Overlay<Message, Theme, Renderer> + for Overlay<'_, '_, Message> { fn layout( &mut self, @@ -490,7 +489,7 @@ mod toast { .translate(Vector::new(self.position.x, self.position.y)) } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -498,10 +497,8 @@ mod toast { renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { if let Event::Window(window::Event::RedrawRequested(now)) = &event { - let mut next_redraw: Option<window::RedrawRequest> = None; - self.instants.iter_mut().enumerate().for_each( |(index, maybe_instant)| { if let Some(instant) = maybe_instant.as_mut() { @@ -512,55 +509,43 @@ mod toast { if remaining == Duration::ZERO { maybe_instant.take(); shell.publish((self.on_close)(index)); - next_redraw = - Some(window::RedrawRequest::NextFrame); } else { - let redraw_at = - window::RedrawRequest::At(*now + remaining); - next_redraw = next_redraw - .map(|redraw| redraw.min(redraw_at)) - .or(Some(redraw_at)); + shell.request_redraw_at(*now + remaining); } } }, ); - - if let Some(redraw) = next_redraw { - shell.request_redraw(redraw); - } } let viewport = layout.bounds(); - self.toasts + for (((child, state), layout), instant) in self + .toasts .iter_mut() .zip(self.state.iter_mut()) .zip(layout.children()) .zip(self.instants.iter_mut()) - .map(|(((child, state), layout), instant)| { - let mut local_messages = vec![]; - let mut local_shell = Shell::new(&mut local_messages); - - let status = child.as_widget_mut().on_event( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - &mut local_shell, - &viewport, - ); + { + let mut local_messages = vec![]; + let mut local_shell = Shell::new(&mut local_messages); - if !local_shell.is_empty() { - instant.take(); - } + child.as_widget_mut().update( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + &mut local_shell, + &viewport, + ); - shell.merge(local_shell, std::convert::identity); + if !local_shell.is_empty() { + instant.take(); + } - status - }) - .fold(event::Status::Ignored, event::Status::merge) + shell.merge(local_shell, std::convert::identity); + } } fn draw( diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index 0d72be86..16f4fdd2 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -20,12 +20,15 @@ tracing-subscriber = "0.3" [target.'cfg(target_arch = "wasm32")'.dependencies] iced.workspace = true -iced.features = ["debug", "webgl"] +iced.features = ["debug", "webgl", "fira-sans"] uuid = { version = "1.0", features = ["js"] } web-sys = { workspace = true, features = ["Window", "Storage"] } wasm-timer.workspace = true +[dev-dependencies] +iced_test.workspace = true + [package.metadata.deb] assets = [ ["target/release-opt/todos", "usr/bin/iced-todos", "755"], diff --git a/examples/todos/snapshots/creates_a_new_task.sha256 b/examples/todos/snapshots/creates_a_new_task.sha256 new file mode 100644 index 00000000..193132c5 --- /dev/null +++ b/examples/todos/snapshots/creates_a_new_task.sha256 @@ -0,0 +1 @@ +3160686067cb7c738802009cdf2f3c5f5a5bd8c89ada70517388b7adbe64c313
\ No newline at end of file diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 25e3ead2..e86e23b5 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -15,7 +15,7 @@ pub fn main() -> iced::Result { iced::application(Todos::title, Todos::update, Todos::view) .subscription(Todos::subscription) - .font(include_bytes!("../fonts/icons.ttf").as_slice()) + .font(Todos::ICON_FONT) .window_size((500.0, 800.0)) .run_with(Todos::new) } @@ -48,6 +48,8 @@ enum Message { } impl Todos { + const ICON_FONT: &'static [u8] = include_bytes!("../fonts/icons.ttf"); + fn new() -> (Self, Command<Message>) { ( Self::Loading, @@ -147,9 +149,7 @@ impl Todos { } } Message::ToggleFullscreen(mode) => window::get_latest() - .and_then(move |window| { - window::change_mode(window, mode) - }), + .and_then(move |window| window::set_mode(window, mode)), Message::Loaded(_) => Command::none(), }; @@ -449,11 +449,10 @@ fn empty_message(message: &str) -> Element<'_, Message> { } // Fonts -const ICONS: Font = Font::with_name("Iced-Todos-Icons"); fn icon(unicode: char) -> Text<'static> { text(unicode.to_string()) - .font(ICONS) + .font(Font::with_name("Iced-Todos-Icons")) .width(20) .align_x(Center) } @@ -584,3 +583,49 @@ impl SavedState { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + use iced::{Settings, Theme}; + use iced_test::selector::text; + use iced_test::{Error, Simulator}; + + fn simulator(todos: &Todos) -> Simulator<Message> { + Simulator::with_settings( + Settings { + fonts: vec![Todos::ICON_FONT.into()], + ..Settings::default() + }, + todos.view(), + ) + } + + #[test] + fn it_creates_a_new_task() -> Result<(), Error> { + let (mut todos, _command) = Todos::new(); + let _command = todos.update(Message::Loaded(Err(LoadError::File))); + + let mut ui = simulator(&todos); + let _input = ui.click("new-task")?; + + let _ = ui.typewrite("Create the universe"); + let _ = ui.tap_key(keyboard::key::Named::Enter); + + for message in ui.into_messages() { + let _command = todos.update(message); + } + + let mut ui = simulator(&todos); + let _ = ui.find(text("Create the universe"))?; + + let snapshot = ui.snapshot(&Theme::Dark)?; + assert!( + snapshot.matches_hash("snapshots/creates_a_new_task")?, + "snapshots should match!" + ); + + Ok(()) + } +} diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index 9e984ad1..719d355f 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -14,7 +14,7 @@ tracing-subscriber = "0.3" [target.'cfg(target_arch = "wasm32")'.dependencies] iced.workspace = true -iced.features = ["image", "debug", "webgl"] +iced.features = ["image", "debug", "webgl", "fira-sans"] console_error_panic_hook = "0.1" console_log = "1.0" diff --git a/examples/visible_bounds/Cargo.toml b/examples/visible_bounds/Cargo.toml index 37594b84..1193334d 100644 --- a/examples/visible_bounds/Cargo.toml +++ b/examples/visible_bounds/Cargo.toml @@ -8,5 +8,3 @@ publish = false [dependencies] iced.workspace = true iced.features = ["debug"] - -once_cell.workspace = true diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs index 77fec65e..f8f9f420 100644 --- a/examples/visible_bounds/src/main.rs +++ b/examples/visible_bounds/src/main.rs @@ -157,9 +157,9 @@ impl Example { } } -use once_cell::sync::Lazy; +use std::sync::LazyLock; -static OUTER_CONTAINER: Lazy<container::Id> = - Lazy::new(|| container::Id::new("outer")); -static INNER_CONTAINER: Lazy<container::Id> = - Lazy::new(|| container::Id::new("inner")); +static OUTER_CONTAINER: LazyLock<container::Id> = + LazyLock::new(|| container::Id::new("outer")); +static INNER_CONTAINER: LazyLock<container::Id> = + LazyLock::new(|| container::Id::new("inner")); diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index c7075fb3..787dbbe1 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -9,7 +9,6 @@ publish = false iced.workspace = true iced.features = ["debug", "tokio"] -once_cell.workspace = true warp = "0.3" [dependencies.async-tungstenite] diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 8b1efb41..ae658471 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -4,7 +4,7 @@ use iced::widget::{ self, button, center, column, row, scrollable, text, text_input, }; use iced::{color, Center, Element, Fill, Subscription, Task}; -use once_cell::sync::Lazy; +use std::sync::LazyLock; pub fn main() -> iced::Result { iced::application("WebSocket - Iced", WebSocket::update, WebSocket::view) @@ -138,4 +138,5 @@ enum State { Connected(echo::Connection), } -static MESSAGE_LOG: Lazy<scrollable::Id> = Lazy::new(scrollable::Id::unique); +static MESSAGE_LOG: LazyLock<scrollable::Id> = + LazyLock::new(scrollable::Id::unique); diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 7e2d767b..43191a59 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -33,7 +33,6 @@ bytemuck.workspace = true cosmic-text.workspace = true half.workspace = true log.workspace = true -once_cell.workspace = true raw-window-handle.workspace = true rustc-hash.workspace = true thiserror.workspace = true diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 3026bead..0b862bdb 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -90,7 +90,6 @@ pub trait Compositor: Sized { fn screenshot<T: AsRef<str>>( &mut self, renderer: &mut Self::Renderer, - surface: &mut Self::Surface, viewport: &Viewport, background_color: Color, overlay: &[T], @@ -201,7 +200,6 @@ impl Compositor for () { fn screenshot<T: AsRef<str>>( &mut self, _renderer: &mut Self::Renderer, - _surface: &mut Self::Surface, _viewport: &Viewport, _background_color: Color, _overlay: &[T], diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs index 17d60451..8aa42798 100644 --- a/graphics/src/damage.rs +++ b/graphics/src/damage.rs @@ -45,15 +45,12 @@ pub fn list<T>( /// Groups the given damage regions that are close together inside the given /// bounds. pub fn group(mut damage: Vec<Rectangle>, bounds: Rectangle) -> Vec<Rectangle> { - use std::cmp::Ordering; - const AREA_THRESHOLD: f32 = 20_000.0; damage.sort_by(|a, b| { a.center() .distance(Point::ORIGIN) - .partial_cmp(&b.center().distance(Point::ORIGIN)) - .unwrap_or(Ordering::Equal) + .total_cmp(&b.center().distance(Point::ORIGIN)) }); let mut output = Vec::new(); diff --git a/graphics/src/geometry/stroke.rs b/graphics/src/geometry/stroke.rs index b8f4515e..88a5fd7b 100644 --- a/graphics/src/geometry/stroke.rs +++ b/graphics/src/geometry/stroke.rs @@ -23,7 +23,7 @@ pub struct Stroke<'a> { pub line_dash: LineDash<'a>, } -impl<'a> Stroke<'a> { +impl Stroke<'_> { /// Sets the color of the [`Stroke`]. pub fn with_color(self, color: Color) -> Self { Stroke { @@ -48,7 +48,7 @@ impl<'a> Stroke<'a> { } } -impl<'a> Default for Stroke<'a> { +impl Default for Stroke<'_> { fn default() -> Self { Stroke { style: Style::Solid(Color::BLACK), diff --git a/graphics/src/text.rs b/graphics/src/text.rs index feb9932a..7694ff1f 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -14,9 +14,9 @@ use crate::core::font::{self, Font}; use crate::core::text::{Shaping, Wrapping}; use crate::core::{Color, Pixels, Point, Rectangle, Size, Transformation}; -use once_cell::sync::OnceCell; use std::borrow::Cow; -use std::sync::{Arc, RwLock, Weak}; +use std::collections::HashSet; +use std::sync::{Arc, OnceLock, RwLock, Weak}; /// A text primitive. #[derive(Debug, Clone, PartialEq)] @@ -146,16 +146,17 @@ impl Text { /// The regular variant of the [Fira Sans] font. /// -/// It is loaded as part of the default fonts in Wasm builds. +/// It is loaded as part of the default fonts when the `fira-sans` +/// feature is enabled. /// /// [Fira Sans]: https://mozilla.github.io/Fira/ -#[cfg(all(target_arch = "wasm32", feature = "fira-sans"))] -pub const FIRA_SANS_REGULAR: &'static [u8] = +#[cfg(feature = "fira-sans")] +pub const FIRA_SANS_REGULAR: &[u8] = include_bytes!("../fonts/FiraSans-Regular.ttf").as_slice(); /// Returns the global [`FontSystem`]. pub fn font_system() -> &'static RwLock<FontSystem> { - static FONT_SYSTEM: OnceCell<RwLock<FontSystem>> = OnceCell::new(); + static FONT_SYSTEM: OnceLock<RwLock<FontSystem>> = OnceLock::new(); FONT_SYSTEM.get_or_init(|| { RwLock::new(FontSystem { @@ -163,11 +164,12 @@ pub fn font_system() -> &'static RwLock<FontSystem> { cosmic_text::fontdb::Source::Binary(Arc::new( include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), )), - #[cfg(all(target_arch = "wasm32", feature = "fira-sans"))] + #[cfg(feature = "fira-sans")] cosmic_text::fontdb::Source::Binary(Arc::new( include_bytes!("../fonts/FiraSans-Regular.ttf").as_slice(), )), ]), + loaded_fonts: HashSet::new(), version: Version::default(), }) }) @@ -177,6 +179,7 @@ pub fn font_system() -> &'static RwLock<FontSystem> { #[allow(missing_debug_implementations)] pub struct FontSystem { raw: cosmic_text::FontSystem, + loaded_fonts: HashSet<usize>, version: Version, } @@ -188,6 +191,14 @@ impl FontSystem { /// Loads a font from its bytes. pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + if let Cow::Borrowed(bytes) = bytes { + let address = bytes.as_ptr() as usize; + + if !self.loaded_fonts.insert(address) { + return; + } + } + let _ = self.raw.db_mut().load_font_source( cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())), ); diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 07ddbb82..48c8e9e6 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -80,6 +80,8 @@ impl core::text::Paragraph for Paragraph { Some(text.bounds.height), ); + buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrapping)); + buffer.set_text( font_system.raw(), text.content, @@ -122,6 +124,8 @@ impl core::text::Paragraph for Paragraph { Some(text.bounds.height), ); + buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrapping)); + buffer.set_rich_text( font_system.raw(), text.content.iter().enumerate().map(|(i, span)| { diff --git a/highlighter/Cargo.toml b/highlighter/Cargo.toml index 7962b89d..4c20a678 100644 --- a/highlighter/Cargo.toml +++ b/highlighter/Cargo.toml @@ -16,5 +16,4 @@ workspace = true [dependencies] iced_core.workspace = true -once_cell.workspace = true syntect.workspace = true diff --git a/highlighter/src/lib.rs b/highlighter/src/lib.rs index 83a15cb1..d2abc6b1 100644 --- a/highlighter/src/lib.rs +++ b/highlighter/src/lib.rs @@ -5,16 +5,16 @@ use crate::core::font::{self, Font}; use crate::core::text::highlighter::{self, Format}; use crate::core::Color; -use once_cell::sync::Lazy; use std::ops::Range; +use std::sync::LazyLock; use syntect::highlighting; use syntect::parsing; -static SYNTAXES: Lazy<parsing::SyntaxSet> = - Lazy::new(parsing::SyntaxSet::load_defaults_nonewlines); +static SYNTAXES: LazyLock<parsing::SyntaxSet> = + LazyLock::new(parsing::SyntaxSet::load_defaults_nonewlines); -static THEMES: Lazy<highlighting::ThemeSet> = - Lazy::new(highlighting::ThemeSet::load_defaults); +static THEMES: LazyLock<highlighting::ThemeSet> = + LazyLock::new(highlighting::ThemeSet::load_defaults); const LINES_PER_SNAPSHOT: usize = 50; diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 8cb18bde..52b8317f 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -353,34 +353,27 @@ where fn screenshot<T: AsRef<str>>( &mut self, renderer: &mut Self::Renderer, - surface: &mut Self::Surface, viewport: &graphics::Viewport, background_color: Color, overlay: &[T], ) -> Vec<u8> { - match (self, renderer, surface) { - ( - Self::Primary(compositor), - Renderer::Primary(renderer), - Surface::Primary(surface), - ) => compositor.screenshot( - renderer, - surface, - viewport, - background_color, - overlay, - ), - ( - Self::Secondary(compositor), - Renderer::Secondary(renderer), - Surface::Secondary(surface), - ) => compositor.screenshot( - renderer, - surface, - viewport, - background_color, - overlay, - ), + match (self, renderer) { + (Self::Primary(compositor), Renderer::Primary(renderer)) => { + compositor.screenshot( + renderer, + viewport, + background_color, + overlay, + ) + } + (Self::Secondary(compositor), Renderer::Secondary(renderer)) => { + compositor.screenshot( + renderer, + viewport, + background_color, + overlay, + ) + } _ => unreachable!(), } } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 220542e1..ee20a458 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -23,6 +23,9 @@ pub type Compositor = renderer::Compositor; #[cfg(all(feature = "wgpu", feature = "tiny-skia"))] mod renderer { + use crate::core::renderer; + use crate::core::{Color, Font, Pixels, Size}; + pub type Renderer = crate::fallback::Renderer< iced_wgpu::Renderer, iced_tiny_skia::Renderer, @@ -32,6 +35,31 @@ mod renderer { iced_wgpu::window::Compositor, iced_tiny_skia::window::Compositor, >; + + impl renderer::Headless for Renderer { + fn new(default_font: Font, default_text_size: Pixels) -> Self { + Self::Secondary(iced_tiny_skia::Renderer::new( + default_font, + default_text_size, + )) + } + + fn screenshot( + &mut self, + size: Size<u32>, + scale_factor: f32, + background_color: Color, + ) -> Vec<u8> { + match self { + crate::fallback::Renderer::Primary(_) => unreachable!( + "iced_wgpu does not support headless mode yet!" + ), + crate::fallback::Renderer::Secondary(renderer) => { + renderer.screenshot(size, scale_factor, background_color) + } + } + } + } } #[cfg(all(feature = "wgpu", not(feature = "tiny-skia")))] diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index da3e6929..342ad70c 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -158,7 +158,7 @@ where } /// Processes a runtime [`Event`]. - pub fn on_event( + pub fn update( &mut self, event: Event, layout: Layout<'_>, @@ -166,7 +166,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { fn recurse<Message, Theme, Renderer>( element: &mut overlay::Element<'_, Message, Theme, Renderer>, layout: Layout<'_>, @@ -175,31 +175,30 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> (event::Status, bool) + ) -> bool where Renderer: renderer::Renderer, { let mut layouts = layout.children(); if let Some(layout) = layouts.next() { - let (nested_status, nested_is_over) = - if let Some((mut nested, nested_layout)) = - element.overlay(layout, renderer).zip(layouts.next()) - { - recurse( - &mut nested, - nested_layout, - event.clone(), - cursor, - renderer, - clipboard, - shell, - ) - } else { - (event::Status::Ignored, false) - }; + let nested_is_over = if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse( + &mut nested, + nested_layout, + event.clone(), + cursor, + renderer, + clipboard, + shell, + ) + } else { + false + }; - if matches!(nested_status, event::Status::Ignored) { + if shell.event_status() == event::Status::Ignored { let is_over = nested_is_over || cursor .position() @@ -212,30 +211,29 @@ where }) .unwrap_or_default(); - ( - element.on_event( - event, - layout, - if nested_is_over { - mouse::Cursor::Unavailable - } else { - cursor - }, - renderer, - clipboard, - shell, - ), - is_over, - ) + element.update( + event, + layout, + if nested_is_over { + mouse::Cursor::Unavailable + } else { + cursor + }, + renderer, + clipboard, + shell, + ); + + is_over } else { - (nested_status, nested_is_over) + nested_is_over } } else { - (event::Status::Ignored, false) + false } } - let (status, _) = recurse( + let _ = recurse( &mut self.overlay, layout, event, @@ -244,8 +242,6 @@ where clipboard, shell, ); - - status } /// Returns the current [`mouse::Interaction`] of the [`Nested`] overlay. diff --git a/runtime/src/task.rs b/runtime/src/task.rs index 4554c74b..22cfb63e 100644 --- a/runtime/src/task.rs +++ b/runtime/src/task.rs @@ -9,6 +9,7 @@ use crate::futures::{boxed_stream, BoxStream, MaybeSend}; use crate::Action; use std::future::Future; +use std::sync::Arc; /// A set of concurrent actions to be performed by the iced runtime. /// @@ -183,16 +184,16 @@ impl<T> Task<T> { ( Self(Some(boxed_stream(stream))), Handle { - raw: Some(handle), - abort_on_drop: false, + internal: InternalHandle::Manual(handle), }, ) } None => ( Self(None), Handle { - raw: None, - abort_on_drop: false, + internal: InternalHandle::Manual( + stream::AbortHandle::new_pair().0, + ), }, ), } @@ -220,44 +221,64 @@ impl<T> Task<T> { /// A handle to a [`Task`] that can be used for aborting it. #[derive(Debug, Clone)] pub struct Handle { - raw: Option<stream::AbortHandle>, - abort_on_drop: bool, + internal: InternalHandle, +} + +#[derive(Debug, Clone)] +enum InternalHandle { + Manual(stream::AbortHandle), + AbortOnDrop(Arc<stream::AbortHandle>), +} + +impl InternalHandle { + pub fn as_ref(&self) -> &stream::AbortHandle { + match self { + InternalHandle::Manual(handle) => handle, + InternalHandle::AbortOnDrop(handle) => handle.as_ref(), + } + } } impl Handle { /// Aborts the [`Task`] of this [`Handle`]. pub fn abort(&self) { - if let Some(handle) = &self.raw { - handle.abort(); - } + self.internal.as_ref().abort(); } /// Returns a new [`Handle`] that will call [`Handle::abort`] whenever - /// it is dropped. + /// all of its instances are dropped. + /// + /// If a [`Handle`] is cloned, [`Handle::abort`] will only be called + /// once all of the clones are dropped. /// /// This can be really useful if you do not want to worry about calling /// [`Handle::abort`] yourself. - pub fn abort_on_drop(mut self) -> Self { - Self { - raw: self.raw.take(), - abort_on_drop: true, + pub fn abort_on_drop(self) -> Self { + match &self.internal { + InternalHandle::Manual(handle) => Self { + internal: InternalHandle::AbortOnDrop(Arc::new(handle.clone())), + }, + InternalHandle::AbortOnDrop(_) => self, } } /// Returns `true` if the [`Task`] of this [`Handle`] has been aborted. pub fn is_aborted(&self) -> bool { - if let Some(handle) = &self.raw { - handle.is_aborted() - } else { - true - } + self.internal.as_ref().is_aborted() } } impl Drop for Handle { fn drop(&mut self) { - if self.abort_on_drop { - self.abort(); + if let InternalHandle::AbortOnDrop(handle) = &mut self.internal { + let handle = std::mem::replace( + handle, + Arc::new(stream::AbortHandle::new_pair().0), + ); + + if let Some(handle) = Arc::into_inner(handle) { + handle.abort(); + } } } } diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 8dfc97a7..b2826f71 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -210,7 +210,7 @@ where for event in events.iter().cloned() { let mut shell = Shell::new(messages); - let event_status = overlay.on_event( + overlay.update( event, Layout::new(&layout), cursor, @@ -219,7 +219,7 @@ where &mut shell, ); - event_statuses.push(event_status); + event_statuses.push(shell.event_status()); match (redraw_request, shell.redraw_request()) { (None, Some(at)) => { @@ -308,7 +308,7 @@ where let mut shell = Shell::new(messages); - let event_status = self.root.as_widget_mut().on_event( + self.root.as_widget_mut().update( &mut self.state, event, Layout::new(&self.base), @@ -319,7 +319,7 @@ where &viewport, ); - if matches!(event_status, event::Status::Captured) { + if shell.event_status() == event::Status::Captured { self.overlay = None; } @@ -347,7 +347,7 @@ where outdated = true; } - event_status.merge(overlay_status) + shell.event_status().merge(overlay_status) }) .collect(); diff --git a/runtime/src/window.rs b/runtime/src/window.rs index 382f4518..183fab97 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -1,11 +1,8 @@ //! Build window-based GUI applications. -pub mod screenshot; - -pub use screenshot::Screenshot; - use crate::core::time::Instant; use crate::core::window::{ - Event, Icon, Id, Level, Mode, Settings, UserAttention, + Direction, Event, Icon, Id, Level, Mode, Screenshot, Settings, + UserAttention, }; use crate::core::{Point, Size}; use crate::futures::event; @@ -35,10 +32,17 @@ pub enum Action { /// Move the window with the left mouse button until the button is /// released. /// - /// There’s no guarantee that this will work unless the left mouse + /// There's no guarantee that this will work unless the left mouse /// button was pressed immediately before this function is called. Drag(Id), + /// Resize the window with the left mouse button until the button is + /// released. + /// + /// There's no guarantee that this will work unless the left mouse + /// button was pressed immediately before this function is called. + DragResize(Id, Direction), + /// Resize the window to the given logical dimensions. Resize(Id, Size), @@ -72,7 +76,7 @@ pub enum Action { Move(Id, Point), /// Change the [`Mode`] of the window. - ChangeMode(Id, Mode), + SetMode(Id, Mode), /// Get the current [`Mode`] of the window. GetMode(Id, oneshot::Sender<Mode>), @@ -115,7 +119,7 @@ pub enum Action { GainFocus(Id), /// Change the window [`Level`]. - ChangeLevel(Id, Level), + SetLevel(Id, Level), /// Show the system menu at cursor position. /// @@ -140,7 +144,7 @@ pub enum Action { /// /// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM. That /// said, it's usually in the same ballpark as on Windows. - ChangeIcon(Id, Icon), + SetIcon(Id, Icon), /// Runs the closure with the native window handle of the window with the given [`Id`]. RunWithHandle(Id, Box<dyn FnOnce(WindowHandle<'_>) + Send>), @@ -159,6 +163,18 @@ pub enum Action { /// This enables mouse events for the window and stops mouse events /// from being passed to whatever is underneath. DisableMousePassthrough(Id), + + /// Set the minimum inner window size. + SetMinSize(Id, Option<Size>), + + /// Set the maximum inner window size. + SetMaxSize(Id, Option<Size>), + + /// Set the window to be resizable or not. + SetResizable(Id, bool), + + /// Set the window size increment. + SetResizeIncrements(Id, Option<Size>), } /// Subscribes to the frames of the window of the running application. @@ -264,11 +280,40 @@ pub fn drag<T>(id: Id) -> Task<T> { task::effect(crate::Action::Window(Action::Drag(id))) } +/// Begins resizing the window while the left mouse button is held. +pub fn drag_resize<T>(id: Id, direction: Direction) -> Task<T> { + task::effect(crate::Action::Window(Action::DragResize(id, direction))) +} + /// Resizes the window to the given logical dimensions. pub fn resize<T>(id: Id, new_size: Size) -> Task<T> { task::effect(crate::Action::Window(Action::Resize(id, new_size))) } +/// Set the window to be resizable or not. +pub fn set_resizable<T>(id: Id, resizable: bool) -> Task<T> { + task::effect(crate::Action::Window(Action::SetResizable(id, resizable))) +} + +/// Set the inner maximum size of the window. +pub fn set_max_size<T>(id: Id, size: Option<Size>) -> Task<T> { + task::effect(crate::Action::Window(Action::SetMaxSize(id, size))) +} + +/// Set the inner minimum size of the window. +pub fn set_min_size<T>(id: Id, size: Option<Size>) -> Task<T> { + task::effect(crate::Action::Window(Action::SetMinSize(id, size))) +} + +/// Set the window size increment. +/// +/// This is usually used by apps such as terminal emulators that need "blocky" resizing. +pub fn set_resize_increments<T>(id: Id, increments: Option<Size>) -> Task<T> { + task::effect(crate::Action::Window(Action::SetResizeIncrements( + id, increments, + ))) +} + /// Get the window's size in logical dimensions. pub fn get_size(id: Id) -> Task<Size> { task::oneshot(move |channel| { @@ -319,11 +364,6 @@ pub fn move_to<T>(id: Id, position: Point) -> Task<T> { task::effect(crate::Action::Window(Action::Move(id, position))) } -/// Changes the [`Mode`] of the window. -pub fn change_mode<T>(id: Id, mode: Mode) -> Task<T> { - task::effect(crate::Action::Window(Action::ChangeMode(id, mode))) -} - /// Gets the current [`Mode`] of the window. pub fn get_mode(id: Id) -> Task<Mode> { task::oneshot(move |channel| { @@ -331,6 +371,11 @@ pub fn get_mode(id: Id) -> Task<Mode> { }) } +/// Changes the [`Mode`] of the window. +pub fn set_mode<T>(id: Id, mode: Mode) -> Task<T> { + task::effect(crate::Action::Window(Action::SetMode(id, mode))) +} + /// Toggles the window to maximized or back. pub fn toggle_maximize<T>(id: Id) -> Task<T> { task::effect(crate::Action::Window(Action::ToggleMaximize(id))) @@ -368,8 +413,8 @@ pub fn gain_focus<T>(id: Id) -> Task<T> { } /// Changes the window [`Level`]. -pub fn change_level<T>(id: Id, level: Level) -> Task<T> { - task::effect(crate::Action::Window(Action::ChangeLevel(id, level))) +pub fn set_level<T>(id: Id, level: Level) -> Task<T> { + task::effect(crate::Action::Window(Action::SetLevel(id, level))) } /// Show the [system menu] at cursor position. @@ -388,8 +433,8 @@ pub fn get_raw_id<Message>(id: Id) -> Task<u64> { } /// Changes the [`Icon`] of the window. -pub fn change_icon<T>(id: Id, icon: Icon) -> Task<T> { - task::effect(crate::Action::Window(Action::ChangeIcon(id, icon))) +pub fn set_icon<T>(id: Id, icon: Icon) -> Task<T> { + task::effect(crate::Action::Window(Action::SetIcon(id, icon))) } /// Runs the given callback with the native window handle for the window with the given id. diff --git a/src/application.rs b/src/application.rs index 2ba764be..c79ed62b 100644 --- a/src/application.rs +++ b/src/application.rs @@ -31,6 +31,7 @@ //! } //! ``` use crate::program::{self, Program}; +use crate::theme; use crate::window; use crate::{ Element, Executor, Font, Result, Settings, Size, Subscription, Task, @@ -38,8 +39,6 @@ use crate::{ use std::borrow::Cow; -pub use crate::shell::program::{Appearance, DefaultStyle}; - /// Creates an iced [`Application`] given its title, update, and view logic. /// /// # Example @@ -76,7 +75,7 @@ pub fn application<State, Message, Theme, Renderer>( where State: 'static, Message: Send + std::fmt::Debug + 'static, - Theme: Default + DefaultStyle, + Theme: Default + theme::Base, Renderer: program::Renderer, { use std::marker::PhantomData; @@ -94,7 +93,7 @@ where for Instance<State, Message, Theme, Renderer, Update, View> where Message: Send + std::fmt::Debug + 'static, - Theme: Default + DefaultStyle, + Theme: Default + theme::Base, Renderer: program::Renderer, Update: self::Update<State, Message>, View: for<'a> self::View<'a, State, Message, Theme, Renderer>, @@ -352,7 +351,7 @@ impl<P: Program> Application<P> { /// Sets the style logic of the [`Application`]. pub fn style( self, - f: impl Fn(&P::State, &P::Theme) -> Appearance, + f: impl Fn(&P::State, &P::Theme) -> theme::Style, ) -> Application< impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, > { diff --git a/src/daemon.rs b/src/daemon.rs index 81254bf9..fd6d0278 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,13 +1,12 @@ //! Create and run daemons that run in the background. use crate::application; use crate::program::{self, Program}; +use crate::theme; use crate::window; use crate::{Element, Executor, Font, Result, Settings, Subscription, Task}; use std::borrow::Cow; -pub use crate::shell::program::{Appearance, DefaultStyle}; - /// Creates an iced [`Daemon`] given its title, update, and view logic. /// /// A [`Daemon`] will not open a window by default, but will run silently @@ -26,7 +25,7 @@ pub fn daemon<State, Message, Theme, Renderer>( where State: 'static, Message: Send + std::fmt::Debug + 'static, - Theme: Default + DefaultStyle, + Theme: Default + theme::Base, Renderer: program::Renderer, { use std::marker::PhantomData; @@ -44,7 +43,7 @@ where for Instance<State, Message, Theme, Renderer, Update, View> where Message: Send + std::fmt::Debug + 'static, - Theme: Default + DefaultStyle, + Theme: Default + theme::Base, Renderer: program::Renderer, Update: application::Update<State, Message>, View: for<'a> self::View<'a, State, Message, Theme, Renderer>, @@ -201,7 +200,7 @@ impl<P: Program> Daemon<P> { /// Sets the style logic of the [`Daemon`]. pub fn style( self, - f: impl Fn(&P::State, &P::Theme) -> Appearance, + f: impl Fn(&P::State, &P::Theme) -> theme::Style, ) -> Daemon< impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, > { @@ -491,7 +491,6 @@ mod program; pub mod application; pub mod daemon; -pub mod settings; pub mod time; pub mod window; @@ -506,8 +505,8 @@ pub use crate::core::padding; pub use crate::core::theme; pub use crate::core::{ Alignment, Background, Border, Color, ContentFit, Degrees, Gradient, - Length, Padding, Pixels, Point, Radians, Rectangle, Rotation, Shadow, Size, - Theme, Transformation, Vector, + Length, Padding, Pixels, Point, Radians, Rectangle, Rotation, Settings, + Shadow, Size, Theme, Transformation, Vector, }; pub use crate::runtime::exit; pub use iced_futures::Subscription; @@ -624,8 +623,8 @@ pub use error::Error; pub use event::Event; pub use executor::Executor; pub use font::Font; +pub use program::Program; pub use renderer::Renderer; -pub use settings::Settings; pub use task::Task; #[doc(inline)] @@ -686,7 +685,7 @@ pub fn run<State, Message, Theme, Renderer>( where State: Default + 'static, Message: std::fmt::Debug + Send + 'static, - Theme: Default + program::DefaultStyle + 'static, + Theme: Default + theme::Base + 'static, Renderer: program::Renderer + 'static, { application(title, update, view).run() diff --git a/src/program.rs b/src/program.rs index 94cb9a7d..ace4da74 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,11 +1,10 @@ use crate::core::text; use crate::graphics::compositor; use crate::shell; +use crate::theme; use crate::window; use crate::{Element, Executor, Result, Settings, Subscription, Task}; -pub use crate::shell::program::{Appearance, DefaultStyle}; - /// The internal definition of a [`Program`]. /// /// You should not need to implement this trait directly. Instead, use the @@ -19,7 +18,7 @@ pub trait Program: Sized { type Message: Send + std::fmt::Debug + 'static; /// The theme of the program. - type Theme: Default + DefaultStyle; + type Theme: Default + theme::Base; /// The renderer of the program. type Renderer: Renderer; @@ -51,11 +50,11 @@ pub trait Program: Sized { } fn theme(&self, _state: &Self::State, _window: window::Id) -> Self::Theme { - Self::Theme::default() + <Self::Theme as Default>::default() } - fn style(&self, _state: &Self::State, theme: &Self::Theme) -> Appearance { - DefaultStyle::default_style(theme) + fn style(&self, _state: &Self::State, theme: &Self::Theme) -> theme::Style { + theme::Base::base(theme) } fn scale_factor(&self, _state: &Self::State, _window: window::Id) -> f64 { @@ -153,7 +152,7 @@ pub trait Program: Sized { self.program.theme(&self.state, window) } - fn style(&self, theme: &Self::Theme) -> Appearance { + fn style(&self, theme: &Self::Theme) -> theme::Style { self.program.style(&self.state, theme) } @@ -252,7 +251,7 @@ pub fn with_title<P: Program>( &self, state: &Self::State, theme: &Self::Theme, - ) -> Appearance { + ) -> theme::Style { self.program.style(state, theme) } @@ -322,7 +321,7 @@ pub fn with_subscription<P: Program>( &self, state: &Self::State, theme: &Self::Theme, - ) -> Appearance { + ) -> theme::Style { self.program.style(state, theme) } @@ -395,7 +394,7 @@ pub fn with_theme<P: Program>( &self, state: &Self::State, theme: &Self::Theme, - ) -> Appearance { + ) -> theme::Style { self.program.style(state, theme) } @@ -409,7 +408,7 @@ pub fn with_theme<P: Program>( pub fn with_style<P: Program>( program: P, - f: impl Fn(&P::State, &P::Theme) -> Appearance, + f: impl Fn(&P::State, &P::Theme) -> theme::Style, ) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { struct WithStyle<P, F> { program: P, @@ -418,7 +417,7 @@ pub fn with_style<P: Program>( impl<P: Program, F> Program for WithStyle<P, F> where - F: Fn(&P::State, &P::Theme) -> Appearance, + F: Fn(&P::State, &P::Theme) -> theme::Style, { type State = P::State; type Message = P::Message; @@ -430,7 +429,7 @@ pub fn with_style<P: Program>( &self, state: &Self::State, theme: &Self::Theme, - ) -> Appearance { + ) -> theme::Style { (self.style)(state, theme) } @@ -535,7 +534,7 @@ pub fn with_scale_factor<P: Program>( &self, state: &Self::State, theme: &Self::Theme, - ) -> Appearance { + ) -> theme::Style { self.program.style(state, theme) } @@ -609,7 +608,7 @@ pub fn with_executor<P: Program, E: Executor>( &self, state: &Self::State, theme: &Self::Theme, - ) -> Appearance { + ) -> theme::Style { self.program.style(state, theme) } diff --git a/src/window/icon.rs b/src/window/icon.rs index 7fe4ca7b..f453d580 100644 --- a/src/window/icon.rs +++ b/src/window/icon.rs @@ -13,7 +13,7 @@ use std::path::Path; /// This will return an error in case the file is missing at run-time. You may prefer [`from_file_data`] instead. #[cfg(feature = "image")] pub fn from_file<P: AsRef<Path>>(icon_path: P) -> Result<Icon, Error> { - let icon = image::io::Reader::open(icon_path)?.decode()?.to_rgba8(); + let icon = image::ImageReader::open(icon_path)?.decode()?.to_rgba8(); Ok(icon::from_rgba(icon.to_vec(), icon.width(), icon.height())?) } @@ -27,7 +27,7 @@ pub fn from_file_data( data: &[u8], explicit_format: Option<image::ImageFormat>, ) -> Result<Icon, Error> { - let mut icon = image::io::Reader::new(std::io::Cursor::new(data)); + let mut icon = image::ImageReader::new(std::io::Cursor::new(data)); let icon_with_format = match explicit_format { Some(format) => { diff --git a/test/Cargo.toml b/test/Cargo.toml new file mode 100644 index 00000000..2dd35e7f --- /dev/null +++ b/test/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "iced_test" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] +iced_runtime.workspace = true + +iced_renderer.workspace = true +iced_renderer.features = ["fira-sans"] + +png.workspace = true +sha2.workspace = true +thiserror.workspace = true diff --git a/test/src/lib.rs b/test/src/lib.rs new file mode 100644 index 00000000..73726f08 --- /dev/null +++ b/test/src/lib.rs @@ -0,0 +1,637 @@ +//! Test your `iced` applications in headless mode. +//! +//! # Basic Usage +//! Let's assume we want to test [the classical counter interface]. +//! +//! First, we will want to create a [`Simulator`] of our interface: +//! +//! ```rust,no_run +//! # struct Counter { value: i64 } +//! # impl Counter { +//! # pub fn view(&self) -> iced_runtime::core::Element<(), iced_runtime::core::Theme, iced_renderer::Renderer> { unimplemented!() } +//! # } +//! use iced_test::simulator; +//! +//! let mut counter = Counter { value: 0 }; +//! let mut ui = simulator(counter.view()); +//! ``` +//! +//! Now we can simulate a user interacting with our interface. Let's use [`Simulator::click`] to click +//! the counter buttons: +//! +//! ```rust,no_run +//! # struct Counter { value: i64 } +//! # impl Counter { +//! # pub fn view(&self) -> iced_runtime::core::Element<(), iced_runtime::core::Theme, iced_renderer::Renderer> { unimplemented!() } +//! # } +//! use iced_test::selector::text; +//! # use iced_test::simulator; +//! # +//! # let mut counter = Counter { value: 0 }; +//! # let mut ui = simulator(counter.view()); +//! +//! let _ = ui.click(text("+")); +//! let _ = ui.click(text("+")); +//! let _ = ui.click(text("-")); +//! ``` +//! +//! [`Simulator::click`] takes a [`Selector`]. A [`Selector`] describes a way to query the widgets of an interface. In this case, +//! [`selector::text`] lets us select a widget by the text it contains. +//! +//! We can now process any messages produced by these interactions and then assert that the final value of our counter is +//! indeed `1`! +//! +//! ```rust,no_run +//! # struct Counter { value: i64 } +//! # impl Counter { +//! # pub fn update(&mut self, message: ()) {} +//! # pub fn view(&self) -> iced_runtime::core::Element<(), iced_runtime::core::Theme, iced_renderer::Renderer> { unimplemented!() } +//! # } +//! # use iced_test::selector::text; +//! # use iced_test::simulator; +//! # +//! # let mut counter = Counter { value: 0 }; +//! # let mut ui = simulator(counter.view()); +//! # +//! # let _ = ui.click(text("+")); +//! # let _ = ui.click(text("+")); +//! # let _ = ui.click(text("-")); +//! # +//! for message in ui.into_messages() { +//! counter.update(message); +//! } +//! +//! assert_eq!(counter.value, 1); +//! ``` +//! +//! We can even rebuild the interface to make sure the counter _displays_ the proper value with [`Simulator::find`]: +//! +//! ```rust,no_run +//! # struct Counter { value: i64 } +//! # impl Counter { +//! # pub fn view(&self) -> iced_runtime::core::Element<(), iced_runtime::core::Theme, iced_renderer::Renderer> { unimplemented!() } +//! # } +//! # use iced_test::selector::text; +//! # use iced_test::simulator; +//! # +//! # let mut counter = Counter { value: 0 }; +//! let mut ui = simulator(counter.view()); +//! +//! assert!(ui.find(text("1")).is_ok(), "Counter should display 1!"); +//! ``` +//! +//! And that's it! That's the gist of testing `iced` applications! +//! +//! [`Simulator`] contains additional operations you can use to simulate more interactions—like [`tap_key`](Simulator::tap_key) or +//! [`typewrite`](Simulator::typewrite)—and even perform [_snapshot testing_](Simulator::snapshot)! +//! +//! [the classical counter interface]: https://book.iced.rs/architecture.html#dissecting-an-interface +pub mod selector; + +pub use selector::Selector; + +use iced_renderer as renderer; +use iced_runtime as runtime; +use iced_runtime::core; + +use crate::core::clipboard; +use crate::core::event; +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::theme; +use crate::core::time; +use crate::core::widget; +use crate::core::window; +use crate::core::{ + Element, Event, Font, Point, Rectangle, Settings, Size, SmolStr, +}; +use crate::runtime::user_interface; +use crate::runtime::UserInterface; + +use std::borrow::Cow; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +/// Creates a new [`Simulator`]. +/// +/// This is just a function version of [`Simulator::new`]. +pub fn simulator<'a, Message, Theme, Renderer>( + element: impl Into<Element<'a, Message, Theme, Renderer>>, +) -> Simulator<'a, Message, Theme, Renderer> +where + Theme: theme::Base, + Renderer: core::Renderer + core::renderer::Headless, +{ + Simulator::new(element) +} + +/// A user interface that can be interacted with and inspected programmatically. +#[allow(missing_debug_implementations)] +pub struct Simulator< + 'a, + Message, + Theme = core::Theme, + Renderer = renderer::Renderer, +> { + raw: UserInterface<'a, Message, Theme, Renderer>, + renderer: Renderer, + size: Size, + cursor: mouse::Cursor, + messages: Vec<Message>, +} + +/// A specific area of a [`Simulator`], normally containing a widget. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Target { + /// The bounds of the area. + pub bounds: Rectangle, +} + +impl<'a, Message, Theme, Renderer> Simulator<'a, Message, Theme, Renderer> +where + Theme: theme::Base, + Renderer: core::Renderer + core::renderer::Headless, +{ + /// Creates a new [`Simulator`] with default [`Settings`] and a default size (1024x768). + pub fn new( + element: impl Into<Element<'a, Message, Theme, Renderer>>, + ) -> Self { + Self::with_settings(Settings::default(), element) + } + + /// Creates a new [`Simulator`] with the given [`Settings`] and a default size (1024x768). + pub fn with_settings( + settings: Settings, + element: impl Into<Element<'a, Message, Theme, Renderer>>, + ) -> Self { + Self::with_size(settings, window::Settings::default().size, element) + } + + /// Creates a new [`Simulator`] with the given [`Settings`] and size. + pub fn with_size( + settings: Settings, + size: impl Into<Size>, + element: impl Into<Element<'a, Message, Theme, Renderer>>, + ) -> Self { + let size = size.into(); + + let default_font = match settings.default_font { + Font::DEFAULT => Font::with_name("Fira Sans"), + _ => settings.default_font, + }; + + for font in settings.fonts { + load_font(font).expect("Font must be valid"); + } + + let mut renderer = + Renderer::new(default_font, settings.default_text_size); + + let raw = UserInterface::build( + element, + size, + user_interface::Cache::default(), + &mut renderer, + ); + + Simulator { + raw, + renderer, + size, + cursor: mouse::Cursor::Unavailable, + messages: Vec::new(), + } + } + + /// Finds the [`Target`] of the given widget [`Selector`] in the [`Simulator`]. + pub fn find( + &mut self, + selector: impl Into<Selector>, + ) -> Result<Target, Error> { + let selector = selector.into(); + + match &selector { + Selector::Id(id) => { + struct FindById<'a> { + id: &'a widget::Id, + target: Option<Target>, + } + + impl widget::Operation for FindById<'_> { + fn container( + &mut self, + id: Option<&widget::Id>, + bounds: Rectangle, + operate_on_children: &mut dyn FnMut( + &mut dyn widget::Operation<()>, + ), + ) { + if self.target.is_some() { + return; + } + + if Some(self.id) == id { + self.target = Some(Target { bounds }); + return; + } + + operate_on_children(self); + } + + fn scrollable( + &mut self, + id: Option<&widget::Id>, + bounds: Rectangle, + _content_bounds: Rectangle, + _translation: core::Vector, + _state: &mut dyn widget::operation::Scrollable, + ) { + if self.target.is_some() { + return; + } + + if Some(self.id) == id { + self.target = Some(Target { bounds }); + } + } + + fn text_input( + &mut self, + id: Option<&widget::Id>, + bounds: Rectangle, + _state: &mut dyn widget::operation::TextInput, + ) { + if self.target.is_some() { + return; + } + + if Some(self.id) == id { + self.target = Some(Target { bounds }); + } + } + + fn text( + &mut self, + id: Option<&widget::Id>, + bounds: Rectangle, + _text: &str, + ) { + if self.target.is_some() { + return; + } + + if Some(self.id) == id { + self.target = Some(Target { bounds }); + } + } + + fn custom( + &mut self, + id: Option<&widget::Id>, + bounds: Rectangle, + _state: &mut dyn std::any::Any, + ) { + if self.target.is_some() { + return; + } + + if Some(self.id) == id { + self.target = Some(Target { bounds }); + } + } + } + + let mut find = FindById { id, target: None }; + self.raw.operate(&self.renderer, &mut find); + + find.target.ok_or(Error::NotFound(selector)) + } + Selector::Text(text) => { + struct FindByText<'a> { + text: &'a str, + target: Option<Target>, + } + + impl widget::Operation for FindByText<'_> { + fn container( + &mut self, + _id: Option<&widget::Id>, + _bounds: Rectangle, + operate_on_children: &mut dyn FnMut( + &mut dyn widget::Operation<()>, + ), + ) { + if self.target.is_some() { + return; + } + + operate_on_children(self); + } + + fn text( + &mut self, + _id: Option<&widget::Id>, + bounds: Rectangle, + text: &str, + ) { + if self.target.is_some() { + return; + } + + if self.text == text { + self.target = Some(Target { bounds }); + } + } + } + + let mut find = FindByText { text, target: None }; + self.raw.operate(&self.renderer, &mut find); + + find.target.ok_or(Error::NotFound(selector)) + } + } + } + + /// Points the mouse cursor at the given position in the [`Simulator`]. + /// + /// This does _not_ produce mouse movement events! + pub fn point_at(&mut self, position: impl Into<Point>) { + self.cursor = mouse::Cursor::Available(position.into()); + } + + /// Clicks the [`Target`] found by the given [`Selector`], if any. + /// + /// This consists in: + /// - Pointing the mouse cursor at the center of the [`Target`]. + /// - Simulating a [`click`]. + pub fn click( + &mut self, + selector: impl Into<Selector>, + ) -> Result<Target, Error> { + let target = self.find(selector)?; + self.point_at(target.bounds.center()); + + let _ = self.simulate(click()); + + Ok(target) + } + + /// Simulates a key press, followed by a release, in the [`Simulator`]. + pub fn tap_key(&mut self, key: impl Into<keyboard::Key>) -> event::Status { + self.simulate(tap_key(key, None)) + .first() + .copied() + .unwrap_or(event::Status::Ignored) + } + + /// Simulates a user typing in the keyboard the given text in the [`Simulator`]. + pub fn typewrite(&mut self, text: &str) -> event::Status { + let statuses = self.simulate(typewrite(text)); + + statuses + .into_iter() + .fold(event::Status::Ignored, event::Status::merge) + } + + /// Simulates the given raw sequence of events in the [`Simulator`]. + pub fn simulate( + &mut self, + events: impl IntoIterator<Item = Event>, + ) -> Vec<event::Status> { + let events: Vec<Event> = events.into_iter().collect(); + + let (_state, statuses) = self.raw.update( + &events, + self.cursor, + &mut self.renderer, + &mut clipboard::Null, + &mut self.messages, + ); + + statuses + } + + /// Draws and takes a [`Snapshot`] of the interface in the [`Simulator`]. + pub fn snapshot(&mut self, theme: &Theme) -> Result<Snapshot, Error> { + let base = theme.base(); + + let _ = self.raw.update( + &[Event::Window(window::Event::RedrawRequested( + time::Instant::now(), + ))], + self.cursor, + &mut self.renderer, + &mut clipboard::Null, + &mut self.messages, + ); + + let _ = self.raw.draw( + &mut self.renderer, + theme, + &core::renderer::Style { + text_color: base.text_color, + }, + self.cursor, + ); + + let scale_factor = 2.0; + + let physical_size = Size::new( + (self.size.width * scale_factor).round() as u32, + (self.size.height * scale_factor).round() as u32, + ); + + let rgba = self.renderer.screenshot( + physical_size, + scale_factor, + base.background_color, + ); + + Ok(Snapshot { + screenshot: window::Screenshot::new( + rgba, + physical_size, + f64::from(scale_factor), + ), + }) + } + + /// Turns the [`Simulator`] into the sequence of messages produced by any interactions. + pub fn into_messages(self) -> impl Iterator<Item = Message> { + self.messages.into_iter() + } +} + +/// A frame of a user interface rendered by a [`Simulator`]. +#[derive(Debug, Clone)] +pub struct Snapshot { + screenshot: window::Screenshot, +} + +impl Snapshot { + /// Compares the [`Snapshot`] with the PNG image found in the given path, returning + /// `true` if they are identical. + /// + /// If the PNG image does not exist, it will be created by the [`Snapshot`] for future + /// testing and `true` will be returned. + pub fn matches_image(&self, path: impl AsRef<Path>) -> Result<bool, Error> { + let path = snapshot_path(path, "png"); + + if path.exists() { + let file = fs::File::open(&path)?; + let decoder = png::Decoder::new(file); + + let mut reader = decoder.read_info()?; + let mut bytes = vec![0; reader.output_buffer_size()]; + let info = reader.next_frame(&mut bytes)?; + + Ok(self.screenshot.bytes == bytes[..info.buffer_size()]) + } else { + if let Some(directory) = path.parent() { + fs::create_dir_all(directory)?; + } + + let file = fs::File::create(path)?; + + let mut encoder = png::Encoder::new( + file, + self.screenshot.size.width, + self.screenshot.size.height, + ); + encoder.set_color(png::ColorType::Rgba); + + let mut writer = encoder.write_header()?; + writer.write_image_data(&self.screenshot.bytes)?; + writer.finish()?; + + Ok(true) + } + } + + /// Compares the [`Snapshot`] with the SHA-256 hash file found in the given path, returning + /// `true` if they are identical. + /// + /// If the hash file does not exist, it will be created by the [`Snapshot`] for future + /// testing and `true` will be returned. + pub fn matches_hash(&self, path: impl AsRef<Path>) -> Result<bool, Error> { + use sha2::{Digest, Sha256}; + + let path = snapshot_path(path, "sha256"); + + let hash = { + let mut hasher = Sha256::new(); + hasher.update(&self.screenshot.bytes); + format!("{:x}", hasher.finalize()) + }; + + if path.exists() { + let saved_hash = fs::read_to_string(&path)?; + + Ok(hash == saved_hash) + } else { + if let Some(directory) = path.parent() { + fs::create_dir_all(directory)?; + } + + fs::write(path, hash)?; + Ok(true) + } + } +} + +/// Returns the sequence of events of a click. +pub fn click() -> impl Iterator<Item = Event> { + [ + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)), + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)), + ] + .into_iter() +} + +/// Returns the sequence of events of a "key tap" (i.e. pressing and releasing a key). +pub fn tap_key( + key: impl Into<keyboard::Key>, + text: Option<SmolStr>, +) -> impl Iterator<Item = Event> { + let key = key.into(); + + [ + Event::Keyboard(keyboard::Event::KeyPressed { + key: key.clone(), + modified_key: key.clone(), + physical_key: keyboard::key::Physical::Unidentified( + keyboard::key::NativeCode::Unidentified, + ), + location: keyboard::Location::Standard, + modifiers: keyboard::Modifiers::default(), + text, + }), + Event::Keyboard(keyboard::Event::KeyReleased { + key: key.clone(), + modified_key: key, + physical_key: keyboard::key::Physical::Unidentified( + keyboard::key::NativeCode::Unidentified, + ), + location: keyboard::Location::Standard, + modifiers: keyboard::Modifiers::default(), + }), + ] + .into_iter() +} + +/// Returns the sequence of events of typewriting the given text in a keyboard. +pub fn typewrite(text: &str) -> impl Iterator<Item = Event> + '_ { + text.chars() + .map(|c| SmolStr::new_inline(&c.to_string())) + .flat_map(|c| tap_key(keyboard::Key::Character(c.clone()), Some(c))) +} + +/// A test error. +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { + /// No matching widget was found for the [`Selector`]. + #[error("no matching widget was found for the selector: {0:?}")] + NotFound(Selector), + /// An IO operation failed. + #[error("an IO operation failed: {0}")] + IOFailed(Arc<io::Error>), + /// The decoding of some PNG image failed. + #[error("the decoding of some PNG image failed: {0}")] + PngDecodingFailed(Arc<png::DecodingError>), + /// The encoding of some PNG image failed. + #[error("the encoding of some PNG image failed: {0}")] + PngEncodingFailed(Arc<png::EncodingError>), +} + +impl From<io::Error> for Error { + fn from(error: io::Error) -> Self { + Self::IOFailed(Arc::new(error)) + } +} + +impl From<png::DecodingError> for Error { + fn from(error: png::DecodingError) -> Self { + Self::PngDecodingFailed(Arc::new(error)) + } +} + +impl From<png::EncodingError> for Error { + fn from(error: png::EncodingError) -> Self { + Self::PngEncodingFailed(Arc::new(error)) + } +} + +fn load_font(font: impl Into<Cow<'static, [u8]>>) -> Result<(), Error> { + renderer::graphics::text::font_system() + .write() + .expect("Write to font system") + .load_font(font.into()); + + Ok(()) +} + +fn snapshot_path(path: impl AsRef<Path>, extension: &str) -> PathBuf { + path.as_ref().with_extension(extension) +} diff --git a/test/src/selector.rs b/test/src/selector.rs new file mode 100644 index 00000000..7b8dcb7e --- /dev/null +++ b/test/src/selector.rs @@ -0,0 +1,29 @@ +//! Select widgets of a user interface. +use crate::core::text; +use crate::core::widget; + +/// A selector describes a strategy to find a certain widget in a user interface. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Selector { + /// Find the widget with the given [`widget::Id`]. + Id(widget::Id), + /// Find the widget containing the given [`text::Fragment`]. + Text(text::Fragment<'static>), +} + +impl From<widget::Id> for Selector { + fn from(id: widget::Id) -> Self { + Self::Id(id) + } +} + +impl From<&'static str> for Selector { + fn from(id: &'static str) -> Self { + Self::Id(widget::Id::new(id)) + } +} + +/// Creates [`Selector`] that finds the widget containing the given text fragment. +pub fn text(fragment: impl text::IntoFragment<'static>) -> Selector { + Selector::Text(fragment.into_fragment()) +} diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index 758921d4..a42f1de4 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -29,7 +29,7 @@ pub use geometry::Geometry; use crate::core::renderer; use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Transformation, + Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, }; use crate::engine::Engine; use crate::graphics::compositor; @@ -405,3 +405,26 @@ impl core::svg::Renderer for Renderer { impl compositor::Default for Renderer { type Compositor = window::Compositor; } + +impl renderer::Headless for Renderer { + fn new(default_font: Font, default_text_size: Pixels) -> Self { + Self::new(default_font, default_text_size) + } + + fn screenshot( + &mut self, + size: Size<u32>, + scale_factor: f32, + background_color: Color, + ) -> Vec<u8> { + let viewport = + Viewport::with_physical_size(size, f64::from(scale_factor)); + + window::compositor::screenshot::<&str>( + self, + &viewport, + background_color, + &[], + ) + } +} diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 153af6d5..6c144be0 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -121,12 +121,11 @@ impl crate::graphics::Compositor for Compositor { fn screenshot<T: AsRef<str>>( &mut self, renderer: &mut Self::Renderer, - surface: &mut Self::Surface, viewport: &Viewport, background_color: Color, overlay: &[T], ) -> Vec<u8> { - screenshot(renderer, surface, viewport, background_color, overlay) + screenshot(renderer, viewport, background_color, overlay) } } @@ -212,7 +211,6 @@ pub fn present<T: AsRef<str>>( pub fn screenshot<T: AsRef<str>>( renderer: &mut Renderer, - surface: &mut Surface, viewport: &Viewport, background_color: Color, overlay: &[T], @@ -222,6 +220,9 @@ pub fn screenshot<T: AsRef<str>>( let mut offscreen_buffer: Vec<u32> = vec![0; size.width as usize * size.height as usize]; + let mut clip_mask = tiny_skia::Mask::new(size.width, size.height) + .expect("Create clip mask"); + renderer.draw( &mut tiny_skia::PixmapMut::from_bytes( bytemuck::cast_slice_mut(&mut offscreen_buffer), @@ -229,7 +230,7 @@ pub fn screenshot<T: AsRef<str>>( size.height, ) .expect("Create offscreen pixel map"), - &mut surface.clip_mask, + &mut clip_mask, viewport, &[Rectangle::with_size(Size::new( size.width as f32, diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index a8ebf3aa..4b6b0483 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -35,7 +35,6 @@ glam.workspace = true glyphon.workspace = true guillotiere.workspace = true log.workspace = true -once_cell.workspace = true rustc-hash.workspace = true thiserror.workspace = true wgpu.workspace = true diff --git a/wgpu/src/color.rs b/wgpu/src/color.rs index effac8da..0f2c202f 100644 --- a/wgpu/src/color.rs +++ b/wgpu/src/color.rs @@ -108,14 +108,14 @@ pub fn convert( layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, - entry_point: "vs_main", + entry_point: Some("vs_main"), buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default( ), }, fragment: Some(wgpu::FragmentState { module: &shader, - entry_point: "fs_main", + entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format, blend: Some(wgpu::BlendState { diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index cf83c3f2..caac0813 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -128,7 +128,7 @@ impl Pipeline { layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, - entry_point: "vs_main", + entry_point: Some("vs_main"), buffers: &[wgpu::VertexBufferLayout { array_stride: mem::size_of::<Instance>() as u64, step_mode: wgpu::VertexStepMode::Instance, @@ -158,7 +158,7 @@ impl Pipeline { }, fragment: Some(wgpu::FragmentState { module: &shader, - entry_point: "fs_main", + entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format, blend: Some(wgpu::BlendState { diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs index 207b0d73..3d4ca4db 100644 --- a/wgpu/src/quad/gradient.rs +++ b/wgpu/src/quad/gradient.rs @@ -124,7 +124,7 @@ impl Pipeline { layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, - entry_point: "gradient_vs_main", + entry_point: Some("gradient_vs_main"), buffers: &[wgpu::VertexBufferLayout { array_stride: std::mem::size_of::<Gradient>() as u64, @@ -157,7 +157,7 @@ impl Pipeline { }, fragment: Some(wgpu::FragmentState { module: &shader, - entry_point: "gradient_fs_main", + entry_point: Some("gradient_fs_main"), targets: &quad::color_target_state(format), compilation_options: wgpu::PipelineCompilationOptions::default(), diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs index 86f118d6..f3e85ce7 100644 --- a/wgpu/src/quad/solid.rs +++ b/wgpu/src/quad/solid.rs @@ -89,7 +89,7 @@ impl Pipeline { layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, - entry_point: "solid_vs_main", + entry_point: Some("solid_vs_main"), buffers: &[wgpu::VertexBufferLayout { array_stride: std::mem::size_of::<Solid>() as u64, step_mode: wgpu::VertexStepMode::Instance, @@ -119,7 +119,7 @@ impl Pipeline { }, fragment: Some(wgpu::FragmentState { module: &shader, - entry_point: "solid_fs_main", + entry_point: Some("solid_fs_main"), targets: &quad::color_target_state(format), compilation_options: wgpu::PipelineCompilationOptions::default(), diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index bf7eae18..591bc0b7 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -7,9 +7,8 @@ use crate::graphics::text::{font_system, to_color, Editor, Paragraph}; use rustc_hash::FxHashMap; use std::collections::hash_map; -use std::rc::{self, Rc}; use std::sync::atomic::{self, AtomicU64}; -use std::sync::Arc; +use std::sync::{self, Arc}; pub use crate::graphics::Text; @@ -37,7 +36,7 @@ pub enum Item { pub struct Cache { id: Id, group: cache::Group, - text: Rc<[Text]>, + text: Arc<[Text]>, version: usize, } @@ -55,7 +54,7 @@ impl Cache { Some(Self { id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), group, - text: Rc::from(text), + text: Arc::from(text), version: 0, }) } @@ -65,7 +64,7 @@ impl Cache { return; } - self.text = Rc::from(text); + self.text = Arc::from(text); self.version += 1; } } @@ -76,8 +75,8 @@ struct Upload { transformation: Transformation, version: usize, group_version: usize, - text: rc::Weak<[Text]>, - _atlas: rc::Weak<()>, + text: sync::Weak<[Text]>, + _atlas: sync::Weak<()>, } #[derive(Default)] @@ -90,7 +89,7 @@ struct Group { atlas: glyphon::TextAtlas, version: usize, should_trim: bool, - handle: Rc<()>, // Keeps track of active uploads + handle: Arc<()>, // Keeps track of active uploads } impl Storage { @@ -136,7 +135,7 @@ impl Storage { ), version: 0, should_trim: false, - handle: Rc::new(()), + handle: Arc::new(()), } }); @@ -167,7 +166,7 @@ impl Storage { group.should_trim = group.should_trim || upload.version != cache.version; - upload.text = Rc::downgrade(&cache.text); + upload.text = Arc::downgrade(&cache.text); upload.version = cache.version; upload.group_version = group.version; upload.transformation = new_transformation; @@ -206,8 +205,8 @@ impl Storage { transformation: new_transformation, version: 0, group_version: group.version, - text: Rc::downgrade(&cache.text), - _atlas: Rc::downgrade(&group.handle), + text: Arc::downgrade(&cache.text), + _atlas: Arc::downgrade(&group.handle), }); group.should_trim = cache.group.is_singleton(); @@ -226,7 +225,7 @@ impl Storage { .retain(|_id, upload| upload.text.strong_count() > 0); self.groups.retain(|id, group| { - let active_uploads = Rc::weak_count(&group.handle); + let active_uploads = Arc::weak_count(&group.handle); if active_uploads == 0 { log::debug!("Dropping text atlas: {id:?}"); diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index fb858c10..ab88be3b 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -8,8 +8,8 @@ use crate::Buffer; use rustc_hash::FxHashMap; use std::collections::hash_map; -use std::rc::{self, Rc}; use std::sync::atomic::{self, AtomicU64}; +use std::sync::{self, Arc}; const INITIAL_INDEX_COUNT: usize = 1_000; const INITIAL_VERTEX_COUNT: usize = 1_000; @@ -31,7 +31,7 @@ pub enum Item { #[derive(Debug, Clone)] pub struct Cache { id: Id, - batch: Rc<[Mesh]>, + batch: Arc<[Mesh]>, version: usize, } @@ -48,13 +48,13 @@ impl Cache { Some(Self { id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), - batch: Rc::from(meshes), + batch: Arc::from(meshes), version: 0, }) } pub fn update(&mut self, meshes: Vec<Mesh>) { - self.batch = Rc::from(meshes); + self.batch = Arc::from(meshes); self.version += 1; } } @@ -64,7 +64,7 @@ struct Upload { layer: Layer, transformation: Transformation, version: usize, - batch: rc::Weak<[Mesh]>, + batch: sync::Weak<[Mesh]>, } #[derive(Debug, Default)] @@ -113,7 +113,7 @@ impl Storage { new_transformation, ); - upload.batch = Rc::downgrade(&cache.batch); + upload.batch = Arc::downgrade(&cache.batch); upload.version = cache.version; upload.transformation = new_transformation; } @@ -135,7 +135,7 @@ impl Storage { layer, transformation: new_transformation, version: 0, - batch: Rc::downgrade(&cache.batch), + batch: Arc::downgrade(&cache.batch), }); log::debug!( @@ -753,7 +753,7 @@ mod solid { layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, - entry_point: "solid_vs_main", + entry_point: Some("solid_vs_main"), buffers: &[wgpu::VertexBufferLayout { array_stride: std::mem::size_of::< mesh::SolidVertex2D, @@ -773,7 +773,7 @@ mod solid { }, fragment: Some(wgpu::FragmentState { module: &shader, - entry_point: "solid_fs_main", + entry_point: Some("solid_fs_main"), targets: &[Some(triangle::fragment_target(format))], compilation_options: wgpu::PipelineCompilationOptions::default(), @@ -926,7 +926,7 @@ mod gradient { layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, - entry_point: "gradient_vs_main", + entry_point: Some("gradient_vs_main"), buffers: &[wgpu::VertexBufferLayout { array_stride: std::mem::size_of::< mesh::GradientVertex2D, @@ -955,7 +955,7 @@ mod gradient { }, fragment: Some(wgpu::FragmentState { module: &shader, - entry_point: "gradient_fs_main", + entry_point: Some("gradient_fs_main"), targets: &[Some(triangle::fragment_target(format))], compilation_options: wgpu::PipelineCompilationOptions::default(), diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index ec06e747..0a5b134f 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -110,14 +110,14 @@ impl Blit { layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, - entry_point: "vs_main", + entry_point: Some("vs_main"), buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { module: &shader, - entry_point: "fs_main", + entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format, blend: Some( diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 56f33b50..4fe689cf 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -370,7 +370,6 @@ impl graphics::Compositor for Compositor { fn screenshot<T: AsRef<str>>( &mut self, renderer: &mut Self::Renderer, - _surface: &mut Self::Surface, viewport: &Viewport, background_color: Color, overlay: &[T], diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 98a81145..e19cad08 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -33,7 +33,6 @@ iced_renderer.workspace = true iced_runtime.workspace = true num-traits.workspace = true -once_cell.workspace = true rustc-hash.workspace = true thiserror.workspace = true unicode-segmentation.workspace = true diff --git a/widget/src/action.rs b/widget/src/action.rs new file mode 100644 index 00000000..1dd3a787 --- /dev/null +++ b/widget/src/action.rs @@ -0,0 +1,89 @@ +use crate::core::event; +use crate::core::time::Instant; +use crate::core::window; + +/// A runtime action that can be performed by some widgets. +#[derive(Debug, Clone)] +pub struct Action<Message> { + message_to_publish: Option<Message>, + redraw_request: Option<window::RedrawRequest>, + event_status: event::Status, +} + +impl<Message> Action<Message> { + fn new() -> Self { + Self { + message_to_publish: None, + redraw_request: None, + event_status: event::Status::Ignored, + } + } + + /// Creates a new "capturing" [`Action`]. A capturing [`Action`] + /// will make other widgets consider it final and prevent further + /// processing. + /// + /// Prevents "event bubbling". + pub fn capture() -> Self { + Self { + event_status: event::Status::Captured, + ..Self::new() + } + } + + /// Creates a new [`Action`] that publishes the given `Message` for + /// the application to handle. + /// + /// Publishing a `Message` always produces a redraw. + pub fn publish(message: Message) -> Self { + Self { + message_to_publish: Some(message), + ..Self::new() + } + } + + /// Creates a new [`Action`] that requests a redraw to happen as + /// soon as possible; without publishing any `Message`. + pub fn request_redraw() -> Self { + Self { + redraw_request: Some(window::RedrawRequest::NextFrame), + ..Self::new() + } + } + + /// Creates a new [`Action`] that requests a redraw to happen at + /// the given [`Instant`]; without publishing any `Message`. + /// + /// This can be useful to efficiently animate content, like a + /// blinking caret on a text input. + pub fn request_redraw_at(at: Instant) -> Self { + Self { + redraw_request: Some(window::RedrawRequest::At(at)), + ..Self::new() + } + } + + /// Marks the [`Action`] as "capturing". See [`Self::capture`]. + pub fn and_capture(mut self) -> Self { + self.event_status = event::Status::Captured; + self + } + + /// Converts the [`Action`] into its internal parts. + /// + /// This method is meant to be used by runtimes, libraries, or internal + /// widget implementations. + pub fn into_inner( + self, + ) -> ( + Option<Message>, + Option<window::RedrawRequest>, + event::Status, + ) { + ( + self.message_to_publish, + self.redraw_request, + self.event_status, + ) + } +} diff --git a/widget/src/button.rs b/widget/src/button.rs index a3394a01..11839d5e 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -17,7 +17,6 @@ //! } //! ``` use crate::core::border::{self, Border}; -use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; @@ -26,9 +25,10 @@ use crate::core::theme::palette; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::Operation; +use crate::core::window; use crate::core::{ - Background, Clipboard, Color, Element, Layout, Length, Padding, Rectangle, - Shadow, Shell, Size, Theme, Vector, Widget, + Background, Clipboard, Color, Element, Event, Layout, Length, Padding, + Rectangle, Shadow, Shell, Size, Theme, Vector, Widget, }; /// A generic widget that produces a message when pressed. @@ -81,6 +81,7 @@ where padding: Padding, clip: bool, class: Theme::Class<'a>, + status: Option<Status>, } enum OnPress<'a, Message> { @@ -88,7 +89,7 @@ enum OnPress<'a, Message> { Closure(Box<dyn Fn() -> Message + 'a>), } -impl<'a, Message: Clone> OnPress<'a, Message> { +impl<Message: Clone> OnPress<'_, Message> { fn get(&self) -> Message { match self { OnPress::Direct(message) => message.clone(), @@ -117,6 +118,7 @@ where padding: DEFAULT_PADDING, clip: false, class: Theme::default(), + status: None, } } @@ -270,7 +272,7 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -280,8 +282,8 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - if let event::Status::Captured = self.content.as_widget_mut().on_event( + ) { + self.content.as_widget_mut().update( &mut tree.children[0], event.clone(), layout.children().next().unwrap(), @@ -290,8 +292,10 @@ where clipboard, shell, viewport, - ) { - return event::Status::Captured; + ); + + if shell.is_event_captured() { + return; } match event { @@ -305,14 +309,13 @@ where state.is_pressed = true; - return event::Status::Captured; + shell.capture_event(); } } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) => { - if let Some(on_press) = self.on_press.as_ref().map(OnPress::get) - { + if let Some(on_press) = &self.on_press { let state = tree.state.downcast_mut::<State>(); if state.is_pressed { @@ -321,10 +324,10 @@ where let bounds = layout.bounds(); if cursor.is_over(bounds) { - shell.publish(on_press); + shell.publish(on_press.get()); } - return event::Status::Captured; + shell.capture_event(); } } } @@ -336,7 +339,25 @@ where _ => {} } - event::Status::Ignored + let current_status = if self.on_press.is_none() { + Status::Disabled + } else if cursor.is_over(layout.bounds()) { + let state = tree.state.downcast_ref::<State>(); + + if state.is_pressed { + Status::Pressed + } else { + Status::Hovered + } + } else { + Status::Active + }; + + if let Event::Window(window::Event::RedrawRequested(_now)) = event { + self.status = Some(current_status); + } else if self.status.is_some_and(|status| status != current_status) { + shell.request_redraw(); + } } fn draw( @@ -351,23 +372,8 @@ where ) { let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); - let is_mouse_over = cursor.is_over(bounds); - - let status = if self.on_press.is_none() { - Status::Disabled - } else if is_mouse_over { - let state = tree.state.downcast_ref::<State>(); - - if state.is_pressed { - Status::Pressed - } else { - Status::Hovered - } - } else { - Status::Active - }; - - let style = theme.style(&self.class, status); + let style = + theme.style(&self.class, self.status.unwrap_or(Status::Disabled)); if style.background.is_some() || style.border.width > 0.0 @@ -627,6 +633,21 @@ pub fn success(theme: &Theme, status: Status) -> Style { } } +/// A warning button; denoting a risky action. +pub fn warning(theme: &Theme, status: Status) -> Style { + let palette = theme.extended_palette(); + let base = styled(palette.warning.base); + + match status { + Status::Active | Status::Pressed => base, + Status::Hovered => Style { + background: Some(Background::Color(palette.warning.strong.color)), + ..base + }, + Status::Disabled => disabled(base), + } +} + /// A danger button; denoting a destructive action. pub fn danger(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 9fbccf82..23cc3f2b 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -48,24 +48,24 @@ //! canvas(Circle { radius: 50.0 }).into() //! } //! ``` -pub mod event; - mod program; -pub use event::Event; pub use program::Program; +pub use crate::core::event::Event; pub use crate::graphics::cache::Group; pub use crate::graphics::geometry::{ fill, gradient, path, stroke, Fill, Gradient, Image, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, }; +pub use crate::Action; -use crate::core; +use crate::core::event; use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; +use crate::core::window; use crate::core::{ Clipboard, Element, Length, Rectangle, Shell, Size, Vector, Widget, }; @@ -148,6 +148,7 @@ where message_: PhantomData<Message>, theme_: PhantomData<Theme>, renderer_: PhantomData<Renderer>, + last_mouse_interaction: Option<mouse::Interaction>, } impl<P, Message, Theme, Renderer> Canvas<P, Message, Theme, Renderer> @@ -166,6 +167,7 @@ where message_: PhantomData, theme_: PhantomData, renderer_: PhantomData, + last_mouse_interaction: None, } } @@ -213,42 +215,63 @@ where layout::atomic(limits, self.width, self.height) } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: core::Event, + event: Event, layout: Layout<'_>, cursor: mouse::Cursor, - _renderer: &Renderer, + renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - _viewport: &Rectangle, - ) -> event::Status { + viewport: &Rectangle, + ) { let bounds = layout.bounds(); - let canvas_event = match event { - core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), - core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), - core::Event::Keyboard(keyboard_event) => { - Some(Event::Keyboard(keyboard_event)) - } - core::Event::Window(_) => None, - }; - - if let Some(canvas_event) = canvas_event { - let state = tree.state.downcast_mut::<P::State>(); + let state = tree.state.downcast_mut::<P::State>(); + let is_redraw_request = matches!( + event, + Event::Window(window::Event::RedrawRequested(_now)), + ); - let (event_status, message) = - self.program.update(state, canvas_event, bounds, cursor); + if let Some(action) = self.program.update(state, event, bounds, cursor) + { + let (message, redraw_request, event_status) = action.into_inner(); if let Some(message) = message { shell.publish(message); } - return event_status; + if let Some(redraw_request) = redraw_request { + match redraw_request { + window::RedrawRequest::NextFrame => { + shell.request_redraw(); + } + window::RedrawRequest::At(at) => { + shell.request_redraw_at(at); + } + } + } + + if event_status == event::Status::Captured { + shell.capture_event(); + } } - event::Status::Ignored + if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { + let mouse_interaction = self + .mouse_interaction(tree, layout, cursor, viewport, renderer); + + if is_redraw_request { + self.last_mouse_interaction = Some(mouse_interaction); + } else if self.last_mouse_interaction.is_some_and( + |last_mouse_interaction| { + last_mouse_interaction != mouse_interaction + }, + ) { + shell.request_redraw(); + } + } } fn mouse_interaction( diff --git a/widget/src/canvas/event.rs b/widget/src/canvas/event.rs deleted file mode 100644 index a8eb47f7..00000000 --- a/widget/src/canvas/event.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! Handle events of a canvas. -use crate::core::keyboard; -use crate::core::mouse; -use crate::core::touch; - -pub use crate::core::event::Status; - -/// A [`Canvas`] event. -/// -/// [`Canvas`]: crate::Canvas -#[derive(Debug, Clone, PartialEq)] -pub enum Event { - /// A mouse event. - Mouse(mouse::Event), - - /// A touch event. - Touch(touch::Event), - - /// A keyboard event. - Keyboard(keyboard::Event), -} diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs index a7ded0f4..c68b2830 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -1,8 +1,8 @@ -use crate::canvas::event::{self, Event}; use crate::canvas::mouse; -use crate::canvas::Geometry; +use crate::canvas::{Event, Geometry}; use crate::core::Rectangle; use crate::graphics::geometry; +use crate::Action; /// The state and logic of a [`Canvas`]. /// @@ -22,8 +22,9 @@ where /// When a [`Program`] is used in a [`Canvas`], the runtime will call this /// method for each [`Event`]. /// - /// This method can optionally return a `Message` to notify an application - /// of any meaningful interactions. + /// This method can optionally return an [`Action`] to either notify an + /// application of any meaningful interactions, capture the event, or + /// request a redraw. /// /// By default, this method does and returns nothing. /// @@ -34,8 +35,8 @@ where _event: Event, _bounds: Rectangle, _cursor: mouse::Cursor, - ) -> (event::Status, Option<Message>) { - (event::Status::Ignored, None) + ) -> Option<Action<Message>> { + None } /// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. @@ -84,7 +85,7 @@ where event: Event, bounds: Rectangle, cursor: mouse::Cursor, - ) -> (event::Status, Option<Message>) { + ) -> Option<Action<Message>> { T::update(self, state, event, bounds, cursor) } diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 819f0d9d..663bfad1 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -31,7 +31,6 @@ //! ``` //!  use crate::core::alignment; -use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::renderer; @@ -40,9 +39,10 @@ use crate::core::theme::palette; use crate::core::touch; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; +use crate::core::window; use crate::core::{ - Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, - Rectangle, Shell, Size, Theme, Widget, + Background, Border, Clipboard, Color, Element, Event, Layout, Length, + Pixels, Rectangle, Shell, Size, Theme, Widget, }; /// A box that can be checked. @@ -100,6 +100,7 @@ pub struct Checkbox< font: Option<Renderer::Font>, icon: Icon<Renderer::Font>, class: Theme::Class<'a>, + last_status: Option<Status>, } impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer> @@ -139,6 +140,7 @@ where shaping: text::Shaping::Basic, }, class: Theme::default(), + last_status: None, } } @@ -245,8 +247,8 @@ where } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Checkbox<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Checkbox<'_, Message, Theme, Renderer> where Renderer: text::Renderer, Theme: Catalog, @@ -300,7 +302,7 @@ where ) } - fn on_event( + fn update( &mut self, _tree: &mut Tree, event: Event, @@ -310,7 +312,7 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { @@ -319,14 +321,35 @@ where if mouse_over { if let Some(on_toggle) = &self.on_toggle { shell.publish((on_toggle)(!self.is_checked)); - return event::Status::Captured; + shell.capture_event(); } } } _ => {} } - event::Status::Ignored + let current_status = { + let is_mouse_over = cursor.is_over(layout.bounds()); + let is_disabled = self.on_toggle.is_none(); + let is_checked = self.is_checked; + + if is_disabled { + Status::Disabled { is_checked } + } else if is_mouse_over { + Status::Hovered { is_checked } + } else { + Status::Active { is_checked } + } + }; + + if let Event::Window(window::Event::RedrawRequested(_now)) = event { + self.last_status = Some(current_status); + } else if self + .last_status + .is_some_and(|status| status != current_status) + { + shell.request_redraw(); + } } fn mouse_interaction( @@ -351,24 +374,17 @@ where theme: &Theme, defaults: &renderer::Style, layout: Layout<'_>, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, viewport: &Rectangle, ) { - let is_mouse_over = cursor.is_over(layout.bounds()); - let is_disabled = self.on_toggle.is_none(); - let is_checked = self.is_checked; - let mut children = layout.children(); - let status = if is_disabled { - Status::Disabled { is_checked } - } else if is_mouse_over { - Status::Hovered { is_checked } - } else { - Status::Active { is_checked } - }; - - let style = theme.style(&self.class, status); + let style = theme.style( + &self.class, + self.last_status.unwrap_or(Status::Disabled { + is_checked: self.is_checked, + }), + ); { let layout = children.next().unwrap(); @@ -429,6 +445,16 @@ where ); } } + + fn operate( + &self, + _state: &mut Tree, + layout: Layout<'_>, + _renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + operation.text(None, layout.bounds(), &self.label); + } } impl<'a, Message, Theme, Renderer> From<Checkbox<'a, Message, Theme, Renderer>> diff --git a/widget/src/column.rs b/widget/src/column.rs index 213f68fc..c729cbdb 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -1,14 +1,13 @@ //! Distribute content vertically. use crate::core::alignment::{self, Alignment}; -use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::widget::{Operation, Tree}; use crate::core::{ - Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, - Size, Vector, Widget, + Clipboard, Element, Event, Layout, Length, Padding, Pixels, Rectangle, + Shell, Size, Vector, Widget, }; /// A container that distributes its contents vertically. @@ -174,7 +173,7 @@ where } } -impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> +impl<Message, Renderer> Default for Column<'_, Message, Renderer> where Renderer: crate::core::Renderer, { @@ -196,8 +195,8 @@ impl<'a, Message, Theme, Renderer: crate::core::Renderer> } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Column<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Column<'_, Message, Theme, Renderer> where Renderer: crate::core::Renderer, { @@ -258,7 +257,7 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -268,24 +267,24 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.children + ) { + for ((child, state), layout) in self + .children .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .map(|((child, state), layout)| { - child.as_widget_mut().on_event( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - viewport, - ) - }) - .fold(event::Status::Ignored, event::Status::merge) + { + child.as_widget_mut().update( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } } fn mouse_interaction( diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index e300f1d0..500d2bec 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -54,7 +54,6 @@ //! } //! } //! ``` -use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::keyboard::key; use crate::core::layout::{self, Layout}; @@ -64,8 +63,10 @@ use crate::core::renderer; use crate::core::text; use crate::core::time::Instant; use crate::core::widget::{self, Widget}; +use crate::core::window; use crate::core::{ - Clipboard, Element, Length, Padding, Rectangle, Shell, Size, Theme, Vector, + Clipboard, Element, Event, Length, Padding, Rectangle, Shell, Size, Theme, + Vector, }; use crate::overlay::menu; use crate::text::LineHeight; @@ -458,8 +459,8 @@ enum TextInputEvent { TextChanged(String), } -impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for ComboBox<'a, T, Message, Theme, Renderer> +impl<T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for ComboBox<'_, T, Message, Theme, Renderer> where T: Display + Clone + 'static, Message: Clone, @@ -509,7 +510,7 @@ where vec![widget::Tree::new(&self.text_input as &dyn Widget<_, _, _>)] } - fn on_event( + fn update( &mut self, tree: &mut widget::Tree, event: Event, @@ -519,7 +520,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let menu = tree.state.downcast_mut::<Menu<T>>(); let started_focused = { @@ -538,7 +539,7 @@ where let mut local_shell = Shell::new(&mut local_messages); // Provide it to the widget - let mut event_status = self.text_input.on_event( + self.text_input.update( &mut tree.children[0], event.clone(), layout, @@ -549,13 +550,27 @@ where viewport, ); + if local_shell.is_event_captured() { + shell.capture_event(); + } + + if let Some(redraw_request) = local_shell.redraw_request() { + match redraw_request { + window::RedrawRequest::NextFrame => { + shell.request_redraw(); + } + window::RedrawRequest::At(at) => { + shell.request_redraw_at(at); + } + } + } + // Then finally react to them here for message in local_messages { let TextInputEvent::TextChanged(new_value) = message; if let Some(on_input) = &self.on_input { shell.publish((on_input)(new_value.clone())); - published_message_to_shell = true; } // Couple the filtered options with the `ComboBox` @@ -576,6 +591,7 @@ where ); }); shell.invalidate_layout(); + shell.request_redraw(); } let is_focused = { @@ -619,9 +635,9 @@ where } } - event_status = event::Status::Captured; + shell.capture_event(); + shell.request_redraw(); } - (key::Named::ArrowUp, _) | (key::Named::Tab, true) => { if let Some(index) = &mut menu.hovered_option { if *index == 0 { @@ -656,7 +672,8 @@ where } } - event_status = event::Status::Captured; + shell.capture_event(); + shell.request_redraw(); } (key::Named::ArrowDown, _) | (key::Named::Tab, false) @@ -703,7 +720,8 @@ where } } - event_status = event::Status::Captured; + shell.capture_event(); + shell.request_redraw(); } _ => {} } @@ -724,7 +742,7 @@ where published_message_to_shell = true; // Unfocus the input - let _ = self.text_input.on_event( + self.text_input.update( &mut tree.children[0], Event::Mouse(mouse::Event::ButtonPressed( mouse::Button::Left, @@ -761,8 +779,6 @@ where } } } - - event_status } fn mouse_interaction( diff --git a/widget/src/container.rs b/widget/src/container.rs index f4993ac9..a411a7d2 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -21,7 +21,6 @@ //! ``` use crate::core::alignment::{self, Alignment}; use crate::core::border::{self, Border}; -use crate::core::event::{self, Event}; use crate::core::gradient::{self, Gradient}; use crate::core::layout; use crate::core::mouse; @@ -30,7 +29,7 @@ use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Operation}; use crate::core::{ - self, color, Background, Clipboard, Color, Element, Layout, Length, + self, color, Background, Clipboard, Color, Element, Event, Layout, Length, Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector, Widget, }; @@ -229,8 +228,8 @@ where } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Container<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Container<'_, Message, Theme, Renderer> where Theme: Catalog, Renderer: core::Renderer, @@ -298,7 +297,7 @@ where ); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -308,8 +307,8 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.content.as_widget_mut().on_event( + ) { + self.content.as_widget_mut().update( tree, event, layout.children().next().unwrap(), @@ -318,7 +317,7 @@ where clipboard, shell, viewport, - ) + ); } fn mouse_interaction( @@ -494,11 +493,11 @@ pub fn visible_bounds(id: Id) -> Task<Option<Rectangle>> { impl Operation<Option<Rectangle>> for VisibleBounds { fn scrollable( &mut self, - _state: &mut dyn widget::operation::Scrollable, _id: Option<&widget::Id>, bounds: Rectangle, _content_bounds: Rectangle, translation: Vector, + _state: &mut dyn widget::operation::Scrollable, ) { match self.scrollables.last() { Some((last_translation, last_viewport, _depth)) => { @@ -651,7 +650,7 @@ pub trait Catalog { /// A styling function for a [`Container`]. pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>; -impl<'a, Theme> From<Style> for StyleFn<'a, Theme> { +impl<Theme> From<Style> for StyleFn<'_, Theme> { fn from(style: Style) -> Self { Box::new(move |_theme| style) } diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 52290a54..5a0f8107 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -24,7 +24,7 @@ use crate::text_input::{self, TextInput}; use crate::toggler::{self, Toggler}; use crate::tooltip::{self, Tooltip}; use crate::vertical_slider::{self, VerticalSlider}; -use crate::{Column, MouseArea, Row, Space, Stack, Themer}; +use crate::{Column, MouseArea, Pin, Row, Space, Stack, Themer}; use std::borrow::Borrow; use std::ops::RangeInclusive; @@ -249,6 +249,38 @@ where container(content).center(Length::Fill) } +/// Creates a new [`Pin`] widget with the given content. +/// +/// A [`Pin`] widget positions its contents at some fixed coordinates inside of its boundaries. +/// +/// # Example +/// ```no_run +/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::core::Length::Fill; } +/// # pub type State = (); +/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; +/// use iced::widget::pin; +/// use iced::Fill; +/// +/// enum Message { +/// // ... +/// } +/// +/// fn view(state: &State) -> Element<'_, Message> { +/// pin("This text is displayed at coordinates (50, 50)!") +/// .x(50) +/// .y(50) +/// .into() +/// } +/// ``` +pub fn pin<'a, Message, Theme, Renderer>( + content: impl Into<Element<'a, Message, Theme, Renderer>>, +) -> Pin<'a, Message, Theme, Renderer> +where + Renderer: core::Renderer, +{ + Pin::new(content) +} + /// Creates a new [`Column`] with the given children. /// /// Columns distribute their children vertically. @@ -363,19 +395,18 @@ where Theme: 'a, Renderer: core::Renderer + 'a, { - use crate::core::event::{self, Event}; use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; - use crate::core::{Rectangle, Shell, Size}; + use crate::core::{Event, Rectangle, Shell, Size}; struct Opaque<'a, Message, Theme, Renderer> { content: Element<'a, Message, Theme, Renderer>, } - impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Opaque<'a, Message, Theme, Renderer> + impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Opaque<'_, Message, Theme, Renderer> where Renderer: core::Renderer, { @@ -439,7 +470,7 @@ where .operate(state, layout, renderer, operation); } - fn on_event( + fn update( &mut self, state: &mut Tree, event: Event, @@ -449,25 +480,19 @@ where clipboard: &mut dyn core::Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let is_mouse_press = matches!( event, core::Event::Mouse(mouse::Event::ButtonPressed(_)) ); - if let core::event::Status::Captured = - self.content.as_widget_mut().on_event( - state, event, layout, cursor, renderer, clipboard, shell, - viewport, - ) - { - return event::Status::Captured; - } + self.content.as_widget_mut().update( + state, event, layout, cursor, renderer, clipboard, shell, + viewport, + ); if is_mouse_press && cursor.is_over(layout.bounds()) { - event::Status::Captured - } else { - event::Status::Ignored + shell.capture_event(); } } @@ -530,22 +555,22 @@ where Theme: 'a, Renderer: core::Renderer + 'a, { - use crate::core::event::{self, Event}; use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; - use crate::core::{Rectangle, Shell, Size}; + use crate::core::{Event, Rectangle, Shell, Size}; struct Hover<'a, Message, Theme, Renderer> { base: Element<'a, Message, Theme, Renderer>, top: Element<'a, Message, Theme, Renderer>, is_top_focused: bool, is_top_overlay_active: bool, + is_hovered: bool, } - impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Hover<'a, Message, Theme, Renderer> + impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Hover<'_, Message, Theme, Renderer> where Renderer: core::Renderer, { @@ -648,7 +673,7 @@ where } } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -658,11 +683,13 @@ where clipboard: &mut dyn core::Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let mut children = layout.children().zip(&mut tree.children); let (base_layout, base_tree) = children.next().unwrap(); let (top_layout, top_tree) = children.next().unwrap(); + let is_hovered = cursor.is_over(layout.bounds()); + if matches!(event, Event::Window(window::Event::RedrawRequested(_))) { let mut count_focused = operation::focusable::count(); @@ -678,19 +705,23 @@ where operation::Outcome::Some(count) => count.focused.is_some(), _ => false, }; + + self.is_hovered = is_hovered; + } else if is_hovered != self.is_hovered { + shell.request_redraw(); } - let top_status = if matches!( + if matches!( event, Event::Mouse( mouse::Event::CursorMoved { .. } | mouse::Event::ButtonReleased(_) ) - ) || cursor.is_over(layout.bounds()) + ) || is_hovered || self.is_top_focused || self.is_top_overlay_active { - self.top.as_widget_mut().on_event( + self.top.as_widget_mut().update( top_tree, event.clone(), top_layout, @@ -699,16 +730,14 @@ where clipboard, shell, viewport, - ) - } else { - event::Status::Ignored + ); }; - if top_status == event::Status::Captured { - return top_status; + if shell.is_event_captured() { + return; } - self.base.as_widget_mut().on_event( + self.base.as_widget_mut().update( base_tree, event.clone(), base_layout, @@ -717,7 +746,7 @@ where clipboard, shell, viewport, - ) + ); } fn mouse_interaction( @@ -777,6 +806,7 @@ where top: top.into(), is_top_focused: false, is_top_overlay_active: false, + is_hovered: false, }) } @@ -1678,9 +1708,9 @@ where { use crate::core::{Alignment, Font}; use crate::svg; - use once_cell::sync::Lazy; + use std::sync::LazyLock; - static LOGO: Lazy<svg::Handle> = Lazy::new(|| { + static LOGO: LazyLock<svg::Handle> = LazyLock::new(|| { svg::Handle::from_memory(include_bytes!("../assets/iced-logo.svg")) }); diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index b1aad22c..20a7955f 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -1,13 +1,12 @@ //! Zoom and pan on an image. -use crate::core::event::{self, Event}; use crate::core::image::{self, FilterMethod}; use crate::core::layout; use crate::core::mouse; use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Clipboard, ContentFit, Element, Image, Layout, Length, Pixels, Point, - Radians, Rectangle, Shell, Size, Vector, Widget, + Clipboard, ContentFit, Element, Event, Image, Layout, Length, Pixels, + Point, Radians, Rectangle, Shell, Size, Vector, Widget, }; /// A frame that displays an image with the ability to zoom in/out and pan. @@ -149,7 +148,7 @@ where layout::Node::new(final_size) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -157,15 +156,15 @@ where cursor: mouse::Cursor, renderer: &Renderer, _clipboard: &mut dyn Clipboard, - _shell: &mut Shell<'_, Message>, + shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let bounds = layout.bounds(); match event { Event::Mouse(mouse::Event::WheelScrolled { delta }) => { let Some(cursor_position) = cursor.position_over(bounds) else { - return event::Status::Ignored; + return; }; match delta { @@ -216,29 +215,25 @@ where } } - event::Status::Captured + shell.capture_event(); } Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let Some(cursor_position) = cursor.position_over(bounds) else { - return event::Status::Ignored; + return; }; let state = tree.state.downcast_mut::<State>(); state.cursor_grabbed_at = Some(cursor_position); state.starting_offset = state.current_offset; - - event::Status::Captured + shell.capture_event(); } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { let state = tree.state.downcast_mut::<State>(); if state.cursor_grabbed_at.is_some() { state.cursor_grabbed_at = None; - - event::Status::Captured - } else { - event::Status::Ignored + shell.capture_event(); } } Event::Mouse(mouse::Event::CursorMoved { position }) => { @@ -278,13 +273,10 @@ where }; state.current_offset = Vector::new(x, y); - - event::Status::Captured - } else { - event::Status::Ignored + shell.capture_event(); } } - _ => event::Status::Ignored, + _ => {} } } diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index 5852ede1..ab0b0bde 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -1,5 +1,4 @@ //! Keyed columns distribute content vertically while keeping continuity. -use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; @@ -7,8 +6,8 @@ use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::Operation; use crate::core::{ - Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, - Shell, Size, Vector, Widget, + Alignment, Clipboard, Element, Event, Layout, Length, Padding, Pixels, + Rectangle, Shell, Size, Vector, Widget, }; /// A container that distributes its contents vertically while keeping continuity. @@ -186,7 +185,7 @@ where } } -impl<'a, Key, Message, Renderer> Default for Column<'a, Key, Message, Renderer> +impl<Key, Message, Renderer> Default for Column<'_, Key, Message, Renderer> where Key: Copy + PartialEq, Renderer: crate::core::Renderer, @@ -203,8 +202,8 @@ where keys: Vec<Key>, } -impl<'a, Key, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Column<'a, Key, Message, Theme, Renderer> +impl<Key, Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Column<'_, Key, Message, Theme, Renderer> where Renderer: crate::core::Renderer, Key: Copy + PartialEq + 'static, @@ -298,7 +297,7 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -308,24 +307,24 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.children + ) { + for ((child, state), layout) in self + .children .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .map(|((child, state), layout)| { - child.as_widget_mut().on_event( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - viewport, - ) - }) - .fold(event::Status::Ignored, event::Status::merge) + { + child.as_widget_mut().update( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } } fn mouse_interaction( diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 232f254c..c6710e30 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -10,7 +10,6 @@ pub use responsive::Responsive; mod cache; -use crate::core::event::{self, Event}; use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::overlay; @@ -19,7 +18,7 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Widget}; use crate::core::Element; use crate::core::{ - self, Clipboard, Length, Point, Rectangle, Shell, Size, Vector, + self, Clipboard, Event, Length, Point, Rectangle, Shell, Size, Vector, }; use crate::runtime::overlay::Nested; @@ -196,7 +195,7 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -206,9 +205,9 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { self.with_element_mut(|element| { - element.as_widget_mut().on_event( + element.as_widget_mut().update( &mut tree.children[0], event, layout, @@ -217,8 +216,8 @@ where clipboard, shell, viewport, - ) - }) + ); + }); } fn mouse_interaction( @@ -322,16 +321,14 @@ struct Overlay<'a, Message, Theme, Renderer>( Option<Inner<'a, Message, Theme, Renderer>>, ); -impl<'a, Message, Theme, Renderer> Drop - for Overlay<'a, Message, Theme, Renderer> -{ +impl<Message, Theme, Renderer> Drop for Overlay<'_, Message, Theme, Renderer> { fn drop(&mut self) { let heads = self.0.take().unwrap().into_heads(); (*heads.cell.borrow_mut()) = Some(heads.element); } } -impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer> { +impl<Message, Theme, Renderer> Overlay<'_, Message, Theme, Renderer> { fn with_overlay_maybe<T>( &self, f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T, @@ -351,8 +348,8 @@ impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer> { } } -impl<'a, Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer> - for Overlay<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer> + for Overlay<'_, Message, Theme, Renderer> where Renderer: core::Renderer, { @@ -387,7 +384,7 @@ where .unwrap_or_default() } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -395,11 +392,10 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.with_overlay_mut_maybe(|overlay| { - overlay.on_event(event, layout, cursor, renderer, clipboard, shell) - }) - .unwrap_or(event::Status::Ignored) + ) { + let _ = self.with_overlay_mut_maybe(|overlay| { + overlay.update(event, layout, cursor, renderer, clipboard, shell); + }); } fn is_over( diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index c7bc1264..15b8b62e 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -1,12 +1,12 @@ //! Build and reuse custom widgets using The Elm Architecture. #![allow(deprecated)] -use crate::core::event; use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; +use crate::core::window; use crate::core::{ self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, Widget, @@ -141,8 +141,8 @@ struct State<'a, Message: 'a, Theme: 'a, Renderer: 'a, Event: 'a, S: 'a> { element: Option<Element<'this, Event, Theme, Renderer>>, } -impl<'a, Message, Theme, Renderer, Event, S> - Instance<'a, Message, Theme, Renderer, Event, S> +impl<Message, Theme, Renderer, Event, S> + Instance<'_, Message, Theme, Renderer, Event, S> where S: Default + 'static, Renderer: renderer::Renderer, @@ -251,8 +251,8 @@ where } } -impl<'a, Message, Theme, Renderer, Event, S> Widget<Message, Theme, Renderer> - for Instance<'a, Message, Theme, Renderer, Event, S> +impl<Message, Theme, Renderer, Event, S> Widget<Message, Theme, Renderer> + for Instance<'_, Message, Theme, Renderer, Event, S> where S: 'static + Default, Renderer: core::Renderer, @@ -311,7 +311,7 @@ where }) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: core::Event, @@ -321,13 +321,13 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); let t = tree.state.downcast_mut::<Rc<RefCell<Option<Tree>>>>(); - let event_status = self.with_element_mut(|element| { - element.as_widget_mut().on_event( + self.with_element_mut(|element| { + element.as_widget_mut().update( &mut t.borrow_mut().as_mut().unwrap().children[0], event, layout, @@ -336,13 +336,24 @@ where clipboard, &mut local_shell, viewport, - ) + ); }); + if local_shell.is_event_captured() { + shell.capture_event(); + } + local_shell.revalidate_layout(|| shell.invalidate_layout()); if let Some(redraw_request) = local_shell.redraw_request() { - shell.request_redraw(redraw_request); + match redraw_request { + window::RedrawRequest::NextFrame => { + shell.request_redraw(); + } + window::RedrawRequest::At(at) => { + shell.request_redraw_at(at); + } + } } if !local_messages.is_empty() { @@ -369,8 +380,6 @@ where shell.invalidate_layout(); } - - event_status } fn operate( @@ -495,8 +504,8 @@ struct Overlay<'a, 'b, Message, Theme, Renderer, Event, S>( Option<Inner<'a, 'b, Message, Theme, Renderer, Event, S>>, ); -impl<'a, 'b, Message, Theme, Renderer, Event, S> Drop - for Overlay<'a, 'b, Message, Theme, Renderer, Event, S> +impl<Message, Theme, Renderer, Event, S> Drop + for Overlay<'_, '_, Message, Theme, Renderer, Event, S> { fn drop(&mut self) { if let Some(heads) = self.0.take().map(Inner::into_heads) { @@ -520,8 +529,8 @@ struct OverlayInstance<'a, 'b, Message, Theme, Renderer, Event, S> { overlay: Option<Overlay<'a, 'b, Message, Theme, Renderer, Event, S>>, } -impl<'a, 'b, Message, Theme, Renderer, Event, S> - OverlayInstance<'a, 'b, Message, Theme, Renderer, Event, S> +impl<Message, Theme, Renderer, Event, S> + OverlayInstance<'_, '_, Message, Theme, Renderer, Event, S> { fn with_overlay_maybe<T>( &self, @@ -554,9 +563,9 @@ impl<'a, 'b, Message, Theme, Renderer, Event, S> } } -impl<'a, 'b, Message, Theme, Renderer, Event, S> +impl<Message, Theme, Renderer, Event, S> overlay::Overlay<Message, Theme, Renderer> - for OverlayInstance<'a, 'b, Message, Theme, Renderer, Event, S> + for OverlayInstance<'_, '_, Message, Theme, Renderer, Event, S> where Renderer: core::Renderer, S: 'static + Default, @@ -592,7 +601,7 @@ where .unwrap_or_default() } - fn on_event( + fn update( &mut self, event: core::Event, layout: Layout<'_>, @@ -600,27 +609,36 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); - let event_status = self - .with_overlay_mut_maybe(|overlay| { - overlay.on_event( - event, - layout, - cursor, - renderer, - clipboard, - &mut local_shell, - ) - }) - .unwrap_or(event::Status::Ignored); + let _ = self.with_overlay_mut_maybe(|overlay| { + overlay.update( + event, + layout, + cursor, + renderer, + clipboard, + &mut local_shell, + ); + }); + + if local_shell.is_event_captured() { + shell.capture_event(); + } local_shell.revalidate_layout(|| shell.invalidate_layout()); if let Some(redraw_request) = local_shell.redraw_request() { - shell.request_redraw(redraw_request); + match redraw_request { + window::RedrawRequest::NextFrame => { + shell.request_redraw(); + } + window::RedrawRequest::At(at) => { + shell.request_redraw_at(at); + } + } } if !local_messages.is_empty() { @@ -658,8 +676,6 @@ where shell.invalidate_layout(); } - - event_status } fn is_over( diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index a7a99f56..8129336e 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -1,4 +1,3 @@ -use crate::core::event::{self, Event}; use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::overlay; @@ -6,8 +5,8 @@ use crate::core::renderer; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, - Widget, + self, Clipboard, Element, Event, Length, Point, Rectangle, Shell, Size, + Vector, Widget, }; use crate::horizontal_space; use crate::runtime::overlay::Nested; @@ -83,15 +82,21 @@ where new_size: Size, view: &dyn Fn(Size) -> Element<'a, Message, Theme, Renderer>, ) { - if self.size == new_size { - return; - } + if self.size != new_size { + self.element = view(new_size); + self.size = new_size; + self.layout = None; - self.element = view(new_size); - self.size = new_size; - self.layout = None; + tree.diff(&self.element); + } else { + let is_tree_empty = + tree.tag == tree::Tag::stateless() && tree.children.is_empty(); - tree.diff(&self.element); + if is_tree_empty { + self.layout = None; + tree.diff(&self.element); + } + } } fn resolve<R, T>( @@ -126,8 +131,8 @@ struct State { tree: RefCell<Tree>, } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Responsive<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Responsive<'_, Message, Theme, Renderer> where Renderer: core::Renderer, { @@ -180,7 +185,7 @@ where ); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -190,20 +195,20 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::<State>(); let mut content = self.content.borrow_mut(); let mut local_messages = vec![]; let mut local_shell = Shell::new(&mut local_messages); - let status = content.resolve( + content.resolve( &mut state.tree.borrow_mut(), renderer, layout, &self.view, |tree, renderer, layout, element| { - element.as_widget_mut().on_event( + element.as_widget_mut().update( tree, event, layout, @@ -212,7 +217,7 @@ where clipboard, &mut local_shell, viewport, - ) + ); }, ); @@ -221,8 +226,6 @@ where } shell.merge(local_shell, std::convert::identity); - - status } fn draw( @@ -355,9 +358,7 @@ struct Overlay<'a, 'b, Message, Theme, Renderer> { ), } -impl<'a, 'b, Message, Theme, Renderer> - Overlay<'a, 'b, Message, Theme, Renderer> -{ +impl<Message, Theme, Renderer> Overlay<'_, '_, Message, Theme, Renderer> { fn with_overlay_maybe<T>( &self, f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T, @@ -377,9 +378,8 @@ impl<'a, 'b, Message, Theme, Renderer> } } -impl<'a, 'b, Message, Theme, Renderer> - overlay::Overlay<Message, Theme, Renderer> - for Overlay<'a, 'b, Message, Theme, Renderer> +impl<Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer> + for Overlay<'_, '_, Message, Theme, Renderer> where Renderer: core::Renderer, { @@ -414,7 +414,7 @@ where .unwrap_or_default() } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -422,28 +422,20 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { let mut is_layout_invalid = false; - let event_status = self - .with_overlay_mut_maybe(|overlay| { - let event_status = overlay.on_event( - event, layout, cursor, renderer, clipboard, shell, - ); - - is_layout_invalid = shell.is_layout_invalid(); + let _ = self.with_overlay_mut_maybe(|overlay| { + overlay.update(event, layout, cursor, renderer, clipboard, shell); - event_status - }) - .unwrap_or(event::Status::Ignored); + is_layout_invalid = shell.is_layout_invalid(); + }); if is_layout_invalid { self.with_overlay_mut(|(_overlay, layout)| { **layout = None; }); } - - event_status } fn is_over( diff --git a/widget/src/lib.rs b/widget/src/lib.rs index a68720d6..38c9929a 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -8,8 +8,10 @@ pub use iced_renderer::graphics; pub use iced_runtime as runtime; pub use iced_runtime::core; +mod action; mod column; mod mouse_area; +mod pin; mod row; mod space; mod stack; @@ -62,6 +64,8 @@ pub use pane_grid::PaneGrid; #[doc(no_inline)] pub use pick_list::PickList; #[doc(no_inline)] +pub use pin::Pin; +#[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; @@ -131,4 +135,5 @@ pub use qr_code::QRCode; pub mod markdown; pub use crate::core::theme::{self, Theme}; +pub use action::Action; pub use renderer::Renderer; diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index d6bebb9b..c0648e9e 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -305,12 +305,21 @@ pub fn parse(markdown: &str) -> impl Iterator<Item = Item> + '_ { None } pulldown_cmark::Tag::List(first_item) if !metadata && !table => { + let prev = if spans.is_empty() { + None + } else { + produce( + &mut lists, + Item::Paragraph(Text::new(spans.drain(..).collect())), + ) + }; + lists.push(List { start: first_item, items: Vec::new(), }); - None + prev } pulldown_cmark::Tag::Item => { lists diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index c5a37ae3..bdc81bdf 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -1,5 +1,4 @@ //! A container for capturing mouse events. -use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; @@ -7,8 +6,8 @@ use crate::core::renderer; use crate::core::touch; use crate::core::widget::{tree, Operation, Tree}; use crate::core::{ - Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, - Widget, + Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Size, + Vector, Widget, }; /// Emit messages on mouse events. @@ -164,8 +163,8 @@ impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> { } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for MouseArea<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for MouseArea<'_, Message, Theme, Renderer> where Renderer: renderer::Renderer, Message: Clone, @@ -216,7 +215,7 @@ where ); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -226,8 +225,8 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - if let event::Status::Captured = self.content.as_widget_mut().on_event( + ) { + self.content.as_widget_mut().update( &mut tree.children[0], event.clone(), layout, @@ -236,11 +235,13 @@ where clipboard, shell, viewport, - ) { - return event::Status::Captured; + ); + + if shell.is_event_captured() { + return; } - update(self, tree, event, layout, cursor, shell) + update(self, tree, event, layout, cursor, shell); } fn mouse_interaction( @@ -329,7 +330,7 @@ fn update<Message: Clone, Theme, Renderer>( layout: Layout<'_>, cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, -) -> event::Status { +) { let state: &mut State = tree.state.downcast_mut(); let cursor_position = cursor.position(); @@ -363,104 +364,71 @@ fn update<Message: Clone, Theme, Renderer>( } if !cursor.is_over(layout.bounds()) { - return event::Status::Ignored; + return; } - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) = event - { - let mut captured = false; - - if let Some(message) = widget.on_press.as_ref() { - captured = true; - shell.publish(message.clone()); - } + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + if let Some(message) = widget.on_press.as_ref() { + shell.publish(message.clone()); + shell.capture_event(); + } - if let Some(position) = cursor_position { - if let Some(message) = widget.on_double_click.as_ref() { - let new_click = mouse::Click::new( - position, - mouse::Button::Left, - state.previous_click, - ); + if let Some(position) = cursor_position { + if let Some(message) = widget.on_double_click.as_ref() { + let new_click = mouse::Click::new( + position, + mouse::Button::Left, + state.previous_click, + ); - if matches!(new_click.kind(), mouse::click::Kind::Double) { - shell.publish(message.clone()); - } + if matches!(new_click.kind(), mouse::click::Kind::Double) { + shell.publish(message.clone()); + } - state.previous_click = Some(new_click); + state.previous_click = Some(new_click); - // Even if this is not a double click, but the press is nevertheless - // processed by us and should not be popup to parent widgets. - captured = true; + // Even if this is not a double click, but the press is nevertheless + // processed by us and should not be popup to parent widgets. + shell.capture_event(); + } } } - - if captured { - return event::Status::Captured; - } - } - - if let Some(message) = widget.on_release.as_ref() { - if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) = event - { - shell.publish(message.clone()); - - return event::Status::Captured; + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) => { + if let Some(message) = widget.on_release.as_ref() { + shell.publish(message.clone()); + } } - } - - if let Some(message) = widget.on_right_press.as_ref() { - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = - event - { - shell.publish(message.clone()); - - return event::Status::Captured; + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => { + if let Some(message) = widget.on_right_press.as_ref() { + shell.publish(message.clone()); + shell.capture_event(); + } } - } - - if let Some(message) = widget.on_right_release.as_ref() { - if let Event::Mouse(mouse::Event::ButtonReleased( - mouse::Button::Right, - )) = event - { - shell.publish(message.clone()); - - return event::Status::Captured; + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) => { + if let Some(message) = widget.on_right_release.as_ref() { + shell.publish(message.clone()); + } } - } - - if let Some(message) = widget.on_middle_press.as_ref() { - if let Event::Mouse(mouse::Event::ButtonPressed( - mouse::Button::Middle, - )) = event - { - shell.publish(message.clone()); - - return event::Status::Captured; + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) => { + if let Some(message) = widget.on_middle_press.as_ref() { + shell.publish(message.clone()); + shell.capture_event(); + } } - } - - if let Some(message) = widget.on_middle_release.as_ref() { - if let Event::Mouse(mouse::Event::ButtonReleased( - mouse::Button::Middle, - )) = event - { - shell.publish(message.clone()); - - return event::Status::Captured; + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) => { + if let Some(message) = widget.on_middle_release.as_ref() { + shell.publish(message.clone()); + } } - } - - if let Some(on_scroll) = widget.on_scroll.as_ref() { - if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event { - shell.publish(on_scroll(delta)); - - return event::Status::Captured; + Event::Mouse(mouse::Event::WheelScrolled { delta }) => { + if let Some(on_scroll) = widget.on_scroll.as_ref() { + shell.publish(on_scroll(delta)); + shell.capture_event(); + } } + _ => {} } - - event::Status::Ignored } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index b641e8f5..611476ce 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -1,17 +1,17 @@ //! Build and show dropdown menus. use crate::core::alignment; use crate::core::border::{self, Border}; -use crate::core::event::{self, Event}; use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::text::{self, Text}; use crate::core::touch; -use crate::core::widget::Tree; +use crate::core::widget::tree::{self, Tree}; +use crate::core::window; use crate::core::{ - Background, Clipboard, Color, Length, Padding, Pixels, Point, Rectangle, - Size, Theme, Vector, + Background, Clipboard, Color, Event, Length, Padding, Pixels, Point, + Rectangle, Size, Theme, Vector, }; use crate::core::{Element, Shell, Widget}; use crate::scrollable::{self, Scrollable}; @@ -227,9 +227,8 @@ where } } -impl<'a, 'b, Message, Theme, Renderer> - crate::core::Overlay<Message, Theme, Renderer> - for Overlay<'a, 'b, Message, Theme, Renderer> +impl<Message, Theme, Renderer> crate::core::Overlay<Message, Theme, Renderer> + for Overlay<'_, '_, Message, Theme, Renderer> where Theme: Catalog, Renderer: text::Renderer, @@ -262,7 +261,7 @@ where }) } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -270,13 +269,13 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { let bounds = layout.bounds(); - self.list.on_event( + self.list.update( self.state, event, layout, cursor, renderer, clipboard, shell, &bounds, - ) + ); } fn mouse_interaction( @@ -334,13 +333,25 @@ where class: &'a <Theme as Catalog>::Class<'b>, } -impl<'a, 'b, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for List<'a, 'b, T, Message, Theme, Renderer> +struct ListState { + is_hovered: Option<bool>, +} + +impl<T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for List<'_, '_, T, Message, Theme, Renderer> where T: Clone + ToString, Theme: Catalog, Renderer: text::Renderer, { + fn tag(&self) -> tree::Tag { + tree::Tag::of::<Option<bool>>() + } + + fn state(&self) -> tree::State { + tree::State::new(ListState { is_hovered: None }) + } + fn size(&self) -> Size<Length> { Size { width: Length::Fill, @@ -374,9 +385,9 @@ where layout::Node::new(size) } - fn on_event( + fn update( &mut self, - _state: &mut Tree, + tree: &mut Tree, event: Event, layout: Layout<'_>, cursor: mouse::Cursor, @@ -384,14 +395,14 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if cursor.is_over(layout.bounds()) { if let Some(index) = *self.hovered_option { if let Some(option) = self.options.get(index) { shell.publish((self.on_selected)(option.clone())); - return event::Status::Captured; + shell.capture_event(); } } } @@ -411,14 +422,18 @@ where let new_hovered_option = (cursor_position.y / option_height) as usize; - if let Some(on_option_hovered) = self.on_option_hovered { - if *self.hovered_option != Some(new_hovered_option) { - if let Some(option) = - self.options.get(new_hovered_option) + if *self.hovered_option != Some(new_hovered_option) { + if let Some(option) = + self.options.get(new_hovered_option) + { + if let Some(on_option_hovered) = + self.on_option_hovered { shell .publish(on_option_hovered(option.clone())); } + + shell.request_redraw(); } } @@ -443,7 +458,7 @@ where if let Some(index) = *self.hovered_option { if let Some(option) = self.options.get(index) { shell.publish((self.on_selected)(option.clone())); - return event::Status::Captured; + shell.capture_event(); } } } @@ -451,7 +466,15 @@ where _ => {} } - event::Status::Ignored + let state = tree.state.downcast_mut::<ListState>(); + + if let Event::Window(window::Event::RedrawRequested(_now)) = event { + state.is_hovered = Some(cursor.is_over(layout.bounds())); + } else if state.is_hovered.is_some_and(|is_hovered| { + is_hovered != cursor.is_over(layout.bounds()) + }) { + shell.request_redraw(); + } } fn mouse_interaction( diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index e6fda660..5c3b343c 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -79,7 +79,6 @@ pub use state::State; pub use title_bar::TitleBar; use crate::container; -use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::overlay::{self, Group}; @@ -87,8 +86,9 @@ use crate::core::renderer; use crate::core::touch; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; +use crate::core::window; use crate::core::{ - self, Background, Border, Clipboard, Color, Element, Layout, Length, + self, Background, Border, Clipboard, Color, Element, Event, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; @@ -157,7 +157,9 @@ pub struct PaneGrid< Theme: Catalog, Renderer: core::Renderer, { - contents: Contents<'a, Content<'a, Message, Theme, Renderer>>, + internal: &'a state::Internal, + panes: Vec<Pane>, + contents: Vec<Content<'a, Message, Theme, Renderer>>, width: Length, height: Length, spacing: f32, @@ -165,6 +167,7 @@ pub struct PaneGrid< on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, class: <Theme as Catalog>::Class<'a>, + last_mouse_interaction: Option<mouse::Interaction>, } impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer> @@ -180,29 +183,19 @@ where state: &'a State<T>, view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Theme, Renderer>, ) -> Self { - let contents = if let Some((pane, pane_state)) = - state.maximized.and_then(|pane| { - state.panes.get(&pane).map(|pane_state| (pane, pane_state)) - }) { - Contents::Maximized( - pane, - view(pane, pane_state, true), - Node::Pane(pane), - ) - } else { - Contents::All( - state - .panes - .iter() - .map(|(pane, pane_state)| { - (*pane, view(*pane, pane_state, false)) - }) - .collect(), - &state.internal, - ) - }; + let panes = state.panes.keys().copied().collect(); + let contents = state + .panes + .iter() + .map(|(pane, pane_state)| match state.maximized() { + Some(p) if *pane == p => view(*pane, pane_state, true), + _ => view(*pane, pane_state, false), + }) + .collect(); Self { + internal: &state.internal, + panes, contents, width: Length::Fill, height: Length::Fill, @@ -211,6 +204,7 @@ where on_drag: None, on_resize: None, class: <Theme as Catalog>::default(), + last_mouse_interaction: None, } } @@ -248,7 +242,9 @@ where where F: 'a + Fn(DragEvent) -> Message, { - self.on_drag = Some(Box::new(f)); + if self.internal.maximized().is_none() { + self.on_drag = Some(Box::new(f)); + } self } @@ -265,7 +261,9 @@ where where F: 'a + Fn(ResizeEvent) -> Message, { - self.on_resize = Some((leeway.into().0, Box::new(f))); + if self.internal.maximized().is_none() { + self.on_resize = Some((leeway.into().0, Box::new(f))); + } self } @@ -291,46 +289,114 @@ where } fn drag_enabled(&self) -> bool { - (!self.contents.is_maximized()) + self.internal + .maximized() + .is_none() .then(|| self.on_drag.is_some()) .unwrap_or_default() } + + fn grid_interaction( + &self, + action: &state::Action, + layout: Layout<'_>, + cursor: mouse::Cursor, + ) -> Option<mouse::Interaction> { + if action.picked_pane().is_some() { + return Some(mouse::Interaction::Grabbing); + } + + let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway); + let node = self.internal.layout(); + + let resize_axis = + action.picked_split().map(|(_, axis)| axis).or_else(|| { + resize_leeway.and_then(|leeway| { + let cursor_position = cursor.position()?; + let bounds = layout.bounds(); + + let splits = + node.split_regions(self.spacing, bounds.size()); + + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); + + hovered_split( + splits.iter(), + self.spacing + leeway, + relative_cursor, + ) + .map(|(_, axis, _)| axis) + }) + }); + + if let Some(resize_axis) = resize_axis { + return Some(match resize_axis { + Axis::Horizontal => mouse::Interaction::ResizingVertically, + Axis::Vertical => mouse::Interaction::ResizingHorizontally, + }); + } + + None + } +} + +#[derive(Default)] +struct Memory { + action: state::Action, + order: Vec<Pane>, } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for PaneGrid<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for PaneGrid<'_, Message, Theme, Renderer> where Theme: Catalog, Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { - tree::Tag::of::<state::Action>() + tree::Tag::of::<Memory>() } fn state(&self) -> tree::State { - tree::State::new(state::Action::Idle) + tree::State::new(Memory::default()) } fn children(&self) -> Vec<Tree> { - self.contents - .iter() - .map(|(_, content)| content.state()) - .collect() + self.contents.iter().map(Content::state).collect() } fn diff(&self, tree: &mut Tree) { - match &self.contents { - Contents::All(contents, _) => tree.diff_children_custom( - contents, - |state, (_, content)| content.diff(state), - |(_, content)| content.state(), - ), - Contents::Maximized(_, content, _) => tree.diff_children_custom( - &[content], - |state, content| content.diff(state), - |content| content.state(), - ), - } + let Memory { order, .. } = tree.state.downcast_ref(); + + // `Pane` always increments and is iterated by Ord so new + // states are always added at the end. We can simply remove + // states which no longer exist and `diff_children` will + // diff the remaining values in the correct order and + // add new states at the end + + let mut i = 0; + let mut j = 0; + tree.children.retain(|_| { + let retain = self.panes.get(i) == order.get(j); + + if retain { + i += 1; + } + j += 1; + + retain + }); + + tree.diff_children_custom( + &self.contents, + |state, content| content.diff(state), + Content::state, + ); + + let Memory { order, .. } = tree.state.downcast_mut(); + order.clone_from(&self.panes); } fn size(&self) -> Size<Length> { @@ -347,14 +413,23 @@ where limits: &layout::Limits, ) -> layout::Node { let size = limits.resolve(self.width, self.height, Size::ZERO); - let node = self.contents.layout(); - let regions = node.pane_regions(self.spacing, size); + let regions = self.internal.layout().pane_regions(self.spacing, size); let children = self - .contents + .panes .iter() + .copied() + .zip(&self.contents) .zip(tree.children.iter_mut()) .filter_map(|((pane, content), tree)| { + if self + .internal + .maximized() + .is_some_and(|maximized| maximized != pane) + { + return Some(layout::Node::new(Size::ZERO)); + } + let region = regions.get(&pane)?; let size = Size::new(region.width, region.height); @@ -379,17 +454,24 @@ where operation: &mut dyn widget::Operation, ) { operation.container(None, layout.bounds(), &mut |operation| { - self.contents + self.panes .iter() + .copied() + .zip(&self.contents) .zip(&mut tree.children) .zip(layout.children()) - .for_each(|(((_pane, content), state), layout)| { + .filter(|(((pane, _), _), _)| { + self.internal + .maximized() + .map_or(true, |maximized| *pane == maximized) + }) + .for_each(|(((_, content), state), layout)| { content.operate(state, layout, renderer, operation); }); }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -399,11 +481,9 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - let mut event_status = event::Status::Ignored; - - let action = tree.state.downcast_mut::<state::Action>(); - let node = self.contents.layout(); + ) { + let Memory { action, .. } = tree.state.downcast_mut(); + let node = self.internal.layout(); let on_drag = if self.drag_enabled() { &self.on_drag @@ -411,13 +491,43 @@ where &None }; + let picked_pane = action.picked_pane().map(|(pane, _)| pane); + + for (((pane, content), tree), layout) in self + .panes + .iter() + .copied() + .zip(&mut self.contents) + .zip(&mut tree.children) + .zip(layout.children()) + .filter(|(((pane, _), _), _)| { + self.internal + .maximized() + .map_or(true, |maximized| *pane == maximized) + }) + { + let is_picked = picked_pane == Some(pane); + + content.update( + tree, + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + is_picked, + ); + } + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { let bounds = layout.bounds(); if let Some(cursor_position) = cursor.position_over(bounds) { - event_status = event::Status::Captured; + shell.capture_event(); match &self.on_resize { Some((leeway, _)) => { @@ -448,7 +558,10 @@ where layout, cursor_position, shell, - self.contents.iter(), + self.panes + .iter() + .copied() + .zip(&self.contents), &self.on_click, on_drag, ); @@ -460,7 +573,7 @@ where layout, cursor_position, shell, - self.contents.iter(), + self.panes.iter().copied().zip(&self.contents), &self.on_click, on_drag, ); @@ -486,8 +599,10 @@ where } } else { let dropped_region = self - .contents + .panes .iter() + .copied() + .zip(&self.contents) .zip(layout.children()) .find_map(|(target, layout)| { layout_region( @@ -513,13 +628,13 @@ where }; shell.publish(on_drag(event)); + } else { + shell.publish(on_drag(DragEvent::Canceled { + pane, + })); } } } - - event_status = event::Status::Captured; - } else if action.picked_split().is_some() { - event_status = event::Status::Captured; } *action = state::Action::Idle; @@ -561,37 +676,48 @@ where ratio, })); - event_status = event::Status::Captured; + shell.capture_event(); } } + } else if action.picked_pane().is_some() { + shell.request_redraw(); } } } _ => {} } - let picked_pane = action.picked_pane().map(|(pane, _)| pane); - - self.contents - .iter_mut() - .zip(&mut tree.children) - .zip(layout.children()) - .map(|(((pane, content), tree), layout)| { - let is_picked = picked_pane == Some(pane); - - content.on_event( - tree, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - viewport, - is_picked, - ) - }) - .fold(event_status, event::Status::merge) + if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { + let interaction = self + .grid_interaction(action, layout, cursor) + .or_else(|| { + self.panes + .iter() + .zip(&self.contents) + .zip(layout.children()) + .filter(|((&pane, _content), _layout)| { + self.internal + .maximized() + .map_or(true, |maximized| pane == maximized) + }) + .find_map(|((_pane, content), layout)| { + content.grid_interaction( + layout, + cursor, + on_drag.is_some(), + ) + }) + }) + .unwrap_or(mouse::Interaction::None); + + if let Event::Window(window::Event::RedrawRequested(_now)) = event { + self.last_mouse_interaction = Some(interaction); + } else if self.last_mouse_interaction.is_some_and( + |last_mouse_interaction| last_mouse_interaction != interaction, + ) { + shell.request_redraw(); + } + } } fn mouse_interaction( @@ -602,50 +728,26 @@ where viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - let action = tree.state.downcast_ref::<state::Action>(); - - if action.picked_pane().is_some() { - return mouse::Interaction::Grabbing; - } - - let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway); - let node = self.contents.layout(); - - let resize_axis = - action.picked_split().map(|(_, axis)| axis).or_else(|| { - resize_leeway.and_then(|leeway| { - let cursor_position = cursor.position()?; - let bounds = layout.bounds(); - - let splits = - node.split_regions(self.spacing, bounds.size()); - - let relative_cursor = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - hovered_split( - splits.iter(), - self.spacing + leeway, - relative_cursor, - ) - .map(|(_, axis, _)| axis) - }) - }); + let Memory { action, .. } = tree.state.downcast_ref(); - if let Some(resize_axis) = resize_axis { - return match resize_axis { - Axis::Horizontal => mouse::Interaction::ResizingVertically, - Axis::Vertical => mouse::Interaction::ResizingHorizontally, - }; + if let Some(grid_interaction) = + self.grid_interaction(action, layout, cursor) + { + return grid_interaction; } - self.contents + self.panes .iter() + .copied() + .zip(&self.contents) .zip(&tree.children) .zip(layout.children()) - .map(|(((_pane, content), tree), layout)| { + .filter(|(((pane, _), _), _)| { + self.internal + .maximized() + .map_or(true, |maximized| *pane == maximized) + }) + .map(|(((_, content), tree), layout)| { content.mouse_interaction( tree, layout, @@ -669,16 +771,10 @@ where cursor: mouse::Cursor, viewport: &Rectangle, ) { - let action = tree.state.downcast_ref::<state::Action>(); - let node = self.contents.layout(); + let Memory { action, .. } = tree.state.downcast_ref(); + let node = self.internal.layout(); let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway); - let contents = self - .contents - .iter() - .zip(&tree.children) - .map(|((pane, content), tree)| (pane, (content, tree))); - let picked_pane = action.picked_pane().filter(|(_, origin)| { cursor .position() @@ -747,8 +843,18 @@ where let style = Catalog::style(theme, &self.class); - for ((id, (content, tree)), pane_layout) in - contents.zip(layout.children()) + for (((id, content), tree), pane_layout) in self + .panes + .iter() + .copied() + .zip(&self.contents) + .zip(&tree.children) + .zip(layout.children()) + .filter(|(((pane, _), _), _)| { + self.internal + .maximized() + .map_or(true, |maximized| maximized == *pane) + }) { match picked_pane { Some((dragging, origin)) if id == dragging => { @@ -883,11 +989,21 @@ where translation: Vector, ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { let children = self - .contents - .iter_mut() + .panes + .iter() + .copied() + .zip(&mut self.contents) .zip(&mut tree.children) .zip(layout.children()) - .filter_map(|(((_, content), state), layout)| { + .filter_map(|(((pane, content), state), layout)| { + if self + .internal + .maximized() + .is_some_and(|maximized| maximized != pane) + { + return None; + } + content.overlay(state, layout, renderer, translation) }) .collect::<Vec<_>>(); @@ -1136,52 +1252,6 @@ fn hovered_split<'a>( }) } -/// The visible contents of the [`PaneGrid`] -#[derive(Debug)] -pub enum Contents<'a, T> { - /// All panes are visible - All(Vec<(Pane, T)>, &'a state::Internal), - /// A maximized pane is visible - Maximized(Pane, T, Node), -} - -impl<'a, T> Contents<'a, T> { - /// Returns the layout [`Node`] of the [`Contents`] - pub fn layout(&self) -> &Node { - match self { - Contents::All(_, state) => state.layout(), - Contents::Maximized(_, _, layout) => layout, - } - } - - /// Returns an iterator over the values of the [`Contents`] - pub fn iter(&self) -> Box<dyn Iterator<Item = (Pane, &T)> + '_> { - match self { - Contents::All(contents, _) => Box::new( - contents.iter().map(|(pane, content)| (*pane, content)), - ), - Contents::Maximized(pane, content, _) => { - Box::new(std::iter::once((*pane, content))) - } - } - } - - fn iter_mut(&mut self) -> Box<dyn Iterator<Item = (Pane, &mut T)> + '_> { - match self { - Contents::All(contents, _) => Box::new( - contents.iter_mut().map(|(pane, content)| (*pane, content)), - ), - Contents::Maximized(pane, content, _) => { - Box::new(std::iter::once((*pane, content))) - } - } - } - - fn is_maximized(&self) -> bool { - matches!(self, Self::Maximized(..)) - } -} - /// The appearance of a [`PaneGrid`]. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Style { diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index ec0676b1..be5e5066 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -1,12 +1,12 @@ use crate::container; -use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::widget::{self, Tree}; use crate::core::{ - self, Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector, + self, Clipboard, Element, Event, Layout, Point, Rectangle, Shell, Size, + Vector, }; use crate::pane_grid::{Draggable, TitleBar}; @@ -73,7 +73,7 @@ where } } -impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Content<'_, Message, Theme, Renderer> where Theme: container::Catalog, Renderer: core::Renderer, @@ -239,7 +239,7 @@ where ); } - pub(crate) fn on_event( + pub(crate) fn update( &mut self, tree: &mut Tree, event: Event, @@ -250,13 +250,11 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, is_picked: bool, - ) -> event::Status { - let mut event_status = event::Status::Ignored; - + ) { let body_layout = if let Some(title_bar) = &mut self.title_bar { let mut children = layout.children(); - event_status = title_bar.on_event( + title_bar.update( &mut tree.children[1], event.clone(), children.next().unwrap(), @@ -272,10 +270,8 @@ where layout }; - let body_status = if is_picked { - event::Status::Ignored - } else { - self.body.as_widget_mut().on_event( + if !is_picked { + self.body.as_widget_mut().update( &mut tree.children[0], event, body_layout, @@ -284,10 +280,33 @@ where clipboard, shell, viewport, - ) - }; + ); + } + } + + pub(crate) fn grid_interaction( + &self, + layout: Layout<'_>, + cursor: mouse::Cursor, + drag_enabled: bool, + ) -> Option<mouse::Interaction> { + let title_bar = self.title_bar.as_ref()?; + + let mut children = layout.children(); + let title_bar_layout = children.next().unwrap(); + + let is_over_pick_area = cursor + .position() + .map(|cursor_position| { + title_bar.is_over_pick_area(title_bar_layout, cursor_position) + }) + .unwrap_or_default(); + + if is_over_pick_area && drag_enabled { + return Some(mouse::Interaction::Grab); + } - event_status.merge(body_status) + None } pub(crate) fn mouse_interaction( @@ -382,8 +401,8 @@ where } } -impl<'a, Message, Theme, Renderer> Draggable - for &Content<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Draggable + for &Content<'_, Message, Theme, Renderer> where Theme: container::Catalog, Renderer: core::Renderer, diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index c20c3b9c..2f8a64ea 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -6,7 +6,8 @@ use crate::pane_grid::{ Axis, Configuration, Direction, Edge, Node, Pane, Region, Split, Target, }; -use rustc_hash::FxHashMap; +use std::borrow::Cow; +use std::collections::BTreeMap; /// The state of a [`PaneGrid`]. /// @@ -25,17 +26,12 @@ pub struct State<T> { /// The panes of the [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid - pub panes: FxHashMap<Pane, T>, + pub panes: BTreeMap<Pane, T>, /// The internal state of the [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid pub internal: Internal, - - /// The maximized [`Pane`] of the [`PaneGrid`]. - /// - /// [`PaneGrid`]: super::PaneGrid - pub(super) maximized: Option<Pane>, } impl<T> State<T> { @@ -52,16 +48,12 @@ impl<T> State<T> { /// Creates a new [`State`] with the given [`Configuration`]. pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self { - let mut panes = FxHashMap::default(); + let mut panes = BTreeMap::default(); let internal = Internal::from_configuration(&mut panes, config.into(), 0); - State { - panes, - internal, - maximized: None, - } + State { panes, internal } } /// Returns the total amount of panes in the [`State`]. @@ -214,7 +206,7 @@ impl<T> State<T> { } let _ = self.panes.insert(new_pane, state); - let _ = self.maximized.take(); + let _ = self.internal.maximized.take(); Some((new_pane, new_split)) } @@ -228,8 +220,11 @@ impl<T> State<T> { ) { if let Some((state, _)) = self.close(pane) { if let Some((new_pane, _)) = self.split(axis, target, state) { + // Ensure new node corresponds to original closed `Pane` for state continuity + self.relabel(new_pane, pane); + if swap { - self.swap(target, new_pane); + self.swap(target, pane); } } } @@ -259,13 +254,27 @@ impl<T> State<T> { &mut self, axis: Axis, pane: Pane, - swap: bool, + inverse: bool, ) { if let Some((state, _)) = self.close(pane) { - let _ = self.split_node(axis, None, state, swap); + if let Some((new_pane, _)) = + self.split_node(axis, None, state, inverse) + { + // Ensure new node corresponds to original closed `Pane` for state continuity + self.relabel(new_pane, pane); + } } } + fn relabel(&mut self, target: Pane, label: Pane) { + self.swap(target, label); + + let _ = self + .panes + .remove(&target) + .and_then(|state| self.panes.insert(label, state)); + } + /// Swaps the position of the provided panes in the [`State`]. /// /// If you want to swap panes on drag and drop in your [`PaneGrid`], you @@ -303,8 +312,8 @@ impl<T> State<T> { /// Closes the given [`Pane`] and returns its internal state and its closest /// sibling, if it exists. pub fn close(&mut self, pane: Pane) -> Option<(T, Pane)> { - if self.maximized == Some(pane) { - let _ = self.maximized.take(); + if self.internal.maximized == Some(pane) { + let _ = self.internal.maximized.take(); } if let Some(sibling) = self.internal.layout.remove(pane) { @@ -319,7 +328,7 @@ impl<T> State<T> { /// /// [`PaneGrid`]: super::PaneGrid pub fn maximize(&mut self, pane: Pane) { - self.maximized = Some(pane); + self.internal.maximized = Some(pane); } /// Restore the currently maximized [`Pane`] to it's normal size. All panes @@ -327,14 +336,14 @@ impl<T> State<T> { /// /// [`PaneGrid`]: super::PaneGrid pub fn restore(&mut self) { - let _ = self.maximized.take(); + let _ = self.internal.maximized.take(); } /// Returns the maximized [`Pane`] of the [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid pub fn maximized(&self) -> Option<Pane> { - self.maximized + self.internal.maximized } } @@ -345,6 +354,7 @@ impl<T> State<T> { pub struct Internal { layout: Node, last_id: usize, + maximized: Option<Pane>, } impl Internal { @@ -353,7 +363,7 @@ impl Internal { /// /// [`PaneGrid`]: super::PaneGrid pub fn from_configuration<T>( - panes: &mut FxHashMap<Pane, T>, + panes: &mut BTreeMap<Pane, T>, content: Configuration<T>, next_id: usize, ) -> Self { @@ -390,18 +400,34 @@ impl Internal { } }; - Self { layout, last_id } + Self { + layout, + last_id, + maximized: None, + } + } + + pub(super) fn layout(&self) -> Cow<'_, Node> { + match self.maximized { + Some(pane) => Cow::Owned(Node::Pane(pane)), + None => Cow::Borrowed(&self.layout), + } + } + + pub(super) fn maximized(&self) -> Option<Pane> { + self.maximized } } /// The current action of a [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] pub enum Action { /// The [`PaneGrid`] is idle. /// /// [`PaneGrid`]: super::PaneGrid + #[default] Idle, /// A [`Pane`] in the [`PaneGrid`] is being dragged. /// @@ -440,10 +466,3 @@ impl Action { } } } - -impl Internal { - /// The layout [`Node`] of the [`Internal`] state - pub fn layout(&self) -> &Node { - &self.layout - } -} diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 5002b4f7..4bd2c2f6 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -1,13 +1,12 @@ use crate::container; -use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::widget::{self, Tree}; use crate::core::{ - self, Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, - Vector, + self, Clipboard, Element, Event, Layout, Padding, Point, Rectangle, Shell, + Size, Vector, }; use crate::pane_grid::controls::Controls; @@ -99,7 +98,7 @@ where } } -impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> TitleBar<'_, Message, Theme, Renderer> where Theme: container::Catalog, Renderer: core::Renderer, @@ -428,7 +427,7 @@ where } } - pub(crate) fn on_event( + pub(crate) fn update( &mut self, tree: &mut Tree, event: Event, @@ -438,7 +437,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let mut children = layout.children(); let padded = children.next().unwrap(); @@ -446,15 +445,16 @@ where let title_layout = children.next().unwrap(); let mut show_title = true; - let control_status = if let Some(controls) = &mut self.controls { + if let Some(controls) = &mut self.controls { let controls_layout = children.next().unwrap(); + if title_layout.bounds().width + controls_layout.bounds().width > padded.bounds().width { if let Some(compact) = controls.compact.as_mut() { let compact_layout = children.next().unwrap(); - compact.as_widget_mut().on_event( + compact.as_widget_mut().update( &mut tree.children[2], event.clone(), compact_layout, @@ -463,11 +463,11 @@ where clipboard, shell, viewport, - ) + ); } else { show_title = false; - controls.full.as_widget_mut().on_event( + controls.full.as_widget_mut().update( &mut tree.children[1], event.clone(), controls_layout, @@ -476,10 +476,10 @@ where clipboard, shell, viewport, - ) + ); } } else { - controls.full.as_widget_mut().on_event( + controls.full.as_widget_mut().update( &mut tree.children[1], event.clone(), controls_layout, @@ -488,14 +488,12 @@ where clipboard, shell, viewport, - ) + ); } - } else { - event::Status::Ignored - }; + } - let title_status = if show_title { - self.content.as_widget_mut().on_event( + if show_title { + self.content.as_widget_mut().update( &mut tree.children[0], event, title_layout, @@ -504,12 +502,8 @@ where clipboard, shell, viewport, - ) - } else { - event::Status::Ignored - }; - - control_status.merge(title_status) + ); + } } pub(crate) fn mouse_interaction( diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 4f1e9da9..6708e7cd 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -61,7 +61,6 @@ //! } //! ``` use crate::core::alignment; -use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::layout; use crate::core::mouse; @@ -71,9 +70,10 @@ use crate::core::text::paragraph; use crate::core::text::{self, Text}; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; +use crate::core::window; use crate::core::{ - Background, Border, Clipboard, Color, Element, Layout, Length, Padding, - Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, + Background, Border, Clipboard, Color, Element, Event, Layout, Length, + Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::overlay::menu::{self, Menu}; @@ -173,6 +173,7 @@ pub struct PickList< handle: Handle<Renderer::Font>, class: <Theme as Catalog>::Class<'a>, menu_class: <Theme as menu::Catalog>::Class<'a>, + last_status: Option<Status>, } impl<'a, T, L, V, Message, Theme, Renderer> @@ -208,6 +209,7 @@ where handle: Handle::default(), class: <Theme as Catalog>::default(), menu_class: <Theme as Catalog>::default_menu(), + last_status: None, } } @@ -425,7 +427,7 @@ where layout::Node::new(size) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -435,13 +437,12 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { + let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>(); + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { - let state = - tree.state.downcast_mut::<State<Renderer::Paragraph>>(); - if state.is_open { // Event wasn't processed by overlay, so cursor was clicked either outside its // bounds or on the drop-down, either way we close the overlay. @@ -451,7 +452,7 @@ where shell.publish(on_close.clone()); } - event::Status::Captured + shell.capture_event(); } else if cursor.is_over(layout.bounds()) { let selected = self.selected.as_ref().map(Borrow::borrow); @@ -466,17 +467,12 @@ where shell.publish(on_open.clone()); } - event::Status::Captured - } else { - event::Status::Ignored + shell.capture_event(); } } Event::Mouse(mouse::Event::WheelScrolled { delta: mouse::ScrollDelta::Lines { y, .. }, }) => { - let state = - tree.state.downcast_mut::<State<Renderer::Paragraph>>(); - if state.keyboard_modifiers.command() && cursor.is_over(layout.bounds()) && !state.is_open @@ -513,20 +509,34 @@ where shell.publish((self.on_select)(next_option.clone())); } - event::Status::Captured - } else { - event::Status::Ignored + shell.capture_event(); } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - let state = - tree.state.downcast_mut::<State<Renderer::Paragraph>>(); - state.keyboard_modifiers = modifiers; + } + _ => {} + }; + + let status = { + let is_hovered = cursor.is_over(layout.bounds()); - event::Status::Ignored + if state.is_open { + Status::Opened { is_hovered } + } else if is_hovered { + Status::Hovered + } else { + Status::Active } - _ => event::Status::Ignored, + }; + + if let Event::Window(window::Event::RedrawRequested(_now)) = event { + self.last_status = Some(status); + } else if self + .last_status + .is_some_and(|last_status| last_status != status) + { + shell.request_redraw(); } } @@ -555,7 +565,7 @@ where theme: &Theme, _style: &renderer::Style, layout: Layout<'_>, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, viewport: &Rectangle, ) { let font = self.font.unwrap_or_else(|| renderer.default_font()); @@ -563,18 +573,12 @@ where let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>(); let bounds = layout.bounds(); - let is_mouse_over = cursor.is_over(bounds); - let is_selected = selected.is_some(); - let status = if state.is_open { - Status::Opened - } else if is_mouse_over { - Status::Hovered - } else { - Status::Active - }; - - let style = Catalog::style(theme, &self.class, status); + let style = Catalog::style( + theme, + &self.class, + self.last_status.unwrap_or(Status::Active), + ); renderer.fill_quad( renderer::Quad { @@ -671,7 +675,7 @@ where wrapping: text::Wrapping::default(), }, Point::new(bounds.x + self.padding.left, bounds.center_y()), - if is_selected { + if selected.is_some() { style.text_color } else { style.placeholder_color @@ -824,7 +828,10 @@ pub enum Status { /// The [`PickList`] is being hovered. Hovered, /// The [`PickList`] is open. - Opened, + Opened { + /// Whether the [`PickList`] is hovered, while open. + is_hovered: bool, + }, } /// The appearance of a pick list. @@ -898,7 +905,7 @@ pub fn default(theme: &Theme, status: Status) -> Style { match status { Status::Active => active, - Status::Hovered | Status::Opened => Style { + Status::Hovered | Status::Opened { .. } => Style { border: Border { color: palette.primary.strong.color, ..active.border diff --git a/widget/src/pin.rs b/widget/src/pin.rs new file mode 100644 index 00000000..7c1aca61 --- /dev/null +++ b/widget/src/pin.rs @@ -0,0 +1,270 @@ +//! A pin widget positions a widget at some fixed coordinates inside its boundaries. +//! +//! # Example +//! ```no_run +//! # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::core::Length::Fill; } +//! # pub type State = (); +//! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; +//! use iced::widget::pin; +//! use iced::Fill; +//! +//! enum Message { +//! // ... +//! } +//! +//! fn view(state: &State) -> Element<'_, Message> { +//! pin("This text is displayed at coordinates (50, 50)!") +//! .x(50) +//! .y(50) +//! .into() +//! } +//! ``` +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget; +use crate::core::{ + self, Clipboard, Element, Event, Layout, Length, Pixels, Point, Rectangle, + Shell, Size, Vector, Widget, +}; + +/// A widget that positions its contents at some fixed coordinates inside of its boundaries. +/// +/// By default, a [`Pin`] widget will try to fill its parent. +/// +/// # Example +/// ```no_run +/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::core::Length::Fill; } +/// # pub type State = (); +/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; +/// use iced::widget::pin; +/// use iced::Fill; +/// +/// enum Message { +/// // ... +/// } +/// +/// fn view(state: &State) -> Element<'_, Message> { +/// pin("This text is displayed at coordinates (50, 50)!") +/// .x(50) +/// .y(50) +/// .into() +/// } +/// ``` +#[allow(missing_debug_implementations)] +pub struct Pin<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> +where + Renderer: core::Renderer, +{ + content: Element<'a, Message, Theme, Renderer>, + width: Length, + height: Length, + position: Point, +} + +impl<'a, Message, Theme, Renderer> Pin<'a, Message, Theme, Renderer> +where + Renderer: core::Renderer, +{ + /// Creates a [`Pin`] widget with the given content. + pub fn new( + content: impl Into<Element<'a, Message, Theme, Renderer>>, + ) -> Self { + Self { + content: content.into(), + width: Length::Fill, + height: Length::Fill, + position: Point::ORIGIN, + } + } + + /// Sets the width of the [`Pin`]. + pub fn width(mut self, width: impl Into<Length>) -> Self { + self.width = width.into(); + self + } + + /// Sets the height of the [`Pin`]. + pub fn height(mut self, height: impl Into<Length>) -> Self { + self.height = height.into(); + self + } + + /// Sets the position of the [`Pin`]; where the pinned widget will be displayed. + pub fn position(mut self, position: impl Into<Point>) -> Self { + self.position = position.into(); + self + } + + /// Sets the X coordinate of the [`Pin`]. + pub fn x(mut self, x: impl Into<Pixels>) -> Self { + self.position.x = x.into().0; + self + } + + /// Sets the Y coordinate of the [`Pin`]. + pub fn y(mut self, y: impl Into<Pixels>) -> Self { + self.position.y = y.into().0; + self + } +} + +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Pin<'_, Message, Theme, Renderer> +where + Renderer: core::Renderer, +{ + fn tag(&self) -> widget::tree::Tag { + self.content.as_widget().tag() + } + + fn state(&self) -> widget::tree::State { + self.content.as_widget().state() + } + + fn children(&self) -> Vec<widget::Tree> { + self.content.as_widget().children() + } + + fn diff(&self, tree: &mut widget::Tree) { + self.content.as_widget().diff(tree); + } + + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } + } + + fn layout( + &self, + tree: &mut widget::Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height(self.height); + + let available = + limits.max() - Size::new(self.position.x, self.position.y); + + let node = self + .content + .as_widget() + .layout(tree, renderer, &layout::Limits::new(Size::ZERO, available)) + .move_to(self.position); + + let size = limits.resolve(self.width, self.height, node.size()); + layout::Node::with_children(size, vec![node]) + } + + fn operate( + &self, + tree: &mut widget::Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + self.content.as_widget().operate( + tree, + layout.children().next().unwrap(), + renderer, + operation, + ); + } + + fn update( + &mut self, + tree: &mut widget::Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) { + self.content.as_widget_mut().update( + tree, + event, + layout.children().next().unwrap(), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } + + fn mouse_interaction( + &self, + tree: &widget::Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + tree, + layout.children().next().unwrap(), + cursor, + viewport, + renderer, + ) + } + + fn draw( + &self, + tree: &widget::Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + + if let Some(clipped_viewport) = bounds.intersection(viewport) { + self.content.as_widget().draw( + tree, + renderer, + theme, + style, + layout.children().next().unwrap(), + cursor, + &clipped_viewport, + ); + } + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut widget::Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { + self.content.as_widget_mut().overlay( + tree, + layout.children().next().unwrap(), + renderer, + translation, + ) + } +} + +impl<'a, Message, Theme, Renderer> From<Pin<'a, Message, Theme, Renderer>> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: 'a, + Renderer: core::Renderer + 'a, +{ + fn from( + pin: Pin<'a, Message, Theme, Renderer>, + ) -> Element<'a, Message, Theme, Renderer> { + Element::new(pin) + } +} diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs index 9d2b30f4..8f85dcf3 100644 --- a/widget/src/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -117,8 +117,8 @@ where } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for ProgressBar<'a, Theme> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for ProgressBar<'_, Theme> where Theme: Catalog, Renderer: core::Renderer, diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index d1834465..458f3588 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -116,7 +116,7 @@ where } } -impl<'a, Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'a, Theme> +impl<Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'_, Theme> where Theme: Catalog, { diff --git a/widget/src/radio.rs b/widget/src/radio.rs index d2a3bd6a..15c983df 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -58,7 +58,6 @@ //! ``` use crate::core::alignment; use crate::core::border::{self, Border}; -use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::renderer; @@ -66,9 +65,10 @@ use crate::core::text; use crate::core::touch; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; +use crate::core::window; use crate::core::{ - Background, Clipboard, Color, Element, Layout, Length, Pixels, Rectangle, - Shell, Size, Theme, Widget, + Background, Clipboard, Color, Element, Event, Layout, Length, Pixels, + Rectangle, Shell, Size, Theme, Widget, }; /// A circular button representing a choice. @@ -147,6 +147,7 @@ where text_wrapping: text::Wrapping, font: Option<Renderer::Font>, class: Theme::Class<'a>, + last_status: Option<Status>, } impl<'a, Message, Theme, Renderer> Radio<'a, Message, Theme, Renderer> @@ -192,6 +193,7 @@ where text_wrapping: text::Wrapping::default(), font: None, class: Theme::default(), + last_status: None, } } @@ -265,8 +267,8 @@ where } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Radio<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Radio<'_, Message, Theme, Renderer> where Message: Clone, Theme: Catalog, @@ -321,7 +323,7 @@ where ) } - fn on_event( + fn update( &mut self, _state: &mut Tree, event: Event, @@ -331,20 +333,37 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { if cursor.is_over(layout.bounds()) { shell.publish(self.on_click.clone()); - - return event::Status::Captured; + shell.capture_event(); } } _ => {} } - event::Status::Ignored + let current_status = { + let is_mouse_over = cursor.is_over(layout.bounds()); + let is_selected = self.is_selected; + + if is_mouse_over { + Status::Hovered { is_selected } + } else { + Status::Active { is_selected } + } + }; + + if let Event::Window(window::Event::RedrawRequested(_now)) = event { + self.last_status = Some(current_status); + } else if self + .last_status + .is_some_and(|last_status| last_status != current_status) + { + shell.request_redraw(); + } } fn mouse_interaction( @@ -369,21 +388,17 @@ where theme: &Theme, defaults: &renderer::Style, layout: Layout<'_>, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, viewport: &Rectangle, ) { - let is_mouse_over = cursor.is_over(layout.bounds()); - let is_selected = self.is_selected; - let mut children = layout.children(); - let status = if is_mouse_over { - Status::Hovered { is_selected } - } else { - Status::Active { is_selected } - }; - - let style = theme.style(&self.class, status); + let style = theme.style( + &self.class, + self.last_status.unwrap_or(Status::Active { + is_selected: self.is_selected, + }), + ); { let layout = children.next().unwrap(); diff --git a/widget/src/row.rs b/widget/src/row.rs index 9c0fa97e..3b605f07 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -1,13 +1,12 @@ //! Distribute content horizontally. use crate::core::alignment::{self, Alignment}; -use crate::core::event::{self, Event}; use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::widget::{Operation, Tree}; use crate::core::{ - Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, + Clipboard, Element, Event, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, }; @@ -172,7 +171,7 @@ where } } -impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> +impl<Message, Renderer> Default for Row<'_, Message, Renderer> where Renderer: crate::core::Renderer, { @@ -194,8 +193,8 @@ impl<'a, Message, Theme, Renderer: crate::core::Renderer> } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Row<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Row<'_, Message, Theme, Renderer> where Renderer: crate::core::Renderer, { @@ -254,7 +253,7 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -264,24 +263,24 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.children + ) { + for ((child, state), layout) in self + .children .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .map(|((child, state), layout)| { - child.as_widget_mut().on_event( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - viewport, - ) - }) - .fold(event::Status::Ignored, event::Status::merge) + { + child.as_widget_mut().update( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } } fn mouse_interaction( @@ -381,8 +380,8 @@ pub struct Wrapping< row: Row<'a, Message, Theme, Renderer>, } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Wrapping<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Wrapping<'_, Message, Theme, Renderer> where Renderer: crate::core::Renderer, { @@ -493,7 +492,7 @@ where self.row.operate(tree, layout, renderer, operation); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -503,10 +502,10 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.row.on_event( + ) { + self.row.update( tree, event, layout, cursor, renderer, clipboard, shell, viewport, - ) + ); } fn mouse_interaction( diff --git a/widget/src/rule.rs b/widget/src/rule.rs index 24577683..65c0a6dc 100644 --- a/widget/src/rule.rs +++ b/widget/src/rule.rs @@ -98,8 +98,8 @@ where } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Rule<'a, Theme> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Rule<'_, Theme> where Renderer: core::Renderer, Theme: Catalog, diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 528d63c1..b08d5d09 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -21,7 +21,6 @@ //! ``` use crate::container; use crate::core::border::{self, Border}; -use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::layout; use crate::core::mouse; @@ -34,8 +33,8 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ - self, Background, Clipboard, Color, Element, Layout, Length, Padding, - Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, + self, Background, Clipboard, Color, Element, Event, Layout, Length, + Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::runtime::task::{self, Task}; use crate::runtime::Action; @@ -81,6 +80,7 @@ pub struct Scrollable< content: Element<'a, Message, Theme, Renderer>, on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>, class: Theme::Class<'a>, + last_status: Option<Status>, } impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer> @@ -95,7 +95,7 @@ where Self::with_direction(content, Direction::default()) } - /// Creates a new vertical [`Scrollable`]. + /// Creates a new [`Scrollable`] with the given [`Direction`]. pub fn with_direction( content: impl Into<Element<'a, Message, Theme, Renderer>>, direction: impl Into<Direction>, @@ -108,6 +108,7 @@ where content: content.into(), on_scroll: None, class: Theme::default(), + last_status: None, } .validate() } @@ -136,7 +137,7 @@ where self } - /// Creates a new [`Scrollable`] with the given [`Direction`]. + /// Sets the [`Direction`] of the [`Scrollable`]. pub fn direction(mut self, direction: impl Into<Direction>) -> Self { self.direction = direction.into(); self.validate() @@ -384,8 +385,8 @@ pub enum Anchor { End, } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Scrollable<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Scrollable<'_, Message, Theme, Renderer> where Theme: Catalog, Renderer: core::Renderer, @@ -486,11 +487,11 @@ where state.translation(self.direction, bounds, content_bounds); operation.scrollable( - state, self.id.as_ref().map(|id| &id.0), bounds, content_bounds, translation, + state, ); operation.container( @@ -507,7 +508,7 @@ where ); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -517,7 +518,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::<State>(); let bounds = layout.bounds(); let cursor_over_scrollable = cursor.position_over(bounds); @@ -531,6 +532,8 @@ where let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = scrollbars.is_mouse_over(cursor); + let last_offsets = (state.offset_x, state.offset_y); + if let Some(last_scrolled) = state.last_scrolled { let clear_transaction = match event { Event::Mouse( @@ -549,335 +552,387 @@ where } } - if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at { - match event { - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - if let Some(scrollbar) = scrollbars.y { - let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; - }; + let mut update = || { + if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at { + match event { + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if let Some(scrollbar) = scrollbars.y { + let Some(cursor_position) = cursor.position() + else { + return; + }; - state.scroll_y_to( - scrollbar.scroll_percentage_y( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); + state.scroll_y_to( + scrollbar.scroll_percentage_y( + scroller_grabbed_at, + cursor_position, + ), + bounds, + content_bounds, + ); - let _ = notify_scroll( - state, - &self.on_scroll, - bounds, - content_bounds, - shell, - ); + let _ = notify_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); - return event::Status::Captured; + shell.capture_event(); + } } + _ => {} } - _ => {} - } - } else if mouse_over_y_scrollbar { - match event { - Event::Mouse(mouse::Event::ButtonPressed( - mouse::Button::Left, - )) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; - }; + } else if mouse_over_y_scrollbar { + match event { + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + let Some(cursor_position) = cursor.position() else { + return; + }; - if let (Some(scroller_grabbed_at), Some(scrollbar)) = ( - scrollbars.grab_y_scroller(cursor_position), - scrollbars.y, - ) { - state.scroll_y_to( - scrollbar.scroll_percentage_y( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); + if let (Some(scroller_grabbed_at), Some(scrollbar)) = ( + scrollbars.grab_y_scroller(cursor_position), + scrollbars.y, + ) { + state.scroll_y_to( + scrollbar.scroll_percentage_y( + scroller_grabbed_at, + cursor_position, + ), + bounds, + content_bounds, + ); - state.y_scroller_grabbed_at = Some(scroller_grabbed_at); + state.y_scroller_grabbed_at = + Some(scroller_grabbed_at); - let _ = notify_scroll( - state, - &self.on_scroll, - bounds, - content_bounds, - shell, - ); - } + let _ = notify_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); + } - return event::Status::Captured; + shell.capture_event(); + } + _ => {} } - _ => {} } - } - if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at { - match event { - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; - }; + if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at { + match event { + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + let Some(cursor_position) = cursor.position() else { + return; + }; - if let Some(scrollbar) = scrollbars.x { - state.scroll_x_to( - scrollbar.scroll_percentage_x( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); + if let Some(scrollbar) = scrollbars.x { + state.scroll_x_to( + scrollbar.scroll_percentage_x( + scroller_grabbed_at, + cursor_position, + ), + bounds, + content_bounds, + ); - let _ = notify_scroll( - state, - &self.on_scroll, - bounds, - content_bounds, - shell, - ); - } + let _ = notify_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); + } - return event::Status::Captured; + shell.capture_event(); + } + _ => {} } - _ => {} - } - } else if mouse_over_x_scrollbar { - match event { - Event::Mouse(mouse::Event::ButtonPressed( - mouse::Button::Left, - )) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; - }; + } else if mouse_over_x_scrollbar { + match event { + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + let Some(cursor_position) = cursor.position() else { + return; + }; - if let (Some(scroller_grabbed_at), Some(scrollbar)) = ( - scrollbars.grab_x_scroller(cursor_position), - scrollbars.x, - ) { - state.scroll_x_to( - scrollbar.scroll_percentage_x( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); + if let (Some(scroller_grabbed_at), Some(scrollbar)) = ( + scrollbars.grab_x_scroller(cursor_position), + scrollbars.x, + ) { + state.scroll_x_to( + scrollbar.scroll_percentage_x( + scroller_grabbed_at, + cursor_position, + ), + bounds, + content_bounds, + ); - state.x_scroller_grabbed_at = Some(scroller_grabbed_at); + state.x_scroller_grabbed_at = + Some(scroller_grabbed_at); - let _ = notify_scroll( - state, - &self.on_scroll, - bounds, - content_bounds, - shell, - ); + let _ = notify_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); - return event::Status::Captured; + shell.capture_event(); + } } + _ => {} } - _ => {} } - } - let content_status = if state.last_scrolled.is_some() - && matches!(event, Event::Mouse(mouse::Event::WheelScrolled { .. })) - { - event::Status::Ignored - } else { - let cursor = match cursor_over_scrollable { - Some(cursor_position) - if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) => - { - mouse::Cursor::Available( - cursor_position - + state.translation( - self.direction, - bounds, - content_bounds, - ), - ) - } - _ => mouse::Cursor::Unavailable, - }; + if state.last_scrolled.is_none() + || !matches!( + event, + Event::Mouse(mouse::Event::WheelScrolled { .. }) + ) + { + let cursor = match cursor_over_scrollable { + Some(cursor_position) + if !(mouse_over_x_scrollbar + || mouse_over_y_scrollbar) => + { + mouse::Cursor::Available( + cursor_position + + state.translation( + self.direction, + bounds, + content_bounds, + ), + ) + } + _ => mouse::Cursor::Unavailable, + }; - let translation = - state.translation(self.direction, bounds, content_bounds); + let translation = + state.translation(self.direction, bounds, content_bounds); - self.content.as_widget_mut().on_event( - &mut tree.children[0], - event.clone(), - content, - cursor, - renderer, - clipboard, - shell, - &Rectangle { - y: bounds.y + translation.y, - x: bounds.x + translation.x, - ..bounds - }, - ) - }; + self.content.as_widget_mut().update( + &mut tree.children[0], + event.clone(), + content, + cursor, + renderer, + clipboard, + shell, + &Rectangle { + y: bounds.y + translation.y, + x: bounds.x + translation.x, + ..bounds + }, + ); + }; - if matches!( - event, - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch( - touch::Event::FingerLifted { .. } - | touch::Event::FingerLost { .. } - ) - ) { - state.scroll_area_touched_at = None; - state.x_scroller_grabbed_at = None; - state.y_scroller_grabbed_at = None; + if matches!( + event, + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch( + touch::Event::FingerLifted { .. } + | touch::Event::FingerLost { .. } + ) + ) { + state.scroll_area_touched_at = None; + state.x_scroller_grabbed_at = None; + state.y_scroller_grabbed_at = None; - return content_status; - } + return; + } - if let event::Status::Captured = content_status { - return event::Status::Captured; - } + if shell.is_event_captured() { + return; + } - if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) = - event - { - state.keyboard_modifiers = modifiers; + if let Event::Keyboard(keyboard::Event::ModifiersChanged( + modifiers, + )) = event + { + state.keyboard_modifiers = modifiers; - return event::Status::Ignored; - } + return; + } - match event { - Event::Mouse(mouse::Event::WheelScrolled { delta }) => { - if cursor_over_scrollable.is_none() { - return event::Status::Ignored; - } + match event { + Event::Mouse(mouse::Event::WheelScrolled { delta }) => { + if cursor_over_scrollable.is_none() { + return; + } - let delta = match delta { - mouse::ScrollDelta::Lines { x, y } => { - let is_shift_pressed = state.keyboard_modifiers.shift(); + let delta = match delta { + mouse::ScrollDelta::Lines { x, y } => { + let is_shift_pressed = + state.keyboard_modifiers.shift(); - // macOS automatically inverts the axes when Shift is pressed - let (x, y) = - if cfg!(target_os = "macos") && is_shift_pressed { + // macOS automatically inverts the axes when Shift is pressed + let (x, y) = if cfg!(target_os = "macos") + && is_shift_pressed + { (y, x) } else { (x, y) }; - let is_vertical = match self.direction { - Direction::Vertical(_) => true, - Direction::Horizontal(_) => false, - Direction::Both { .. } => !is_shift_pressed, - }; - - let movement = if is_vertical { - Vector::new(x, y) - } else { - Vector::new(y, x) - }; + let is_vertical = match self.direction { + Direction::Vertical(_) => true, + Direction::Horizontal(_) => false, + Direction::Both { .. } => !is_shift_pressed, + }; - // TODO: Configurable speed/friction (?) - -movement * 60.0 - } - mouse::ScrollDelta::Pixels { x, y } => -Vector::new(x, y), - }; + let movement = if is_vertical { + Vector::new(x, y) + } else { + Vector::new(y, x) + }; - state.scroll( - self.direction.align(delta), - bounds, - content_bounds, - ); + // TODO: Configurable speed/friction (?) + -movement * 60.0 + } + mouse::ScrollDelta::Pixels { x, y } => { + -Vector::new(x, y) + } + }; - let has_scrolled = notify_scroll( - state, - &self.on_scroll, - bounds, - content_bounds, - shell, - ); + state.scroll( + self.direction.align(delta), + bounds, + content_bounds, + ); - let in_transaction = state.last_scrolled.is_some(); + let has_scrolled = notify_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); - if has_scrolled || in_transaction { - event::Status::Captured - } else { - event::Status::Ignored - } - } - Event::Touch(event) - if state.scroll_area_touched_at.is_some() - || !mouse_over_y_scrollbar && !mouse_over_x_scrollbar => - { - match event { - touch::Event::FingerPressed { .. } => { - let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; - }; + let in_transaction = state.last_scrolled.is_some(); - state.scroll_area_touched_at = Some(cursor_position); + if has_scrolled || in_transaction { + shell.capture_event(); } - touch::Event::FingerMoved { .. } => { - if let Some(scroll_box_touched_at) = - state.scroll_area_touched_at - { + } + Event::Touch(event) + if state.scroll_area_touched_at.is_some() + || !mouse_over_y_scrollbar + && !mouse_over_x_scrollbar => + { + match event { + touch::Event::FingerPressed { .. } => { let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; + return; }; - let delta = Vector::new( - scroll_box_touched_at.x - cursor_position.x, - scroll_box_touched_at.y - cursor_position.y, - ); - - state.scroll( - self.direction.align(delta), - bounds, - content_bounds, - ); - state.scroll_area_touched_at = Some(cursor_position); - - // TODO: bubble up touch movements if not consumed. - let _ = notify_scroll( - state, - &self.on_scroll, - bounds, - content_bounds, - shell, - ); } + touch::Event::FingerMoved { .. } => { + if let Some(scroll_box_touched_at) = + state.scroll_area_touched_at + { + let Some(cursor_position) = cursor.position() + else { + return; + }; + + let delta = Vector::new( + scroll_box_touched_at.x - cursor_position.x, + scroll_box_touched_at.y - cursor_position.y, + ); + + state.scroll( + self.direction.align(delta), + bounds, + content_bounds, + ); + + state.scroll_area_touched_at = + Some(cursor_position); + + // TODO: bubble up touch movements if not consumed. + let _ = notify_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); + } + } + _ => {} } - _ => {} - } - event::Status::Captured + shell.capture_event(); + } + Event::Window(window::Event::RedrawRequested(_)) => { + let _ = notify_viewport( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); + } + _ => {} } - Event::Window(window::Event::RedrawRequested(_)) => { - let _ = notify_viewport( - state, - &self.on_scroll, - bounds, - content_bounds, - shell, - ); + }; + + update(); - event::Status::Ignored + let status = if state.y_scroller_grabbed_at.is_some() + || state.x_scroller_grabbed_at.is_some() + { + Status::Dragged { + is_horizontal_scrollbar_dragged: state + .x_scroller_grabbed_at + .is_some(), + is_vertical_scrollbar_dragged: state + .y_scroller_grabbed_at + .is_some(), + is_horizontal_scrollbar_disabled: scrollbars.is_x_disabled(), + is_vertical_scrollbar_disabled: scrollbars.is_y_disabled(), } - _ => event::Status::Ignored, + } else if cursor_over_scrollable.is_some() { + Status::Hovered { + is_horizontal_scrollbar_hovered: mouse_over_x_scrollbar, + is_vertical_scrollbar_hovered: mouse_over_y_scrollbar, + is_horizontal_scrollbar_disabled: scrollbars.is_x_disabled(), + is_vertical_scrollbar_disabled: scrollbars.is_y_disabled(), + } + } else { + Status::Active { + is_horizontal_scrollbar_disabled: scrollbars.is_x_disabled(), + is_vertical_scrollbar_disabled: scrollbars.is_y_disabled(), + } + }; + + if let Event::Window(window::Event::RedrawRequested(_now)) = event { + self.last_status = Some(status); + } + + if last_offsets != (state.offset_x, state.offset_y) + || self + .last_status + .is_some_and(|last_status| last_status != status) + { + shell.request_redraw(); } } @@ -920,27 +975,13 @@ where _ => mouse::Cursor::Unavailable, }; - let status = if state.y_scroller_grabbed_at.is_some() - || state.x_scroller_grabbed_at.is_some() - { - Status::Dragged { - is_horizontal_scrollbar_dragged: state - .x_scroller_grabbed_at - .is_some(), - is_vertical_scrollbar_dragged: state - .y_scroller_grabbed_at - .is_some(), - } - } else if cursor_over_scrollable.is_some() { - Status::Hovered { - is_horizontal_scrollbar_hovered: mouse_over_x_scrollbar, - is_vertical_scrollbar_hovered: mouse_over_y_scrollbar, - } - } else { - Status::Active - }; - - let style = theme.style(&self.class, status); + let style = theme.style( + &self.class, + self.last_status.unwrap_or(Status::Active { + is_horizontal_scrollbar_disabled: false, + is_vertical_scrollbar_disabled: false, + }), + ); container::draw_background(renderer, &style.container, layout.bounds()); @@ -1323,7 +1364,7 @@ impl operation::Scrollable for State { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] enum Offset { Absolute(f32), Relative(f32), @@ -1632,6 +1673,7 @@ impl Scrollbars { bounds: scrollbar_bounds, scroller, alignment: vertical.alignment, + disabled: content_bounds.height <= bounds.height, }) } else { None @@ -1701,6 +1743,7 @@ impl Scrollbars { bounds: scrollbar_bounds, scroller, alignment: horizontal.alignment, + disabled: content_bounds.width <= bounds.width, }) } else { None @@ -1729,6 +1772,14 @@ impl Scrollbars { } } + fn is_y_disabled(&self) -> bool { + self.y.map(|y| y.disabled).unwrap_or(false) + } + + fn is_x_disabled(&self) -> bool { + self.x.map(|x| x.disabled).unwrap_or(false) + } + fn grab_y_scroller(&self, cursor_position: Point) -> Option<f32> { let scrollbar = self.y?; let scroller = scrollbar.scroller?; @@ -1775,6 +1826,7 @@ pub(super) mod internals { pub bounds: Rectangle, pub scroller: Option<Scroller>, pub alignment: Anchor, + pub disabled: bool, } impl Scrollbar { @@ -1838,13 +1890,22 @@ pub(super) mod internals { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Status { /// The [`Scrollable`] can be interacted with. - Active, + Active { + /// Whether or not the horizontal scrollbar is disabled meaning the content isn't overflowing. + is_horizontal_scrollbar_disabled: bool, + /// Whether or not the vertical scrollbar is disabled meaning the content isn't overflowing. + is_vertical_scrollbar_disabled: bool, + }, /// The [`Scrollable`] is being hovered. Hovered { /// Indicates if the horizontal scrollbar is being hovered. is_horizontal_scrollbar_hovered: bool, /// Indicates if the vertical scrollbar is being hovered. is_vertical_scrollbar_hovered: bool, + /// Whether or not the horizontal scrollbar is disabled meaning the content isn't overflowing. + is_horizontal_scrollbar_disabled: bool, + /// Whether or not the vertical scrollbar is disabled meaning the content isn't overflowing. + is_vertical_scrollbar_disabled: bool, }, /// The [`Scrollable`] is being dragged. Dragged { @@ -1852,6 +1913,10 @@ pub enum Status { is_horizontal_scrollbar_dragged: bool, /// Indicates if the vertical scrollbar is being dragged. is_vertical_scrollbar_dragged: bool, + /// Whether or not the horizontal scrollbar is disabled meaning the content isn't overflowing. + is_horizontal_scrollbar_disabled: bool, + /// Whether or not the vertical scrollbar is disabled meaning the content isn't overflowing. + is_vertical_scrollbar_disabled: bool, }, } @@ -1929,7 +1994,7 @@ pub fn default(theme: &Theme, status: Status) -> Style { }; match status { - Status::Active => Style { + Status::Active { .. } => Style { container: container::Style::default(), vertical_rail: scrollbar, horizontal_rail: scrollbar, @@ -1938,6 +2003,7 @@ pub fn default(theme: &Theme, status: Status) -> Style { Status::Hovered { is_horizontal_scrollbar_hovered, is_vertical_scrollbar_hovered, + .. } => { let hovered_scrollbar = Rail { scroller: Scroller { @@ -1965,6 +2031,7 @@ pub fn default(theme: &Theme, status: Status) -> Style { Status::Dragged { is_horizontal_scrollbar_dragged, is_vertical_scrollbar_dragged, + .. } => { let dragged_scrollbar = Rail { scroller: Scroller { diff --git a/widget/src/shader.rs b/widget/src/shader.rs index fa692336..8ec57482 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -1,23 +1,22 @@ //! A custom shader widget for wgpu applications. -mod event; mod program; -pub use event::Event; pub use program::Program; -use crate::core; +use crate::core::event; use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Widget}; use crate::core::window; -use crate::core::{Clipboard, Element, Length, Rectangle, Shell, Size}; +use crate::core::{Clipboard, Element, Event, Length, Rectangle, Shell, Size}; use crate::renderer::wgpu::primitive; use std::marker::PhantomData; pub use crate::graphics::Viewport; +pub use crate::Action; pub use primitive::{Primitive, Storage}; /// A widget which can render custom shaders with Iced's `wgpu` backend. @@ -87,7 +86,7 @@ where layout::atomic(limits, self.width, self.height) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: crate::core::Event, @@ -97,40 +96,34 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let bounds = layout.bounds(); - let custom_shader_event = match event { - core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), - core::Event::Keyboard(keyboard_event) => { - Some(Event::Keyboard(keyboard_event)) - } - core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), - core::Event::Window(window::Event::RedrawRequested(instant)) => { - Some(Event::RedrawRequested(instant)) - } - core::Event::Window(_) => None, - }; - - if let Some(custom_shader_event) = custom_shader_event { - let state = tree.state.downcast_mut::<P::State>(); + let state = tree.state.downcast_mut::<P::State>(); - let (event_status, message) = self.program.update( - state, - custom_shader_event, - bounds, - cursor, - shell, - ); + if let Some(action) = self.program.update(state, event, bounds, cursor) + { + let (message, redraw_request, event_status) = action.into_inner(); if let Some(message) = message { shell.publish(message); } - return event_status; - } + if let Some(redraw_request) = redraw_request { + match redraw_request { + window::RedrawRequest::NextFrame => { + shell.request_redraw(); + } + window::RedrawRequest::At(at) => { + shell.request_redraw_at(at); + } + } + } - event::Status::Ignored + if event_status == event::Status::Captured { + shell.capture_event(); + } + } } fn mouse_interaction( @@ -194,9 +187,8 @@ where event: Event, bounds: Rectangle, cursor: mouse::Cursor, - shell: &mut Shell<'_, Message>, - ) -> (event::Status, Option<Message>) { - T::update(self, state, event, bounds, cursor, shell) + ) -> Option<Action<Message>> { + T::update(self, state, event, bounds, cursor) } fn draw( diff --git a/widget/src/shader/event.rs b/widget/src/shader/event.rs deleted file mode 100644 index 005c8725..00000000 --- a/widget/src/shader/event.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Handle events of a custom shader widget. -use crate::core::keyboard; -use crate::core::mouse; -use crate::core::time::Instant; -use crate::core::touch; - -pub use crate::core::event::Status; - -/// A [`Shader`] event. -/// -/// [`Shader`]: crate::Shader -#[derive(Debug, Clone, PartialEq)] -pub enum Event { - /// A mouse event. - Mouse(mouse::Event), - - /// A touch event. - Touch(touch::Event), - - /// A keyboard event. - Keyboard(keyboard::Event), - - /// A window requested a redraw. - RedrawRequested(Instant), -} diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs index 902c7c3b..0fc110af 100644 --- a/widget/src/shader/program.rs +++ b/widget/src/shader/program.rs @@ -1,8 +1,7 @@ -use crate::core::event; use crate::core::mouse; -use crate::core::{Rectangle, Shell}; +use crate::core::Rectangle; use crate::renderer::wgpu::Primitive; -use crate::shader; +use crate::shader::{self, Action}; /// The state and logic of a [`Shader`] widget. /// @@ -18,10 +17,10 @@ pub trait Program<Message> { type Primitive: Primitive + 'static; /// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes - /// based on mouse & other events. You can use the [`Shell`] to publish messages, request a - /// redraw for the window, etc. + /// based on mouse & other events. You can return an [`Action`] to publish a message, request a + /// redraw, or capture the event. /// - /// By default, this method does and returns nothing. + /// By default, this method returns `None`. /// /// [`State`]: Self::State fn update( @@ -30,9 +29,8 @@ pub trait Program<Message> { _event: shader::Event, _bounds: Rectangle, _cursor: mouse::Cursor, - _shell: &mut Shell<'_, Message>, - ) -> (event::Status, Option<Message>) { - (event::Status::Ignored, None) + ) -> Option<Action<Message>> { + None } /// Draws the [`Primitive`]. diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 31aa0e0c..52500854 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -29,7 +29,6 @@ //! } //! ``` use crate::core::border::{self, Border}; -use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::keyboard::key::{self, Key}; use crate::core::layout; @@ -37,9 +36,10 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; +use crate::core::window; use crate::core::{ - self, Background, Clipboard, Color, Element, Layout, Length, Pixels, Point, - Rectangle, Shell, Size, Theme, Widget, + self, Background, Clipboard, Color, Element, Event, Layout, Length, Pixels, + Point, Rectangle, Shell, Size, Theme, Widget, }; use std::ops::RangeInclusive; @@ -95,6 +95,7 @@ where width: Length, height: f32, class: Theme::Class<'a>, + status: Option<Status>, } impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme> @@ -141,6 +142,7 @@ where width: Length::Fill, height: Self::DEFAULT_HEIGHT, class: Theme::default(), + status: None, } } @@ -208,8 +210,8 @@ where } } -impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Slider<'a, T, Message, Theme> +impl<T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Slider<'_, T, Message, Theme> where T: Copy + Into<f64> + num_traits::FromPrimitive, Message: Clone, @@ -240,7 +242,7 @@ where layout::atomic(limits, self.width, self.height) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -250,188 +252,202 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::<State>(); - let is_dragging = state.is_dragging; - let current_value = self.value; + let mut update = || { + let current_value = self.value; - let locate = |cursor_position: Point| -> Option<T> { - let bounds = layout.bounds(); - let new_value = if cursor_position.x <= bounds.x { - Some(*self.range.start()) - } else if cursor_position.x >= bounds.x + bounds.width { - Some(*self.range.end()) - } else { - let step = if state.keyboard_modifiers.shift() { - self.shift_step.unwrap_or(self.step) + let locate = |cursor_position: Point| -> Option<T> { + let bounds = layout.bounds(); + + let new_value = if cursor_position.x <= bounds.x { + Some(*self.range.start()) + } else if cursor_position.x >= bounds.x + bounds.width { + Some(*self.range.end()) } else { - self.step - } - .into(); + let step = if state.keyboard_modifiers.shift() { + self.shift_step.unwrap_or(self.step) + } else { + self.step + } + .into(); + + let start = (*self.range.start()).into(); + let end = (*self.range.end()).into(); - let start = (*self.range.start()).into(); - let end = (*self.range.end()).into(); + let percent = f64::from(cursor_position.x - bounds.x) + / f64::from(bounds.width); - let percent = f64::from(cursor_position.x - bounds.x) - / f64::from(bounds.width); + let steps = (percent * (end - start) / step).round(); + let value = steps * step + start; - let steps = (percent * (end - start) / step).round(); - let value = steps * step + start; + T::from_f64(value.min(end)) + }; - T::from_f64(value.min(end)) + new_value }; - new_value - }; + let increment = |value: T| -> Option<T> { + let step = if state.keyboard_modifiers.shift() { + self.shift_step.unwrap_or(self.step) + } else { + self.step + } + .into(); - let increment = |value: T| -> Option<T> { - let step = if state.keyboard_modifiers.shift() { - self.shift_step.unwrap_or(self.step) - } else { - self.step - } - .into(); + let steps = (value.into() / step).round(); + let new_value = step * (steps + 1.0); - let steps = (value.into() / step).round(); - let new_value = step * (steps + 1.0); + if new_value > (*self.range.end()).into() { + return Some(*self.range.end()); + } - if new_value > (*self.range.end()).into() { - return Some(*self.range.end()); - } + T::from_f64(new_value) + }; - T::from_f64(new_value) - }; + let decrement = |value: T| -> Option<T> { + let step = if state.keyboard_modifiers.shift() { + self.shift_step.unwrap_or(self.step) + } else { + self.step + } + .into(); - let decrement = |value: T| -> Option<T> { - let step = if state.keyboard_modifiers.shift() { - self.shift_step.unwrap_or(self.step) - } else { - self.step - } - .into(); + let steps = (value.into() / step).round(); + let new_value = step * (steps - 1.0); - let steps = (value.into() / step).round(); - let new_value = step * (steps - 1.0); + if new_value < (*self.range.start()).into() { + return Some(*self.range.start()); + } - if new_value < (*self.range.start()).into() { - return Some(*self.range.start()); - } + T::from_f64(new_value) + }; - T::from_f64(new_value) - }; + let change = |new_value: T| { + if (self.value.into() - new_value.into()).abs() > f64::EPSILON { + shell.publish((self.on_change)(new_value)); - let change = |new_value: T| { - if (self.value.into() - new_value.into()).abs() > f64::EPSILON { - shell.publish((self.on_change)(new_value)); + self.value = new_value; + } + }; - self.value = new_value; - } - }; + match &event { + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + if let Some(cursor_position) = + cursor.position_over(layout.bounds()) + { + if state.keyboard_modifiers.command() { + let _ = self.default.map(change); + state.is_dragging = false; + } else { + let _ = locate(cursor_position).map(change); + state.is_dragging = true; + } - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if let Some(cursor_position) = - cursor.position_over(layout.bounds()) - { - if state.keyboard_modifiers.command() { - let _ = self.default.map(change); - state.is_dragging = false; - } else { - let _ = locate(cursor_position).map(change); - state.is_dragging = true; + shell.capture_event(); } - - return event::Status::Captured; } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - if is_dragging { - if let Some(on_release) = self.on_release.clone() { - shell.publish(on_release); - } - state.is_dragging = false; + Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerLifted { .. }) + | Event::Touch(touch::Event::FingerLost { .. }) => { + if state.is_dragging { + if let Some(on_release) = self.on_release.clone() { + shell.publish(on_release); + } + state.is_dragging = false; - return event::Status::Captured; + shell.capture_event(); + } } - } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - if is_dragging { - let _ = cursor.position().and_then(locate).map(change); + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if state.is_dragging { + let _ = cursor.position().and_then(locate).map(change); - return event::Status::Captured; - } - } - Event::Mouse(mouse::Event::WheelScrolled { delta }) - if state.keyboard_modifiers.control() => - { - if cursor.is_over(layout.bounds()) { - let delta = match delta { - mouse::ScrollDelta::Lines { x: _, y } => y, - mouse::ScrollDelta::Pixels { x: _, y } => y, - }; - - if delta < 0.0 { - let _ = decrement(current_value).map(change); - } else { - let _ = increment(current_value).map(change); + shell.capture_event(); } - - return event::Status::Captured; } - } - Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { - if cursor.is_over(layout.bounds()) { - match key { - Key::Named(key::Named::ArrowUp) => { - let _ = increment(current_value).map(change); - } - Key::Named(key::Named::ArrowDown) => { + Event::Mouse(mouse::Event::WheelScrolled { delta }) + if state.keyboard_modifiers.control() => + { + if cursor.is_over(layout.bounds()) { + let delta = match delta { + mouse::ScrollDelta::Lines { x: _, y } => y, + mouse::ScrollDelta::Pixels { x: _, y } => y, + }; + + if *delta < 0.0 { let _ = decrement(current_value).map(change); + } else { + let _ = increment(current_value).map(change); } - _ => (), + + shell.capture_event(); } + } + Event::Keyboard(keyboard::Event::KeyPressed { + key, .. + }) => { + if cursor.is_over(layout.bounds()) { + match key { + Key::Named(key::Named::ArrowUp) => { + let _ = increment(current_value).map(change); + } + Key::Named(key::Named::ArrowDown) => { + let _ = decrement(current_value).map(change); + } + _ => (), + } - return event::Status::Captured; + shell.capture_event(); + } } + Event::Keyboard(keyboard::Event::ModifiersChanged( + modifiers, + )) => { + state.keyboard_modifiers = *modifiers; + } + _ => {} } - Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - state.keyboard_modifiers = modifiers; - } - _ => {} - } + }; - event::Status::Ignored + update(); + + let current_status = if state.is_dragging { + Status::Dragged + } else if cursor.is_over(layout.bounds()) { + Status::Hovered + } else { + Status::Active + }; + + if let Event::Window(window::Event::RedrawRequested(_now)) = event { + self.status = Some(current_status); + } else if self.status.is_some_and(|status| status != current_status) { + shell.request_redraw(); + } } fn draw( &self, - tree: &Tree, + _tree: &Tree, renderer: &mut Renderer, theme: &Theme, _style: &renderer::Style, layout: Layout<'_>, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, _viewport: &Rectangle, ) { - let state = tree.state.downcast_ref::<State>(); let bounds = layout.bounds(); - let is_mouse_over = cursor.is_over(bounds); - let style = theme.style( - &self.class, - if state.is_dragging { - Status::Dragged - } else if is_mouse_over { - Status::Hovered - } else { - Status::Active - }, - ); + let style = + theme.style(&self.class, self.status.unwrap_or(Status::Active)); let (handle_width, handle_height, handle_border_radius) = match style.handle.shape { diff --git a/widget/src/stack.rs b/widget/src/stack.rs index 6a44c328..d2828c56 100644 --- a/widget/src/stack.rs +++ b/widget/src/stack.rs @@ -1,12 +1,12 @@ //! Display content on top of other content. -use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::widget::{Operation, Tree}; use crate::core::{ - Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Vector, Widget, + Clipboard, Element, Event, Layout, Length, Rectangle, Shell, Size, Vector, + Widget, }; /// A container that displays children on top of each other. @@ -116,7 +116,7 @@ where } } -impl<'a, Message, Renderer> Default for Stack<'a, Message, Renderer> +impl<Message, Renderer> Default for Stack<'_, Message, Renderer> where Renderer: crate::core::Renderer, { @@ -204,7 +204,7 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -214,40 +214,41 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let is_over = cursor.is_over(layout.bounds()); - self.children + for ((child, state), layout) in self + .children .iter_mut() .rev() .zip(tree.children.iter_mut().rev()) .zip(layout.children().rev()) - .map(|((child, state), layout)| { - let status = child.as_widget_mut().on_event( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - viewport, + { + child.as_widget_mut().update( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ); + + if is_over && cursor != mouse::Cursor::Unavailable { + let interaction = child.as_widget().mouse_interaction( + state, layout, cursor, viewport, renderer, ); - if is_over && cursor != mouse::Cursor::Unavailable { - let interaction = child.as_widget().mouse_interaction( - state, layout, cursor, viewport, renderer, - ); - - if interaction != mouse::Interaction::None { - cursor = mouse::Cursor::Unavailable; - } + if interaction != mouse::Interaction::None { + cursor = mouse::Cursor::Unavailable; } + } - status - }) - .find(|&status| status == event::Status::Captured) - .unwrap_or(event::Status::Ignored) + if shell.is_event_captured() { + return; + } + } } fn mouse_interaction( diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 8d57265a..72ead4f9 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -148,8 +148,8 @@ where } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Svg<'a, Theme> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Svg<'_, Theme> where Renderer: svg::Renderer, Theme: Catalog, @@ -323,7 +323,7 @@ impl Catalog for Theme { /// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>; -impl<'a, Theme> From<Style> for StyleFn<'a, Theme> { +impl<Theme> From<Style> for StyleFn<'_, Theme> { fn from(style: Style) -> Self { Box::new(move |_theme, _status| style) } diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index 3d241375..a40f2b57 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -1,5 +1,4 @@ use crate::core::alignment; -use crate::core::event; use crate::core::layout; use crate::core::mouse; use crate::core::renderer; @@ -181,8 +180,8 @@ struct State<Link, P: Paragraph> { paragraph: P, } -impl<'a, Link, Theme, Renderer> Widget<Link, Theme, Renderer> - for Rich<'a, Link, Theme, Renderer> +impl<Link, Theme, Renderer> Widget<Link, Theme, Renderer> + for Rich<'_, Link, Theme, Renderer> where Link: Clone + 'static, Theme: Catalog, @@ -355,7 +354,7 @@ where ); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -365,7 +364,7 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Link>, _viewport: &Rectangle, - ) -> event::Status { + ) { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if let Some(position) = cursor.position_in(layout.bounds()) { @@ -374,9 +373,16 @@ where .downcast_mut::<State<Link, Renderer::Paragraph>>(); if let Some(span) = state.paragraph.hit_span(position) { - state.span_pressed = Some(span); - - return event::Status::Captured; + if self + .spans + .as_ref() + .as_ref() + .get(span) + .is_some_and(|span| span.link.is_some()) + { + state.span_pressed = Some(span); + shell.capture_event(); + } } } } @@ -409,8 +415,6 @@ where } _ => {} } - - event::Status::Ignored } fn mouse_interaction( diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index a298252a..ad852ce9 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -33,7 +33,6 @@ //! ``` use crate::core::alignment; use crate::core::clipboard::{self, Clipboard}; -use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::keyboard::key; use crate::core::layout::{self, Layout}; @@ -47,7 +46,7 @@ use crate::core::widget::operation; use crate::core::widget::{self, Widget}; use crate::core::window; use crate::core::{ - Background, Border, Color, Element, Length, Padding, Pixels, Point, + Background, Border, Color, Element, Event, Length, Padding, Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector, }; @@ -120,6 +119,7 @@ pub struct TextEditor< &Highlighter::Highlight, &Theme, ) -> highlighter::Format<Renderer::Font>, + last_status: Option<Status>, } impl<'a, Message, Theme, Renderer> @@ -147,6 +147,7 @@ where highlighter_format: |_highlight, _theme| { highlighter::Format::default() }, + last_status: None, } } } @@ -270,6 +271,7 @@ where on_edit: self.on_edit, highlighter_settings: settings, highlighter_format: to_format, + last_status: self.last_status, } } @@ -511,8 +513,8 @@ impl<Highlighter: text::Highlighter> operation::Focusable } } -impl<'a, Highlighter, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for TextEditor<'a, Highlighter, Message, Theme, Renderer> +impl<Highlighter, Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for TextEditor<'_, Highlighter, Message, Theme, Renderer> where Highlighter: text::Highlighter, Theme: Catalog, @@ -596,7 +598,7 @@ where } } - fn on_event( + fn update( &mut self, tree: &mut widget::Tree, event: Event, @@ -606,12 +608,16 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let Some(on_edit) = self.on_edit.as_ref() else { - return event::Status::Ignored; + return; }; let state = tree.state.downcast_mut::<State<Highlighter>>(); + let is_redraw = matches!( + event, + Event::Window(window::Event::RedrawRequested(_now)), + ); match event { Event::Window(window::Event::Unfocused) => { @@ -624,7 +630,7 @@ where focus.is_window_focused = true; focus.updated_at = Instant::now(); - shell.request_redraw(window::RedrawRequest::NextFrame); + shell.request_redraw(); } } Event::Window(window::Event::RedrawRequested(now)) => { @@ -637,168 +643,193 @@ where - (now - focus.updated_at).as_millis() % Focus::CURSOR_BLINK_INTERVAL_MILLIS; - shell.request_redraw(window::RedrawRequest::At( + shell.request_redraw_at( now + Duration::from_millis( millis_until_redraw as u64, ), - )); + ); } } } _ => {} } - let Some(update) = Update::from_event( + if let Some(update) = Update::from_event( event, state, layout.bounds(), self.padding, cursor, self.key_binding.as_deref(), - ) else { - return event::Status::Ignored; - }; - - match update { - Update::Click(click) => { - let action = match click.kind() { - mouse::click::Kind::Single => { - Action::Click(click.position()) - } - mouse::click::Kind::Double => Action::SelectWord, - mouse::click::Kind::Triple => Action::SelectLine, - }; - - state.focus = Some(Focus::now()); - state.last_click = Some(click); - state.drag_click = Some(click.kind()); + ) { + match update { + Update::Click(click) => { + let action = match click.kind() { + mouse::click::Kind::Single => { + Action::Click(click.position()) + } + mouse::click::Kind::Double => Action::SelectWord, + mouse::click::Kind::Triple => Action::SelectLine, + }; - shell.publish(on_edit(action)); - } - Update::Drag(position) => { - shell.publish(on_edit(Action::Drag(position))); - } - Update::Release => { - state.drag_click = None; - } - Update::Scroll(lines) => { - let bounds = self.content.0.borrow().editor.bounds(); + state.focus = Some(Focus::now()); + state.last_click = Some(click); + state.drag_click = Some(click.kind()); - if bounds.height >= i32::MAX as f32 { - return event::Status::Ignored; + shell.publish(on_edit(action)); + shell.capture_event(); + } + Update::Drag(position) => { + shell.publish(on_edit(Action::Drag(position))); } + Update::Release => { + state.drag_click = None; + } + Update::Scroll(lines) => { + let bounds = self.content.0.borrow().editor.bounds(); - let lines = lines + state.partial_scroll; - state.partial_scroll = lines.fract(); + if bounds.height >= i32::MAX as f32 { + return; + } - shell.publish(on_edit(Action::Scroll { - lines: lines as i32, - })); - } - Update::Binding(binding) => { - fn apply_binding< - H: text::Highlighter, - R: text::Renderer, - Message, - >( - binding: Binding<Message>, - content: &Content<R>, - state: &mut State<H>, - on_edit: &dyn Fn(Action) -> Message, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) { - let mut publish = |action| shell.publish(on_edit(action)); - - match binding { - Binding::Unfocus => { - state.focus = None; - state.drag_click = None; - } - Binding::Copy => { - if let Some(selection) = content.selection() { - clipboard.write( - clipboard::Kind::Standard, - selection, - ); - } - } - Binding::Cut => { - if let Some(selection) = content.selection() { - clipboard.write( - clipboard::Kind::Standard, - selection, - ); + let lines = lines + state.partial_scroll; + state.partial_scroll = lines.fract(); + shell.publish(on_edit(Action::Scroll { + lines: lines as i32, + })); + shell.capture_event(); + } + Update::Binding(binding) => { + fn apply_binding< + H: text::Highlighter, + R: text::Renderer, + Message, + >( + binding: Binding<Message>, + content: &Content<R>, + state: &mut State<H>, + on_edit: &dyn Fn(Action) -> Message, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) { + let mut publish = + |action| shell.publish(on_edit(action)); + + match binding { + Binding::Unfocus => { + state.focus = None; + state.drag_click = None; + } + Binding::Copy => { + if let Some(selection) = content.selection() { + clipboard.write( + clipboard::Kind::Standard, + selection, + ); + } + } + Binding::Cut => { + if let Some(selection) = content.selection() { + clipboard.write( + clipboard::Kind::Standard, + selection, + ); + + publish(Action::Edit(Edit::Delete)); + } + } + Binding::Paste => { + if let Some(contents) = + clipboard.read(clipboard::Kind::Standard) + { + publish(Action::Edit(Edit::Paste( + Arc::new(contents), + ))); + } + } + Binding::Move(motion) => { + publish(Action::Move(motion)); + } + Binding::Select(motion) => { + publish(Action::Select(motion)); + } + Binding::SelectWord => { + publish(Action::SelectWord); + } + Binding::SelectLine => { + publish(Action::SelectLine); + } + Binding::SelectAll => { + publish(Action::SelectAll); + } + Binding::Insert(c) => { + publish(Action::Edit(Edit::Insert(c))); + } + Binding::Enter => { + publish(Action::Edit(Edit::Enter)); + } + Binding::Backspace => { + publish(Action::Edit(Edit::Backspace)); + } + Binding::Delete => { publish(Action::Edit(Edit::Delete)); } - } - Binding::Paste => { - if let Some(contents) = - clipboard.read(clipboard::Kind::Standard) - { - publish(Action::Edit(Edit::Paste(Arc::new( - contents, - )))); + Binding::Sequence(sequence) => { + for binding in sequence { + apply_binding( + binding, content, state, on_edit, + clipboard, shell, + ); + } } - } - Binding::Move(motion) => { - publish(Action::Move(motion)); - } - Binding::Select(motion) => { - publish(Action::Select(motion)); - } - Binding::SelectWord => { - publish(Action::SelectWord); - } - Binding::SelectLine => { - publish(Action::SelectLine); - } - Binding::SelectAll => { - publish(Action::SelectAll); - } - Binding::Insert(c) => { - publish(Action::Edit(Edit::Insert(c))); - } - Binding::Enter => { - publish(Action::Edit(Edit::Enter)); - } - Binding::Backspace => { - publish(Action::Edit(Edit::Backspace)); - } - Binding::Delete => { - publish(Action::Edit(Edit::Delete)); - } - Binding::Sequence(sequence) => { - for binding in sequence { - apply_binding( - binding, content, state, on_edit, - clipboard, shell, - ); + Binding::Custom(message) => { + shell.publish(message); } } - Binding::Custom(message) => { - shell.publish(message); - } } - } - apply_binding( - binding, - self.content, - state, - on_edit, - clipboard, - shell, - ); + apply_binding( + binding, + self.content, + state, + on_edit, + clipboard, + shell, + ); + + if let Some(focus) = &mut state.focus { + focus.updated_at = Instant::now(); + } - if let Some(focus) = &mut state.focus { - focus.updated_at = Instant::now(); + shell.capture_event(); } } } - event::Status::Captured + let status = { + let is_disabled = self.on_edit.is_none(); + let is_hovered = cursor.is_over(layout.bounds()); + + if is_disabled { + Status::Disabled + } else if state.focus.is_some() { + Status::Focused { is_hovered } + } else if is_hovered { + Status::Hovered + } else { + Status::Active + } + }; + + if is_redraw { + self.last_status = Some(status); + } else if self + .last_status + .is_some_and(|last_status| status != last_status) + { + shell.request_redraw(); + } } fn draw( @@ -808,7 +839,7 @@ where theme: &Theme, _defaults: &renderer::Style, layout: Layout<'_>, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, _viewport: &Rectangle, ) { let bounds = layout.bounds(); @@ -824,20 +855,8 @@ where |highlight| (self.highlighter_format)(highlight, theme), ); - let is_disabled = self.on_edit.is_none(); - let is_mouse_over = cursor.is_over(bounds); - - let status = if is_disabled { - Status::Disabled - } else if state.focus.is_some() { - Status::Focused - } else if is_mouse_over { - Status::Hovered - } else { - Status::Active - }; - - let style = theme.style(&self.class, status); + let style = theme + .style(&self.class, self.last_status.unwrap_or(Status::Active)); renderer.fill_quad( renderer::Quad { @@ -952,13 +971,13 @@ where fn operate( &self, tree: &mut widget::Tree, - _layout: Layout<'_>, + layout: Layout<'_>, _renderer: &Renderer, operation: &mut dyn widget::Operation, ) { let state = tree.state.downcast_mut::<State<Highlighter>>(); - operation.focusable(state, None); + operation.focusable(None, layout.bounds(), state); } } @@ -1036,7 +1055,7 @@ impl<Message> Binding<Message> { status, } = event; - if status != Status::Focused { + if !matches!(status, Status::Focused { .. }) { return None; } @@ -1176,7 +1195,9 @@ impl<Message> Update<Message> { .. }) => { let status = if state.focus.is_some() { - Status::Focused + Status::Focused { + is_hovered: cursor.is_over(bounds), + } } else { Status::Active }; @@ -1222,7 +1243,10 @@ pub enum Status { /// The [`TextEditor`] is being hovered. Hovered, /// The [`TextEditor`] is focused. - Focused, + Focused { + /// Whether the [`TextEditor`] is hovered, while focused. + is_hovered: bool, + }, /// The [`TextEditor`] cannot be interacted with. Disabled, } @@ -1297,7 +1321,7 @@ pub fn default(theme: &Theme, status: Status) -> Style { }, ..active }, - Status::Focused => Style { + Status::Focused { .. } => Style { border: Border { color: palette.primary.strong.color, ..active.border diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index ff413779..57ebe46a 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -42,7 +42,6 @@ use editor::Editor; use crate::core::alignment; use crate::core::clipboard::{self, Clipboard}; -use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::keyboard::key; use crate::core::layout; @@ -57,8 +56,8 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ - Background, Border, Color, Element, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, Theme, Vector, Widget, + Background, Border, Color, Element, Event, Layout, Length, Padding, Pixels, + Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::runtime::task::{self, Task}; use crate::runtime::Action; @@ -120,6 +119,7 @@ pub struct TextInput< on_submit: Option<Message>, icon: Option<Icon<Renderer::Font>>, class: Theme::Class<'a>, + last_status: Option<Status>, } /// The default [`Padding`] of a [`TextInput`]. @@ -150,6 +150,7 @@ where on_submit: None, icon: None, class: Theme::default(), + last_status: None, } } @@ -400,7 +401,7 @@ where renderer: &mut Renderer, theme: &Theme, layout: Layout<'_>, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, value: Option<&Value>, viewport: &Rectangle, ) { @@ -416,19 +417,8 @@ where let mut children_layout = layout.children(); let text_bounds = children_layout.next().unwrap().bounds(); - let is_mouse_over = cursor.is_over(bounds); - - let status = if is_disabled { - Status::Disabled - } else if state.is_focused() { - Status::Focused - } else if is_mouse_over { - Status::Hovered - } else { - Status::Active - }; - - let style = theme.style(&self.class, status); + let style = theme + .style(&self.class, self.last_status.unwrap_or(Status::Disabled)); renderer.fill_quad( renderer::Quad { @@ -584,8 +574,8 @@ where } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for TextInput<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for TextInput<'_, Message, Theme, Renderer> where Message: Clone, Theme: Catalog, @@ -627,17 +617,26 @@ where fn operate( &self, tree: &mut Tree, - _layout: Layout<'_>, + layout: Layout<'_>, _renderer: &Renderer, operation: &mut dyn Operation, ) { let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>(); - operation.focusable(state, self.id.as_ref().map(|id| &id.0)); - operation.text_input(state, self.id.as_ref().map(|id| &id.0)); + operation.focusable( + self.id.as_ref().map(|id| &id.0), + layout.bounds(), + state, + ); + + operation.text_input( + self.id.as_ref().map(|id| &id.0), + layout.bounds(), + state, + ); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -647,7 +646,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let update_cache = |state, value| { replace_paragraph( renderer, @@ -660,22 +659,21 @@ where ); }; - match event { + match &event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { let state = state::<Renderer>(tree); + let cursor_before = state.cursor; let click_position = cursor.position_over(layout.bounds()); state.is_focused = if click_position.is_some() { - state.is_focused.or_else(|| { - let now = Instant::now(); - - Some(Focus { - updated_at: now, - now, - is_window_focused: true, - }) + let now = Instant::now(); + + Some(Focus { + updated_at: now, + now, + is_window_focused: true, }) } else { None @@ -760,7 +758,11 @@ where state.last_click = Some(click); - return event::Status::Captured; + if cursor_before != state.cursor { + shell.request_redraw(); + } + + shell.capture_event(); } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) @@ -801,11 +803,21 @@ where ) .unwrap_or(0); + let selection_before = state.cursor.selection(&value); + state .cursor .select_range(state.cursor.start(&value), position); - return event::Status::Captured; + if let Some(focus) = &mut state.is_focused { + focus.updated_at = Instant::now(); + } + + if selection_before != state.cursor.selection(&value) { + shell.request_redraw(); + } + + shell.capture_event(); } } Event::Keyboard(keyboard::Event::KeyPressed { @@ -815,7 +827,6 @@ where if let Some(focus) = &mut state.is_focused { let modifiers = state.keyboard_modifiers; - focus.updated_at = Instant::now(); match key.as_ref() { keyboard::Key::Character("c") @@ -831,14 +842,15 @@ where ); } - return event::Status::Captured; + shell.capture_event(); + return; } keyboard::Key::Character("x") if state.keyboard_modifiers.command() && !self.is_secure => { let Some(on_input) = &self.on_input else { - return event::Status::Ignored; + return; }; if let Some((start, end)) = @@ -856,17 +868,18 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + shell.capture_event(); + focus.updated_at = Instant::now(); update_cache(state, &self.value); - - return event::Status::Captured; + return; } keyboard::Key::Character("v") if state.keyboard_modifiers.command() && !state.keyboard_modifiers.alt() => { let Some(on_input) = &self.on_input else { - return event::Status::Ignored; + return; }; let content = match state.is_pasting.take() { @@ -885,7 +898,6 @@ where let mut editor = Editor::new(&mut self.value, &mut state.cursor); - editor.paste(content.clone()); let message = if let Some(paste) = &self.on_paste { @@ -894,26 +906,35 @@ where (on_input)(editor.contents()) }; shell.publish(message); + shell.capture_event(); state.is_pasting = Some(content); - + focus.updated_at = Instant::now(); update_cache(state, &self.value); - - return event::Status::Captured; + return; } keyboard::Key::Character("a") if state.keyboard_modifiers.command() => { + let cursor_before = state.cursor; + state.cursor.select_all(&self.value); - return event::Status::Captured; + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); + return; } _ => {} } if let Some(text) = text { let Some(on_input) = &self.on_input else { - return event::Status::Ignored; + return; }; state.is_pasting = None; @@ -928,12 +949,11 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + shell.capture_event(); focus.updated_at = Instant::now(); - update_cache(state, &self.value); - - return event::Status::Captured; + return; } } @@ -941,11 +961,12 @@ where keyboard::Key::Named(key::Named::Enter) => { if let Some(on_submit) = self.on_submit.clone() { shell.publish(on_submit); + shell.capture_event(); } } keyboard::Key::Named(key::Named::Backspace) => { let Some(on_input) = &self.on_input else { - return event::Status::Ignored; + return; }; if modifiers.jump() @@ -968,12 +989,14 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + shell.capture_event(); + focus.updated_at = Instant::now(); update_cache(state, &self.value); } keyboard::Key::Named(key::Named::Delete) => { let Some(on_input) = &self.on_input else { - return event::Status::Ignored; + return; }; if modifiers.jump() @@ -999,10 +1022,14 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + shell.capture_event(); + focus.updated_at = Instant::now(); update_cache(state, &self.value); } keyboard::Key::Named(key::Named::Home) => { + let cursor_before = state.cursor; + if modifiers.shift() { state.cursor.select_range( state.cursor.start(&self.value), @@ -1011,8 +1038,18 @@ where } else { state.cursor.move_to(0); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); } keyboard::Key::Named(key::Named::End) => { + let cursor_before = state.cursor; + if modifiers.shift() { state.cursor.select_range( state.cursor.start(&self.value), @@ -1021,10 +1058,20 @@ where } else { state.cursor.move_to(self.value.len()); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); } keyboard::Key::Named(key::Named::ArrowLeft) if modifiers.macos_command() => { + let cursor_before = state.cursor; + if modifiers.shift() { state.cursor.select_range( state.cursor.start(&self.value), @@ -1033,10 +1080,20 @@ where } else { state.cursor.move_to(0); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); } keyboard::Key::Named(key::Named::ArrowRight) if modifiers.macos_command() => { + let cursor_before = state.cursor; + if modifiers.shift() { state.cursor.select_range( state.cursor.start(&self.value), @@ -1045,8 +1102,18 @@ where } else { state.cursor.move_to(self.value.len()); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); } keyboard::Key::Named(key::Named::ArrowLeft) => { + let cursor_before = state.cursor; + if modifiers.jump() && !self.is_secure { if modifiers.shift() { state @@ -1062,8 +1129,18 @@ where } else { state.cursor.move_left(&self.value); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); } keyboard::Key::Named(key::Named::ArrowRight) => { + let cursor_before = state.cursor; + if modifiers.jump() && !self.is_secure { if modifiers.shift() { state @@ -1079,6 +1156,14 @@ where } else { state.cursor.move_right(&self.value); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); } keyboard::Key::Named(key::Named::Escape) => { state.is_focused = None; @@ -1087,39 +1172,22 @@ where state.keyboard_modifiers = keyboard::Modifiers::default(); - } - keyboard::Key::Named( - key::Named::Tab - | key::Named::ArrowUp - | key::Named::ArrowDown, - ) => { - return event::Status::Ignored; + + shell.capture_event(); } _ => {} } - - return event::Status::Captured; } } Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => { let state = state::<Renderer>(tree); if state.is_focused.is_some() { - match key.as_ref() { - keyboard::Key::Character("v") => { - state.is_pasting = None; - } - keyboard::Key::Named( - key::Named::Tab - | key::Named::ArrowUp - | key::Named::ArrowDown, - ) => { - return event::Status::Ignored; - } - _ => {} - } + if let keyboard::Key::Character("v") = key.as_ref() { + state.is_pasting = None; - return event::Status::Captured; + shell.capture_event(); + } } state.is_pasting = None; @@ -1127,7 +1195,7 @@ where Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { let state = state::<Renderer>(tree); - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; } Event::Window(window::Event::Unfocused) => { let state = state::<Renderer>(tree); @@ -1143,32 +1211,59 @@ where focus.is_window_focused = true; focus.updated_at = Instant::now(); - shell.request_redraw(window::RedrawRequest::NextFrame); + shell.request_redraw(); } } Event::Window(window::Event::RedrawRequested(now)) => { let state = state::<Renderer>(tree); if let Some(focus) = &mut state.is_focused { - if focus.is_window_focused { - focus.now = now; + if focus.is_window_focused + && matches!( + state.cursor.state(&self.value), + cursor::State::Index(_) + ) + { + focus.now = *now; let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS - - (now - focus.updated_at).as_millis() + - (*now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS; - shell.request_redraw(window::RedrawRequest::At( - now + Duration::from_millis( + shell.request_redraw_at( + *now + Duration::from_millis( millis_until_redraw as u64, ), - )); + ); } } } _ => {} } - event::Status::Ignored + let state = state::<Renderer>(tree); + let is_disabled = self.on_input.is_none(); + + let status = if is_disabled { + Status::Disabled + } else if state.is_focused() { + Status::Focused { + is_hovered: cursor.is_over(layout.bounds()), + } + } else if cursor.is_over(layout.bounds()) { + Status::Hovered + } else { + Status::Active + }; + + if let Event::Window(window::Event::RedrawRequested(_now)) = event { + self.last_status = Some(status); + } else if self + .last_status + .is_some_and(|last_status| status != last_status) + { + shell.request_redraw(); + } } fn draw( @@ -1535,7 +1630,10 @@ pub enum Status { /// The [`TextInput`] is being hovered. Hovered, /// The [`TextInput`] is focused. - Focused, + Focused { + /// Whether the [`TextInput`] is hovered, while focused. + is_hovered: bool, + }, /// The [`TextInput`] cannot be interacted with. Disabled, } @@ -1612,7 +1710,7 @@ pub fn default(theme: &Theme, status: Status) -> Style { }, ..active }, - Status::Focused => Style { + Status::Focused { .. } => Style { border: Border { color: palette.primary.strong.color, ..active.border diff --git a/widget/src/text_input/cursor.rs b/widget/src/text_input/cursor.rs index f682b17d..a326fc8f 100644 --- a/widget/src/text_input/cursor.rs +++ b/widget/src/text_input/cursor.rs @@ -2,13 +2,13 @@ use crate::text_input::Value; /// The cursor of a text input. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Cursor { state: State, } /// The state of a [`Cursor`]. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum State { /// Cursor without a selection Index(usize), diff --git a/widget/src/themer.rs b/widget/src/themer.rs index 499a9fe8..769cc4ca 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -1,5 +1,4 @@ use crate::container; -use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; @@ -7,8 +6,8 @@ use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::Operation; use crate::core::{ - Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle, - Shell, Size, Vector, Widget, + Background, Clipboard, Color, Element, Event, Layout, Length, Point, + Rectangle, Shell, Size, Vector, Widget, }; use std::marker::PhantomData; @@ -64,8 +63,8 @@ where } } -impl<'a, Message, Theme, NewTheme, F, Renderer> Widget<Message, Theme, Renderer> - for Themer<'a, Message, Theme, NewTheme, F, Renderer> +impl<Message, Theme, NewTheme, F, Renderer> Widget<Message, Theme, Renderer> + for Themer<'_, Message, Theme, NewTheme, F, Renderer> where F: Fn(&Theme) -> NewTheme, Renderer: crate::core::Renderer, @@ -111,7 +110,7 @@ where .operate(tree, layout, renderer, operation); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -121,10 +120,10 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.content.as_widget_mut().on_event( + ) { + self.content.as_widget_mut().update( tree, event, layout, cursor, renderer, clipboard, shell, viewport, - ) + ); } fn mouse_interaction( @@ -188,9 +187,9 @@ where content: overlay::Element<'a, Message, NewTheme, Renderer>, } - impl<'a, Message, Theme, NewTheme, Renderer> + impl<Message, Theme, NewTheme, Renderer> overlay::Overlay<Message, Theme, Renderer> - for Overlay<'a, Message, Theme, NewTheme, Renderer> + for Overlay<'_, Message, Theme, NewTheme, Renderer> where Renderer: crate::core::Renderer, { @@ -219,7 +218,7 @@ where ); } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -227,9 +226,9 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { self.content - .on_event(event, layout, cursor, renderer, clipboard, shell) + .update(event, layout, cursor, renderer, clipboard, shell); } fn operate( diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index fdd2e68c..56c2be1f 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -31,7 +31,6 @@ //! } //! ``` use crate::core::alignment; -use crate::core::event; use crate::core::layout; use crate::core::mouse; use crate::core::renderer; @@ -39,6 +38,7 @@ use crate::core::text; use crate::core::touch; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; +use crate::core::window; use crate::core::{ Border, Clipboard, Color, Element, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Theme, Widget, @@ -99,6 +99,7 @@ pub struct Toggler< spacing: f32, font: Option<Renderer::Font>, class: Theme::Class<'a>, + last_status: Option<Status>, } impl<'a, Message, Theme, Renderer> Toggler<'a, Message, Theme, Renderer> @@ -132,6 +133,7 @@ where spacing: Self::DEFAULT_SIZE / 2.0, font: None, class: Theme::default(), + last_status: None, } } @@ -243,8 +245,8 @@ where } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Toggler<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Toggler<'_, Message, Theme, Renderer> where Theme: Catalog, Renderer: text::Renderer, @@ -304,7 +306,7 @@ where ) } - fn on_event( + fn update( &mut self, _state: &mut Tree, event: Event, @@ -314,9 +316,9 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let Some(on_toggle) = &self.on_toggle else { - return event::Status::Ignored; + return; }; match event { @@ -326,13 +328,31 @@ where if mouse_over { shell.publish(on_toggle(!self.is_toggled)); - - event::Status::Captured - } else { - event::Status::Ignored + shell.capture_event(); } } - _ => event::Status::Ignored, + _ => {} + } + + let current_status = if self.on_toggle.is_none() { + Status::Disabled + } else if cursor.is_over(layout.bounds()) { + Status::Hovered { + is_toggled: self.is_toggled, + } + } else { + Status::Active { + is_toggled: self.is_toggled, + } + }; + + if let Event::Window(window::Event::RedrawRequested(_now)) = event { + self.last_status = Some(current_status); + } else if self + .last_status + .is_some_and(|status| status != current_status) + { + shell.request_redraw(); } } @@ -362,7 +382,7 @@ where theme: &Theme, style: &renderer::Style, layout: Layout<'_>, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, viewport: &Rectangle, ) { /// Makes sure that the border radius of the toggler looks good at every size. @@ -391,21 +411,8 @@ where } let bounds = toggler_layout.bounds(); - let is_mouse_over = cursor.is_over(layout.bounds()); - - let status = if self.on_toggle.is_none() { - Status::Disabled - } else if is_mouse_over { - Status::Hovered { - is_toggled: self.is_toggled, - } - } else { - Status::Active { - is_toggled: self.is_toggled, - } - }; - - let style = theme.style(&self.class, status); + let style = theme + .style(&self.class, self.last_status.unwrap_or(Status::Disabled)); let border_radius = bounds.height / BORDER_RADIUS_RATIO; let space = SPACE_RATIO * bounds.height; diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index e98f4da7..a0ffe392 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -22,7 +22,6 @@ //! } //! ``` use crate::container; -use crate::core::event::{self, Event}; use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::overlay; @@ -30,8 +29,8 @@ use crate::core::renderer; use crate::core::text; use crate::core::widget::{self, Widget}; use crate::core::{ - Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Shell, Size, - Vector, + Clipboard, Element, Event, Length, Padding, Pixels, Point, Rectangle, + Shell, Size, Vector, }; /// An element to display a widget over another. @@ -143,8 +142,8 @@ where } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Tooltip<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Tooltip<'_, Message, Theme, Renderer> where Theme: container::Catalog, Renderer: text::Renderer, @@ -190,7 +189,7 @@ where .layout(&mut tree.children[0], renderer, limits) } - fn on_event( + fn update( &mut self, tree: &mut widget::Tree, event: Event, @@ -200,7 +199,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::<State>(); let was_idle = *state == State::Idle; @@ -214,9 +213,12 @@ where if was_idle != is_idle { shell.invalidate_layout(); + shell.request_redraw(); + } else if !is_idle && self.position == Position::FollowCursor { + shell.request_redraw(); } - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( &mut tree.children[0], event, layout, @@ -225,7 +227,7 @@ where clipboard, shell, viewport, - ) + ); } fn mouse_interaction( @@ -370,9 +372,8 @@ where class: &'b Theme::Class<'a>, } -impl<'a, 'b, Message, Theme, Renderer> - overlay::Overlay<Message, Theme, Renderer> - for Overlay<'a, 'b, Message, Theme, Renderer> +impl<Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer> + for Overlay<'_, '_, Message, Theme, Renderer> where Theme: container::Catalog, Renderer: text::Renderer, diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 18633474..c1af142b 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -35,7 +35,6 @@ pub use crate::slider::{ }; use crate::core::border::Border; -use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::keyboard::key::{self, Key}; use crate::core::layout::{self, Layout}; @@ -44,8 +43,8 @@ use crate::core::renderer; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - self, Clipboard, Element, Length, Pixels, Point, Rectangle, Shell, Size, - Widget, + self, Clipboard, Element, Event, Length, Pixels, Point, Rectangle, Shell, + Size, Widget, }; /// An vertical bar and a handle that selects a single value from a range of @@ -212,8 +211,8 @@ where } } -impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for VerticalSlider<'a, T, Message, Theme> +impl<T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for VerticalSlider<'_, T, Message, Theme> where T: Copy + Into<f64> + num_traits::FromPrimitive, Message: Clone, @@ -244,7 +243,7 @@ where layout::atomic(limits, self.width, self.height) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -254,7 +253,7 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::<State>(); let is_dragging = state.is_dragging; let current_value = self.value; @@ -350,7 +349,7 @@ where state.is_dragging = true; } - return event::Status::Captured; + shell.capture_event(); } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) @@ -362,7 +361,7 @@ where } state.is_dragging = false; - return event::Status::Captured; + shell.capture_event(); } } Event::Mouse(mouse::Event::CursorMoved { .. }) @@ -370,7 +369,7 @@ where if is_dragging { let _ = cursor.position().and_then(locate).map(change); - return event::Status::Captured; + shell.capture_event(); } } Event::Mouse(mouse::Event::WheelScrolled { delta }) @@ -388,7 +387,7 @@ where let _ = increment(current_value).map(change); } - return event::Status::Captured; + shell.capture_event(); } } Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { @@ -403,7 +402,7 @@ where _ => (), } - return event::Status::Captured; + shell.capture_event(); } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { @@ -411,8 +410,6 @@ where } _ => {} } - - event::Status::Ignored } fn draw( diff --git a/winit/Cargo.toml b/winit/Cargo.toml index bd6feb00..10a6369b 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -22,7 +22,7 @@ x11 = ["winit/x11"] wayland = ["winit/wayland"] wayland-dlopen = ["winit/wayland-dlopen"] wayland-csd-adwaita = ["winit/wayland-csd-adwaita"] -multi-window = ["iced_runtime/multi-window"] +unconditional-rendering = [] [dependencies] iced_futures.workspace = true diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 5d0f8348..462be65b 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -23,6 +23,12 @@ pub fn window_attributes( width: settings.size.width, height: settings.size.height, }) + .with_maximized(settings.maximized) + .with_fullscreen( + settings + .fullscreen + .then_some(winit::window::Fullscreen::Borderless(None)), + ) .with_resizable(settings.resizable) .with_enabled_buttons(if settings.resizable { winit::window::WindowButtons::all() @@ -191,6 +197,8 @@ pub fn window_event( })) } }, + // Ignore keyboard presses/releases during window focus/unfocus + WindowEvent::KeyboardInput { is_synthetic, .. } if is_synthetic => None, WindowEvent::KeyboardInput { event, .. } => Some(Event::Keyboard({ let key = { #[cfg(not(target_arch = "wasm32"))] @@ -1112,7 +1120,7 @@ pub fn native_key_code( } } -/// Converts some [`UserAttention`] into it's `winit` counterpart. +/// Converts some [`UserAttention`] into its `winit` counterpart. /// /// [`UserAttention`]: window::UserAttention pub fn user_attention( @@ -1128,6 +1136,30 @@ pub fn user_attention( } } +/// Converts some [`window::Direction`] into a [`winit::window::ResizeDirection`]. +pub fn resize_direction( + resize_direction: window::Direction, +) -> winit::window::ResizeDirection { + match resize_direction { + window::Direction::North => winit::window::ResizeDirection::North, + window::Direction::South => winit::window::ResizeDirection::South, + window::Direction::East => winit::window::ResizeDirection::East, + window::Direction::West => winit::window::ResizeDirection::West, + window::Direction::NorthEast => { + winit::window::ResizeDirection::NorthEast + } + window::Direction::NorthWest => { + winit::window::ResizeDirection::NorthWest + } + window::Direction::SouthEast => { + winit::window::ResizeDirection::SouthEast + } + window::Direction::SouthWest => { + winit::window::ResizeDirection::SouthWest + } + } +} + /// Converts some [`window::Icon`] into it's `winit` counterpart. /// /// Returns `None` if there is an error during the conversion. diff --git a/winit/src/program.rs b/winit/src/program.rs index 8d1eec3a..5387e5e5 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -8,10 +8,11 @@ use crate::conversion; use crate::core; use crate::core::mouse; use crate::core::renderer; +use crate::core::theme; use crate::core::time::Instant; use crate::core::widget::operation; use crate::core::window; -use crate::core::{Color, Element, Point, Size, Theme}; +use crate::core::{Element, Point, Size}; use crate::futures::futures::channel::mpsc; use crate::futures::futures::channel::oneshot; use crate::futures::futures::task; @@ -46,7 +47,7 @@ use std::sync::Arc; pub trait Program where Self: Sized, - Self::Theme: DefaultStyle, + Self::Theme: theme::Base, { /// The type of __messages__ your [`Program`] will produce. type Message: std::fmt::Debug + Send; @@ -106,8 +107,8 @@ where fn theme(&self, window: window::Id) -> Self::Theme; /// Returns the `Style` variation of the `Theme`. - fn style(&self, theme: &Self::Theme) -> Appearance { - theme.default_style() + fn style(&self, theme: &Self::Theme) -> theme::Style { + theme::Base::base(theme) } /// Returns the event `Subscription` for the current state of the @@ -138,37 +139,6 @@ where } } -/// The appearance of a program. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Appearance { - /// The background [`Color`] of the application. - pub background_color: Color, - - /// The default text [`Color`] of the application. - pub text_color: Color, -} - -/// The default style of a [`Program`]. -pub trait DefaultStyle { - /// Returns the default style of a [`Program`]. - fn default_style(&self) -> Appearance; -} - -impl DefaultStyle for Theme { - fn default_style(&self) -> Appearance { - default(self) - } -} - -/// The default [`Appearance`] of a [`Program`] with the built-in [`Theme`]. -pub fn default(theme: &Theme) -> Appearance { - let palette = theme.extended_palette(); - - Appearance { - background_color: palette.background.base.color, - text_color: palette.background.base.text, - } -} /// Runs a [`Program`] with an executor, compositor, and the provided /// settings. pub fn run<P, C>( @@ -180,7 +150,7 @@ pub fn run<P, C>( where P: Program + 'static, C: Compositor<Renderer = P::Renderer> + 'static, - P::Theme: DefaultStyle, + P::Theme: theme::Base, { use winit::event_loop::EventLoop; @@ -222,7 +192,6 @@ where runtime.enter(|| program.subscription().map(Action::Output)), )); - let (boot_sender, boot_receiver) = oneshot::channel(); let (event_sender, event_receiver) = mpsc::unbounded(); let (control_sender, control_receiver) = mpsc::unbounded(); @@ -231,133 +200,49 @@ where runtime, proxy.clone(), debug, - boot_receiver, event_receiver, control_sender, is_daemon, + graphics_settings, + settings.fonts, )); let context = task::Context::from_waker(task::noop_waker_ref()); - struct Runner<Message: 'static, F, C> { + struct Runner<Message: 'static, F> { instance: std::pin::Pin<Box<F>>, context: task::Context<'static>, id: Option<String>, - boot: Option<BootConfig<C>>, sender: mpsc::UnboundedSender<Event<Action<Message>>>, receiver: mpsc::UnboundedReceiver<Control>, error: Option<Error>, #[cfg(target_arch = "wasm32")] - is_booted: std::rc::Rc<std::cell::RefCell<bool>>, - #[cfg(target_arch = "wasm32")] canvas: Option<web_sys::HtmlCanvasElement>, } - struct BootConfig<C> { - sender: oneshot::Sender<Boot<C>>, - fonts: Vec<Cow<'static, [u8]>>, - graphics_settings: graphics::Settings, - } - let runner = Runner { instance, context, id: settings.id, - boot: Some(BootConfig { - sender: boot_sender, - fonts: settings.fonts, - graphics_settings, - }), sender: event_sender, receiver: control_receiver, error: None, #[cfg(target_arch = "wasm32")] - is_booted: std::rc::Rc::new(std::cell::RefCell::new(false)), - #[cfg(target_arch = "wasm32")] canvas: None, }; - impl<Message, F, C> winit::application::ApplicationHandler<Action<Message>> - for Runner<Message, F, C> + impl<Message, F> winit::application::ApplicationHandler<Action<Message>> + for Runner<Message, F> where Message: std::fmt::Debug, F: Future<Output = ()>, - C: Compositor + 'static, { - fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { - let Some(BootConfig { - sender, - fonts, - graphics_settings, - }) = self.boot.take() - else { - return; - }; - - let window = { - let attributes = winit::window::WindowAttributes::default(); - - #[cfg(target_os = "windows")] - let attributes = { - use winit::platform::windows::WindowAttributesExtWindows; - attributes.with_drag_and_drop(false) - }; - - match event_loop.create_window(attributes.with_visible(false)) { - Ok(window) => Arc::new(window), - Err(error) => { - self.error = Some(Error::WindowCreationFailed(error)); - event_loop.exit(); - return; - } - } - }; - - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::WindowExtWebSys; - self.canvas = window.canvas(); - } - - let finish_boot = async move { - let mut compositor = - C::new(graphics_settings, window.clone()).await?; - - for font in fonts { - compositor.load_font(font); - } - - sender - .send(Boot { compositor }) - .ok() - .expect("Send boot event"); - - Ok::<_, graphics::Error>(()) - }; - - #[cfg(not(target_arch = "wasm32"))] - if let Err(error) = - crate::futures::futures::executor::block_on(finish_boot) - { - self.error = Some(Error::GraphicsCreationFailed(error)); - event_loop.exit(); - } - - #[cfg(target_arch = "wasm32")] - { - let is_booted = self.is_booted.clone(); - - wasm_bindgen_futures::spawn_local(async move { - finish_boot.await.expect("Finish boot!"); - - *is_booted.borrow_mut() = true; - }); - - event_loop - .set_control_flow(winit::event_loop::ControlFlow::Poll); - } + fn resumed( + &mut self, + _event_loop: &winit::event_loop::ActiveEventLoop, + ) { } fn new_events( @@ -365,15 +250,6 @@ where event_loop: &winit::event_loop::ActiveEventLoop, cause: winit::event::StartCause, ) { - if self.boot.is_some() { - return; - } - - #[cfg(target_arch = "wasm32")] - if !*self.is_booted.borrow() { - return; - } - self.process_event( event_loop, Event::EventLoopAwakened(winit::event::Event::NewEvents(cause)), @@ -452,11 +328,6 @@ where &mut self, event_loop: &winit::event_loop::ActiveEventLoop, ) { - #[cfg(target_arch = "wasm32")] - if !*self.is_booted.borrow() { - return; - } - self.process_event( event_loop, Event::EventLoopAwakened(winit::event::Event::AboutToWait), @@ -464,10 +335,9 @@ where } } - impl<Message, F, C> Runner<Message, F, C> + impl<Message, F> Runner<Message, F> where F: Future<Output = ()>, - C: Compositor, { fn process_event( &mut self, @@ -538,10 +408,25 @@ where log::info!("Window attributes for id `{id:#?}`: {window_attributes:#?}"); + // On macOS, the `position` in `WindowAttributes` represents the "inner" + // position of the window; while on other platforms it's the "outer" position. + // We fix the inconsistency on macOS by positioning the window after creation. + #[cfg(target_os = "macos")] + let mut window_attributes = window_attributes; + + #[cfg(target_os = "macos")] + let position = + window_attributes.position.take(); + let window = event_loop .create_window(window_attributes) .expect("Create window"); + #[cfg(target_os = "macos")] + if let Some(position) = position { + window.set_outer_position(position); + } + #[cfg(target_arch = "wasm32")] { use winit::platform::web::WindowExtWebSys; @@ -592,7 +477,7 @@ where event_loop, Event::WindowCreated { id, - window, + window: Arc::new(window), exit_on_close_request, make_visible: visible, on_open, @@ -602,6 +487,10 @@ where Control::Exit => { event_loop.exit(); } + Control::Crash(error) => { + self.error = Some(error); + event_loop.exit(); + } }, _ => { break; @@ -633,15 +522,11 @@ where } } -struct Boot<C> { - compositor: C, -} - #[derive(Debug)] enum Event<Message: 'static> { WindowCreated { id: window::Id, - window: winit::window::Window, + window: Arc<winit::window::Window>, exit_on_close_request: bool, make_visible: bool, on_open: oneshot::Sender<window::Id>, @@ -653,6 +538,7 @@ enum Event<Message: 'static> { enum Control { ChangeFlow(winit::event_loop::ControlFlow), Exit, + Crash(Error), CreateWindow { id: window::Id, settings: window::Settings, @@ -667,23 +553,23 @@ async fn run_instance<P, C>( mut runtime: Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>, mut proxy: Proxy<P::Message>, mut debug: Debug, - boot: oneshot::Receiver<Boot<C>>, mut event_receiver: mpsc::UnboundedReceiver<Event<Action<P::Message>>>, mut control_sender: mpsc::UnboundedSender<Control>, is_daemon: bool, + graphics_settings: graphics::Settings, + default_fonts: Vec<Cow<'static, [u8]>>, ) where P: Program + 'static, C: Compositor<Renderer = P::Renderer> + 'static, - P::Theme: DefaultStyle, + P::Theme: theme::Base, { use winit::event; use winit::event_loop::ControlFlow; - let Boot { mut compositor } = boot.await.expect("Receive boot"); - let mut window_manager = WindowManager::new(); let mut is_window_opening = !is_daemon; + let mut compositor = None; let mut events = Vec::new(); let mut messages = Vec::new(); let mut actions = 0; @@ -691,12 +577,35 @@ async fn run_instance<P, C>( let mut ui_caches = FxHashMap::default(); let mut user_interfaces = ManuallyDrop::new(FxHashMap::default()); let mut clipboard = Clipboard::unconnected(); + let mut compositor_receiver: Option<oneshot::Receiver<_>> = None; debug.startup_finished(); loop { + let event = if compositor_receiver.is_some() { + let compositor_receiver = + compositor_receiver.take().expect("Waiting for compositor"); + + match compositor_receiver.await { + Ok(Ok((new_compositor, event))) => { + compositor = Some(new_compositor); + + Some(event) + } + Ok(Err(error)) => { + control_sender + .start_send(Control::Crash( + Error::GraphicsCreationFailed(error), + )) + .expect("Send control action"); + break; + } + Err(error) => { + panic!("Compositor initialization failed: {error}") + } + } // Empty the queue if possible - let event = if let Ok(event) = event_receiver.try_next() { + } else if let Ok(event) = event_receiver.try_next() { event } else { event_receiver.next().await @@ -714,11 +623,63 @@ async fn run_instance<P, C>( make_visible, on_open, } => { + if compositor.is_none() { + let (compositor_sender, new_compositor_receiver) = + oneshot::channel(); + + compositor_receiver = Some(new_compositor_receiver); + + let create_compositor = { + let default_fonts = default_fonts.clone(); + + async move { + let mut compositor = + C::new(graphics_settings, window.clone()).await; + + if let Ok(compositor) = &mut compositor { + for font in default_fonts { + compositor.load_font(font.clone()); + } + } + + compositor_sender + .send(compositor.map(|compositor| { + ( + compositor, + Event::WindowCreated { + id, + window, + exit_on_close_request, + make_visible, + on_open, + }, + ) + })) + .ok() + .expect("Send compositor"); + } + }; + + #[cfg(not(target_arch = "wasm32"))] + crate::futures::futures::executor::block_on( + create_compositor, + ); + + #[cfg(target_arch = "wasm32")] + { + wasm_bindgen_futures::spawn_local(create_compositor); + } + + continue; + } + let window = window_manager.insert( id, - Arc::new(window), + window, &program, - &mut compositor, + compositor + .as_mut() + .expect("Compositor must be initialized"), exit_on_close_request, ); @@ -758,12 +719,23 @@ async fn run_instance<P, C>( } Event::EventLoopAwakened(event) => { match event { + event::Event::NewEvents(event::StartCause::Init) => { + for (_id, window) in window_manager.iter_mut() { + window.raw.request_redraw(); + } + } event::Event::NewEvents( - event::StartCause::Init - | event::StartCause::ResumeTimeReached { .. }, + event::StartCause::ResumeTimeReached { .. }, ) => { + let now = Instant::now(); + for (_id, window) in window_manager.iter_mut() { - window.raw.request_redraw(); + if let Some(redraw_at) = window.redraw_at { + if redraw_at <= now { + window.raw.request_redraw(); + window.redraw_at = None; + } + } } } event::Event::PlatformSpecific( @@ -801,17 +773,49 @@ async fn run_instance<P, C>( event: event::WindowEvent::RedrawRequested, .. } => { + let Some(compositor) = &mut compositor else { + continue; + }; + let Some((id, window)) = window_manager.get_mut_alias(id) else { continue; }; - // TODO: Avoid redrawing all the time by forcing widgets to - // request redraws on state changes - // - // Then, we can use the `interface_state` here to decide if a redraw - // is needed right away, or simply wait until a specific time. + let physical_size = window.state.physical_size(); + + if physical_size.width == 0 || physical_size.height == 0 + { + continue; + } + + if window.viewport_version + != window.state.viewport_version() + { + let logical_size = window.state.logical_size(); + + debug.layout_started(); + let ui = user_interfaces + .remove(&id) + .expect("Remove user interface"); + + let _ = user_interfaces.insert( + id, + ui.relayout(logical_size, &mut window.renderer), + ); + debug.layout_finished(); + + compositor.configure_surface( + &mut window.surface, + physical_size.width, + physical_size.height, + ); + + window.viewport_version = + window.state.viewport_version(); + } + let redraw_event = core::Event::Window( window::Event::RedrawRequested(Instant::now()), ); @@ -857,81 +861,18 @@ async fn run_instance<P, C>( status: core::event::Status::Ignored, }); - let _ = control_sender.start_send(Control::ChangeFlow( - match ui_state { - user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - window::RedrawRequest::NextFrame => { - window.raw.request_redraw(); - - ControlFlow::Wait - } - window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - }, - )); - - let physical_size = window.state.physical_size(); - - if physical_size.width == 0 || physical_size.height == 0 - { - continue; - } - - if window.viewport_version - != window.state.viewport_version() + if let user_interface::State::Updated { + redraw_request: Some(redraw_request), + } = ui_state { - let logical_size = window.state.logical_size(); - - debug.layout_started(); - let ui = user_interfaces - .remove(&id) - .expect("Remove user interface"); - - let _ = user_interfaces.insert( - id, - ui.relayout(logical_size, &mut window.renderer), - ); - debug.layout_finished(); - - debug.draw_started(); - let new_mouse_interaction = user_interfaces - .get_mut(&id) - .expect("Get user interface") - .draw( - &mut window.renderer, - window.state.theme(), - &renderer::Style { - text_color: window.state.text_color(), - }, - window.state.cursor(), - ); - debug.draw_finished(); - - if new_mouse_interaction != window.mouse_interaction - { - window.raw.set_cursor( - conversion::mouse_interaction( - new_mouse_interaction, - ), - ); - - window.mouse_interaction = - new_mouse_interaction; + match redraw_request { + window::RedrawRequest::NextFrame => { + window.raw.request_redraw(); + } + window::RedrawRequest::At(at) => { + window.redraw_at = Some(at); + } } - - compositor.configure_surface( - &mut window.surface, - physical_size.width, - physical_size.height, - ); - - window.viewport_version = - window.state.viewport_version(); } debug.render_started(); @@ -995,6 +936,13 @@ async fn run_instance<P, C>( if matches!( window_event, + winit::event::WindowEvent::Resized(_) + ) { + window.raw.request_redraw(); + } + + if matches!( + window_event, winit::event::WindowEvent::CloseRequested ) && window.exit_on_close_request { @@ -1031,7 +979,10 @@ async fn run_instance<P, C>( } } event::Event::AboutToWait => { - if events.is_empty() && messages.is_empty() { + if events.is_empty() + && messages.is_empty() + && window_manager.is_idle() + { continue; } @@ -1065,13 +1016,27 @@ async fn run_instance<P, C>( &mut messages, ); + #[cfg(feature = "unconditional-rendering")] window.raw.request_redraw(); - if !uis_stale { - uis_stale = matches!( - ui_state, - user_interface::State::Outdated - ); + match ui_state { + #[cfg(not( + feature = "unconditional-rendering" + ))] + user_interface::State::Updated { + redraw_request: Some(redraw_request), + } => match redraw_request { + window::RedrawRequest::NextFrame => { + window.raw.request_redraw(); + } + window::RedrawRequest::At(at) => { + window.redraw_at = Some(at); + } + }, + user_interface::State::Outdated => { + uis_stale = true; + } + user_interface::State::Updated { .. } => {} } for (event, status) in window_events @@ -1139,6 +1104,17 @@ async fn run_instance<P, C>( actions = 0; } } + + if let Some(redraw_at) = window_manager.redraw_at() { + let _ = + control_sender.start_send(Control::ChangeFlow( + ControlFlow::WaitUntil(redraw_at), + )); + } else { + let _ = control_sender.start_send( + Control::ChangeFlow(ControlFlow::Wait), + ); + } } _ => {} } @@ -1159,7 +1135,7 @@ fn build_user_interface<'a, P: Program>( id: window::Id, ) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> where - P::Theme: DefaultStyle, + P::Theme: theme::Base, { debug.view_started(); let view = program.view(id); @@ -1178,7 +1154,7 @@ fn update<P: Program, E: Executor>( debug: &mut Debug, messages: &mut Vec<P::Message>, ) where - P::Theme: DefaultStyle, + P::Theme: theme::Base, { for message in messages.drain(..) { debug.log_message(&message); @@ -1199,7 +1175,7 @@ fn update<P: Program, E: Executor>( fn run_action<P, C>( action: Action<P::Message>, program: &P, - compositor: &mut C, + compositor: &mut Option<C>, events: &mut Vec<(window::Id, core::Event)>, messages: &mut Vec<P::Message>, clipboard: &mut Clipboard, @@ -1215,7 +1191,7 @@ fn run_action<P, C>( ) where P: Program, C: Compositor<Renderer = P::Renderer> + 'static, - P::Theme: DefaultStyle, + P::Theme: theme::Base, { use crate::runtime::clipboard; use crate::runtime::system; @@ -1267,6 +1243,10 @@ fn run_action<P, C>( core::Event::Window(core::window::Event::Closed), )); } + + if window_manager.is_empty() { + *compositor = None; + } } window::Action::GetOldest(channel) => { let id = @@ -1285,6 +1265,13 @@ fn run_action<P, C>( let _ = window.raw.drag_window(); } } + window::Action::DragResize(id, direction) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = window.raw.drag_resize_window( + conversion::resize_direction(direction), + ); + } + } window::Action::Resize(id, size) => { if let Some(window) = window_manager.get_mut(id) { let _ = window.raw.request_inner_size( @@ -1295,6 +1282,41 @@ fn run_action<P, C>( ); } } + window::Action::SetMinSize(id, size) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_min_inner_size(size.map(|size| { + winit::dpi::LogicalSize { + width: size.width, + height: size.height, + } + })); + } + } + window::Action::SetMaxSize(id, size) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_max_inner_size(size.map(|size| { + winit::dpi::LogicalSize { + width: size.width, + height: size.height, + } + })); + } + } + window::Action::SetResizeIncrements(id, increments) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_resize_increments(increments.map(|size| { + winit::dpi::LogicalSize { + width: size.width, + height: size.height, + } + })); + } + } + window::Action::SetResizable(id, resizable) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_resizable(resizable); + } + } window::Action::GetSize(id, channel) => { if let Some(window) = window_manager.get_mut(id) { let size = window @@ -1329,7 +1351,7 @@ fn run_action<P, C>( if let Some(window) = window_manager.get(id) { let position = window .raw - .inner_position() + .outer_position() .map(|position| { let position = position .to_logical::<f32>(window.raw.scale_factor()); @@ -1358,7 +1380,7 @@ fn run_action<P, C>( ); } } - window::Action::ChangeMode(id, mode) => { + window::Action::SetMode(id, mode) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_visible(conversion::visible(mode)); window.raw.set_fullscreen(conversion::fullscreen( @@ -1367,7 +1389,7 @@ fn run_action<P, C>( )); } } - window::Action::ChangeIcon(id, icon) => { + window::Action::SetIcon(id, icon) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_window_icon(conversion::icon(icon)); } @@ -1405,7 +1427,7 @@ fn run_action<P, C>( window.raw.focus_window(); } } - window::Action::ChangeLevel(id, level) => { + window::Action::SetLevel(id, level) => { if let Some(window) = window_manager.get_mut(id) { window .raw @@ -1443,19 +1465,20 @@ fn run_action<P, C>( } window::Action::Screenshot(id, channel) => { if let Some(window) = window_manager.get_mut(id) { - let bytes = compositor.screenshot( - &mut window.renderer, - &mut window.surface, - window.state.viewport(), - window.state.background_color(), - &debug.overlay(), - ); + if let Some(compositor) = compositor { + let bytes = compositor.screenshot( + &mut window.renderer, + window.state.viewport(), + window.state.background_color(), + &debug.overlay(), + ); - let _ = channel.send(window::Screenshot::new( - bytes, - window.state.physical_size(), - window.state.viewport().scale_factor(), - )); + let _ = channel.send(core::window::Screenshot::new( + bytes, + window.state.physical_size(), + window.state.viewport().scale_factor(), + )); + } } } window::Action::EnableMousePassthrough(id) => { @@ -1473,14 +1496,16 @@ fn run_action<P, C>( system::Action::QueryInformation(_channel) => { #[cfg(feature = "system")] { - let graphics_info = compositor.fetch_information(); + if let Some(compositor) = compositor { + let graphics_info = compositor.fetch_information(); - let _ = std::thread::spawn(move || { - let information = - crate::system::information(graphics_info); + let _ = std::thread::spawn(move || { + let information = + crate::system::information(graphics_info); - let _ = _channel.send(information); - }); + let _ = _channel.send(information); + }); + } } } }, @@ -1504,10 +1529,12 @@ fn run_action<P, C>( } } Action::LoadFont { bytes, channel } => { - // TODO: Error handling (?) - compositor.load_font(bytes.clone()); + if let Some(compositor) = compositor { + // TODO: Error handling (?) + compositor.load_font(bytes.clone()); - let _ = channel.send(Ok(())); + let _ = channel.send(Ok(())); + } } Action::Exit => { control_sender @@ -1526,7 +1553,7 @@ pub fn build_user_interfaces<'a, P: Program, C>( ) -> FxHashMap<window::Id, UserInterface<'a, P::Message, P::Theme, P::Renderer>> where C: Compositor<Renderer = P::Renderer>, - P::Theme: DefaultStyle, + P::Theme: theme::Base, { cached_user_interfaces .drain() diff --git a/winit/src/program/state.rs b/winit/src/program/state.rs index a7fa2788..e883d04a 100644 --- a/winit/src/program/state.rs +++ b/winit/src/program/state.rs @@ -1,17 +1,18 @@ use crate::conversion; -use crate::core::{mouse, window}; +use crate::core::{mouse, theme, window}; use crate::core::{Color, Size}; use crate::graphics::Viewport; -use crate::program::{self, Program}; -use std::fmt::{Debug, Formatter}; +use crate::program::Program; use winit::event::{Touch, WindowEvent}; use winit::window::Window; +use std::fmt::{Debug, Formatter}; + /// The state of a multi-windowed [`Program`]. pub struct State<P: Program> where - P::Theme: program::DefaultStyle, + P::Theme: theme::Base, { title: String, scale_factor: f64, @@ -20,12 +21,12 @@ where cursor_position: Option<winit::dpi::PhysicalPosition<f64>>, modifiers: winit::keyboard::ModifiersState, theme: P::Theme, - appearance: program::Appearance, + style: theme::Style, } impl<P: Program> Debug for State<P> where - P::Theme: program::DefaultStyle, + P::Theme: theme::Base, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("multi_window::State") @@ -34,14 +35,14 @@ where .field("viewport", &self.viewport) .field("viewport_version", &self.viewport_version) .field("cursor_position", &self.cursor_position) - .field("appearance", &self.appearance) + .field("style", &self.style) .finish() } } impl<P: Program> State<P> where - P::Theme: program::DefaultStyle, + P::Theme: theme::Base, { /// Creates a new [`State`] for the provided [`Program`]'s `window`. pub fn new( @@ -52,7 +53,7 @@ where let title = application.title(window_id); let scale_factor = application.scale_factor(window_id); let theme = application.theme(window_id); - let appearance = application.style(&theme); + let style = application.style(&theme); let viewport = { let physical_size = window.inner_size(); @@ -71,7 +72,7 @@ where cursor_position: None, modifiers: winit::keyboard::ModifiersState::default(), theme, - appearance, + style, } } @@ -127,12 +128,12 @@ where /// Returns the current background [`Color`] of the [`State`]. pub fn background_color(&self) -> Color { - self.appearance.background_color + self.style.background_color } /// Returns the current text [`Color`] of the [`State`]. pub fn text_color(&self) -> Color { - self.appearance.text_color + self.style.text_color } /// Processes the provided window event and updates the [`State`] accordingly. @@ -190,7 +191,10 @@ where .. }, .. - } => _debug.toggle(), + } => { + _debug.toggle(); + window.request_redraw(); + } _ => {} } } @@ -234,6 +238,6 @@ where // Update theme and appearance self.theme = application.theme(window_id); - self.appearance = application.style(&self.theme); + self.style = application.style(&self.theme); } } diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index 3d22e155..a3c991df 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -1,8 +1,10 @@ use crate::core::mouse; +use crate::core::theme; +use crate::core::time::Instant; use crate::core::window::Id; use crate::core::{Point, Size}; use crate::graphics::Compositor; -use crate::program::{DefaultStyle, Program, State}; +use crate::program::{Program, State}; use std::collections::BTreeMap; use std::sync::Arc; @@ -13,7 +15,7 @@ pub struct WindowManager<P, C> where P: Program, C: Compositor<Renderer = P::Renderer>, - P::Theme: DefaultStyle, + P::Theme: theme::Base, { aliases: BTreeMap<winit::window::WindowId, Id>, entries: BTreeMap<Id, Window<P, C>>, @@ -23,7 +25,7 @@ impl<P, C> WindowManager<P, C> where P: Program, C: Compositor<Renderer = P::Renderer>, - P::Theme: DefaultStyle, + P::Theme: theme::Base, { pub fn new() -> Self { Self { @@ -62,6 +64,7 @@ where surface, renderer, mouse_interaction: mouse::Interaction::None, + redraw_at: None, }, ); @@ -74,6 +77,19 @@ where self.entries.is_empty() } + pub fn is_idle(&self) -> bool { + self.entries + .values() + .all(|window| window.redraw_at.is_none()) + } + + pub fn redraw_at(&self) -> Option<Instant> { + self.entries + .values() + .filter_map(|window| window.redraw_at) + .min() + } + pub fn first(&self) -> Option<&Window<P, C>> { self.entries.first_key_value().map(|(_id, window)| window) } @@ -117,7 +133,7 @@ impl<P, C> Default for WindowManager<P, C> where P: Program, C: Compositor<Renderer = P::Renderer>, - P::Theme: DefaultStyle, + P::Theme: theme::Base, { fn default() -> Self { Self::new() @@ -129,7 +145,7 @@ pub struct Window<P, C> where P: Program, C: Compositor<Renderer = P::Renderer>, - P::Theme: DefaultStyle, + P::Theme: theme::Base, { pub raw: Arc<winit::window::Window>, pub state: State<P>, @@ -138,17 +154,18 @@ where pub mouse_interaction: mouse::Interaction, pub surface: C::Surface, pub renderer: P::Renderer, + pub redraw_at: Option<Instant>, } impl<P, C> Window<P, C> where P: Program, C: Compositor<Renderer = P::Renderer>, - P::Theme: DefaultStyle, + P::Theme: theme::Base, { pub fn position(&self) -> Option<Point> { self.raw - .inner_position() + .outer_position() .ok() .map(|position| position.to_logical(self.raw.scale_factor())) .map(|position| Point { diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 78368a04..e2bf8abf 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -1,4 +1,6 @@ //! Configure your application. +use crate::core; + use std::borrow::Cow; /// The settings of an application. @@ -13,3 +15,12 @@ pub struct Settings { /// The fonts to load on boot. pub fonts: Vec<Cow<'static, [u8]>>, } + +impl From<core::Settings> for Settings { + fn from(settings: core::Settings) -> Self { + Self { + id: settings.id, + fonts: settings.fonts, + } + } +} |