From acb02397dbe25374bcba86efd2b9e9ee0ad495f3 Mon Sep 17 00:00:00 2001 From: HigherOrderLogic <73709188+HigherOrderLogic@users.noreply.github.com> Date: Mon, 9 Mar 2026 14:53:11 +1100 Subject: [PATCH] feat: compiled elements --- Cargo.lock | 700 +++++++++++++++++- Cargo.toml | 2 + crates/adapters/Cargo.toml | 1 + crates/adapters/src/lib.rs | 4 +- .../src/rendering/femtovg/main_window.rs | 16 +- .../src/wayland/layer_shell_platform.rs | 544 ++++++++++++++ crates/adapters/src/wayland/mod.rs | 1 + crates/adapters/src/wayland/shell_adapter.rs | 58 ++ .../src/wayland/surfaces/surface_state.rs | 30 +- crates/composition/src/lib.rs | 19 +- examples/multi-surface-compiled/Cargo.toml | 18 + examples/multi-surface-compiled/build.rs | 3 + examples/multi-surface-compiled/src/main.rs | 33 + examples/multi-surface-compiled/ui/app.slint | 35 + src/lib.rs | 3 +- src/prelude.rs | 4 +- 16 files changed, 1427 insertions(+), 44 deletions(-) create mode 100644 crates/adapters/src/wayland/layer_shell_platform.rs create mode 100644 examples/multi-surface-compiled/Cargo.toml create mode 100644 examples/multi-surface-compiled/build.rs create mode 100644 examples/multi-surface-compiled/src/main.rs create mode 100644 examples/multi-surface-compiled/ui/app.slint diff --git a/Cargo.lock b/Cargo.lock index 598f5d7..587bc9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -52,6 +52,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "allocator-api2" version = "0.2.18" @@ -167,6 +176,23 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "arrayref" version = "0.3.8" @@ -334,6 +360,29 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" +dependencies = [ + "arrayvec", +] + [[package]] name = "base64" version = "0.22.1" @@ -370,6 +419,12 @@ dependencies = [ "syn", ] +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -385,6 +440,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + [[package]] name = "block" version = "0.1.6" @@ -431,6 +492,12 @@ dependencies = [ "cfg_aliases", ] +[[package]] +name = "built" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" + [[package]] name = "bumpalo" version = "3.16.0" @@ -550,7 +617,17 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom", + "nom 7.1.3", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", ] [[package]] @@ -759,12 +836,37 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "cursor-icon" version = "1.2.0" @@ -962,6 +1064,26 @@ dependencies = [ "log", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1018,6 +1140,21 @@ dependencies = [ "log", ] +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide 0.8.9", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "2.1.0" @@ -1144,6 +1281,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e57e16b3fe8ff4364c0661fdaac543fb38b29ea9bc9c2f45612d90adf931d2b" dependencies = [ "hashbrown 0.15.5", + "rayon", "ttf-parser 0.21.1", ] @@ -1345,6 +1483,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -1357,6 +1506,16 @@ dependencies = [ "wasip2", ] +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gif" version = "0.14.1" @@ -1450,6 +1609,17 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "harfrust" version = "0.3.2" @@ -1478,6 +1648,7 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash 0.1.5", + "rayon", ] [[package]] @@ -1628,7 +1799,9 @@ dependencies = [ "annotate-snippets", "by_address", "derive_more", + "fontdue", "i-slint-common", + "image", "itertools 0.14.0", "linked_hash_set", "lyon_extra", @@ -1636,7 +1809,10 @@ dependencies = [ "num_enum", "proc-macro2", "quote", + "rayon", + "resvg", "rowan", + "rspolib", "smol_str 0.3.4", "strum", "typed-index-collections", @@ -1835,12 +2011,31 @@ checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" dependencies = [ "bytemuck", "byteorder-lite", + "color_quant", + "exr", + "gif 0.13.3", + "image-webp 0.1.3", "num-traits", "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", "zune-core 0.4.12", "zune-jpeg 0.4.21", ] +[[package]] +name = "image-webp" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" +dependencies = [ + "byteorder-lite", + "quick-error", +] + [[package]] name = "image-webp" version = "0.2.4" @@ -1865,9 +2060,9 @@ checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" [[package]] name = "indexmap" -version = "2.12.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -1901,6 +2096,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -1918,6 +2124,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -1997,6 +2212,12 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" + [[package]] name = "js-sys" version = "0.3.81" @@ -2060,6 +2281,7 @@ dependencies = [ "glutin", "layer-shika-domain", "log", + "ordermap", "raw-window-handle", "slint", "slint-interpreter", @@ -2097,12 +2319,34 @@ version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libloading" version = "0.8.5" @@ -2202,6 +2446,15 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "lyon_algorithms" version = "1.0.4" @@ -2252,6 +2505,15 @@ dependencies = [ "libc", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", +] + [[package]] name = "memchr" version = "2.7.4" @@ -2330,6 +2592,23 @@ dependencies = [ "log", ] +[[package]] +name = "multi-surface-compiled" +version = "0.3.1" +dependencies = [ + "env_logger", + "layer-shika", + "log", + "slint", + "slint-build", +] + +[[package]] +name = "natord" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c" + [[package]] name = "ndk" version = "0.9.0" @@ -2360,6 +2639,12 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nix" version = "0.30.1" @@ -2383,6 +2668,62 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2853,6 +3194,15 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "ordermap" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa78c92071bbd3628c22b1a964f7e0eb201dc1456555db072beb1662ecd6715" +dependencies = [ + "indexmap", +] + [[package]] name = "owned_ttf_parser" version = "0.25.1" @@ -2882,6 +3232,12 @@ dependencies = [ "swash", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2995,6 +3351,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.20" @@ -3011,7 +3376,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit", + "toml_edit 0.23.7", ] [[package]] @@ -3023,6 +3388,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "pulldown-cmark" version = "0.13.1" @@ -3042,6 +3426,15 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quick-error" version = "2.0.1" @@ -3072,6 +3465,85 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.12.1", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror 1.0.63", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -3090,6 +3562,26 @@ dependencies = [ "objc2-quartz-core 0.3.2", ] +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "read-fonts" version = "0.35.0" @@ -3154,8 +3646,8 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b563218631706d614e23059436526d005b50ab5f2d506b55a17eb65c5eb83419" dependencies = [ - "gif", - "image-webp", + "gif 0.14.1", + "image-webp 0.2.4", "log", "pico-args", "rgb", @@ -3195,6 +3687,19 @@ dependencies = [ "memchr", ] +[[package]] +name = "rspolib" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fda9a7796aff63a7b1b39ccc93fffaaf65e20042984b4843041a49ca4677535" +dependencies = [ + "lazy_static", + "natord", + "snafu", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "runtime-surface-config" version = "0.3.1" @@ -3380,6 +3885,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_spanned" version = "1.0.3" @@ -3437,6 +3951,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "simple-bar" version = "0.3.1" @@ -3484,7 +4007,7 @@ dependencies = [ "regex", "serde_json", "tar", - "toml", + "toml 0.9.8", ] [[package]] @@ -3538,6 +4061,19 @@ dependencies = [ "vtable", ] +[[package]] +name = "slint-build" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064ef470cc8ab046319db94d0727f080fbd05322d07d774eb6de607d97defb8d" +dependencies = [ + "derive_more", + "fontique", + "i-slint-compiler", + "spin_on", + "toml_edit 0.24.1+spec-1.1.0", +] + [[package]] name = "slint-interpreter" version = "1.15.1" @@ -3673,6 +4209,27 @@ dependencies = [ "serde_core", ] +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "softbuffer" version = "0.4.5" @@ -3806,6 +4363,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml 0.8.23", + "version-compare", +] + [[package]] name = "tar" version = "0.4.41" @@ -3817,6 +4387,12 @@ dependencies = [ "xattr", ] +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tempfile" version = "3.12.0" @@ -3876,6 +4452,17 @@ dependencies = [ "syn", ] +[[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-skia" version = "0.11.4" @@ -3927,6 +4514,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + [[package]] name = "toml" version = "0.9.8" @@ -3935,8 +4534,8 @@ checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ "indexmap", "serde_core", - "serde_spanned", - "toml_datetime", + "serde_spanned 1.0.3", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow", @@ -3944,13 +4543,35 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "winnow", +] + [[package]] name = "toml_edit" version = "0.23.7" @@ -3958,25 +4579,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.24.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01f2eadbbc6b377a847be05f60791ef1058d9f696ecb51d2c07fe911d8569d8e" +dependencies = [ + "indexmap", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", + "toml_writer", "winnow", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tracing" @@ -4200,6 +4834,23 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + [[package]] name = "version_check" version = "0.9.5" @@ -4239,6 +4890,12 @@ dependencies = [ "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.1+wasi-0.2.4" @@ -5277,6 +5934,15 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + [[package]] name = "zune-jpeg" version = "0.4.21" diff --git a/Cargo.toml b/Cargo.toml index 864cdff..3d12043 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ members = [ "examples/session-lock-standalone", "examples/simple-popup", "examples/surface-input-region", + "examples/multi-surface-compiled", ] [workspace.package] @@ -69,6 +70,7 @@ tracing = { version = "0.1.44", default-features = false, features = ["std"] } wayland-client = "0.31.12" wayland-protocols = { version = "0.32.10", features = ["client", "staging"] } xkbcommon = { version = "0.9.0", features = ["wayland"] } +ordermap = "1.1.0" layer-shika-domain = { version = "0.3.1", path = "crates/domain" } layer-shika-adapters = { version = "0.3.1", path = "crates/adapters", default-features = false } diff --git a/crates/adapters/Cargo.toml b/crates/adapters/Cargo.toml index d11e3cd..845c5c3 100644 --- a/crates/adapters/Cargo.toml +++ b/crates/adapters/Cargo.toml @@ -17,6 +17,7 @@ workspace = true glutin.workspace = true layer-shika-domain.workspace = true log = { workspace = true, optional = true } +ordermap.workspace = true raw-window-handle.workspace = true slint.workspace = true slint-interpreter.workspace = true diff --git a/crates/adapters/src/lib.rs b/crates/adapters/src/lib.rs index 11619fb..46d72ec 100644 --- a/crates/adapters/src/lib.rs +++ b/crates/adapters/src/lib.rs @@ -6,6 +6,7 @@ pub(crate) mod wayland; pub use rendering::femtovg::popup_window::PopupWindow; pub use wayland::config::{MultiSurfaceConfig, ShellSurfaceConfig, WaylandSurfaceConfig}; +pub use wayland::layer_shell_platform::{LayerShell, LayerShellWindow}; pub use wayland::ops::WaylandSystemOps; pub use wayland::session_lock::{ LockSurfaceOutputContext, OutputFilter, create_lock_property_operation_with_output_filter, @@ -26,7 +27,8 @@ pub(crate) mod logger { } pub mod platform { - pub use {slint, slint_interpreter}; + pub use slint; + pub use slint_interpreter; pub mod calloop { pub use smithay_client_toolkit::reexports::calloop::generic::Generic; diff --git a/crates/adapters/src/rendering/femtovg/main_window.rs b/crates/adapters/src/rendering/femtovg/main_window.rs index 71592f1..f9d8d57 100644 --- a/crates/adapters/src/rendering/femtovg/main_window.rs +++ b/crates/adapters/src/rendering/femtovg/main_window.rs @@ -1,9 +1,9 @@ use core::ops::Deref; -use std::cell::Cell; +use std::cell::{Cell, RefCell}; use std::rc::{Rc, Weak}; use slint::platform::femtovg_renderer::FemtoVGRenderer; -use slint::platform::{Renderer, WindowAdapter, WindowEvent}; +use slint::platform::{Renderer, WindowAdapter, WindowEvent, WindowProperties}; use slint::{PhysicalSize, Window, WindowSize}; use super::renderable_window::{RenderState, RenderableWindow}; @@ -16,6 +16,7 @@ pub struct FemtoVGWindow { render_state: Cell, size: Cell, scale_factor: Cell, + window_properties_handler: RefCell Fn(WindowProperties<'a>)>>>, } impl FemtoVGWindow { @@ -29,9 +30,14 @@ impl FemtoVGWindow { render_state: Cell::new(RenderState::Clean), size: Cell::new(PhysicalSize::default()), scale_factor: Cell::new(1.), + window_properties_handler: RefCell::new(None), } }) } + + pub fn set_window_properties_handler(&self, handler: Rc Fn(WindowProperties<'a>)>) { + self.window_properties_handler.replace(Some(handler)); + } } impl RenderableWindow for FemtoVGWindow { @@ -89,6 +95,12 @@ impl WindowAdapter for FemtoVGWindow { fn request_redraw(&self) { RenderableWindow::request_redraw(self); } + + fn update_window_properties(&self, properties: WindowProperties<'_>) { + if let Some(handler) = self.window_properties_handler.borrow().as_ref() { + handler(properties); + } + } } impl Deref for FemtoVGWindow { diff --git a/crates/adapters/src/wayland/layer_shell_platform.rs b/crates/adapters/src/wayland/layer_shell_platform.rs new file mode 100644 index 0000000..27416c9 --- /dev/null +++ b/crates/adapters/src/wayland/layer_shell_platform.rs @@ -0,0 +1,544 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use layer_shika_domain::prelude::KeyboardInteractivity; +use layer_shika_domain::value_objects::anchor::AnchorEdges; +use layer_shika_domain::value_objects::handle::SurfaceHandle; +use layer_shika_domain::value_objects::layer::Layer; +use layer_shika_domain::value_objects::margins::Margins; +use layer_shika_domain::value_objects::output_policy::OutputPolicy; +use slint::platform::{Platform, WindowAdapter, WindowProperties}; +use slint::{ + LogicalPosition, LogicalSize, PhysicalSize, PlatformError, WindowPosition, WindowSize, +}; +use ordermap::OrderMap; +use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{ + zwlr_layer_shell_v1::Layer as WaylandLayer, + zwlr_layer_surface_v1::{ + Anchor, KeyboardInteractivity as WaylandKeyboardInteractivity, ZwlrLayerSurfaceV1, + }, +}; +use slint::platform::femtovg_renderer::FemtoVGRenderer; +use wayland_client::backend::ObjectId; +use wayland_client::protocol::wl_output::WlOutput; +use wayland_client::protocol::wl_surface::WlSurface; +use wayland_client::Proxy; + +use crate::logger; +use crate::rendering::egl::context_factory::RenderContextFactory; +use crate::rendering::femtovg::main_window::FemtoVGWindow; +use crate::rendering::femtovg::renderable_window::RenderableWindow; +use crate::wayland::config::LayerSurfaceConfig; +use crate::wayland::globals::context::GlobalContext; +use crate::wayland::surfaces::app_state::AppState; +use crate::wayland::surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams}; +use crate::wayland::surfaces::surface_builder::SurfaceStateBuilder; +use crate::wayland::surfaces::surface_state::SurfaceState; +use crate::wayland::shell_adapter::WaylandShellSystem; + +const DEFAULT_SIZE: u32 = 50; + +#[derive(Clone)] +struct LayerShellConfig { + anchor: AnchorEdges, + margin: Margins, + exclusive_zone: Option, + keyboard_interactivity: KeyboardInteractivity, + height: Option, + width: Option, + last_preferred: Option, + layer: Layer, + scale_factor: f32, +} + +impl Default for LayerShellConfig { + fn default() -> Self { + Self { + anchor: AnchorEdges::default(), + margin: Margins::default(), + exclusive_zone: None, + keyboard_interactivity: KeyboardInteractivity::None, + height: None, + width: None, + last_preferred: None, + layer: Layer::Top, + scale_factor: 1.0, + } + } +} + +impl LayerShellConfig { + fn wayland_anchor(&self) -> Anchor { + let mut result = Anchor::empty(); + + if self.anchor.has_top() { + result = result.union(Anchor::Top); + } + if self.anchor.has_bottom() { + result = result.union(Anchor::Bottom); + } + if self.anchor.has_left() { + result = result.union(Anchor::Left); + } + if self.anchor.has_right() { + result = result.union(Anchor::Right); + } + + result + } + + fn wayland_layer(&self) -> WaylandLayer { + match self.layer { + Layer::Background => WaylandLayer::Background, + Layer::Bottom => WaylandLayer::Bottom, + Layer::Top => WaylandLayer::Top, + Layer::Overlay => WaylandLayer::Overlay, + } + } + + fn wayland_keyboard_interactivity(&self) -> WaylandKeyboardInteractivity { + match self.keyboard_interactivity { + KeyboardInteractivity::None => WaylandKeyboardInteractivity::None, + KeyboardInteractivity::Exclusive => WaylandKeyboardInteractivity::Exclusive, + KeyboardInteractivity::OnDemand => WaylandKeyboardInteractivity::OnDemand, + } + } + + fn layer_surface_config(&self) -> LayerSurfaceConfig { + let (width, height) = self.resolve_size(); + LayerSurfaceConfig { + anchor: self.wayland_anchor(), + margin: self.margin, + exclusive_zone: self.resolve_exclusive_zone(width, height), + keyboard_interactivity: self.wayland_keyboard_interactivity(), + height, + width, + } + } + + fn apply_to(&self, entry: &LayerShellSurfaceEntry) { + let (width, height) = self.resolve_size(); + entry.layer_surface.set_anchor(self.wayland_anchor()); + entry.layer_surface.set_margin( + self.margin.top, + self.margin.right, + self.margin.bottom, + self.margin.left, + ); + entry + .layer_surface + .set_exclusive_zone(self.resolve_exclusive_zone(width, height)); + entry + .layer_surface + .set_keyboard_interactivity(self.wayland_keyboard_interactivity()); + entry.layer_surface.set_size(width, height); + entry.layer_surface.set_layer(self.wayland_layer()); + entry.surface.commit(); + entry.window.set_scale_factor(self.scale_factor); + } + + fn resolve_size(&self) -> (u32, u32) { + let width = if let Some(width) = self.width { + width + } else if self.anchor.has_left() && self.anchor.has_right() { + 0 + } else if let Some(pref) = self.last_preferred { + pref.width.max(0.0).round() as u32 + } else { + DEFAULT_SIZE + }; + + let height = if let Some(height) = self.height { + height + } else if self.anchor.has_top() && self.anchor.has_bottom() { + 0 + } else if let Some(pref) = self.last_preferred { + pref.height.max(0.0).round() as u32 + } else { + DEFAULT_SIZE + }; + + (width, height) + } + + fn resolve_exclusive_zone(&self, width: u32, height: u32) -> i32 { + if let Some(zone) = self.exclusive_zone { + return zone; + } + + if self.anchor.has_top() ^ self.anchor.has_bottom() { + height as i32 + } else if self.anchor.has_left() ^ self.anchor.has_right() { + width as i32 + } else { + 0 + } + } +} + +struct LayerShellSurfaceEntry { + surface_id: ObjectId, + surface: Rc, + layer_surface: Rc, + window: Rc, +} + +struct LayerShellConfigState { + configs_by_title: HashMap, + surfaces_by_id: HashMap, + surface_id_by_title: HashMap, + title_by_surface_id: HashMap, +} + +impl LayerShellConfigState { + fn new() -> Self { + Self { + configs_by_title: HashMap::new(), + surfaces_by_id: HashMap::new(), + surface_id_by_title: HashMap::new(), + title_by_surface_id: HashMap::new(), + } + } + + fn register_surface(&mut self, entry: LayerShellSurfaceEntry) { + self.surfaces_by_id.insert(entry.surface_id.clone(), entry); + } + + fn register_title(&mut self, surface_id: &ObjectId, title: &str) { + if let Some(old_title) = self.title_by_surface_id.get(surface_id).cloned() { + if old_title != title { + self.surface_id_by_title.remove(&old_title); + } + } + + self.surface_id_by_title + .insert(title.to_string(), surface_id.clone()); + self.title_by_surface_id + .insert(surface_id.clone(), title.to_string()); + + if let Some(entry) = self.surfaces_by_id.get(surface_id) { + if let Some(config) = self.configs_by_title.get(title) { + config.apply_to(entry); + } + } + } + + fn update_preferred_size(&mut self, title: &str, preferred: LogicalSize) { + let config = self.configs_by_title.entry(title.to_string()).or_default(); + config.last_preferred = Some(preferred); + + if let Some(surface_id) = self.surface_id_by_title.get(title) { + if let Some(entry) = self.surfaces_by_id.get(surface_id) { + config.apply_to(entry); + } + } + } + + fn set_for_title(&mut self, title: &str, updater: F) + where + F: FnOnce(&mut LayerShellConfig), + { + let config = self.configs_by_title.entry(title.to_string()).or_default(); + updater(config); + + if let Some(surface_id) = self.surface_id_by_title.get(title) { + if let Some(entry) = self.surfaces_by_id.get(surface_id) { + config.apply_to(entry); + } + } + } +} + +pub struct LayerShell { + system: RefCell>, + pending_windows: RefCell>>, + config_state: Rc>, + namespaces: Rc>>, + output_policies: Rc>>, +} + +impl LayerShell { + pub fn new() -> Result, PlatformError> { + Ok(Box::new(Self { + system: RefCell::new(None), + pending_windows: RefCell::new(Vec::new()), + config_state: Rc::new(RefCell::new(LayerShellConfigState::new())), + namespaces: Rc::new(RefCell::new(OrderMap::new())), + output_policies: Rc::new(RefCell::new(OrderMap::new())), + })) + } + + pub fn window(&self, title: impl Into) -> LayerShellWindow { + LayerShellWindow { + title: title.into(), + config_state: Rc::clone(&self.config_state), + namespaces: Rc::clone(&self.namespaces), + output_policies: Rc::clone(&self.output_policies), + } + } + + fn ensure_system(&self) -> Result<(), PlatformError> { + if self.system.borrow().is_some() { + return Ok(()); + } + + let system = WaylandShellSystem::new_layer_shell() + .map_err(|e| PlatformError::Other(format!("Wayland init failed: {e}")))?; + *self.system.borrow_mut() = Some(system); + Ok(()) + } + + fn select_output<'a>( + global_ctx: &'a GlobalContext, + app_state: &mut AppState, + policy: &OutputPolicy, + ) -> Result<&'a WlOutput, PlatformError> { + if global_ctx.outputs.is_empty() { + return Err(PlatformError::Other("No outputs available".to_string())); + } + + for output in &global_ctx.outputs { + let handle = app_state.ensure_output_registered(&output.id()); + if let Some(info) = app_state.get_output_info(handle) { + if policy.should_render(info) { + return Ok(output); + } + } + } + + Err(PlatformError::Other( + "No outputs matched output policy".to_string(), + )) + } +} + +impl Platform for LayerShell { + fn create_window_adapter(&self) -> Result, PlatformError> { + self.ensure_system()?; + let mut system_ref = self.system.borrow_mut(); + let system = system_ref + .as_mut() + .ok_or_else(|| PlatformError::Other("Wayland system unavailable".to_string()))?; + + let global_ctx = system + .global_context() + .ok_or_else(|| PlatformError::Other("Wayland globals unavailable".to_string()))?; + let render_factory = + RenderContextFactory::new(Rc::clone(&global_ctx.render_context_manager)); + let pointer = Rc::new( + global_ctx + .seat + .get_pointer(&system.event_queue_handle(), ()), + ); + let shared_serial = system + .shared_serial() + .ok_or_else(|| PlatformError::Other("Wayland serial unavailable".to_string()))?; + + let layer_shell = global_ctx.layer_shell.as_ref().ok_or_else(|| { + PlatformError::Other( + "wlr-layer-shell protocol not available - cannot create layer surfaces".to_string(), + ) + })?; + + let output_policy = self + .output_policies + .borrow_mut() + .remove_index(0) + .map_or_else(|| OutputPolicy::PrimaryOnly, |(_, policy)| policy); + + let output = + LayerShell::select_output(&global_ctx, system.app_state_mut(), &output_policy)?; + + let config = LayerShellConfig::default(); + let namespace = self + .namespaces + .borrow_mut() + .remove_index(0) + .map_or_else(|| "layer-shika".into(), |(_, n)| n); + + let queue_handle = system.event_queue_handle(); + + let setup_params = SurfaceSetupParams { + compositor: &global_ctx.compositor, + output, + layer_shell, + fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(), + viewporter: global_ctx.viewporter.as_ref(), + queue_handle: &queue_handle, + layer: config.wayland_layer(), + namespace, + }; + + let surface_ctx = SurfaceCtx::setup(&setup_params, &config.layer_surface_config()); + let main_surface_id = surface_ctx.surface.id(); + + let init_size = PhysicalSize::new(1, 1); + let context = render_factory + .create_context(&surface_ctx.surface.id(), init_size) + .map_err(|e| PlatformError::Other(format!("Renderer context failed: {e}")))?; + let renderer = FemtoVGRenderer::new(context) + .map_err(|e| PlatformError::Other(format!("Renderer init failed: {e}")))?; + let window = FemtoVGWindow::new(renderer); + window.set_size(WindowSize::Physical(init_size)); + window.set_scale_factor(config.scale_factor); + window.set_position(WindowPosition::Logical(LogicalPosition::new(0., 0.))); + + let (width, height) = config.resolve_size(); + let exclusive_zone = config.resolve_exclusive_zone(width, height); + let connection = system.connection(); + let mut builder = SurfaceStateBuilder::new() + .with_surface(Rc::clone(&surface_ctx.surface)) + .with_layer_surface(Rc::clone(&surface_ctx.layer_surface)) + .with_scale_factor(config.scale_factor) + .with_height(height) + .with_width(width) + .with_exclusive_zone(exclusive_zone) + .with_connection(Rc::clone(&connection)) + .with_pointer(Rc::clone(&pointer)) + .with_window(Rc::clone(&window)); + + if let Some(fs) = &surface_ctx.fractional_scale { + builder = builder.with_fractional_scale(Rc::clone(fs)); + } + + if let Some(vp) = &surface_ctx.viewport { + builder = builder.with_viewport(Rc::clone(vp)); + } + + let mut surface_state = SurfaceState::new(builder) + .map_err(|e| PlatformError::Other(format!("Surface init failed: {e}")))?; + surface_state.set_shared_pointer_serial(Rc::clone(&shared_serial)); + + let output_id = output.id(); + let surface_handle = SurfaceHandle::new(); + let surface_name = "slint-window".to_string(); + + system.app_state_mut().add_shell_surface( + &output_id, + surface_handle, + &surface_name, + main_surface_id.clone(), + surface_state, + ); + + let entry = LayerShellSurfaceEntry { + surface_id: main_surface_id.clone(), + surface: Rc::clone(&surface_ctx.surface), + layer_surface: Rc::clone(&surface_ctx.layer_surface), + window: Rc::clone(&window), + }; + self.config_state.borrow_mut().register_surface(entry); + RenderableWindow::request_redraw(window.as_ref()); + + let config_state = Rc::clone(&self.config_state); + window.set_window_properties_handler(Rc::new(move |properties: WindowProperties<'_>| { + let title = properties.title(); + if title.is_empty() { + return; + } + let preferred = properties.layout_constraints().preferred; + let mut state = config_state.borrow_mut(); + state.register_title(&main_surface_id, title.as_ref()); + state.update_preferred_size(&title, preferred); + })); + + let window_adapter = window as Rc; + self.pending_windows + .borrow_mut() + .push(Rc::clone(&window_adapter)); + + Ok(window_adapter) + } + + fn run_event_loop(&self) -> Result<(), PlatformError> { + logger::info!("Starting LayerShell event loop"); + self.ensure_system()?; + + let mut system_ref = self.system.borrow_mut(); + let system = system_ref + .as_mut() + .ok_or_else(|| PlatformError::Other("Wayland system unavailable".to_string()))?; + + self.pending_windows.borrow_mut().clear(); + system + .run() + .map_err(|e| PlatformError::Other(format!("Event loop failed: {e}"))) + } +} + +pub struct LayerShellWindow { + title: String, + config_state: Rc>, + namespaces: Rc>>, + output_policies: Rc>>, +} + +impl LayerShellWindow { + pub fn namespace(self, namespace: impl Into) -> Self { + let namespace = namespace.into(); + self.namespaces + .borrow_mut() + .insert(self.title.clone(), namespace); + self + } + + pub fn output_policy(self, policy: OutputPolicy) -> Self { + self.output_policies + .borrow_mut() + .insert(self.title.clone(), policy); + self + } + + pub fn anchor(self, anchor: AnchorEdges) -> Self { + self.config_state + .borrow_mut() + .set_for_title(&self.title, |config| config.anchor = anchor); + self + } + + pub fn exclusive_zone(self, zone: i32) -> Self { + self.config_state + .borrow_mut() + .set_for_title(&self.title, |config| config.exclusive_zone = Some(zone)); + self + } + + pub fn layer(self, layer: Layer) -> Self { + self.config_state + .borrow_mut() + .set_for_title(&self.title, |config| config.layer = layer); + self + } + + pub fn margins(self, margins: Margins) -> Self { + self.config_state + .borrow_mut() + .set_for_title(&self.title, |config| config.margin = margins); + self + } + + pub fn keyboard_interactivity(self, mode: KeyboardInteractivity) -> Self { + self.config_state + .borrow_mut() + .set_for_title(&self.title, |config| config.keyboard_interactivity = mode); + self + } + + pub fn size(self, width: u32, height: u32) -> Self { + self.config_state + .borrow_mut() + .set_for_title(&self.title, |config| { + config.width = Some(width); + config.height = Some(height); + }); + self + } + + pub fn scale_factor(self, scale_factor: f32) -> Self { + self.config_state + .borrow_mut() + .set_for_title(&self.title, |config| config.scale_factor = scale_factor); + self + } +} diff --git a/crates/adapters/src/wayland/mod.rs b/crates/adapters/src/wayland/mod.rs index bcddde6..f929ad2 100644 --- a/crates/adapters/src/wayland/mod.rs +++ b/crates/adapters/src/wayland/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod config; pub(crate) mod event_handling; pub(crate) mod globals; pub(crate) mod input; +pub(crate) mod layer_shell_platform; pub(crate) mod managed_proxies; pub mod ops; pub(crate) mod outputs; diff --git a/crates/adapters/src/wayland/shell_adapter.rs b/crates/adapters/src/wayland/shell_adapter.rs index fa3ef98..6db4c59 100644 --- a/crates/adapters/src/wayland/shell_adapter.rs +++ b/crates/adapters/src/wayland/shell_adapter.rs @@ -68,6 +68,8 @@ pub struct WaylandShellSystem { connection: Rc, event_queue: EventQueue, event_loop: EventLoop<'static, AppState>, + global_ctx: Option>, + shared_serial: Option>, } impl WaylandShellSystem { @@ -84,6 +86,8 @@ impl WaylandShellSystem { connection, event_queue, event_loop, + global_ctx: None, + shared_serial: None, }) } @@ -107,6 +111,8 @@ impl WaylandShellSystem { connection, event_queue, event_loop, + global_ctx: None, + shared_serial: None, }) } @@ -123,6 +129,42 @@ impl WaylandShellSystem { connection, event_queue, event_loop, + global_ctx: None, + shared_serial: None, + }) + } + + pub fn new_layer_shell() -> Result { + logger::info!("Initializing WindowingSystem for LayerShell platform"); + let (connection, event_queue) = Self::init_wayland_connection()?; + let event_loop = + EventLoop::try_new().map_err(|e| EventLoopError::Creation { source: e })?; + + let global_ctx = Rc::new(GlobalContext::initialize( + connection.as_ref(), + &event_queue.handle(), + )?); + + let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ())); + let keyboard = Rc::new(global_ctx.seat.get_keyboard(&event_queue.handle(), ())); + let shared_serial = Rc::new(SharedPointerSerial::new()); + + let mut state = AppState::new( + ManagedWlPointer::new(Rc::clone(&pointer), Rc::new(connection.as_ref().clone())), + ManagedWlKeyboard::new(Rc::clone(&keyboard), Rc::new(connection.as_ref().clone())), + Rc::clone(&shared_serial), + ); + + state.set_queue_handle(event_queue.handle()); + state.set_global_context(Rc::clone(&global_ctx)); + + Ok(Self { + state, + connection, + event_queue, + event_loop, + global_ctx: Some(global_ctx), + shared_serial: Some(shared_serial), }) } @@ -813,6 +855,22 @@ impl WaylandShellSystem { &mut self.state } + pub fn connection(&self) -> Rc { + Rc::clone(&self.connection) + } + + pub fn event_queue_handle(&self) -> QueueHandle { + self.event_queue.handle() + } + + pub fn global_context(&self) -> Option> { + self.global_ctx.clone() + } + + pub fn shared_serial(&self) -> Option> { + self.shared_serial.clone() + } + pub fn spawn_surface(&mut self, config: &ShellSurfaceConfig) -> Result> { logger::info!("Spawning new surface '{}'", config.name); diff --git a/crates/adapters/src/wayland/surfaces/surface_state.rs b/crates/adapters/src/wayland/surfaces/surface_state.rs index 3a1f4e0..daabea0 100644 --- a/crates/adapters/src/wayland/surfaces/surface_state.rs +++ b/crates/adapters/src/wayland/surfaces/surface_state.rs @@ -29,7 +29,7 @@ use wayland_client::{ use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; pub struct SurfaceState { - component: ComponentState, + component: Option, rendering: RenderingState, event_context: RefCell, display_metrics: SharedDisplayMetrics, @@ -39,20 +39,21 @@ pub struct SurfaceState { impl SurfaceState { pub fn new(builder: SurfaceStateBuilder) -> Result { - let component_definition = - builder - .component_definition - .ok_or_else(|| LayerShikaError::InvalidInput { - message: "Component definition is required".into(), - })?; let window = builder .window .ok_or_else(|| LayerShikaError::InvalidInput { message: "Window is required".into(), })?; - let component = - ComponentState::new(component_definition, builder.compilation_result, &window)?; + let component = if let Some(component_definition) = builder.component_definition { + Some(ComponentState::new( + component_definition, + builder.compilation_result, + &window, + )?) + } else { + None + }; let connection = builder .connection @@ -211,13 +212,18 @@ impl SurfaceState { self.display_metrics.borrow().output_size() } - pub const fn component_instance(&self) -> &ComponentInstance { - self.component.component_instance() + pub fn component_instance(&self) -> &ComponentInstance { + self.component + .as_ref() + .expect("Surface has no Slint component instance") + .component_instance() } #[must_use] pub fn compilation_result(&self) -> Option> { - self.component.compilation_result() + self.component + .as_ref() + .and_then(ComponentState::compilation_result) } pub fn render_frame_if_dirty(&self) -> Result<()> { diff --git a/crates/composition/src/lib.rs b/crates/composition/src/lib.rs index ce69cfb..e1ebfd4 100644 --- a/crates/composition/src/lib.rs +++ b/crates/composition/src/lib.rs @@ -18,9 +18,9 @@ pub mod value_conversion; use std::result::Result as StdResult; pub use event_loop::{EventLoopHandle, ShellEventLoop}; -pub use layer_shika_adapters::PopupWindow; use layer_shika_adapters::errors::LayerShikaError; pub use layer_shika_adapters::platform::{slint, slint_interpreter}; +pub use layer_shika_adapters::{LayerShell, LayerShellWindow, PopupWindow}; pub use layer_shika_domain::entities::output_registry::OutputRegistry; use layer_shika_domain::errors::DomainError; pub use layer_shika_domain::prelude::AnchorStrategy; @@ -107,13 +107,14 @@ pub mod prelude { pub use crate::{ AnchorEdges, AnchorStrategy, CompiledUiSource, DEFAULT_COMPONENT_NAME, DEFAULT_SURFACE_NAME, EventDispatchContext, EventLoopHandle, Handle, IntoValue, - KeyboardInteractivity, Layer, LayerSurfaceHandle, LockSelection, Output, OutputGeometry, - OutputHandle, OutputInfo, OutputPolicy, OutputRegistry, PopupBuilder, PopupConfig, - PopupHandle, PopupPosition, PopupShell, PopupSize, PopupWindow, PropertyError, Result, - Selection, SelectionResult, Selector, SessionLock, SessionLockBuilder, Shell, ShellBuilder, - ShellConfig, ShellControl, ShellEventContext, ShellEventLoop, ShellRuntime, - ShellSurfaceConfigHandler, Surface, SurfaceComponentConfig, SurfaceConfigBuilder, - SurfaceControlHandle, SurfaceDefinition, SurfaceEntry, SurfaceHandle, SurfaceInfo, - SurfaceMetadata, SurfaceRegistry, slint, slint_interpreter, + KeyboardInteractivity, Layer, LayerShell, LayerShellWindow, LayerSurfaceHandle, + LockSelection, Output, OutputGeometry, OutputHandle, OutputInfo, OutputPolicy, + OutputRegistry, PopupBuilder, PopupConfig, PopupHandle, PopupPosition, PopupShell, + PopupSize, PopupWindow, PropertyError, Result, Selection, SelectionResult, Selector, + SessionLock, SessionLockBuilder, Shell, ShellBuilder, ShellConfig, ShellControl, + ShellEventContext, ShellEventLoop, ShellRuntime, ShellSurfaceConfigHandler, Surface, + SurfaceComponentConfig, SurfaceConfigBuilder, SurfaceControlHandle, SurfaceDefinition, + SurfaceEntry, SurfaceHandle, SurfaceInfo, SurfaceMetadata, SurfaceRegistry, slint, + slint_interpreter, }; } diff --git a/examples/multi-surface-compiled/Cargo.toml b/examples/multi-surface-compiled/Cargo.toml new file mode 100644 index 0000000..0c13d55 --- /dev/null +++ b/examples/multi-surface-compiled/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "multi-surface-compiled" +version.workspace = true +edition.workspace = true +license.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +layer-shika = { path = "../.." } +env_logger = "0.11.7" +log.workspace = true +slint.workspace = true + +[build-dependencies] +slint-build = "1.15.1" diff --git a/examples/multi-surface-compiled/build.rs b/examples/multi-surface-compiled/build.rs new file mode 100644 index 0000000..688393d --- /dev/null +++ b/examples/multi-surface-compiled/build.rs @@ -0,0 +1,3 @@ +fn main() { + slint_build::compile("ui/app.slint").expect("Slint build failed"); +} diff --git a/examples/multi-surface-compiled/src/main.rs b/examples/multi-surface-compiled/src/main.rs new file mode 100644 index 0000000..23a4fb4 --- /dev/null +++ b/examples/multi-surface-compiled/src/main.rs @@ -0,0 +1,33 @@ +use layer_shika::prelude::*; + +slint::include_modules!(); + +fn main() -> Result<()> { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .init(); + + let platform = LayerShell::new().unwrap(); + + platform + .window("TopBar") + .namespace("top-bar") + .anchor(AnchorEdges::top_bar()); + + platform + .window("BottomBar") + .namespace("bottom-bar") + .layer(Layer::Overlay) + .anchor(AnchorEdges::bottom_bar()) + .exclusive_zone(0); + + slint::platform::set_platform(platform).unwrap(); + + let top = TopBar::new().unwrap(); + let bottom = BottomBar::new().unwrap(); + + top.show().unwrap(); + bottom.run().unwrap(); + + Ok(()) +} diff --git a/examples/multi-surface-compiled/ui/app.slint b/examples/multi-surface-compiled/ui/app.slint new file mode 100644 index 0000000..ac40961 --- /dev/null +++ b/examples/multi-surface-compiled/ui/app.slint @@ -0,0 +1,35 @@ +import { Button } from "std-widgets.slint"; + +export component TopBar inherits Window { + title: "TopBar"; + height: 60px; + Rectangle { + background: blue; + VerticalLayout { + padding: 8px; + label := Text { + text: "Button not clicked"; + color: pink; + } + Button { + text: "Click Me"; + clicked => { + label.text = "Button clicked!"; + } + } + } + } +} + +export component BottomBar inherits Window { + title: "BottomBar"; + height: 60px; + Rectangle { + background: green; + Text { + font-size: 48px; + text: "Hello"; + color: red; + } + } +} diff --git a/src/lib.rs b/src/lib.rs index ef854a1..366021b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -133,7 +133,8 @@ pub mod window; pub use event::{EventDispatchContext, EventLoopHandle, ShellEventLoop}; pub use layer_shika_composition::{ - CallbackContext, Error, Handle, Result, SurfaceHandle, SurfaceInstanceId, SurfaceTarget, + CallbackContext, Error, Handle, LayerShell, LayerShellWindow, Result, SurfaceHandle, + SurfaceInstanceId, SurfaceTarget, }; pub use output::{OutputGeometry, OutputHandle, OutputInfo, OutputPolicy, OutputRegistry}; pub use shell::{ diff --git a/src/prelude.rs b/src/prelude.rs index d302f14..8ce3703 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -28,6 +28,6 @@ pub use crate::window::{ PopupHandle, PopupPosition, PopupShell, PopupSize, }; pub use crate::{ - CallbackContext, Error, Handle, Result, SurfaceHandle, SurfaceInstanceId, SurfaceTarget, - calloop, + CallbackContext, Error, Handle, LayerShell, LayerShellWindow, Result, SurfaceHandle, + SurfaceInstanceId, SurfaceTarget, calloop, };