git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commitf7c858e07640f0e12887ce789ff21f98603d99c2
parent6f48f0c0e5
authorLucas Galante <[email protected]>
date2026-05-25 00:03
Migrate from winit to smithay-client-toolkit and calloop

 Cargo.lock     | 898 ++-------------------------------------------------------
 Cargo.toml     |   7 +-
 src/lib.rs     |   1 +
 src/main.rs    | 760 ++++++++++++++++++++++++++++++++++--------------
 src/wayland.rs |  31 ++
 src/widget.rs  | 207 ++++++++++++-
 6 files changed, 814 insertions(+), 1090 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index cfd45fe..b92d826 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,60 +2,6 @@
 # It is not intended for manual editing.
 version = 4
 
-[[package]]
-name = "ab_glyph"
-version = "0.2.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2"
-dependencies = [
- "ab_glyph_rasterizer",
- "owned_ttf_parser",
-]
-
-[[package]]
-name = "ab_glyph_rasterizer"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
-
-[[package]]
-name = "ahash"
-version = "0.8.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
-dependencies = [
- "cfg-if",
- "getrandom",
- "once_cell",
- "version_check",
- "zerocopy",
-]
-
-[[package]]
-name = "android-activity"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd"
-dependencies = [
- "android-properties",
- "bitflags 2.11.1",
- "cc",
- "jni",
- "libc",
- "log",
- "ndk",
- "ndk-context",
- "ndk-sys 0.6.0+11769913",
- "num_enum",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "android-properties"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
-
 [[package]]
 name = "android_system_properties"
 version = "0.1.5"
@@ -65,24 +11,12 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "arrayref"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
-
 [[package]]
 name = "arrayvec"
 version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
 
-[[package]]
-name = "as-raw-xcb-connection"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
-
 [[package]]
 name = "ash"
 version = "0.38.0+1.3.281"
@@ -92,12 +26,6 @@ dependencies = [
  "libloading",
 ]
 
-[[package]]
-name = "atomic-waker"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
-
 [[package]]
 name = "autocfg"
 version = "1.5.0"
@@ -140,15 +68,6 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
 
-[[package]]
-name = "block2"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
-dependencies = [
- "objc2",
-]
-
 [[package]]
 name = "bumpalo"
 version = "3.20.2"
@@ -214,8 +133,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
 dependencies = [
  "find-msvc-tools",
- "jobserver",
- "libc",
  "shlex",
 ]
 
@@ -236,11 +153,16 @@ name = "clear-ui"
 version = "0.1.0"
 dependencies = [
  "bytemuck",
+ "calloop",
+ "calloop-wayland-source",
  "glyphon",
  "pollster",
+ "raw-window-handle",
+ "smithay-client-toolkit",
  "tokio",
+ "wayland-client",
  "wgpu",
- "winit",
+ "xkeysym",
 ]
 
 [[package]]
@@ -253,16 +175,6 @@ dependencies = [
  "unicode-width",
 ]
 
-[[package]]
-name = "combine"
-version = "4.6.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
-dependencies = [
- "bytes",
- "memchr",
-]
-
 [[package]]
 name = "concurrent-queue"
 version = "2.5.0"
@@ -288,19 +200,6 @@ version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
-[[package]]
-name = "core-graphics"
-version = "0.23.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
-dependencies = [
- "bitflags 1.3.2",
- "core-foundation",
- "core-graphics-types",
- "foreign-types",
- "libc",
-]
-
 [[package]]
 name = "core-graphics-types"
 version = "0.1.3"
@@ -366,12 +265,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
 
-[[package]]
-name = "dispatch"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
-
 [[package]]
 name = "dlib"
 version = "0.5.3"
@@ -396,12 +289,6 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
 
-[[package]]
-name = "dpi"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
-
 [[package]]
 name = "either"
 version = "1.15.0"
@@ -481,7 +368,7 @@ checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3"
 dependencies = [
  "fontconfig-parser",
  "log",
- "memmap2",
+ "memmap2 0.9.10",
  "slotmap",
  "tinyvec",
  "ttf-parser 0.20.0",
@@ -538,28 +425,6 @@ dependencies = [
  "slab",
 ]
 
-[[package]]
-name = "gethostname"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
-dependencies = [
- "rustix 1.1.4",
- "windows-link",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
-dependencies = [
- "cfg-if",
- "libc",
- "r-efi",
- "wasip2",
-]
-
 [[package]]
 name = "gl_generator"
 version = "0.14.0"
@@ -699,36 +564,6 @@ dependencies = [
  "hashbrown 0.17.1",
 ]
 
-[[package]]
-name = "jni"
-version = "0.22.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
-dependencies = [
- "cfg-if",
- "combine",
- "jni-macros",
- "jni-sys 0.4.1",
- "log",
- "simd_cesu8",
- "thiserror 2.0.18",
- "walkdir",
- "windows-link",
-]
-
-[[package]]
-name = "jni-macros"
-version = "0.22.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
-dependencies = [
- "proc-macro2",
- "quote",
- "rustc_version",
- "simd_cesu8",
- "syn",
-]
-
 [[package]]
 name = "jni-sys"
 version = "0.3.1"
@@ -757,16 +592,6 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "jobserver"
-version = "0.1.34"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
-dependencies = [
- "getrandom",
- "libc",
-]
-
 [[package]]
 name = "js-sys"
 version = "0.3.98"
@@ -818,18 +643,6 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
 
-[[package]]
-name = "libredox"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
-dependencies = [
- "bitflags 2.11.1",
- "libc",
- "plain",
- "redox_syscall 0.7.5",
-]
-
 [[package]]
 name = "linux-raw-sys"
 version = "0.4.15"
@@ -884,6 +697,15 @@ version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
 
+[[package]]
+name = "memmap2"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "memmap2"
 version = "0.9.10"
@@ -941,27 +763,6 @@ dependencies = [
  "unicode-xid",
 ]
 
-[[package]]
-name = "ndk"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
-dependencies = [
- "bitflags 2.11.1",
- "jni-sys 0.3.1",
- "log",
- "ndk-sys 0.6.0+11769913",
- "num_enum",
- "raw-window-handle",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "ndk-context"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
-
 [[package]]
 name = "ndk-sys"
 version = "0.5.0+25.2.9519653"
@@ -971,15 +772,6 @@ dependencies = [
  "jni-sys 0.3.1",
 ]
 
-[[package]]
-name = "ndk-sys"
-version = "0.6.0+11769913"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
-dependencies = [
- "jni-sys 0.3.1",
-]
-
 [[package]]
 name = "num-traits"
 version = "0.2.19"
@@ -989,28 +781,6 @@ dependencies = [
  "autocfg",
 ]
 
-[[package]]
-name = "num_enum"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
-dependencies = [
- "num_enum_derive",
- "rustversion",
-]
-
-[[package]]
-name = "num_enum_derive"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
-dependencies = [
- "proc-macro-crate",
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "objc"
 version = "0.2.7"
@@ -1020,225 +790,12 @@ dependencies = [
  "malloc_buf",
 ]
 
-[[package]]
-name = "objc-sys"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
-
-[[package]]
-name = "objc2"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
-dependencies = [
- "objc-sys",
- "objc2-encode",
-]
-
-[[package]]
-name = "objc2-app-kit"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "libc",
- "objc2",
- "objc2-core-data",
- "objc2-core-image",
- "objc2-foundation",
- "objc2-quartz-core",
-]
-
-[[package]]
-name = "objc2-cloud-kit"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-core-location",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-contacts"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
-dependencies = [
- "block2",
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-core-data"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-core-image"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
-dependencies = [
- "block2",
- "objc2",
- "objc2-foundation",
- "objc2-metal",
-]
-
-[[package]]
-name = "objc2-core-location"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
-dependencies = [
- "block2",
- "objc2",
- "objc2-contacts",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-encode"
-version = "4.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
-
-[[package]]
-name = "objc2-foundation"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "dispatch",
- "libc",
- "objc2",
-]
-
-[[package]]
-name = "objc2-link-presentation"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
-dependencies = [
- "block2",
- "objc2",
- "objc2-app-kit",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-metal"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-quartz-core"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-foundation",
- "objc2-metal",
-]
-
-[[package]]
-name = "objc2-symbols"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
-dependencies = [
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-ui-kit"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-cloud-kit",
- "objc2-core-data",
- "objc2-core-image",
- "objc2-core-location",
- "objc2-foundation",
- "objc2-link-presentation",
- "objc2-quartz-core",
- "objc2-symbols",
- "objc2-uniform-type-identifiers",
- "objc2-user-notifications",
-]
-
-[[package]]
-name = "objc2-uniform-type-identifiers"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
-dependencies = [
- "block2",
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-user-notifications"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-core-location",
- "objc2-foundation",
-]
-
 [[package]]
 name = "once_cell"
 version = "1.21.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
 
-[[package]]
-name = "orbclient"
-version = "0.3.54"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a570f6bca41d29acb2139229a7c873ec99bc9a313bd10804081d89bfac8ff329"
-dependencies = [
- "libc",
- "libredox",
-]
-
 [[package]]
 name = "ordered-float"
 version = "4.6.0"
@@ -1248,15 +805,6 @@ dependencies = [
  "num-traits",
 ]
 
-[[package]]
-name = "owned_ttf_parser"
-version = "0.25.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
-dependencies = [
- "ttf-parser 0.25.1",
-]
-
 [[package]]
 name = "parking_lot"
 version = "0.12.5"
@@ -1275,7 +823,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
 dependencies = [
  "cfg-if",
  "libc",
- "redox_syscall 0.5.18",
+ "redox_syscall",
  "smallvec",
  "windows-link",
 ]
@@ -1286,32 +834,6 @@ version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
 
-[[package]]
-name = "percent-encoding"
-version = "2.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
-
-[[package]]
-name = "pin-project"
-version = "1.1.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
-dependencies = [
- "pin-project-internal",
-]
-
-[[package]]
-name = "pin-project-internal"
-version = "1.1.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "pin-project-lite"
 version = "0.2.17"
@@ -1324,12 +846,6 @@ version = "0.3.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
 
-[[package]]
-name = "plain"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
-
 [[package]]
 name = "polling"
 version = "3.11.0"
@@ -1356,15 +872,6 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
 
-[[package]]
-name = "proc-macro-crate"
-version = "3.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
-dependencies = [
- "toml_edit",
-]
-
 [[package]]
 name = "proc-macro2"
 version = "1.0.106"
@@ -1398,12 +905,6 @@ dependencies = [
  "proc-macro2",
 ]
 
-[[package]]
-name = "r-efi"
-version = "5.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
-
 [[package]]
 name = "range-alloc"
 version = "0.1.5"
@@ -1452,15 +953,6 @@ dependencies = [
  "font-types",
 ]
 
-[[package]]
-name = "redox_syscall"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
-dependencies = [
- "bitflags 1.3.2",
-]
-
 [[package]]
 name = "redox_syscall"
 version = "0.5.18"
@@ -1470,15 +962,6 @@ dependencies = [
  "bitflags 2.11.1",
 ]
 
-[[package]]
-name = "redox_syscall"
-version = "0.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b"
-dependencies = [
- "bitflags 2.11.1",
-]
-
 [[package]]
 name = "renderdoc-sys"
 version = "1.1.0"
@@ -1503,15 +986,6 @@ version = "2.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
 
-[[package]]
-name = "rustc_version"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
-dependencies = [
- "semver",
-]
-
 [[package]]
 name = "rustix"
 version = "0.38.44"
@@ -1561,15 +1035,6 @@ dependencies = [
  "unicode-script",
 ]
 
-[[package]]
-name = "same-file"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
-dependencies = [
- "winapi-util",
-]
-
 [[package]]
 name = "scoped-tls"
 version = "1.0.1"
@@ -1582,40 +1047,12 @@ 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.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89"
 
-[[package]]
-name = "semver"
-version = "1.0.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
-
-[[package]]
-name = "serde"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
-dependencies = [
- "serde_core",
-]
-
 [[package]]
 name = "serde_core"
 version = "1.0.228"
@@ -1652,22 +1089,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "simd_cesu8"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
-dependencies = [
- "rustc_version",
- "simdutf8",
-]
-
-[[package]]
-name = "simdutf8"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
-
 [[package]]
 name = "skrifa"
 version = "0.22.3"
@@ -1706,12 +1127,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
 dependencies = [
  "bitflags 2.11.1",
+ "bytemuck",
  "calloop",
  "calloop-wayland-source",
  "cursor-icon",
  "libc",
  "log",
- "memmap2",
+ "memmap2 0.9.10",
+ "pkg-config",
  "rustix 0.38.44",
  "thiserror 1.0.69",
  "wayland-backend",
@@ -1721,18 +1144,10 @@ dependencies = [
  "wayland-protocols",
  "wayland-protocols-wlr",
  "wayland-scanner",
+ "xkbcommon",
  "xkeysym",
 ]
 
-[[package]]
-name = "smol_str"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead"
-dependencies = [
- "serde",
-]
-
 [[package]]
 name = "socket2"
 version = "0.6.3"
@@ -1758,12 +1173,6 @@ 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 = "strum"
 version = "0.26.3"
@@ -1872,31 +1281,6 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "tiny-skia"
-version = "0.11.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
-dependencies = [
- "arrayref",
- "arrayvec",
- "bytemuck",
- "cfg-if",
- "log",
- "tiny-skia-path",
-]
-
-[[package]]
-name = "tiny-skia-path"
-version = "0.11.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
-dependencies = [
- "arrayref",
- "bytemuck",
- "strict-num",
-]
-
 [[package]]
 name = "tinyvec"
 version = "1.11.0"
@@ -1940,52 +1324,6 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "toml_datetime"
-version = "1.1.1+spec-1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
-dependencies = [
- "serde_core",
-]
-
-[[package]]
-name = "toml_edit"
-version = "0.25.11+spec-1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
-dependencies = [
- "indexmap",
- "toml_datetime",
- "toml_parser",
- "winnow",
-]
-
-[[package]]
-name = "toml_parser"
-version = "1.1.2+spec-1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
-dependencies = [
- "winnow",
-]
-
-[[package]]
-name = "tracing"
-version = "0.1.44"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
-dependencies = [
- "pin-project-lite",
- "tracing-core",
-]
-
-[[package]]
-name = "tracing-core"
-version = "0.1.36"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
-
 [[package]]
 name = "ttf-parser"
 version = "0.20.0"
@@ -1998,12 +1336,6 @@ version = "0.21.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
 
-[[package]]
-name = "ttf-parser"
-version = "0.25.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.18"
@@ -2070,31 +1402,12 @@ version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
 
-[[package]]
-name = "walkdir"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
-dependencies = [
- "same-file",
- "winapi-util",
-]
-
 [[package]]
 name = "wasi"
 version = "0.11.1+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
 
-[[package]]
-name = "wasip2"
-version = "1.0.3+wasi-0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
-dependencies = [
- "wit-bindgen",
-]
-
 [[package]]
 name = "wasm-bindgen"
 version = "0.2.121"
@@ -2210,19 +1523,6 @@ dependencies = [
  "wayland-scanner",
 ]
 
-[[package]]
-name = "wayland-protocols-plasma"
-version = "0.3.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91"
-dependencies = [
- "bitflags 2.11.1",
- "wayland-backend",
- "wayland-client",
- "wayland-protocols",
- "wayland-scanner",
-]
-
 [[package]]
 name = "wayland-protocols-wlr"
 version = "0.3.12"
@@ -2255,7 +1555,6 @@ checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
 dependencies = [
  "dlib",
  "log",
- "once_cell",
  "pkg-config",
 ]
 
@@ -2269,16 +1568,6 @@ dependencies = [
  "wasm-bindgen",
 ]
 
-[[package]]
-name = "web-time"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
 [[package]]
 name = "wgpu"
 version = "24.0.5"
@@ -2357,7 +1646,7 @@ dependencies = [
  "log",
  "metal",
  "naga",
- "ndk-sys 0.5.0+25.2.9519653",
+ "ndk-sys",
  "objc",
  "once_cell",
  "ordered-float",
@@ -2467,15 +1756,6 @@ dependencies = [
  "windows-targets",
 ]
 
-[[package]]
-name = "windows-sys"
-version = "0.52.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
-dependencies = [
- "windows-targets",
-]
-
 [[package]]
 name = "windows-sys"
 version = "0.59.0"
@@ -2558,105 +1838,6 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
-[[package]]
-name = "winit"
-version = "0.30.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d"
-dependencies = [
- "ahash",
- "android-activity",
- "atomic-waker",
- "bitflags 2.11.1",
- "block2",
- "bytemuck",
- "calloop",
- "cfg_aliases",
- "concurrent-queue",
- "core-foundation",
- "core-graphics",
- "cursor-icon",
- "dpi",
- "js-sys",
- "libc",
- "memmap2",
- "ndk",
- "objc2",
- "objc2-app-kit",
- "objc2-foundation",
- "objc2-ui-kit",
- "orbclient",
- "percent-encoding",
- "pin-project",
- "raw-window-handle",
- "redox_syscall 0.4.1",
- "rustix 0.38.44",
- "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",
- "x11-dl",
- "x11rb",
- "xkbcommon-dl",
-]
-
-[[package]]
-name = "winnow"
-version = "1.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "wit-bindgen"
-version = "0.57.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
-
-[[package]]
-name = "x11-dl"
-version = "2.21.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
-dependencies = [
- "libc",
- "once_cell",
- "pkg-config",
-]
-
-[[package]]
-name = "x11rb"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
-dependencies = [
- "as-raw-xcb-connection",
- "gethostname",
- "libc",
- "libloading",
- "once_cell",
- "rustix 1.1.4",
- "x11rb-protocol",
-]
-
-[[package]]
-name = "x11rb-protocol"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
-
 [[package]]
 name = "xcursor"
 version = "0.3.10"
@@ -2664,15 +1845,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
 
 [[package]]
-name = "xkbcommon-dl"
-version = "0.4.2"
+name = "xkbcommon"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5"
+checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e"
 dependencies = [
- "bitflags 2.11.1",
- "dlib",
- "log",
- "once_cell",
+ "libc",
+ "memmap2 0.8.0",
  "xkeysym",
 ]
 
@@ -2681,6 +1860,9 @@ name = "xkeysym"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
+dependencies = [
+ "bytemuck",
+]
 
 [[package]]
 name = "xml-rs"
@@ -2699,23 +1881,3 @@ name = "zeno"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"
-
-[[package]]
-name = "zerocopy"
-version = "0.8.48"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
-dependencies = [
- "zerocopy-derive",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.8.48"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
diff --git a/Cargo.toml b/Cargo.toml
index e0bd151..8541b84 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,9 +8,14 @@ name = "clear_ui"
 path = "src/lib.rs"
 
 [dependencies]
-winit = "0.30"
+raw-window-handle = "0.6"
 wgpu = "24"
 bytemuck = { version = "1", features = ["derive"] }
 pollster = "0.4"
 glyphon = "0.8"
 tokio = { version = "1", features = ["full"] }
+smithay-client-toolkit = { version = "0.19.2", features = ["calloop"] }
+calloop = "0.13.0"
+calloop-wayland-source = "0.3.0"
+wayland-client = { version = "0.31", features = ["system"] }
+xkeysym = "0.2"
diff --git a/src/lib.rs b/src/lib.rs
index b6515ea..84b7d9a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,7 @@
 pub mod color;
 pub mod widget;
 pub mod layout;
+pub mod wayland;
 
 pub mod colors {
     pub use crate::color::*;
diff --git a/src/main.rs b/src/main.rs
index ce48e1c..9f6433a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,17 +1,42 @@
-use std::sync::Arc;
-
-use winit::application::ApplicationHandler;
-use winit::event::{ElementState, MouseButton, WindowEvent};
-use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
-use winit::window::{Window, WindowAttributes};
-
-use clear_ui::widget::{Button, Checkbox, ContentBg, Header, Panel, ProgressBar, Sidebar, Slider, Spinbox, StatusBar, TextLabel, Toggle, Widget};
+use clear_ui::widget::{
+    Button, Checkbox, ContentBg, Header, Panel, ProgressBar, Sidebar, Slider, Spinbox, StatusBar,
+    TextLabel, Toggle, Widget,
+};
 
 use glyphon::{
     Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas,
     TextBounds, TextRenderer, Viewport,
 };
 
+use smithay_client_toolkit::{
+    compositor::{CompositorHandler, CompositorState},
+    delegate_compositor, delegate_keyboard, delegate_pointer, delegate_registry,
+    delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, delegate_output,
+    registry::{ProvidesRegistryState, RegistryState},
+    registry_handlers,
+    output::{OutputHandler, OutputState},
+    seat::{
+        keyboard::KeyboardHandler,
+        pointer::PointerHandler,
+        Capability, SeatHandler, SeatState,
+    },
+    shell::{
+        xdg::{
+            window::{Window as XdgWindow, WindowConfigure, WindowHandler, WindowDecorations},
+            XdgShell,
+        },
+        WaylandSurface,
+    },
+    shm::{Shm, ShmHandler},
+};
+use wayland_client::{
+    globals::registry_queue_init,
+    protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
+    Connection, QueueHandle, Proxy,
+};
+use calloop::EventLoop;
+use calloop_wayland_source::WaylandSource;
+
 #[repr(C)]
 #[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
 struct Vertex {
@@ -68,7 +93,6 @@ fn make_text_buffer(font_system: &mut FontSystem, text: &str, size: f32) -> Buff
 }
 
 struct State {
-    window: Arc<Window>,
     surface: wgpu::Surface<'static>,
     device: wgpu::Device,
     queue: wgpu::Queue,
@@ -104,11 +128,8 @@ struct State {
 }
 
 impl State {
-    async fn new(window: Arc<Window>) -> Self {
-        let scale = window.scale_factor();
-        let physical_size = window.inner_size();
-        let pw = physical_size.width.max(1);
-        let ph = physical_size.height.max(1);
+    async fn new(wayland_handle: &'static clear_ui::wayland::WaylandSurfaceHandle, pw: u32, ph: u32) -> Self {
+        let scale = 1.0f64;
         let lw = pw as f32 / scale as f32;
         let lh = ph as f32 / scale as f32;
         let sw = lw;
@@ -120,7 +141,7 @@ impl State {
         });
 
         let surface = instance
-            .create_surface(window.clone())
+            .create_surface(wayland_handle)
             .expect("Failed to create surface");
 
         let adapter = instance
@@ -146,14 +167,15 @@ impl State {
             .await
             .expect("Failed to create device");
 
-        let config = surface
+        let mut config = surface
             .get_default_config(&adapter, pw, ph)
             .expect("Failed to get surface config");
+        config.present_mode = wgpu::PresentMode::Fifo;
         surface.configure(&device, &config);
 
         let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
             label: Some("Shader"),
-            source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
+            source: wgpu::ShaderSource::Wgsl(clear_ui::SHADER.into()),
         });
 
         let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
@@ -241,7 +263,6 @@ impl State {
         });
 
         let mut state = Self {
-            window,
             surface,
             device,
             queue,
@@ -303,6 +324,9 @@ impl State {
     fn upload_vertices(&mut self) {
         let verts = self.collect_vertices();
         self.vertex_count = verts.len() as u32;
+        if self.vertex_count == 0 {
+            return;
+        }
         let data = bytemuck::cast_slice(&verts);
         let needed = data.len() as wgpu::BufferAddress;
         if needed > self.vertex_buffer.size() {
@@ -404,14 +428,14 @@ impl State {
             .unwrap();
     }
 
-    fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
-        if new_size.width > 0 && new_size.height > 0 {
-            self.physical_width = new_size.width;
-            self.physical_height = new_size.height;
-            self.width = new_size.width as f32 / self.scale as f32;
-            self.height = new_size.height as f32 / self.scale as f32;
-            self.config.width = new_size.width;
-            self.config.height = new_size.height;
+    fn resize(&mut self, width: u32, height: u32) {
+        if width > 0 && height > 0 {
+            self.physical_width = width;
+            self.physical_height = height;
+            self.width = width as f32 / self.scale as f32;
+            self.height = height as f32 / self.scale as f32;
+            self.config.width = width;
+            self.config.height = height;
             self.surface.configure(&self.device, &self.config);
             self.positions = demo_positions(self.width, self.height);
             self.apply_layout();
@@ -419,103 +443,6 @@ impl State {
         }
     }
 
-    fn handle_event(&mut self, event: &WindowEvent) -> bool {
-        match event {
-            WindowEvent::MouseWheel { delta, .. } => {
-                let mut changed = false;
-                for w in &mut self.widgets {
-                    if w.mouse_wheel(delta, self.cursor_x, self.cursor_y) {
-                        changed = true;
-                    }
-                }
-                changed
-            }
-            WindowEvent::CursorMoved { position, .. } => {
-                self.cursor_x = position.x as f32 / self.scale as f32;
-                self.cursor_y = position.y as f32 / self.scale as f32;
-                let mut changed = false;
-
-                if let Some(idx) = self.drag_widget {
-                    if self.widgets[idx].drag_update(self.cursor_x, self.cursor_y) {
-                        changed = true;
-                    }
-                }
-
-                if self.drag_widget.is_none() {
-                    for w in &mut self.widgets {
-                        if w.cursor_moved(self.cursor_x, self.cursor_y) {
-                            changed = true;
-                        }
-                    }
-                }
-                changed
-            }
-            WindowEvent::MouseInput { state: btn_state, button, .. } => {
-                if *button != MouseButton::Left { return false; }
-                let mut changed = false;
-
-                match btn_state {
-                    ElementState::Pressed => {
-                        if let Some(old) = self.focused_widget.take() {
-                            self.widgets[old].unfocus();
-                        }
-                        for i in (0..self.widgets.len()).rev() {
-                            if self.widgets[i].hit_test(self.cursor_x, self.cursor_y) {
-                                if self.widgets[i].mouse_input(*button, *btn_state, self.cursor_x, self.cursor_y) {
-                                    changed = true;
-                                }
-                                if self.widgets[i].draggable() {
-                                    self.widgets[i].drag_begin(self.cursor_x, self.cursor_y);
-                                    self.drag_widget = Some(i);
-                                }
-                                self.widgets[i].focus();
-                                self.focused_widget = Some(i);
-                                break;
-                            }
-                        }
-                    }
-                    ElementState::Released => {
-                        if let Some(idx) = self.drag_widget {
-                            self.widgets[idx].drag_end();
-                            self.drag_widget = None;
-                            changed = true;
-                        }
-                        for w in &mut self.widgets {
-                            if w.mouse_input(*button, *btn_state, self.cursor_x, self.cursor_y) {
-                                changed = true;
-                            }
-                        }
-                        let mut clicked = false;
-                        for w in &mut self.widgets {
-                            if w.take_click() {
-                                clicked = true;
-                            }
-                        }
-                        if clicked {
-                            self.click_count += 1;
-                            self.update_status_text(&format!("Clicks: {}", self.click_count));
-                        }
-                    }
-                }
-                changed
-            }
-            WindowEvent::KeyboardInput { event, .. } => {
-                if let Some(idx) = self.focused_widget {
-                    let val = self.widgets[idx].value();
-                    let changed = self.widgets[idx].keyboard_input(event);
-                    if self.widgets[idx].value() != val {
-                        changed || true
-                    } else {
-                        changed
-                    }
-                } else {
-                    false
-                }
-            }
-            _ => false,
-        }
-    }
-
     fn render(&mut self) {
         self.prepare_text();
 
@@ -546,7 +473,10 @@ impl State {
                     resolve_target: None,
                     ops: wgpu::Operations {
                         load: wgpu::LoadOp::Clear(wgpu::Color {
-                            r: 0.06, g: 0.06, b: 0.08, a: 1.0,
+                            r: 0.05,
+                            g: 0.05,
+                            b: 0.08,
+                            a: 0.92,
                         }),
                         store: wgpu::StoreOp::Store,
                     },
@@ -556,136 +486,530 @@ impl State {
                 occlusion_query_set: None,
             });
 
-            // Draw colored quads
-            pass.set_pipeline(&self.render_pipeline);
-            pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
-            pass.draw(0..self.vertex_count, 0..1);
+            if self.vertex_count > 0 {
+                pass.set_pipeline(&self.render_pipeline);
+                pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
+                pass.draw(0..self.vertex_count, 0..1);
+            }
 
-            // Draw text
-            self.text_renderer.render(&self.text_atlas, &self.text_viewport, &mut pass).unwrap();
+            self.text_renderer
+                .render(&self.text_atlas, &self.text_viewport, &mut pass)
+                .unwrap();
         }
 
         self.queue.submit(std::iter::once(encoder.finish()));
-        self.window.pre_present_notify();
         output.present();
     }
 }
 
-struct App {
+fn demo_positions(sw: f32, sh: f32) -> Vec<(f32, f32, f32, f32)> {
+    vec![
+        (0.0, 0.0, sw, 40.0),               // 0 header
+        (0.0, 40.0, 60.0, sh - 68.0),       // 1 sidebar
+        (60.0, 40.0, sw - 60.0, sh - 68.0), // 2 content_bg
+        (70.0, 50.0, 140.0, 40.0),          // 3 btn_a
+        (220.0, 50.0, 140.0, 40.0),         // 4 btn_b
+        (370.0, 50.0, 140.0, 40.0),         // 5 btn_c
+        (70.0, 100.0, 400.0, 250.0),        // 6 panel
+        (70.0, 360.0, 140.0, 40.0),         // 7 click_me
+        (220.0, 360.0, 140.0, 40.0),        // 8 reset
+        (70.0, 410.0, 24.0, 24.0),          // 9 checkbox
+        (104.0, 410.0, 48.0, 24.0),         // 10 toggle
+        (162.0, 410.0, 160.0, 24.0),        // 11 progress_bar
+        (70.0, 444.0, 300.0, 32.0),         // 12 slider
+        (70.0, 486.0, 120.0, 32.0),         // 13 spinbox
+        (0.0, sh - 28.0, sw, 28.0),         // 14 status_bar
+    ]
+}
+
+struct AppState {
+    registry_state: RegistryState,
+    compositor_state: CompositorState,
+    xdg_shell_state: XdgShell,
+    shm_state: Shm,
+    seat_state: SeatState,
+    output_state: OutputState,
+
+    seats: Vec<wl_seat::WlSeat>,
+    pointer: Option<wl_pointer::WlPointer>,
+    keyboard: Option<wl_keyboard::WlKeyboard>,
+
+    window: XdgWindow,
+    surface: wl_surface::WlSurface,
+
     state: Option<State>,
+    exit: bool,
+    redraw: bool,
+}
+
+impl CompositorHandler for AppState {
+    fn scale_factor_changed(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        scale_factor: i32,
+    ) {
+        if let Some(state) = &mut self.state {
+            state.scale = scale_factor as f64;
+            state.resize(state.physical_width, state.physical_height);
+        }
+        self.redraw = true;
+    }
+
+    fn transform_changed(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        _new_transform: wl_output::Transform,
+    ) {
+    }
+
+    fn frame(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        _time: u32,
+    ) {
+    }
+
+    fn surface_enter(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        _output: &wl_output::WlOutput,
+    ) {
+    }
+
+    fn surface_leave(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        _output: &wl_output::WlOutput,
+    ) {
+    }
 }
 
-impl App {
-    fn new() -> Self { Self { state: None } }
+impl OutputHandler for AppState {
+    fn output_state(&mut self) -> &mut OutputState {
+        &mut self.output_state
+    }
+
+    fn new_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _output: wl_output::WlOutput) {}
+
+    fn update_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _output: wl_output::WlOutput) {}
+
+    fn output_destroyed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _output: wl_output::WlOutput) {}
 }
 
-impl ApplicationHandler for App {
-    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
-        if self.state.is_some() { return; }
+impl SeatHandler for AppState {
+    fn seat_state(&mut self) -> &mut SeatState {
+        &mut self.seat_state
+    }
 
-        let window = Arc::new(
-            event_loop
-                .create_window(
-                    WindowAttributes::default()
-                        .with_title("Clear UI - Test Window")
-                        .with_inner_size(winit::dpi::LogicalSize::new(1024, 768)),
-                )
-                .unwrap(),
-        );
+    fn new_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
+        self.seats.push(seat);
+    }
 
-        let state = pollster::block_on(State::new(window));
-        self.state = Some(state);
-        self.state.as_ref().unwrap().window.request_redraw();
+    fn new_capability(
+        &mut self,
+        _conn: &Connection,
+        qh: &QueueHandle<Self>,
+        seat: wl_seat::WlSeat,
+        capability: Capability,
+    ) {
+        if capability == Capability::Pointer && self.pointer.is_none() {
+            let pointer = self.seat_state.get_pointer(qh, &seat).unwrap();
+            self.pointer = Some(pointer);
+        }
+        if capability == Capability::Keyboard && self.keyboard.is_none() {
+            let keyboard = self.seat_state.get_keyboard(qh, &seat, None).unwrap();
+            self.keyboard = Some(keyboard);
+        }
     }
 
-    fn window_event(
+    fn remove_capability(
         &mut self,
-        event_loop: &ActiveEventLoop,
-        _window_id: winit::window::WindowId,
-        event: WindowEvent,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _seat: wl_seat::WlSeat,
+        capability: Capability,
     ) {
-        let needs_redraw = match event {
-            WindowEvent::CloseRequested => {
-                event_loop.exit();
-                true
+        if capability == Capability::Pointer {
+            self.pointer = None;
+        }
+        if capability == Capability::Keyboard {
+            self.keyboard = None;
+        }
+    }
+
+    fn remove_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
+        self.seats.retain(|s| s != &seat);
+    }
+}
+
+impl ShmHandler for AppState {
+    fn shm_state(&mut self) -> &mut Shm {
+        &mut self.shm_state
+    }
+}
+
+impl PointerHandler for AppState {
+    fn pointer_frame(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _pointer: &wl_pointer::WlPointer,
+        events: &[smithay_client_toolkit::seat::pointer::PointerEvent],
+    ) {
+        use smithay_client_toolkit::seat::pointer::PointerEventKind;
+        for event in events {
+            let (x, y) = event.position;
+            if let Some(state) = &mut self.state {
+                state.cursor_x = x as f32;
+                state.cursor_y = y as f32;
             }
-            WindowEvent::Resized(size) => {
-                if let Some(state) = &mut self.state {
-                    state.resize(size);
+
+            match &event.kind {
+                PointerEventKind::Enter { .. } => {}
+                PointerEventKind::Leave { .. } => {}
+                PointerEventKind::Motion { .. } => {
+                    if let Some(state) = &mut self.state {
+                        let mut changed = false;
+                        if let Some(idx) = state.drag_widget {
+                            if state.widgets[idx].drag_update(state.cursor_x, state.cursor_y) {
+                                changed = true;
+                            }
+                        }
+                        if state.drag_widget.is_none() {
+                            for w in &mut state.widgets {
+                                if w.cursor_moved(state.cursor_x, state.cursor_y) {
+                                    changed = true;
+                                }
+                            }
+                        }
+                        if changed {
+                            state.upload_vertices();
+                            self.redraw = true;
+                        }
+                    }
                 }
-                true
-            }
-            WindowEvent::RedrawRequested => {
-                if let Some(state) = &mut self.state {
-                    state.render();
-                    state.window.request_redraw();
+                PointerEventKind::Press { button, .. } => {
+                    if *button != 272 {
+                        continue;
+                    }
+                    if let Some(st) = &mut self.state {
+                        let button = clear_ui::widget::MouseButton::Left;
+                        let btn_state = clear_ui::widget::ElementState::Pressed;
+                        let mut changed = false;
+                        if let Some(old) = st.focused_widget.take() {
+                            st.widgets[old].unfocus();
+                        }
+                        for i in (0..st.widgets.len()).rev() {
+                            if st.widgets[i].hit_test(st.cursor_x, st.cursor_y) {
+                                if st.widgets[i].mouse_input(
+                                    button,
+                                    btn_state,
+                                    st.cursor_x,
+                                    st.cursor_y,
+                                ) {
+                                    changed = true;
+                                }
+                                if st.widgets[i].draggable() {
+                                    st.widgets[i].drag_begin(st.cursor_x, st.cursor_y);
+                                    st.drag_widget = Some(i);
+                                }
+                                st.widgets[i].focus();
+                                st.focused_widget = Some(i);
+                                break;
+                            }
+                        }
+                        if changed {
+                            st.upload_vertices();
+                            self.redraw = true;
+                        }
+                    }
                 }
-                true
-            }
-            WindowEvent::ScaleFactorChanged { scale_factor, mut inner_size_writer } => {
-                if let Some(state) = &mut self.state {
-                    let new_physical = winit::dpi::PhysicalSize::new(
-                        (state.width as f64 * scale_factor) as u32,
-                        (state.height as f64 * scale_factor) as u32,
-                    );
-                    let _ = inner_size_writer.request_inner_size(new_physical);
-                    state.scale = scale_factor;
-                    state.resize(new_physical);
-                    state.window.request_redraw();
+                PointerEventKind::Release { button, .. } => {
+                    if *button != 272 {
+                        continue;
+                    }
+                    if let Some(st) = &mut self.state {
+                        let button = clear_ui::widget::MouseButton::Left;
+                        let btn_state = clear_ui::widget::ElementState::Released;
+                        let mut changed = false;
+                        if let Some(idx) = st.drag_widget {
+                            st.widgets[idx].drag_end();
+                            st.drag_widget = None;
+                            changed = true;
+                        }
+                        for w in &mut st.widgets {
+                            if w.mouse_input(button, btn_state, st.cursor_x, st.cursor_y) {
+                                changed = true;
+                            }
+                        }
+                        let mut clicked = false;
+                        for w in &mut st.widgets {
+                            if w.take_click() {
+                                clicked = true;
+                            }
+                        }
+                        if clicked {
+                            st.click_count += 1;
+                            st.update_status_text(&format!("Clicks: {}", st.click_count));
+                        }
+                        if changed {
+                            st.upload_vertices();
+                            self.redraw = true;
+                        }
+                    }
+                }
+                PointerEventKind::Axis { horizontal, vertical, .. } => {
+                    if let Some(state) = &mut self.state {
+                        let h_scroll = horizontal.absolute as f32;
+                        let v_scroll = vertical.absolute as f32;
+                        
+                        let delta = clear_ui::widget::MouseScrollDelta::LineDelta(-h_scroll / 10.0, -v_scroll / 10.0);
+                        let mut changed = false;
+                        for w in &mut state.widgets {
+                            if w.mouse_wheel(&delta, state.cursor_x, state.cursor_y) {
+                                changed = true;
+                            }
+                        }
+                        if changed {
+                            state.upload_vertices();
+                            self.redraw = true;
+                        }
+                    }
                 }
-                true
             }
+        }
+    }
+}
+
+impl KeyboardHandler for AppState {
+    fn enter(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _surface: &wl_surface::WlSurface,
+        _serial: u32,
+        _raw_modifiers: &[u32],
+        _keysyms: &[xkeysym::Keysym],
+    ) {
+    }
+
+    fn leave(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _surface: &wl_surface::WlSurface,
+        _serial: u32,
+    ) {
+    }
+
+    fn press_key(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _serial: u32,
+        event: smithay_client_toolkit::seat::keyboard::KeyEvent,
+    ) {
+        self.handle_key(event, clear_ui::widget::ElementState::Pressed);
+    }
+
+    fn release_key(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _serial: u32,
+        event: smithay_client_toolkit::seat::keyboard::KeyEvent,
+    ) {
+        self.handle_key(event, clear_ui::widget::ElementState::Released);
+    }
+
+    fn update_modifiers(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _serial: u32,
+        _modifiers: smithay_client_toolkit::seat::keyboard::Modifiers,
+        _layout: u32,
+    ) {
+    }
+}
+
+impl AppState {
+    fn handle_key(&mut self, event: smithay_client_toolkit::seat::keyboard::KeyEvent, state: clear_ui::widget::ElementState) {
+        use clear_ui::widget::{Key, KeyEvent, NamedKey};
+        let logical_key = match event.keysym {
+            xkeysym::Keysym::Escape => Key::Named(NamedKey::Escape),
+            xkeysym::Keysym::Return => Key::Named(NamedKey::Enter),
+            xkeysym::Keysym::BackSpace => Key::Named(NamedKey::Backspace),
+            xkeysym::Keysym::Down => Key::Named(NamedKey::ArrowDown),
+            xkeysym::Keysym::Up => Key::Named(NamedKey::ArrowUp),
+            xkeysym::Keysym::Left => Key::Named(NamedKey::ArrowLeft),
+            xkeysym::Keysym::Right => Key::Named(NamedKey::ArrowRight),
+            xkeysym::Keysym::Tab => Key::Named(NamedKey::Tab),
+            xkeysym::Keysym::Delete => Key::Named(NamedKey::Delete),
+            xkeysym::Keysym::space => Key::Named(NamedKey::Space),
             _ => {
-                if let Some(state) = &mut self.state {
-                    let prev = state.click_count;
-                    let changed = state.handle_event(&event);
-                    if changed {
-                        state.upload_vertices();
-                    }
-                    if state.click_count != prev {
-                        let clicks = state.click_count;
-                        state.window.set_title(&format!(
-                            "Clear UI - Test Window  |  clicks: {}", clicks
-                        ));
-                    }
-                    changed
+                if let Some(ref text) = event.utf8 {
+                    Key::Character(text.clone())
                 } else {
-                    false
+                    return;
                 }
             }
         };
-        if needs_redraw {
+
+        let custom_event = KeyEvent {
+            state,
+            logical_key,
+            text: event.utf8.clone(),
+            repeat: false,
+        };
+
+        if let Some(st) = &mut self.state {
+            if let Some(idx) = st.focused_widget {
+                let val = st.widgets[idx].value();
+                let mut changed = st.widgets[idx].keyboard_input(&custom_event);
+                if st.widgets[idx].value() != val {
+                    changed = true;
+                }
+                if changed {
+                    st.upload_vertices();
+                    self.redraw = true;
+                }
+            }
+        }
+    }
+}
+
+impl WindowHandler for AppState {
+    fn configure(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _window: &XdgWindow,
+        configure: WindowConfigure,
+        _serial: u32,
+    ) {
+        let (w, h) = configure.new_size;
+        if let (Some(w), Some(h)) = (w, h) {
+            let width = w.get();
+            let height = h.get();
             if let Some(state) = &mut self.state {
-                state.window.request_redraw();
+                state.resize(width, height);
             }
         }
+        self.redraw = true;
+    }
+
+    fn request_close(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _window: &XdgWindow) {
+        self.exit = true;
     }
 }
 
-fn demo_positions(sw: f32, sh: f32) -> Vec<(f32, f32, f32, f32)> {
-    vec![
-        (0.0, 0.0, sw, 40.0),                        // 0 header
-        (0.0, 40.0, 60.0, sh - 68.0),                // 1 sidebar
-        (60.0, 40.0, sw - 60.0, sh - 68.0),          // 2 content_bg
-        (70.0, 50.0, 140.0, 40.0),                   // 3 btn_a
-        (220.0, 50.0, 140.0, 40.0),                  // 4 btn_b
-        (370.0, 50.0, 140.0, 40.0),                  // 5 btn_c
-        (70.0, 100.0, 400.0, 250.0),                 // 6 panel
-        (70.0, 360.0, 140.0, 40.0),                  // 7 click_me
-        (220.0, 360.0, 140.0, 40.0),                 // 8 reset
-        (70.0, 410.0, 24.0, 24.0),                   // 9 checkbox
-        (104.0, 410.0, 48.0, 24.0),                  // 10 toggle
-        (162.0, 410.0, 160.0, 24.0),                 // 11 progress_bar
-        (70.0, 444.0, 300.0, 32.0),                  // 12 slider
-        (70.0, 486.0, 120.0, 32.0),                  // 13 spinbox
-        (0.0, sh - 28.0, sw, 28.0),                  // 14 status_bar
-    ]
+impl ProvidesRegistryState for AppState {
+    fn registry(&mut self) -> &mut RegistryState {
+        &mut self.registry_state
+    }
+    
+    fn runtime_add_global(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _name: u32,
+        _interface: &str,
+        _version: u32,
+    ) {}
+    
+    fn runtime_remove_global(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _name: u32,
+        _interface: &str,
+    ) {}
 }
 
-fn main() {
-    let event_loop = EventLoop::new().unwrap();
-    event_loop.set_control_flow(ControlFlow::Poll);
+delegate_compositor!(AppState);
+delegate_xdg_shell!(AppState);
+delegate_xdg_window!(AppState);
+delegate_shm!(AppState);
+delegate_seat!(AppState);
+delegate_pointer!(AppState);
+delegate_keyboard!(AppState);
+delegate_registry!(AppState);
+delegate_output!(AppState);
 
-    let mut app = App::new();
-    event_loop.run_app(&mut app).unwrap();
+fn main() {
+    let conn = Connection::connect_to_env().unwrap();
+    let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
+    let qh = event_queue.handle();
+
+    let compositor_state = CompositorState::bind(&globals, &qh).unwrap();
+    let xdg_shell_state = XdgShell::bind(&globals, &qh).unwrap();
+    let shm_state = Shm::bind(&globals, &qh).unwrap();
+    let seat_state = SeatState::new(&globals, &qh);
+    let output_state = OutputState::new(&globals, &qh);
+
+    let surface = compositor_state.create_surface(&qh);
+    let window = xdg_shell_state.create_window(surface.clone(), WindowDecorations::None, &qh);
+    window.set_title("Clear UI - Test Window");
+    window.set_app_id("clear-ui");
+    window.set_min_size(Some((1024, 768)));
+    window.commit();
+
+    let wayland_handle = Box::leak(Box::new(clear_ui::wayland::WaylandSurfaceHandle {
+        display_ptr: conn.backend().display_id().as_ptr() as *mut std::ffi::c_void,
+        surface_ptr: surface.id().as_ptr() as *mut std::ffi::c_void,
+    }));
+
+    let state = pollster::block_on(State::new(wayland_handle, 1024, 768));
+
+    let mut app = AppState {
+        registry_state: RegistryState::new(&globals),
+        compositor_state,
+        xdg_shell_state,
+        shm_state,
+        seat_state,
+        output_state,
+        seats: Vec::new(),
+        pointer: None,
+        keyboard: None,
+        window,
+        surface,
+        state: Some(state),
+        exit: false,
+        redraw: true,
+    };
+
+    let mut event_loop = EventLoop::try_new().unwrap();
+    let loop_handle = event_loop.handle();
+    WaylandSource::new(conn, event_queue).insert(loop_handle).unwrap();
+
+    loop {
+        event_loop
+            .dispatch(std::time::Duration::from_millis(16), &mut app)
+            .unwrap();
+        if app.exit {
+            break;
+        }
+        if app.redraw {
+            app.redraw = false;
+            if let Some(state) = &mut app.state {
+                state.render();
+            }
+        }
+    }
 }
diff --git a/src/wayland.rs b/src/wayland.rs
new file mode 100644
index 0000000..d853205
--- /dev/null
+++ b/src/wayland.rs
@@ -0,0 +1,31 @@
+use raw_window_handle::{
+    DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle,
+    RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, WindowHandle,
+};
+
+#[derive(Debug, Clone, Copy)]
+pub struct WaylandSurfaceHandle {
+    pub display_ptr: *mut std::ffi::c_void,
+    pub surface_ptr: *mut std::ffi::c_void,
+}
+
+unsafe impl Send for WaylandSurfaceHandle {}
+unsafe impl Sync for WaylandSurfaceHandle {}
+
+impl HasDisplayHandle for WaylandSurfaceHandle {
+    fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
+        let raw = RawDisplayHandle::Wayland(WaylandDisplayHandle::new(
+            std::ptr::NonNull::new(self.display_ptr).ok_or(HandleError::Unavailable)?,
+        ));
+        unsafe { Ok(DisplayHandle::borrow_raw(raw)) }
+    }
+}
+
+impl HasWindowHandle for WaylandSurfaceHandle {
+    fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
+        let raw = RawWindowHandle::Wayland(WaylandWindowHandle::new(
+            std::ptr::NonNull::new(self.surface_ptr).ok_or(HandleError::Unavailable)?,
+        ));
+        unsafe { Ok(WindowHandle::borrow_raw(raw)) }
+    }
+}
diff --git a/src/widget.rs b/src/widget.rs
index beb3888..eac70ff 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -1,5 +1,62 @@
-use winit::event::{ElementState, KeyEvent, MouseButton, MouseScrollDelta};
-use winit::keyboard::{Key, NamedKey};
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ElementState {
+    Pressed,
+    Released,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum MouseButton {
+    Left,
+    Right,
+    Middle,
+    Back,
+    Forward,
+    Other(u16),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Position {
+    pub x: f64,
+    pub y: f64,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum MouseScrollDelta {
+    LineDelta(f32, f32),
+    PixelDelta(Position),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum Key {
+    Named(NamedKey),
+    Character(String),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum NamedKey {
+    Backspace,
+    Tab,
+    Enter,
+    Escape,
+    Space,
+    ArrowDown,
+    ArrowLeft,
+    ArrowRight,
+    ArrowUp,
+    End,
+    Home,
+    PageDown,
+    PageUp,
+    Delete,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct KeyEvent {
+    pub state: ElementState,
+    pub logical_key: Key,
+    pub text: Option<String>,
+    pub repeat: bool,
+}
 
 use crate::colors;
 
@@ -2898,10 +2955,117 @@ impl Widget for Spreadsheet {
     }
 }
 
+#[derive(Debug, Clone)]
+pub struct ScrollBox {
+    x: f32, y: f32, w: f32, h: f32,
+    pub scroll_y: f32,
+    pub content_h: f32,
+    pub viewport_y: f32,
+    pub viewport_h: f32,
+    hovered: bool,
+}
+
+impl ScrollBox {
+    pub fn new() -> Self {
+        Self {
+            x: 0.0, y: 0.0, w: 0.0, h: 0.0,
+            scroll_y: 0.0,
+            content_h: 0.0,
+            viewport_y: 0.0,
+            viewport_h: 0.0,
+            hovered: false,
+        }
+    }
+
+    pub fn update_bounds(&mut self, content_h: f32, viewport_y: f32, viewport_h: f32) {
+        self.content_h = content_h;
+        self.viewport_y = viewport_y;
+        self.viewport_h = viewport_h;
+        let max_scroll = (content_h - viewport_h).max(0.0);
+        self.scroll_y = self.scroll_y.clamp(0.0, max_scroll);
+    }
+
+    pub fn get_item_draw_y(&self, virtual_y: f32, item_h: f32) -> Option<f32> {
+        let draw_y = self.viewport_y + virtual_y - self.scroll_y;
+        if draw_y >= self.viewport_y + 2.0 && draw_y + item_h <= self.viewport_y + self.viewport_h - 2.0 {
+            Some(draw_y)
+        } else {
+            None
+        }
+    }
+}
+
+impl Widget for ScrollBox {
+    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
+    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
+    fn color(&self) -> [f32; 4] { [0.08, 0.08, 0.12, 0.3] }
+    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
+    fn hovered(&self) -> bool { self.hovered }
+    fn hover_highlight(&self) -> Option<[f32; 4]> { None }
+
+    fn cursor_moved(&mut self, px: f32, py: f32) -> bool {
+        let was = self.hovered;
+        self.hovered = self.hit_test(px, py);
+        was != self.hovered
+    }
+
+    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32) -> bool {
+        if self.hit_test(px, py) {
+            let scroll_speed = 24.0;
+            let dy = match delta {
+                MouseScrollDelta::LineDelta(_, y) => -y * scroll_speed,
+                MouseScrollDelta::PixelDelta(pos) => -pos.y as f32,
+            };
+            let old_scroll = self.scroll_y;
+            let max_scroll = (self.content_h - self.viewport_h).max(0.0);
+            self.scroll_y = (self.scroll_y + dy).clamp(0.0, max_scroll);
+            (self.scroll_y - old_scroll).abs() > 0.01
+        } else {
+            false
+        }
+    }
+
+    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+        let mut quads = Vec::new();
+        
+        // Background
+        quads.push((self.x, self.y, self.w, self.h, [0.08, 0.08, 0.12, 0.3]));
+
+        // Border lines
+        let box_border_color = [0.18, 0.18, 0.24, 1.0];
+        quads.push((self.x, self.y, self.w, 1.0, box_border_color)); // Top
+        quads.push((self.x, self.y + self.h - 1.0, self.w, 1.0, box_border_color)); // Bottom
+        quads.push((self.x, self.y, 1.0, self.h, box_border_color)); // Left
+        quads.push((self.x + self.w - 1.0, self.y, 1.0, self.h, box_border_color)); // Right
+
+        // Scrollbar
+        if self.content_h > self.viewport_h {
+            let sb_x = self.x + self.w - 8.0;
+            let sb_w = 4.0;
+            let sb_track_h = self.viewport_h - 8.0;
+            let sb_track_y = self.viewport_y + 4.0;
+
+            // Track
+            quads.push((sb_x, sb_track_y, sb_w, sb_track_h, [0.15, 0.15, 0.20, 0.3]));
+
+            // Thumb
+            let visible_ratio = self.viewport_h / self.content_h;
+            let thumb_h = (sb_track_h * visible_ratio).clamp(20.0, sb_track_h);
+            let max_scroll = (self.content_h - self.viewport_h).max(0.0);
+            let scroll_ratio = if max_scroll > 0.0 { self.scroll_y / max_scroll } else { 0.0 };
+            let thumb_y = sb_track_y + scroll_ratio * (sb_track_h - thumb_h);
+
+            quads.push((sb_x, thumb_y, sb_w, thumb_h, [0.60, 0.60, 0.65, 0.4]));
+        }
+
+        quads
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
-    use winit::event::{ElementState, MouseButton};
+
 
     #[test]
     fn test_node_toggle_geometry_visibility() {
@@ -3144,4 +3308,41 @@ mod tests {
         // but rows shouldn't be
         assert_eq!(labels.len(), 1); // Only header ColA
     }
+
+    #[test]
+    fn test_scroll_box_bounds_scrolling() {
+        let mut sb = ScrollBox::new();
+        sb.set_rect(10.0, 20.0, 100.0, 100.0);
+        
+        // 1. Initially scroll is 0
+        assert_eq!(sb.scroll_y, 0.0);
+
+        // 2. Update bounds: content_h = 150 (greater than viewport_h = 100)
+        sb.update_bounds(150.0, 20.0, 100.0);
+        assert_eq!(sb.scroll_y, 0.0);
+        assert_eq!(sb.content_h, 150.0);
+        assert_eq!(sb.viewport_h, 100.0);
+
+        // 3. Scroll inside bounds
+        let delta = MouseScrollDelta::LineDelta(0.0, -2.0); // scroll down by 2 lines (48px)
+        let changed = sb.mouse_wheel(&delta, 50.0, 50.0);
+        assert!(changed);
+        assert_eq!(sb.scroll_y, 48.0);
+
+        // 4. Clamps at max scroll: 150 - 100 = 50
+        let delta_large = MouseScrollDelta::LineDelta(0.0, -10.0);
+        sb.mouse_wheel(&delta_large, 50.0, 50.0);
+        assert_eq!(sb.scroll_y, 50.0);
+
+        // 5. Test item draw coordinates
+        // Virtual item at virtual_y = 10, item_h = 24
+        // Screen draw y = viewport_y + virtual_y - scroll_y = 20 + 10 - 50 = -20
+        // -20 < viewport_y + 2.0 (22.0), so it should return None (not visible)
+        assert!(sb.get_item_draw_y(10.0, 24.0).is_none());
+
+        // Virtual item at virtual_y = 60, item_h = 24
+        // Screen draw y = 20 + 60 - 50 = 30
+        // 30 >= 22.0 and 30 + 24 <= 118.0, so it should return Some(30.0)
+        assert_eq!(sb.get_item_draw_y(60.0, 24.0), Some(30.0));
+    }
 }