From f3b2500b80178ff233e39b9408b7dedcae101571 Mon Sep 17 00:00:00 2001
From: Caleb Yates <calebyates42@gmail.com>
Date: Sat, 7 Dec 2024 00:06:03 +1000
Subject: [PATCH] Update to bevy 0.15 (#166)

* feat: main code compiles (untested)

* feat: basic_sprite example works as expected

* mark: example basic_ui works

* mark: every_option example works
except for the border, which was just removed

* mark: example font_per_widget works

* mark: image_background example works

* marK: multiple_sprites example works

* marK: placeholder example works

* marK: readonly example works

* marK: readonly example works

* mark: final example works!

* feat: updated module structure to use a prelude

* feat: fixed all warnings

* fix: tests pass

* refactor: `CosmicBuffer` -> `CosmicEditBuffer` as `bevy::text::CosmicBuffer` already exists

* todo: remove cosmic source component

* refactor: Removed `CosmicSource`

* feat: Removed old `CosmicWidgetSize` component in favour of an implementation-specific getter world query

* feat: half the examples compiling and working
Except for this annoying bug with the ui looking weird

* mark: only every_option example to go

* feat: All examples ported over

* refactor: Renamed `ScrollDisabled` to `ScrollEnabled`

* fix: cargo doc returns no warnings

* refactor: Using bevy builtin `SwashCache`

* fix: documentation is now more consistent

* fix: documentation and privacy are now much better

* fix: wasm compiles again

* refactor: utils is now private module

* doc: minor adjustments

* fix: utils module now public

* doc: documented `print_editor_sizes`

* fmt

* doc: todo

* todo: move all target-specific code into render_targets module

* todo: clean up lots of mathy code todo with render boxes

* wip: trying to debug annoying UI start glitch

* feat: fixed final UI bug

* fmt
---
 Cargo.lock                                    | 2067 +++++++++++++----
 Cargo.toml                                    |   19 +-
 TODO.md                                       |    5 +
 examples/basic_sprite.rs                      |   34 +-
 examples/basic_ui.rs                          |   44 +-
 examples/bevy_editor_pls.rs                   |   73 +
 examples/every_option.rs                      |   81 +-
 examples/font_per_widget.rs                   |   76 +-
 examples/image_background.rs                  |   42 +-
 examples/multiple_sprites.rs                  |   75 +-
 examples/password.rs                          |   29 +-
 examples/placeholder.rs                       |   65 +-
 examples/readonly.rs                          |   62 +-
 examples/sprite_and_ui_clickable.rs           |   71 +-
 readme.md                                     |    3 +-
 src/buffer.rs                                 |  156 +-
 src/cosmic_edit.rs                            |  224 +-
 src/cursor.rs                                 |  119 +-
 src/events.rs                                 |    1 +
 src/focus.rs                                  |   14 +-
 src/input.rs                                  |  114 +-
 src/lib.rs                                    |  281 +--
 src/password.rs                               |   60 +-
 src/placeholder.rs                            |   28 +-
 src/primary.rs                                |  163 ++
 src/render.rs                                 |   79 +-
 src/render_targets.rs                         |  242 ++
 ...it__primary__tests__spawn_cosmic_edit.snap |    8 +
 ...cosmic_edit__tests__spawn_cosmic_edit.snap |    8 -
 src/user_select.rs                            |    3 +-
 src/{util.rs => utils.rs}                     |  104 +-
 src/widget.rs                                 |  139 +-
 32 files changed, 2905 insertions(+), 1584 deletions(-)
 create mode 100644 TODO.md
 create mode 100644 examples/bevy_editor_pls.rs
 create mode 100644 src/primary.rs
 create mode 100644 src/render_targets.rs
 create mode 100644 src/snapshots/bevy_cosmic_edit__primary__tests__spawn_cosmic_edit.snap
 delete mode 100644 src/snapshots/bevy_cosmic_edit__tests__spawn_cosmic_edit.snap
 rename src/{util.rs => utils.rs} (63%)

diff --git a/Cargo.lock b/Cargo.lock
index f953811..c691068 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,12 +1,12 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "ab_glyph"
-version = "0.2.28"
+version = "0.2.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79faae4620f45232f599d9bc7b290f88247a0834162c4495ab2f02d60004adfb"
+checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0"
 dependencies = [
  "ab_glyph_rasterizer",
  "owned_ttf_parser",
@@ -20,52 +20,55 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
 
 [[package]]
 name = "accesskit"
-version = "0.14.0"
+version = "0.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6cf780eb737f2d4a49ffbd512324d53ad089070f813f7be7f99dbd5123a7f448"
+checksum = "d3d3b8f9bae46a948369bc4a03e815d4ed6d616bd00de4051133a5019dc31c5a"
 
 [[package]]
 name = "accesskit_consumer"
-version = "0.22.0"
+version = "0.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3bdfa1638ddd6eb9c752def95568df8b3ad832df252e9156d2eb783b201ca8a9"
+checksum = "f47983a1084940ba9a39c077a8c63e55c619388be5476ac04c804cfbd1e63459"
 dependencies = [
  "accesskit",
+ "hashbrown 0.15.2",
  "immutable-chunkmap",
 ]
 
 [[package]]
 name = "accesskit_macos"
-version = "0.15.0"
+version = "0.18.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c236a84ff1111defc280cee755eaa953d0b24398786851b9d28322c6d3bb1ebd"
+checksum = "7329821f3bd1101e03a7d2e03bd339e3ac0dc64c70b4c9f9ae1949e3ba8dece1"
 dependencies = [
  "accesskit",
  "accesskit_consumer",
+ "hashbrown 0.15.2",
  "objc2",
  "objc2-app-kit",
  "objc2-foundation",
- "once_cell",
 ]
 
 [[package]]
 name = "accesskit_windows"
-version = "0.20.0"
+version = "0.24.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d7f43d24b16b3e76bef248124fbfd2493c3a9860edb5aae1010c890e826de5e"
+checksum = "24fcd5d23d70670992b823e735e859374d694a3d12bfd8dd32bd3bd8bedb5d81"
 dependencies = [
  "accesskit",
  "accesskit_consumer",
+ "hashbrown 0.15.2",
  "paste",
  "static_assertions",
- "windows 0.54.0",
+ "windows",
+ "windows-core",
 ]
 
 [[package]]
 name = "accesskit_winit"
-version = "0.20.4"
+version = "0.23.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "755535e6bf711a42dac28b888b884b10fc00ff4010d9d3bd871c5f5beae5aa78"
+checksum = "6a6a48dad5530b6deb9fc7a52cc6c3bf72cdd9eb8157ac9d32d69f2427a5e879"
 dependencies = [
  "accesskit",
  "accesskit_macos",
@@ -93,6 +96,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
 dependencies = [
  "cfg-if",
+ "const-random",
  "getrandom",
  "once_cell",
  "version_check",
@@ -209,7 +213,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -232,11 +236,22 @@ checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
 
 [[package]]
 name = "ash"
-version = "0.37.3+1.3.251"
+version = "0.38.0+1.3.281"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
+dependencies = [
+ "libloading",
+]
+
+[[package]]
+name = "assert_type_match"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a"
+checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043"
 dependencies = [
- "libloading 0.7.4",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -308,6 +323,12 @@ version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
 
+[[package]]
+name = "atomicow"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "467163b50876d3a4a44da5f4dbd417537e522fc059ede8d518d57941cfb3d745"
+
 [[package]]
 name = "autocfg"
 version = "1.3.0"
@@ -345,30 +366,76 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
 [[package]]
 name = "bevy"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8ece0d8dde51890fb52dcba5b04fd1c657617a4022908c327b2d6e83d173a32"
+checksum = "b6a01cd51a5cd310e4e7aa6e1560b1aabf29efc6a095a01e6daa8bf0a19f1fea"
 dependencies = [
  "bevy_internal",
 ]
 
+[[package]]
+name = "bevy-inspector-egui"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd64580f4496ed987c6231c6a7d833068914331a9084bf5a3dd9dcbc66fd8a73"
+dependencies = [
+ "bevy-inspector-egui-derive",
+ "bevy_app",
+ "bevy_asset",
+ "bevy_color",
+ "bevy_core",
+ "bevy_core_pipeline",
+ "bevy_ecs",
+ "bevy_egui",
+ "bevy_hierarchy",
+ "bevy_image",
+ "bevy_log",
+ "bevy_math",
+ "bevy_pbr",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_state",
+ "bevy_time",
+ "bevy_utils",
+ "bevy_window",
+ "bytemuck",
+ "disqualified",
+ "egui",
+ "fuzzy-matcher",
+ "image",
+ "smallvec",
+ "winit",
+]
+
+[[package]]
+name = "bevy-inspector-egui-derive"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3afc67826e0a4347414545e022e748f42550a577a502b26af44e6d03742c9266"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "bevy_a11y"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82d84e0ae7155afa21c4926fb8b89ebe0cbd66239ac817fdb834025327c4089b"
+checksum = "82c66b5bc82a2660a5663d85b3354ddb72c8ab2c443989333cbea146f39a4e9a"
 dependencies = [
  "accesskit",
  "bevy_app",
  "bevy_derive",
  "bevy_ecs",
+ "bevy_reflect",
 ]
 
 [[package]]
 name = "bevy_app"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0af99549f5de61cc91c8c23303b13aa07f97b73fbace39695dee0a0a32cec9d4"
+checksum = "652574e4c10efcfa70f98036709dd5b67e5cb8d46c58087ef48c2ac6b62df9da"
 dependencies = [
  "bevy_derive",
  "bevy_ecs",
@@ -376,38 +443,44 @@ dependencies = [
  "bevy_tasks",
  "bevy_utils",
  "console_error_panic_hook",
+ "ctrlc",
+ "derive_more",
  "downcast-rs",
- "thiserror",
  "wasm-bindgen",
  "web-sys",
 ]
 
 [[package]]
 name = "bevy_asset"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6533d17f13b44ea4fb5177f83b0900269ed13c0fd45772ccffd19a69980647ec"
+checksum = "4d7d501eda01be6d500d843a06d9b9800c3f0fffaae3c29d17d9e4e172c28d37"
 dependencies = [
  "async-broadcast",
  "async-fs",
  "async-lock",
+ "atomicow",
  "bevy_app",
  "bevy_asset_macros",
  "bevy_ecs",
  "bevy_reflect",
  "bevy_tasks",
  "bevy_utils",
- "bevy_winit",
+ "bevy_window",
+ "bitflags 2.6.0",
  "blake3",
  "crossbeam-channel",
+ "derive_more",
+ "disqualified",
  "downcast-rs",
+ "either",
  "futures-io",
  "futures-lite",
  "js-sys",
  "parking_lot",
  "ron",
  "serde",
- "thiserror",
+ "stackfuture",
  "uuid",
  "wasm-bindgen",
  "wasm-bindgen-futures",
@@ -416,36 +489,36 @@ dependencies = [
 
 [[package]]
 name = "bevy_asset_macros"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74b0e132a89e254c0f5c8bc8deebb0f2490f5662f4aa2215a6996701446d6a7b"
+checksum = "7474b77fc27db11ec03d49ca04f1a7471f369dc373fd5e091a12ad7ab533d8c8"
 dependencies = [
  "bevy_macro_utils",
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
 name = "bevy_color"
-version = "0.14.2"
+version = "0.15.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82c031f121b8d72e7637c94ef139097613bd32935784d36728f83e77cfdf26f4"
+checksum = "87bccacba27db37375eb97ffc86e91a7d95db3f5faa6a834fa7306db02cde327"
 dependencies = [
  "bevy_math",
  "bevy_reflect",
  "bytemuck",
+ "derive_more",
  "encase",
  "serde",
- "thiserror",
  "wgpu-types",
 ]
 
 [[package]]
 name = "bevy_core"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ccc7118a2865267136afb5e6a2c0aed30994e522f298b2ba0b088878e6ddf59"
+checksum = "ecccf7be33330f58d4c7033b212a25c414d388e3a8d55b61331346da5dbabf22"
 dependencies = [
  "bevy_app",
  "bevy_ecs",
@@ -457,9 +530,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_core_pipeline"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "559ad1dc48c3fa6bbace503df2fe44a7de38c8dfe11bee911ec0ffaf93e3e57d"
+checksum = "8a3fb9f84fa60c2006d4a15e039c3d08d4d10599441b9175907341a77a69d627"
 dependencies = [
  "bevy_app",
  "bevy_asset",
@@ -467,26 +540,28 @@ dependencies = [
  "bevy_core",
  "bevy_derive",
  "bevy_ecs",
+ "bevy_image",
  "bevy_math",
  "bevy_reflect",
  "bevy_render",
  "bevy_transform",
  "bevy_utils",
+ "bevy_window",
  "bitflags 2.6.0",
+ "derive_more",
  "nonmax",
  "radsort",
  "serde",
  "smallvec",
- "thiserror",
 ]
 
 [[package]]
 name = "bevy_cosmic_edit"
-version = "0.25.0"
+version = "0.26.0"
 dependencies = [
  "arboard",
  "bevy",
- "cosmic-text",
+ "bevy_editor_pls",
  "crossbeam-channel",
  "document-features",
  "image",
@@ -501,20 +576,20 @@ dependencies = [
 
 [[package]]
 name = "bevy_derive"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8675f337f374b2b8ae90539982b947d171f9adb302d00c032b823bd5231f8978"
+checksum = "e141b7eda52a23bb88740b37a291e26394524cb9ee3b034c7014669671fc2bb5"
 dependencies = [
  "bevy_macro_utils",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
 name = "bevy_diagnostic"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdcc1d7ba5797e4285a7867227134d4cabaaf8cabfb7cdc42eb697d3b3db0460"
+checksum = "fa97748337405089edfb2857f7608f21bcc648a7ad272c9209808aad252ed542"
 dependencies = [
  "bevy_app",
  "bevy_core",
@@ -527,9 +602,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_ecs"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a3eed7f144811946ebfa1c740da9e3bcd6dd2dd4da844eda085249d29bc9fef"
+checksum = "cb4c4b60d2a712c6d5cbe610bac7ecf0838fc56a095fd5b15f30230873e84f15"
 dependencies = [
  "bevy_ecs_macros",
  "bevy_ptr",
@@ -538,30 +613,106 @@ dependencies = [
  "bevy_utils",
  "bitflags 2.6.0",
  "concurrent-queue",
+ "derive_more",
+ "disqualified",
  "fixedbitset 0.5.7",
  "nonmax",
  "petgraph",
  "serde",
- "thiserror",
+ "smallvec",
 ]
 
 [[package]]
 name = "bevy_ecs_macros"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d523630f2eb9fde6727e6c5ea48fa708079c5345da21ffeb1a4bd8ca761830da"
+checksum = "cb4296b3254b8bd29769f6a4512731b2e6c4b163343ca18b72316927315b6096"
 dependencies = [
  "bevy_macro_utils",
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
+]
+
+[[package]]
+name = "bevy_editor_pls"
+version = "0.11.0"
+source = "git+https://github.com/ActuallyHappening/bevy_editor_pls.git#2e57ce6e49cf0963903922770ccbc2e32353564c"
+dependencies = [
+ "bevy",
+ "bevy_editor_pls_core",
+ "bevy_editor_pls_default_windows",
+ "egui",
+ "transform-gizmo-bevy",
+]
+
+[[package]]
+name = "bevy_editor_pls_core"
+version = "0.11.0"
+source = "git+https://github.com/ActuallyHappening/bevy_editor_pls.git#2e57ce6e49cf0963903922770ccbc2e32353564c"
+dependencies = [
+ "bevy",
+ "bevy-inspector-egui",
+ "egui_dock",
+ "indexmap",
+]
+
+[[package]]
+name = "bevy_editor_pls_default_windows"
+version = "0.11.0"
+source = "git+https://github.com/ActuallyHappening/bevy_editor_pls.git#2e57ce6e49cf0963903922770ccbc2e32353564c"
+dependencies = [
+ "bevy",
+ "bevy-inspector-egui",
+ "bevy_editor_pls_core",
+ "bevy_mod_debugdump",
+ "indexmap",
+ "opener",
+ "pretty-type-name",
+ "transform-gizmo-bevy",
+]
+
+[[package]]
+name = "bevy_egui"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "954fbe8551af4b40767ea9390ec7d32fe1070a6ab55d524cf0868c17f8469a55"
+dependencies = [
+ "arboard",
+ "bevy_app",
+ "bevy_asset",
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_image",
+ "bevy_input",
+ "bevy_log",
+ "bevy_math",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_time",
+ "bevy_utils",
+ "bevy_window",
+ "bevy_winit",
+ "bytemuck",
+ "crossbeam-channel",
+ "egui",
+ "encase",
+ "js-sys",
+ "log",
+ "thread_local",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "webbrowser",
+ "wgpu-types",
+ "winit",
 ]
 
 [[package]]
 name = "bevy_encase_derive"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a20ec101b103f430481112591e280a8fc3f2db6741579f885595372073b749b"
+checksum = "bfe562b883fb652acde84cb6bb01cbc9f23c377e411f1484467ecfdd3a3d234e"
 dependencies = [
  "bevy_macro_utils",
  "encase_derive_impl",
@@ -569,9 +720,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_gizmos"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "248324352331d719071b0d1545a43e3d63470f4730e75312edee575f210d3a77"
+checksum = "e1c82341f6a3517efeeeef2fe68135ac3a91b11b6e369fc1a07f6e9a4b462b57"
 dependencies = [
  "bevy_app",
  "bevy_asset",
@@ -579,7 +730,9 @@ dependencies = [
  "bevy_core_pipeline",
  "bevy_ecs",
  "bevy_gizmos_macros",
+ "bevy_image",
  "bevy_math",
+ "bevy_pbr",
  "bevy_reflect",
  "bevy_render",
  "bevy_sprite",
@@ -591,50 +744,72 @@ dependencies = [
 
 [[package]]
 name = "bevy_gizmos_macros"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbe1708bb0e45a1d0fe0f32e998557689231dfe7bdae62083326e8008e97de23"
+checksum = "9454ac9f0a2141900ef9f3482af9333e490d5546bbea3cab63a777447d35beed"
 dependencies = [
  "bevy_macro_utils",
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
 name = "bevy_hierarchy"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb332d2789442ca1577c765977bafefea1dcd4db29479713ec8c6932dfb82cdb"
+checksum = "6fe0b538beea7edbf30a6062242b99e67ff3bfa716566aacf91d5b5e027f02a2"
 dependencies = [
  "bevy_app",
  "bevy_core",
  "bevy_ecs",
  "bevy_reflect",
  "bevy_utils",
+ "disqualified",
  "smallvec",
 ]
 
+[[package]]
+name = "bevy_image"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db46fa6a2f9e20435f3231710abbb136d2cc0a376f3f8e6ecfe071e286f5a246"
+dependencies = [
+ "bevy_asset",
+ "bevy_color",
+ "bevy_math",
+ "bevy_reflect",
+ "bevy_utils",
+ "bitflags 2.6.0",
+ "bytemuck",
+ "derive_more",
+ "futures-lite",
+ "image",
+ "serde",
+ "wgpu",
+]
+
 [[package]]
 name = "bevy_input"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9ce5f27a8729b473205b01927cd6a5c4898a004cb8fcffa7c896e19ba999d98"
+checksum = "46b4ea60095d1a1851e40cb12481ad3d5d234e14376d6b73142a85586c266b74"
 dependencies = [
  "bevy_app",
+ "bevy_core",
  "bevy_ecs",
  "bevy_math",
  "bevy_reflect",
  "bevy_utils",
+ "derive_more",
  "smol_str",
- "thiserror",
 ]
 
 [[package]]
 name = "bevy_internal"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2463102e46d7d67440dbfe3bc97d34bf529c93191c4f4bb41395f4982062ff3"
+checksum = "d4237e6e9b03902321032f00f931f18a4a211093bd9a7cf81276a0228a2a4417"
 dependencies = [
  "bevy_a11y",
  "bevy_app",
@@ -647,10 +822,12 @@ dependencies = [
  "bevy_ecs",
  "bevy_gizmos",
  "bevy_hierarchy",
+ "bevy_image",
  "bevy_input",
  "bevy_log",
  "bevy_math",
  "bevy_pbr",
+ "bevy_picking",
  "bevy_ptr",
  "bevy_reflect",
  "bevy_render",
@@ -668,59 +845,102 @@ dependencies = [
 
 [[package]]
 name = "bevy_log"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52f2c0c374af59007396793a51f747f6b10d74ca4acfb080ce0ade267118827b"
+checksum = "1a0bdb42b00ac3752f0d6f531fbda8abf313603157a7b3163da8529412119a0a"
 dependencies = [
  "android_log-sys",
  "bevy_app",
  "bevy_ecs",
  "bevy_utils",
  "tracing-log",
+ "tracing-oslog",
  "tracing-subscriber",
  "tracing-wasm",
 ]
 
 [[package]]
 name = "bevy_macro_utils"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ec4a585ec2a6dedd4f4143c07219d120ae142121929f0d83e68d82a452cdc9b"
+checksum = "3954dbb56a66a6c09c783e767f6ceca0dc0492c22e536e2aeaefb5545eac33c6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
  "toml_edit 0.22.20",
 ]
 
 [[package]]
 name = "bevy_math"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40253578fe83a5ffe5f4fcb4dfa196b7d9c50f36dc8efaa231a53344bf4b3e57"
+checksum = "9ae26f952598e293acac783d947b21af1809673cbeba25d76b969a56f287160b"
 dependencies = [
  "bevy_reflect",
+ "derive_more",
  "glam",
+ "itertools 0.13.0",
  "rand",
+ "rand_distr",
  "serde",
  "smallvec",
- "thiserror",
+]
+
+[[package]]
+name = "bevy_mesh"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c324d45ca0043a4696d7324b569de65be17066ed3a97dd42205bc28693d20b5"
+dependencies = [
+ "bevy_asset",
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_image",
+ "bevy_math",
+ "bevy_mikktspace",
+ "bevy_reflect",
+ "bevy_transform",
+ "bevy_utils",
+ "bitflags 2.6.0",
+ "bytemuck",
+ "derive_more",
+ "hexasphere",
+ "serde",
+ "wgpu",
 ]
 
 [[package]]
 name = "bevy_mikktspace"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24a1ad15685c6035e01bdc9d5ea082558ef1438e9d40d69fc552857dd7e83e71"
+checksum = "da5ea3ad25d74ea36ea45418ad799f135d046db35c322b9704c4a8934eb65ce9"
 dependencies = [
  "glam",
 ]
 
+[[package]]
+name = "bevy_mod_debugdump"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2855a39000ecb45c94f1b44ca14133398b8e6c6a0913d80d932037b7c1c016f5"
+dependencies = [
+ "bevy_app",
+ "bevy_color",
+ "bevy_ecs",
+ "bevy_render",
+ "bevy_utils",
+ "lexopt",
+ "once_cell",
+ "petgraph",
+ "pretty-type-name",
+]
+
 [[package]]
 name = "bevy_pbr"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "588998ba295db4a14dec54e571f272490f2885e5aaac59191fb4fa32a25835d0"
+checksum = "01b3bd8e646ddd3f27743b712957d2990d7361eb21044accc47c4f66711bf2cb"
 dependencies = [
  "bevy_app",
  "bevy_asset",
@@ -728,6 +948,7 @@ dependencies = [
  "bevy_core_pipeline",
  "bevy_derive",
  "bevy_ecs",
+ "bevy_image",
  "bevy_math",
  "bevy_reflect",
  "bevy_render",
@@ -736,6 +957,7 @@ dependencies = [
  "bevy_window",
  "bitflags 2.6.0",
  "bytemuck",
+ "derive_more",
  "fixedbitset 0.5.7",
  "nonmax",
  "radsort",
@@ -743,49 +965,73 @@ dependencies = [
  "static_assertions",
 ]
 
+[[package]]
+name = "bevy_picking"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97a137ed706574dc4a01cac527eb2c44a0b0e477d5bce3afc892a9ee95ee0078"
+dependencies = [
+ "bevy_app",
+ "bevy_asset",
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_hierarchy",
+ "bevy_input",
+ "bevy_math",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_time",
+ "bevy_transform",
+ "bevy_utils",
+ "bevy_window",
+ "uuid",
+]
+
 [[package]]
 name = "bevy_ptr"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ed72afbb6249a6803a3ed7bd2f68ff080d9392f550475e050b34c1e1c1e3e8f"
+checksum = "2af9e30b40fb3f0a80a658419f670f2de1e743efcaca1952c43cdcc923287944"
 
 [[package]]
 name = "bevy_reflect"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb37e8fc3c61d04da480c95cc8c303aa7781afed6be01dae333b336af493c38e"
+checksum = "52a37e2ae5ed62df4a0e3f958076effe280b39bc81fe878587350897a89332a2"
 dependencies = [
+ "assert_type_match",
  "bevy_ptr",
  "bevy_reflect_derive",
  "bevy_utils",
+ "derive_more",
+ "disqualified",
  "downcast-rs",
  "erased-serde",
  "glam",
  "serde",
  "smallvec",
  "smol_str",
- "thiserror",
  "uuid",
 ]
 
 [[package]]
 name = "bevy_reflect_derive"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fc00d5086f5bf534b4c2dbeba549a6b8d3223515f3cb5ba4fdaabe953ec6cea"
+checksum = "94c683fc68c75fc26f90bb1e529590095380d7cec66f6610dbe6b93d9fd26f94"
 dependencies = [
  "bevy_macro_utils",
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
  "uuid",
 ]
 
 [[package]]
 name = "bevy_render"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f570f36154296ae5377587d5ef19e1feb4c5734923785c571f55a9fff091701"
+checksum = "d188f392edf4edcae53dfda07f3ec618a7a704183ec3f2e8504657a9fb940c8a"
 dependencies = [
  "async-channel",
  "bevy_app",
@@ -797,8 +1043,9 @@ dependencies = [
  "bevy_ecs",
  "bevy_encase_derive",
  "bevy_hierarchy",
+ "bevy_image",
  "bevy_math",
- "bevy_mikktspace",
+ "bevy_mesh",
  "bevy_reflect",
  "bevy_render_macros",
  "bevy_tasks",
@@ -806,22 +1053,21 @@ dependencies = [
  "bevy_transform",
  "bevy_utils",
  "bevy_window",
- "bitflags 2.6.0",
  "bytemuck",
  "codespan-reporting",
+ "derive_more",
  "downcast-rs",
  "encase",
  "futures-lite",
- "hexasphere",
  "image",
  "js-sys",
  "naga",
  "naga_oil",
  "nonmax",
+ "offset-allocator",
  "send_wrapper",
  "serde",
  "smallvec",
- "thiserror",
  "wasm-bindgen",
  "web-sys",
  "wgpu",
@@ -829,21 +1075,21 @@ dependencies = [
 
 [[package]]
 name = "bevy_render_macros"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe2d5008c7b4a8a516ef7b58452b8e40e4c2317068fc7505398bedf34e8d45f7"
+checksum = "4ab37ee2945f93e9ba8daf91cd968b4cba9c677ac51d349dd8512a107a9a5d92"
 dependencies = [
  "bevy_macro_utils",
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
 name = "bevy_scene"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3910087f6fc45e7833fb62e4de282c36a7012ff381c0584eb2cc84dede02e72f"
+checksum = "0e883fd3c6d6e7761f1fe662e79bc7bdc7e917e73e7bfc434b1d16d2a5852119"
 dependencies = [
  "bevy_app",
  "bevy_asset",
@@ -854,16 +1100,16 @@ dependencies = [
  "bevy_render",
  "bevy_transform",
  "bevy_utils",
+ "derive_more",
  "serde",
- "thiserror",
  "uuid",
 ]
 
 [[package]]
 name = "bevy_sprite"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fffdfb9a18968c8606286b6be83c9323ff9008b5cc043a23a3ecc95ff72fb20c"
+checksum = "e975abc3f3f3432d6ad86ae32de804e96d7faf59d27f32b065b5ddc1e73ed7e1"
 dependencies = [
  "bevy_app",
  "bevy_asset",
@@ -871,42 +1117,76 @@ dependencies = [
  "bevy_core_pipeline",
  "bevy_derive",
  "bevy_ecs",
+ "bevy_image",
  "bevy_math",
+ "bevy_picking",
  "bevy_reflect",
  "bevy_render",
  "bevy_transform",
  "bevy_utils",
+ "bevy_window",
  "bitflags 2.6.0",
  "bytemuck",
+ "derive_more",
  "fixedbitset 0.5.7",
  "guillotiere",
+ "nonmax",
  "radsort",
  "rectangle-pack",
- "thiserror",
 ]
 
 [[package]]
-name = "bevy_tasks"
-version = "0.14.1"
+name = "bevy_state"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "036ec832197eae51b8a842220d2df03591dff75b4566dcf0f81153bbcb2b593b"
+dependencies = [
+ "bevy_app",
+ "bevy_ecs",
+ "bevy_hierarchy",
+ "bevy_reflect",
+ "bevy_state_macros",
+ "bevy_utils",
+]
+
+[[package]]
+name = "bevy_state_macros"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84f5414c3f49c96e02ceccf5fa12fb6cfbf8b271d2a820902d6f622e9c2fa681"
+checksum = "2828eb6762af9eccfebb5e4a0e56dbc4bd07bf3192083fa3e8525cfdb3e95add"
+dependencies = [
+ "bevy_macro_utils",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "bevy_tasks"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5171c605b462b4e3249e01986505e62e3933aa27642a9f793c841814fcbbfb4f"
 dependencies = [
  "async-executor",
+ "futures-channel",
  "futures-lite",
+ "pin-project",
  "wasm-bindgen-futures",
 ]
 
 [[package]]
 name = "bevy_text"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3e77dd86def15f5380e6e7c178ec54e5e356b3f464e2ad35794a08d5ecb82e4"
+checksum = "4fb000b2abad9f82f7a137fac7e0e3d2c6488cbf8dd9ddbb68f9a6b7e7af8d84"
 dependencies = [
- "ab_glyph",
  "bevy_app",
  "bevy_asset",
  "bevy_color",
+ "bevy_derive",
  "bevy_ecs",
+ "bevy_hierarchy",
+ "bevy_image",
  "bevy_math",
  "bevy_reflect",
  "bevy_render",
@@ -914,45 +1194,48 @@ dependencies = [
  "bevy_transform",
  "bevy_utils",
  "bevy_window",
- "glyph_brush_layout",
+ "cosmic-text",
+ "derive_more",
  "serde",
- "thiserror",
+ "smallvec",
+ "sys-locale",
+ "unicode-bidi",
 ]
 
 [[package]]
 name = "bevy_time"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3fb18cfac62098e07786e422e84b4f45f469f27ccb5b572b409500bef465f33"
+checksum = "291b6993b899c04554fc034ebb9e0d7fde9cb9b2fb58dcd912bfa6247abdedbb"
 dependencies = [
  "bevy_app",
  "bevy_ecs",
  "bevy_reflect",
  "bevy_utils",
  "crossbeam-channel",
- "thiserror",
 ]
 
 [[package]]
 name = "bevy_transform"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ff09cea0dd0d4e6a3ed5f7dcbd4fbbcec07e518ceb64a4c8a75dedbe294ab60"
+checksum = "dc35665624d0c728107ab0920d5ad2d352362b906a8c376eaf375ec9c751faf4"
 dependencies = [
  "bevy_app",
  "bevy_ecs",
  "bevy_hierarchy",
  "bevy_math",
  "bevy_reflect",
- "thiserror",
+ "derive_more",
 ]
 
 [[package]]
 name = "bevy_ui"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50684629a03b7d4891b5953e84dd971c7a8bbd290751bab5ce06e119b692220b"
+checksum = "43da3326aa592d6f6326e31893901bf17cd6957ded4e0ea02bc54652e5624b7f"
 dependencies = [
+ "accesskit",
  "bevy_a11y",
  "bevy_app",
  "bevy_asset",
@@ -961,8 +1244,10 @@ dependencies = [
  "bevy_derive",
  "bevy_ecs",
  "bevy_hierarchy",
+ "bevy_image",
  "bevy_input",
  "bevy_math",
+ "bevy_picking",
  "bevy_reflect",
  "bevy_render",
  "bevy_sprite",
@@ -971,22 +1256,22 @@ dependencies = [
  "bevy_utils",
  "bevy_window",
  "bytemuck",
+ "derive_more",
  "nonmax",
  "smallvec",
  "taffy",
- "thiserror",
 ]
 
 [[package]]
 name = "bevy_utils"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6efbe5a621b56cc4ffa41074929eca84107e242302496b9bb7550675e6bf2e7"
+checksum = "a0a48bad33c385a7818b7683a16c8b5c6930eded05cd3f176264fc1f5acea473"
 dependencies = [
  "ahash",
  "bevy_utils_proc_macros",
  "getrandom",
- "hashbrown",
+ "hashbrown 0.14.5",
  "thread_local",
  "tracing",
  "web-time",
@@ -994,24 +1279,26 @@ dependencies = [
 
 [[package]]
 name = "bevy_utils_proc_macros"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36a1e91b4294cad2d08620ac062509395d4f65247b636946d6497eaeccf4dbfd"
+checksum = "3dfd8d4a525b8f04f85863e45ccad3e922d4c11ed4a8d54f7f62a40bf83fb90f"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
 name = "bevy_window"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ba11880f05a3b267ecfa4149fe789b0c046c35fd8418dd8899fad3a4359c986"
+checksum = "05f3520279aae65935d6a84443202c154ead3abebf8dae906d095665162de358"
 dependencies = [
+ "android-activity",
  "bevy_a11y",
  "bevy_app",
  "bevy_ecs",
+ "bevy_input",
  "bevy_math",
  "bevy_reflect",
  "bevy_utils",
@@ -1021,17 +1308,20 @@ dependencies = [
 
 [[package]]
 name = "bevy_winit"
-version = "0.14.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5aeb4b2e3b1ece189fcf37ea2de625ceab93e6ac2a14d4b15b0393351e4c18b"
+checksum = "581bb2249a82285707e0977a9a1c79a2248ede587fcb289708faa03a82ebfa7f"
 dependencies = [
+ "accesskit",
  "accesskit_winit",
  "approx",
  "bevy_a11y",
  "bevy_app",
+ "bevy_asset",
  "bevy_derive",
  "bevy_ecs",
  "bevy_hierarchy",
+ "bevy_image",
  "bevy_input",
  "bevy_log",
  "bevy_math",
@@ -1039,21 +1329,52 @@ dependencies = [
  "bevy_tasks",
  "bevy_utils",
  "bevy_window",
+ "bytemuck",
  "cfg-if",
  "crossbeam-channel",
  "raw-window-handle",
  "wasm-bindgen",
  "web-sys",
+ "wgpu-types",
  "winit",
 ]
 
+[[package]]
+name = "bindgen"
+version = "0.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
+dependencies = [
+ "bitflags 2.6.0",
+ "cexpr",
+ "clang-sys",
+ "itertools 0.13.0",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn",
+]
+
 [[package]]
 name = "bit-set"
 version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
 dependencies = [
- "bit-vec",
+ "bit-vec 0.6.3",
+]
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec 0.8.0",
 ]
 
 [[package]]
@@ -1062,6 +1383,12 @@ version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
 
+[[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"
@@ -1130,6 +1457,17 @@ dependencies = [
  "piper",
 ]
 
+[[package]]
+name = "bstr"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22"
+dependencies = [
+ "memchr",
+ "regex-automata 0.4.7",
+ "serde",
+]
+
 [[package]]
 name = "built"
 version = "0.7.4"
@@ -1144,9 +1482,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
 
 [[package]]
 name = "bytemuck"
-version = "1.17.0"
+version = "1.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31"
+checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
 dependencies = [
  "bytemuck_derive",
 ]
@@ -1159,7 +1497,7 @@ checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -1194,6 +1532,18 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "calloop-wayland-source"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
+dependencies = [
+ "calloop",
+ "rustix",
+ "wayland-backend",
+ "wayland-client",
+]
+
 [[package]]
 name = "cc"
 version = "1.1.14"
@@ -1211,6 +1561,15 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
 
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
 [[package]]
 name = "cfg-expr"
 version = "0.15.8"
@@ -1239,6 +1598,17 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
 
+[[package]]
+name = "clang-sys"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
 [[package]]
 name = "clipboard-win"
 version = "5.4.0"
@@ -1264,37 +1634,6 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
 
-[[package]]
-name = "com"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6"
-dependencies = [
- "com_macros",
-]
-
-[[package]]
-name = "com_macros"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5"
-dependencies = [
- "com_macros_support",
- "proc-macro2",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "com_macros_support"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
 [[package]]
 name = "combine"
 version = "4.6.7"
@@ -1342,6 +1681,26 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca"
 
+[[package]]
+name = "const-random"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
+dependencies = [
+ "const-random-macro",
+]
+
+[[package]]
+name = "const-random-macro"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "tiny-keccak",
+]
+
 [[package]]
 name = "const_panic"
 version = "0.2.9"
@@ -1379,6 +1738,16 @@ dependencies = [
  "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"
@@ -1392,7 +1761,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
 dependencies = [
  "bitflags 1.3.2",
- "core-foundation",
+ "core-foundation 0.9.4",
  "core-graphics-types",
  "foreign-types",
  "libc",
@@ -1405,7 +1774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
 dependencies = [
  "bitflags 1.3.2",
- "core-foundation",
+ "core-foundation 0.9.4",
  "libc",
 ]
 
@@ -1481,6 +1850,16 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
 
+[[package]]
+name = "ctrlc"
+version = "3.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
+dependencies = [
+ "nix",
+ "windows-sys 0.59.0",
+]
+
 [[package]]
 name = "cursor-icon"
 version = "1.1.0"
@@ -1488,14 +1867,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
 
 [[package]]
-name = "d3d12"
-version = "0.20.0"
+name = "darling"
+version = "0.20.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b28bfe653d79bd16c77f659305b195b82bb5ce0c0eb2a4846b82ddbd77586813"
+checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
 dependencies = [
- "bitflags 2.6.0",
- "libloading 0.8.5",
- "winapi",
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -1504,19 +1906,57 @@ version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
 
+[[package]]
+name = "derive_more"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "unicode-xid",
+]
+
 [[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 = "disqualified"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd"
+
 [[package]]
 name = "dlib"
 version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
 dependencies = [
- "libloading 0.8.5",
+ "libloading",
 ]
 
 [[package]]
@@ -1540,17 +1980,70 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
 
+[[package]]
+name = "duplicate"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97af9b5f014e228b33e77d75ee0e6e87960124f0f4b16337b586a6bec91867b1"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "proc-macro2-diagnostics",
+]
+
+[[package]]
+name = "ecolor"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "775cfde491852059e386c4e1deb4aef381c617dc364184c6f6afee99b87c402b"
+dependencies = [
+ "bytemuck",
+ "emath",
+]
+
+[[package]]
+name = "egui"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53eafabcce0cb2325a59a98736efe0bf060585b437763f8c476957fb274bb974"
+dependencies = [
+ "ahash",
+ "emath",
+ "epaint",
+ "nohash-hasher",
+]
+
+[[package]]
+name = "egui_dock"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fe4e3414481dea5ed59fdfa88f2e00b6ea60fbe758ca7a8636d0dbed93ab9cf"
+dependencies = [
+ "duplicate",
+ "egui",
+ "paste",
+]
+
 [[package]]
 name = "either"
 version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
 
+[[package]]
+name = "emath"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1fe0049ce51d0fb414d029e668dd72eb30bc2b739bf34296ed97bd33df544f3"
+dependencies = [
+ "bytemuck",
+]
+
 [[package]]
 name = "encase"
-version = "0.8.0"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a9299a95fa5671ddf29ecc22b00e121843a65cb9ff24911e394b4ae556baf36"
+checksum = "b0a05902cf601ed11d564128448097b98ebe3c6574bd7b6a653a3d56d54aa020"
 dependencies = [
  "const_panic",
  "encase_derive",
@@ -1560,22 +2053,22 @@ dependencies = [
 
 [[package]]
 name = "encase_derive"
-version = "0.8.0"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07e09decb3beb1fe2db6940f598957b2e1f7df6206a804d438ff6cb2a9cddc10"
+checksum = "181d475b694e2dd56ae919ce7699d344d1fd259292d590c723a50d1189a2ea85"
 dependencies = [
  "encase_derive_impl",
 ]
 
 [[package]]
 name = "encase_derive_impl"
-version = "0.8.0"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd31dbbd9743684d339f907a87fe212cb7b51d75b9e8e74181fe363199ee9b47"
+checksum = "f97b51c5cc57ef7c5f7a0c57c250251c49ee4c28f819f87ac32f4aceabc36792"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -1584,6 +2077,61 @@ version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
 
+[[package]]
+name = "enum_dispatch"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
+dependencies = [
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "enumset"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a4b049558765cef5f0c1a273c3fc57084d768b44d2f98127aef4cceb17293"
+dependencies = [
+ "enumset_derive",
+]
+
+[[package]]
+name = "enumset_derive"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59c3b24c345d8c314966bdc1832f6c2635bfcce8e7cf363bd115987bba2ee242"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "epaint"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a32af8da821bd4f43f2c137e295459ee2e1661d87ca8779dfa0eaf45d870e20f"
+dependencies = [
+ "ab_glyph",
+ "ahash",
+ "bytemuck",
+ "ecolor",
+ "emath",
+ "epaint_default_fonts",
+ "nohash-hasher",
+ "parking_lot",
+]
+
+[[package]]
+name = "epaint_default_fonts"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "483440db0b7993cf77a20314f08311dbe95675092405518c0677aa08c151a3ea"
+
 [[package]]
 name = "equivalent"
 version = "1.0.1"
@@ -1714,6 +2262,18 @@ dependencies = [
  "spin",
 ]
 
+[[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.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
+
 [[package]]
 name = "font-types"
 version = "0.6.0"
@@ -1764,7 +2324,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -1773,6 +2333,24 @@ 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-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+]
+
 [[package]]
 name = "futures-core"
 version = "0.3.30"
@@ -1798,6 +2376,15 @@ dependencies = [
  "pin-project-lite",
 ]
 
+[[package]]
+name = "fuzzy-matcher"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
+dependencies = [
+ "thread_local",
+]
+
 [[package]]
 name = "gethostname"
 version = "0.4.3"
@@ -1844,20 +2431,27 @@ dependencies = [
 
 [[package]]
 name = "glam"
-version = "0.27.0"
+version = "0.29.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9"
+checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677"
 dependencies = [
  "bytemuck",
+ "mint",
  "rand",
  "serde",
 ]
 
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
 [[package]]
 name = "glow"
-version = "0.13.1"
+version = "0.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1"
+checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483"
 dependencies = [
  "js-sys",
  "slotmap",
@@ -1867,24 +2461,13 @@ dependencies = [
 
 [[package]]
 name = "glutin_wgl_sys"
-version = "0.5.0"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead"
+checksum = "0a4e1951bbd9434a81aa496fe59ccc2235af3820d27b85f9314e279609211e2c"
 dependencies = [
  "gl_generator",
 ]
 
-[[package]]
-name = "glyph_brush_layout"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b1e288bfd2f6c0313f78bf5aa538356ad481a3bb97e9b7f93220ab0066c5992"
-dependencies = [
- "ab_glyph",
- "approx",
- "xi-unicode",
-]
-
 [[package]]
 name = "gpu-alloc"
 version = "0.6.0"
@@ -1905,117 +2488,264 @@ dependencies = [
 ]
 
 [[package]]
-name = "gpu-allocator"
-version = "0.25.0"
+name = "gpu-allocator"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd"
+dependencies = [
+ "log",
+ "presser",
+ "thiserror",
+ "windows",
+]
+
+[[package]]
+name = "gpu-descriptor"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557"
+dependencies = [
+ "bitflags 2.6.0",
+ "gpu-descriptor-types",
+ "hashbrown 0.14.5",
+]
+
+[[package]]
+name = "gpu-descriptor-types"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
+dependencies = [
+ "bitflags 2.6.0",
+]
+
+[[package]]
+name = "grid"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be136d9dacc2a13cc70bb6c8f902b414fb2641f8db1314637c6b7933411a8f82"
+
+[[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 = "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.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+dependencies = [
+ "ahash",
+ "allocator-api2",
+ "serde",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
+dependencies = [
+ "foldhash",
+]
+
+[[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.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
+
+[[package]]
+name = "hexasphere"
+version = "15.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "741ab88b8cc670443da777c3daab02cebf5a3caccfc04e3c052f55c94d1643fe"
+dependencies = [
+ "constgebra",
+ "glam",
+]
+
+[[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.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "icu_collections"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884"
+checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
 dependencies = [
- "log",
- "presser",
- "thiserror",
- "winapi",
- "windows 0.52.0",
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+ "zerovec",
 ]
 
 [[package]]
-name = "gpu-descriptor"
-version = "0.3.0"
+name = "icu_locid"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557"
+checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
 dependencies = [
- "bitflags 2.6.0",
- "gpu-descriptor-types",
- "hashbrown",
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
 ]
 
 [[package]]
-name = "gpu-descriptor-types"
-version = "0.2.0"
+name = "icu_locid_transform"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
+checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
 dependencies = [
- "bitflags 2.6.0",
+ "displaydoc",
+ "icu_locid",
+ "icu_locid_transform_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
 ]
 
 [[package]]
-name = "grid"
-version = "0.14.0"
+name = "icu_locid_transform_data"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be136d9dacc2a13cc70bb6c8f902b414fb2641f8db1314637c6b7933411a8f82"
+checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
 
 [[package]]
-name = "guillotiere"
-version = "0.6.2"
+name = "icu_normalizer"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782"
+checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
 dependencies = [
- "euclid",
- "svg_fmt",
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "utf16_iter",
+ "utf8_iter",
+ "write16",
+ "zerovec",
 ]
 
 [[package]]
-name = "half"
-version = "2.4.1"
+name = "icu_normalizer_data"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
-dependencies = [
- "cfg-if",
- "crunchy",
-]
+checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
 
 [[package]]
-name = "hashbrown"
-version = "0.14.5"
+name = "icu_properties"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
 dependencies = [
- "ahash",
- "allocator-api2",
- "serde",
+ "displaydoc",
+ "icu_collections",
+ "icu_locid_transform",
+ "icu_properties_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
 ]
 
 [[package]]
-name = "hassle-rs"
-version = "0.11.0"
+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 = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890"
+checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
 dependencies = [
- "bitflags 2.6.0",
- "com",
- "libc",
- "libloading 0.8.5",
- "thiserror",
- "widestring",
- "winapi",
+ "displaydoc",
+ "icu_locid",
+ "icu_provider_macros",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerovec",
 ]
 
 [[package]]
-name = "heck"
-version = "0.5.0"
+name = "icu_provider_macros"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
 
 [[package]]
-name = "hermit-abi"
-version = "0.4.0"
+name = "ident_case"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
 
 [[package]]
-name = "hexasphere"
-version = "12.0.0"
+name = "idna"
+version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edd6b038160f086b0a7496edae34169ae22f328793cbe2b627a5a3d8373748ec"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
 dependencies = [
- "constgebra",
- "glam",
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
 ]
 
 [[package]]
-name = "hexf-parse"
-version = "0.2.1"
+name = "idna_adapter"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
+checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
 
 [[package]]
 name = "image"
@@ -2058,9 +2788,9 @@ checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
 
 [[package]]
 name = "immutable-chunkmap"
-version = "2.0.5"
+version = "2.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4419f022e55cc63d5bbd6b44b71e1d226b9c9480a47824c706e9d54e5c40c5eb"
+checksum = "12f97096f508d54f8f8ab8957862eee2ccd628847b6217af1a335e1c44dee578"
 dependencies = [
  "arrayvec",
 ]
@@ -2072,7 +2802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
 dependencies = [
  "equivalent",
- "hashbrown",
+ "hashbrown 0.14.5",
 ]
 
 [[package]]
@@ -2095,7 +2825,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -2107,6 +2837,15 @@ dependencies = [
  "either",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "jni"
 version = "0.21.1"
@@ -2146,10 +2885,11 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
 
 [[package]]
 name = "js-sys"
-version = "0.3.70"
+version = "0.3.74"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
+checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705"
 dependencies = [
+ "once_cell",
  "wasm-bindgen",
 ]
 
@@ -2160,7 +2900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
 dependencies = [
  "libc",
- "libloading 0.8.5",
+ "libloading",
  "pkg-config",
 ]
 
@@ -2182,6 +2922,12 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
 
+[[package]]
+name = "lexopt"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401"
+
 [[package]]
 name = "libc"
 version = "0.2.158"
@@ -2199,16 +2945,6 @@ dependencies = [
  "once_cell",
 ]
 
-[[package]]
-name = "libloading"
-version = "0.7.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
-dependencies = [
- "cfg-if",
- "winapi",
-]
-
 [[package]]
 name = "libloading"
 version = "0.8.5"
@@ -2248,6 +2984,12 @@ version = "0.4.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
 
+[[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"
@@ -2323,9 +3065,9 @@ dependencies = [
 
 [[package]]
 name = "metal"
-version = "0.28.0"
+version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb"
+checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21"
 dependencies = [
  "bitflags 2.6.0",
  "block",
@@ -2361,20 +3103,26 @@ dependencies = [
  "adler2",
 ]
 
+[[package]]
+name = "mint"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff"
+
 [[package]]
 name = "naga"
-version = "0.20.0"
+version = "23.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231"
+checksum = "3d5941e45a15b53aad4375eedf02033adb7a28931eedc31117faffa52e6a857e"
 dependencies = [
  "arrayvec",
- "bit-set",
+ "bit-set 0.8.0",
  "bitflags 2.6.0",
+ "cfg_aliases 0.1.1",
  "codespan-reporting",
  "hexf-parse",
  "indexmap",
  "log",
- "num-traits",
  "pp-rs",
  "rustc-hash",
  "spirv",
@@ -2385,11 +3133,11 @@ dependencies = [
 
 [[package]]
 name = "naga_oil"
-version = "0.14.0"
+version = "0.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "275d9720a7338eedac966141089232514c84d76a246a58ef501af88c5edf402f"
+checksum = "31ea1f080bb359927cd5404d0af1e5e6758f4f2d82ecfbebb0a0c434764e40f1"
 dependencies = [
- "bit-set",
+ "bit-set 0.5.3",
  "codespan-reporting",
  "data-encoding",
  "indexmap",
@@ -2448,6 +3196,24 @@ 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.6.0",
+ "cfg-if",
+ "cfg_aliases 0.2.1",
+ "libc",
+]
+
+[[package]]
+name = "nohash-hasher"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
+
 [[package]]
 name = "nom"
 version = "7.1.3"
@@ -2470,6 +3236,15 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
 
+[[package]]
+name = "normpath"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
 [[package]]
 name = "nu-ansi-term"
 version = "0.46.0"
@@ -2498,7 +3273,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -2528,6 +3303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
 dependencies = [
  "autocfg",
+ "libm",
 ]
 
 [[package]]
@@ -2548,7 +3324,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -2763,11 +3539,32 @@ dependencies = [
  "objc2-foundation",
 ]
 
+[[package]]
+name = "offset-allocator"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e234d535da3521eb95106f40f0b73483d80bfb3aacf27c40d7e2b72f1a3e00a2"
+dependencies = [
+ "log",
+ "nonmax",
+]
+
 [[package]]
 name = "once_cell"
-version = "1.19.0"
+version = "1.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
+
+[[package]]
+name = "opener"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+checksum = "6c62dcb6174f9cb326eac248f07e955d5d559c272730b6c03e396b443b562788"
+dependencies = [
+ "bstr",
+ "normpath",
+ "winapi",
+]
 
 [[package]]
 name = "orbclient"
@@ -2786,11 +3583,11 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
 
 [[package]]
 name = "owned_ttf_parser"
-version = "0.24.0"
+version = "0.25.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "490d3a563d3122bf7c911a59b0add9389e5ec0f5f0c3ac6b91ff235a0e6a7f90"
+checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4"
 dependencies = [
- "ttf-parser 0.24.1",
+ "ttf-parser 0.25.1",
 ]
 
 [[package]]
@@ -2861,7 +3658,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -2939,6 +3736,22 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
 
+[[package]]
+name = "pretty-type-name"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f73cdaf19b52e6143685c3606206e114a4dfa969d6b14ec3894c88eb38bd4b"
+
+[[package]]
+name = "prettyplease"
+version = "0.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
 [[package]]
 name = "proc-macro-crate"
 version = "3.1.0"
@@ -2950,13 +3763,26 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.86"
+version = "1.0.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
 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.15"
@@ -2973,7 +3799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
 dependencies = [
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -2991,6 +3817,15 @@ version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
 
+[[package]]
+name = "quick-xml"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "quote"
 version = "1.0.37"
@@ -3036,6 +3871,16 @@ dependencies = [
  "getrandom",
 ]
 
+[[package]]
+name = "rand_distr"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
+dependencies = [
+ "num-traits",
+ "rand",
+]
+
 [[package]]
 name = "range-alloc"
 version = "0.1.3"
@@ -3062,7 +3907,7 @@ dependencies = [
  "built",
  "cfg-if",
  "interpolate_name",
- "itertools",
+ "itertools 0.12.1",
  "libc",
  "libfuzzer-sys",
  "log",
@@ -3279,12 +4124,31 @@ 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 = "sctk-adwaita"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec"
+dependencies = [
+ "ab_glyph",
+ "log",
+ "memmap2",
+ "smithay-client-toolkit",
+ "tiny-skia",
+]
+
 [[package]]
 name = "self_cell"
 version = "1.0.4"
@@ -3314,7 +4178,7 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -3396,6 +4260,31 @@ version = "1.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
 
+[[package]]
+name = "smithay-client-toolkit"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
+dependencies = [
+ "bitflags 2.6.0",
+ "calloop",
+ "calloop-wayland-source",
+ "cursor-icon",
+ "libc",
+ "log",
+ "memmap2",
+ "rustix",
+ "thiserror",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-csd-frame",
+ "wayland-cursor",
+ "wayland-protocols",
+ "wayland-protocols-wlr",
+ "wayland-scanner",
+ "xkeysym",
+]
+
 [[package]]
 name = "smol_str"
 version = "0.2.2"
@@ -3423,12 +4312,30 @@ dependencies = [
  "bitflags 2.6.0",
 ]
 
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "stackfuture"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6eae92052b72ef70dafa16eddbabffc77e5ca3574be2f7bc1127b36f0a7ad7f2"
+
 [[package]]
 name = "static_assertions"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
+[[package]]
+name = "strict-num"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
+
 [[package]]
 name = "svg_fmt"
 version = "0.4.3"
@@ -3448,9 +4355,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.109"
+version = "2.0.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3458,14 +4365,14 @@ dependencies = [
 ]
 
 [[package]]
-name = "syn"
-version = "2.0.76"
+name = "synstructure"
+version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
 dependencies = [
  "proc-macro2",
  "quote",
- "unicode-ident",
+ "syn",
 ]
 
 [[package]]
@@ -3519,44 +4426,88 @@ dependencies = [
 ]
 
 [[package]]
-name = "thiserror"
-version = "1.0.63"
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[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 = "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 = "tiny-keccak"
+version = "2.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
 dependencies = [
- "thiserror-impl",
+ "crunchy",
 ]
 
 [[package]]
-name = "thiserror-impl"
-version = "1.0.63"
+name = "tiny-skia"
+version = "0.11.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
+checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
 dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.76",
+ "arrayref",
+ "arrayvec",
+ "bytemuck",
+ "cfg-if",
+ "log",
+ "tiny-skia-path",
 ]
 
 [[package]]
-name = "thread_local"
-version = "1.1.8"
+name = "tiny-skia-path"
+version = "0.11.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
 dependencies = [
- "cfg-if",
- "once_cell",
+ "arrayref",
+ "bytemuck",
+ "strict-num",
 ]
 
 [[package]]
-name = "tiff"
-version = "0.9.1"
+name = "tinystr"
+version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
+checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
 dependencies = [
- "flate2",
- "jpeg-decoder",
- "weezl",
+ "displaydoc",
+ "zerovec",
 ]
 
 [[package]]
@@ -3621,9 +4572,9 @@ dependencies = [
 
 [[package]]
 name = "tracing"
-version = "0.1.40"
+version = "0.1.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
 dependencies = [
  "pin-project-lite",
  "tracing-attributes",
@@ -3632,20 +4583,20 @@ dependencies = [
 
 [[package]]
 name = "tracing-attributes"
-version = "0.1.27"
+version = "0.1.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
 name = "tracing-core"
-version = "0.1.32"
+version = "0.1.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
 dependencies = [
  "once_cell",
  "valuable",
@@ -3662,6 +4613,21 @@ dependencies = [
  "tracing-core",
 ]
 
+[[package]]
+name = "tracing-oslog"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528bdd1f0e27b5dd9a4ededf154e824b0532731e4af73bb531de46276e0aab1e"
+dependencies = [
+ "bindgen",
+ "cc",
+ "cfg-if",
+ "once_cell",
+ "parking_lot",
+ "tracing-core",
+ "tracing-subscriber",
+]
+
 [[package]]
 name = "tracing-subscriber"
 version = "0.3.18"
@@ -3691,6 +4657,47 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "transform-gizmo"
+version = "0.5.0"
+source = "git+https://github.com/ActuallyHappening/transform-gizmo.git#0f6beb350e17a18e3ae008f0d17d665d612bff7e"
+dependencies = [
+ "ahash",
+ "ecolor",
+ "emath",
+ "enum_dispatch",
+ "enumset",
+ "epaint",
+ "glam",
+ "mint",
+]
+
+[[package]]
+name = "transform-gizmo-bevy"
+version = "0.5.0"
+source = "git+https://github.com/ActuallyHappening/transform-gizmo.git#0f6beb350e17a18e3ae008f0d17d665d612bff7e"
+dependencies = [
+ "bevy_app",
+ "bevy_asset",
+ "bevy_core",
+ "bevy_core_pipeline",
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_image",
+ "bevy_input",
+ "bevy_log",
+ "bevy_math",
+ "bevy_pbr",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_transform",
+ "bevy_utils",
+ "bevy_window",
+ "bytemuck",
+ "transform-gizmo",
+ "uuid",
+]
+
 [[package]]
 name = "ttf-parser"
 version = "0.20.0"
@@ -3705,9 +4712,9 @@ checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
 
 [[package]]
 name = "ttf-parser"
-version = "0.24.1"
+version = "0.25.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a"
+checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
 
 [[package]]
 name = "typeid"
@@ -3771,9 +4778,32 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
 
 [[package]]
 name = "unicode-xid"
-version = "0.2.5"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[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",
+]
+
+[[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 = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
 
 [[package]]
 name = "uuid"
@@ -3832,9 +4862,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.95"
+version = "0.2.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
+checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -3843,24 +4873,24 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.95"
+version = "0.2.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
+checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd"
 dependencies = [
  "bumpalo",
  "log",
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.43"
+version = "0.4.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
+checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -3870,9 +4900,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.95"
+version = "0.2.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
+checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -3880,28 +4910,137 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.95"
+version = "0.2.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
+checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.95"
+version = "0.2.97"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49"
+
+[[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",
+ "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.6.0",
+ "rustix",
+ "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.6.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",
+ "wayland-client",
+ "xcursor",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.32.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e"
+dependencies = [
+ "bitflags 2.6.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-plasma"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b31cab548ee68c7eb155517f2212049dc151f7cd7910c2b66abfd31c3ee12bd"
+dependencies = [
+ "bitflags 2.6.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "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.6.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
+checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3"
+dependencies = [
+ "proc-macro2",
+ "quick-xml",
+ "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.70"
+version = "0.3.74"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
+checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -3917,6 +5056,24 @@ dependencies = [
  "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 = "weezl"
 version = "0.1.8"
@@ -3925,12 +5082,11 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
 
 [[package]]
 name = "wgpu"
-version = "0.20.1"
+version = "23.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90e37c7b9921b75dfd26dd973fdcbce36f13dfa6e2dc82aece584e0ed48c355c"
+checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a"
 dependencies = [
  "arrayvec",
- "cfg-if",
  "cfg_aliases 0.1.1",
  "document-features",
  "js-sys",
@@ -3951,15 +5107,14 @@ dependencies = [
 
 [[package]]
 name = "wgpu-core"
-version = "0.21.1"
+version = "23.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d50819ab545b867d8a454d1d756b90cd5f15da1f2943334ca314af10583c9d39"
+checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a"
 dependencies = [
  "arrayvec",
- "bit-vec",
+ "bit-vec 0.8.0",
  "bitflags 2.6.0",
  "cfg_aliases 0.1.1",
- "codespan-reporting",
  "document-features",
  "indexmap",
  "log",
@@ -3971,36 +5126,34 @@ dependencies = [
  "rustc-hash",
  "smallvec",
  "thiserror",
- "web-sys",
  "wgpu-hal",
  "wgpu-types",
 ]
 
 [[package]]
 name = "wgpu-hal"
-version = "0.21.1"
+version = "23.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "172e490a87295564f3fcc0f165798d87386f6231b04d4548bca458cbbfd63222"
+checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821"
 dependencies = [
  "android_system_properties",
  "arrayvec",
  "ash",
- "bit-set",
+ "bit-set 0.8.0",
  "bitflags 2.6.0",
  "block",
+ "bytemuck",
  "cfg_aliases 0.1.1",
  "core-graphics-types",
- "d3d12",
  "glow",
  "glutin_wgl_sys",
  "gpu-alloc",
  "gpu-allocator",
  "gpu-descriptor",
- "hassle-rs",
  "js-sys",
  "khronos-egl",
  "libc",
- "libloading 0.8.5",
+ "libloading",
  "log",
  "metal",
  "naga",
@@ -4018,26 +5171,21 @@ dependencies = [
  "wasm-bindgen",
  "web-sys",
  "wgpu-types",
- "winapi",
+ "windows",
+ "windows-core",
 ]
 
 [[package]]
 name = "wgpu-types"
-version = "0.20.0"
+version = "23.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef"
+checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068"
 dependencies = [
  "bitflags 2.6.0",
  "js-sys",
  "web-sys",
 ]
 
-[[package]]
-name = "widestring"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
-
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -4071,73 +5219,65 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
 name = "windows"
-version = "0.52.0"
+version = "0.58.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
+checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
 dependencies = [
- "windows-core 0.52.0",
+ "windows-core",
  "windows-targets 0.52.6",
 ]
 
 [[package]]
-name = "windows"
-version = "0.54.0"
+name = "windows-core"
+version = "0.58.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
+checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
 dependencies = [
- "windows-core 0.54.0",
  "windows-implement",
  "windows-interface",
- "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.54.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
-dependencies = [
  "windows-result",
+ "windows-strings",
  "windows-targets 0.52.6",
 ]
 
 [[package]]
 name = "windows-implement"
-version = "0.53.0"
+version = "0.58.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd"
+checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
 name = "windows-interface"
-version = "0.53.0"
+version = "0.58.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60"
+checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
 name = "windows-result"
-version = "0.1.2"
+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 = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
+checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
 dependencies = [
+ "windows-result",
  "windows-targets 0.52.6",
 ]
 
@@ -4361,6 +5501,7 @@ version = "0.30.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67"
 dependencies = [
+ "ahash",
  "android-activity",
  "atomic-waker",
  "bitflags 2.6.0",
@@ -4369,12 +5510,13 @@ dependencies = [
  "calloop",
  "cfg_aliases 0.2.1",
  "concurrent-queue",
- "core-foundation",
+ "core-foundation 0.9.4",
  "core-graphics",
  "cursor-icon",
  "dpi",
  "js-sys",
  "libc",
+ "memmap2",
  "ndk",
  "objc2",
  "objc2-app-kit",
@@ -4386,11 +5528,17 @@ dependencies = [
  "raw-window-handle",
  "redox_syscall 0.4.1",
  "rustix",
+ "sctk-adwaita",
+ "smithay-client-toolkit",
  "smol_str",
  "tracing",
  "unicode-segmentation",
  "wasm-bindgen",
  "wasm-bindgen-futures",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-protocols-plasma",
  "web-sys",
  "web-time",
  "windows-sys 0.52.0",
@@ -4417,6 +5565,18 @@ dependencies = [
  "memchr",
 ]
 
+[[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"
@@ -4437,7 +5597,7 @@ dependencies = [
  "as-raw-xcb-connection",
  "gethostname",
  "libc",
- "libloading 0.8.5",
+ "libloading",
  "once_cell",
  "rustix",
  "x11rb-protocol",
@@ -4450,10 +5610,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
 
 [[package]]
-name = "xi-unicode"
-version = "0.3.0"
+name = "xcursor"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a"
+checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61"
 
 [[package]]
 name = "xkbcommon-dl"
@@ -4480,12 +5640,42 @@ version = "0.8.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "539a77ee7c0de333dcc6da69b177380a0b81e0dacfa4f7344c465a36871ee601"
 
+[[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 = "zeno"
 version = "0.2.3"
@@ -4510,7 +5700,50 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "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 = "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]]
diff --git a/Cargo.toml b/Cargo.toml
index e06542a..7c87816 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "bevy_cosmic_edit"
-version = "0.25.0"
+version = "0.26.0"
 edition = "2021"
 license = "MIT OR Apache-2.0"
 description = "Bevy cosmic-text multiline text input"
@@ -11,15 +11,14 @@ keywords = ["bevy"]
 exclude = ["assets/*"]
 
 [features]
-## Enable to avoid panicing when multiple cameras are used in the same world
+## Enable to avoid panicing when multiple cameras are used in the same world.
 ## Requires you to add `CosmicPrimaryCamera` marker component to the primary camera
 multicam = []
-webgpu = ["bevy/webgpu"]
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-bevy = { version = "0.14", default-features = false, features = [
+bevy = { version = "0.15", default-features = false, features = [
     "bevy_asset",
     "bevy_core_pipeline",
     "bevy_render",
@@ -28,14 +27,13 @@ bevy = { version = "0.14", default-features = false, features = [
     "bevy_text",
     "bevy_ui",
     "bevy_winit",
+    "bevy_window",
     "png",
     "x11",
     "webgl2",
 ] }
-cosmic-text = { version = "0.12.0" }
-# TODO: move crossbeam to wasm32, once input.rs has separate wasm copy/paste fn
 unicode-segmentation = { version = "1.11.0" }
-
+# TODO: move crossbeam to wasm32, once input.rs has separate wasm copy/paste fn
 crossbeam-channel = "0.5.8"
 image = "0.25.1"
 sys-locale = "0.3.0"
@@ -56,3 +54,10 @@ web-sys = { version = "0.3.70", features = [
 
 [dev-dependencies]
 insta = "1.29.0"
+bevy_editor_pls = "0.11.0"
+
+[patch.crates-io]
+# remove once https://github.com/jakobhellermann/bevy_editor_pls/pull/118 lands and is published
+bevy_editor_pls = { git = "https://github.com/ActuallyHappening/bevy_editor_pls.git" }
+# remove once https://github.com/urholaukkarinen/transform-gizmo/pull/85 lands and is published
+transform-gizmo-bevy = { git = "https://github.com/ActuallyHappening/transform-gizmo.git" }
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..8da1fe2
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,5 @@
+Migrate to using bevy builtin CosmicFontSystem resource and CosmicBuffer component
+Maintain invariant of FocusedWidget using observers, and add events for focus change
+
+Fix bug where clicking on a UiNode changes the editor from looking 'squished' to looking normal.
+Add an xcode subcommand to run a simulated CI run for cargo fmt and lint checking
diff --git a/examples/basic_sprite.rs b/examples/basic_sprite.rs
index 818f24d..99594ba 100644
--- a/examples/basic_sprite.rs
+++ b/examples/basic_sprite.rs
@@ -1,7 +1,7 @@
 use bevy::{prelude::*, window::PrimaryWindow};
 use bevy_cosmic_edit::{
     cosmic_text::{Attrs, Family, Metrics},
-    *,
+    prelude::*,
 };
 
 fn setup(
@@ -10,34 +10,34 @@ fn setup(
     mut font_system: ResMut<CosmicFontSystem>,
 ) {
     let primary_window = windows.single();
-    let camera_bundle = Camera2dBundle {
-        camera: Camera {
+    let camera_bundle = (
+        Camera2d,
+        CosmicPrimaryCamera,
+        Camera {
             clear_color: ClearColorConfig::Custom(Color::WHITE),
             ..default()
         },
-        ..default()
-    };
+    );
     commands.spawn(camera_bundle);
 
     let mut attrs = Attrs::new();
     attrs = attrs.family(Family::Name("Victor Mono"));
     attrs = attrs.color(CosmicColor::rgb(0x94, 0x00, 0xD3));
 
-    let cosmic_edit = (CosmicEditBundle {
-        buffer: CosmicBuffer::new(&mut font_system, Metrics::new(14., 18.)).with_text(
+    let cosmic_edit = (
+        CosmicEditBuffer::new(&mut font_system, Metrics::new(14., 18.)).with_text(
             &mut font_system,
             "😀😀😀 x => y",
             attrs,
         ),
-        sprite_bundle: SpriteBundle {
-            sprite: Sprite {
-                custom_size: Some(Vec2::new(primary_window.width(), primary_window.height())),
-                ..default()
-            },
+        TextEdit2d,
+        Sprite {
+            // You must specify custom size
+            // so the editor knows what size images to render to the sprite
+            custom_size: Some(Vec2::new(primary_window.width(), primary_window.height())),
             ..default()
         },
-        ..default()
-    },);
+    );
 
     let cosmic_edit = commands.spawn(cosmic_edit).id();
 
@@ -54,10 +54,8 @@ fn main() {
 
     App::new()
         .add_plugins(DefaultPlugins)
-        .add_plugins(CosmicEditPlugin {
-            font_config,
-            ..default()
-        })
+        .add_plugins(bevy_editor_pls::EditorPlugin::default())
+        .add_plugins(CosmicEditPlugin { font_config })
         .add_systems(Startup, setup)
         .add_systems(
             Update,
diff --git a/examples/basic_ui.rs b/examples/basic_ui.rs
index 7a6dffa..ccb4625 100644
--- a/examples/basic_ui.rs
+++ b/examples/basic_ui.rs
@@ -1,17 +1,17 @@
 use bevy::prelude::*;
 use bevy_cosmic_edit::{
     cosmic_text::{Attrs, Family, Metrics},
-    *,
+    prelude::*,
 };
 
 fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
-    let camera_bundle = Camera2dBundle {
-        camera: Camera {
+    let camera_bundle = (
+        Camera2d,
+        Camera {
             clear_color: ClearColorConfig::Custom(bevy::color::palettes::css::PINK.into()),
             ..default()
         },
-        ..default()
-    };
+    );
     commands.spawn(camera_bundle);
 
     let mut attrs = Attrs::new();
@@ -19,33 +19,20 @@ fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
     attrs = attrs.color(CosmicColor::rgb(0x94, 0x00, 0xD3));
 
     let cosmic_edit = commands
-        .spawn((CosmicEditBundle {
-            buffer: CosmicBuffer::new(&mut font_system, Metrics::new(20., 20.)).with_rich_text(
+        .spawn((
+            TextEdit,
+            CosmicEditBuffer::new(&mut font_system, Metrics::new(20., 20.)).with_rich_text(
                 &mut font_system,
                 vec![("Banana", attrs)],
                 attrs,
             ),
-            ..default()
-        },))
-        .id();
-
-    commands
-        .spawn(
-            // Use buttonbundle for layout
-            // Includes Interaction and UiImage which are used by the plugin.
-            ButtonBundle {
-                style: Style {
-                    width: Val::Percent(100.),
-                    height: Val::Percent(100.),
-                    ..default()
-                },
+            Node {
+                width: Val::Percent(100.),
+                height: Val::Percent(100.),
                 ..default()
             },
-        )
-        // point editor at this entity.
-        // Plugin looks for UiImage and sets it's
-        // texture to the editor's rendered image
-        .insert(CosmicSource(cosmic_edit));
+        ))
+        .id();
 
     commands.insert_resource(FocusedWidget(Some(cosmic_edit)));
 }
@@ -60,10 +47,7 @@ fn main() {
 
     App::new()
         .add_plugins(DefaultPlugins)
-        .add_plugins(CosmicEditPlugin {
-            font_config,
-            ..default()
-        })
+        .add_plugins(CosmicEditPlugin { font_config })
         .add_systems(Startup, setup)
         .add_systems(
             Update,
diff --git a/examples/bevy_editor_pls.rs b/examples/bevy_editor_pls.rs
new file mode 100644
index 0000000..5f8f573
--- /dev/null
+++ b/examples/bevy_editor_pls.rs
@@ -0,0 +1,73 @@
+//! With [bevy_editor_pls] integration
+//! Requires `multicam` features enabled
+
+use bevy::prelude::*;
+use bevy_cosmic_edit::{
+    cosmic_text::{Attrs, Family, Metrics},
+    prelude::*,
+};
+
+fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
+    let camera_bundle = (
+        Camera2d,
+        // marker from bevy_cosmic_edit
+        bevy_cosmic_edit::CosmicPrimaryCamera,
+        // marker from bevy
+        // required else for some reason no UI renders to the screen
+        bevy::prelude::IsDefaultUiCamera,
+        Camera {
+            clear_color: ClearColorConfig::Custom(bevy::color::palettes::css::PINK.into()),
+            ..default()
+        },
+    );
+    commands.spawn(camera_bundle);
+
+    let mut attrs = Attrs::new();
+    attrs = attrs.family(Family::Name("Victor Mono"));
+    attrs = attrs.color(CosmicColor::rgb(0x94, 0x00, 0xD3));
+
+    let cosmic_edit = commands
+        .spawn((
+            TextEdit,
+            CosmicEditBuffer::new(&mut font_system, Metrics::new(40., 40.)).with_rich_text(
+                &mut font_system,
+                vec![("Banana", attrs)],
+                attrs,
+            ),
+            Node {
+                width: Val::Percent(60.),
+                height: Val::Percent(60.),
+                top: Val::Percent(10.),
+                left: Val::Percent(30.),
+                ..default()
+            },
+        ))
+        .id();
+
+    commands.insert_resource(FocusedWidget(Some(cosmic_edit)));
+}
+
+fn main() {
+    let font_bytes: &[u8] = include_bytes!("../assets/fonts/VictorMono-Regular.ttf");
+    let font_config = CosmicFontConfig {
+        fonts_dir_path: None,
+        font_bytes: Some(vec![font_bytes]),
+        load_system_fonts: true,
+    };
+
+    App::new()
+        .add_plugins(DefaultPlugins)
+        .add_plugins(CosmicEditPlugin { font_config })
+        // add editor plugin
+        .add_plugins(bevy_editor_pls::EditorPlugin::default())
+        .add_systems(Startup, setup)
+        .add_systems(
+            Update,
+            (
+                print_editor_text,
+                change_active_editor_ui,
+                deselect_editor_on_esc,
+            ),
+        )
+        .run();
+}
diff --git a/examples/every_option.rs b/examples/every_option.rs
index 1104db3..8f88213 100644
--- a/examples/every_option.rs
+++ b/examples/every_option.rs
@@ -1,59 +1,46 @@
-use bevy::prelude::*;
+use bevy::{prelude::*, window::SystemCursorIcon, winit::cursor::CursorIcon};
 use bevy_cosmic_edit::{
     cosmic_text::{Attrs, AttrsOwned, Metrics},
-    *,
+    prelude::*,
+    CosmicBackgroundColor, CosmicBackgroundImage, CosmicTextAlign, CosmicWrap, CursorColor,
+    DefaultAttrs, HoverCursor, MaxChars, MaxLines, SelectedTextColor, SelectionColor,
 };
 
 #[derive(Resource)]
 struct TextChangeTimer(pub Timer);
 
 fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
-    commands.spawn(Camera2dBundle::default());
+    commands.spawn(Camera2d);
 
     let attrs = Attrs::new().color(Color::srgb(0.27, 0.27, 0.27).to_cosmic());
 
-    let editor = commands
-        .spawn(CosmicEditBundle {
-            buffer: CosmicBuffer::new(&mut font_system, Metrics::new(16., 16.)).with_text(
+    commands.spawn((
+        (
+            // cosmic edit components
+            CosmicEditBuffer::new(&mut font_system, Metrics::new(16., 16.)).with_text(
                 &mut font_system,
                 "Begin counting.",
                 attrs,
             ),
-            cursor_color: CursorColor(bevy::color::palettes::css::LIME.into()),
-            selection_color: SelectionColor(bevy::color::palettes::css::DEEP_PINK.into()),
-            fill_color: CosmicBackgroundColor(bevy::color::palettes::css::YELLOW_GREEN.into()),
-            x_offset: XOffset::default(),
-            text_position: CosmicTextAlign::default(),
-            background_image: CosmicBackgroundImage::default(),
-            default_attrs: DefaultAttrs(AttrsOwned::new(attrs)),
-            max_chars: MaxChars(15),
-            max_lines: MaxLines(1),
-            mode: CosmicWrap::Wrap,
-            hover_cursor: HoverCursor(CursorIcon::Pointer),
-            // CosmicEdit draws to this spritebundle
-            sprite_bundle: SpriteBundle {
-                sprite: Sprite {
-                    // when using another target like a UI element, this is overridden
-                    custom_size: Some(Vec2::ONE * 128.0),
-                    ..default()
-                },
-                // this is the default behaviour for targeting UI elements.
-                // If wanting a sprite, define your own SpriteBundle and
-                // leave the visibility on. See examples/basic_sprite.rs
-                visibility: Visibility::Hidden,
-                ..default()
-            },
-            // Computed fields
-            padding: Default::default(),
-            widget_size: Default::default(),
-        })
-        .insert(SelectedTextColor(Color::WHITE))
-        .id();
-
-    commands
-        .spawn(ButtonBundle {
-            border_color: bevy::color::palettes::css::LIMEGREEN.into(),
-            style: Style {
+            CursorColor(bevy::color::palettes::css::LIME.into()),
+            SelectionColor(bevy::color::palettes::css::DEEP_PINK.into()),
+            CosmicBackgroundColor(bevy::color::palettes::css::YELLOW_GREEN.into()),
+            CosmicTextAlign::Center { padding: 0 },
+            CosmicBackgroundImage(None),
+            DefaultAttrs(AttrsOwned::new(attrs)),
+            MaxChars(15),
+            MaxLines(1),
+            CosmicWrap::Wrap,
+            HoverCursor(CursorIcon::System(SystemCursorIcon::Pointer)),
+            SelectedTextColor(Color::WHITE),
+        ),
+        (
+            TextEdit,
+            // the image mode is optional, but due to bevy 0.15 mechanics is required to
+            // render the border within the `ImageNode`
+            // See bevy issue https://github.com/bevyengine/bevy/issues/16643#issuecomment-2518163688
+            ImageNode::default().with_mode(bevy::ui::widget::NodeImageMode::Stretch),
+            Node {
                 // Size and position of text box
                 border: UiRect::all(Val::Px(4.)),
                 width: Val::Percent(20.),
@@ -62,10 +49,12 @@ fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
                 top: Val::Px(100.),
                 ..default()
             },
-            background_color: Color::WHITE.into(),
-            ..default()
-        })
-        .insert(CosmicSource(editor));
+            BorderColor(bevy::color::palettes::css::LIMEGREEN.into()),
+            BorderRadius::all(Val::Px(10.)),
+            // This is overriden by setting `CosmicBackgroundColor` so you don't see any white
+            BackgroundColor(Color::WHITE),
+        ),
+    ));
 
     commands.insert_resource(TextChangeTimer(Timer::from_seconds(
         1.,
@@ -77,7 +66,7 @@ fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
 fn text_swapper(
     mut timer: ResMut<TextChangeTimer>,
     time: Res<Time>,
-    mut cosmic_q: Query<(&mut CosmicBuffer, &DefaultAttrs)>,
+    mut cosmic_q: Query<(&mut CosmicEditBuffer, &DefaultAttrs)>,
     mut count: Local<usize>,
     mut font_system: ResMut<CosmicFontSystem>,
 ) {
diff --git a/examples/font_per_widget.rs b/examples/font_per_widget.rs
index 2285602..caf65fd 100644
--- a/examples/font_per_widget.rs
+++ b/examples/font_per_widget.rs
@@ -3,19 +3,16 @@
 use bevy::prelude::*;
 use bevy_cosmic_edit::{
     cosmic_text::{Attrs, Family, Metrics},
-    *,
+    prelude::*,
 };
 
 fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
-    commands.spawn(Camera2dBundle::default());
+    commands.spawn(Camera2d);
     let root = commands
-        .spawn(NodeBundle {
-            style: bevy::prelude::Style {
-                position_type: PositionType::Absolute,
-                width: Val::Percent(100.),
-                height: Val::Percent(100.),
-                ..default()
-            },
+        .spawn(Node {
+            position_type: PositionType::Absolute,
+            width: Val::Percent(100.),
+            height: Val::Percent(100.),
             ..default()
         })
         .id();
@@ -159,57 +156,42 @@ fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
         ),
     ];
 
-    let cosmic_edit_1 = commands
-        .spawn(CosmicEditBundle {
-            buffer: CosmicBuffer::new(&mut font_system, Metrics::new(18., 22.)).with_rich_text(
+    commands.entity(root).with_children(|parent| {
+        parent.spawn((
+            CosmicEditBuffer::new(&mut font_system, Metrics::new(18., 22.)).with_rich_text(
                 &mut font_system,
                 lines,
                 attrs,
             ),
-            ..default()
-        })
-        .id();
+            TextEdit,
+            Node {
+                width: Val::Percent(50.),
+                height: Val::Percent(100.),
+                ..default()
+            },
+            BackgroundColor(Color::WHITE),
+        ));
+    });
 
     let mut attrs_2 = Attrs::new();
     attrs_2 = attrs_2.family(Family::Name("Times New Roman"));
     attrs_2.color_opt = Some(bevy::color::palettes::css::PURPLE.to_cosmic());
-
-    let cosmic_edit_2 = commands
-        .spawn(CosmicEditBundle {
-            buffer: CosmicBuffer::new(&mut font_system, Metrics::new(28., 36.)).with_text(
+    commands.entity(root).with_children(|parent| {
+        parent.spawn((
+            CosmicEditBuffer::new(&mut font_system, Metrics::new(28., 36.)).with_text(
                 &mut font_system,
                 "Widget 2.\nClick on me =>",
                 attrs_2,
             ),
-            ..default()
-        })
-        .id();
-
-    // Spawn the CosmicEditUiBundles as children of root
-    commands.entity(root).with_children(|parent| {
-        parent
-            .spawn(ButtonBundle {
-                style: Style {
-                    width: Val::Percent(50.),
-                    height: Val::Percent(100.),
-                    ..default()
-                },
-                background_color: BackgroundColor(Color::WHITE),
-                ..default()
-            })
-            .insert(CosmicSource(cosmic_edit_1));
-
-        parent
-            .spawn(ButtonBundle {
-                background_color: BackgroundColor(bevy::prelude::Color::WHITE.with_alpha(0.8)),
-                style: Style {
-                    width: Val::Percent(50.),
-                    height: Val::Percent(100.),
-                    ..default()
-                },
+            ImageNode::default(),
+            Button,
+            Node {
+                width: Val::Percent(50.),
+                height: Val::Percent(100.),
                 ..default()
-            })
-            .insert(CosmicSource(cosmic_edit_2));
+            },
+            BackgroundColor(Color::WHITE.with_alpha(0.8)),
+        ));
     });
 }
 
diff --git a/examples/image_background.rs b/examples/image_background.rs
index 7df8a51..19ab10b 100644
--- a/examples/image_background.rs
+++ b/examples/image_background.rs
@@ -1,37 +1,31 @@
 use bevy::prelude::*;
 use bevy_cosmic_edit::{
     cosmic_text::{Attrs, AttrsOwned},
-    *,
+    prelude::*,
+    CosmicBackgroundImage,
 };
 
 fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
-    commands.spawn(Camera2dBundle::default());
+    commands.spawn(Camera2d);
 
     let bg_image_handle = asset_server.load("img/bevy_logo_light.png");
 
-    let editor = commands
-        .spawn(CosmicEditBundle {
-            default_attrs: DefaultAttrs(AttrsOwned::new(
-                Attrs::new().color(bevy::color::palettes::basic::LIME.to_cosmic()),
-            )),
-            background_image: CosmicBackgroundImage(Some(bg_image_handle)),
+    commands.spawn((
+        TextEdit,
+        CosmicEditBuffer::default(),
+        DefaultAttrs(AttrsOwned::new(
+            Attrs::new().color(bevy::color::palettes::basic::LIME.to_cosmic()),
+        )),
+        CosmicBackgroundImage(Some(bg_image_handle)),
+        Node {
+            // Size and position of text box
+            width: Val::Px(300.),
+            height: Val::Px(50.),
+            left: Val::Px(100.),
+            top: Val::Px(100.),
             ..default()
-        })
-        .id();
-
-    commands
-        .spawn(ButtonBundle {
-            style: Style {
-                // Size and position of text box
-                width: Val::Px(300.),
-                height: Val::Px(50.),
-                left: Val::Px(100.),
-                top: Val::Px(100.),
-                ..default()
-            },
-            ..default()
-        })
-        .insert(CosmicSource(editor));
+        },
+    ));
 }
 
 fn main() {
diff --git a/examples/multiple_sprites.rs b/examples/multiple_sprites.rs
index 714b943..e787e3c 100644
--- a/examples/multiple_sprites.rs
+++ b/examples/multiple_sprites.rs
@@ -1,7 +1,8 @@
 use bevy::{prelude::*, window::PrimaryWindow};
 use bevy_cosmic_edit::{
     cosmic_text::{Attrs, Family, Metrics},
-    *,
+    prelude::*,
+    CosmicBackgroundColor,
 };
 
 fn setup(
@@ -10,66 +11,57 @@ fn setup(
     mut font_system: ResMut<CosmicFontSystem>,
 ) {
     let primary_window = windows.single();
-    let camera_bundle = Camera2dBundle {
-        camera: Camera {
+    let camera_bundle = (
+        Camera2d,
+        Camera {
             clear_color: ClearColorConfig::Custom(Color::WHITE),
             ..default()
         },
-        ..default()
-    };
+    );
     commands.spawn(camera_bundle);
 
     let mut attrs = Attrs::new();
     attrs = attrs.family(Family::Name("Victor Mono"));
     attrs = attrs.color(bevy::color::palettes::basic::PURPLE.to_cosmic());
 
-    commands.spawn(CosmicEditBundle {
-        fill_color: CosmicBackgroundColor(bevy::color::palettes::css::ALICE_BLUE.into()),
-        buffer: CosmicBuffer::new(&mut font_system, Metrics::new(14., 18.)).with_text(
+    commands.spawn((
+        TextEdit2d,
+        CosmicEditBuffer::new(&mut font_system, Metrics::new(14., 18.)).with_text(
             &mut font_system,
             "😀😀😀 x => y",
             attrs,
         ),
-        sprite_bundle: SpriteBundle {
-            sprite: Sprite {
-                custom_size: Some(Vec2 {
-                    x: primary_window.width() / 2.,
-                    y: primary_window.height(),
-                }),
-                ..default()
-            },
-            transform: Transform::from_translation(Vec3::new(-primary_window.width() / 4., 0., 1.)),
+        CosmicBackgroundColor(bevy::color::palettes::css::ALICE_BLUE.into()),
+        Sprite {
+            custom_size: Some(Vec2 {
+                x: primary_window.width() / 2.,
+                y: primary_window.height(),
+            }),
             ..default()
         },
-        ..default()
-    });
+        Transform::from_translation(Vec3::new(-primary_window.width() / 4., 0., 1.)),
+    ));
 
-    commands.spawn(CosmicEditBundle {
-        fill_color: CosmicBackgroundColor(
-            bevy::color::palettes::basic::GRAY.with_alpha(0.5).into(),
-        ),
-        buffer: CosmicBuffer::new(&mut font_system, Metrics::new(14., 18.)).with_text(
+    commands.spawn((
+        CosmicEditBuffer::new(&mut font_system, Metrics::new(14., 18.)).with_text(
             &mut font_system,
             "Widget_2. Click on me",
             attrs,
         ),
-        sprite_bundle: SpriteBundle {
-            sprite: Sprite {
-                custom_size: Some(Vec2 {
-                    x: primary_window.width() / 2.,
-                    y: primary_window.height() / 2.,
-                }),
-                ..default()
-            },
-            transform: Transform::from_translation(Vec3::new(
-                primary_window.width() / 4.,
-                -primary_window.height() / 4.,
-                1.,
-            )),
+        CosmicBackgroundColor(bevy::color::palettes::basic::GRAY.with_alpha(0.5).into()),
+        Sprite {
+            custom_size: Some(Vec2 {
+                x: primary_window.width() / 2.,
+                y: primary_window.height() / 2.,
+            }),
             ..default()
         },
-        ..default()
-    });
+        Transform::from_translation(Vec3::new(
+            primary_window.width() / 4.,
+            -primary_window.height() / 4.,
+            1.,
+        )),
+    ));
 }
 
 fn main() {
@@ -82,10 +74,7 @@ fn main() {
 
     App::new()
         .add_plugins(DefaultPlugins)
-        .add_plugins(CosmicEditPlugin {
-            font_config,
-            ..default()
-        })
+        .add_plugins(CosmicEditPlugin { font_config })
         .add_systems(Startup, setup)
         .add_systems(Update, change_active_editor_sprite)
         .run();
diff --git a/examples/password.rs b/examples/password.rs
index d9655cc..e13af7e 100644
--- a/examples/password.rs
+++ b/examples/password.rs
@@ -1,26 +1,24 @@
 use bevy::prelude::*;
-use bevy_cosmic_edit::{cosmic_text::Attrs, *};
+use bevy_cosmic_edit::{
+    cosmic_text::Attrs, prelude::*, CosmicWrap, InputSet, MaxLines, Password, Placeholder,
+};
 
 fn setup(mut commands: Commands) {
-    commands.spawn(Camera2dBundle::default());
+    commands.spawn(Camera2d);
 
     // Sprite editor
     commands.spawn((
-        CosmicEditBundle {
-            max_lines: MaxLines(1),
-            mode: CosmicWrap::InfiniteLine,
-            sprite_bundle: SpriteBundle {
-                // Sets size of text box
-                sprite: Sprite {
-                    custom_size: Some(Vec2::new(300., 100.)),
-                    ..default()
-                },
-                // Position of text box
-                transform: Transform::from_xyz(0., 100., 0.),
-                ..default()
-            },
+        TextEdit2d,
+        CosmicEditBuffer::default(),
+        MaxLines(1),
+        CosmicWrap::InfiniteLine,
+        // Sets size of text box
+        Sprite {
+            custom_size: Some(Vec2::new(300., 100.)),
             ..default()
         },
+        // Position of text box
+        Transform::from_xyz(0., 100., 0.),
         Password::default(),
         Placeholder::new("Password", Attrs::new()),
     ));
@@ -36,6 +34,7 @@ fn main() {
             (
                 change_active_editor_sprite,
                 deselect_editor_on_esc,
+                // If you don't .after(InputSet) you'll just see the hashed-out safe text
                 print_editor_text.after(InputSet),
             ),
         )
diff --git a/examples/placeholder.rs b/examples/placeholder.rs
index bf09920..1a09fbf 100644
--- a/examples/placeholder.rs
+++ b/examples/placeholder.rs
@@ -1,55 +1,41 @@
 use bevy::prelude::*;
 use bevy_cosmic_edit::{
     cosmic_text::{Attrs, Family, Metrics},
-    *,
+    prelude::*,
+    Placeholder,
 };
 
 fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
-    let camera_bundle = Camera2dBundle {
-        camera: Camera {
+    let camera_bundle = (
+        Camera2d,
+        Camera {
             clear_color: ClearColorConfig::Custom(bevy::color::palettes::css::PINK.into()),
             ..default()
         },
-        ..default()
-    };
+    );
     commands.spawn(camera_bundle);
 
     let mut attrs = Attrs::new();
     attrs = attrs.family(Family::Name("Victor Mono"));
     attrs = attrs.color(CosmicColor::rgb(0x94, 0x00, 0xD3));
 
-    let cosmic_edit =
-        commands
-            .spawn((
-                CosmicEditBundle {
-                    buffer: CosmicBuffer::new(&mut font_system, Metrics::new(20., 20.))
-                        .with_rich_text(&mut font_system, vec![("", attrs)], attrs),
-                    ..default()
-                },
-                Placeholder::new(
-                    "Placeholder",
-                    attrs.color(bevy::color::palettes::basic::GRAY.to_cosmic()),
-                ),
-            ))
-            .id();
-
-    commands
-        .spawn(
-            // Use buttonbundle for layout
-            // Includes Interaction and UiImage which are used by the plugin.
-            ButtonBundle {
-                style: Style {
-                    width: Val::Percent(100.),
-                    height: Val::Percent(100.),
-                    ..default()
-                },
-                ..default()
-            },
-        )
-        // point editor at this entity.
-        // Plugin looks for UiImage and sets it's
-        // texture to the editor's rendered image
-        .insert(CosmicSource(cosmic_edit));
+    commands.spawn((
+        TextEdit,
+        CosmicEditBuffer::new(&mut font_system, Metrics::new(20., 20.)).with_rich_text(
+            &mut font_system,
+            vec![("", attrs)],
+            attrs,
+        ),
+        Placeholder::new(
+            "Placeholder",
+            attrs.color(bevy::color::palettes::basic::GRAY.to_cosmic()),
+        ),
+        Node {
+            width: Val::Percent(100.),
+            height: Val::Percent(100.),
+            ..default()
+        },
+    ));
 }
 
 fn main() {
@@ -62,10 +48,7 @@ fn main() {
 
     App::new()
         .add_plugins(DefaultPlugins)
-        .add_plugins(CosmicEditPlugin {
-            font_config,
-            ..default()
-        })
+        .add_plugins(CosmicEditPlugin { font_config })
         .add_systems(Startup, setup)
         .add_systems(
             Update,
diff --git a/examples/readonly.rs b/examples/readonly.rs
index 0e42c72..dd04535 100644
--- a/examples/readonly.rs
+++ b/examples/readonly.rs
@@ -1,55 +1,32 @@
 use bevy::prelude::*;
 use bevy_cosmic_edit::{
     cosmic_text::{Attrs, Family, Metrics},
-    *,
+    prelude::*,
 };
 
 fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
-    commands.spawn(Camera2dBundle::default());
-    let root = commands
-        .spawn(NodeBundle {
-            style: Style {
-                display: Display::Flex,
-                width: Val::Percent(100.),
-                height: Val::Percent(100.),
-                ..default()
-            },
-            ..default()
-        })
-        .id();
+    commands.spawn(Camera2d);
 
     let mut attrs = Attrs::new();
     attrs = attrs.family(Family::Name("Victor Mono"));
     attrs = attrs.color(bevy::color::palettes::basic::PURPLE.to_cosmic());
 
     // spawn editor
-    let cosmic_edit = commands
-        .spawn(CosmicEditBundle {
-            buffer: CosmicBuffer::new(&mut font_system, Metrics::new(14., 18.)).with_text(
-                &mut font_system,
-                "😀😀😀 x => y\nRead only widget",
-                attrs,
-            ),
+    commands.spawn((
+        TextEdit,
+        ReadOnly,
+        CosmicEditBuffer::new(&mut font_system, Metrics::new(14., 18.)).with_text(
+            &mut font_system,
+            "😀😀😀 x => y\nRead only widget",
+            attrs,
+        ),
+        Node {
+            width: Val::Percent(100.),
+            height: Val::Percent(100.),
             ..default()
-        })
-        .insert(ReadOnly)
-        .id();
-
-    // Spawn the ButtonBundle as a child of root
-    commands.entity(root).with_children(|parent| {
-        parent
-            .spawn(ButtonBundle {
-                style: Style {
-                    width: Val::Percent(100.),
-                    height: Val::Percent(100.),
-                    ..default()
-                },
-                background_color: BackgroundColor(Color::WHITE),
-                ..default()
-            })
-            // add cosmic source
-            .insert(CosmicSource(cosmic_edit));
-    });
+        },
+        BackgroundColor(Color::WHITE),
+    ));
 }
 
 fn main() {
@@ -62,11 +39,8 @@ fn main() {
 
     App::new()
         .add_plugins(DefaultPlugins)
-        .add_plugins(CosmicEditPlugin {
-            font_config,
-            ..default()
-        })
+        .add_plugins(CosmicEditPlugin { font_config })
         .add_systems(Startup, setup)
-        .add_systems(Update, change_active_editor_ui)
+        .add_systems(Update, (change_active_editor_ui, deselect_editor_on_esc))
         .run();
 }
diff --git a/examples/sprite_and_ui_clickable.rs b/examples/sprite_and_ui_clickable.rs
index 362ce26..4a01b17 100644
--- a/examples/sprite_and_ui_clickable.rs
+++ b/examples/sprite_and_ui_clickable.rs
@@ -1,55 +1,46 @@
 use bevy::prelude::*;
 use bevy_cosmic_edit::{
     cosmic_text::{Attrs, AttrsOwned},
-    *,
+    prelude::*,
+    CosmicTextAlign, CosmicTextChanged, CosmicWrap, MaxLines, TextHoverIn, TextHoverOut,
 };
 
 fn setup(mut commands: Commands) {
-    commands.spawn(Camera2dBundle::default());
+    commands.spawn(Camera2d);
 
-    // UI editor
-    let ui_editor = commands
-        .spawn(CosmicEditBundle {
-            default_attrs: DefaultAttrs(AttrsOwned::new(
-                Attrs::new().color(bevy::color::palettes::css::LIMEGREEN.to_cosmic()),
-            )),
-            max_lines: MaxLines(1),
-            mode: CosmicWrap::InfiniteLine,
-            text_position: CosmicTextAlign::Left { padding: 5 },
+    // Ui editor
+    commands.spawn((
+        TextEdit,
+        CosmicEditBuffer::default(),
+        DefaultAttrs(AttrsOwned::new(
+            Attrs::new().color(bevy::color::palettes::css::LIMEGREEN.to_cosmic()),
+        )),
+        MaxLines(1),
+        CosmicWrap::InfiniteLine,
+        CosmicTextAlign::Left { padding: 5 },
+        Node {
+            // Size and position of text box
+            width: Val::Px(300.),
+            height: Val::Px(50.),
+            left: Val::Px(100.),
+            top: Val::Px(100.),
             ..default()
-        })
-        .id();
-
-    commands
-        .spawn(ButtonBundle {
-            style: Style {
-                // Size and position of text box
-                width: Val::Px(300.),
-                height: Val::Px(50.),
-                left: Val::Px(100.),
-                top: Val::Px(100.),
-                ..default()
-            },
-            ..default()
-        })
-        .insert(CosmicSource(ui_editor));
+        },
+    ));
 
     // Sprite editor
-    commands.spawn((CosmicEditBundle {
-        max_lines: MaxLines(1),
-        mode: CosmicWrap::InfiniteLine,
-        sprite_bundle: SpriteBundle {
-            // Sets size of text box
-            sprite: Sprite {
-                custom_size: Some(Vec2::new(300., 100.)),
-                ..default()
-            },
-            // Position of text box
-            transform: Transform::from_xyz(0., 100., 0.),
+    commands.spawn((
+        CosmicEditBuffer::default(),
+        MaxLines(1),
+        CosmicWrap::InfiniteLine,
+        // Sets size of text box
+        Sprite {
+            custom_size: Some(Vec2::new(300., 100.)),
             ..default()
         },
-        ..default()
-    },));
+        // Position of text box
+        Transform::from_xyz(0., 100., 0.),
+    ));
 }
 
 fn ev_test(
diff --git a/readme.md b/readme.md
index 4c5c727..f5be2ef 100644
--- a/readme.md
+++ b/readme.md
@@ -29,7 +29,8 @@ RUSTFLAGS=--cfg=web_sys_unstable_apis cargo r --target wasm32-unknown-unknown --
 
 | bevy   | bevy_cosmic_edit |
 | ------ | ---------------- |
-| 0.14.0 | 0.21 - latest    |
+| 0.15.0 | 0.26 - latest    |
+| 0.14.0 | 0.21 - 0.25      |
 | 0.13.0 | 0.16 - 0.20      |
 | 0.12.* | 0.15             |
 | 0.11.* | 0.8 - 0.14       |
diff --git a/src/buffer.rs b/src/buffer.rs
index 88ae227..5205038 100644
--- a/src/buffer.rs
+++ b/src/buffer.rs
@@ -1,15 +1,16 @@
-use crate::*;
-use bevy::{
-    ecs::component::{ComponentHooks, StorageType},
+use crate::{
+    cosmic_edit::{ScrollEnabled, XOffset},
     prelude::*,
+    widget::CosmicPadding,
+    CosmicBackgroundColor, CosmicBackgroundImage, CosmicTextAlign, CosmicWrap, CursorColor,
+    HoverCursor, MaxChars, MaxLines, SelectionColor,
+};
+use bevy::{
+    ecs::{component::ComponentId, query::QueryData, world::DeferredWorld},
     window::PrimaryWindow,
 };
 use cosmic_text::{Attrs, AttrsOwned, Buffer, Edit, FontSystem, Metrics, Shaping};
 
-/// Set of all buffer setup functions. Runs in [`First`]
-#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
-pub struct BufferSet;
-
 pub(crate) struct BufferPlugin;
 
 impl Plugin for BufferPlugin {
@@ -21,7 +22,7 @@ impl Plugin for BufferPlugin {
                 set_initial_scale,
                 set_redraw,
                 set_editor_redraw,
-                swap_target_handle,
+                update_internal_target_handles,
             )
                 .chain(),
         );
@@ -40,7 +41,7 @@ impl BufferExtras for Buffer {
     /// * none, takes the rust magic ref to self
     ///
     /// # Returns
-    ///
+    ///input
     /// A [`String`] containing the cosmic text content.
     fn get_text(&self) -> String {
         let mut text = String::new();
@@ -59,39 +60,50 @@ impl BufferExtras for Buffer {
 }
 
 /// Component wrapper for [`Buffer`]
-#[derive(Deref, DerefMut)]
-pub struct CosmicBuffer(pub Buffer);
-
-impl Component for CosmicBuffer {
-    const STORAGE_TYPE: StorageType = StorageType::Table;
+#[derive(Component, Deref, DerefMut)]
+#[component(on_remove = remove_focus_from_entity)]
+#[require(
+    CosmicBackgroundColor,
+    CursorColor,
+    SelectionColor,
+    DefaultAttrs,
+    CosmicBackgroundImage,
+    CosmicRenderOutput,
+    MaxLines,
+    MaxChars,
+    XOffset,
+    CosmicWrap,
+    CosmicTextAlign,
+    CosmicPadding,
+    HoverCursor,
+    ScrollEnabled
+)]
+pub struct CosmicEditBuffer(pub Buffer);
 
-    fn register_component_hooks(hooks: &mut ComponentHooks) {
-        hooks.on_remove(|mut world, entity, _| {
-            if let Some(mut focused_widget) = world.get_resource_mut::<FocusedWidget>() {
-                if let Some(focused) = focused_widget.0 {
-                    if focused == entity {
-                        focused_widget.0 = None;
-                    }
-                }
+fn remove_focus_from_entity(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
+    if let Some(mut focused_widget) = world.get_resource_mut::<FocusedWidget>() {
+        if let Some(focused) = focused_widget.0 {
+            if focused == entity {
+                focused_widget.0 = None;
             }
-        });
+        }
     }
 }
 
-impl Default for CosmicBuffer {
+impl Default for CosmicEditBuffer {
     fn default() -> Self {
-        CosmicBuffer(Buffer::new_empty(Metrics::new(20., 20.)))
+        CosmicEditBuffer(Buffer::new_empty(Metrics::new(20., 20.)))
     }
 }
 
-impl<'s, 'r> CosmicBuffer {
+impl<'s, 'r> CosmicEditBuffer {
     /// Create a new buffer with a font system
     pub fn new(font_system: &mut FontSystem, metrics: Metrics) -> Self {
         Self(Buffer::new(font_system, metrics))
     }
 
     // Das a lotta boilerplate just to hide the shaping argument
-    /// Add text to a newly created [`CosmicBuffer`]
+    /// Add text to a newly created [`CosmicEditBuffer`]
     pub fn with_text(
         mut self,
         font_system: &mut FontSystem,
@@ -102,7 +114,7 @@ impl<'s, 'r> CosmicBuffer {
         self
     }
 
-    /// Add rich text to a newly created [`CosmicBuffer`]
+    /// Add rich text to a newly created [`CosmicEditBuffer`]
     ///
     /// Rich text is an iterable of `(&'s str, Attrs<'r>)`
     pub fn with_rich_text<I>(
@@ -190,10 +202,10 @@ impl<'s, 'r> CosmicBuffer {
     }
 }
 
-/// Adds a [`FontSystem`] to a newly created [`CosmicBuffer`] if one was not provided
-pub fn add_font_system(
+/// Adds a [`FontSystem`] to a newly created [`CosmicEditBuffer`] if one was not provided
+pub(crate) fn add_font_system(
     mut font_system: ResMut<CosmicFontSystem>,
-    mut q: Query<&mut CosmicBuffer, Added<CosmicBuffer>>,
+    mut q: Query<&mut CosmicEditBuffer, Added<CosmicEditBuffer>>,
 ) {
     for mut b in q.iter_mut() {
         if !b.lines.is_empty() {
@@ -204,10 +216,10 @@ pub fn add_font_system(
     }
 }
 
-/// Initialises [`CosmicBuffer`] scale factor
-pub fn set_initial_scale(
+/// Initialises [`CosmicEditBuffer`] scale factor
+pub(crate) fn set_initial_scale(
     window_q: Query<&Window, With<PrimaryWindow>>,
-    mut cosmic_query: Query<&mut CosmicBuffer, Added<CosmicBuffer>>,
+    mut cosmic_query: Query<&mut CosmicEditBuffer, Added<CosmicEditBuffer>>,
     mut font_system: ResMut<CosmicFontSystem>,
 ) {
     if let Ok(window) = window_q.get_single() {
@@ -220,66 +232,76 @@ pub fn set_initial_scale(
     }
 }
 
-/// Initialises new [`CosmicBuffer`] redraw flag to true
-pub fn set_redraw(mut q: Query<&mut CosmicBuffer, Added<CosmicBuffer>>) {
+/// Initialises new [`CosmicEditBuffer`] redraw flag to true
+pub(crate) fn set_redraw(mut q: Query<&mut CosmicEditBuffer, Added<CosmicEditBuffer>>) {
     for mut b in q.iter_mut() {
         b.set_redraw(true);
     }
 }
 
 /// Initialises new [`CosmicEditor`] redraw flag to true
-pub fn set_editor_redraw(mut q: Query<&mut CosmicEditor, Added<CosmicEditor>>) {
-    for mut b in q.iter_mut() {
-        b.set_redraw(true);
+pub(crate) fn set_editor_redraw(mut q: Query<&mut CosmicEditor, Added<CosmicEditor>>) {
+    for mut ed in q.iter_mut() {
+        ed.set_redraw(true);
     }
 }
 
-/// Sets image of UI elements to the [`CosmicBuffer`] output
-pub fn swap_target_handle(
-    source_q: Query<&Handle<Image>, With<CosmicBuffer>>,
-    mut dest_q: Query<
-        (
-            Option<&mut Handle<Image>>,
-            Option<&mut UiImage>,
-            &CosmicSource,
-        ),
-        Without<CosmicBuffer>,
-    >,
-) {
-    // TODO: do this once
-    for (dest_handle_opt, dest_ui_opt, source_entity) in dest_q.iter_mut() {
-        if let Ok(source_handle) = source_q.get(source_entity.0) {
-            if let Some(mut dest_handle) = dest_handle_opt {
-                *dest_handle = source_handle.clone_weak();
-            }
-            if let Some(mut dest_ui) = dest_ui_opt {
-                dest_ui.texture = source_handle.clone_weak();
-            }
+/// Will attempt to find a place on the receiving entity to place
+/// a [`Handle<Image>`]
+#[derive(QueryData)]
+#[query_data(mutable)]
+pub(crate) struct OutputToEntity {
+    sprite_target: Option<&'static mut Sprite>,
+    image_node_target: Option<&'static mut ImageNode>,
+}
+
+impl OutputToEntityItem<'_> {
+    pub fn write_image_data(&mut self, image: &Handle<Image>) {
+        if let Some(sprite) = self.sprite_target.as_mut() {
+            sprite.image = image.clone_weak();
         }
+        if let Some(image_node) = self.image_node_target.as_mut() {
+            image_node.image = image.clone_weak();
+        }
+    }
+}
+
+/// Every frame updates the output (in [`CosmicRenderOutput`]) to its receiver
+/// on the same entity, e.g. [`Sprite`]
+pub(crate) fn update_internal_target_handles(
+    mut buffers_q: Query<(&CosmicRenderOutput, OutputToEntity), With<CosmicEditBuffer>>,
+) {
+    for (output_data, mut output_components) in buffers_q.iter_mut() {
+        output_components.write_image_data(&output_data.0);
     }
 }
 
 // TODO put this on impl CosmicBuffer
 
-pub fn get_text_size(buffer: &Buffer) -> (f32, f32) {
+/// Returns in physical pixels
+pub(crate) fn get_text_size(buffer: &Buffer) -> Vec2 {
     if buffer.layout_runs().count() == 0 {
-        return (0., buffer.metrics().line_height);
+        return Vec2::new(0., buffer.metrics().line_height);
     }
+    // get max width
     let width = buffer
         .layout_runs()
         .map(|run| run.line_w)
         .reduce(f32::max)
         .unwrap();
+    // get total height
     let height = buffer.layout_runs().count() as f32 * buffer.metrics().line_height;
-    (width, height)
+    Vec2::new(width, height)
 }
 
-pub fn get_y_offset_center(widget_height: f32, buffer: &Buffer) -> i32 {
-    let (_, text_height) = get_text_size(buffer);
+/// Returns in physical pixels
+pub(crate) fn get_y_offset_center(widget_height: f32, buffer: &Buffer) -> i32 {
+    let text_height = get_text_size(buffer).y;
     ((widget_height - text_height) / 2.0) as i32
 }
 
-pub fn get_x_offset_center(widget_width: f32, buffer: &Buffer) -> i32 {
-    let (text_width, _) = get_text_size(buffer);
+/// Returns in physical pixels
+pub(crate) fn get_x_offset_center(widget_width: f32, buffer: &Buffer) -> i32 {
+    let text_width = get_text_size(buffer).x;
     ((widget_width - text_width) / 2.0) as i32
 }
diff --git a/src/cosmic_edit.rs b/src/cosmic_edit.rs
index 392bb8e..83aa1c0 100644
--- a/src/cosmic_edit.rs
+++ b/src/cosmic_edit.rs
@@ -1,7 +1,19 @@
-use crate::*;
-use bevy::prelude::*;
+use crate::prelude::*;
 use cosmic_text::{Attrs, AttrsOwned, Editor, FontSystem};
 
+pub(crate) fn plugin(app: &mut App) {
+    app.register_type::<CosmicWrap>()
+        .register_type::<CosmicTextAlign>()
+        .register_type::<XOffset>()
+        .register_type::<CosmicBackgroundImage>()
+        .register_type::<CosmicBackgroundColor>()
+        .register_type::<CursorColor>()
+        .register_type::<SelectionColor>()
+        .register_type::<MaxLines>()
+        .register_type::<MaxChars>()
+        .register_type::<ScrollEnabled>();
+}
+
 /// Enum representing text wrapping in a cosmic [`Buffer`]
 #[derive(Component, Reflect, Clone, PartialEq, Default)]
 pub enum CosmicWrap {
@@ -10,7 +22,8 @@ pub enum CosmicWrap {
     Wrap,
 }
 
-/// Enum representing the text alignment in a cosmic [`Buffer`]
+/// Enum representing the text alignment in a cosmic [`Buffer`].
+/// Defaults to [`CosmicTextAlign::Center`]
 #[derive(Component, Reflect, Clone)]
 pub enum CosmicTextAlign {
     Center { padding: i32 },
@@ -24,19 +37,26 @@ impl Default for CosmicTextAlign {
     }
 }
 
-/// Tag component to disable writing to a [`CosmicBuffer`]
+/// Tag component to disable writing to a [`CosmicEditBuffer`]
 // TODO: Code example
 #[derive(Component, Default)]
 pub struct ReadOnly; // tag component
 
 /// Internal value used to decide what section of a [`Buffer`] to render
 #[derive(Component, Reflect, Debug, Default)]
-pub struct XOffset {
+pub(crate) struct XOffset {
+    /// How much space in logical units from the left of the [`Buffer`]
+    /// to start rendering text.
     pub left: f32,
-    pub width: f32,
+
+    /// Width of buffer that includes text that should be rendered,
+    /// in logical units.
+    ///
+    /// Should only be [None] if in default state
+    pub width: Option<f32>,
 }
 
-/// Default text attributes to be used on a [`CosmicBuffer`]
+/// Default text attributes to be used on a [`CosmicEditBuffer`]
 #[derive(Component, Deref, DerefMut)]
 pub struct DefaultAttrs(pub AttrsOwned);
 
@@ -54,19 +74,34 @@ pub struct CosmicBackgroundImage(pub Option<Handle<Image>>);
 #[derive(Component, Reflect, Default, Deref)]
 pub struct CosmicBackgroundColor(pub Color);
 
-/// Color to be used for the text cursor
-#[derive(Component, Reflect, Default, Deref)]
+/// Color to be used for the text cursor.
+/// Defaults to [`Color::BLACK`]
+#[derive(Component, Reflect, Deref)]
 pub struct CursorColor(pub Color);
 
-/// Color to be used as the selected text background
-#[derive(Component, Reflect, Default, Deref)]
+impl Default for CursorColor {
+    fn default() -> Self {
+        CursorColor(Color::BLACK)
+    }
+}
+
+/// Color to be used as the selected text background.
+/// Defaults to [`Color::GRAY`]
+#[derive(Component, Reflect, Deref)]
 pub struct SelectionColor(pub Color);
 
+impl Default for SelectionColor {
+    fn default() -> Self {
+        SelectionColor(bevy::color::palettes::basic::GRAY.into())
+    }
+}
+
 /// Color to be used for the selected text
 #[derive(Component, Reflect, Default, Deref)]
 pub struct SelectedTextColor(pub Color);
 
 /// Maximum number of lines allowed in a buffer
+// TODO: Actually test this? I'm not sure this does anything afaik
 #[derive(Component, Reflect, Default)]
 pub struct MaxLines(pub usize);
 
@@ -75,164 +110,33 @@ pub struct MaxLines(pub usize);
 #[derive(Component, Reflect, Default)]
 pub struct MaxChars(pub usize);
 
-/// Buffer does not respond to scroll events
-#[derive(Component, Default)]
-pub struct ScrollDisabled;
-
-/// A pointer to an entity with a [`CosmicEditBundle`], used to apply cosmic rendering to a UI
-/// element.
-///
-///```
-/// # use bevy::prelude::*;
-/// # use bevy_cosmic_edit::*;
-/// #
-/// # fn setup(mut commands: Commands) {
-/// // Create a new cosmic bundle
-/// let cosmic_edit = commands.spawn(CosmicEditBundle::default()).id();
-///
-/// // Spawn the target bundle
-/// commands
-///     .spawn(ButtonBundle {
-///         style: Style {
-///             width: Val::Percent(100.),
-///             height: Val::Percent(100.),
-///             ..default()
-///         },
-///         background_color: BackgroundColor(Color::WHITE),
-///         ..default()
-///     })
-///     // Add the source component to the target element
-///     .insert(CosmicSource(cosmic_edit));
-/// # }
-/// #
-/// # fn main() {
-/// #     App::new()
-/// #         .add_plugins(MinimalPlugins)
-/// #         .add_plugins(CosmicEditPlugin::default())
-/// #         .add_systems(Startup, setup);
-/// # }
-#[derive(Component, Reflect)]
-pub struct CosmicSource(pub Entity);
-
-/// A bundle containing all the required components for [`CosmicBuffer`] functionality.
-///
-/// Uses an invisible [`SpriteBundle`] for rendering by default, so should either be paired with another
-/// entity with a [`CosmicSource`] pointing to it's entity, or have the sprite set.
-///
-/// ### UI mode
-///
-///```
-/// # use bevy::prelude::*;
-/// # use bevy_cosmic_edit::*;
-/// #
-/// # fn setup(mut commands: Commands) {
-/// // Create a new cosmic bundle
-/// let cosmic_edit = commands.spawn(CosmicEditBundle::default()).id();
-///
-/// // Spawn the target bundle
-/// commands
-///     .spawn(ButtonBundle {
-///         style: Style {
-///             width: Val::Percent(100.),
-///             height: Val::Percent(100.),
-///             ..default()
-///         },
-///         background_color: BackgroundColor(Color::WHITE),
-///         ..default()
-///     })
-///     // Add the source component to the target element
-///     .insert(CosmicSource(cosmic_edit));
-/// # }
-/// #
-/// # fn main() {
-/// #     App::new()
-/// #         .add_plugins(MinimalPlugins)
-/// #         .add_plugins(CosmicEditPlugin::default())
-/// #         .add_systems(Startup, setup);
-/// # }
-/// ```
-/// ### Sprite mode
-/// ```
-/// # use bevy::prelude::*;
-/// # use bevy_cosmic_edit::*;
-/// #
-/// # fn setup(mut commands: Commands) {
-/// // Create a new cosmic bundle
-/// commands.spawn(CosmicEditBundle {
-///     sprite_bundle: SpriteBundle {
-///         sprite: Sprite {
-///             custom_size: Some(Vec2::new(300.0, 40.0)),
-///             ..default()
-///         },
-///         ..default()
-///     },
-///     ..default()
-/// });
-/// # }
-/// #
-/// # fn main() {
-/// #     App::new()
-/// #         .add_plugins(MinimalPlugins)
-/// #         .add_plugins(CosmicEditPlugin::default())
-/// #         .add_systems(Startup, setup);
-/// # }
-#[derive(Bundle)]
-pub struct CosmicEditBundle {
-    // cosmic bits
-    pub buffer: CosmicBuffer,
-    // render bits
-    pub fill_color: CosmicBackgroundColor,
-    pub cursor_color: CursorColor,
-    pub selection_color: SelectionColor,
-    pub default_attrs: DefaultAttrs,
-    pub background_image: CosmicBackgroundImage,
-    pub sprite_bundle: SpriteBundle,
-    // restriction bits
-    pub max_lines: MaxLines,
-    pub max_chars: MaxChars,
-    // layout bits
-    pub x_offset: XOffset,
-    pub mode: CosmicWrap,
-    pub text_position: CosmicTextAlign,
-    pub padding: CosmicPadding,
-    pub widget_size: CosmicWidgetSize,
-    pub hover_cursor: HoverCursor,
+/// Should [`CosmicEditBuffer`] respond to scroll events?
+#[derive(Component, Reflect, Default)]
+pub enum ScrollEnabled {
+    #[default]
+    Enabled,
+    Disabled,
 }
 
-impl Default for CosmicEditBundle {
-    fn default() -> Self {
-        CosmicEditBundle {
-            buffer: Default::default(),
-            fill_color: Default::default(),
-            cursor_color: CursorColor(Color::BLACK),
-            selection_color: SelectionColor(bevy::color::palettes::basic::GRAY.into()),
-            text_position: Default::default(),
-            default_attrs: Default::default(),
-            background_image: Default::default(),
-            max_lines: Default::default(),
-            max_chars: Default::default(),
-            mode: Default::default(),
-            sprite_bundle: SpriteBundle {
-                sprite: Sprite {
-                    custom_size: Some(Vec2::ONE * 128.0),
-                    ..default()
-                },
-                visibility: Visibility::Hidden,
-                ..default()
-            },
-            x_offset: Default::default(),
-            padding: Default::default(),
-            widget_size: Default::default(),
-            hover_cursor: Default::default(),
-        }
+impl ScrollEnabled {
+    pub fn should_scroll(&self) -> bool {
+        matches!(self, ScrollEnabled::Enabled)
     }
 }
 
 /// Holds the font system used internally by [`cosmic_text`]
+///
+/// Note: When bevy provides enough initialisation flexibility,
+/// this should be merged with its builtin resource
 #[derive(Resource, Deref, DerefMut)]
 pub struct CosmicFontSystem(pub FontSystem);
 
 /// Wrapper component for an [`Editor`] with a few helpful values for cursor blinking
+///
+/// [`cosmic_text::Editor`] is basically a mutable version of [`cosmic_text::Buffer`].
+///
+/// This component should be on a focussed [`CosmicEditBuffer`]
+// Managed by crate::focus::add_editor_to_focussed and similar systems
 #[derive(Component, Deref, DerefMut)]
 pub struct CosmicEditor {
     #[deref]
@@ -246,7 +150,7 @@ impl CosmicEditor {
         Self {
             editor,
             cursor_visible: true,
-            cursor_timer: Timer::new(Duration::from_millis(530), TimerMode::Repeating),
+            cursor_timer: Timer::new(std::time::Duration::from_millis(530), TimerMode::Repeating),
         }
     }
 }
diff --git a/src/cursor.rs b/src/cursor.rs
index ae3b250..ee3504e 100644
--- a/src/cursor.rs
+++ b/src/cursor.rs
@@ -1,14 +1,14 @@
 // This will all be rewritten soon, looking toward per-widget cursor control
 // Rewrite should address issue #93 too
 
-use crate::*;
-use bevy::{input::mouse::MouseMotion, prelude::*, window::PrimaryWindow};
+use crate::prelude::*;
+use bevy::{
+    input::mouse::MouseMotion,
+    window::{PrimaryWindow, SystemCursorIcon},
+    winit::cursor::CursorIcon,
+};
 
-/// System set for mouse cursor systems. Runs in [`Update`]
-#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
-pub struct CursorSet;
-
-pub struct CursorPlugin;
+pub(crate) struct CursorPlugin;
 
 /// Unit resource whose existence in the world disables the cursor plugin systems.
 #[derive(Resource)]
@@ -18,139 +18,88 @@ impl Plugin for CursorPlugin {
     fn build(&self, app: &mut App) {
         app.add_systems(
             Update,
-            ((hover_sprites, hover_ui), change_cursor)
+            (
+                (crate::render_targets::hover_sprites, hover_ui),
+                change_cursor,
+            )
                 .chain()
                 .run_if(not(resource_exists::<CursorPluginDisabled>)),
         )
         .add_event::<TextHoverIn>()
         .register_type::<TextHoverIn>()
-        .add_event::<TextHoverOut>();
+        .add_event::<TextHoverOut>()
+        .register_type::<TextHoverOut>()
+        .register_type::<HoverCursor>();
     }
 }
 
+/// What cursor icon to show when hovering over a widget
+///
+/// By default is [`CursorIcon::System(SystemCursorIcon::Text)`]
 #[derive(Component, Reflect, Deref)]
 pub struct HoverCursor(pub CursorIcon);
 
 impl Default for HoverCursor {
     fn default() -> Self {
-        Self(CursorIcon::Text)
+        Self(CursorIcon::System(SystemCursorIcon::Text))
     }
 }
 
 /// For use with custom cursor control
-/// Event is emitted when cursor enters a text widget
+///
+/// Event is emitted when cursor enters a text widget.
 /// Event contains the cursor from the buffer's [`HoverCursor`]
 #[derive(Event, Reflect, Deref, Debug)]
 pub struct TextHoverIn(pub CursorIcon);
 
 /// For use with custom cursor control
 /// Event is emitted when cursor leaves a text widget
-#[derive(Event, Debug)]
+#[derive(Event, Reflect, Debug)]
 pub struct TextHoverOut;
 
 pub(crate) fn change_cursor(
     mut evr_hover_in: EventReader<TextHoverIn>,
     evr_hover_out: EventReader<TextHoverOut>,
-    evr_text_changed: EventReader<CosmicTextChanged>,
+    evr_text_changed: EventReader<crate::events::CosmicTextChanged>,
     evr_mouse_motion: EventReader<MouseMotion>,
     mouse_buttons: Res<ButtonInput<MouseButton>>,
-    mut windows: Query<&mut Window, With<PrimaryWindow>>,
+    mut windows: Query<(&mut Window, &mut CursorIcon), With<PrimaryWindow>>,
 ) {
     if windows.iter().len() == 0 {
         return;
     }
-    let mut window = windows.single_mut();
+    let (mut window, mut window_cursor_icon) = windows.single_mut();
 
     if let Some(ev) = evr_hover_in.read().last() {
-        window.cursor.icon = ev.0;
+        *window_cursor_icon = ev.0.clone();
     } else if !evr_hover_out.is_empty() {
-        window.cursor.icon = CursorIcon::Default;
+        *window_cursor_icon = CursorIcon::System(SystemCursorIcon::Default);
     }
 
     if !evr_text_changed.is_empty() {
-        window.cursor.visible = false;
+        window.cursor_options.visible = false;
     }
 
     if mouse_buttons.get_just_pressed().len() != 0 || !evr_mouse_motion.is_empty() {
-        window.cursor.visible = true;
-    }
-}
-
-#[cfg(feature = "multicam")]
-type CameraQuery<'a, 'b, 'c, 'd> =
-    Query<'a, 'b, (&'c Camera, &'d GlobalTransform), With<CosmicPrimaryCamera>>;
-
-#[cfg(not(feature = "multicam"))]
-type CameraQuery<'a, 'b, 'c, 'd> = Query<'a, 'b, (&'c Camera, &'d GlobalTransform)>;
-
-pub(crate) fn hover_sprites(
-    windows: Query<&Window, With<PrimaryWindow>>,
-    mut cosmic_edit_query: Query<
-        (&mut Sprite, &Visibility, &GlobalTransform, &HoverCursor),
-        With<CosmicBuffer>,
-    >,
-    camera_q: CameraQuery,
-    mut hovered: Local<bool>,
-    mut last_hovered: Local<bool>,
-    mut evw_hover_in: EventWriter<TextHoverIn>,
-    mut evw_hover_out: EventWriter<TextHoverOut>,
-) {
-    *hovered = false;
-    if windows.iter().len() == 0 {
-        return;
+        window.cursor_options.visible = true;
     }
-    let window = windows.single();
-    let (camera, camera_transform) = camera_q.single();
-
-    let mut icon = CursorIcon::Default;
-
-    for (sprite, visibility, node_transform, hover) in &mut cosmic_edit_query.iter_mut() {
-        if visibility == Visibility::Hidden {
-            continue;
-        }
-
-        let size = sprite.custom_size.unwrap_or(Vec2::ONE);
-        if get_node_cursor_pos(
-            window,
-            node_transform,
-            size,
-            false,
-            camera,
-            camera_transform,
-        )
-        .is_some()
-        {
-            *hovered = true;
-            icon = hover.0;
-        }
-    }
-
-    if *last_hovered != *hovered {
-        if *hovered {
-            evw_hover_in.send(TextHoverIn(icon));
-        } else {
-            evw_hover_out.send(TextHoverOut);
-        }
-    }
-
-    *last_hovered = *hovered;
 }
 
 pub(crate) fn hover_ui(
-    interaction_query: Query<(&Interaction, &CosmicSource), Changed<Interaction>>,
-    cosmic_query: Query<&HoverCursor, With<CosmicBuffer>>,
+    interaction_query: Query<
+        (&Interaction, &HoverCursor),
+        (With<CosmicEditBuffer>, Changed<Interaction>),
+    >,
     mut evw_hover_in: EventWriter<TextHoverIn>,
     mut evw_hover_out: EventWriter<TextHoverOut>,
 ) {
-    for (interaction, source) in interaction_query.iter() {
+    for (interaction, hover) in interaction_query.iter() {
         match interaction {
             Interaction::None => {
                 evw_hover_out.send(TextHoverOut);
             }
             Interaction::Hovered => {
-                if let Ok(hover) = cosmic_query.get(source.0) {
-                    evw_hover_in.send(TextHoverIn(hover.0));
-                }
+                evw_hover_in.send(TextHoverIn(hover.0.clone()));
             }
             _ => {}
         }
diff --git a/src/events.rs b/src/events.rs
index 1f542b3..ecbb82d 100644
--- a/src/events.rs
+++ b/src/events.rs
@@ -13,6 +13,7 @@ impl Plugin for EventsPlugin {
 }
 
 /// Text change events
+///
 /// Sent when text is changed in a cosmic buffer
 /// Contains the entity on which the text was changed, and the new text as a [`String`]
 #[derive(Event, Reflect, Debug)]
diff --git a/src/focus.rs b/src/focus.rs
index 88cf6e5..04df1ab 100644
--- a/src/focus.rs
+++ b/src/focus.rs
@@ -1,10 +1,9 @@
-use crate::*;
-use bevy::prelude::*;
+use crate::{prelude::*, widget::WidgetSet};
 use cosmic_text::{Edit, Editor};
 
 /// System set for focus systems. Runs in `PostUpdate`
 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
-pub struct FocusSet;
+pub(crate) struct FocusSet;
 
 pub(crate) struct FocusPlugin;
 
@@ -23,14 +22,18 @@ impl Plugin for FocusPlugin {
 }
 
 /// Resource struct that keeps track of the currently active editor entity.
+///
+/// The focussed entity must have a [`CosmicEditBuffer`], and should have a
+/// [`CosmicEditor`] component as well if it can be mutated (i.e. isn't [`Readonly`]).
 #[derive(Resource, Reflect, Default, Deref, DerefMut)]
 #[reflect(Resource)]
 pub struct FocusedWidget(pub Option<Entity>);
 
+/// Adds [`CosmicEditor`] by copying from existing [`CosmicEditBuffer`].
 pub(crate) fn add_editor_to_focused(
     mut commands: Commands,
     active_editor: Res<FocusedWidget>,
-    q: Query<&CosmicBuffer, Without<CosmicEditor>>,
+    q: Query<&CosmicEditBuffer, Without<CosmicEditor>>,
 ) {
     if let Some(e) = active_editor.0 {
         let Ok(b) = q.get(e) else {
@@ -42,10 +45,11 @@ pub(crate) fn add_editor_to_focused(
     }
 }
 
+/// Removes [`CosmicEditor`]
 pub(crate) fn drop_editor_unfocused(
     mut commands: Commands,
     active_editor: Res<FocusedWidget>,
-    mut q: Query<(Entity, &mut CosmicBuffer, &CosmicEditor)>,
+    mut q: Query<(Entity, &mut CosmicEditBuffer, &CosmicEditor)>,
 ) {
     if active_editor.0.is_none() {
         for (e, mut b, ed) in q.iter_mut() {
diff --git a/src/input.rs b/src/input.rs
index 931028b..124abfe 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -1,21 +1,25 @@
 #![allow(clippy::too_many_arguments, clippy::type_complexity)]
 
-use crate::*;
+use crate::{
+    buffer::{get_x_offset_center, get_y_offset_center},
+    cosmic_edit::{CosmicTextAlign, MaxChars, MaxLines, ReadOnly, ScrollEnabled, XOffset},
+    events::CosmicTextChanged,
+    prelude::*,
+    CosmicWidgetSize,
+};
 use bevy::{
     input::{
         keyboard::{Key, KeyboardInput},
         mouse::{MouseMotion, MouseScrollUnit, MouseWheel},
     },
-    prelude::*,
     window::PrimaryWindow,
 };
 use cosmic_text::{Action, Cursor, Edit, Motion, Selection};
 
-#[cfg(target_arch = "wasm32")]
-use crate::DefaultAttrs;
 #[cfg(target_arch = "wasm32")]
 use bevy::tasks::AsyncComputeTaskPool;
 #[cfg(target_arch = "wasm32")]
+#[allow(unused_imports)]
 use js_sys::Promise;
 #[cfg(target_arch = "wasm32")]
 use wasm_bindgen::prelude::*;
@@ -38,24 +42,32 @@ impl Plugin for InputPlugin {
                     .in_set(InputSet),
             )
             .insert_resource(ClickTimer(Timer::from_seconds(0.5, TimerMode::Once)));
+
+        #[cfg(target_arch = "wasm32")]
+        {
+            let (tx, rx) = crossbeam_channel::bounded::<WasmPaste>(1);
+            app.insert_resource(WasmPasteAsyncChannel { tx, rx })
+                .add_systems(Update, poll_wasm_paste);
+        }
     }
 }
 
 /// Timer for double / triple clicks
 #[derive(Resource)]
-pub struct ClickTimer(pub Timer);
+pub(crate) struct ClickTimer(pub(crate) Timer);
 
 // TODO: hide this behind #cfg wasm, depends on wasm having own copy/paste fn
 /// Crossbeam channel struct for Wasm clipboard data
-#[allow(dead_code)]
-pub struct WasmPaste {
+#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
+pub(crate) struct WasmPaste {
     text: String,
     entity: Entity,
 }
 
 /// Async channel for receiving from the clipboard in Wasm
+#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
 #[derive(Resource)]
-pub struct WasmPasteAsyncChannel {
+pub(crate) struct WasmPasteAsyncChannel {
     pub tx: crossbeam_channel::Sender<WasmPaste>,
     pub rx: crossbeam_channel::Receiver<WasmPaste>,
 }
@@ -69,12 +81,10 @@ pub(crate) fn input_mouse(
         &mut CosmicEditor,
         &GlobalTransform,
         &CosmicTextAlign,
-        Entity,
         &XOffset,
-        &mut Sprite,
-        Option<&ScrollDisabled>,
+        &ScrollEnabled,
+        CosmicWidgetSize,
     )>,
-    node_q: Query<(&Node, &GlobalTransform, &CosmicSource)>,
     mut font_system: ResMut<CosmicFontSystem>,
     mut scroll_evr: EventReader<MouseWheel>,
     camera_q: Query<(&Camera, &GlobalTransform)>,
@@ -83,12 +93,9 @@ pub(crate) fn input_mouse(
     time: Res<Time>,
     evr_mouse_motion: EventReader<MouseMotion>,
 ) {
+    // handle click timer and click_count
     click_timer.0.tick(time.delta());
 
-    let Some(active_editor_entity) = active_editor.0 else {
-        return;
-    };
-
     if click_timer.0.finished() || !evr_mouse_motion.is_empty() {
         *click_count = 0;
     }
@@ -102,6 +109,11 @@ pub(crate) fn input_mouse(
         *click_count = 0;
     }
 
+    // unwrap resources
+    let Some(active_editor_entity) = active_editor.0 else {
+        return;
+    };
+
     let Ok(primary_window) = windows.get_single() else {
         return;
     };
@@ -111,33 +123,20 @@ pub(crate) fn input_mouse(
         return;
     };
 
-    if let Ok((
-        mut editor,
-        sprite_transform,
-        text_position,
-        entity,
-        x_offset,
-        sprite,
-        scroll_disabled,
-    )) = editor_q.get_mut(active_editor_entity)
+    // TODO: generalize this over UI and sprite
+    if let Ok((mut editor, transform, text_position, x_offset, scroll_disabled, target_size)) =
+        editor_q.get_mut(active_editor_entity)
     {
         let buffer = editor.with_buffer(|b| b.clone());
 
-        let mut is_ui_node = false;
-        let mut transform = sprite_transform;
-        let sprite_size = sprite.custom_size.expect("Must specify Sprite.custom_size");
-        let (mut width, mut height) = (sprite_size.x, sprite_size.y);
-
-        // TODO: this is bad loop nesting, rethink system with relationships in mind
-        for (node, node_transform, source) in node_q.iter() {
-            if source.0 != entity {
-                continue;
-            }
-            is_ui_node = true;
-            transform = node_transform;
-            width = node.size().x;
-            height = node.size().y;
-        }
+        // get size of render target
+        let Ok(source_type) = target_size.scan() else {
+            return;
+        };
+        let Ok(size) = target_size.logical_size() else {
+            return;
+        };
+        let (width, height) = (size.x, size.y);
 
         let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);
 
@@ -159,7 +158,8 @@ pub(crate) fn input_mouse(
                 get_y_offset_center(height * scale_factor, &buffer),
             ),
         };
-        let point = |node_cursor_pos: Vec2| {
+        // Converts a node-relative space coordinate to a screen space physical coord
+        let screen_physical = |node_cursor_pos: Vec2| {
             (
                 (node_cursor_pos.x * scale_factor) as i32 - padding_x,
                 (node_cursor_pos.y * scale_factor) as i32 - padding_y,
@@ -170,15 +170,15 @@ pub(crate) fn input_mouse(
             editor.cursor_visible = true;
             editor.cursor_timer.reset();
 
-            if let Some(node_cursor_pos) = get_node_cursor_pos(
+            if let Some(node_cursor_pos) = crate::render_targets::get_node_cursor_pos(
                 primary_window,
                 transform,
                 Vec2::new(width, height),
-                is_ui_node,
+                source_type,
                 camera,
                 camera_transform,
             ) {
-                let (mut x, y) = point(node_cursor_pos);
+                let (mut x, y) = screen_physical(node_cursor_pos);
                 x += x_offset.left as i32;
                 if shift {
                     editor.action(&mut font_system.0, Action::Drag { x, y });
@@ -210,15 +210,15 @@ pub(crate) fn input_mouse(
         }
 
         if buttons.pressed(MouseButton::Left) && *click_count == 0 {
-            if let Some(node_cursor_pos) = get_node_cursor_pos(
+            if let Some(node_cursor_pos) = crate::render_targets::get_node_cursor_pos(
                 primary_window,
                 transform,
                 Vec2::new(width, height),
-                is_ui_node,
+                source_type,
                 camera,
                 camera_transform,
             ) {
-                let (mut x, y) = point(node_cursor_pos);
+                let (mut x, y) = screen_physical(node_cursor_pos);
                 x += x_offset.left as i32;
                 if active_editor.is_changed() && !shift {
                     editor.action(&mut font_system.0, Action::Click { x, y });
@@ -229,7 +229,7 @@ pub(crate) fn input_mouse(
             return;
         }
 
-        if scroll_disabled.is_none() {
+        if scroll_disabled.should_scroll() {
             for ev in scroll_evr.read() {
                 match ev.unit {
                     MouseScrollUnit::Line => {
@@ -255,7 +255,7 @@ pub(crate) fn input_mouse(
     }
 }
 
-pub fn kb_move_cursor(
+pub(crate) fn kb_move_cursor(
     active_editor: Res<FocusedWidget>,
     keys: Res<ButtonInput<KeyCode>>,
     mut cosmic_edit_query: Query<(&mut CosmicEditor,)>,
@@ -408,7 +408,7 @@ pub(crate) fn kb_input_text(
     mut char_evr: EventReader<KeyboardInput>,
     mut cosmic_edit_query: Query<(
         &mut CosmicEditor,
-        &mut CosmicBuffer,
+        &mut CosmicEditBuffer,
         &MaxLines,
         &MaxChars,
         Entity,
@@ -522,14 +522,14 @@ pub(crate) fn kb_input_text(
     }
 }
 
-pub fn kb_clipboard(
+pub(crate) fn kb_clipboard(
     active_editor: Res<FocusedWidget>,
     keys: Res<ButtonInput<KeyCode>>,
     mut evw_changed: EventWriter<CosmicTextChanged>,
     mut font_system: ResMut<CosmicFontSystem>,
     mut cosmic_edit_query: Query<(
         &mut CosmicEditor,
-        &mut CosmicBuffer,
+        &mut CosmicEditBuffer,
         &MaxLines,
         &MaxChars,
         Entity,
@@ -664,13 +664,12 @@ pub fn read_clipboard_wasm() -> Promise {
 }
 
 #[cfg(target_arch = "wasm32")]
-pub fn poll_wasm_paste(
+pub(crate) fn poll_wasm_paste(
     channel: Res<WasmPasteAsyncChannel>,
     mut editor_q: Query<
         (
             &mut CosmicEditor,
-            &mut CosmicBuffer,
-            &crate::DefaultAttrs,
+            &mut CosmicEditBuffer,
             &MaxChars,
             &MaxChars,
         ),
@@ -683,11 +682,8 @@ pub fn poll_wasm_paste(
     match inlet {
         Ok(inlet) => {
             let entity = inlet.entity;
-            if let Ok((mut editor, mut buffer, attrs, max_chars, max_lines)) =
-                editor_q.get_mut(entity)
-            {
+            if let Ok((mut editor, buffer, max_chars, max_lines)) = editor_q.get_mut(entity) {
                 let text = inlet.text;
-                let attrs = &attrs.0;
                 for c in text.chars() {
                     if max_chars.0 == 0 || buffer.get_text().len() < max_chars.0 {
                         if c == 0xA as char {
diff --git a/src/lib.rs b/src/lib.rs
index 1568a32..8052432 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11,44 +11,8 @@
 //!
 //!   *Warning: This plugin is currently in early development, and its API is subject to change.*
 //!
-//! ```
-//! use bevy::prelude::*;
-//! use bevy_cosmic_edit::*;
-//!
-//! fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
-//!     commands.spawn(Camera2dBundle::default());
-//!
-//!     // Text attributes
-//!     let font_size = 16.0;
-//!     let line_height = 18.0;
-//!     let attrs = Attrs::new()
-//!         .family(Family::Monospace)
-//!         .color(Color::DARK_GRAY.to_cosmic())
-//!         .weight(FontWeight::BOLD);
-//!
-//!     // Spawning
-//!     commands.spawn(CosmicEditBundle {
-//!         buffer: CosmicBuffer::new(&mut font_system, Metrics::new(font_size, line_height))
-//!             .with_text(&mut font_system, "Hello, Cosmic!", attrs),
-//!         sprite_bundle: SpriteBundle {
-//!             sprite: Sprite {
-//!                 custom_size: Some(Vec2::new(300.0, 40.0)),
-//!                 ..default()
-//!             },
-//!             ..default()
-//!         },
-//!         ..default()
-//!     });
-//! }
-//!
-//! fn main() {
-//!     App::new()
-//!         .add_plugins(DefaultPlugins)
-//!         .add_plugins(CosmicEditPlugin::default())
-//!         .add_systems(Startup, setup)
-//!         .add_systems(Update, change_active_editor_sprite)
-//!         .run();
-//! }
+//! ```rust,no_run
+#![doc = include_str!("../examples/basic_ui.rs")]
 //! ```
 //!
 //! Check the examples folder for much more!
@@ -70,7 +34,9 @@
 //!
 //! | bevy   | bevy_cosmic_edit |
 //! | ------ | ---------------- |
-//! | 0.13.0 | 0.16 - latest    |
+//! | 0.15.0 | 0.26 - latest    |
+//! | 0.14.0 | 0.21 - 0.25      |
+//! | 0.13.0 | 0.16 - 0.20      |
 //! | 0.12.* | 0.15             |
 //! | 0.11.* | 0.8 - 0.14       |
 //!
@@ -82,199 +48,66 @@
 //! MIT or Apache-2.0
 #![allow(clippy::type_complexity)]
 
+pub mod prelude {
+    // external re-exports
+    pub(crate) use bevy::prelude::*;
+    pub(crate) use bevy::text::SwashCache;
+    #[cfg_attr(not(doc), allow(unused_imports))]
+    pub(crate) use cosmic_text::Buffer;
+
+    // internal re-exports
+    pub(crate) use crate::buffer::BufferExtras as _;
+    pub(crate) use crate::cosmic_text;
+    pub(crate) use crate::primary::CosmicRenderOutput;
+    pub(crate) use crate::utils::*;
+
+    // public internal re-exports
+    pub use crate::buffer::CosmicEditBuffer; // todo: migrate to builtin bevy CosmicBuffer
+    pub use crate::cosmic_edit::CosmicFontSystem; // todo: migrate to using builtin bevy cosmic font system
+    pub use crate::cosmic_edit::{CosmicEditor, DefaultAttrs, ReadOnly};
+    pub use crate::focus::FocusedWidget;
+    pub use crate::primary::{CosmicEditPlugin, CosmicFontConfig, CosmicPrimaryCamera};
+    pub use crate::render_targets::{TextEdit, TextEdit2d};
+    pub use crate::utils::{
+        change_active_editor_sprite, change_active_editor_ui, deselect_editor_on_esc,
+        print_editor_text, ColorExtras as _,
+    };
+    #[doc(no_inline)]
+    pub use bevy::text::cosmic_text::{
+        Color as CosmicColor, Style as FontStyle, Weight as FontWeight,
+    };
+}
+
+pub use bevy::text::cosmic_text;
+
+pub use primary::{CosmicEditPlugin, CosmicFontConfig, CosmicPrimaryCamera};
+/// Contains the library global important types you probably want to explore first
+mod primary;
+
+pub use buffer::CosmicEditBuffer;
 mod buffer;
+pub use cosmic_edit::{
+    CosmicBackgroundColor, CosmicBackgroundImage, CosmicEditor, CosmicFontSystem, CosmicTextAlign,
+    CosmicWrap, CursorColor, DefaultAttrs, MaxChars, MaxLines, ReadOnly, ScrollEnabled,
+    SelectedTextColor, SelectionColor,
+};
 mod cosmic_edit;
+pub use cursor::{CursorPluginDisabled, HoverCursor, TextHoverIn, TextHoverOut};
 mod cursor;
+pub use events::CosmicTextChanged;
 mod events;
+pub use focus::FocusedWidget;
 mod focus;
+pub use input::InputSet;
 mod input;
+pub use password::Password;
 mod password;
+pub use placeholder::Placeholder;
 mod placeholder;
 mod render;
+pub use user_select::UserSelectNone;
 mod user_select;
-mod util;
+pub mod utils;
 mod widget;
-
-use std::{path::PathBuf, time::Duration};
-
-use bevy::prelude::*;
-
-pub use buffer::*;
-pub use cosmic_edit::*;
-#[doc(no_inline)]
-pub use cosmic_text::{self, Color as CosmicColor, Style as FontStyle, Weight as FontWeight};
-pub use cursor::*;
-pub use events::*;
-pub use focus::*;
-pub use input::*;
-pub use password::*;
-pub use placeholder::*;
-pub use render::*;
-pub use user_select::*;
-pub use util::*;
-pub use widget::*;
-
-/// Plugin struct that adds systems and initializes resources related to cosmic edit functionality.
-#[derive(Default)]
-pub struct CosmicEditPlugin {
-    pub font_config: CosmicFontConfig,
-}
-
-impl Plugin for CosmicEditPlugin {
-    fn build(&self, app: &mut App) {
-        let font_system = create_cosmic_font_system(self.font_config.clone());
-
-        app.add_plugins((
-            BufferPlugin,
-            RenderPlugin,
-            WidgetPlugin,
-            InputPlugin,
-            FocusPlugin,
-            CursorPlugin,
-            PlaceholderPlugin,
-            PasswordPlugin,
-            EventsPlugin,
-            UserSelectPlugin,
-        ))
-        .insert_resource(CosmicFontSystem(font_system));
-
-        app.register_type::<CosmicWrap>()
-            .register_type::<CosmicTextAlign>()
-            .register_type::<XOffset>()
-            .register_type::<CosmicBackgroundImage>()
-            .register_type::<CosmicBackgroundColor>()
-            .register_type::<CursorColor>()
-            .register_type::<SelectionColor>()
-            .register_type::<MaxLines>()
-            .register_type::<MaxChars>()
-            .register_type::<CosmicSource>()
-            .register_type::<HoverCursor>();
-
-        #[cfg(target_arch = "wasm32")]
-        {
-            let (tx, rx) = crossbeam_channel::bounded::<WasmPaste>(1);
-            app.insert_resource(WasmPasteAsyncChannel { tx, rx })
-                .add_systems(Update, poll_wasm_paste);
-        }
-    }
-}
-
-/// Attach to primary camera, and enable the `multicam` feature to use multiple cameras.
-/// Will panic if no Camera's without this component exist and the `multicam` feature is enabled.
-///
-/// A very basic example which doesn't panic:
-/// ```rust
-/// use bevy::prelude::*;
-/// use bevy_cosmic_edit::CosmicPrimaryCamera;
-///
-/// fn main() {
-///   App::new()
-///     .add_plugins((
-///       DefaultPlugins,
-///       bevy_cosmic_edit::CosmicEditPlugin::default(),
-///     ))
-///     .add_systems(Startup, setup)
-///     .run();
-/// }
-///
-/// fn setup(mut commands: Commands) {
-///   commands.spawn((Camera3dBundle::default(), CosmicPrimaryCamera));
-///   commands.spawn(Camera3dBundle {
-///     camera: Camera {
-///       order: 2,
-///       ..default()
-///     },
-///     ..default()
-///   });
-/// }
-/// ```
-#[derive(Component, Debug, Default)]
-pub struct CosmicPrimaryCamera;
-
-/// Resource struct that holds configuration options for cosmic fonts.
-#[derive(Resource, Clone)]
-pub struct CosmicFontConfig {
-    pub fonts_dir_path: Option<PathBuf>,
-    pub font_bytes: Option<Vec<&'static [u8]>>,
-    /// If [false], some characters (esspecially Unicode emojies) might not load properly
-    /// Caution: this can be relatively slow
-    pub load_system_fonts: bool,
-}
-
-impl Default for CosmicFontConfig {
-    fn default() -> Self {
-        let fallback_font = include_bytes!("./font/FiraMono-Regular-subset.ttf");
-        Self {
-            load_system_fonts: true,
-            font_bytes: Some(vec![fallback_font]),
-            fonts_dir_path: None,
-        }
-    }
-}
-
-fn create_cosmic_font_system(cosmic_font_config: CosmicFontConfig) -> cosmic_text::FontSystem {
-    let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
-    let mut db = cosmic_text::fontdb::Database::new();
-    if let Some(dir_path) = cosmic_font_config.fonts_dir_path.clone() {
-        db.load_fonts_dir(dir_path);
-    }
-    if let Some(custom_font_data) = &cosmic_font_config.font_bytes {
-        for elem in custom_font_data {
-            db.load_font_data(elem.to_vec());
-        }
-    }
-    if cosmic_font_config.load_system_fonts {
-        db.load_system_fonts();
-    }
-    cosmic_text::FontSystem::new_with_locale_and_db(locale, db)
-}
-
-#[cfg(test)]
-mod tests {
-    use bevy::input::keyboard::KeyboardInput;
-
-    use crate::*;
-
-    use self::buffer::CosmicBuffer;
-
-    fn test_spawn_cosmic_edit_system(
-        mut commands: Commands,
-        mut font_system: ResMut<CosmicFontSystem>,
-    ) {
-        let attrs = cosmic_text::Attrs::new();
-        commands.spawn(CosmicEditBundle {
-            buffer: CosmicBuffer::new(&mut font_system, cosmic_text::Metrics::new(20., 20.))
-                .with_rich_text(&mut font_system, vec![("Blah", attrs)], attrs),
-            ..Default::default()
-        });
-    }
-
-    #[test]
-    fn test_spawn_cosmic_edit() {
-        let mut app = App::new();
-        app.add_plugins(TaskPoolPlugin::default());
-        app.add_plugins(AssetPlugin::default());
-        app.insert_resource(CosmicFontSystem(create_cosmic_font_system(
-            CosmicFontConfig::default(),
-        )));
-        app.add_systems(Update, test_spawn_cosmic_edit_system);
-
-        let input = ButtonInput::<KeyCode>::default();
-        app.insert_resource(input);
-        let mouse_input: ButtonInput<MouseButton> = ButtonInput::<MouseButton>::default();
-        app.insert_resource(mouse_input);
-
-        app.add_event::<KeyboardInput>();
-
-        app.update();
-
-        let mut text_nodes_query = app.world_mut().query::<&CosmicBuffer>();
-        for cosmic_editor in text_nodes_query.iter(&app.world()) {
-            insta::assert_debug_snapshot!(cosmic_editor
-                .lines
-                .iter()
-                .map(|line| line.text())
-                .collect::<Vec<_>>());
-        }
-    }
-}
+pub(crate) use render_targets::{ChangedCosmicWidgetSize, CosmicWidgetSize};
+pub mod render_targets;
diff --git a/src/password.rs b/src/password.rs
index 54a62c7..5cd1966 100644
--- a/src/password.rs
+++ b/src/password.rs
@@ -1,5 +1,7 @@
-use crate::*;
-use bevy::prelude::*;
+use crate::{
+    cosmic_edit::DefaultAttrs, focus::FocusSet, placeholder::Placeholder, prelude::*,
+    render::RenderSet,
+};
 use cosmic_text::{Cursor, Edit, Selection, Shaping};
 use unicode_segmentation::UnicodeSegmentation;
 
@@ -7,28 +9,31 @@ pub(crate) struct PasswordPlugin;
 
 /// System set for password blocking systems. Runs in [`PostUpdate`]
 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
-pub struct PasswordSet;
+pub(crate) struct PasswordSet;
 
 impl Plugin for PasswordPlugin {
     fn build(&self, app: &mut App) {
-        app.add_systems(PreUpdate, (hide_password_text.before(input_mouse),))
-            .add_systems(
-                Update,
-                (restore_password_text
-                    .before(kb_input_text)
-                    .after(kb_move_cursor),),
-            )
-            .add_systems(
-                PostUpdate,
-                (
-                    hide_password_text.before(RenderSet).in_set(PasswordSet),
-                    restore_password_text.before(FocusSet).after(RenderSet),
-                ),
-            );
+        app.add_systems(
+            PreUpdate,
+            (hide_password_text.before(crate::input::input_mouse),),
+        )
+        .add_systems(
+            Update,
+            (restore_password_text
+                .before(crate::input::kb_input_text)
+                .after(crate::input::kb_move_cursor),),
+        )
+        .add_systems(
+            PostUpdate,
+            (
+                hide_password_text.before(RenderSet).in_set(PasswordSet),
+                restore_password_text.before(FocusSet).after(RenderSet),
+            ),
+        );
     }
 }
 
-/// Component to be added to an entity with a [`CosmicEditBundle`] to block contents with a
+/// Component to be added to an entity with a [`CosmicEditBuffer`] to block contents with a
 /// password blocker glyph
 ///
 /// ```
@@ -37,16 +42,12 @@ impl Plugin for PasswordPlugin {
 /// #
 /// # fn setup(mut commands: Commands) {
 /// // Create a new cosmic bundle
-/// commands.spawn((CosmicEditBundle {
-///     sprite_bundle: SpriteBundle {
-///         sprite: Sprite {
-///             custom_size: Some(Vec2::new(300.0, 40.0)),
-///             ..default()
-///         },
+/// commands.spawn((
+///     CosmicEditBuffer::default(),
+///     Sprite {
+///         custom_size: Some(Vec2::new(300.0, 40.0)),
 ///         ..default()
 ///     },
-///     ..default()
-///     },
 ///     Password::default()
 /// ));
 /// # }
@@ -79,10 +80,11 @@ impl Password {
     }
 }
 
+/// Stores [`CosmicEditBuffer`] contents into [`Password.real_text`]
 fn hide_password_text(
     mut q: Query<(
         &mut Password,
-        &mut CosmicBuffer,
+        &mut CosmicEditBuffer,
         &DefaultAttrs,
         Option<&mut CosmicEditor>,
         Option<&Placeholder>,
@@ -92,6 +94,7 @@ fn hide_password_text(
     for (mut password, mut buffer, attrs, editor_opt, placeholder_opt) in q.iter_mut() {
         if let Some(placeholder) = placeholder_opt {
             if placeholder.is_active() {
+                // doesn't override placeholder
                 continue;
             }
         }
@@ -157,10 +160,11 @@ fn hide_password_text(
     }
 }
 
+/// Replaces [`CosmicEditBuffer`] contents with [`Password.real_text`]
 fn restore_password_text(
     mut q: Query<(
         &Password,
-        &mut CosmicBuffer,
+        &mut CosmicEditBuffer,
         &DefaultAttrs,
         Option<&mut CosmicEditor>,
         Option<&Placeholder>,
diff --git a/src/placeholder.rs b/src/placeholder.rs
index a7b4797..55d6489 100644
--- a/src/placeholder.rs
+++ b/src/placeholder.rs
@@ -1,24 +1,24 @@
-use crate::*;
-use bevy::prelude::*;
+use crate::{
+    cosmic_edit::DefaultAttrs, events::CosmicTextChanged, input::InputSet, prelude::*,
+    render::RenderSet,
+};
 use cosmic_text::{Attrs, Edit};
 
-/// Component to be added to an entity with a [`CosmicEditBundle`] add placeholder text
+/// Component to be added to an entity with a [`CosmicEditBuffer`] add placeholder text
 ///
 /// ```
 /// # use bevy::prelude::*;
-/// # use bevy_cosmic_edit::*;
+/// # use bevy_cosmic_edit::prelude::*;
+/// use bevy_cosmic_edit::Placeholder;
+///
 /// # fn setup(mut commands: Commands) {
-/// commands.spawn((CosmicEditBundle {
-///     sprite_bundle: SpriteBundle {
-///         sprite: Sprite {
-///             custom_size: Some(Vec2::new(300.0, 40.0)),
-///             ..default()
-///         },
+/// commands.spawn((
+///     CosmicEditBuffer::default(),
+///     Sprite {
+///         custom_size: Some(Vec2::new(300.0, 40.0)),
 ///         ..default()
 ///     },
-///     ..default()
-///     },
-///     Placeholder::new("Email", Attrs::new().color(Color::GRAY.to_cosmic())),
+///     Placeholder::new("Email", Attrs::new().color(Color::from(bevy::color::palettes::css::GRAY).to_cosmic())),
 /// ));
 /// # }
 /// # fn main() {
@@ -71,7 +71,7 @@ impl Plugin for PlaceholderPlugin {
 }
 
 fn add_placeholder_to_buffer(
-    mut q: Query<(&mut CosmicBuffer, &mut Placeholder)>,
+    mut q: Query<(&mut CosmicEditBuffer, &mut Placeholder)>,
     mut font_system: ResMut<CosmicFontSystem>,
 ) {
     for (mut buffer, mut placeholder) in q.iter_mut() {
diff --git a/src/primary.rs b/src/primary.rs
new file mode 100644
index 0000000..e2e41f1
--- /dev/null
+++ b/src/primary.rs
@@ -0,0 +1,163 @@
+use std::path::PathBuf;
+
+use crate::prelude::*;
+
+/// Plugin struct that adds systems and initializes resources related to cosmic edit functionality.
+#[derive(Default)]
+pub struct CosmicEditPlugin {
+    pub font_config: CosmicFontConfig,
+}
+
+impl Plugin for CosmicEditPlugin {
+    fn build(&self, app: &mut App) {
+        trace!("Loading cosmic edit plugin");
+        let font_system = create_cosmic_font_system(self.font_config.clone());
+
+        app.add_plugins((
+            crate::cosmic_edit::plugin,
+            crate::buffer::BufferPlugin,
+            crate::render::RenderPlugin,
+            crate::widget::WidgetPlugin,
+            crate::input::InputPlugin,
+            crate::focus::FocusPlugin,
+            crate::cursor::CursorPlugin,
+            crate::placeholder::PlaceholderPlugin,
+            crate::password::PasswordPlugin,
+            crate::events::EventsPlugin,
+            crate::user_select::UserSelectPlugin,
+        ))
+        // TODO: Use the builtin bevy CosmicFontSystem
+        .insert_resource(crate::cosmic_edit::CosmicFontSystem(font_system));
+
+        app.register_type::<CosmicRenderOutput>();
+    }
+}
+
+/// Attach to primary camera, and enable the `multicam` feature to use multiple cameras.
+/// Will panic if no Camera's without this component exist and the `multicam` feature is enabled.
+///
+/// A very basic example which doesn't panic:
+/// ```rust,no_run
+/// use bevy::prelude::*;
+/// use bevy_cosmic_edit::prelude::*;
+///
+/// fn main() {
+///     App::new()
+///         .add_plugins((
+///             DefaultPlugins,
+///             CosmicEditPlugin::default(),
+///         ))
+///     .add_systems(Startup, setup)
+///     .run();
+/// }
+///
+/// fn setup(mut commands: Commands) {
+///     commands.spawn((Camera3d::default(), CosmicPrimaryCamera));
+///     commands.spawn((
+///         Camera3d::default(),
+///         Camera {
+///             order: 2,
+///             ..default()
+///         },
+///     ));
+/// }
+/// ```
+#[derive(Component, Debug, Default)]
+pub struct CosmicPrimaryCamera;
+
+#[cfg(feature = "multicam")]
+pub(crate) type CameraFilter = With<CosmicPrimaryCamera>;
+
+#[cfg(not(feature = "multicam"))]
+pub(crate) type CameraFilter = ();
+
+/// Resource struct that holds configuration options for cosmic fonts.
+#[derive(Resource, Clone)]
+pub struct CosmicFontConfig {
+    pub fonts_dir_path: Option<PathBuf>,
+    pub font_bytes: Option<Vec<&'static [u8]>>,
+    /// If [false], some characters (esspecially Unicode emojies) might not load properly
+    /// Caution: this can be relatively slow
+    pub load_system_fonts: bool,
+}
+
+impl Default for CosmicFontConfig {
+    fn default() -> Self {
+        let fallback_font = include_bytes!("./font/FiraMono-Regular-subset.ttf");
+        Self {
+            load_system_fonts: true,
+            font_bytes: Some(vec![fallback_font]),
+            fonts_dir_path: None,
+        }
+    }
+}
+
+/// Used to ferry data from a [`CosmicEditBuffer`]
+#[derive(Component, Reflect, Default, Debug, Deref)]
+pub(crate) struct CosmicRenderOutput(pub(crate) Handle<Image>);
+
+fn create_cosmic_font_system(cosmic_font_config: CosmicFontConfig) -> cosmic_text::FontSystem {
+    let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
+    let mut db = cosmic_text::fontdb::Database::new();
+    if let Some(dir_path) = cosmic_font_config.fonts_dir_path.clone() {
+        db.load_fonts_dir(dir_path);
+    }
+    if let Some(custom_font_data) = &cosmic_font_config.font_bytes {
+        for elem in custom_font_data {
+            db.load_font_data(elem.to_vec());
+        }
+    }
+    if cosmic_font_config.load_system_fonts {
+        db.load_system_fonts();
+    }
+    cosmic_text::FontSystem::new_with_locale_and_db(locale, db)
+}
+
+#[cfg(test)]
+mod tests {
+    use bevy::input::keyboard::KeyboardInput;
+
+    use super::*;
+
+    fn test_spawn_cosmic_edit_system(
+        mut commands: Commands,
+        mut font_system: ResMut<CosmicFontSystem>,
+    ) {
+        let attrs = cosmic_text::Attrs::new();
+        commands.spawn(
+            CosmicEditBuffer::new(&mut font_system, cosmic_text::Metrics::new(20., 20.))
+                .with_rich_text(&mut font_system, vec![("Blah", attrs)], attrs),
+        );
+    }
+
+    #[test]
+    fn test_spawn_cosmic_edit() {
+        let mut app = App::new();
+        app.add_plugins(TaskPoolPlugin::default());
+        app.add_plugins(AssetPlugin::default());
+        app.insert_resource(CosmicFontSystem(create_cosmic_font_system(
+            CosmicFontConfig::default(),
+        )));
+        app.add_systems(Update, test_spawn_cosmic_edit_system);
+
+        // todo: these lines probably won't do anything now,
+        // maybe we should test for something different?
+        let input = ButtonInput::<KeyCode>::default();
+        app.insert_resource(input);
+        let mouse_input: ButtonInput<MouseButton> = ButtonInput::<MouseButton>::default();
+        app.insert_resource(mouse_input);
+
+        app.add_event::<KeyboardInput>();
+
+        app.update();
+
+        let mut text_nodes_query = app.world_mut().query::<&CosmicEditBuffer>();
+        for cosmic_editor in text_nodes_query.iter(app.world()) {
+            insta::assert_debug_snapshot!(cosmic_editor
+                .lines
+                .iter()
+                .map(|line| line.text())
+                .collect::<Vec<_>>());
+        }
+    }
+}
diff --git a/src/render.rs b/src/render.rs
index 0fa8a80..ecfe228 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -1,32 +1,30 @@
-use crate::*;
-use bevy::{prelude::*, render::render_resource::Extent3d};
-use cosmic_text::{Color, Edit, SwashCache};
+use crate::widget::CosmicPadding;
+use crate::{cosmic_edit::ReadOnly, prelude::*, widget::WidgetSet};
+use crate::{cosmic_edit::*, CosmicWidgetSize};
+use bevy::render::render_resource::Extent3d;
+use cosmic_text::{Color, Edit};
 use image::{imageops::FilterType, GenericImageView};
 
 /// System set for cosmic text rendering systems. Runs in [`PostUpdate`]
 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
-pub struct RenderSet;
+pub(crate) struct RenderSet;
 
 pub(crate) struct RenderPlugin;
 
 impl Plugin for RenderPlugin {
     fn build(&self, app: &mut App) {
-        app.insert_resource(SwashCacheState {
-            swash_cache: SwashCache::new(),
-        })
-        .add_systems(Update, blink_cursor)
-        .add_systems(
+        if !app.world().contains_resource::<SwashCache>() {
+            app.insert_resource(SwashCache::default());
+        } else {
+            debug!("Skipping inserting `SwashCache` resource");
+        }
+        app.add_systems(Update, blink_cursor).add_systems(
             PostUpdate,
             (render_texture,).in_set(RenderSet).after(WidgetSet),
         );
     }
 }
 
-#[derive(Resource)]
-pub(crate) struct SwashCacheState {
-    pub swash_cache: SwashCache,
-}
-
 pub(crate) fn blink_cursor(mut q: Query<&mut CosmicEditor, Without<ReadOnly>>, time: Res<Time>) {
     for mut e in q.iter_mut() {
         e.cursor_timer.tick(time.delta());
@@ -77,18 +75,20 @@ fn draw_pixel(buffer: &mut [u8], width: i32, height: i32, x: i32, y: i32, color:
     buffer[offset + 3] = (out.alpha * 255.0) as u8;
 }
 
+/// Renders to the [CosmicRenderOutput]
+#[allow(unused_mut)] // for .set_redraw(false) commented out
 fn render_texture(
     mut query: Query<(
         Option<&mut CosmicEditor>,
-        &mut CosmicBuffer,
+        &mut CosmicEditBuffer,
         &DefaultAttrs,
         &CosmicBackgroundImage,
         &CosmicBackgroundColor,
         &CursorColor,
         &SelectionColor,
         Option<&SelectedTextColor>,
-        &Handle<Image>,
-        &CosmicWidgetSize,
+        &CosmicRenderOutput,
+        CosmicWidgetSize,
         &CosmicPadding,
         &XOffset,
         Option<&ReadOnly>,
@@ -96,7 +96,7 @@ fn render_texture(
     )>,
     mut font_system: ResMut<CosmicFontSystem>,
     mut images: ResMut<Assets<Image>>,
-    mut swash_cache_state: ResMut<SwashCacheState>,
+    mut swash_cache_state: ResMut<SwashCache>,
 ) {
     for (
         editor,
@@ -115,15 +115,28 @@ fn render_texture(
         position,
     ) in query.iter_mut()
     {
+        let Ok(size) = size.logical_size() else {
+            continue;
+        };
+
+        // avoids a panic
+        if size.x == 0. || size.y == 0. {
+            debug!(
+                message = "Size of buffer is zero, skipping",
+                // once = "This log only appears once"
+            );
+            continue;
+        }
+
         // Draw background
-        let mut pixels = vec![0; size.0.x as usize * size.0.y as usize * 4];
+        let mut pixels = vec![0; size.x as usize * size.y as usize * 4];
         if let Some(bg_image) = background_image.0.clone() {
             if let Some(image) = images.get(&bg_image) {
                 let mut dynamic_image = image.clone().try_into_dynamic().unwrap();
-                if image.size().x != size.0.x as u32 || image.size().y != size.0.y as u32 {
+                if image.size() != size.as_uvec2() {
                     dynamic_image = dynamic_image.resize_to_fill(
-                        size.0.x as u32,
-                        size.0.y as u32,
+                        size.x as u32,
+                        size.y as u32,
                         FilterType::Triangle,
                     );
                 }
@@ -162,8 +175,8 @@ fn render_texture(
                 for col in 0..w as i32 {
                     draw_pixel(
                         &mut pixels,
-                        size.0.x as i32,
-                        size.0.y as i32,
+                        size.x as i32,
+                        size.y as i32,
                         x + col + padding.x.max(min_pad) as i32 - x_offset.left as i32,
                         y + row + padding.y as i32,
                         color,
@@ -195,34 +208,38 @@ fn render_texture(
 
             editor.draw(
                 &mut font_system.0,
-                &mut swash_cache_state.swash_cache,
+                &mut swash_cache_state.0,
                 font_color,
                 cursor_color,
                 selection_color,
                 selected_text_color,
                 draw_closure,
             );
-            editor.set_redraw(false);
+            // TODO: Performance optimization, read all possible render-input
+            // changes and only redraw if necessary
+            // editor.set_redraw(false);
         } else {
             if !buffer.redraw() {
                 continue;
             }
             buffer.draw(
                 &mut font_system.0,
-                &mut swash_cache_state.swash_cache,
+                &mut swash_cache_state.0,
                 font_color,
                 draw_closure,
             );
-            buffer.set_redraw(false);
+            // TODO: Performance optimization, read all possible render-input
+            // changes and only redraw if necessary
+            // buffer.set_redraw(false);
         }
 
-        if let Some(prev_image) = images.get_mut(canvas) {
+        if let Some(prev_image) = images.get_mut(&canvas.0) {
             prev_image.data.clear();
             // Updates the stored asset image with the computed pixels
             prev_image.data.extend_from_slice(pixels.as_slice());
             prev_image.resize(Extent3d {
-                width: size.0.x as u32,
-                height: size.0.y as u32,
+                width: size.x as u32,
+                height: size.y as u32,
                 depth_or_array_layers: 1,
             });
         }
diff --git a/src/render_targets.rs b/src/render_targets.rs
new file mode 100644
index 0000000..d7ddca2
--- /dev/null
+++ b/src/render_targets.rs
@@ -0,0 +1,242 @@
+//! Generalizes over render target implementations.
+//!
+//! ## Sprite:
+//! Requires [`Sprite`] component and requires [`Sprite.custom_size`] to be Some( non-zero )
+//!
+//! ## UI:
+//! Requires [`ImageNode`] for rendering and [`Button`] for [`Interaction`]s
+// TODO: Remove `CosmicWidgetSize`?
+
+use bevy::{
+    ecs::query::{QueryData, QueryFilter},
+    window::SystemCursorIcon,
+    winit::cursor::CursorIcon,
+};
+
+use crate::{prelude::*, primary::CameraFilter, HoverCursor, TextHoverIn, TextHoverOut};
+
+/// The top level UI text edit component
+///
+/// Adding [`TextEdit`] will pull in the required components for setting up
+/// a text edit widget, notably [`CosmicEditBuffer`]
+///
+/// Hopefully this API will eventually mirror [`bevy::prelude::Text`].
+/// See [`CosmicEditBuffer`] for more information.
+#[derive(Component)]
+#[require(ImageNode, Button, CosmicEditBuffer)]
+pub struct TextEdit;
+
+/// The top-level 2D text edit component
+///
+/// Adding [`TextEdit2d`] will pull in the required components for setting up
+/// a 2D text editor using a [`Sprite`] with [`Sprite.custom_size`] set,
+/// to set the size of the text editor add a [`Sprite`] component with
+/// [`Sprite.custom_size`] set.
+///
+/// Hopefully this API will eventually mirror [`bevy::prelude::Text2d`].
+/// See [`CosmicEditBuffer`] for more information.
+#[derive(Component)]
+#[require(Sprite, CosmicEditBuffer)]
+pub struct TextEdit2d;
+
+/// TODO: Generalize implementations depending on this
+/// and add 3D
+pub(crate) enum SourceType {
+    Ui,
+    Sprite,
+}
+
+#[derive(Debug)]
+pub(crate) enum RenderTargetError {
+    /// When no recognized [`SourceType`] could be found
+    NoTargetsAvailable,
+
+    /// When more than one [`SourceType`] was detected.
+    ///
+    /// This will always be thrown if more than one target type is available,
+    /// there is no propritisation procedure as this should be considered a
+    /// logic error.
+    MoreThanOneTargetAvailable,
+
+    /// When a [`RenderTypeScan`] was successfully conducted yet the expected
+    /// [required component/s](https://docs.rs/bevy/latest/bevy/ecs/prelude/trait.Component.html#required-components)
+    /// were not found
+    RequiredComponentNotAvailable,
+
+    /// When using [`SourceType::Sprite`], you must set [`Sprite.custom_size`]
+    SpriteCustomSizeNotSet,
+}
+
+type Result<T> = core::result::Result<T, RenderTargetError>;
+
+#[derive(QueryData)]
+pub(crate) struct RenderTypeScan {
+    is_sprite: Has<TextEdit2d>,
+    is_ui: Has<TextEdit>,
+}
+
+impl RenderTypeScanItem<'_> {
+    pub fn scan(&self) -> Result<SourceType> {
+        match (self.is_sprite, self.is_ui) {
+            (true, false) => Ok(SourceType::Sprite),
+            (false, true) => Ok(SourceType::Ui),
+            (true, true) => Err(RenderTargetError::MoreThanOneTargetAvailable),
+            (false, false) => Err(RenderTargetError::NoTargetsAvailable),
+        }
+    }
+}
+
+/// Query the size of a widget using any [`SourceType`]
+#[derive(QueryData)]
+pub(crate) struct CosmicWidgetSize {
+    scan: RenderTypeScan,
+    sprite: Option<&'static Sprite>,
+    ui: Option<&'static ComputedNode>,
+}
+
+/// Allows `.scan()` to be called on a [`CosmicWidgetSize`] through deref
+impl<'s> std::ops::Deref for CosmicWidgetSizeItem<'s> {
+    type Target = RenderTypeScanItem<'s>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.scan
+    }
+}
+
+/// An optimization [`QueryFilter`](bevy::ecs::query::QueryFilter)
+#[derive(QueryFilter)]
+pub(crate) struct ChangedCosmicWidgetSize {
+    sprite: Changed<Sprite>,
+    ui: Changed<ComputedNode>,
+}
+
+pub(crate) trait NodeSizeExt {
+    fn logical_size(&self) -> Vec2;
+}
+
+impl NodeSizeExt for ComputedNode {
+    fn logical_size(&self) -> Vec2 {
+        self.size() * self.inverse_scale_factor()
+    }
+}
+
+impl CosmicWidgetSizeItem<'_> {
+    pub fn logical_size(&self) -> Result<Vec2> {
+        let source_type = self.scan.scan()?;
+        match source_type {
+            SourceType::Ui => {
+                let ui = self
+                    .ui
+                    .ok_or(RenderTargetError::RequiredComponentNotAvailable)?;
+                Ok(ui.logical_size())
+            }
+            SourceType::Sprite => {
+                let sprite = self
+                    .sprite
+                    .ok_or(RenderTargetError::RequiredComponentNotAvailable)?;
+                Ok(sprite
+                    .custom_size
+                    .ok_or(RenderTargetError::SpriteCustomSizeNotSet)?)
+            }
+        }
+    }
+}
+
+/// Function to find the location of the mouse cursor in a cosmic widget.
+/// Returns in logical pixels
+// TODO: Change this to use builtin `bevy::picking` instead
+pub(crate) fn get_node_cursor_pos(
+    window: &Window,
+    node_transform: &GlobalTransform,
+    size: Vec2,
+    source_type: SourceType,
+    camera: &Camera,
+    camera_transform: &GlobalTransform,
+) -> Option<Vec2> {
+    let node_translation = node_transform.affine().translation;
+    let node_bounds = Rect::new(
+        node_translation.x - size.x / 2.,
+        node_translation.y - size.y / 2.,
+        node_translation.x + size.x / 2.,
+        node_translation.y + size.y / 2.,
+    );
+
+    window.cursor_position().and_then(|pos| match source_type {
+        SourceType::Ui => {
+            if node_bounds.contains(pos) {
+                Some(Vec2::new(
+                    pos.x - node_bounds.min.x,
+                    pos.y - node_bounds.min.y,
+                ))
+            } else {
+                None
+            }
+        }
+        SourceType::Sprite => camera
+            .viewport_to_world_2d(camera_transform, pos)
+            .ok()
+            .and_then(|pos| {
+                if node_bounds.contains(pos) {
+                    Some(Vec2::new(
+                        pos.x - node_bounds.min.x,
+                        node_bounds.max.y - pos.y,
+                    ))
+                } else {
+                    None
+                }
+            }),
+    })
+}
+
+pub(crate) fn hover_sprites(
+    windows: Query<&Window, With<bevy::window::PrimaryWindow>>,
+    mut cosmic_edit_query: Query<
+        (&mut Sprite, &Visibility, &GlobalTransform, &HoverCursor),
+        With<CosmicEditBuffer>,
+    >,
+    camera_q: Query<(&Camera, &GlobalTransform), CameraFilter>,
+    mut hovered: Local<bool>,
+    mut last_hovered: Local<bool>,
+    mut evw_hover_in: EventWriter<TextHoverIn>,
+    mut evw_hover_out: EventWriter<TextHoverOut>,
+) {
+    *hovered = false;
+    if windows.iter().len() == 0 {
+        return;
+    }
+    let window = windows.single();
+    let (camera, camera_transform) = camera_q.single();
+
+    let mut icon = CursorIcon::System(SystemCursorIcon::Default);
+
+    for (sprite, visibility, node_transform, hover) in &mut cosmic_edit_query.iter_mut() {
+        if visibility == Visibility::Hidden {
+            continue;
+        }
+
+        let size = sprite.custom_size.unwrap_or(Vec2::ONE);
+        if crate::render_targets::get_node_cursor_pos(
+            window,
+            node_transform,
+            size,
+            SourceType::Sprite,
+            camera,
+            camera_transform,
+        )
+        .is_some()
+        {
+            *hovered = true;
+            icon = hover.0.clone();
+        }
+    }
+
+    if *last_hovered != *hovered {
+        if *hovered {
+            evw_hover_in.send(TextHoverIn(icon));
+        } else {
+            evw_hover_out.send(TextHoverOut);
+        }
+    }
+
+    *last_hovered = *hovered;
+}
diff --git a/src/snapshots/bevy_cosmic_edit__primary__tests__spawn_cosmic_edit.snap b/src/snapshots/bevy_cosmic_edit__primary__tests__spawn_cosmic_edit.snap
new file mode 100644
index 0000000..7acda1a
--- /dev/null
+++ b/src/snapshots/bevy_cosmic_edit__primary__tests__spawn_cosmic_edit.snap
@@ -0,0 +1,8 @@
+---
+source: src/primary.rs
+expression: "cosmic_editor.lines.iter().map(|line| line.text()).collect::<Vec<_>>()"
+snapshot_kind: text
+---
+[
+    "Blah",
+]
diff --git a/src/snapshots/bevy_cosmic_edit__tests__spawn_cosmic_edit.snap b/src/snapshots/bevy_cosmic_edit__tests__spawn_cosmic_edit.snap
deleted file mode 100644
index f8ca019..0000000
--- a/src/snapshots/bevy_cosmic_edit__tests__spawn_cosmic_edit.snap
+++ /dev/null
@@ -1,8 +0,0 @@
----
-source: crates/bevy_cosmic_edit/src/lib.rs
-assertion_line: 368
-expression: "node.editor.buffer().lines.iter().map(|line| line.text()).collect::<Vec<_>>()"
----
-[
-    "Blah",
-]
diff --git a/src/user_select.rs b/src/user_select.rs
index 1c573e7..af9e69e 100644
--- a/src/user_select.rs
+++ b/src/user_select.rs
@@ -1,5 +1,4 @@
-use crate::*;
-use bevy::prelude::*;
+use crate::{input::InputSet, prelude::*};
 use cosmic_text::Edit;
 
 pub(crate) struct UserSelectPlugin;
diff --git a/src/util.rs b/src/utils.rs
similarity index 63%
rename from src/util.rs
rename to src/utils.rs
index 7c5233d..12e9a4b 100644
--- a/src/util.rs
+++ b/src/utils.rs
@@ -1,6 +1,9 @@
 // Common functions for examples
-use crate::*;
-use bevy::{prelude::*, window::PrimaryWindow};
+use crate::{
+    cosmic_edit::ReadOnly, prelude::*, primary::CameraFilter, ChangedCosmicWidgetSize,
+    CosmicWidgetSize,
+};
+use bevy::{ecs::query::QueryData, window::PrimaryWindow};
 use cosmic_text::Edit;
 
 /// Trait for adding color conversion from [`bevy::prelude::Color`] to [`cosmic_text::Color`]
@@ -32,50 +35,6 @@ pub fn deselect_editor_on_esc(i: Res<ButtonInput<KeyCode>>, mut focus: ResMut<Fo
     }
 }
 
-/// Function to find the location of the mouse cursor in a cosmic widget
-pub fn get_node_cursor_pos(
-    window: &Window,
-    node_transform: &GlobalTransform,
-    size: Vec2,
-    is_ui_node: bool,
-    camera: &Camera,
-    camera_transform: &GlobalTransform,
-) -> Option<Vec2> {
-    let node_translation = node_transform.affine().translation;
-    let node_bounds = Rect::new(
-        node_translation.x - size.x / 2.,
-        node_translation.y - size.y / 2.,
-        node_translation.x + size.x / 2.,
-        node_translation.y + size.y / 2.,
-    );
-
-    window.cursor_position().and_then(|pos| {
-        if is_ui_node {
-            if node_bounds.contains(pos) {
-                Some(Vec2::new(
-                    pos.x - node_bounds.min.x,
-                    pos.y - node_bounds.min.y,
-                ))
-            } else {
-                None
-            }
-        } else {
-            camera
-                .viewport_to_world_2d(camera_transform, pos)
-                .and_then(|pos| {
-                    if node_bounds.contains(pos) {
-                        Some(Vec2::new(
-                            pos.x - node_bounds.min.x,
-                            node_bounds.max.y - pos.y,
-                        ))
-                    } else {
-                        None
-                    }
-                })
-        }
-    })
-}
-
 /// System to allow focus on click for sprite widgets
 pub fn change_active_editor_sprite(
     mut commands: Commands,
@@ -83,9 +42,9 @@ pub fn change_active_editor_sprite(
     buttons: Res<ButtonInput<MouseButton>>,
     mut cosmic_edit_query: Query<
         (&mut Sprite, &GlobalTransform, &Visibility, Entity),
-        (With<CosmicBuffer>, Without<ReadOnly>),
+        (With<CosmicEditBuffer>, Without<ReadOnly>),
     >,
-    camera_q: Query<(&Camera, &GlobalTransform)>,
+    camera_q: Query<(&Camera, &GlobalTransform), CameraFilter>,
 ) {
     let window = windows.single();
     let (camera, camera_transform) = camera_q.single();
@@ -100,7 +59,7 @@ pub fn change_active_editor_sprite(
             let x_max = node_transform.affine().translation.x + size.x / 2.;
             let y_max = node_transform.affine().translation.y + size.y / 2.;
             if let Some(pos) = window.cursor_position() {
-                if let Some(pos) = camera.viewport_to_world_2d(camera_transform, pos) {
+                if let Ok(pos) = camera.viewport_to_world_2d(camera_transform, pos) {
                     if x_min < pos.x && pos.x < x_max && y_min < pos.y && pos.y < y_max {
                         commands.insert_resource(FocusedWidget(Some(entity)))
                     };
@@ -112,15 +71,19 @@ pub fn change_active_editor_sprite(
 
 /// System to allow focus on click for UI widgets
 pub fn change_active_editor_ui(
-    mut commands: Commands,
     mut interaction_query: Query<
-        (&Interaction, &CosmicSource),
-        (Changed<Interaction>, Without<ReadOnly>),
+        (&Interaction, Entity),
+        (
+            Changed<Interaction>,
+            Without<ReadOnly>,
+            With<CosmicEditBuffer>,
+        ),
     >,
+    mut focussed_widget: ResMut<FocusedWidget>,
 ) {
-    for (interaction, source) in interaction_query.iter_mut() {
+    for (interaction, entity) in interaction_query.iter_mut() {
         if let Interaction::Pressed = interaction {
-            commands.insert_resource(FocusedWidget(Some(source.0)));
+            *focussed_widget = FocusedWidget(Some(entity));
         }
     }
 }
@@ -145,15 +108,44 @@ pub fn print_editor_text(
     }
 }
 
+/// Quick utility to print the name of an entity if available
+#[derive(QueryData)]
+struct DebugName {
+    name: Option<&'static Name>,
+    entity: Entity,
+}
+
+impl std::fmt::Debug for DebugNameItem<'_> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        // write!(f, "{:?} {:?}", self.name, self.entity)
+        match self.name {
+            Some(name) => write!(f, "DebugName::Name({:?})", name),
+            None => write!(f, "Entity({:?})", self.entity),
+        }
+    }
+}
+
+/// Debug print the size of all editors
+#[allow(dead_code)]
+#[allow(private_interfaces)]
+pub fn print_editor_sizes(
+    editors: Query<(CosmicWidgetSize, DebugName), (With<CosmicEditor>, ChangedCosmicWidgetSize)>,
+) {
+    for (size, name) in editors.iter() {
+        println!("Size of editor {:?} is: {:?}", name, size.logical_size());
+    }
+}
+
 /// Calls javascript to get the current timestamp
 #[cfg(target_arch = "wasm32")]
-pub fn get_timestamp() -> f64 {
+pub(crate) fn get_timestamp() -> f64 {
     js_sys::Date::now()
 }
 
 /// Utility function to get the current unix timestamp
 #[cfg(not(target_arch = "wasm32"))]
-pub fn get_timestamp() -> f64 {
+#[allow(dead_code)] // idk why this isn't used
+pub(crate) fn get_timestamp() -> f64 {
     use std::time::SystemTime;
     use std::time::UNIX_EPOCH;
     let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
diff --git a/src/widget.rs b/src/widget.rs
index c6410af..1e2d1d3 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -1,11 +1,18 @@
-use crate::*;
-use bevy::{prelude::*, window::PrimaryWindow};
+use crate::buffer::get_x_offset_center;
+use crate::buffer::get_y_offset_center;
+use crate::cosmic_edit::CosmicTextAlign;
+use crate::cosmic_edit::CosmicWrap;
+use crate::cosmic_edit::XOffset;
+use crate::input::InputSet;
+use crate::prelude::*;
+use crate::ChangedCosmicWidgetSize;
+use crate::CosmicWidgetSize;
 use cosmic_text::Affinity;
 use cosmic_text::Edit;
 
 /// System set for cosmic text layout systems. Runs in [`PostUpdate`]
 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
-pub struct WidgetSet;
+pub(crate) struct WidgetSet;
 
 pub(crate) struct WidgetPlugin;
 
@@ -15,8 +22,7 @@ impl Plugin for WidgetPlugin {
             .add_systems(
                 PostUpdate,
                 (
-                    (new_image_from_default, set_sprite_size_from_ui),
-                    set_widget_size,
+                    new_image_from_default,
                     set_buffer_size,
                     set_padding,
                     set_x_offset,
@@ -25,8 +31,7 @@ impl Plugin for WidgetPlugin {
                     .in_set(WidgetSet)
                     .after(TransformSystem::TransformPropagate),
             )
-            .register_type::<CosmicPadding>()
-            .register_type::<CosmicWidgetSize>();
+            .register_type::<CosmicPadding>();
     }
 }
 
@@ -34,14 +39,14 @@ impl Plugin for WidgetPlugin {
 /// This is set programatically, not for user modification.
 /// To set a widget's padding, use [`CosmicTextAlign`]
 #[derive(Component, Reflect, Default, Deref, DerefMut, Debug)]
-pub struct CosmicPadding(pub Vec2);
+pub(crate) struct CosmicPadding(pub(crate) Vec2);
 
-/// Wrapper for a [`Vec2`] describing the horizontal and vertical size of a widget.
-/// This is set programatically, not for user modification.
-/// To set a widget's size, use either it's [`Sprite`] dimensions or modify the target UI element's
-/// size.
-#[derive(Component, Reflect, Default, Deref, DerefMut)]
-pub struct CosmicWidgetSize(pub Vec2);
+// /// Wrapper for a [`Vec2`] describing the horizontal and vertical size of a widget.
+// /// This is set programatically, not for user modification.
+// /// To set a widget's size, use either it's [`Sprite`] dimensions or modify the target UI element's
+// /// size.
+// #[derive(Component, Reflect, Default, Deref, DerefMut)]
+// pub(crate) struct CosmicWidgetSize(pub(crate) Vec2);
 
 /// Reshapes text in a [`CosmicEditor`]
 fn reshape(mut query: Query<&mut CosmicEditor>, mut font_system: ResMut<CosmicFontSystem>) {
@@ -56,19 +61,23 @@ fn set_padding(
         (
             &mut CosmicPadding,
             &CosmicTextAlign,
-            &CosmicBuffer,
-            &CosmicWidgetSize,
+            &CosmicEditBuffer,
+            CosmicWidgetSize,
             Option<&CosmicEditor>,
         ),
         Or<(
             With<CosmicEditor>,
             Changed<CosmicTextAlign>,
-            Changed<CosmicBuffer>,
-            Changed<CosmicWidgetSize>,
+            Changed<CosmicEditBuffer>,
+            ChangedCosmicWidgetSize,
         )>,
     >,
 ) {
     for (mut padding, position, buffer, size, editor_opt) in query.iter_mut() {
+        let Ok(size) = size.logical_size() else {
+            continue;
+        };
+
         // TODO: At least one of these clones is uneccessary
         let mut buffer = buffer.0.clone();
 
@@ -82,51 +91,38 @@ fn set_padding(
 
         padding.0 = match position {
             CosmicTextAlign::Center { padding: _ } => Vec2::new(
-                get_x_offset_center(size.0.x, &buffer) as f32,
-                get_y_offset_center(size.0.y, &buffer) as f32,
+                get_x_offset_center(size.x, &buffer) as f32,
+                get_y_offset_center(size.y, &buffer) as f32,
             ),
             CosmicTextAlign::TopLeft { padding } => Vec2::new(*padding as f32, *padding as f32),
-            CosmicTextAlign::Left { padding } => Vec2::new(
-                *padding as f32,
-                get_y_offset_center(size.0.y, &buffer) as f32,
-            ),
+            CosmicTextAlign::Left { padding } => {
+                Vec2::new(*padding as f32, get_y_offset_center(size.y, &buffer) as f32)
+            }
         }
     }
 }
 
-/// Programatically sets the [`CosmicWidgetSize`] of a widget based on it's [`Sprite`] properties
-fn set_widget_size(
-    mut query: Query<(&mut CosmicWidgetSize, &Sprite), Changed<Sprite>>,
-    windows: Query<&Window, With<PrimaryWindow>>,
-) {
-    if windows.iter().len() == 0 {
-        return;
-    }
-    // TODO: early return if sprite size is unchanged
-    let scale = windows.single().scale_factor();
-    for (mut size, sprite) in query.iter_mut() {
-        size.0 = sprite.custom_size.unwrap().ceil() * scale;
-    }
-}
-
 /// Sets the internal [`Buffer`]'s size according to the [`CosmicWidgetSize`] and [`CosmicTextAlign`]
 fn set_buffer_size(
     mut query: Query<
         (
-            &mut CosmicBuffer,
+            &mut CosmicEditBuffer,
             &CosmicWrap,
-            &CosmicWidgetSize,
+            CosmicWidgetSize,
             &CosmicTextAlign,
         ),
         Or<(
             Changed<CosmicWrap>,
-            Changed<CosmicWidgetSize>,
+            ChangedCosmicWidgetSize,
             Changed<CosmicTextAlign>,
         )>,
     >,
     mut font_system: ResMut<CosmicFontSystem>,
 ) {
     for (mut buffer, mode, size, position) in query.iter_mut() {
+        let Ok(size) = size.logical_size() else {
+            continue;
+        };
         let padding_x = match position {
             CosmicTextAlign::Center { padding: _ } => 0.,
             CosmicTextAlign::TopLeft { padding } => *padding as f32,
@@ -134,21 +130,22 @@ fn set_buffer_size(
         };
 
         let (buffer_width, buffer_height) = match mode {
-            CosmicWrap::InfiniteLine => (f32::MAX, size.0.y),
-            CosmicWrap::Wrap => (size.0.x - padding_x, size.0.y),
+            CosmicWrap::InfiniteLine => (f32::MAX, size.y),
+            CosmicWrap::Wrap => (size.x - padding_x, size.y),
         };
 
         buffer.set_size(&mut font_system.0, Some(buffer_width), Some(buffer_height));
     }
 }
 
-/// Instantiates a new image for a [`CosmicBuffer`]
+/// Instantiates a new image for a [`CosmicEditBuffer`]
 fn new_image_from_default(
-    mut query: Query<&mut Handle<Image>, Added<CosmicBuffer>>,
+    mut query: Query<&mut CosmicRenderOutput, Added<CosmicEditBuffer>>,
     mut images: ResMut<Assets<Image>>,
 ) {
     for mut canvas in query.iter_mut() {
-        *canvas = images.add(Image::default());
+        debug!(message = "Initializing a new canvas");
+        *canvas = CosmicRenderOutput(images.add(Image::default()));
     }
 }
 
@@ -157,30 +154,39 @@ fn set_x_offset(
         &mut XOffset,
         &CosmicWrap,
         &CosmicEditor,
-        &CosmicWidgetSize,
+        CosmicWidgetSize,
         &CosmicTextAlign,
     )>,
 ) {
     for (mut x_offset, mode, editor, size, position) in query.iter_mut() {
+        let Ok(size) = size.logical_size() else {
+            continue;
+        };
         if mode != &CosmicWrap::InfiniteLine {
-            return;
+            continue; // this used to be `return` though I feel like it should be continue
         }
 
         let mut cursor_x = 0.;
         let cursor = editor.cursor();
 
+        // counts the width of the glyphs up to the cursor in `cursor_x`
         if let Some(line) = editor.with_buffer(|b| b.clone()).layout_runs().next() {
             for (idx, glyph) in line.glyphs.iter().enumerate() {
-                if cursor.affinity == Affinity::Before {
-                    if idx <= cursor.index {
-                        cursor_x += glyph.w;
-                    } else {
-                        break;
+                match cursor.affinity {
+                    Affinity::Before => {
+                        if idx <= cursor.index {
+                            cursor_x += glyph.w;
+                        } else {
+                            break;
+                        }
+                    }
+                    Affinity::After => {
+                        if idx < cursor.index {
+                            cursor_x += glyph.w;
+                        } else {
+                            break;
+                        }
                     }
-                } else if idx < cursor.index {
-                    cursor_x += glyph.w;
-                } else {
-                    break;
                 }
             }
         }
@@ -191,11 +197,11 @@ fn set_x_offset(
             CosmicTextAlign::Left { padding } => *padding as f32,
         };
 
-        if x_offset.width == 0. {
-            x_offset.width = size.x - padding_x * 2.;
+        if x_offset.width.is_none() {
+            x_offset.width = Some(size.x - padding_x * 2.);
         }
 
-        let right = x_offset.width + x_offset.left;
+        let right = x_offset.width.unwrap() + x_offset.left;
 
         if cursor_x > right {
             let diff = cursor_x - right;
@@ -207,14 +213,3 @@ fn set_x_offset(
         }
     }
 }
-
-fn set_sprite_size_from_ui(
-    mut source_q: Query<&mut Sprite, With<CosmicBuffer>>,
-    dest_q: Query<(&Node, &CosmicSource), Changed<Node>>,
-) {
-    for (node, source) in dest_q.iter() {
-        if let Ok(mut sprite) = source_q.get_mut(source.0) {
-            sprite.custom_size = Some(node.size().ceil().max(Vec2::ONE));
-        }
-    }
-}
-- 
GitLab