summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.cargo/config.toml1
-rw-r--r--.github/workflows/test.yml2
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock7419
-rw-r--r--Cargo.toml43
-rw-r--r--core/Cargo.toml1
-rw-r--r--core/src/element.rs25
-rw-r--r--core/src/keyboard/key.rs6
-rw-r--r--core/src/layout/flex.rs53
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/overlay.rs8
-rw-r--r--core/src/overlay/element.rs27
-rw-r--r--core/src/overlay/group.rs39
-rw-r--r--core/src/renderer.rs19
-rw-r--r--core/src/settings.rs (renamed from src/settings.rs)13
-rw-r--r--core/src/shell.rs47
-rw-r--r--core/src/text.rs4
-rw-r--r--core/src/theme.rs47
-rw-r--r--core/src/theme/palette.rs155
-rw-r--r--core/src/widget.rs8
-rw-r--r--core/src/widget/operation.rs203
-rw-r--r--core/src/widget/operation/focusable.rs35
-rw-r--r--core/src/widget/operation/scrollable.rs6
-rw-r--r--core/src/widget/operation/text_input.rs28
-rw-r--r--core/src/widget/text.rs14
-rw-r--r--core/src/window.rs4
-rw-r--r--core/src/window/direction.rs27
-rw-r--r--core/src/window/event.rs4
-rw-r--r--core/src/window/screenshot.rs (renamed from runtime/src/window/screenshot.rs)2
-rw-r--r--core/src/window/settings.rs9
-rw-r--r--examples/bezier_tool/src/main.rs80
-rw-r--r--examples/changelog/Cargo.toml3
-rw-r--r--examples/color_palette/src/main.rs1
-rw-r--r--examples/counter/Cargo.toml5
-rw-r--r--examples/counter/src/main.rs28
-rw-r--r--examples/custom_quad/src/main.rs2
-rw-r--r--examples/custom_shader/src/scene/pipeline.rs8
-rw-r--r--examples/custom_widget/src/main.rs4
-rw-r--r--examples/download_progress/src/download.rs19
-rw-r--r--examples/download_progress/src/main.rs68
-rw-r--r--examples/game_of_life/src/main.rs52
-rw-r--r--examples/geometry/src/main.rs2
-rw-r--r--examples/gradient/src/main.rs10
-rw-r--r--examples/integration/src/scene.rs4
-rw-r--r--examples/layout/src/main.rs24
-rw-r--r--examples/loading_spinners/Cargo.toml1
-rw-r--r--examples/loading_spinners/src/circular.rs13
-rw-r--r--examples/loading_spinners/src/easing.rs15
-rw-r--r--examples/loading_spinners/src/linear.rs13
-rw-r--r--examples/loupe/src/main.rs2
-rw-r--r--examples/multi_window/Cargo.toml2
-rw-r--r--examples/multitouch/src/main.rs33
-rw-r--r--examples/scrollable/Cargo.toml2
-rw-r--r--examples/scrollable/src/main.rs5
-rw-r--r--examples/sierpinski_triangle/src/main.rs33
-rw-r--r--examples/styling/src/main.rs54
-rw-r--r--examples/system_information/src/main.rs20
-rw-r--r--examples/toast/src/main.rs79
-rw-r--r--examples/todos/Cargo.toml5
-rw-r--r--examples/todos/snapshots/creates_a_new_task.sha2561
-rw-r--r--examples/todos/src/main.rs57
-rw-r--r--examples/tour/Cargo.toml2
-rw-r--r--examples/visible_bounds/Cargo.toml2
-rw-r--r--examples/visible_bounds/src/main.rs10
-rw-r--r--examples/websocket/Cargo.toml1
-rw-r--r--examples/websocket/src/main.rs5
-rw-r--r--graphics/Cargo.toml1
-rw-r--r--graphics/src/compositor.rs2
-rw-r--r--graphics/src/damage.rs5
-rw-r--r--graphics/src/geometry/stroke.rs4
-rw-r--r--graphics/src/text.rs25
-rw-r--r--graphics/src/text/paragraph.rs4
-rw-r--r--highlighter/Cargo.toml1
-rw-r--r--highlighter/src/lib.rs10
-rw-r--r--renderer/src/fallback.rs41
-rw-r--r--renderer/src/lib.rs28
-rw-r--r--runtime/src/overlay/nested.rs76
-rw-r--r--runtime/src/task.rs63
-rw-r--r--runtime/src/user_interface.rs10
-rw-r--r--runtime/src/window.rs81
-rw-r--r--src/application.rs9
-rw-r--r--src/daemon.rs9
-rw-r--r--src/lib.rs9
-rw-r--r--src/program.rs29
-rw-r--r--src/window/icon.rs4
-rw-r--r--test/Cargo.toml24
-rw-r--r--test/src/lib.rs637
-rw-r--r--test/src/selector.rs29
-rw-r--r--tiny_skia/src/lib.rs25
-rw-r--r--tiny_skia/src/window/compositor.rs9
-rw-r--r--wgpu/Cargo.toml1
-rw-r--r--wgpu/src/color.rs4
-rw-r--r--wgpu/src/image/mod.rs4
-rw-r--r--wgpu/src/quad/gradient.rs4
-rw-r--r--wgpu/src/quad/solid.rs4
-rw-r--r--wgpu/src/text.rs25
-rw-r--r--wgpu/src/triangle.rs22
-rw-r--r--wgpu/src/triangle/msaa.rs4
-rw-r--r--wgpu/src/window/compositor.rs1
-rw-r--r--widget/Cargo.toml1
-rw-r--r--widget/src/action.rs89
-rw-r--r--widget/src/button.rs85
-rw-r--r--widget/src/canvas.rs71
-rw-r--r--widget/src/canvas/event.rs21
-rw-r--r--widget/src/canvas/program.rs15
-rw-r--r--widget/src/checkbox.rs72
-rw-r--r--widget/src/column.rs43
-rw-r--r--widget/src/combo_box.rs46
-rw-r--r--widget/src/container.rs19
-rw-r--r--widget/src/helpers.rs102
-rw-r--r--widget/src/image/viewer.rs32
-rw-r--r--widget/src/keyed/column.rs43
-rw-r--r--widget/src/lazy.rs34
-rw-r--r--widget/src/lazy/component.rs88
-rw-r--r--widget/src/lazy/responsive.rs70
-rw-r--r--widget/src/lib.rs5
-rw-r--r--widget/src/markdown.rs11
-rw-r--r--widget/src/mouse_area.rs162
-rw-r--r--widget/src/overlay/menu.rs69
-rw-r--r--widget/src/pane_grid.rs442
-rw-r--r--widget/src/pane_grid/content.rs53
-rw-r--r--widget/src/pane_grid/state.rs83
-rw-r--r--widget/src/pane_grid/title_bar.rs42
-rw-r--r--widget/src/pick_list.rs83
-rw-r--r--widget/src/pin.rs270
-rw-r--r--widget/src/progress_bar.rs4
-rw-r--r--widget/src/qr_code.rs2
-rw-r--r--widget/src/radio.rs57
-rw-r--r--widget/src/row.rs53
-rw-r--r--widget/src/rule.rs4
-rw-r--r--widget/src/scrollable.rs677
-rw-r--r--widget/src/shader.rs58
-rw-r--r--widget/src/shader/event.rs25
-rw-r--r--widget/src/shader/program.rs16
-rw-r--r--widget/src/slider.rs298
-rw-r--r--widget/src/stack.rs57
-rw-r--r--widget/src/svg.rs6
-rw-r--r--widget/src/text/rich.rs24
-rw-r--r--widget/src/text_editor.rs340
-rw-r--r--widget/src/text_input.rs270
-rw-r--r--widget/src/text_input/cursor.rs4
-rw-r--r--widget/src/themer.rs27
-rw-r--r--widget/src/toggler.rs61
-rw-r--r--widget/src/tooltip.rs25
-rw-r--r--widget/src/vertical_slider.rs25
-rw-r--r--winit/Cargo.toml2
-rw-r--r--winit/src/conversion.rs34
-rw-r--r--winit/src/program.rs579
-rw-r--r--winit/src/program/state.rs32
-rw-r--r--winit/src/program/window_manager.rs31
-rw-r--r--winit/src/settings.rs11
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:
diff --git a/.gitignore b/.gitignore
index f05ec438..9d164436 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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",
+]
diff --git a/Cargo.toml b/Cargo.toml
index bee83d2e..be9f8b49 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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>,
> {
diff --git a/src/lib.rs b/src/lib.rs
index 8f526cfd..427e789c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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 @@
//! ```
//! ![Checkbox drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/checkbox.png?raw=true)
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,
+ }
+ }
+}