From 90d2c418a73af0669302e58fb6a0978bb73a7436 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 22 Apr 2026 14:36:39 -0600 Subject: [PATCH 01/51] chore: update to wgpu 28 and cosmic-text 0.19 --- Cargo.lock | 179 ++++++------------- Cargo.toml | 9 +- examples/custom_shader/src/scene/pipeline.rs | 12 +- examples/integration/src/scene.rs | 5 +- wgpu/src/buffer.rs | 3 - wgpu/src/color.rs | 5 +- wgpu/src/image/atlas.rs | 5 +- wgpu/src/image/cache.rs | 10 +- wgpu/src/image/mod.rs | 13 +- wgpu/src/lib.rs | 4 + wgpu/src/quad.rs | 4 +- wgpu/src/quad/gradient.rs | 6 +- wgpu/src/quad/solid.rs | 6 +- wgpu/src/text.rs | 2 +- wgpu/src/triangle.rs | 25 +-- wgpu/src/triangle/msaa.rs | 7 +- wgpu/src/window/compositor.rs | 3 + 17 files changed, 113 insertions(+), 185 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 805e6066..383d8cfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -157,6 +157,12 @@ dependencies = [ "equator", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-activity" version = "0.6.1" @@ -1314,8 +1320,9 @@ dependencies = [ [[package]] name = "cosmic-text" -version = "0.18.2" -source = "git+https://github.com/pop-os/cosmic-text.git#4d74f795cc771fdcc7ea0f9cacba63fcf036fad6" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73" dependencies = [ "bitflags 2.11.0", "fontdb", @@ -1439,7 +1446,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "cryoglyph" version = "0.1.0" -source = "git+https://github.com/pop-os/glyphon.git?tag=cosmic-0.14#c49de15bce4d8254ac136d1be9911960cc85ce12" +source = "git+https://github.com/iced-rs/cryoglyph.git?rev=e429a025df36ab8145708acb309080ae3deec17a#e429a025df36ab8145708acb309080ae3deec17a" dependencies = [ "cosmic-text", "etagere", @@ -1561,7 +1568,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1789,7 +1796,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2292,7 +2299,7 @@ dependencies = [ "gobject-sys", "libc", "system-deps", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2403,35 +2410,18 @@ dependencies = [ "system-deps", ] -[[package]] -name = "gpu-alloc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" -dependencies = [ - "bitflags 2.11.0", - "gpu-alloc-types", -] - -[[package]] -name = "gpu-alloc-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" -dependencies = [ - "bitflags 2.11.0", -] - [[package]] name = "gpu-allocator" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" dependencies = [ + "ash", + "hashbrown 0.16.1", "log", "presser", - "thiserror 1.0.69", - "windows 0.58.0", + "thiserror 2.0.18", + "windows 0.57.0", ] [[package]] @@ -2699,6 +2689,8 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ + "allocator-api2", + "equivalent", "foldhash 0.2.0", ] @@ -3478,7 +3470,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3536,7 +3528,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4045,9 +4037,9 @@ dependencies = [ [[package]] name = "metal" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" +checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" dependencies = [ "bitflags 2.11.0", "block", @@ -4193,9 +4185,9 @@ checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" [[package]] name = "naga" -version = "27.0.3" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" +checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135" dependencies = [ "arrayvec", "bit-set", @@ -4300,7 +4292,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5706,7 +5698,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5719,7 +5711,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6334,7 +6326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6628,7 +6620,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -7167,7 +7159,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7823,12 +7815,13 @@ checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] name = "wgpu" -version = "27.0.1" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" +checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f" dependencies = [ "arrayvec", "bitflags 2.11.0", + "bytemuck", "cfg-if", "cfg_aliases", "document-features", @@ -7852,9 +7845,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "27.0.3" +version = "28.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" +checksum = "d23f4642f53f666adcfd2d3218ab174d1e6681101aef18696b90cbe64d1c10f9" dependencies = [ "arrayvec", "bit-set", @@ -7885,45 +7878,45 @@ dependencies = [ [[package]] name = "wgpu-core-deps-apple" -version = "27.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233" +checksum = "87b7b696b918f337c486bf93142454080a32a37832ba8a31e4f48221890047da" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-emscripten" -version = "27.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5" +checksum = "34b251c331f84feac147de3c4aa3aa45112622a95dd7ee1b74384fa0458dbd79" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-wasm" -version = "27.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1027dcf3b027a877e44819df7ceb0e2e98578830f8cd34cd6c3c7c2a7a50b7" +checksum = "12a2cf578ce8d7d50d0e63ddc2345c7dcb599f6eb90b888813406ea78b9b7010" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "27.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3" +checksum = "68ca976e72b2c9964eb243e281f6ce7f14a514e409920920dcda12ae40febaae" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-hal" -version = "27.0.4" +version = "28.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce" +checksum = "44d6cb474beb218824dcc9e1ce679d973f719262789bfb27407da560cac20eeb" dependencies = [ "android_system_properties", "arrayvec", @@ -7937,7 +7930,6 @@ dependencies = [ "core-graphics-types 0.2.0", "glow", "glutin_wgl_sys", - "gpu-alloc", "gpu-allocator", "gpu-descriptor", "hashbrown 0.16.1", @@ -7964,21 +7956,20 @@ dependencies = [ "wasm-bindgen", "web-sys", "wgpu-types", - "windows 0.58.0", - "windows-core 0.58.0", + "windows 0.62.2", + "windows-core 0.62.2", ] [[package]] name = "wgpu-types" -version = "27.0.1" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" +checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c" dependencies = [ "bitflags 2.11.0", "bytemuck", "js-sys", "log", - "thiserror 2.0.18", "web-sys", ] @@ -8004,7 +7995,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -8038,16 +8029,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.61.3" @@ -8103,19 +8084,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.61.2" @@ -8175,17 +8143,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -8208,17 +8165,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-interface" version = "0.59.3" @@ -8282,15 +8228,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.3.4" @@ -8309,16 +8246,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows-strings" version = "0.4.2" diff --git a/Cargo.toml b/Cargo.toml index 49b4b5d7..d60bcb8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -202,7 +202,7 @@ repository = "https://github.com/iced-rs/iced" homepage = "https://iced.rs" categories = ["gui"] keywords = ["gui", "ui", "graphics", "interface", "widgets"] -rust-version = "1.89" +rust-version = "1.92" [workspace.dependencies] iced = { version = "0.14.0", path = "." } @@ -238,10 +238,9 @@ async-std = "1.0" bitflags = "2.5" bytemuck = { version = "1.0", features = ["derive"] } bytes = "1.6" -cosmic-text = { git = "https://github.com/pop-os/cosmic-text.git" } -# cosmic-text = "0.10" +cosmic-text = "0.19" +cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "e429a025df36ab8145708acb309080ae3deec17a" } dark-light = "1.0" -cryoglyph = { package = "cryoglyph", git = "https://github.com/pop-os/glyphon.git", tag = "cosmic-0.14" } resvg = "0.45" web-sys = "0.3.69" guillotiere = "0.6" @@ -288,7 +287,7 @@ url = "2.5" wasm-bindgen-futures = "0.4" wasmtimer = "0.4.2" web-time = "1.1" -wgpu = { version = "27.0", default-features = false, features = [ +wgpu = { version = "28.0", default-features = false, features = [ "std", "wgsl", ] } diff --git a/examples/custom_shader/src/scene/pipeline.rs b/examples/custom_shader/src/scene/pipeline.rs index 80b0d533..30262a5d 100644 --- a/examples/custom_shader/src/scene/pipeline.rs +++ b/examples/custom_shader/src/scene/pipeline.rs @@ -140,7 +140,7 @@ impl Pipeline { address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::MipmapFilterMode::Linear, ..Default::default() }); @@ -223,7 +223,7 @@ impl Pipeline { device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("cubes pipeline layout"), bind_group_layouts: &[&uniform_bind_group_layout], - push_constant_ranges: &[], + immediate_size: 0, }); let shader = @@ -280,7 +280,7 @@ impl Pipeline { compilation_options: wgpu::PipelineCompilationOptions::default(), }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -388,6 +388,7 @@ impl Pipeline { ), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_viewport( @@ -478,7 +479,7 @@ impl DepthPipeline { device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("cubes.depth_pipeline.layout"), bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], + immediate_size: 0, }); let shader = @@ -520,7 +521,7 @@ impl DepthPipeline { compilation_options: wgpu::PipelineCompilationOptions::default(), }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -586,6 +587,7 @@ impl DepthPipeline { ), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_viewport( diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs index 4e4757fa..1b248036 100644 --- a/examples/integration/src/scene.rs +++ b/examples/integration/src/scene.rs @@ -43,6 +43,7 @@ impl Scene { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }) } @@ -64,8 +65,8 @@ fn build_pipeline( let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, - push_constant_ranges: &[], bind_group_layouts: &[], + immediate_size: 0, }); device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { @@ -101,7 +102,7 @@ fn build_pipeline( mask: !0, alpha_to_coverage_enabled: false, }, - multiview: None, + multiview_mask: None, cache: None, }) } diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs index 172922da..e251e979 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -63,7 +63,6 @@ impl Buffer { /// Returns the size of the written bytes. pub fn write( &mut self, - device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, offset: usize, @@ -79,7 +78,6 @@ impl Buffer { &self.raw, (offset + bytes_written) as u64, MAX_WRITE_SIZE_U64, - device, ) .copy_from_slice( &bytes[bytes_written..bytes_written + MAX_WRITE_SIZE], @@ -100,7 +98,6 @@ impl Buffer { &self.raw, (offset + bytes_written) as u64, bytes_left, - device, ) .copy_from_slice(&bytes[bytes_written..]); diff --git a/wgpu/src/color.rs b/wgpu/src/color.rs index 52fe8463..642d4ea7 100644 --- a/wgpu/src/color.rs +++ b/wgpu/src/color.rs @@ -99,7 +99,7 @@ pub fn convert( device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu.offscreen.blit.pipeline_layout"), bind_group_layouts: &[&constant_layout, &texture_layout], - push_constant_ranges: &[], + immediate_size: 0, }); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { @@ -149,7 +149,7 @@ pub fn convert( }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -194,6 +194,7 @@ pub fn convert( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_pipeline(&pipeline); diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 09258329..d23e7452 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -132,7 +132,7 @@ impl Atlas { match &entry { Entry::Contiguous(allocation) => { self.upload_allocation( - pixels, width, 0, allocation, device, encoder, belt, + pixels, width, 0, allocation, encoder, belt, ); } Entry::Fragmented { fragments, .. } => { @@ -145,7 +145,6 @@ impl Atlas { width, offset, &fragment.allocation, - device, encoder, belt, ); @@ -312,7 +311,6 @@ impl Atlas { image_width: u32, offset: usize, allocation: &Allocation, - device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, ) { @@ -334,7 +332,6 @@ impl Atlas { let buffer_slice = belt.allocate( wgpu::BufferSize::new(total_bytes as u64).unwrap(), wgpu::BufferSize::new(8 * 4).unwrap(), - device, ); const PIXEL: usize = 4; diff --git a/wgpu/src/image/cache.rs b/wgpu/src/image/cache.rs index 097a383f..88a48640 100644 --- a/wgpu/src/image/cache.rs +++ b/wgpu/src/image/cache.rs @@ -38,7 +38,10 @@ impl Cache { raster: Raster { cache: crate::image::raster::Cache::default(), pending: HashMap::new(), - belt: wgpu::util::StagingBelt::new(2 * 1024 * 1024), + belt: wgpu::util::StagingBelt::new( + device.clone(), + 2 * 1024 * 1024, + ), }, #[cfg(feature = "svg")] vector: crate::image::vector::Cache::default(), @@ -451,7 +454,10 @@ mod worker { backend, texture_layout, shell: shell.clone(), - belt: wgpu::util::StagingBelt::new(4 * 1024 * 1024), + belt: wgpu::util::StagingBelt::new( + device.clone(), + 4 * 1024 * 1024, + ), jobs: jobs_receiver, output: work_sender, quit: quit_receiver, diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index ba98f443..d0665ee4 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -45,7 +45,7 @@ impl Pipeline { address_mode_w: wgpu::AddressMode::ClampToEdge, min_filter: wgpu::FilterMode::Nearest, mag_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::MipmapFilterMode::Nearest, ..Default::default() }); @@ -55,7 +55,7 @@ impl Pipeline { address_mode_w: wgpu::AddressMode::ClampToEdge, min_filter: wgpu::FilterMode::Linear, mag_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::MipmapFilterMode::Linear, ..Default::default() }); @@ -106,8 +106,8 @@ impl Pipeline { let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu::image pipeline layout"), - push_constant_ranges: &[], bind_group_layouts: &[&constant_layout, &texture_layout], + immediate_size: 0, }); let shader = @@ -191,7 +191,7 @@ impl Pipeline { mask: !0, alpha_to_coverage_enabled: false, }, - multiview: None, + multiview_mask: None, cache: None, }); @@ -528,7 +528,6 @@ impl Layer { &self.uniforms, 0, (bytes.len() as u64).try_into().expect("Sized uniforms"), - device, ) .copy_from_slice(bytes); @@ -539,11 +538,11 @@ impl Layer { let mut offset = 0; if !nearest.is_empty() { - offset += self.instances.write(device, encoder, belt, 0, nearest); + offset += self.instances.write(encoder, belt, 0, nearest); } if !linear.is_empty() { - let _ = self.instances.write(device, encoder, belt, offset, linear); + let _ = self.instances.write(encoder, belt, offset, linear); } } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 4eb52cc8..03a72042 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -126,6 +126,7 @@ impl Renderer { // It would be great if the `StagingBelt` API exposed methods // for introspection to detect when a resize may be worth it. staging_belt: wgpu::util::StagingBelt::new( + engine.device.clone(), buffer::MAX_WRITE_SIZE as u64, ), @@ -454,6 +455,7 @@ impl Renderer { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }, )); @@ -528,6 +530,7 @@ impl Renderer { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }, )); } @@ -621,6 +624,7 @@ impl Renderer { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }, )); } diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 31bf69cf..4cab2598 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -209,7 +209,7 @@ impl Layer { transformation: Transformation, scale: f32, ) { - self.update(device, encoder, belt, transformation, scale); + self.update(encoder, belt, transformation, scale); if !quads.solids.is_empty() { self.solid.prepare(device, encoder, belt, &quads.solids); @@ -223,7 +223,6 @@ impl Layer { pub fn update( &mut self, - device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, transformation: Transformation, @@ -237,7 +236,6 @@ impl Layer { &self.constants_buffer, 0, (bytes.len() as u64).try_into().expect("Sized uniforms"), - device, ) .copy_from_slice(bytes); } diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs index 6a5eed8d..55e62e79 100644 --- a/wgpu/src/quad/gradient.rs +++ b/wgpu/src/quad/gradient.rs @@ -51,7 +51,7 @@ impl Layer { instances: &[Gradient], ) { let _ = self.instances.resize(device, instances.len()); - let _ = self.instances.write(device, encoder, belt, 0, instances); + let _ = self.instances.write(encoder, belt, 0, instances); self.instance_count = instances.len(); } @@ -75,8 +75,8 @@ impl Pipeline { let layout = device.create_pipeline_layout( &wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu.quad.gradient.pipeline"), - push_constant_ranges: &[], bind_group_layouts: &[constants_layout], + immediate_size: 0, }, ); @@ -155,7 +155,7 @@ impl Pipeline { mask: !0, alpha_to_coverage_enabled: false, }, - multiview: None, + multiview_mask: None, cache: None, }, ); diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs index 7e48358e..49e4cf01 100644 --- a/wgpu/src/quad/solid.rs +++ b/wgpu/src/quad/solid.rs @@ -45,7 +45,7 @@ impl Layer { instances: &[Solid], ) { let _ = self.instances.resize(device, instances.len()); - let _ = self.instances.write(device, encoder, belt, 0, instances); + let _ = self.instances.write(encoder, belt, 0, instances); self.instance_count = instances.len(); } @@ -65,8 +65,8 @@ impl Pipeline { let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu.quad.solid.pipeline"), - push_constant_ranges: &[], bind_group_layouts: &[constants_layout], + immediate_size: 0, }); let shader = @@ -139,7 +139,7 @@ impl Pipeline { mask: !0, alpha_to_coverage_enabled: false, }, - multiview: None, + multiview_mask: None, cache: None, }); diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 16efa092..b6d9e679 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -619,7 +619,7 @@ fn prepare( )?; Some(cryoglyph::TextArea { - buffer, + text: buffer.layout_runs(), left: position.x, top: position.y, scale: transformation.scale_factor() diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index fa5ecb9c..5607d03a 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -313,6 +313,7 @@ fn render<'a>( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }) }; @@ -425,18 +426,13 @@ impl Layer { let indices = mesh.indices(); - index_offset += self.index_buffer.write( - device, - encoder, - belt, - index_offset, - indices, - ); + index_offset += + self.index_buffer + .write(encoder, belt, index_offset, indices); match mesh { Mesh::Solid { buffers, .. } => { solid_vertex_offset += self.solid.vertices.write( - device, encoder, belt, solid_vertex_offset, @@ -444,16 +440,14 @@ impl Layer { ); solid_uniform_offset += self.solid.uniforms.write( - device, encoder, belt, solid_uniform_offset, &[uniforms], - ); + ) } Mesh::Gradient { buffers, .. } => { gradient_vertex_offset += self.gradient.vertices.write( - device, encoder, belt, gradient_vertex_offset, @@ -461,7 +455,6 @@ impl Layer { ); gradient_uniform_offset += self.gradient.uniforms.write( - device, encoder, belt, gradient_uniform_offset, @@ -731,7 +724,7 @@ mod solid { &wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu.triangle.solid.pipeline_layout"), bind_group_layouts: &[&constants_layout], - push_constant_ranges: &[], + immediate_size: 0, }, ); @@ -784,7 +777,7 @@ mod solid { primitive: triangle::primitive_state(), depth_stencil: None, multisample: triangle::multisample_state(antialiasing), - multiview: None, + multiview_mask: None, cache: None, }, ); @@ -886,7 +879,7 @@ mod gradient { &wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu.triangle.gradient.pipeline_layout"), bind_group_layouts: &[&constants_layout], - push_constant_ranges: &[], + immediate_size: 0, }, ); @@ -949,7 +942,7 @@ mod gradient { primitive: triangle::primitive_state(), depth_stencil: None, multisample: triangle::multisample_state(antialiasing), - multiview: None, + multiview_mask: None, cache: None, }, ); diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index 8ca49461..46118361 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -69,8 +69,8 @@ impl Pipeline { let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu::triangle::msaa pipeline layout"), - push_constant_ranges: &[], bind_group_layouts: &[&constant_layout, &texture_layout], + immediate_size: 0, }); let shader = @@ -116,7 +116,7 @@ impl Pipeline { mask: !0, alpha_to_coverage_enabled: false, }, - multiview: None, + multiview_mask: None, cache: None, }); @@ -177,6 +177,7 @@ impl Pipeline { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }) } } @@ -320,7 +321,6 @@ impl State { 0, NonZeroU64::new(std::mem::size_of::() as u64) .expect("non-empty ratio"), - device, ) .copy_from_slice(bytemuck::bytes_of(&ratio)); @@ -351,6 +351,7 @@ impl State { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); render_pass.set_pipeline(&pipeline.raw); diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 833a642e..b575ef61 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -126,6 +126,7 @@ impl Compositor { // if log::max_level() >= log::LevelFilter::Info { // let available_adapters: Vec<_> = instance // .enumerate_adapters(settings.backends) + // .await // .iter() // .map(wgpu::Adapter::get_info) // .collect(); @@ -155,6 +156,7 @@ impl Compositor { if let Some((vendor_id, device_id)) = ids { adapter = instance .enumerate_adapters(settings.backends) + .await .into_iter() .filter(|adapter| { let info = adapter.get_info(); @@ -172,6 +174,7 @@ impl Compositor { } else if let Ok(name) = std::env::var("WGPU_ADAPTER_NAME") { adapter = instance .enumerate_adapters(settings.backends) + .await .into_iter() .filter(|adapter| { let info = adapter.get_info(); From 3928e0d38572d63ff1ef1d768b17770e9a91d7c0 Mon Sep 17 00:00:00 2001 From: edwloef Date: Sat, 27 Dec 2025 16:57:41 +0100 Subject: [PATCH 02/51] wgpu: request devices with `SHADER_F16` feature --- wgpu/src/window/compositor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index b575ef61..d3f4bf8c 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -297,7 +297,7 @@ impl Compositor { label: Some( "iced_wgpu::window::compositor device descriptor", ), - required_features: wgpu::Features::empty(), + required_features: wgpu::Features::SHADER_F16, required_limits: required_limits.clone(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, From e000c71ff230036244b5ed4e82d1ba3b0c59bdad Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 6 May 2026 11:27:02 -0400 Subject: [PATCH 03/51] fix: send redraw request after events creation events must be handled before processing redraws --- .../wayland/event_loop/mod.rs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index 976595a7..61bf12ea 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -488,6 +488,23 @@ impl SctkEventLoop { let had_events = !state.state.sctk_events.is_empty(); let mut wake_up = had_events; + for e in state.state.sctk_events.drain(..) { + if let SctkEvent::Winit(id, e) = e { + _ = state + .state + .events_sender + .unbounded_send(Control::Winit(id, e)); + } else { + _ = + state + .state + .events_sender + .unbounded_send(Control::PlatformSpecific( + crate::platform_specific::Event::Wayland(e), + )); + } + } + for s in state .state @@ -532,22 +549,6 @@ impl SctkEventLoop { ); } - for e in state.state.sctk_events.drain(..) { - if let SctkEvent::Winit(id, e) = e { - _ = state - .state - .events_sender - .unbounded_send(Control::Winit(id, e)); - } else { - _ = - state - .state - .events_sender - .unbounded_send(Control::PlatformSpecific( - crate::platform_specific::Event::Wayland(e), - )); - } - } if wake_up { state.state.proxy.wake_up(); } From d7595fab95579f640d43d90e16c174709c4b5c01 Mon Sep 17 00:00:00 2001 From: Lcstyle Date: Wed, 18 Feb 2026 17:22:13 -0500 Subject: [PATCH 04/51] fix: bootstrap FrameStatus on session lock surface configure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The session lock configure handler was missing receive_frame() and request_redraw() calls that the layer surface configure handler has. Without these, after S3 resume or DPMS wake, the FrameStatus entry for lock surfaces could be missing, causing the rendering gate to never reach Ready state — resulting in permanently grey lock screens. Co-Authored-By: Claude Opus 4.6 --- .../platform_specific/wayland/handlers/session_lock.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/winit/src/platform_specific/wayland/handlers/session_lock.rs b/winit/src/platform_specific/wayland/handlers/session_lock.rs index d0eee5ef..6c617a20 100644 --- a/winit/src/platform_specific/wayland/handlers/session_lock.rs +++ b/winit/src/platform_specific/wayland/handlers/session_lock.rs @@ -1,6 +1,10 @@ use crate::{ event_loop::state::CommonSurface, - platform_specific::wayland::{handlers::SctkState, sctk_event::SctkEvent}, + platform_specific::wayland::{ + event_loop::state::receive_frame, + handlers::SctkState, + sctk_event::SctkEvent, + }, }; use cctk::sctk::{ delegate_session_lock, @@ -67,6 +71,10 @@ impl SessionLockHandler for SctkState { configure, first, }); + + let wl_surface = session_lock_surface.wl_surface().clone(); + receive_frame(&mut self.frame_status, &wl_surface); + self.request_redraw(&wl_surface); } } From f752bba18f4651eee63d2d187ebfdfb0e620f8ef Mon Sep 17 00:00:00 2001 From: KENZ Date: Sat, 25 Apr 2026 09:54:04 +0900 Subject: [PATCH 05/51] =?UTF-8?q?Revert=20"Fix=20the=20candidate=20window?= =?UTF-8?q?=20position=20of=20fcitx5=20which=20does't=20work=20well=20wi?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit a9bcb0052944d9fbf33ce0f6a01a80a20cbcd61b. --- winit/src/window.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/winit/src/window.rs b/winit/src/window.rs index 87d35a5a..e56483f4 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -335,19 +335,9 @@ where } if self.ime_state != Some((cursor, purpose)) { - // Specify only the bottom-left position of the cursor on Linux - // because fcitx5 doesn't work well with cursor areas of - // the top-left position and height. - // The candidate window hides the composing text (a.k.a. preedit). - let (cursor_y, cursor_height) = - if cfg!(not(any(target_os = "windows", target_os = "macos"))) { - (cursor.y + cursor.height, 0.0) - } else { - (cursor.y, cursor.height) - }; self.raw.set_ime_cursor_area( - LogicalPosition::new(cursor.x, cursor_y).into(), - LogicalSize::new(cursor.width, cursor_height).into(), + LogicalPosition::new(cursor.x, cursor.y).into(), + LogicalSize::new(cursor.width, cursor.height).into(), ); self.raw.set_ime_purpose(conversion::ime_purpose(purpose)); From 1444d5c43e8b103ca8077555606443ab77e03ce5 Mon Sep 17 00:00:00 2001 From: KENZ Date: Sat, 2 May 2026 14:28:09 +0900 Subject: [PATCH 06/51] fix(ime): Don't panic if some IME sends DeleteSurrounding events --- winit/src/conversion.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index cc5676b5..97cc97a6 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -387,19 +387,19 @@ pub fn window_event( self::modifiers(new_modifiers.state()), ))) } - WindowEvent::Ime(event) => Some(Event::InputMethod(match event { - Ime::Enabled => input_method::Event::Opened, - Ime::Preedit(content, size) => input_method::Event::Preedit( + WindowEvent::Ime(event) => match event { + Ime::Enabled => Some(input_method::Event::Opened), + Ime::Preedit(content, size) => Some(input_method::Event::Preedit( content, size.map(|(start, end)| start..end), - ), - Ime::Commit(content) => input_method::Event::Commit(content), - Ime::Disabled => input_method::Event::Closed, - Ime::DeleteSurrounding { - before_bytes, - after_bytes, - } => todo!(), - })), + )), + Ime::Commit(content) => Some(input_method::Event::Commit(content)), + Ime::Disabled => Some(input_method::Event::Closed), + // Upstream iced depends on the winit version which doesn't support + // this kind of ime event. + Ime::DeleteSurrounding { .. } => None, + } + .map(Event::InputMethod), WindowEvent::Focused(focused) => Some(Event::Window(if focused { window::Event::Focused } else { From 347d91f7ead2912694f67416e60a72afdba00715 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 20 Apr 2026 12:16:44 -0400 Subject: [PATCH 07/51] improv(wgpu): adapter selection --- wgpu/src/window/compositor.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index d3f4bf8c..0c354dfd 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -76,18 +76,23 @@ impl Compositor { .or_else(|| get_x11_device_ids(window)) }); + #[cfg(all( + unix, + not(feature = "cctk"), + not(target_os = "macos"), + not(target_os = "redox") + ))] + let ids = compatible_window + .as_ref() + .and_then(|window| get_x11_device_ids(window)); + // HACK: // 1. If we specifically didn't select an nvidia gpu // 2. and the user didn't specifically request an nvidia gpu // 3. and the user didn't set an adapter name, // 4. and the user didn't request the high power pref // => don't load the nvidia icd, as it might power on the gpu in hybrid setups causing severe delays - #[cfg(all( - unix, - feature = "cctk", - not(target_os = "macos"), - not(target_os = "redox") - ))] + #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] if !matches!(ids, Some((0x10de, _))) && std::env::var_os("__NV_PRIME_RENDER_OFFLOAD") .is_none_or(|var| var == "0") @@ -149,7 +154,6 @@ impl Compositor { if std::env::var_os("WGPU_ADAPTER_NAME").is_none() { #[cfg(all( unix, - feature = "cctk", not(target_os = "macos"), not(target_os = "redox") ))] @@ -187,6 +191,8 @@ impl Compositor { true } }); + } else { + adapter = instance.request_adapter(&adapter_options).await.ok(); } let adapter = match adapter { From 251254ad5179f05b849e420425b72d4c40091db1 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Fri, 15 May 2026 13:38:15 -0600 Subject: [PATCH 08/51] feat: add `selectable()` to Text --- Cargo.lock | 1 + core/Cargo.toml | 1 + core/src/widget/text.rs | 451 +++++++++++++++++++++++++++++++++++-- examples/todos/src/main.rs | 1 + tester/src/lib.rs | 2 + widget/src/checkbox.rs | 1 + widget/src/radio.rs | 1 + widget/src/text/rich.rs | 2 +- widget/src/toggler.rs | 1 + 9 files changed, 442 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 383d8cfa..38f19187 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3023,6 +3023,7 @@ dependencies = [ "serde", "smol_str", "thiserror 2.0.18", + "unicode-segmentation", "web-time", "window_clipboard", ] diff --git a/core/Cargo.toml b/core/Cargo.toml index e64a5c37..0a376b7c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -31,6 +31,7 @@ num-traits.workspace = true rustc-hash.workspace = true smol_str.workspace = true thiserror.workspace = true +unicode-segmentation.workspace = true web-time.workspace = true serde.workspace = true diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 1e40bf9e..0c0ddc54 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -22,16 +22,18 @@ //! ``` use crate::alignment; use crate::layout; -use crate::mouse; +use crate::mouse::{self, click}; use crate::renderer; +use crate::text; use crate::text::paragraph::{self, Paragraph}; -use crate::text::{self, Fragment}; use crate::widget::tree::{self, Tree}; use crate::{ - Color, Element, Layout, Length, Pixels, Rectangle, Size, Theme, Widget, + Clipboard, Color, Element, Event, Layout, Length, Pixels, Point, Rectangle, + Shell, Size, Theme, Widget, keyboard, touch, }; -use std::borrow::Cow; +use unicode_segmentation::UnicodeSegmentation; + pub use text::{Alignment, Ellipsize, LineHeight, Shaping, Wrapping}; /// A bunch of text. @@ -65,6 +67,7 @@ where fragment: text::Fragment<'a>, format: Format, class: Theme::Class<'a>, + selectable: bool, } impl<'a, Theme, Renderer> Text<'a, Theme, Renderer> @@ -79,6 +82,7 @@ where fragment: fragment.into_fragment(), format: Format::default(), class: Theme::default(), + selectable: false, } } @@ -189,7 +193,17 @@ where { let color = color.map(Into::into); - self.style(move |_theme| Style { color }) + self.style(move |_theme| Style { + color, + ..Style::default() + }) + } + + /// Makes the [`Text`] selectable. When enabled, the user can click and + /// drag to select text, and copy it with Ctrl+C / Cmd+C. + pub fn selectable(mut self) -> Self { + self.selectable = true; + self } /// Sets the style class of the [`Text`]. @@ -202,7 +216,53 @@ where } /// The internal state of a [`Text`] widget. -pub type State

= paragraph::Plain

; +pub struct State { + /// The cached paragraph layout. + pub paragraph: paragraph::Plain

, + /// Lazily allocated when text is selectable and first interacted with. + selection: Option>, +} + +impl Default for State

{ + fn default() -> Self { + Self { + paragraph: paragraph::Plain::default(), + selection: None, + } + } +} + +impl std::fmt::Debug for State

{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("State") + .field("selection", &self.selection) + .finish_non_exhaustive() + } +} + +impl std::ops::Deref for State

{ + type Target = paragraph::Plain

; + + fn deref(&self) -> ¶graph::Plain

{ + &self.paragraph + } +} + +impl std::ops::DerefMut for State

{ + fn deref_mut(&mut self) -> &mut paragraph::Plain

{ + &mut self.paragraph + } +} + +#[derive(Debug, Clone, Default)] +struct SelectionState { + anchor: usize, + end: usize, + dragging: bool, + focused: bool, + modifiers: keyboard::Modifiers, + last_click: Option, +} impl Widget for Text<'_, Theme, Renderer> @@ -215,7 +275,7 @@ where } fn state(&self) -> tree::State { - tree::State::new(paragraph::Plain::::default()) + tree::State::new(State::::default()) } fn size(&self) -> Size { @@ -231,8 +291,10 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { + let state = tree.state.downcast_mut::>(); + layout( - tree.state.downcast_mut::>(), + &mut state.paragraph, renderer, limits, &self.fragment, @@ -252,15 +314,285 @@ where ) { let state = tree.state.downcast_ref::>(); let style = theme.style(&self.class); + let bounds = layout.bounds(); + let paragraph = state.paragraph.raw(); + if let Some(sel) = &state.selection { + let left = sel.anchor.min(sel.end); + let right = sel.anchor.max(sel.end); + let content: &str = self.fragment.as_ref(); - draw( - renderer, - defaults, - layout.bounds(), - state.raw(), - style, - viewport, - ); + if left != right { + let lo_byte = grapheme_to_byte(content, left); + let hi_byte = grapheme_to_byte(content, right); + + let anchor = bounds.anchor( + paragraph.min_bounds(), + paragraph.align_x(), + paragraph.align_y(), + ); + + let rects = paragraph.highlight( + 0, + (lo_byte, text::Affinity::After), + (hi_byte, text::Affinity::Before), + ); + + for r in rects { + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: anchor.x + r.x, + y: anchor.y + r.y, + width: r.width, + height: r.height, + }, + ..renderer::Quad::default() + }, + style.selected_fill, + ); + } + } + } + + draw(renderer, defaults, bounds, paragraph, style, viewport); + } + + fn update( + &mut self, + tree: &mut Tree, + event: &Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + _renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, + ) { + if !self.selectable { + return; + } + + let state = tree.state.downcast_mut::>(); + let bounds = layout.bounds(); + let content: &str = self.fragment.as_ref(); + let grapheme_count = content.graphemes(true).count(); + let paragraph = state.paragraph.raw(); + + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + if let Some(pos) = cursor.position_over(bounds) { + let sel = state.selection.get_or_insert_with(|| { + Box::new(SelectionState::default()) + }); + + let anchor = bounds.anchor( + paragraph.min_bounds(), + paragraph.align_x(), + paragraph.align_y(), + ); + let relative = + Point::new(pos.x - anchor.x, pos.y - anchor.y); + + let grapheme_pos = + hit_to_grapheme(paragraph, relative, content); + + let new_click = click::Click::new( + pos, + mouse::Button::Left, + sel.last_click.take(), + ); + + match new_click.kind() { + click::Kind::Single => { + if sel.modifiers.shift() { + sel.end = grapheme_pos; + } else { + sel.anchor = grapheme_pos; + sel.end = grapheme_pos; + } + sel.dragging = true; + } + click::Kind::Double => { + sel.anchor = + previous_start_of_word(content, grapheme_pos); + sel.end = next_end_of_word(content, grapheme_pos); + sel.dragging = true; + } + click::Kind::Triple => { + sel.anchor = 0; + sel.end = grapheme_count; + sel.dragging = true; + } + } + + sel.last_click = Some(new_click); + sel.focused = true; + shell.capture_event(); + } else if let Some(sel) = &mut state.selection { + sel.focused = false; + sel.anchor = 0; + sel.end = 0; + sel.dragging = false; + } + } + + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) => { + if let Some(sel) = &mut state.selection { + sel.dragging = false; + } + } + + Event::Mouse(mouse::Event::CursorMoved { position }) + | Event::Touch(touch::Event::FingerMoved { position, .. }) => { + if let Some(sel) = &mut state.selection { + if sel.dragging { + let anchor = bounds.anchor( + paragraph.min_bounds(), + paragraph.align_x(), + paragraph.align_y(), + ); + let relative = Point::new( + position.x - anchor.x, + position.y - anchor.y, + ); + + sel.end = hit_to_grapheme(paragraph, relative, content); + shell.capture_event(); + } + } + } + + Event::Keyboard(keyboard::Event::KeyPressed { + key, + modifiers, + physical_key, + text: _, + .. + }) => { + let focused = + state.selection.as_ref().is_some_and(|s| s.focused); + if !focused { + return; + } + let sel = state.selection.as_mut().unwrap(); + + if modifiers.command() { + match key.to_latin(*physical_key) { + Some('c') => { + let left = sel.anchor.min(sel.end); + let right = sel.anchor.max(sel.end); + if left != right { + let selected: String = content + .graphemes(true) + .skip(left) + .take(right - left) + .collect(); + clipboard.write( + crate::clipboard::Kind::Standard, + selected, + ); + } + shell.capture_event(); + return; + } + Some('a') => { + sel.anchor = 0; + sel.end = grapheme_count; + shell.capture_event(); + return; + } + _ => {} + } + } + + match key { + keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => { + let by_word = is_jump_modifier(*modifiers); + if modifiers.shift() { + sel.end = if by_word { + previous_start_of_word(content, sel.end) + } else { + sel.end.saturating_sub(1) + }; + } else { + let left = sel.anchor.min(sel.end); + let pos = if by_word { + previous_start_of_word(content, left) + } else { + left.saturating_sub(1) + }; + sel.anchor = pos; + sel.end = pos; + } + shell.capture_event(); + } + keyboard::Key::Named(keyboard::key::Named::ArrowRight) => { + let by_word = is_jump_modifier(*modifiers); + if modifiers.shift() { + sel.end = if by_word { + next_end_of_word(content, sel.end) + } else { + (sel.end + 1).min(grapheme_count) + }; + } else { + let right = sel.anchor.max(sel.end); + let pos = if by_word { + next_end_of_word(content, right) + } else { + (right + 1).min(grapheme_count) + }; + sel.anchor = pos; + sel.end = pos; + } + shell.capture_event(); + } + keyboard::Key::Named(keyboard::key::Named::Home) => { + if modifiers.shift() { + sel.end = 0; + } else { + sel.anchor = 0; + sel.end = 0; + } + shell.capture_event(); + } + keyboard::Key::Named(keyboard::key::Named::End) => { + if modifiers.shift() { + sel.end = grapheme_count; + } else { + sel.anchor = grapheme_count; + sel.end = grapheme_count; + } + shell.capture_event(); + } + _ => {} + } + } + + Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { + if let Some(sel) = &mut state.selection { + sel.modifiers = *modifiers; + } + } + + _ => {} + } + } + + fn mouse_interaction( + &self, + _tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + if self.selectable && cursor.is_over(layout.bounds()) { + mouse::Interaction::Text + } else { + mouse::Interaction::None + } } fn operate( @@ -471,12 +803,23 @@ where } /// The appearance of some text. -#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Style { /// The [`Color`] of the text. /// /// The default, `None`, means using the inherited color. pub color: Option, + /// The fill [`Color`] of the selection highlight. + pub selected_fill: Color, +} + +impl Default for Style { + fn default() -> Self { + Self { + color: None, + selected_fill: DEFAULT_SELECTION_COLOR, + } + } } /// The theme catalog of a [`Text`]. @@ -510,13 +853,14 @@ impl Catalog for Theme { /// The default text styling; color is inherited. pub fn default(_theme: &Theme) -> Style { - Style { color: None } + Style::default() } /// Text with the default base color. pub fn base(theme: &Theme) -> Style { Style { color: Some(theme.palette().text), + ..Style::default() } } @@ -524,6 +868,7 @@ pub fn base(theme: &Theme) -> Style { pub fn primary(theme: &Theme) -> Style { Style { color: Some(theme.palette().primary), + ..Style::default() } } @@ -531,6 +876,7 @@ pub fn primary(theme: &Theme) -> Style { pub fn secondary(theme: &Theme) -> Style { Style { color: Some(theme.extended_palette().secondary.base.color), + ..Style::default() } } @@ -538,6 +884,7 @@ pub fn secondary(theme: &Theme) -> Style { pub fn success(theme: &Theme) -> Style { Style { color: Some(theme.palette().success), + ..Style::default() } } @@ -545,6 +892,7 @@ pub fn success(theme: &Theme) -> Style { pub fn warning(theme: &Theme) -> Style { Style { color: Some(theme.palette().warning), + ..Style::default() } } @@ -552,5 +900,72 @@ pub fn warning(theme: &Theme) -> Style { pub fn danger(theme: &Theme) -> Style { Style { color: Some(theme.palette().danger), + ..Style::default() + } +} + +const DEFAULT_SELECTION_COLOR: Color = Color { + r: 0.0, + g: 0.47, + b: 0.84, + a: 0.3, +}; + +fn grapheme_to_byte(content: &str, grapheme_index: usize) -> usize { + content + .graphemes(true) + .take(grapheme_index) + .map(|g| g.len()) + .sum() +} + +fn hit_to_grapheme( + paragraph: &P, + point: Point, + content: &str, +) -> usize { + match paragraph.hit_test(point) { + Some(hit) => { + let byte_offset = hit.cursor().min(content.len()); + content[..byte_offset].graphemes(true).count() + } + None => content.graphemes(true).count(), + } +} + +fn previous_start_of_word(content: &str, grapheme_index: usize) -> usize { + let graphemes: Vec<&str> = content.graphemes(true).collect(); + let clamped = grapheme_index.min(graphemes.len()); + let before: String = graphemes[..clamped].concat(); + + UnicodeSegmentation::split_word_bound_indices(&*before) + .filter(|(_, word)| !word.trim_start().is_empty()) + .next_back() + .map_or(0, |(i, prev_word)| { + clamped + - prev_word.graphemes(true).count() + - before[i + prev_word.len()..].graphemes(true).count() + }) +} + +fn next_end_of_word(content: &str, grapheme_index: usize) -> usize { + let graphemes: Vec<&str> = content.graphemes(true).collect(); + let clamped = grapheme_index.min(graphemes.len()); + let after: String = graphemes[clamped..].concat(); + + UnicodeSegmentation::split_word_bound_indices(&*after) + .find(|(_, word)| !word.trim_start().is_empty()) + .map_or(graphemes.len(), |(i, next_word)| { + clamped + + next_word.graphemes(true).count() + + after[..i].graphemes(true).count() + }) +} + +fn is_jump_modifier(modifiers: keyboard::Modifiers) -> bool { + if cfg!(target_os = "macos") { + modifiers.alt() + } else { + modifiers.control() } } diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 2e3722b1..9fe39035 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -477,6 +477,7 @@ fn delete_icon() -> Text<'static> { fn subtle(theme: &Theme) -> text::Style { text::Style { color: Some(theme.extended_palette().background.strongest.color), + ..text::Style::default() } } diff --git a/tester/src/lib.rs b/tester/src/lib.rs index 334f17bc..f41fbb68 100644 --- a/tester/src/lib.rs +++ b/tester/src/lib.rs @@ -775,6 +775,7 @@ impl Tester

{ } _ => None, }, + ..text::Style::default() }) .into() }, @@ -945,6 +946,7 @@ where text(label).size(14).style(|theme: &core::Theme| { text::Style { color: Some(theme.extended_palette().background.weak.text), + ..text::Style::default() } }), space::horizontal(), diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 873a4541..6ac48b0e 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -505,6 +505,7 @@ where state.raw(), crate::text::Style { color: style.text_color, + ..crate::text::Style::default() }, viewport, ); diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 1aa97355..bdca259c 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -459,6 +459,7 @@ where state.raw(), crate::text::Style { color: style.text_color, + ..crate::text::Style::default() }, viewport, ); diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index cd368e83..80abc8d5 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -179,7 +179,7 @@ where { let color = color.map(Into::into); - self.style(move |_theme| Style { color }) + self.style(move |_theme| Style { color, ..Style::default() }) } /// Sets the default style class of the [`Rich`] text. diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index d398a79b..6709f1dc 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -483,6 +483,7 @@ where state.raw(), crate::text::Style { color: style.text_color, + ..crate::text::Style::default() }, viewport, ); From 346faf2e7951dd4ea6fe7fb3f677e34631145d88 Mon Sep 17 00:00:00 2001 From: Ericky Dos Santos Date: Tue, 6 Jan 2026 13:26:01 -0500 Subject: [PATCH 09/51] Make shader_f16 optional because it does not work in webgl --- wgpu/src/window/compositor.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 0c354dfd..bbef994d 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -295,6 +295,13 @@ impl Compositor { ..limits }); + // Request SHADER_F16 only if the adapter supports it (e.g., not available in WebGL2) + let required_features = if adapter.features().contains(wgpu::Features::SHADER_F16) { + wgpu::Features::SHADER_F16 + } else { + wgpu::Features::empty() + }; + let mut errors = Vec::new(); for required_limits in limits { @@ -303,7 +310,7 @@ impl Compositor { label: Some( "iced_wgpu::window::compositor device descriptor", ), - required_features: wgpu::Features::SHADER_F16, + required_features, required_limits: required_limits.clone(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, From 9ae155b8e26480fd78ff289fceec0cdf2d476463 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 20 May 2026 00:25:37 -0600 Subject: [PATCH 10/51] fix: crash while resizing on x11 --- wgpu/src/window/compositor.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index bbef994d..4033aea4 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -368,6 +368,13 @@ pub fn present( ) -> Result<(), compositor::SurfaceError> { match surface.get_current_texture() { Ok(frame) => { + if frame.suboptimal + || frame.texture.width() != viewport.physical_width() + || frame.texture.height() != viewport.physical_height() + { + return Err(compositor::SurfaceError::Outdated); + } + let view = &frame .texture .create_view(&wgpu::TextureViewDescriptor::default()); From 2e1e4bd6e0a8cc46290d9354428bf7a0f3ca752d Mon Sep 17 00:00:00 2001 From: Hojjat Date: Tue, 19 May 2026 19:36:16 -0600 Subject: [PATCH 11/51] feat: add HasSelectableText trait to Text widget --- core/src/widget/text.rs | 220 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 208 insertions(+), 12 deletions(-) diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 0c0ddc54..2ba0ada4 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -221,6 +221,9 @@ pub struct State { pub paragraph: paragraph::Plain

, /// Lazily allocated when text is selectable and first interacted with. selection: Option>, + focused: bool, + keyboard_focused: bool, + context_menu_position: Option, } impl Default for State

{ @@ -228,6 +231,9 @@ impl Default for State

{ Self { paragraph: paragraph::Plain::default(), selection: None, + focused: false, + keyboard_focused: false, + context_menu_position: None, } } } @@ -236,10 +242,43 @@ impl std::fmt::Debug for State

{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("State") .field("selection", &self.selection) + .field("focused", &self.focused) .finish_non_exhaustive() } } +impl State

{ + /// Returns `true` if the widget currently has focus. + pub fn is_focused(&self) -> bool { + self.focused + } + + /// Returns `true` if focus was gained via keyboard navigation (Tab). + pub fn is_keyboard_focused(&self) -> bool { + self.keyboard_focused + } + + /// Clears focus, selection, and all interaction state. + pub fn clear_focus(&mut self) { + self.focused = false; + self.keyboard_focused = false; + self.context_menu_position = None; + if let Some(sel) = &mut self.selection { + sel.clear(); + } + } + + /// Returns the context menu position, if a context menu should be shown. + pub fn context_menu_position(&self) -> Option { + self.context_menu_position + } + + /// Sets or clears the context menu position. + pub fn set_context_menu_position(&mut self, pos: Option) { + self.context_menu_position = pos; + } +} + impl std::ops::Deref for State

{ type Target = paragraph::Plain

; @@ -259,11 +298,33 @@ struct SelectionState { anchor: usize, end: usize, dragging: bool, - focused: bool, modifiers: keyboard::Modifiers, last_click: Option, } +impl SelectionState { + fn clear(&mut self) { + self.anchor = 0; + self.end = 0; + self.dragging = false; + } +} + +impl crate::widget::operation::Focusable for State

{ + fn is_focused(&self) -> bool { + self.focused + } + + fn focus(&mut self) { + self.focused = true; + self.keyboard_focused = true; + } + + fn unfocus(&mut self) { + self.clear_focus(); + } +} + impl Widget for Text<'_, Theme, Renderer> where @@ -378,9 +439,29 @@ where let grapheme_count = content.graphemes(true).count(); let paragraph = state.paragraph.raw(); + // Any click outside this widget clears selection and focus. + if matches!( + event, + Event::Mouse(mouse::Event::ButtonPressed(_)) + | Event::Touch(touch::Event::FingerPressed { .. }) + ) { + if cursor.position_over(bounds).is_none() { + state.focused = false; + state.keyboard_focused = false; + if let Some(sel) = &mut state.selection { + sel.clear(); + } + } + } + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { + if state.context_menu_position.take().is_some() { + shell.capture_event(); + return; + } + if let Some(pos) = cursor.position_over(bounds) { let sel = state.selection.get_or_insert_with(|| { Box::new(SelectionState::default()) @@ -427,13 +508,18 @@ where } sel.last_click = Some(new_click); - sel.focused = true; + state.focused = true; + state.keyboard_focused = false; + shell.capture_event(); + } + } + + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => { + if let Some(pos) = cursor.position_over(bounds) { + state.context_menu_position = Some(pos); + state.focused = true; + state.keyboard_focused = false; shell.capture_event(); - } else if let Some(sel) = &mut state.selection { - sel.focused = false; - sel.anchor = 0; - sel.end = 0; - sel.dragging = false; } } @@ -471,12 +557,12 @@ where text: _, .. }) => { - let focused = - state.selection.as_ref().is_some_and(|s| s.focused); - if !focused { + if !state.focused { return; } - let sel = state.selection.as_mut().unwrap(); + let sel = state + .selection + .get_or_insert_with(|| Box::new(SelectionState::default())); if modifiers.command() { match key.to_latin(*physical_key) { @@ -597,12 +683,17 @@ where fn operate( &mut self, - _tree: &mut Tree, + tree: &mut Tree, layout: Layout<'_>, _renderer: &Renderer, operation: &mut dyn super::Operation, ) { operation.text(None, layout.bounds(), &self.fragment); + + if self.selectable { + let state = tree.state.downcast_mut::>(); + operation.focusable(None, layout.bounds(), state); + } } #[cfg(feature = "a11y")] @@ -904,6 +995,111 @@ pub fn danger(theme: &Theme) -> Style { } } +use crate::widget::tree::Tree as WidgetTree; + +/// Implement this on a **widget** to enable context menu support for +/// text selection (Copy, Select All, and optionally Cut / Paste) in libcosmic +pub trait HasSelectableText { + /// Returns the currently selected text, if any. + fn selected_text(&self, tree: &WidgetTree) -> Option; + + /// Selects all text. + fn select_all(&self, tree: &mut WidgetTree); + + /// Returns `true` if the widget is editable (enables Cut / Paste). + fn is_editable(&self) -> bool { + false + } + + /// Returns `true` if the widget is currently focused. + fn is_focused(&self, tree: &WidgetTree) -> bool; + + /// Returns the position where the context menu should appear + fn context_menu_position(&self, tree: &WidgetTree) -> Option; + + /// Sets or clears the context menu position. + fn set_context_menu_position( + &self, + tree: &mut WidgetTree, + pos: Option, + ); + + /// Copies the selection to the clipboard. + fn copy_to_clipboard( + &self, + tree: &WidgetTree, + clipboard: &mut dyn Clipboard, + ) { + if let Some(text) = self.selected_text(tree) { + clipboard.write(crate::clipboard::Kind::Standard, text); + } + } + + /// Deletes the selected text and returns the new full content. + /// Only called when [`is_editable`](Self::is_editable) is `true`. + fn delete_selection(&self, _tree: &mut WidgetTree) -> Option { + None + } + + /// Inserts `text` at the cursor (replacing any selection) and returns + /// the new full content. + /// Only called when [`is_editable`](Self::is_editable) is `true`. + fn paste_text( + &self, + _tree: &mut WidgetTree, + _text: &str, + ) -> Option { + None + } +} + +impl HasSelectableText + for Text<'_, Theme, Renderer> +{ + fn selected_text(&self, tree: &WidgetTree) -> Option { + let state = tree.state.downcast_ref::>(); + let sel = state.selection.as_ref()?; + let left = sel.anchor.min(sel.end); + let right = sel.anchor.max(sel.end); + if left == right { + return None; + } + let content = state.paragraph.content(); + let lo = grapheme_to_byte(content, left); + let hi = grapheme_to_byte(content, right); + content.get(lo..hi).map(|s| s.to_owned()) + } + + fn select_all(&self, tree: &mut WidgetTree) { + let state = tree.state.downcast_mut::>(); + let count = state.paragraph.content().graphemes(true).count(); + let sel = state + .selection + .get_or_insert_with(|| Box::new(SelectionState::default())); + sel.anchor = 0; + sel.end = count; + } + + fn is_focused(&self, tree: &WidgetTree) -> bool { + let state = tree.state.downcast_ref::>(); + state.is_focused() + } + + fn context_menu_position(&self, tree: &WidgetTree) -> Option { + let state = tree.state.downcast_ref::>(); + state.context_menu_position() + } + + fn set_context_menu_position( + &self, + tree: &mut WidgetTree, + pos: Option, + ) { + let state = tree.state.downcast_mut::>(); + state.set_context_menu_position(pos); + } +} + const DEFAULT_SELECTION_COLOR: Color = Color { r: 0.0, g: 0.47, From a7ca336eb4db0fe89d2bdeb90b57140e0ebca141 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Tue, 19 May 2026 19:36:22 -0600 Subject: [PATCH 12/51] feat: HasSelectableText impl to TextEditor --- core/src/text/editor.rs | 2 + graphics/src/text/editor.rs | 4 ++ widget/src/text_editor.rs | 126 +++++++++++++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 1 deletion(-) diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index e0b87756..ff89af01 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -78,6 +78,8 @@ pub enum Action { SelectLine, /// Select the entire buffer. SelectAll, + /// Clear the selection + ClearSelection, /// Perform an [`Edit`]. Edit(Edit), /// Click the [`Editor`] at the given [`Point`]. diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index b284c76b..1586479e 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -336,6 +336,10 @@ impl editor::Editor for Editor { } } + Action::ClearSelection => { + editor.set_selection(cosmic_text::Selection::None); + } + // Editing events Action::Edit(edit) => { let topmost_line_before_edit = editor diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index b93c96f3..a8f243fa 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -544,6 +544,8 @@ pub struct State { highlighter: RefCell, highlighter_settings: Highlighter::Settings, highlighter_format_address: usize, + context_menu_position: Option, + pending_edit: Option, } #[derive(Debug, Clone)] @@ -579,6 +581,14 @@ impl State { pub fn is_focused(&self) -> bool { self.focus.is_some() } + + /// Clears focus, selection, and all interaction state. + pub fn clear_focus(&mut self) { + self.focus = None; + self.drag_click = None; + self.context_menu_position = None; + self.pending_edit = Some(Action::ClearSelection); + } } impl operation::Focusable @@ -593,7 +603,7 @@ impl operation::Focusable } fn unfocus(&mut self) { - self.focus = None; + self.clear_focus(); } } @@ -621,6 +631,8 @@ where )), highlighter_settings: self.highlighter_settings.clone(), highlighter_format_address: self.highlighter_format as usize, + context_menu_position: None, + pending_edit: None, }) } @@ -699,6 +711,58 @@ where shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) { + // Handle context menu and focus-loss on clicks outside. + { + let state = tree.state.downcast_mut::>(); + let text_bounds = layout.bounds(); + + if matches!( + event, + Event::Mouse(mouse::Event::ButtonPressed(_)) + | Event::Touch( + crate::core::touch::Event::FingerPressed { .. } + ) + ) && cursor.position_over(text_bounds).is_none() + { + state.clear_focus(); + } + + match event { + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Right, + )) => { + if let Some(pos) = cursor.position_over(text_bounds) { + if state.focus.is_none() { + state.focus = Some(Focus::now()); + } + state.context_menu_position = Some(pos); + shell.capture_event(); + return; + } + } + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) => { + if state.context_menu_position.take().is_some() { + shell.capture_event(); + return; + } + } + _ => {} + } + } + + // Process pending edits from context menu + // publishes via on_edit so the app calls content.perform(). + { + let state = tree.state.downcast_mut::>(); + if let Some(action) = state.pending_edit.take() { + if let Some(on_edit) = self.on_edit.as_ref() { + shell.publish((on_edit)(action)); + } + } + } + let Some(on_edit) = self.on_edit.as_ref() else { return; }; @@ -1545,3 +1609,63 @@ pub(crate) fn convert_macos_shortcut( Some(keyboard::Key::Named(key)) } + +use crate::core::widget::text::HasSelectableText; + +impl HasSelectableText + for TextEditor<'_, Highlighter, Message, Theme, Renderer> +where + Highlighter: text::Highlighter, + Theme: Catalog, + Renderer: text::Renderer, +{ + fn selected_text(&self, _tree: &widget::Tree) -> Option { + self.content.selection() + } + + fn select_all(&self, tree: &mut widget::Tree) { + let state = tree.state.downcast_mut::>(); + state.pending_edit = Some(Action::SelectAll); + } + + fn is_editable(&self) -> bool { + self.on_edit.is_some() + } + + fn is_focused(&self, tree: &widget::Tree) -> bool { + tree.state.downcast_ref::>().is_focused() + } + + fn context_menu_position(&self, tree: &widget::Tree) -> Option { + tree.state + .downcast_ref::>() + .context_menu_position + } + + fn set_context_menu_position( + &self, + tree: &mut widget::Tree, + pos: Option, + ) { + tree.state + .downcast_mut::>() + .context_menu_position = pos; + } + + fn delete_selection(&self, tree: &mut widget::Tree) -> Option { + let state = tree.state.downcast_mut::>(); + state.pending_edit = Some(Action::Edit(Edit::Delete)); + Some(String::new()) + } + + fn paste_text( + &self, + tree: &mut widget::Tree, + text: &str, + ) -> Option { + let state = tree.state.downcast_mut::>(); + state.pending_edit = + Some(Action::Edit(Edit::Paste(Arc::new(text.to_owned())))); + Some(String::new()) + } +} From b007caba44b0be1d282edbe1cd6fcd440ef06eab Mon Sep 17 00:00:00 2001 From: Hojjat Date: Tue, 19 May 2026 19:36:26 -0600 Subject: [PATCH 13/51] feat: add direct SCTK action sender for wayland popups --- winit/src/platform_specific/wayland/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 9b3a7ec0..0ba156d7 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -25,10 +25,23 @@ use std::{collections::HashMap, sync::Arc}; use subsurface_widget::{SubsurfaceInstance, SubsurfaceState}; use wayland_backend::client::ObjectId; use wayland_client::{Connection, Proxy}; +use std::sync::OnceLock; use winit::dpi::Size; use winit::event_loop::OwnedDisplayHandle; use winit::window::ImePurpose; +static SCTK_ACTION_SENDER: OnceLock> = OnceLock::new(); + +/// Sends a platform-specific wayland action directly to the SCTK event loop, +/// bypassing the async Task system. This is needed when widgets queue popup +/// actions outside of the normal message flow (e.g., text context menus that +/// don't publish app messages). +pub fn send_wayland_action_direct(action: iced_runtime::platform_specific::wayland::Action) { + if let Some(sender) = SCTK_ACTION_SENDER.get() { + _ = sender.send(Action::Action(action)); + } +} + pub(crate) enum Action { Action(iced_runtime::platform_specific::wayland::Action), SetCursor(CursorIcon), @@ -136,6 +149,9 @@ impl PlatformSpecific { self.wayland.display_handle.clone().unwrap(), ) .ok(); + if let Some(ref sender) = self.wayland.sender { + _ = SCTK_ACTION_SENDER.set(sender.clone()); + } self } From 98fb6a0fba98cfdb6c04f33ccf0be6abad7fcfff Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 20 May 2026 17:00:28 +0200 Subject: [PATCH 14/51] docs: update cosmic-protocols with docs cfg fix --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d60bcb8d..e9f43f7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,7 +120,7 @@ winit = [ ] wayland = ["iced_renderer/wayland", "iced_winit/wayland"] cctk = ["iced_winit/cctk", "iced_widget/cctk", "iced_core/cctk", "wayland"] -# Enables the sctk shell. +# Enables the sctk shell. [dependencies] @@ -274,7 +274,7 @@ tiny-skia = { version = "0.11", default-features = false, features = [ "std", "simd", ] } -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086" } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "c253ec1" } softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" } syntect = "5.2" tokio = "1.0" From 6105d8bdef8bcd39fbca1ef168d82c986dd18d20 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 22 May 2026 15:40:59 -0400 Subject: [PATCH 15/51] reexport geometry from iced_graphics --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8d408969..46685d76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -682,7 +682,7 @@ pub use error::Error; pub use event::Event; pub use executor::Executor; pub use font::Font; -pub use renderer::Renderer; +pub use renderer::{Renderer, graphics::geometry}; pub use task::Task; pub use window::Window; From a7d71d9aceddcdd6cad5628b6ca18aab89edfa97 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 27 May 2026 11:30:30 -0400 Subject: [PATCH 16/51] chore: modifiers event on keyboard enter and leave --- .../wayland/event_loop/state.rs | 8 +-- .../wayland/handlers/seat/keyboard.rs | 60 +++++++++++++++++-- .../wayland/handlers/seat/seat.rs | 5 +- 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index f99e48ba..e47caa94 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -30,7 +30,6 @@ use std::{ convert::Infallible, fmt::Debug, sync::{Arc, Mutex, atomic::AtomicU32}, - thread::panicking, time::Duration, }; use wayland_backend::client::ObjectId; @@ -71,7 +70,7 @@ use cctk::{ registry::RegistryState, seat::{ SeatState, - keyboard::KeyEvent, + keyboard::{KeyEvent, Modifiers}, pointer::{CursorIcon, PointerData, ThemedPointer}, touch::TouchData, }, @@ -97,7 +96,6 @@ use cctk::{ }; use iced_runtime::{ core::{self, Point, touch}, - keyboard::Modifiers, platform_specific::{ self, wayland::{ @@ -1469,7 +1467,7 @@ impl SctkState { // TODO how to handle this when there's no lock? if let Some((surface, _)) = self.get_lock_surface(id, &output) { let wl_surface = surface.wl_surface(); - + receive_frame(&mut self.frame_status, &wl_surface); } } @@ -1653,7 +1651,7 @@ impl SctkState { bg_effect_mgr.enqueue(id, rectangles.clone()); return Ok(()); } - + let s = if let Some(s) = self.popups.iter().find(|s| s.data.id == id) { s.popup.wl_surface() } else if let Some(s) = self.layer_surfaces.iter().find(|s| s.id == id) { diff --git a/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs index 5ff7fb4b..f83e1180 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs @@ -4,10 +4,46 @@ use crate::platform_specific::wayland::{ }; use cctk::sctk::{ delegate_keyboard, - seat::keyboard::{KeyboardHandler, Keysym}, + seat::keyboard::{KeyboardHandler, Keysym, Modifiers}, }; use cctk::sctk::{reexports::client::Proxy, seat::keyboard::RawModifiers}; +fn modifiers_from_keysyms( + keysyms: &[Keysym], + previous: Modifiers, +) -> Modifiers { + let mut modifiers = Modifiers { + caps_lock: previous.caps_lock, + num_lock: previous.num_lock, + ..Modifiers::default() + }; + + for keysym in keysyms { + match keysym.raw() { + xkeysym::key::Shift_L | xkeysym::key::Shift_R => { + modifiers.shift = true + } + xkeysym::key::Control_L | xkeysym::key::Control_R => { + modifiers.ctrl = true + } + xkeysym::key::Alt_L | xkeysym::key::Alt_R => modifiers.alt = true, + xkeysym::key::Super_L + | xkeysym::key::Super_R + | xkeysym::key::Hyper_L + | xkeysym::key::Hyper_R + | xkeysym::key::Meta_L + | xkeysym::key::Meta_R => modifiers.logo = true, + xkeysym::key::Caps_Lock | xkeysym::key::Shift_Lock => { + modifiers.caps_lock = true + } + xkeysym::key::Num_Lock => modifiers.num_lock = true, + _ => {} + } + } + + modifiers +} + impl KeyboardHandler for SctkState { fn enter( &mut self, @@ -17,9 +53,9 @@ impl KeyboardHandler for SctkState { surface: &cctk::sctk::reexports::client::protocol::wl_surface::WlSurface, _serial: u32, _raw: &[u32], - _keysyms: &[Keysym], + keysyms: &[Keysym], ) { - let (i, mut is_active, seat) = { + let (i, mut is_active, seat, modifiers) = { let (i, is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { if s.kbd.as_ref() == Some(keyboard) { @@ -41,9 +77,11 @@ impl KeyboardHandler for SctkState { surface }; _ = my_seat.kbd_focus.replace(surface.clone()); + let modifiers = modifiers_from_keysyms(keysyms, my_seat._modifiers); + my_seat._modifiers = modifiers; let seat = my_seat.seat.clone(); - (i, is_active, seat) + (i, is_active, seat, modifiers) }; if !is_active && self.seats[0].kbd_focus.is_none() { @@ -73,6 +111,12 @@ impl KeyboardHandler for SctkState { seat_id: seat.clone(), surface: surface.clone(), }); + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Modifiers(modifiers), + kbd_id: keyboard.clone(), + seat_id: seat.clone(), + surface: surface.clone(), + }); } } } @@ -126,6 +170,7 @@ impl KeyboardHandler for SctkState { if self.windows.iter().any(|w| w.window.id() == id) { continue; } + let modifiers = s._modifiers; self.sctk_events.push(SctkEvent::Winit( id, winit::event::WindowEvent::Focused(true), @@ -137,6 +182,12 @@ impl KeyboardHandler for SctkState { kbd_id: s.kbd.clone().unwrap(), seat_id: s.seat.clone(), surface: surface.clone(), + }); + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Modifiers(modifiers), + kbd_id: s.kbd.clone().unwrap(), + seat_id: s.seat.clone(), + surface: surface.clone(), }) } } @@ -248,6 +299,7 @@ impl KeyboardHandler for SctkState { }; let seat_id = my_seat.seat.clone(); let kbd_id = keyboard.clone(); + my_seat._modifiers = modifiers; if is_active { if let Some(surface) = my_seat.kbd_focus.clone() { diff --git a/winit/src/platform_specific/wayland/handlers/seat/seat.rs b/winit/src/platform_specific/wayland/handlers/seat/seat.rs index 2901ce8e..ee6b0e6e 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/seat.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/seat.rs @@ -4,10 +4,9 @@ use crate::platform_specific::wayland::{ }; use cctk::sctk::{ delegate_seat, - reexports::client::{protocol::wl_keyboard::WlKeyboard, Proxy}, - seat::{pointer::ThemeSpec, SeatHandler}, + reexports::client::{Proxy, protocol::wl_keyboard::WlKeyboard}, + seat::{SeatHandler, keyboard::Modifiers, pointer::ThemeSpec}, }; -use iced_runtime::keyboard::Modifiers; use std::sync::Arc; impl SeatHandler for SctkState { From 0094b347d408aabc78091f744cb4eac49de1a7e4 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 21 May 2026 17:38:04 -0600 Subject: [PATCH 17/51] fix: avoid deadlock in context menu rendering --- winit/src/platform_specific/wayland/event_loop/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index 61bf12ea..3949d270 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -538,7 +538,6 @@ impl SctkEventLoop { wake_up = true; _ = s.frame(&state.state.queue_handle, s.clone()); - _ = state.state.frame_status.remove(&id); _ = state.state.events_sender.unbounded_send( Control::Winit( winit::window::WindowId::from_raw( @@ -547,6 +546,7 @@ impl SctkEventLoop { winit::event::WindowEvent::RedrawRequested, ), ); + _ = state.state.frame_status.insert(id, state::FrameStatus::Received); } if wake_up { From dfcd989947a7493b73342732ace786c0af873261 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 27 May 2026 14:50:31 -0600 Subject: [PATCH 18/51] fix: present suboptimal frames --- wgpu/src/window/compositor.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 4033aea4..f0595f2b 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -368,13 +368,14 @@ pub fn present( ) -> Result<(), compositor::SurfaceError> { match surface.get_current_texture() { Ok(frame) => { - if frame.suboptimal - || frame.texture.width() != viewport.physical_width() + if frame.texture.width() != viewport.physical_width() || frame.texture.height() != viewport.physical_height() { return Err(compositor::SurfaceError::Outdated); } + let suboptimal = frame.suboptimal; + let view = &frame .texture .create_view(&wgpu::TextureViewDescriptor::default()); @@ -390,6 +391,10 @@ pub fn present( on_pre_present(); frame.present(); + if suboptimal { + return Err(compositor::SurfaceError::Outdated); + } + Ok(()) } Err(error) => match error { From 9c748f1656bc15d159651eea0091c13c024ebc47 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 27 May 2026 17:25:49 -0400 Subject: [PATCH 19/51] Revert "fix: avoid deadlock in context menu rendering" This reverts commit 0094b347d408aabc78091f744cb4eac49de1a7e4. --- winit/src/platform_specific/wayland/event_loop/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index 3949d270..61bf12ea 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -538,6 +538,7 @@ impl SctkEventLoop { wake_up = true; _ = s.frame(&state.state.queue_handle, s.clone()); + _ = state.state.frame_status.remove(&id); _ = state.state.events_sender.unbounded_send( Control::Winit( winit::window::WindowId::from_raw( @@ -546,7 +547,6 @@ impl SctkEventLoop { winit::event::WindowEvent::RedrawRequested, ), ); - _ = state.state.frame_status.insert(id, state::FrameStatus::Received); } if wake_up { From 590e093086247dfc0b41580f752a4d59d5817a78 Mon Sep 17 00:00:00 2001 From: Gavin John Date: Fri, 29 May 2026 10:47:32 -0700 Subject: [PATCH 20/51] fix(text): don't reload cache every redraw I have >1000 fonts installed. COSMIC has always been somewhat laggy for me, but I had assumed it was just like that for everyone, or was the fault of my T480s. Eventually, when trying to diagnose some hitching, I ended up discovering that cosmic-launcher was consuming on average ~5% total CPU over time, which led me to discover that it was fetching all the fonts from disk and software rendering the fonts, every redraw, causing the high cpu usage *and* the stutters (as a result of hammering disk I/O). Oops. The fix is simple: just persist the cache across redraws. --- tiny_skia/src/text.rs | 11 ++++++++--- wgpu/src/text.rs | 5 ++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index add4dee8..dfd4d5f8 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -15,6 +15,7 @@ use std::collections::hash_map; pub struct Pipeline { glyph_cache: GlyphCache, cache: RefCell, + swash_cache: cosmic_text::SwashCache, } impl Pipeline { @@ -22,6 +23,7 @@ impl Pipeline { Pipeline { glyph_cache: GlyphCache::new(), cache: RefCell::new(Cache::new()), + swash_cache: cosmic_text::SwashCache::new(), } } @@ -54,6 +56,7 @@ impl Pipeline { draw( font_system.raw(), &mut self.glyph_cache, + &mut self.swash_cache, paragraph.buffer(), position, color, @@ -81,6 +84,7 @@ impl Pipeline { draw( font_system.raw(), &mut self.glyph_cache, + &mut self.swash_cache, editor.buffer(), position, color, @@ -142,6 +146,7 @@ impl Pipeline { draw( font_system, &mut self.glyph_cache, + &mut self.swash_cache, &entry.buffer, Point::new(x, y), color, @@ -165,6 +170,7 @@ impl Pipeline { draw( font_system.raw(), &mut self.glyph_cache, + &mut self.swash_cache, buffer, position, color, @@ -183,6 +189,7 @@ impl Pipeline { fn draw( font_system: &mut cosmic_text::FontSystem, glyph_cache: &mut GlyphCache, + swash_cache: &mut cosmic_text::SwashCache, buffer: &cosmic_text::Buffer, position: Point, color: Color, @@ -192,8 +199,6 @@ fn draw( ) { let position = position * transformation; - let mut swash = cosmic_text::SwashCache::new(); - for run in buffer.layout_runs() { for glyph in run.glyphs { let physical_glyph = glyph.physical( @@ -205,7 +210,7 @@ fn draw( physical_glyph.cache_key, glyph.color_opt.map(from_color).unwrap_or(color), font_system, - &mut swash, + swash_cache, ) { let pixmap = tiny_skia::PixmapRef::from_bytes( buffer, diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index b6d9e679..76b16c5c 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -308,6 +308,7 @@ pub struct State { prepare_layer: usize, cache: BufferCache, storage: Storage, + swash_cache: cryoglyph::SwashCache, } impl State { @@ -352,6 +353,7 @@ impl State { renderer, &mut atlas, &mut self.cache, + &mut self.swash_cache, text, layer_bounds * layer_transformation, layer_transformation * *transformation, @@ -448,6 +450,7 @@ fn prepare( renderer: &mut cryoglyph::TextRenderer, atlas: &mut cryoglyph::TextAtlas, buffer_cache: &mut BufferCache, + swash_cache: &mut cryoglyph::SwashCache, sections: &[Text], layer_bounds: Rectangle, layer_transformation: Transformation, @@ -643,6 +646,6 @@ fn prepare( atlas, viewport, text_areas, - &mut cryoglyph::SwashCache::new(), + swash_cache, ) } From ebb3b125bfc829617411f831afada122b6b4d02c Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 29 May 2026 16:43:44 -0400 Subject: [PATCH 21/51] Revert "fix(text): don't reload cache every redraw" This reverts commit 590e093086247dfc0b41580f752a4d59d5817a78. --- tiny_skia/src/text.rs | 11 +++-------- wgpu/src/text.rs | 5 +---- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index dfd4d5f8..add4dee8 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -15,7 +15,6 @@ use std::collections::hash_map; pub struct Pipeline { glyph_cache: GlyphCache, cache: RefCell, - swash_cache: cosmic_text::SwashCache, } impl Pipeline { @@ -23,7 +22,6 @@ impl Pipeline { Pipeline { glyph_cache: GlyphCache::new(), cache: RefCell::new(Cache::new()), - swash_cache: cosmic_text::SwashCache::new(), } } @@ -56,7 +54,6 @@ impl Pipeline { draw( font_system.raw(), &mut self.glyph_cache, - &mut self.swash_cache, paragraph.buffer(), position, color, @@ -84,7 +81,6 @@ impl Pipeline { draw( font_system.raw(), &mut self.glyph_cache, - &mut self.swash_cache, editor.buffer(), position, color, @@ -146,7 +142,6 @@ impl Pipeline { draw( font_system, &mut self.glyph_cache, - &mut self.swash_cache, &entry.buffer, Point::new(x, y), color, @@ -170,7 +165,6 @@ impl Pipeline { draw( font_system.raw(), &mut self.glyph_cache, - &mut self.swash_cache, buffer, position, color, @@ -189,7 +183,6 @@ impl Pipeline { fn draw( font_system: &mut cosmic_text::FontSystem, glyph_cache: &mut GlyphCache, - swash_cache: &mut cosmic_text::SwashCache, buffer: &cosmic_text::Buffer, position: Point, color: Color, @@ -199,6 +192,8 @@ fn draw( ) { let position = position * transformation; + let mut swash = cosmic_text::SwashCache::new(); + for run in buffer.layout_runs() { for glyph in run.glyphs { let physical_glyph = glyph.physical( @@ -210,7 +205,7 @@ fn draw( physical_glyph.cache_key, glyph.color_opt.map(from_color).unwrap_or(color), font_system, - swash_cache, + &mut swash, ) { let pixmap = tiny_skia::PixmapRef::from_bytes( buffer, diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 76b16c5c..b6d9e679 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -308,7 +308,6 @@ pub struct State { prepare_layer: usize, cache: BufferCache, storage: Storage, - swash_cache: cryoglyph::SwashCache, } impl State { @@ -353,7 +352,6 @@ impl State { renderer, &mut atlas, &mut self.cache, - &mut self.swash_cache, text, layer_bounds * layer_transformation, layer_transformation * *transformation, @@ -450,7 +448,6 @@ fn prepare( renderer: &mut cryoglyph::TextRenderer, atlas: &mut cryoglyph::TextAtlas, buffer_cache: &mut BufferCache, - swash_cache: &mut cryoglyph::SwashCache, sections: &[Text], layer_bounds: Rectangle, layer_transformation: Transformation, @@ -646,6 +643,6 @@ fn prepare( atlas, viewport, text_areas, - swash_cache, + &mut cryoglyph::SwashCache::new(), ) } From 5013f63956d9797571cfcebba6778adf9c8c3d87 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Fri, 29 May 2026 17:16:30 -0600 Subject: [PATCH 22/51] improv: reuse loaded fonts by cosmic-text for svg renderer --- graphics/src/text.rs | 8 ++++++++ tiny_skia/src/vector.rs | 15 ++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/graphics/src/text.rs b/graphics/src/text.rs index 230a362d..910cbbde 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -161,6 +161,14 @@ impl FontSystem { &mut self.raw } + /// Returns a reference to the underlying [`fontdb::Database`]. + /// + /// Unlike going through [`raw`](Self::raw) and `db_mut`, this does not + /// clear `cosmic-text`'s internal font-match cache. + pub fn db(&self) -> &cosmic_text::fontdb::Database { + self.raw.db() + } + /// Loads a font from its bytes. pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { if let Cow::Borrowed(bytes) = bytes { diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index 59cd1993..2c3b219c 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -1,5 +1,6 @@ use crate::core::svg::{Data, Handle}; use crate::core::{Color, Rectangle, Size}; +use crate::graphics::text; use resvg::usvg; use rustc_hash::{FxHashMap, FxHashSet}; @@ -74,6 +75,7 @@ struct Cache { rasters: FxHashMap, raster_hits: FxHashSet, fontdb: Option>, + fontdb_version: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -87,12 +89,15 @@ impl Cache { fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> { let id = handle.id(); - // TODO: Reuse `cosmic-text` font database - if self.fontdb.is_none() { - let mut fontdb = usvg::fontdb::Database::new(); - fontdb.load_system_fonts(); + // Reuse the font database already loaded by the `cosmic-text` font + // system. We clone it once and refresh only when a font is + // loaded (i.e. when the font system's version changes). + let font_system = text::font_system().read().expect("Read font system"); + let version = font_system.version(); - self.fontdb = Some(Arc::new(fontdb)); + if self.fontdb_version != Some(version) { + self.fontdb = Some(Arc::new(font_system.db().clone())); + self.fontdb_version = Some(version); } let options = usvg::Options { From dcf4cf01943b5a1baf7b7360ab7d604369a5c470 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 5 Jun 2026 19:20:37 -0400 Subject: [PATCH 23/51] improv: ignore duplicate surfaces and log a warning --- .../wayland/event_loop/state.rs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index e47caa94..6e39fdc2 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -508,6 +508,10 @@ pub struct SctkState { /// An error that occurred while running an application. #[derive(Debug, thiserror::Error)] pub enum PopupCreationError { + /// The requested ID already exists. + #[error("The requested ID already exists")] + AlreadyExists, + /// Positioner creation failed #[error("Positioner creation failed")] PositionerCreationFailed(GlobalError), @@ -528,6 +532,10 @@ pub enum PopupCreationError { /// An error that occurred while running an application. #[derive(Debug, thiserror::Error)] pub enum LayerSurfaceCreationError { + /// The requested ID already exists. + #[error("The requested ID already exists")] + AlreadyExists, + /// Layer shell is not supported by the compositor #[error("Layer shell is not supported by the compositor")] LayerShellNotSupported, @@ -544,6 +552,10 @@ pub enum LayerSurfaceCreationError { /// An error that occurred while running an application. #[derive(Debug, thiserror::Error)] pub enum SubsurfaceCreationError { + /// The requested ID already exists. + #[error("The requested ID already exists")] + AlreadyExists, + /// Subsurface creation failed #[error("Subsurface creation failed")] CreationFailed(GlobalError), @@ -682,6 +694,18 @@ impl SctkState { ), PopupCreationError, > { + if self.layer_surfaces.iter().any(|s| s.id == settings.id) + || self.windows.iter().any(|w| w.id == settings.id) + || self.popups.iter().any(|p| p.data.id == settings.id) + || self.subsurfaces.iter().any(|s| s.id == settings.id) + { + log::warn!( + "Popup surface with id {:?} already exists", + settings.id + ); + return Err(PopupCreationError::AlreadyExists); + } + let (parent, toplevel) = if let Some(parent) = self.layer_surfaces.iter().find(|l| l.id == settings.parent) { @@ -922,6 +946,15 @@ impl SctkState { IcedOutput::Output(output) => Some(output), }; + if self.layer_surfaces.iter().any(|s| s.id == id) + || self.windows.iter().any(|w| w.id == id) + || self.popups.iter().any(|p| p.data.id == id) + || self.subsurfaces.iter().any(|s| s.id == id) + { + log::warn!("Layer surface with id {id:?} already exists"); + return Err(LayerSurfaceCreationError::AlreadyExists); + } + let layer_shell = self .layer_shell .as_ref() @@ -1015,6 +1048,14 @@ impl SctkState { id: core::window::Id, output: &WlOutput, ) -> Option<(CommonSurface, Arc>)> { + if self.layer_surfaces.iter().any(|s| s.id == id) + || self.windows.iter().any(|w| w.id == id) + || self.popups.iter().any(|p| p.data.id == id) + || self.subsurfaces.iter().any(|s| s.id == id) + { + log::warn!("Lock surface with id {id:?} already exists"); + return None; + } if let Some(lock) = self.session_lock.as_ref() { let wl_surface = self.compositor_state.create_surface(&self.queue_handle); @@ -1755,6 +1796,14 @@ impl SctkState { let Some(subsurface_state) = self.subsurface_state.as_ref() else { return Err(SubsurfaceCreationError::Unsupported); }; + if self.layer_surfaces.iter().any(|s| s.id == settings.id) + || self.windows.iter().any(|w| w.id == settings.id) + || self.popups.iter().any(|p| p.data.id == settings.id) + || self.subsurfaces.iter().any(|s| s.id == settings.id) + { + log::warn!("Subsurface with id {:?} already exists", settings.id); + return Err(SubsurfaceCreationError::AlreadyExists); + } let size = settings.size.unwrap_or(Size::new(1., 1.)); let half_w = size.width / 2.; From f725961c8714b3e7f9257c70f44d9a5fb1589a07 Mon Sep 17 00:00:00 2001 From: Ilia Malanin Date: Tue, 9 Jun 2026 13:56:32 +0200 Subject: [PATCH 24/51] fix: reuse wgpu compositor across windows --- winit/src/lib.rs | 53 +++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 735d73aa..95df205b 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -864,28 +864,30 @@ async fn run_instance

( platform_specific_handler.send_wayland( platform_specific::Action::TrackWindow(window.clone(), id), ); - match create_compositor::

( - window.clone(), - CreateCompositor { - proxy: &proxy, - runtime: &mut runtime, - display_handle: &display_handle, - graphics_settings: &graphics_settings, - default_fonts: &default_fonts, - }, - ) - .await - { - Ok(comp) => { - compositor = Some(comp); - } - Err(error) => { - control_sender - .start_send(Control::Crash( - Error::GraphicsCreationFailed(error), - )) - .expect("Send control message"); - continue; + if compositor.is_none() { + match create_compositor::

( + window.clone(), + CreateCompositor { + proxy: &proxy, + runtime: &mut runtime, + display_handle: &display_handle, + graphics_settings: &graphics_settings, + default_fonts: &default_fonts, + }, + ) + .await + { + Ok(comp) => { + compositor = Some(comp); + } + Err(error) => { + control_sender + .start_send(Control::Crash( + Error::GraphicsCreationFailed(error), + )) + .expect("Send control message"); + continue; + } } } @@ -1014,6 +1016,7 @@ async fn run_instance

( &mut is_window_opening, &mut system_theme, &mut platform_specific_handler, + is_daemon, ); if exited { runtime.track(None.into_iter()); @@ -1171,6 +1174,7 @@ async fn run_instance

( &mut is_window_opening, &mut system_theme, &mut platform_specific_handler, + is_daemon, ); } @@ -1369,6 +1373,7 @@ async fn run_instance

( &mut is_window_opening, &mut system_theme, &mut platform_specific_handler, + is_daemon, ); } else { window.state.update(&program, window.raw.as_ref(), &event); @@ -1562,6 +1567,7 @@ async fn run_instance

( &mut is_window_opening, &mut system_theme, &mut platform_specific_handler, + is_daemon, ); if exited { runtime.track(None.into_iter()); @@ -2106,6 +2112,7 @@ fn run_action<'a, P, C>( is_window_opening: &mut bool, system_theme: &mut theme::Mode, platform_specific: &mut crate::platform_specific::PlatformSpecific, + is_daemon: bool, ) -> bool where P: Program, @@ -2190,7 +2197,7 @@ where )); } - if window_manager.is_empty() { + if window_manager.is_empty() && !is_daemon { *compositor = None; } } From c3c8fcf52cbe46c864459e74357dfe7985072a70 Mon Sep 17 00:00:00 2001 From: Ilia Malanin Date: Tue, 9 Jun 2026 14:06:09 +0200 Subject: [PATCH 25/51] refactor: wrap repeated run_action call in a macro --- winit/src/lib.rs | 94 ++++++++++++++---------------------------------- 1 file changed, 26 insertions(+), 68 deletions(-) diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 95df205b..410949ac 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -838,6 +838,28 @@ async fn run_instance

( log::info!("System theme: {system_theme:?}"); + macro_rules! run_action { + ($action:expr) => { + run_action( + $action, + &program, + &mut runtime, + &mut compositor, + &mut events, + &mut messages, + &mut clipboard, + &mut control_sender, + &mut user_interfaces, + &mut window_manager, + &mut ui_caches, + &mut is_window_opening, + &mut system_theme, + &mut platform_specific_handler, + is_daemon, + ) + }; + } + 'next_event: loop { // Empty the queue if possible let event = if let Ok(event) = event_receiver.try_recv() { @@ -1001,23 +1023,7 @@ async fn run_instance

( } } Event::UserEvent(action) => { - let exited = run_action( - action, - &program, - &mut runtime, - &mut compositor, - &mut events, - &mut messages, - &mut clipboard, - &mut control_sender, - &mut user_interfaces, - &mut window_manager, - &mut ui_caches, - &mut is_window_opening, - &mut system_theme, - &mut platform_specific_handler, - is_daemon, - ); + let exited = run_action!(action); if exited { runtime.track(None.into_iter()); } @@ -1159,23 +1165,7 @@ async fn run_instance

( continue; } - _ = run_action( - action, - &program, - &mut runtime, - &mut compositor, - &mut events, - &mut messages, - &mut clipboard, - &mut control_sender, - &mut user_interfaces, - &mut window_manager, - &mut ui_caches, - &mut is_window_opening, - &mut system_theme, - &mut platform_specific_handler, - is_daemon, - ); + _ = run_action!(action); } for (window_id, window) in window_manager.iter_mut() { @@ -1358,23 +1348,7 @@ async fn run_instance

( if matches!(event, winit::event::WindowEvent::CloseRequested) && window.exit_on_close_request { - _ = run_action( - Action::Window(runtime::window::Action::Close(id)), - &program, - &mut runtime, - &mut compositor, - &mut events, - &mut messages, - &mut clipboard, - &mut control_sender, - &mut user_interfaces, - &mut window_manager, - &mut ui_caches, - &mut is_window_opening, - &mut system_theme, - &mut platform_specific_handler, - is_daemon, - ); + _ = run_action!(Action::Window(runtime::window::Action::Close(id))); } else { window.state.update(&program, window.raw.as_ref(), &event); @@ -1552,23 +1526,7 @@ async fn run_instance

( )); for action in actions { - let exited = run_action( - action, - &program, - &mut runtime, - &mut compositor, - &mut events, - &mut messages, - &mut clipboard, - &mut control_sender, - &mut user_interfaces, - &mut window_manager, - &mut ui_caches, - &mut is_window_opening, - &mut system_theme, - &mut platform_specific_handler, - is_daemon, - ); + let exited = run_action!(action); if exited { runtime.track(None.into_iter()); } From 62b90179a298b06ed3e88362e08ef47f8557da27 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 10 Jun 2026 11:47:42 -0600 Subject: [PATCH 26/51] Fix List widget not updating children because iterator not consumed --- widget/src/list.rs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/widget/src/list.rs b/widget/src/list.rs index de9fe9b8..d7cadd50 100644 --- a/widget/src/list.rs +++ b/widget/src/list.rs @@ -363,24 +363,25 @@ where let state = tree.state.downcast_mut::(); let offset = layout.position() - Point::ORIGIN; - self.visible_elements + for (element, (index, layout, tree)) in self + .visible_elements .iter_mut() .zip(&mut state.visible_layouts) - .map(|(element, (index, layout, tree))| { - element.as_widget_mut().update( - tree, - event, - Layout::with_offset( - offset + Vector::new(0.0, self.spacing * *index as f32), - layout, - ), - cursor, - renderer, - clipboard, - shell, - viewport, - ) - }); + { + element.as_widget_mut().update( + tree, + event, + Layout::with_offset( + offset + Vector::new(0.0, self.spacing * *index as f32), + layout, + ), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } if let Event::Window(window::Event::RedrawRequested(_)) = event { match &mut state.task { From 7afd9ee0f83cfd1a4e9f9b56cf0f365e5ae60347 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Fri, 19 Jun 2026 18:31:15 -0600 Subject: [PATCH 27/51] fix: add alt clipboard bindings for text_editor --- widget/src/text_editor.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index a8f243fa..34a8825f 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -1287,6 +1287,23 @@ impl Binding { return Some(binding); } + let alt_clipboard = match key.as_ref() { + keyboard::Key::Named(key::Named::Insert) if modifiers.shift() => { + Some(Self::Paste) + } + keyboard::Key::Named(key::Named::Insert) if modifiers.command() => { + Some(Self::Copy) + } + keyboard::Key::Named(key::Named::Delete) if modifiers.shift() => { + Some(Self::Cut) + } + _ => None, + }; + + if let Some(binding) = alt_clipboard { + return Some(binding); + } + #[cfg(target_os = "macos")] let modified_key = convert_macos_shortcut(&key, modifiers).unwrap_or(modified_key); From 9f410558611b6e7e1027a7b227a7fab0b9019b93 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Mon, 29 Jun 2026 14:41:20 -0600 Subject: [PATCH 28/51] Revert "feat: add direct SCTK action sender for wayland popups" This reverts commit b007caba44b0be1d282edbe1cd6fcd440ef06eab. --- winit/src/platform_specific/wayland/mod.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 0ba156d7..9b3a7ec0 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -25,23 +25,10 @@ use std::{collections::HashMap, sync::Arc}; use subsurface_widget::{SubsurfaceInstance, SubsurfaceState}; use wayland_backend::client::ObjectId; use wayland_client::{Connection, Proxy}; -use std::sync::OnceLock; use winit::dpi::Size; use winit::event_loop::OwnedDisplayHandle; use winit::window::ImePurpose; -static SCTK_ACTION_SENDER: OnceLock> = OnceLock::new(); - -/// Sends a platform-specific wayland action directly to the SCTK event loop, -/// bypassing the async Task system. This is needed when widgets queue popup -/// actions outside of the normal message flow (e.g., text context menus that -/// don't publish app messages). -pub fn send_wayland_action_direct(action: iced_runtime::platform_specific::wayland::Action) { - if let Some(sender) = SCTK_ACTION_SENDER.get() { - _ = sender.send(Action::Action(action)); - } -} - pub(crate) enum Action { Action(iced_runtime::platform_specific::wayland::Action), SetCursor(CursorIcon), @@ -149,9 +136,6 @@ impl PlatformSpecific { self.wayland.display_handle.clone().unwrap(), ) .ok(); - if let Some(ref sender) = self.wayland.sender { - _ = SCTK_ACTION_SENDER.set(sender.clone()); - } self } From 1bcba7e30cb082df1629f8ecdb02123f1403439b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 16 Apr 2026 23:01:58 -0400 Subject: [PATCH 29/51] wip: corner radius v2 --- Cargo.lock | 4 +- Cargo.toml | 2 +- src/lib.rs | 4 +- .../wayland/event_loop/mod.rs | 13 +- .../wayland/event_loop/state.rs | 123 ++++++++++++------ .../wayland/handlers/shell/corner_radius.rs | 55 +++++--- winit/src/platform_specific/wayland/mod.rs | 1 - 7 files changed, 137 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38f19187..11a85d77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1294,7 +1294,7 @@ dependencies = [ [[package]] name = "cosmic-client-toolkit" version = "0.2.0" -source = "git+https://github.com/pop-os/cosmic-protocols?rev=160b086#160b086abe03cd34a8a375d7fbe47b24308d1f38" +source = "git+https://github.com/pop-os/cosmic-protocols?rev=a7d2d7a#a7d2d7a6188425f699eaddf7a025617ce56068ae" dependencies = [ "bitflags 2.11.0", "cosmic-protocols", @@ -1307,7 +1307,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.2.0" -source = "git+https://github.com/pop-os/cosmic-protocols?rev=160b086#160b086abe03cd34a8a375d7fbe47b24308d1f38" +source = "git+https://github.com/pop-os/cosmic-protocols?rev=a7d2d7a#a7d2d7a6188425f699eaddf7a025617ce56068ae" dependencies = [ "bitflags 2.11.0", "wayland-backend", diff --git a/Cargo.toml b/Cargo.toml index e9f43f7f..b5e1b8d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -274,7 +274,7 @@ tiny-skia = { version = "0.11", default-features = false, features = [ "std", "simd", ] } -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "c253ec1" } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "a7d2d7a" } softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" } syntect = "5.2" tokio = "1.0" diff --git a/src/lib.rs b/src/lib.rs index 46685d76..6e57128b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -682,7 +682,9 @@ pub use error::Error; pub use event::Event; pub use executor::Executor; pub use font::Font; -pub use renderer::{Renderer, graphics::geometry}; +pub use renderer::Renderer; +#[cfg(feature = "canvas")] +pub use renderer::graphics::geometry; pub use task::Task; pub use window::Window; diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index 61bf12ea..fea1c28c 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -33,7 +33,7 @@ use cctk::{ reexports::{ calloop::{self, EventLoop}, client::{ - ConnectError, Connection, Proxy, globals::registry_queue_init, + Connection, Proxy, globals::registry_queue_init, }, }, registry::RegistryState, @@ -46,8 +46,6 @@ use cctk::{ }; use raw_window_handle::HasDisplayHandle; use state::{FrameStatus, SctkWindow, send_event}; -#[cfg(feature = "a11y")] -use std::sync::{Arc, Mutex}; use std::{ collections::{HashMap, HashSet}, fmt::Debug, @@ -71,8 +69,8 @@ pub struct SctkEventLoop { pub(crate) state: SctkState, } +#[derive(Debug)] pub enum Error { - Connect(ConnectError), Calloop(calloop::Error), Global(GlobalError), NoDisplayHandle, @@ -129,7 +127,7 @@ impl SctkEventLoop { crate::platform_specific::Action::ResizeWindow(id) => { if let Some((_, v)) = state.windows.iter() .find(|w| w.id == id) - .map(|w| w.corner_radius.as_ref()) + .map(|w| state.corner_radii.get(&id)) .unwrap_or_default() { _ = state.handle_action(iced_runtime::platform_specific::wayland::Action::RoundedCorners(id, *v)); } @@ -138,7 +136,7 @@ impl SctkEventLoop { window, id, ) => { - state.windows.push(SctkWindow { window, id, corner_radius: Default::default() }); + state.windows.push(SctkWindow { window, id }); if let Some(v) = state.pending_corner_radius.remove(&id) { _ = state.handle_action(iced_runtime::platform_specific::wayland::Action::RoundedCorners(id, Some(v))); } @@ -350,7 +348,7 @@ impl SctkEventLoop { ), corner_radius_manager: registry_state.bind_one::( &qh, - 1..=1, + 1..=2, (), ).ok(), toplevel_manager: ToplevelManagerState::try_new( @@ -377,6 +375,7 @@ impl SctkEventLoop { seats: Vec::new(), windows: Vec::new(), blur_surfaces: HashMap::new(), + corner_radii: HashMap::new(), layer_surfaces: Vec::new(), popups: Vec::new(), lock_surfaces: Vec::new(), diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 6e39fdc2..11f987f9 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1,10 +1,7 @@ use crate::{ Control, handlers::{ - activation::IcedRequestData, - ext_background_effect, - overlap::{OverlapNotificationV1, OverlapNotifyV1}, - text_input::{Preedit, TextInputManager}, + activation::IcedRequestData, ext_background_effect, overlap::{OverlapNotificationV1, OverlapNotifyV1}, shell::corner_radius::CornerRadiusWrapper, text_input::{Preedit, TextInputManager} }, platform_specific::{ Event, @@ -65,7 +62,7 @@ use cctk::{ wl_surface::{self, WlSurface}, wl_touch::WlTouch, }, - }, + }, protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, }, registry::RegistryState, seat::{ @@ -341,22 +338,13 @@ pub struct SctkPopupData { pub(crate) grab: bool, } -#[derive(Debug)] -pub struct MyCosmicCornerRadiusToplevelV1(CosmicCornerRadiusToplevelV1); - -impl Drop for MyCosmicCornerRadiusToplevelV1 { - fn drop(&mut self) { - self.0.destroy(); - } -} #[derive(Debug, Clone)] -pub struct SctkCornerRadius(Arc); +pub struct SctkCornerRadius(Arc); pub struct SctkWindow { pub(crate) window: Arc, pub(crate) id: core::window::Id, - pub(crate) corner_radius: Option<(SctkCornerRadius, Option)>, } impl SctkWindow { @@ -444,6 +432,7 @@ pub struct SctkState { pub(crate) subsurfaces: Vec, pub(crate) lock_surfaces: Vec, pub(crate) blur_surfaces: HashMap>, + pub(crate) corner_radii: HashMap)>, pub(crate) touch_points: HashMap, /// Window updates, which are coming from SCTK or the compositor, which require @@ -1137,6 +1126,7 @@ impl SctkState { s.destroy(); } } + _ = self.corner_radii.remove(&id); let (removed, remaining): (Vec<_>, Vec<_>) = self .subsurfaces @@ -1396,6 +1386,8 @@ impl SctkState { s.destroy(); } } + _ = self.corner_radii.remove(&id); + let (removed, remaining): (Vec<_>, Vec<_>) = self .subsurfaces @@ -1524,6 +1516,8 @@ impl SctkState { s.destroy(); } } + _ = self.corner_radii.remove(&id); + let (removed, remaining): (Vec<_>, Vec<_>) = self .subsurfaces .drain(..) @@ -1609,6 +1603,8 @@ impl SctkState { s.destroy(); } } + _ = self.corner_radii.remove(&id); + if let Some((wl_surface, f)) = self.seats.iter_mut().find(|f| { f.kbd_focus.as_ref().is_some_and(|f| *f == destroyed) }).and_then(|f| Some((parent, &mut f.kbd_focus))) { @@ -1636,8 +1632,25 @@ impl SctkState { } Action::RoundedCorners(id, v) => { if let Some(manager) = self.corner_radius_manager.as_ref() { - if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) { - let geo_size: LogicalSize = w.window.surface_size().cast::().to_logical(w.window.scale_factor()); + enum Surface { + Xdg(XdgSurface, Option), + Wlr(ZwlrLayerSurfaceV1), + } + let s = if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) { + Some((Surface::Xdg(w.xdg_surface(&self.connection), Some(w.xdg_toplevel(&self.connection))), w.window.surface_size().cast::().to_logical(w.window.scale_factor()))) + } else if let Some(p) = self.popups.iter_mut().find(|w| w.data.id == id) { + let guard = p.common.lock().unwrap(); + Some((Surface::Xdg(p.popup.xdg_surface().clone(), None), guard.size.cast::())) + } else if let Some(l) = self.layer_surfaces.iter_mut().find(|l| l.id == id) { + let guard = l.common.lock().unwrap(); + match l.surface.kind() { + SurfaceKind::Wlr(l) => Some((Surface::Wlr(l.clone()), guard.size.cast::())), + _ => None + } + } else { + None + }; + if let Some((s, geo_size)) = s { let half_min_dim = (geo_size.width as u32).min(geo_size.height as u32) / 2; if let Some(radii) = v { @@ -1647,37 +1660,73 @@ impl SctkState { bottom_right: radii.bottom_right.min(half_min_dim), bottom_left: radii.bottom_left.min(half_min_dim), }; - if let Some((protocol_object, corner_radii)) = w.corner_radius.as_mut() { + if let Some((protocol_object, corner_radii)) = self.corner_radii.get_mut(&id) { if *corner_radii != Some(adjusted_radii) { - protocol_object.0.0.set_radius( + match protocol_object.0.as_ref() { + CornerRadiusWrapper::Xdg(protocol_object) => protocol_object.set_radius( + adjusted_radii.top_left, + adjusted_radii.top_right, + adjusted_radii.bottom_right, + adjusted_radii.bottom_left, + ), + CornerRadiusWrapper::Wlr(protocol_object) => protocol_object.set_radius( + adjusted_radii.top_left, + adjusted_radii.top_right, + adjusted_radii.bottom_right, + adjusted_radii.bottom_left, + ) + }; + *corner_radii = Some(adjusted_radii.clone()); + } + } else { + + let protocol_object = match s { + Surface::Xdg(s, w) => { + if manager.version() == 1 { + if let Some(w) = w { + CornerRadiusWrapper::Xdg(manager.get_corner_radius(&w, &self.queue_handle, ())) + } else { + log::error!("Corner radius is not supported for popups on xdg shell v1"); + return Ok(()); + } + } else { + CornerRadiusWrapper::Xdg(manager.get_corner_radius_surface(&s, &self.queue_handle, ()))} + } + Surface::Wlr(l) => { + if manager.version() == 1 { + return Ok(()); + } + CornerRadiusWrapper::Wlr(manager.get_corner_radius_layer(&l, &self.queue_handle, ()))} + }; + match &protocol_object { + CornerRadiusWrapper::Xdg(protocol_object) => protocol_object.set_radius( adjusted_radii.top_left, adjusted_radii.top_right, adjusted_radii.bottom_right, adjusted_radii.bottom_left, - ); - *corner_radii = Some(adjusted_radii.clone()); - } - } else { - let toplevel = w.xdg_toplevel(&self.connection); - - let protocol_object = manager.get_corner_radius(&toplevel, &self.queue_handle, ()); - - protocol_object.set_radius( - adjusted_radii.top_left, - adjusted_radii.top_right, - adjusted_radii.bottom_right, - adjusted_radii.bottom_left, - ); - w.corner_radius = Some((SctkCornerRadius(Arc::new(MyCosmicCornerRadiusToplevelV1( protocol_object))), Some(adjusted_radii.clone()))); + ), + CornerRadiusWrapper::Wlr(protocol_object) => protocol_object.set_radius( + adjusted_radii.top_left, + adjusted_radii.top_right, + adjusted_radii.bottom_right, + adjusted_radii.bottom_left, + ) + }; + _ = self.corner_radii.insert(id, (SctkCornerRadius(Arc::new(protocol_object)), Some(adjusted_radii.clone()))); } } else { - if let Some(old) = w.corner_radius.as_mut() { - old.0.0.as_ref().0.unset_radius(); + if let Some(old) = self.corner_radii.get_mut(&id) { + match old.0.0.as_ref() { + CornerRadiusWrapper::Xdg(protocol_object) => protocol_object.unset_radius(), + CornerRadiusWrapper::Wlr(protocol_object) => { + protocol_object.unset_radius() + } + }; old.1 = None; } } } else { - if let Some(v) = v{ + if let Some(v) = v { _ = self.pending_corner_radius.insert(id, v); } else { _ = self.pending_corner_radius.remove(&id); diff --git a/winit/src/platform_specific/wayland/handlers/shell/corner_radius.rs b/winit/src/platform_specific/wayland/handlers/shell/corner_radius.rs index bd7d2409..c4bdda88 100644 --- a/winit/src/platform_specific/wayland/handlers/shell/corner_radius.rs +++ b/winit/src/platform_specific/wayland/handlers/shell/corner_radius.rs @@ -1,17 +1,29 @@ -use cctk::{sctk, cosmic_protocols::{ - corner_radius::v1::client::{ +use cctk::{ + cosmic_protocols::corner_radius::v1::client::{ + cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1, cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, }, - overlap_notify::v1::client::zcosmic_overlap_notification_v1::ZcosmicOverlapNotificationV1, -}}; -use sctk::reexports::{ - client::{Connection, Dispatch, Proxy}, - + sctk, }; +use sctk::reexports::client::{Connection, Dispatch, Proxy}; use crate::event_loop::state::SctkState; -use crate::platform_specific::wayland::SctkEvent; + +#[derive(Debug)] +pub enum CornerRadiusWrapper { + Xdg(CosmicCornerRadiusToplevelV1), + Wlr(CosmicCornerRadiusLayerV1), +} + +impl Drop for CornerRadiusWrapper { + fn drop(&mut self) { + match self { + Self::Xdg(c) => c.destroy(), + Self::Wlr(c) => c.destroy(), + }; + } +} impl Dispatch for SctkState { fn event( @@ -21,15 +33,11 @@ impl Dispatch for SctkState { _data: &(), _conn: &Connection, _qhandle: &sctk::reexports::client::QueueHandle, - ) {} + ) { + } } -impl - Dispatch< - CosmicCornerRadiusToplevelV1, - (), - > for SctkState -{ +impl Dispatch for SctkState { fn event( state: &mut Self, _proxy: &CosmicCornerRadiusToplevelV1, @@ -39,7 +47,22 @@ impl _qhandle: &sctk::reexports::client::QueueHandle, ) { match event { - _ => unimplemented!() + _ => unimplemented!(), + } + } +} + +impl Dispatch for SctkState { + fn event( + state: &mut Self, + _proxy: &CosmicCornerRadiusLayerV1, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &sctk::reexports::client::QueueHandle, + ) { + match event { + _ => unimplemented!(), } } } diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 9b3a7ec0..bbbce8f1 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -128,7 +128,6 @@ impl PlatformSpecific { }; self.wayland.display_handle = Some(display); self.wayland.proxy = Some(raw); - // TODO remove this self.wayland.sender = crate::platform_specific::event_loop::SctkEventLoop::new( self.wayland.winit_event_sender.clone().unwrap(), From a63d57261f07b86f3237ce4be053efa94cb33dbe Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 17 Apr 2026 13:41:09 -0400 Subject: [PATCH 30/51] blur enabled event --- core/src/event/wayland/mod.rs | 2 ++ .../wayland/handlers/ext_background_effect.rs | 14 ++++++++--- .../platform_specific/wayland/sctk_event.rs | 25 ++++++------------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/core/src/event/wayland/mod.rs b/core/src/event/wayland/mod.rs index 03e74407..5050ce66 100644 --- a/core/src/event/wayland/mod.rs +++ b/core/src/event/wayland/mod.rs @@ -46,4 +46,6 @@ pub enum Event { Subsurface(SubsurfaceEvent), /// Keyboard inhibit shortcuts ShortcutsInhibited(bool), + /// Blur Support Enabled + BlurEnabled, } diff --git a/winit/src/platform_specific/wayland/handlers/ext_background_effect.rs b/winit/src/platform_specific/wayland/handlers/ext_background_effect.rs index 03a80da9..2d0df67a 100644 --- a/winit/src/platform_specific/wayland/handlers/ext_background_effect.rs +++ b/winit/src/platform_specific/wayland/handlers/ext_background_effect.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use cctk::sctk; +use iced_futures::futures::SinkExt; use iced_runtime::core::Rectangle; use iced_runtime::platform_specific::wayland::Action; use sctk::globals::GlobalData; @@ -11,7 +12,7 @@ use wayland_protocols::ext::background_effect::v1::client::ext_background_effect use wayland_protocols::ext::background_effect::v1::client::ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1; use crate::event_loop::state::SctkState; -use crate::window; +use crate::{platform_specific, window}; #[derive(Debug, Clone)] pub struct ExtBackgroundEffectManager { @@ -70,8 +71,15 @@ impl Dispatch state.ext_background_effect_manager.as_mut() { bg_effect_mgr.capabilities = capability; - queued_actions = - bg_effect_mgr.queued_blur_actions.drain().collect(); + if capability.contains(Capability::Blur) { + queued_actions = bg_effect_mgr + .queued_blur_actions + .drain() + .collect(); + state + .sctk_events + .push(crate::sctk_event::SctkEvent::Blur); + } } for (id, rects) in queued_actions { _ = state.handle_action(Action::BlurSurface(id, rects)); diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index 8845b0c9..2ac54bed 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -68,10 +68,8 @@ use cctk::{ xdg::{popup::PopupConfigure, window::WindowConfigure}, }, }, - wayland_client::protocol::wl_subsurface::WlSubsurface, }; use std::{ - any::Any, collections::HashMap, num::NonZeroU32, sync::{Arc, Mutex}, @@ -98,9 +96,7 @@ use super::{ #[derive(Debug, Clone)] pub enum SctkEvent { - // - // Input events - // + Blur, SeatEvent { variant: SeatEventVariant, id: WlSeat, @@ -122,11 +118,6 @@ pub enum SctkEvent { seat_id: WlSeat, surface: WlSurface, }, - // TODO data device & touch - - // - // Surface Events - // WindowEvent { variant: WindowEventVariant, id: WlSurface, @@ -167,10 +158,6 @@ pub enum SctkEvent { }, SubsurfaceEvent(SubsurfaceEventVariant), - - // - // output events - // NewOutput { id: WlOutput, info: Option, @@ -180,9 +167,7 @@ pub enum SctkEvent { info: OutputInfo, }, RemovedOutput(WlOutput), - // - // compositor events - // + ScaleFactorChanged { factor: f64, id: WlOutput, @@ -1738,6 +1723,12 @@ impl SctkEvent { ), ), )), + SctkEvent::Blur => events.push(( + None, + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::BlurEnabled), + ), + )), } } } From e0c7d353aa3915a7543c253679f5038703f20436 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 17 Apr 2026 13:41:21 -0400 Subject: [PATCH 31/51] better handle pending corners --- .../wayland/event_loop/state.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 11f987f9..ff6e892a 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1099,6 +1099,12 @@ impl SctkState { id: wl_surface.clone(), } ); + if let Some(corners) = self.pending_corner_radius.remove(&id) { + self.handle_action(Action::RoundedCorners( + id, + Some(corners), + ))?; + } } } platform_specific::wayland::layer_surface::Action::Size { @@ -1318,6 +1324,12 @@ impl SctkState { SctkEvent::PopupEvent { variant: crate::platform_specific::wayland::sctk_event::PopupEventVariant::Created(queue_handle.clone(), surface, id, common, state.connection.display()), toplevel_id, parent_id, id: wl_surface }); + if let Some(corners) = state.pending_corner_radius.remove(&id) { + state.handle_action(Action::RoundedCorners ( + id, + Some(corners), + )); + } } Err(err) => { log::error!("Failed to create popup. {err:?}"); @@ -1327,7 +1339,6 @@ impl SctkState { } }); } - // log::error!("Invalid popup Id {:?}", popup.id); } else { self.pending_popup = None; match self.get_popup(settings) { @@ -1337,6 +1348,12 @@ impl SctkState { SctkEvent::PopupEvent { variant: crate::platform_specific::wayland::sctk_event::PopupEventVariant::Created(self.queue_handle.clone(), surface, id, common, self.connection.display()), toplevel_id, parent_id, id: wl_surface }); + if let Some(corners) = self.pending_corner_radius.remove(&id) { + self.handle_action(Action::RoundedCorners ( + id, + Some(corners), + )); + } } Err(err) => { log::error!("Failed to create popup. {err:?}"); From f6fa506f6cc60774fd8606125d005ba9f5074565 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 20 Apr 2026 15:33:26 -0400 Subject: [PATCH 32/51] chore(layer shell): add padding action --- .../wayland/layer_surface.rs | 43 ++++++++++++++----- .../wayland/commands/layer_surface.rs | 15 ++++++- .../wayland/event_loop/state.rs | 8 ++++ 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/runtime/src/platform_specific/wayland/layer_surface.rs b/runtime/src/platform_specific/wayland/layer_surface.rs index 4745ef67..6e5f3e9e 100644 --- a/runtime/src/platform_specific/wayland/layer_surface.rs +++ b/runtime/src/platform_specific/wayland/layer_surface.rs @@ -6,8 +6,8 @@ use cctk::sctk::{ }; use iced_core::layout::Limits; -use iced_core::window::Id; use iced_core::Rectangle; +use iced_core::window::Id; /// output for layer surface #[derive(Debug, Clone)] @@ -150,6 +150,13 @@ pub enum Action { /// layer of the layer surface layer: Layer, }, + /// padding of the surface geometry + Padding { + /// id of the layer surface + id: Id, + /// padding of the surface geometry + padding: IcedMargin, + }, } impl fmt::Debug for Action { @@ -162,35 +169,51 @@ impl fmt::Debug for Action { ), Action::Size { id, width, height } => write!( f, - "Action::LayerSurfaceAction::Size {{ id: {:#?}, width: {:?}, height: {:?} }}", id, width, height + "Action::LayerSurfaceAction::Size {{ id: {:#?}, width: {:?}, height: {:?} }}", + id, width, height ), Action::Destroy(id) => write!( f, - "Action::LayerSurfaceAction::Destroy {{ id: {:#?} }}", id + "Action::LayerSurfaceAction::Destroy {{ id: {:#?} }}", + id ), Action::Anchor { id, anchor } => write!( f, - "Action::LayerSurfaceAction::Anchor {{ id: {:#?}, anchor: {:?} }}", id, anchor + "Action::LayerSurfaceAction::Anchor {{ id: {:#?}, anchor: {:?} }}", + id, anchor ), Action::ExclusiveZone { id, exclusive_zone } => write!( f, - "Action::LayerSurfaceAction::ExclusiveZone {{ id: {:#?}, exclusive_zone: {exclusive_zone} }}", id + "Action::LayerSurfaceAction::ExclusiveZone {{ id: {:#?}, exclusive_zone: {exclusive_zone} }}", + id ), Action::Margin { id, margin } => write!( f, - "Action::LayerSurfaceAction::Margin {{ id: {:#?}, margin: {:?} }}", id, margin + "Action::LayerSurfaceAction::Margin {{ id: {:#?}, margin: {:?} }}", + id, margin ), - Action::KeyboardInteractivity { id, keyboard_interactivity } => write!( + Action::KeyboardInteractivity { + id, + keyboard_interactivity, + } => write!( f, - "Action::LayerSurfaceAction::KeyboardInteractivity {{ id: {:#?}, keyboard_interactivity: {:?} }}", id, keyboard_interactivity + "Action::LayerSurfaceAction::KeyboardInteractivity {{ id: {:#?}, keyboard_interactivity: {:?} }}", + id, keyboard_interactivity ), Action::InputZone { id, zone } => write!( f, - "Action::LayerSurfaceAction::InputZone {{ id: {:#?}, zone: {:?} }}", id, zone + "Action::LayerSurfaceAction::InputZone {{ id: {:#?}, zone: {:?} }}", + id, zone ), Action::Layer { id, layer } => write!( f, - "Action::LayerSurfaceAction::Layer {{ id: {:#?}, layer: {:?} }}", id, layer + "Action::LayerSurfaceAction::Layer {{ id: {:#?}, layer: {:?} }}", + id, layer + ), + Action::Padding { id, padding } => write!( + f, + "Action::LayerSurfaceAction::Padding {{ id: {:#?}, padding: {:?} }}", + id, padding ), } } diff --git a/winit/src/platform_specific/wayland/commands/layer_surface.rs b/winit/src/platform_specific/wayland/commands/layer_surface.rs index 839db95c..cdb4e93d 100644 --- a/winit/src/platform_specific/wayland/commands/layer_surface.rs +++ b/winit/src/platform_specific/wayland/commands/layer_surface.rs @@ -2,7 +2,7 @@ use crate::core::window::Id as SurfaceId; use iced_runtime::{ - self, + self, Action, Task, platform_specific::{ self, wayland::{ @@ -10,7 +10,7 @@ use iced_runtime::{ layer_surface::{IcedMargin, SctkLayerSurfaceSettings}, }, }, - task, Action, Task, + task, }; pub use cctk::sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}; @@ -114,3 +114,14 @@ pub fn set_layer(id: SurfaceId, layer: Layer) -> Task { )), )) } + +pub fn set_padding( + id: SurfaceId, + padding: IcedMargin, +) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::Padding { id, padding }, + )), + )) +} diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index ff6e892a..d0159a35 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1229,6 +1229,14 @@ impl SctkState { } }, + platform_specific::wayland::layer_surface::Action::Padding { id, padding } => { + // corner radius padding + if let Some((protocol_object, _)) = self.corner_radii.get_mut(&id) { + if let CornerRadiusWrapper::Wlr(protocol_object) = protocol_object.0.as_ref() { + protocol_object.set_padding(padding.top, padding.right, padding.bottom, padding.left); + } + } + }, }, Action::Popup(action) => match action { platform_specific::wayland::popup::Action::Popup { popup: settings } => { From 6521f7fa20a891d61f1c8782f78e5435968f1f48 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sun, 10 May 2026 22:36:29 -0400 Subject: [PATCH 33/51] fix: improved layer surface popup cleanup --- winit/src/platform_specific/wayland/event_loop/state.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index d0159a35..dc793561 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1124,6 +1124,12 @@ impl SctkState { } }, platform_specific::wayland::layer_surface::Action::Destroy(id) => { + if let Some(p) = self + .popups + .iter().find_map(|p| + self.layer_surfaces.iter().find_map(|l| if l.id == id && l.surface.wl_surface() == p.data.parent.wl_surface() {Some(p.data.id)} else {None})) { + _ = self.handle_action(Action::Popup(platform_specific::wayland::popup::Action::Destroy { id: p })); + } if let Some(i) = self.layer_surfaces.iter().position(|l| l.id == id) { let l = self.layer_surfaces.remove(i); @@ -1155,6 +1161,8 @@ impl SctkState { if let Some(destroyed) = self.id_map.remove(&l.surface.wl_surface().id()) { _ = self.destroyed.insert(destroyed); } + + send_event(&self.events_sender, &self.proxy, SctkEvent::LayerSurfaceEvent { variant: LayerSurfaceEventVariant::Done, id: l.surface.wl_surface().clone(), From b8b0804140e8ade4fb2058eff173b4cd450a7b83 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 19 May 2026 14:47:13 -0400 Subject: [PATCH 34/51] fix(overlap): fix check for disabling --- winit/src/platform_specific/wayland/event_loop/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index dc793561..998a599a 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1590,7 +1590,7 @@ impl SctkState { }; let notification = overlap_notify_state.notify.notify_on_overlap(wlr, &self.queue_handle, OverlapNotificationV1 { surface: layer_surface.surface.wl_surface().clone() }); _ = self.overlap_notifications.insert(my_id, notification); - } else { + } else if !enabled { _ = self.overlap_notifications.remove(&my_id); } } else { From 6be49cb530e76c78ac767e89cd9f869533409476 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 21 May 2026 14:10:28 -0400 Subject: [PATCH 35/51] blur command helper --- .../platform_specific/wayland/commands/blur.rs | 17 +++++++++++++++++ .../platform_specific/wayland/commands/mod.rs | 1 + 2 files changed, 18 insertions(+) create mode 100644 winit/src/platform_specific/wayland/commands/blur.rs diff --git a/winit/src/platform_specific/wayland/commands/blur.rs b/winit/src/platform_specific/wayland/commands/blur.rs new file mode 100644 index 00000000..e9a73646 --- /dev/null +++ b/winit/src/platform_specific/wayland/commands/blur.rs @@ -0,0 +1,17 @@ +use iced_futures::core::window; +use iced_runtime::{ + self, Action, Task, + platform_specific::{self, wayland}, + task, +}; + +pub fn blur( + id: window::Id, + blur: Option>, +) -> Task<()> { + task::oneshot(|_| { + Action::PlatformSpecific(platform_specific::Action::Wayland( + wayland::Action::BlurSurface(id, blur), + )) + }) +} diff --git a/winit/src/platform_specific/wayland/commands/mod.rs b/winit/src/platform_specific/wayland/commands/mod.rs index 191dc3a5..cd6832c7 100644 --- a/winit/src/platform_specific/wayland/commands/mod.rs +++ b/winit/src/platform_specific/wayland/commands/mod.rs @@ -1,6 +1,7 @@ //! Interact with the wayland objects of your application. pub mod activation; +pub mod blur; pub mod corner_radius; pub mod keyboard_shortcuts_inhibit; pub mod layer_surface; From 5cc9f8d621df536facf605adcee3a25c25666e9f Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 21 May 2026 14:11:20 -0400 Subject: [PATCH 36/51] fix: single blur surface per wl surface --- .../wayland/event_loop/state.rs | 72 ++++++------------- 1 file changed, 23 insertions(+), 49 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 998a599a..0fb1a132 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -431,7 +431,7 @@ pub struct SctkState { pub(crate) popups: Vec, pub(crate) subsurfaces: Vec, pub(crate) lock_surfaces: Vec, - pub(crate) blur_surfaces: HashMap>, + pub(crate) blur_surfaces: HashMap, pub(crate) corner_radii: HashMap)>, pub(crate) touch_points: HashMap, @@ -1134,9 +1134,7 @@ impl SctkState { let l = self.layer_surfaces.remove(i); if let Some(blurred) = self.blur_surfaces.remove(&l.id) { - for s in blurred { - s.destroy(); - } + blurred.destroy(); } _ = self.corner_radii.remove(&id); @@ -1415,9 +1413,7 @@ impl SctkState { } if let Some(blurred) = self.blur_surfaces.remove(&id) { - for s in blurred { - s.destroy(); - } + blurred.destroy(); } _ = self.corner_radii.remove(&id); @@ -1545,9 +1541,7 @@ impl SctkState { { let surface = self.lock_surfaces.remove(i); if let Some(blurred) = self.blur_surfaces.remove(&surface.id) { - for s in blurred { - s.destroy(); - } + blurred.destroy(); } _ = self.corner_radii.remove(&id); @@ -1632,9 +1626,7 @@ impl SctkState { } for (destroyed, parent, id) in destroyed { if let Some(blurred) = self.blur_surfaces.remove(&id) { - for s in blurred { - s.destroy(); - } + blurred.destroy(); } _ = self.corner_radii.remove(&id); @@ -1790,45 +1782,27 @@ impl SctkState { let existing_blur = self.blur_surfaces.entry(id); match (existing_blur, rectangles) { (Entry::Occupied(occupied_entry), None) => { - let blur_surfaces = occupied_entry.remove(); - for region in blur_surfaces { - region.destroy(); - } + let blur_surface = occupied_entry.remove(); + blur_surface.destroy(); }, (Entry::Occupied(mut occupied_entry), Some(rectangles)) => { - let blur_surfaces = occupied_entry.get_mut(); - let regions = rectangles.into_iter().map(|rect| { - let region = self + let blur_surface = occupied_entry.get_mut(); + let region = self .compositor_state .wl_compositor() .create_region(&self.queue_handle, ()); + for rect in rectangles.into_iter() { region.add( rect.x.round() as i32, rect.y.round() as i32, rect.width.round() as i32, rect.height.round() as i32, ); - region - }).collect::>(); - if regions.len() > blur_surfaces.len() { - // add extra blur surfaces if needed - for _ in blur_surfaces.len()..regions.len() { - let Some(extra_region) = self.ext_background_effect_manager.as_mut().map(|mgr| mgr.blur(s, &self.queue_handle)) else { - log::error!("Failed to create blur effect for surface"); - return Ok(()); - }; - blur_surfaces.push(extra_region); - } - } else if regions.len() < blur_surfaces.len() { - for surface in blur_surfaces.iter().skip(regions.len()) { - surface.destroy(); - } } - + // update existing blur surfaces - for (blur_surface, region) in blur_surfaces.iter().zip(regions.into_iter()) { - blur_surface.set_blur_region(Some(®ion)); - } + blur_surface.set_blur_region(Some(®ion)); + }, (Entry::Vacant(..), None) => { // nothing to remove @@ -1838,23 +1812,23 @@ impl SctkState { log::error!("Blur effect is not supported."); return Ok(()); } - let blur_surfaces = rectangles.into_iter().map(|rect| { let region = self .compositor_state .wl_compositor() .create_region(&self.queue_handle, ()); - region.add( - rect.x.round() as i32, - rect.y.round() as i32, - rect.width.round() as i32, - rect.height.round() as i32, - ); + for rect in rectangles { + region.add( + rect.x.round() as i32, + rect.y.round() as i32, + rect.width.round() as i32, + rect.height.round() as i32, + ); + } + let blur_manager = self.ext_background_effect_manager.as_mut().unwrap(); let blur_surface = blur_manager.blur(s, &self.queue_handle); blur_surface.set_blur_region(Some(®ion)); - blur_surface - }).collect::>(); - _ = vacant_entry.insert(blur_surfaces); + _ = vacant_entry.insert(blur_surface); }, } }, From 46781102fe38b1bc26e6486085ea5446230293f3 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 28 May 2026 19:21:54 -0400 Subject: [PATCH 37/51] fix: set blur and corners on creation --- .../wayland/event_loop/mod.rs | 1 + .../wayland/event_loop/state.rs | 180 ++++++++++-------- 2 files changed, 100 insertions(+), 81 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index fea1c28c..b4589840 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -401,6 +401,7 @@ impl SctkEventLoop { preedit: None, pending_delete: None, pending_commit: None, + pending_blur: HashMap::new(), }, _features: Default::default(), }; diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 0fb1a132..d66f99c5 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -39,15 +39,12 @@ use cctk::{ cosmic_protocols::{ corner_radius::v1::client::{ cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, - cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, }, overlap_notify::v1::client::zcosmic_overlap_notification_v1::ZcosmicOverlapNotificationV1, - }, - sctk::{ + }, sctk::{ activation::{ActivationState, RequestData}, compositor::CompositorState, error::GlobalError, - globals::GlobalData, output::OutputState, reexports::{ calloop::{LoopHandle, timer::TimeoutAction}, @@ -87,9 +84,7 @@ use cctk::{ }, }, shm::{Shm, multi::MultiPool}, - }, - toplevel_info::ToplevelInfoState, - toplevel_management::ToplevelManagerState, + }, toplevel_info::ToplevelInfoState, toplevel_management::ToplevelManagerState }; use iced_runtime::{ core::{self, Point, touch}, @@ -104,7 +99,7 @@ use iced_runtime::{ }, }; use wayland_protocols::{ - ext::background_effect::v1::client::ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1, + ext::background_effect::v1::client::{ext_background_effect_manager_v1, ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1}, wp::{ fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1, keyboard_shortcuts_inhibit::zv1::client::{ @@ -486,6 +481,7 @@ pub struct SctkState { pub(crate) corner_radius_manager: Option, pub(crate) pending_corner_radius: HashMap, + pub(crate) pending_blur: HashMap>, pub(crate) text_input_manager: Option, pub(crate) text_input: Option>, @@ -836,6 +832,7 @@ impl SctkState { ) } }; + let toplevel = toplevel.clone(); popup.xdg_surface().set_window_geometry( 0, @@ -868,6 +865,18 @@ impl SctkState { } _ = wl_surface.frame(&self.queue_handle, wl_surface.clone()); + if let Some(corners) = self.pending_corner_radius.remove(&settings.id) { + self.handle_action(Action::RoundedCorners ( + settings.id, + Some(corners), + )); + } + if let Some(blur) = self.pending_blur.remove(&settings.id) { + self.apply_blur(settings.id, Some(blur), &wl_surface); + } + + + wl_surface.commit(); let wp_viewport = self.viewporter_state.as_ref().map(|state| { @@ -996,6 +1005,15 @@ impl SctkState { layer_surface.set_input_region(Some(®ion)); region.destroy(); } + if let Some(blur) = self.pending_blur.remove(&id) { + self.apply_blur(id, Some(blur), &wl_surface); + } + if let Some(corners) = self.pending_corner_radius.remove(&id) { + _ = self.handle_action(Action::RoundedCorners( + id, + Some(corners), + )); + } layer_surface.commit(); let wp_viewport = self.viewporter_state.as_ref().map(|state| { @@ -1099,12 +1117,6 @@ impl SctkState { id: wl_surface.clone(), } ); - if let Some(corners) = self.pending_corner_radius.remove(&id) { - self.handle_action(Action::RoundedCorners( - id, - Some(corners), - ))?; - } } } platform_specific::wayland::layer_surface::Action::Size { @@ -1338,12 +1350,6 @@ impl SctkState { SctkEvent::PopupEvent { variant: crate::platform_specific::wayland::sctk_event::PopupEventVariant::Created(queue_handle.clone(), surface, id, common, state.connection.display()), toplevel_id, parent_id, id: wl_surface }); - if let Some(corners) = state.pending_corner_radius.remove(&id) { - state.handle_action(Action::RoundedCorners ( - id, - Some(corners), - )); - } } Err(err) => { log::error!("Failed to create popup. {err:?}"); @@ -1362,12 +1368,6 @@ impl SctkState { SctkEvent::PopupEvent { variant: crate::platform_specific::wayland::sctk_event::PopupEventVariant::Created(self.queue_handle.clone(), surface, id, common, self.connection.display()), toplevel_id, parent_id, id: wl_surface }); - if let Some(corners) = self.pending_corner_radius.remove(&id) { - self.handle_action(Action::RoundedCorners ( - id, - Some(corners), - )); - } } Err(err) => { log::error!("Failed to create popup. {err:?}"); @@ -1776,66 +1776,74 @@ impl SctkState { } else if let Some(subsurface) = self.subsurfaces.iter().find(|s| s.id == id) { &subsurface.instance.wl_surface } else { - log::error!("Failed to find surface for blur action"); + log::info!("Failed to find surface for blur action"); + if let Some(rectangles) = rectangles { + _ = self.pending_blur.insert(id, rectangles.clone()); + } return Ok(()); - }; - let existing_blur = self.blur_surfaces.entry(id); - match (existing_blur, rectangles) { - (Entry::Occupied(occupied_entry), None) => { - let blur_surface = occupied_entry.remove(); - blur_surface.destroy(); - }, - (Entry::Occupied(mut occupied_entry), Some(rectangles)) => { - let blur_surface = occupied_entry.get_mut(); - let region = self - .compositor_state - .wl_compositor() - .create_region(&self.queue_handle, ()); - for rect in rectangles.into_iter() { - region.add( - rect.x.round() as i32, - rect.y.round() as i32, - rect.width.round() as i32, - rect.height.round() as i32, - ); - } - - // update existing blur surfaces - blur_surface.set_blur_region(Some(®ion)); - - }, - (Entry::Vacant(..), None) => { - // nothing to remove - }, - (Entry::Vacant(vacant_entry), Some(rectangles)) => { - if self.ext_background_effect_manager.is_none() { - log::error!("Blur effect is not supported."); - return Ok(()); - } - let region = self - .compositor_state - .wl_compositor() - .create_region(&self.queue_handle, ()); - for rect in rectangles { - region.add( - rect.x.round() as i32, - rect.y.round() as i32, - rect.width.round() as i32, - rect.height.round() as i32, - ); - } - - let blur_manager = self.ext_background_effect_manager.as_mut().unwrap(); - let blur_surface = blur_manager.blur(s, &self.queue_handle); - blur_surface.set_blur_region(Some(®ion)); - _ = vacant_entry.insert(blur_surface); - }, - } + }.clone(); + self.apply_blur(id, rectangles, &s) }, }; Ok(()) } + fn apply_blur(&mut self, id: core::window::Id, rectangles: Option>, s: &WlSurface) { + let existing_blur = self.blur_surfaces.entry(id); + match (existing_blur, rectangles) { + (Entry::Occupied(occupied_entry), None) => { + let blur_surface = occupied_entry.remove(); + blur_surface.destroy(); + }, + (Entry::Occupied(mut occupied_entry), Some(rectangles)) => { + let blur_surface = occupied_entry.get_mut(); + let region = self + .compositor_state + .wl_compositor() + .create_region(&self.queue_handle, ()); + for rect in rectangles.into_iter() { + region.add( + rect.x.round() as i32, + rect.y.round() as i32, + rect.width.round() as i32, + rect.height.round() as i32, + ); + } + + // update existing blur surfaces + blur_surface.set_blur_region(Some(®ion)); + + }, + (Entry::Vacant(..), None) => { + // nothing to remove + }, + (Entry::Vacant(vacant_entry), Some(rectangles)) => { + if self.ext_background_effect_manager.is_none() { + log::error!("Blur effect is not supported."); + return; + } + let region = self + .compositor_state + .wl_compositor() + .create_region(&self.queue_handle, ()); + for rect in rectangles { + region.add( + rect.x.round() as i32, + rect.y.round() as i32, + rect.width.round() as i32, + rect.height.round() as i32, + ); + } + + let blur_manager = self.ext_background_effect_manager.as_mut().unwrap(); + let blur_surface = blur_manager.blur(s, &self.queue_handle); + blur_surface.set_blur_region(Some(®ion)); + _ = vacant_entry.insert(blur_surface); + }, + } + + } + pub fn get_subsurface( &mut self, settings: SctkSubsurfaceSettings, @@ -1849,7 +1857,7 @@ impl SctkState { ), SubsurfaceCreationError, > { - let Some(subsurface_state) = self.subsurface_state.as_ref() else { + let Some(subsurface_state) = self.subsurface_state.clone() else { return Err(SubsurfaceCreationError::Unsupported); }; if self.layer_surfaces.iter().any(|s| s.id == settings.id) @@ -1965,6 +1973,16 @@ impl SctkState { region.destroy(); } + if let Some(blur) = self.pending_blur.remove(&settings.id) { + _ = self.apply_blur(settings.id, Some(blur), &wl_surface); + } + if let Some(corners) = self.pending_corner_radius.remove(&settings.id) { + self.handle_action(Action::RoundedCorners ( + settings.id, + Some(corners), + )); + } + wl_surface.commit(); let wp_viewport = subsurface_state.wp_viewporter.get_viewport( From 8e94441efad36dca2cef70fa3bc431dd5160871a Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 4 Jun 2026 19:59:01 -0400 Subject: [PATCH 38/51] fix: apply initial corner radius after adding surfaces to the state otherwise the corners won't be set. --- .../wayland/event_loop/state.rs | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index d66f99c5..4fdfd09d 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -865,19 +865,12 @@ impl SctkState { } _ = wl_surface.frame(&self.queue_handle, wl_surface.clone()); - if let Some(corners) = self.pending_corner_radius.remove(&settings.id) { - self.handle_action(Action::RoundedCorners ( - settings.id, - Some(corners), - )); - } + if let Some(blur) = self.pending_blur.remove(&settings.id) { self.apply_blur(settings.id, Some(blur), &wl_surface); } - - wl_surface.commit(); let wp_viewport = self.viewporter_state.as_ref().map(|state| { let viewport = @@ -909,7 +902,14 @@ impl SctkState { common: common.clone(), close_with_children: settings.close_with_children, }); - + + if let Some(corners) = self.pending_corner_radius.remove(&settings.id) { + self.handle_action(Action::RoundedCorners ( + settings.id, + Some(corners), + )); + } + wl_surface.commit(); Ok(( settings.id, parent.wl_surface().clone(), @@ -1008,13 +1008,7 @@ impl SctkState { if let Some(blur) = self.pending_blur.remove(&id) { self.apply_blur(id, Some(blur), &wl_surface); } - if let Some(corners) = self.pending_corner_radius.remove(&id) { - _ = self.handle_action(Action::RoundedCorners( - id, - Some(corners), - )); - } - layer_surface.commit(); + let wp_viewport = self.viewporter_state.as_ref().map(|state| { state.get_viewport(layer_surface.wl_surface(), &self.queue_handle) @@ -1048,6 +1042,13 @@ impl SctkState { wp_fractional_scale, common: common.clone(), }); + if let Some(corners) = self.pending_corner_radius.remove(&id) { + _ = self.handle_action(Action::RoundedCorners( + id, + Some(corners), + )); + } + layer_surface.commit(); Ok((id, CommonSurface::Layer(layer_surface), common)) } pub fn get_lock_surface( @@ -1976,14 +1977,7 @@ impl SctkState { if let Some(blur) = self.pending_blur.remove(&settings.id) { _ = self.apply_blur(settings.id, Some(blur), &wl_surface); } - if let Some(corners) = self.pending_corner_radius.remove(&settings.id) { - self.handle_action(Action::RoundedCorners ( - settings.id, - Some(corners), - )); - } - - wl_surface.commit(); + let wp_viewport = subsurface_state.wp_viewporter.get_viewport( &wl_surface, @@ -2059,7 +2053,14 @@ impl SctkState { // XXX subsurfaces need to be sorted by z in descending order self.subsurfaces .sort_by(|a, b| b.instance.z.cmp(&a.instance.z)); + if let Some(corners) = self.pending_corner_radius.remove(&id) { + self.handle_action(Action::RoundedCorners ( + id, + Some(corners), + )); + } + wl_surface.commit(); Ok(( id, parent.wl_surface().clone(), From da9d15b2e8a0c15934035a898b1c64dab7af4059 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 25 Jun 2026 13:44:29 -0400 Subject: [PATCH 39/51] chore: update cosmic-protocols --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b5e1b8d7..555f7901 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -274,7 +274,7 @@ tiny-skia = { version = "0.11", default-features = false, features = [ "std", "simd", ] } -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "a7d2d7a" } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "32283d7" } softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" } syntect = "5.2" tokio = "1.0" From 633ed144110b5be8bf87c14326ea46a09260bab9 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 25 Jun 2026 15:25:36 -0400 Subject: [PATCH 40/51] feat: popup reposition --- .../src/platform_specific/wayland/popup.rs | 28 +++++++----- .../wayland/commands/popup.rs | 21 +++++++-- .../wayland/event_loop/state.rs | 43 +++++++++++++++++++ 3 files changed, 79 insertions(+), 13 deletions(-) diff --git a/runtime/src/platform_specific/wayland/popup.rs b/runtime/src/platform_specific/wayland/popup.rs index e0475eb8..27279ab6 100644 --- a/runtime/src/platform_specific/wayland/popup.rs +++ b/runtime/src/platform_specific/wayland/popup.rs @@ -124,26 +124,34 @@ pub enum Action { /// height height: u32, }, + /// set the size of the popup + Reposition { + /// id of the popup + id: Id, + /// positioner of the popup + positioner: SctkPositioner, + }, } impl fmt::Debug for Action { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Action::Popup { popup, .. } => write!( - f, - "Action::PopupAction::Popup {{ popup: {:?} }}", - popup - ), - Action::Destroy { id } => write!( - f, - "Action::PopupAction::Destroy {{ id: {:?} }}", - id - ), + Action::Popup { popup, .. } => { + write!(f, "Action::PopupAction::Popup {{ popup: {:?} }}", popup) + } + Action::Destroy { id } => { + write!(f, "Action::PopupAction::Destroy {{ id: {:?} }}", id) + } Action::Size { id, width, height } => write!( f, "Action::PopupAction::Size {{ id: {:?}, width: {:?}, height: {:?} }}", id, width, height ), + Action::Reposition { id, positioner } => write!( + f, + "Action::Reposition {{ id: {:?}, positioner: {:?} }}", + id, positioner + ), } } } diff --git a/winit/src/platform_specific/wayland/commands/popup.rs b/winit/src/platform_specific/wayland/commands/popup.rs index f70d97f4..c702f4f1 100644 --- a/winit/src/platform_specific/wayland/commands/popup.rs +++ b/winit/src/platform_specific/wayland/commands/popup.rs @@ -1,12 +1,15 @@ //! Interact with the popups of your application. use crate::core::window::Id as SurfaceId; use iced_runtime::{ - self, + self, Action, Task, platform_specific::{ self, - wayland::{self, popup::SctkPopupSettings}, + wayland::{ + self, + popup::{SctkPopupSettings, SctkPositioner}, + }, }, - task, Action, Task, + task, }; /// @@ -32,6 +35,18 @@ pub fn set_size( )) } +/// +pub fn reposition( + id: SurfaceId, + positioner: SctkPositioner, +) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Popup( + wayland::popup::Action::Reposition { id, positioner }, + )), + )) +} + /// pub fn destroy_popup(id: SurfaceId) -> Task { task::effect(Action::PlatformSpecific( diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 4fdfd09d..8126e864 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1454,6 +1454,49 @@ impl SctkState { SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Size(width, height), toplevel_id: sctk_popup.data.parent.wl_surface().clone(), parent_id: sctk_popup.data.parent.wl_surface().clone(), id: surface }); } }, + platform_specific::wayland::popup::Action::Reposition { id, positioner } => { + if let Some(sctk_popup) = self + .popups + .iter_mut() + .find(|s| s.data.id == id) + { + sctk_popup.data.positioner.set_anchor(positioner.anchor); + sctk_popup.data.positioner.set_anchor_rect( + positioner.anchor_rect.x, + positioner.anchor_rect.y, + positioner.anchor_rect.width, + positioner.anchor_rect.height, + ); + if let Ok(constraint_adjustment) = + positioner.constraint_adjustment.try_into() + { + sctk_popup.data.positioner.set_constraint_adjustment(constraint_adjustment); + } + sctk_popup.data.positioner.set_gravity(positioner.gravity); + sctk_popup.data.positioner.set_offset( + positioner.offset.0, + positioner.offset.1, + ); + if positioner.reactive { + sctk_popup.data.positioner.set_reactive(); + } + let guard =sctk_popup.common.lock().unwrap(); + let w = guard.size.width; + let h = guard.size.height; + drop(guard); + let size = positioner.size.unwrap_or((w, h)); + sctk_popup.popup + .xdg_surface() + .set_window_geometry(0, 0, w as i32, h as i32); + sctk_popup.update_viewport(w, h); + // update positioner + sctk_popup.data.positioner.set_size(w as i32, h as i32); + sctk_popup.popup.reposition(&sctk_popup.data.positioner, TOKEN_CTR.fetch_add(1, std::sync::atomic::Ordering::Relaxed)); let surface = sctk_popup.popup.wl_surface().clone(); + dbg!("repositioning..."); + _ = send_event(&self.events_sender, &self.proxy, + SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Size(size.0, size.1), toplevel_id: sctk_popup.data.parent.wl_surface().clone(), parent_id: sctk_popup.data.parent.wl_surface().clone(), id: surface }); + } + }, }, Action::Activation(activation_event) => match activation_event { platform_specific::wayland::activation::Action::RequestToken { app_id, window, channel } => { From c781ff6199715f5650411c7ac625d4e1929bfdfb Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 25 Jun 2026 15:27:31 -0400 Subject: [PATCH 41/51] chore: cargo fmt --- examples/counter/src/main.rs | 4 +- examples/sctk_drag/src/dnd_destination.rs | 10 +- examples/sctk_drag/src/dnd_source.rs | 40 +++---- examples/sctk_subsurface/src/main.rs | 2 +- runtime/src/dnd.rs | 2 +- .../platform_specific/wayland/activation.rs | 2 +- wgpu/src/window/compositor.rs | 11 +- widget/src/keyed/column.rs | 27 +++-- widget/src/text/rich.rs | 5 +- winit/src/lib.rs | 108 ++++++++--------- .../wayland/commands/activation.rs | 4 +- .../wayland/commands/corner_radius.rs | 4 +- .../commands/keyboard_shortcuts_inhibit.rs | 4 +- .../wayland/commands/overlap_notify.rs | 3 +- .../wayland/commands/session_lock.rs | 4 +- .../wayland/commands/subsurface.rs | 4 +- .../wayland/event_loop/proxy.rs | 2 +- .../wayland/event_loop/state.rs | 110 +++++++++--------- .../wayland/handlers/seat/touch.rs | 4 +- .../wayland/handlers/session_lock.rs | 3 +- .../wayland/handlers/shell/layer.rs | 4 +- .../wayland/handlers/shell/mod.rs | 2 +- .../wayland/handlers/wp_viewporter.rs | 4 +- winit/src/platform_specific/wayland/mod.rs | 1 + 24 files changed, 192 insertions(+), 172 deletions(-) diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs index 5027afd7..cfae782a 100644 --- a/examples/counter/src/main.rs +++ b/examples/counter/src/main.rs @@ -1,5 +1,5 @@ +use iced::widget::{button, column, text, Column}; use iced::Center; -use iced::widget::{Column, button, column, text}; pub fn main() -> iced::Result { iced::run(Counter::update, Counter::view) @@ -42,7 +42,7 @@ impl Counter { #[cfg(test)] mod tests { use super::*; - use iced_test::{Error, simulator}; + use iced_test::{simulator, Error}; #[test] fn it_counts() -> Result<(), Error> { diff --git a/examples/sctk_drag/src/dnd_destination.rs b/examples/sctk_drag/src/dnd_destination.rs index 1081d8bb..d1b3374b 100644 --- a/examples/sctk_drag/src/dnd_destination.rs +++ b/examples/sctk_drag/src/dnd_destination.rs @@ -3,20 +3,20 @@ use std::{ sync::atomic::{AtomicU64, Ordering}, }; -use iced::{Element, id::Id}; use iced::{ - Event, Length, Rectangle, clipboard::{ dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}, mime::AllowedMimeTypes, }, event, id::Internal, - mouse, overlay, + mouse, overlay, Event, Length, Rectangle, }; +use iced::{id::Id, Element}; use iced_core::{ - self, Clipboard, Layout, Shell, Widget, layout, - widget::{Tree, tree}, + self, layout, + widget::{tree, Tree}, + Clipboard, Layout, Shell, Widget, }; pub fn dnd_destination<'a, Message: 'static>( diff --git a/examples/sctk_drag/src/dnd_source.rs b/examples/sctk_drag/src/dnd_source.rs index 1d8b85c4..e93b925e 100644 --- a/examples/sctk_drag/src/dnd_source.rs +++ b/examples/sctk_drag/src/dnd_source.rs @@ -1,16 +1,16 @@ use std::any::Any; -use iced::Element; use iced::id::Id; use iced::widget::container; +use iced::Element; use iced::{ - Event, Length, Point, Rectangle, clipboard::dnd::{DndAction, DndEvent, SourceEvent}, - event, mouse, overlay, + event, mouse, overlay, Event, Length, Point, Rectangle, }; use iced_core::{ - Clipboard, Shell, layout, renderer, - widget::{Tree, tree}, + layout, renderer, + widget::{tree, Tree}, + Clipboard, Shell, }; use iced_core::{Layout, Widget}; @@ -37,11 +37,11 @@ pub struct DndSource<'a, Message, AppMessage, D> { } impl< - 'a, - Message: 'static, - AppMessage: 'static, - D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, -> DndSource<'a, Message, AppMessage, D> + 'a, + Message: 'static, + AppMessage: 'static, + D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, + > DndSource<'a, Message, AppMessage, D> { pub fn new(child: impl Into>) -> Self { Self { @@ -119,11 +119,11 @@ impl< } impl< - 'a, - Message: 'static, - AppMessage: 'static, - D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, -> Widget + 'a, + Message: 'static, + AppMessage: 'static, + D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, + > Widget for DndSource<'a, Message, AppMessage, D> { fn children(&self) -> Vec { @@ -357,11 +357,11 @@ impl< } impl< - 'a, - Message: 'static, - AppMessage: 'static, - D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, -> From> for Element<'a, Message> + 'a, + Message: 'static, + AppMessage: 'static, + D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, + > From> for Element<'a, Message> { fn from(e: DndSource<'a, Message, AppMessage, D>) -> Element<'a, Message> { Element::new(e) diff --git a/examples/sctk_subsurface/src/main.rs b/examples/sctk_subsurface/src/main.rs index c00f7225..a204572c 100644 --- a/examples/sctk_subsurface/src/main.rs +++ b/examples/sctk_subsurface/src/main.rs @@ -7,7 +7,6 @@ use cctk::sctk::reexports::{ use iced::platform_specific::shell::commands::subsurface::get_subsurface; use iced::{ - Element, Length, Subscription, Task, event::wayland::Event as WaylandEvent, platform_specific::{ runtime::wayland::subsurface::SctkSubsurfaceSettings, @@ -15,6 +14,7 @@ use iced::{ }, widget::{button, column, text, text_input}, window::{self, Id, Settings}, + Element, Length, Subscription, Task, }; use std::sync::{Arc, Mutex}; diff --git a/runtime/src/dnd.rs b/runtime/src/dnd.rs index e6f2b267..2a0db116 100644 --- a/runtime/src/dnd.rs +++ b/runtime/src/dnd.rs @@ -6,7 +6,7 @@ use dnd::{DndDestinationRectangle, DndSurface}; use iced_core::clipboard::DndSource; use window_clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; -use crate::{oneshot, task, Action, Task}; +use crate::{Action, Task, oneshot, task}; /// An action to be performed on the system. pub enum DndAction { diff --git a/runtime/src/platform_specific/wayland/activation.rs b/runtime/src/platform_specific/wayland/activation.rs index 64d4dc4d..3f48f01c 100644 --- a/runtime/src/platform_specific/wayland/activation.rs +++ b/runtime/src/platform_specific/wayland/activation.rs @@ -36,7 +36,7 @@ impl fmt::Debug for Action { f, "Action::ActivationAction::Activate {{ window: {:?}, token: {:?} }}", window, token, - ) + ), } } } diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index f0595f2b..58c7d713 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -296,11 +296,12 @@ impl Compositor { }); // Request SHADER_F16 only if the adapter supports it (e.g., not available in WebGL2) - let required_features = if adapter.features().contains(wgpu::Features::SHADER_F16) { - wgpu::Features::SHADER_F16 - } else { - wgpu::Features::empty() - }; + let required_features = + if adapter.features().contains(wgpu::Features::SHADER_F16) { + wgpu::Features::SHADER_F16 + } else { + wgpu::Features::empty() + }; let mut errors = Vec::new(); diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index baaee5c9..ef7d9d9a 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -293,9 +293,12 @@ where .zip(&mut tree.children) .zip(layout.children()) .for_each(|((child, state), c_layout)| { - child - .as_widget_mut() - .operate(state, c_layout.with_virtual_offset(layout.virtual_offset()), renderer, operation); + child.as_widget_mut().operate( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); }); }); } @@ -318,7 +321,13 @@ where .zip(layout.children()) { child.as_widget_mut().update( - tree, event, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, shell, + tree, + event, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, viewport, ); } @@ -337,9 +346,13 @@ where .zip(&tree.children) .zip(layout.children()) .map(|((child, tree), c_layout)| { - child - .as_widget() - .mouse_interaction(tree, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer) + child.as_widget().mouse_interaction( + tree, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + renderer, + ) }) .max() .unwrap_or_default() diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index 80abc8d5..ef44e2bf 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -179,7 +179,10 @@ where { let color = color.map(Into::into); - self.style(move |_theme| Style { color, ..Style::default() }) + self.style(move |_theme| Style { + color, + ..Style::default() + }) } /// Sets the default style class of the [`Rich`] text. diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 410949ac..517eddbc 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -40,12 +40,6 @@ pub mod clipboard; pub mod conversion; pub mod platform_specific; -#[cfg(feature = "program")] -pub mod program; - -#[cfg(feature = "system")] -pub mod system; - mod error; mod proxy; mod window; @@ -193,10 +187,13 @@ where error: Option, system_theme: Option>, control_sender: mpsc::UnboundedSender, - + #[cfg(feature = "a11y")] - adapters: std::collections::HashMap, - + adapters: std::collections::HashMap< + window::Id, + (u64, iced_accessibility::accesskit_winit::Adapter), + >, + #[cfg(target_arch = "wasm32")] is_booted: std::rc::Rc>, #[cfg(target_arch = "wasm32")] @@ -501,9 +498,15 @@ where } }; } - let window: Arc = Arc::from(window); + let window: Arc< + dyn winit::window::Window + 'static, + > = Arc::from(window); #[cfg(feature = "a11y")] - self.init_adapter(event_loop, id, window.clone()); + self.init_adapter( + event_loop, + id, + window.clone(), + ); self.process_event( event_loop, @@ -572,11 +575,13 @@ where .expect("Send event"); } Control::Winit(id, e) => { - #[cfg(all(feature = "cctk", target_os = "linux"))] + #[cfg(all( + feature = "cctk", + target_os = "linux" + ))] { if matches!(e, WindowEvent::RedrawRequested) - { - + { for id in crate::subsurface_widget::subsurface_ids(id) { _ = self.sender .unbounded_send(Event::Winit( @@ -597,7 +602,7 @@ where } } } - + self.sender .start_send(Event::Winit(id, e)) .expect("Send event"); @@ -610,7 +615,7 @@ where #[cfg(feature = "a11y")] Control::InitAdapter(id, window) => { self.init_adapter(event_loop, id, window); - + self.process_event( event_loop, Some(Event::A11yAdapter(id)), @@ -618,8 +623,8 @@ where } #[cfg(feature = "a11y")] Control::Cleanup(id) => { - _ = self.adapters.remove(&id); - }, + _ = self.adapters.remove(&id); + } }, _ => { break; @@ -633,34 +638,35 @@ where } } - #[cfg(feature = "a11y")] - fn init_adapter(&mut self, event_loop: &(dyn winit::event_loop::ActiveEventLoop + 'static), id: core::window::Id, window: Arc) { + #[cfg(feature = "a11y")] + fn init_adapter( + &mut self, + event_loop: &(dyn winit::event_loop::ActiveEventLoop + 'static), + id: core::window::Id, + window: Arc, + ) { use crate::a11y::*; use iced_accessibility::accesskit::{ - ActivationHandler, Node, NodeId, Role, - Tree, TreeUpdate, + ActivationHandler, Node, NodeId, Role, Tree, TreeUpdate, }; use iced_accessibility::accesskit_winit::Adapter; - - let node_id = - iced_runtime::core::id::window_node_id(); - - let activation_handler = - WinitActivationHandler { - proxy: self.control_sender.clone(), - title: String::new(), - }; - + + let node_id = iced_runtime::core::id::window_node_id(); + + let activation_handler = WinitActivationHandler { + proxy: self.control_sender.clone(), + title: String::new(), + }; + let action_handler = WinitActionHandler { id, proxy: self.control_sender.clone(), }; - - let deactivation_handler = - WinitDeactivationHandler { - proxy: self.control_sender.clone(), - }; - + + let deactivation_handler = WinitDeactivationHandler { + proxy: self.control_sender.clone(), + }; + _ = self.adapters.insert( id, ( @@ -674,8 +680,6 @@ where ), ), ); - - } } @@ -879,9 +883,8 @@ async fn run_instance

( exit_on_close_request, make_visible, on_open, - resize_border + resize_border, } => { - #[cfg(all(feature = "cctk", target_os = "linux"))] platform_specific_handler.send_wayland( platform_specific::Action::TrackWindow(window.clone(), id), @@ -937,7 +940,7 @@ async fn run_instance

( compositor, exit_on_close_request, system_theme, - resize_border + resize_border, ); window.raw.set_theme(conversion::window_theme( @@ -1034,7 +1037,7 @@ async fn run_instance

( continue; }; - let Some((id, mut window)) = + let Some((id, mut window)) = window_manager.get_mut_alias(window_id) else { continue; @@ -1049,12 +1052,12 @@ async fn run_instance

( )) .expect("Send redraw event"); continue; - } + } // XX must force update to corner radius before the surface is committed. #[cfg(all(feature = "cctk", target_os = "linux"))] if (window.surface_version != window.state.surface_version() - || window.logical_size() != window.state.logical_size() - ) && !crate::subsurface_widget::is_subsurface(window_id) + || window.logical_size() != window.state.logical_size()) + && !crate::subsurface_widget::is_subsurface(window_id) { platform_specific_handler.send_wayland( platform_specific::Action::ResizeWindow(id), @@ -1320,9 +1323,7 @@ async fn run_instance

( continue; }; // Initiates a drag resize window state when found. - if let Some(func) = - window.drag_resize_window_func.as_mut() - { + if let Some(func) = window.drag_resize_window_func.as_mut() { if func(window.raw.as_ref(), &event) { continue; } @@ -1348,7 +1349,9 @@ async fn run_instance

( if matches!(event, winit::event::WindowEvent::CloseRequested) && window.exit_on_close_request { - _ = run_action!(Action::Window(runtime::window::Action::Close(id))); + _ = run_action!(Action::Window( + runtime::window::Action::Close(id) + )); } else { window.state.update(&program, window.raw.as_ref(), &event); @@ -1736,7 +1739,6 @@ async fn run_instance

( }; // search windows for widget with operation - if result.is_none() { log::warn!( "start_dnd: widget {:?} not found; drag will fail", @@ -2113,7 +2115,7 @@ where on_open: channel, }) .expect("Send control action"); - + *is_window_opening = true; } window::Action::Close(id) => { diff --git a/winit/src/platform_specific/wayland/commands/activation.rs b/winit/src/platform_specific/wayland/commands/activation.rs index fa051031..442ee08c 100644 --- a/winit/src/platform_specific/wayland/commands/activation.rs +++ b/winit/src/platform_specific/wayland/commands/activation.rs @@ -1,8 +1,8 @@ use crate::core::window::Id as SurfaceId; use iced_runtime::{ - self, + self, Action, Task, platform_specific::{self, wayland}, - task, Action, Task, + task, }; pub fn request_token( diff --git a/winit/src/platform_specific/wayland/commands/corner_radius.rs b/winit/src/platform_specific/wayland/commands/corner_radius.rs index c5ba6016..4cc122dd 100644 --- a/winit/src/platform_specific/wayland/commands/corner_radius.rs +++ b/winit/src/platform_specific/wayland/commands/corner_radius.rs @@ -1,11 +1,11 @@ use iced_futures::core::window; use iced_runtime::{ - self, + self, Action, Task, platform_specific::{ self, wayland::{self, CornerRadius}, }, - task, Action, Task, + task, }; pub fn corner_radius( diff --git a/winit/src/platform_specific/wayland/commands/keyboard_shortcuts_inhibit.rs b/winit/src/platform_specific/wayland/commands/keyboard_shortcuts_inhibit.rs index 783b62c6..afc2e27b 100644 --- a/winit/src/platform_specific/wayland/commands/keyboard_shortcuts_inhibit.rs +++ b/winit/src/platform_specific/wayland/commands/keyboard_shortcuts_inhibit.rs @@ -1,7 +1,7 @@ use iced_runtime::{ - self, + self, Action, Task, platform_specific::{self, wayland}, - task, Action, Task, + task, }; pub fn inhibit_shortcuts(inhibit: bool) -> Task<()> { diff --git a/winit/src/platform_specific/wayland/commands/overlap_notify.rs b/winit/src/platform_specific/wayland/commands/overlap_notify.rs index 860838c3..417cfb24 100644 --- a/winit/src/platform_specific/wayland/commands/overlap_notify.rs +++ b/winit/src/platform_specific/wayland/commands/overlap_notify.rs @@ -1,7 +1,8 @@ use iced_futures::core::window::Id; use iced_runtime::{ + Action, Task, platform_specific::{self, wayland}, - task, Action, Task, + task, }; /// Request subscription for overlap notification events on the surface diff --git a/winit/src/platform_specific/wayland/commands/session_lock.rs b/winit/src/platform_specific/wayland/commands/session_lock.rs index 7f8748f7..a530c12e 100644 --- a/winit/src/platform_specific/wayland/commands/session_lock.rs +++ b/winit/src/platform_specific/wayland/commands/session_lock.rs @@ -1,9 +1,9 @@ use crate::core::window::Id as SurfaceId; use cctk::sctk::reexports::client::protocol::wl_output::WlOutput; use iced_runtime::{ - self, + self, Action, Task, platform_specific::{self, wayland}, - task, Action, Task, + task, }; pub fn lock() -> Task { diff --git a/winit/src/platform_specific/wayland/commands/subsurface.rs b/winit/src/platform_specific/wayland/commands/subsurface.rs index a76e3b4e..20c3cdb7 100644 --- a/winit/src/platform_specific/wayland/commands/subsurface.rs +++ b/winit/src/platform_specific/wayland/commands/subsurface.rs @@ -1,12 +1,12 @@ use crate::core::window::Id as SurfaceId; pub use cctk::sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}; use iced_runtime::{ - self, + self, Action, Task, platform_specific::{ self, wayland::{self, subsurface::SctkSubsurfaceSettings}, }, - task, Action, Task, + task, }; pub fn get_subsurface( diff --git a/winit/src/platform_specific/wayland/event_loop/proxy.rs b/winit/src/platform_specific/wayland/event_loop/proxy.rs index bdbc44a4..58229dc1 100644 --- a/winit/src/platform_specific/wayland/event_loop/proxy.rs +++ b/winit/src/platform_specific/wayland/event_loop/proxy.rs @@ -1,8 +1,8 @@ use cctk::sctk::reexports::calloop; use iced_futures::futures::{ + Sink, channel::mpsc, task::{Context, Poll}, - Sink, }; use std::pin::Pin; diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 8126e864..b817e4d5 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1,7 +1,11 @@ use crate::{ Control, handlers::{ - activation::IcedRequestData, ext_background_effect, overlap::{OverlapNotificationV1, OverlapNotifyV1}, shell::corner_radius::CornerRadiusWrapper, text_input::{Preedit, TextInputManager} + activation::IcedRequestData, + ext_background_effect, + overlap::{OverlapNotificationV1, OverlapNotifyV1}, + shell::corner_radius::CornerRadiusWrapper, + text_input::{Preedit, TextInputManager}, }, platform_specific::{ Event, @@ -37,11 +41,10 @@ use winit::{ use cctk::{ cosmic_protocols::{ - corner_radius::v1::client::{ - cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, - }, + corner_radius::v1::client::cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, overlap_notify::v1::client::zcosmic_overlap_notification_v1::ZcosmicOverlapNotificationV1, - }, sctk::{ + }, + sctk::{ activation::{ActivationState, RequestData}, compositor::CompositorState, error::GlobalError, @@ -59,7 +62,8 @@ use cctk::{ wl_surface::{self, WlSurface}, wl_touch::WlTouch, }, - }, protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, + }, + protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, }, registry::RegistryState, seat::{ @@ -84,7 +88,9 @@ use cctk::{ }, }, shm::{Shm, multi::MultiPool}, - }, toplevel_info::ToplevelInfoState, toplevel_management::ToplevelManagerState + }, + toplevel_info::ToplevelInfoState, + toplevel_management::ToplevelManagerState, }; use iced_runtime::{ core::{self, Point, touch}, @@ -99,7 +105,10 @@ use iced_runtime::{ }, }; use wayland_protocols::{ - ext::background_effect::v1::client::{ext_background_effect_manager_v1, ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1}, + ext::background_effect::v1::client::{ + ext_background_effect_manager_v1, + ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1, + }, wp::{ fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1, keyboard_shortcuts_inhibit::zv1::client::{ @@ -333,7 +342,6 @@ pub struct SctkPopupData { pub(crate) grab: bool, } - #[derive(Debug, Clone)] pub struct SctkCornerRadius(Arc); @@ -865,13 +873,11 @@ impl SctkState { } _ = wl_surface.frame(&self.queue_handle, wl_surface.clone()); - + if let Some(blur) = self.pending_blur.remove(&settings.id) { self.apply_blur(settings.id, Some(blur), &wl_surface); } - - let wp_viewport = self.viewporter_state.as_ref().map(|state| { let viewport = state.get_viewport(popup.wl_surface(), &self.queue_handle); @@ -902,9 +908,9 @@ impl SctkState { common: common.clone(), close_with_children: settings.close_with_children, }); - + if let Some(corners) = self.pending_corner_radius.remove(&settings.id) { - self.handle_action(Action::RoundedCorners ( + self.handle_action(Action::RoundedCorners( settings.id, Some(corners), )); @@ -1008,7 +1014,6 @@ impl SctkState { if let Some(blur) = self.pending_blur.remove(&id) { self.apply_blur(id, Some(blur), &wl_surface); } - let wp_viewport = self.viewporter_state.as_ref().map(|state| { state.get_viewport(layer_surface.wl_surface(), &self.queue_handle) @@ -1043,10 +1048,7 @@ impl SctkState { common: common.clone(), }); if let Some(corners) = self.pending_corner_radius.remove(&id) { - _ = self.handle_action(Action::RoundedCorners( - id, - Some(corners), - )); + _ = self.handle_action(Action::RoundedCorners(id, Some(corners))); } layer_surface.commit(); Ok((id, CommonSurface::Layer(layer_surface), common)) @@ -1139,7 +1141,7 @@ impl SctkState { platform_specific::wayland::layer_surface::Action::Destroy(id) => { if let Some(p) = self .popups - .iter().find_map(|p| + .iter().find_map(|p| self.layer_surfaces.iter().find_map(|l| if l.id == id && l.surface.wl_surface() == p.data.parent.wl_surface() {Some(p.data.id)} else {None})) { _ = self.handle_action(Action::Popup(platform_specific::wayland::popup::Action::Destroy { id: p })); } @@ -1173,7 +1175,6 @@ impl SctkState { _ = self.destroyed.insert(destroyed); } - send_event(&self.events_sender, &self.proxy, SctkEvent::LayerSurfaceEvent { variant: LayerSurfaceEventVariant::Done, id: l.surface.wl_surface().clone(), @@ -1492,7 +1493,6 @@ impl SctkState { // update positioner sctk_popup.data.positioner.set_size(w as i32, h as i32); sctk_popup.popup.reposition(&sctk_popup.data.positioner, TOKEN_CTR.fetch_add(1, std::sync::atomic::Ordering::Relaxed)); let surface = sctk_popup.popup.wl_surface().clone(); - dbg!("repositioning..."); _ = send_event(&self.events_sender, &self.proxy, SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Size(size.0, size.1), toplevel_id: sctk_popup.data.parent.wl_surface().clone(), parent_id: sctk_popup.data.parent.wl_surface().clone(), id: surface }); } @@ -1832,19 +1832,24 @@ impl SctkState { Ok(()) } - fn apply_blur(&mut self, id: core::window::Id, rectangles: Option>, s: &WlSurface) { + fn apply_blur( + &mut self, + id: core::window::Id, + rectangles: Option>, + s: &WlSurface, + ) { let existing_blur = self.blur_surfaces.entry(id); match (existing_blur, rectangles) { (Entry::Occupied(occupied_entry), None) => { let blur_surface = occupied_entry.remove(); blur_surface.destroy(); - }, + } (Entry::Occupied(mut occupied_entry), Some(rectangles)) => { let blur_surface = occupied_entry.get_mut(); let region = self - .compositor_state - .wl_compositor() - .create_region(&self.queue_handle, ()); + .compositor_state + .wl_compositor() + .create_region(&self.queue_handle, ()); for rect in rectangles.into_iter() { region.add( rect.x.round() as i32, @@ -1853,41 +1858,40 @@ impl SctkState { rect.height.round() as i32, ); } - + // update existing blur surfaces blur_surface.set_blur_region(Some(®ion)); - - }, + } (Entry::Vacant(..), None) => { // nothing to remove - }, + } (Entry::Vacant(vacant_entry), Some(rectangles)) => { if self.ext_background_effect_manager.is_none() { log::error!("Blur effect is not supported."); return; } - let region = self - .compositor_state - .wl_compositor() - .create_region(&self.queue_handle, ()); - for rect in rectangles { - region.add( - rect.x.round() as i32, - rect.y.round() as i32, - rect.width.round() as i32, - rect.height.round() as i32, - ); - } - - let blur_manager = self.ext_background_effect_manager.as_mut().unwrap(); - let blur_surface = blur_manager.blur(s, &self.queue_handle); - blur_surface.set_blur_region(Some(®ion)); + let region = self + .compositor_state + .wl_compositor() + .create_region(&self.queue_handle, ()); + for rect in rectangles { + region.add( + rect.x.round() as i32, + rect.y.round() as i32, + rect.width.round() as i32, + rect.height.round() as i32, + ); + } + + let blur_manager = + self.ext_background_effect_manager.as_mut().unwrap(); + let blur_surface = blur_manager.blur(s, &self.queue_handle); + blur_surface.set_blur_region(Some(®ion)); _ = vacant_entry.insert(blur_surface); - }, + } } - } - + pub fn get_subsurface( &mut self, settings: SctkSubsurfaceSettings, @@ -2020,7 +2024,6 @@ impl SctkState { if let Some(blur) = self.pending_blur.remove(&settings.id) { _ = self.apply_blur(settings.id, Some(blur), &wl_surface); } - let wp_viewport = subsurface_state.wp_viewporter.get_viewport( &wl_surface, @@ -2097,10 +2100,7 @@ impl SctkState { self.subsurfaces .sort_by(|a, b| b.instance.z.cmp(&a.instance.z)); if let Some(corners) = self.pending_corner_radius.remove(&id) { - self.handle_action(Action::RoundedCorners ( - id, - Some(corners), - )); + self.handle_action(Action::RoundedCorners(id, Some(corners))); } wl_surface.commit(); diff --git a/winit/src/platform_specific/wayland/handlers/seat/touch.rs b/winit/src/platform_specific/wayland/handlers/seat/touch.rs index f726fd93..a0b1cce0 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/touch.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/touch.rs @@ -9,12 +9,12 @@ use crate::{ use cctk::sctk::{ delegate_touch, reexports::client::{ - protocol::{wl_surface::WlSurface, wl_touch::WlTouch}, Connection, Proxy, QueueHandle, + protocol::{wl_surface::WlSurface, wl_touch::WlTouch}, }, seat::touch::TouchHandler, }; -use iced_runtime::core::{touch, Point}; +use iced_runtime::core::{Point, touch}; impl TouchHandler for SctkState { fn down( diff --git a/winit/src/platform_specific/wayland/handlers/session_lock.rs b/winit/src/platform_specific/wayland/handlers/session_lock.rs index 6c617a20..98eb5586 100644 --- a/winit/src/platform_specific/wayland/handlers/session_lock.rs +++ b/winit/src/platform_specific/wayland/handlers/session_lock.rs @@ -1,8 +1,7 @@ use crate::{ event_loop::state::CommonSurface, platform_specific::wayland::{ - event_loop::state::receive_frame, - handlers::SctkState, + event_loop::state::receive_frame, handlers::SctkState, sctk_event::SctkEvent, }, }; diff --git a/winit/src/platform_specific/wayland/handlers/shell/layer.rs b/winit/src/platform_specific/wayland/handlers/shell/layer.rs index 65e5724b..22fd6560 100644 --- a/winit/src/platform_specific/wayland/handlers/shell/layer.rs +++ b/winit/src/platform_specific/wayland/handlers/shell/layer.rs @@ -1,13 +1,13 @@ use crate::platform_specific::wayland::{ - event_loop::state::{receive_frame, SctkState}, + event_loop::state::{SctkState, receive_frame}, sctk_event::{LayerSurfaceEventVariant, SctkEvent}, }; use cctk::sctk::{ delegate_layer, reexports::client::Proxy, shell::{ - wlr_layer::{Anchor, KeyboardInteractivity, LayerShellHandler}, WaylandSurface, + wlr_layer::{Anchor, KeyboardInteractivity, LayerShellHandler}, }, }; use std::fmt::Debug; diff --git a/winit/src/platform_specific/wayland/handlers/shell/mod.rs b/winit/src/platform_specific/wayland/handlers/shell/mod.rs index af8b5961..c4117eb7 100644 --- a/winit/src/platform_specific/wayland/handlers/shell/mod.rs +++ b/winit/src/platform_specific/wayland/handlers/shell/mod.rs @@ -1,4 +1,4 @@ +pub mod corner_radius; pub mod layer; pub mod xdg_popup; pub mod xdg_window; -pub mod corner_radius; diff --git a/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs b/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs index 5cfe28d7..7bbd66d1 100644 --- a/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs +++ b/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs @@ -1,10 +1,10 @@ //! Handling of the wp-viewporter. +use cctk::sctk::reexports::client::Dispatch; use cctk::sctk::reexports::client::globals::{BindError, GlobalList}; use cctk::sctk::reexports::client::protocol::wl_surface::WlSurface; -use cctk::sctk::reexports::client::Dispatch; use cctk::sctk::reexports::client::{ - delegate_dispatch, Connection, Proxy, QueueHandle, + Connection, Proxy, QueueHandle, delegate_dispatch, }; use cctk::sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport; use cctk::sctk::reexports::protocols::wp::viewporter::client::wp_viewporter::WpViewporter; diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index bbbce8f1..e98316ce 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -21,6 +21,7 @@ use iced_runtime::core::{Vector, window}; use raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle}; use raw_window_handle::{HasRawDisplayHandle, RawWindowHandle}; use sctk_event::SctkEvent; +use std::sync::OnceLock; use std::{collections::HashMap, sync::Arc}; use subsurface_widget::{SubsurfaceInstance, SubsurfaceState}; use wayland_backend::client::ObjectId; From a50a29b3905168b6c93cc48ae9bfa2205c59149a Mon Sep 17 00:00:00 2001 From: leyoda Date: Fri, 24 Apr 2026 06:00:03 +0200 Subject: [PATCH 42/51] yoda: drop x11 default on winit workspace dep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Was pulling winit's full x11 backend (winit-x11 + x11-dl) unconditionally despite iced_winit's own x11 feature being disabled. Fixed by adding default-features=false on the workspace-level winit dep — iced_winit/wayland + wayland-dlopen propagate to winit/wayland + wayland-dlopen as needed. --- Cargo.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 555f7901..bf7bcfdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -304,7 +304,11 @@ winapi = "0.3" window_clipboard = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" } dnd = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" } mime = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" } -winit = { git = "https://github.com/pop-os/winit.git", tag = "cosmic-0.14" } +# Yoda: default-features = false drops winit's x11 default, which otherwise +# pulls winit-x11, x11-dl, tiny-xlib, as-raw-xcb-connection. Our Wayland-only +# fork doesn't need any of that — the wayland + wayland-dlopen features are +# re-enabled via iced_winit's own feature propagation from libcosmic-yoda. +winit = { git = "https://github.com/pop-os/winit.git", tag = "cosmic-0.14", default-features = false } winit-core = { git = "https://github.com/pop-os/winit.git", tag = "cosmic-0.14" } cursor-icon = "1.1.0" From 702b6359a54a4eb4ff5cd31a1edc818a31233f19 Mon Sep 17 00:00:00 2001 From: leyoda Date: Fri, 24 Apr 2026 06:24:15 +0200 Subject: [PATCH 43/51] yoda: gate iced_wgpu x11 backend behind an opt-in feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iced_wgpu's unix block unconditionally depended on as-raw-xcb-connection, tiny-xlib and x11rb for a DRI3 device-id fallback in window/compositor.rs that only fires when wayland can't determine the GPU. On a Wayland-only build this code path never runs, so the deps are pure bloat. Changes: - Cargo.toml: move the 3 x11 crates to optional + new feature `x11` (not in default — wayland-only is the yoda target; users who need X11 opt in). - window.rs: cfg-gate `mod x11;` on feature = "x11" - window/compositor.rs: cfg-gate `use super::x11::get_x11_device_ids` + the or_else fallback on feature = "x11" (wrapped in a let-binding so the non-x11 branch compiles cleanly). Result: iced_wgpu no longer pulls any x11 crate when built without the feature. --- wgpu/Cargo.toml | 11 ++++++++--- wgpu/src/window.rs | 2 +- wgpu/src/window/compositor.rs | 8 +++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 48fca1b8..ffbcd293 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -25,6 +25,11 @@ svg = ["iced_graphics/svg", "resvg/text"] web-colors = ["iced_graphics/web-colors"] webgl = ["wgpu/webgl"] strict-assertions = [] +# Yoda: opt-in X11 backend. Pulls tiny-xlib/as-raw-xcb-connection/x11rb +# (which otherwise were unconditional direct deps on unix). Wayland-only +# builds just leave this off — window/x11.rs and the DRI3 device-id +# fallback in window/compositor.rs are cfg-gated on it. +x11 = ["dep:as-raw-xcb-connection", "dep:tiny-xlib", "dep:x11rb"] [dependencies] iced_debug.workspace = true @@ -57,9 +62,9 @@ wayland-protocols.workspace = true wayland-backend = { version = "0.3.3", features = ["client_system"] } wayland-client = { version = "0.31.2" } wayland-sys = { version = "0.31.1", features = ["dlopen"] } -as-raw-xcb-connection = "1.0.1" -tiny-xlib = "0.2.3" -x11rb = { version = "0.13.1", features = [ +as-raw-xcb-connection = { version = "1.0.1", optional = true } +tiny-xlib = { version = "0.2.3", optional = true } +x11rb = { version = "0.13.1", optional = true, features = [ "allow-unsafe-code", "dl-libxcb", "dri3", diff --git a/wgpu/src/window.rs b/wgpu/src/window.rs index b9ec54bd..f71b7d3f 100644 --- a/wgpu/src/window.rs +++ b/wgpu/src/window.rs @@ -7,7 +7,7 @@ pub mod compositor; not(target_os = "redox") ))] mod wayland; -#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] +#[cfg(all(unix, feature = "x11", not(target_os = "macos"), not(target_os = "redox")))] mod x11; pub use compositor::Compositor; diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 58c7d713..d6186ab5 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -16,7 +16,7 @@ use crate::{Engine, Renderer}; not(target_os = "redox") ))] use super::wayland::get_wayland_device_ids; -#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] +#[cfg(all(unix, feature = "x11", not(target_os = "macos"), not(target_os = "redox")))] use super::x11::get_x11_device_ids; use std::future::Future; @@ -72,8 +72,10 @@ impl Compositor { not(target_os = "redox") ))] let ids = compatible_window.as_ref().and_then(|window| { - get_wayland_device_ids(window) - .or_else(|| get_x11_device_ids(window)) + let w = get_wayland_device_ids(window); + #[cfg(feature = "x11")] + let w = w.or_else(|| get_x11_device_ids(window)); + w }); #[cfg(all( From f1f42f2bff0e1198c2f0b07d238f6b552df33717 Mon Sep 17 00:00:00 2001 From: leyoda Date: Fri, 24 Apr 2026 06:53:36 +0200 Subject: [PATCH 44/51] yoda: softbuffer + window_clipboard default-features=false Two one-line workspace-dep fixes that drop the last remaining X11 crates for Wayland-only builds: - softbuffer: its default = [kms, x11, x11-dlopen, wayland, wayland-dlopen] pulled tiny-xlib + as-raw-xcb-connection unconditionally even though iced_tiny_skia only needed softbuffer/wayland. Disabling the default lets iced_tiny_skia's own feature propagation ask for just wayland + wayland-dlopen. - window_clipboard: upstream pulled clipboard_x11 + clipboard_wayland unconditionally on unix. Our local fork (branch yoda-x11-optional) gates clipboard_x11 behind an opt-in feature, so default-features=false + features=["wayland"] drops ~500 x11 symbols (clipboard_x11, x11rb, x11rb-protocol). Combined with previous yoda-v4 cuts, cosmic-yoterm goes from 1526 -> 43 x11 symbols (-97%), and 55.4 MB -> 53.3 MB (-2.1 MB). --- Cargo.toml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bf7bcfdf..f3d7b8f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -275,7 +275,10 @@ tiny-skia = { version = "0.11", default-features = false, features = [ "simd", ] } cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "32283d7" } -softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" } +# Yoda: default-features=false drops softbuffer's x11 / x11-dlopen (pulled +# tiny-xlib + as-raw-xcb-connection). The wayland + wayland-dlopen features +# are re-activated via iced_tiny_skia's own `wayland` feature propagation. +softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0", default-features = false } syntect = "5.2" tokio = "1.0" tracing = "0.1" @@ -301,7 +304,11 @@ wayland-client = { version = "0.31.5" } winapi = "0.3" # window_clipboard = "0.4.1" -window_clipboard = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" } +# Yoda: window_clipboard via git path = our local fork that has x11 gated +# behind an opt-in feature. default-features=false + features=["wayland"] +# drops clipboard_x11 + x11rb from the build. +# Kept at /home/lionel/Devels/window_clipboard branch yoda-x11-optional. +window_clipboard = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20", default-features = false, features = ["wayland"] } dnd = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" } mime = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" } # Yoda: default-features = false drops winit's x11 default, which otherwise From ce5ea665e2d45042456232ee0cc989eef9a7c5e4 Mon Sep 17 00:00:00 2001 From: Votre Nom Date: Tue, 5 May 2026 08:27:03 +0200 Subject: [PATCH 45/51] Fix high-signal widget and Wayland warnings Avoid recursive operation traversal, execute visible list updates instead of leaving a lazy iterator unused, remove unreachable cfg-gated surface creation code, and handle subsurface buffer attachment failures. --- widget/src/container.rs | 2 +- winit/src/platform_specific/mod.rs | 7 +++++-- winit/src/platform_specific/wayland/subsurface_widget.rs | 5 ++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/widget/src/container.rs b/widget/src/container.rs index c9a300c9..e4aeabf2 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -601,7 +601,7 @@ pub fn visible_bounds(id: Id) -> Task> { operate: &mut dyn FnMut(&mut dyn Operation>), ) { self.depth += 1; - self.traverse(operate); + operate(self); self.depth -= 1; } diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index 9b74ee9c..5e84d16a 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -153,9 +153,12 @@ impl PlatformSpecific { ) -> Option> { #[cfg(all(feature = "cctk", target_os = "linux"))] { - return self.wayland.create_surface(); + self.wayland.create_surface() + } + #[cfg(not(all(feature = "cctk", target_os = "linux")))] + { + None } - None } pub(crate) fn update_surface_shm( diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index dbecc52d..c3173fa9 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -409,7 +409,10 @@ impl SubsurfaceState { .unwrap(); canvas[0..width as usize * height as usize * 4].copy_from_slice(data); surface.damage_buffer(0, 0, width as i32, height as i32); - buffer.attach_to(&surface); + if let Err(err) = buffer.attach_to(&surface) { + log::warn!("failed to attach shm buffer to subsurface: {err}"); + return; + } surface.offset(offset.x as i32, offset.y as i32); wp_viewport.set_destination( (width as f64 / scale) as i32, From b90b1413a69acb3bdbed49d62c7a6e14d43170d3 Mon Sep 17 00:00:00 2001 From: Votre Nom Date: Tue, 5 May 2026 12:52:06 +0200 Subject: [PATCH 46/51] yoda: point window_clipboard at public Forgejo fork MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace pop-os/window_clipboard sctk-0.20 (no wayland feature) with forge.aditua.com/leyoda/window_clipboard branch yoda-x11-optional. This removes the need for cosmic-files' [patch] redirect to a local path and makes libcosmic build standalone again. Leyoda 2026 – GPLv3 --- Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f3d7b8f9..1a0b86f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -304,13 +304,13 @@ wayland-client = { version = "0.31.5" } winapi = "0.3" # window_clipboard = "0.4.1" -# Yoda: window_clipboard via git path = our local fork that has x11 gated -# behind an opt-in feature. default-features=false + features=["wayland"] -# drops clipboard_x11 + x11rb from the build. -# Kept at /home/lionel/Devels/window_clipboard branch yoda-x11-optional. -window_clipboard = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20", default-features = false, features = ["wayland"] } -dnd = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" } -mime = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" } +# Yoda: window_clipboard pinned to our public fork that has x11 gated behind +# an opt-in feature. default-features=false + features=["wayland"] drops +# clipboard_x11 + x11rb from the build. Branch yoda-x11-optional on +# https://forge.aditua.com/leyoda/window_clipboard. +window_clipboard = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional", default-features = false, features = ["wayland"] } +dnd = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional" } +mime = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional" } # Yoda: default-features = false drops winit's x11 default, which otherwise # pulls winit-x11, x11-dl, tiny-xlib, as-raw-xcb-connection. Our Wayland-only # fork doesn't need any of that — the wayland + wayland-dlopen features are From 4e2e58e03d5f01ce8a328a9128ac23fefd61ca39 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Sat, 23 May 2026 20:55:59 +0200 Subject: [PATCH 47/51] chore: gate accessibility-only winit state --- winit/src/lib.rs | 2 ++ winit/src/window/state.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 517eddbc..918f6c2a 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -186,6 +186,7 @@ where receiver: mpsc::UnboundedReceiver, error: Option, system_theme: Option>, + #[cfg(feature = "a11y")] control_sender: mpsc::UnboundedSender, #[cfg(feature = "a11y")] @@ -212,6 +213,7 @@ where id: settings.id, sender: event_sender, receiver: control_receiver, + #[cfg(feature = "a11y")] control_sender: control_sender.clone(), error: None, system_theme: Some(system_theme_sender), diff --git a/winit/src/window/state.rs b/winit/src/window/state.rs index 98fdf9ee..1e16fc51 100644 --- a/winit/src/window/state.rs +++ b/winit/src/window/state.rs @@ -97,6 +97,7 @@ where self.ready = ready; } + #[cfg(feature = "a11y")] pub(crate) fn set_a11y_ready(&mut self, ready: bool) { self.a11y_ready = ready; } From 3982e04aa2d876adfcdb5fbf3a7beecd18d5d63f Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 18:09:40 +0200 Subject: [PATCH 48/51] chore: use local cctk and clipboard crates --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1a0b86f1..6496fc07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -274,7 +274,7 @@ tiny-skia = { version = "0.11", default-features = false, features = [ "std", "simd", ] } -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "32283d7" } +cctk = { path = "../../cosmic-protocols/client-toolkit", package = "cosmic-client-toolkit" } # Yoda: default-features=false drops softbuffer's x11 / x11-dlopen (pulled # tiny-xlib + as-raw-xcb-connection). The wayland + wayland-dlopen features # are re-activated via iced_tiny_skia's own `wayland` feature propagation. @@ -308,9 +308,9 @@ winapi = "0.3" # an opt-in feature. default-features=false + features=["wayland"] drops # clipboard_x11 + x11rb from the build. Branch yoda-x11-optional on # https://forge.aditua.com/leyoda/window_clipboard. -window_clipboard = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional", default-features = false, features = ["wayland"] } -dnd = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional" } -mime = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional" } +window_clipboard = { path = "../../window_clipboard", default-features = false, features = ["wayland"] } +dnd = { path = "../../window_clipboard/dnd" } +mime = { path = "../../window_clipboard/mime" } # Yoda: default-features = false drops winit's x11 default, which otherwise # pulls winit-x11, x11-dl, tiny-xlib, as-raw-xcb-connection. Our Wayland-only # fork doesn't need any of that — the wayland + wayland-dlopen features are From cf7828fb705bc1901dd9684d682c000b6ff66bc1 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 18:27:54 +0200 Subject: [PATCH 49/51] chore: use local glyphon and cosmic-text --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6496fc07..22151d21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -238,8 +238,8 @@ async-std = "1.0" bitflags = "2.5" bytemuck = { version = "1.0", features = ["derive"] } bytes = "1.6" -cosmic-text = "0.19" -cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "e429a025df36ab8145708acb309080ae3deec17a" } +cosmic-text = { path = "../../cosmic-text" } +cryoglyph = { package = "cryoglyph", path = "../../glyphon" } dark-light = "1.0" resvg = "0.45" web-sys = "0.3.69" From 362b4abb1558374686080db674b8f7c5868ae6ac Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 19:36:22 +0200 Subject: [PATCH 50/51] chore: use local winit and softbuffer --- Cargo.lock | 14 -------------- Cargo.toml | 25 ++++++++++++++++++++++--- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11a85d77..1f6f716f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1658,7 +1658,6 @@ dependencies = [ [[package]] name = "dpi" version = "0.1.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" [[package]] name = "drm" @@ -6275,7 +6274,6 @@ dependencies = [ [[package]] name = "smithay-clipboard" version = "0.8.0" -source = "git+https://github.com/pop-os/smithay-clipboard?tag=sctk-0.20#859b02c88f45c554049a67c6ddeec1692ce0e20b" dependencies = [ "libc", "raw-window-handle", @@ -6333,7 +6331,6 @@ dependencies = [ [[package]] name = "softbuffer" version = "0.4.1" -source = "git+https://github.com/pop-os/softbuffer?tag=cosmic-4.0#a3f77e251e7422803f693df6e3fc313c010c4dcb" dependencies = [ "as-raw-xcb-connection", "bytemuck", @@ -8517,7 +8514,6 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winit" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "bitflags 2.11.0", "cfg_aliases", @@ -8543,7 +8539,6 @@ dependencies = [ [[package]] name = "winit-android" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "android-activity", "bitflags 2.11.0", @@ -8558,7 +8553,6 @@ dependencies = [ [[package]] name = "winit-appkit" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "bitflags 2.11.0", "block2 0.6.2", @@ -8580,7 +8574,6 @@ dependencies = [ [[package]] name = "winit-common" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "memmap2 0.9.10", "objc2 0.6.4", @@ -8595,7 +8588,6 @@ dependencies = [ [[package]] name = "winit-core" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "bitflags 2.11.0", "cursor-icon", @@ -8609,7 +8601,6 @@ dependencies = [ [[package]] name = "winit-orbital" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "bitflags 2.11.0", "dpi", @@ -8625,7 +8616,6 @@ dependencies = [ [[package]] name = "winit-uikit" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "bitflags 2.11.0", "block2 0.6.2", @@ -8645,7 +8635,6 @@ dependencies = [ [[package]] name = "winit-wayland" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "ahash", "bitflags 2.11.0", @@ -8671,7 +8660,6 @@ dependencies = [ [[package]] name = "winit-web" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "atomic-waker", "bitflags 2.11.0", @@ -8693,7 +8681,6 @@ dependencies = [ [[package]] name = "winit-win32" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "bitflags 2.11.0", "cursor-icon", @@ -8709,7 +8696,6 @@ dependencies = [ [[package]] name = "winit-x11" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "bitflags 2.11.0", "bytemuck", diff --git a/Cargo.toml b/Cargo.toml index 22151d21..c49fcf92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -278,7 +278,7 @@ cctk = { path = "../../cosmic-protocols/client-toolkit", package = "cosmic-clien # Yoda: default-features=false drops softbuffer's x11 / x11-dlopen (pulled # tiny-xlib + as-raw-xcb-connection). The wayland + wayland-dlopen features # are re-activated via iced_tiny_skia's own `wayland` feature propagation. -softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0", default-features = false } +softbuffer = { path = "../../softbuffer", default-features = false } syntect = "5.2" tokio = "1.0" tracing = "0.1" @@ -315,10 +315,29 @@ mime = { path = "../../window_clipboard/mime" } # pulls winit-x11, x11-dl, tiny-xlib, as-raw-xcb-connection. Our Wayland-only # fork doesn't need any of that — the wayland + wayland-dlopen features are # re-enabled via iced_winit's own feature propagation from libcosmic-yoda. -winit = { git = "https://github.com/pop-os/winit.git", tag = "cosmic-0.14", default-features = false } -winit-core = { git = "https://github.com/pop-os/winit.git", tag = "cosmic-0.14" } +winit = { path = "../../winit/winit", default-features = false } +winit-core = { path = "../../winit/winit-core" } cursor-icon = "1.1.0" +[patch.'https://github.com/pop-os/softbuffer'] +softbuffer = { path = "../../softbuffer" } + +[patch.'https://github.com/pop-os/smithay-clipboard'] +smithay-clipboard = { path = "../../smithay-clipboard" } + +[patch.'https://github.com/pop-os/winit.git'] +dpi = { path = "../../winit/dpi" } +winit = { path = "../../winit/winit" } +winit-android = { path = "../../winit/winit-android" } +winit-appkit = { path = "../../winit/winit-appkit" } +winit-common = { path = "../../winit/winit-common" } +winit-core = { path = "../../winit/winit-core" } +winit-orbital = { path = "../../winit/winit-orbital" } +winit-uikit = { path = "../../winit/winit-uikit" } +winit-wayland = { path = "../../winit/winit-wayland" } +winit-web = { path = "../../winit/winit-web" } +winit-win32 = { path = "../../winit/winit-win32" } +winit-x11 = { path = "../../winit/winit-x11" } [workspace.lints.rust] rust_2018_idioms = { level = "deny", priority = -1 } From 1a57634add6e820c299f1c59664cd98098a9c348 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Thu, 2 Jul 2026 19:01:15 +0200 Subject: [PATCH 51/51] yoda: cargo fix sweep post-rebase (unused imports/vars) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Leyoda 2026 – GPLv3 --- Cargo.lock | 89 +++++++------------------- core/src/element.rs | 2 +- core/src/overlay/group.rs | 2 +- core/src/widget/operation/search_id.rs | 2 +- graphics/src/image.rs | 2 - runtime/src/dnd.rs | 4 +- runtime/src/lib.rs | 1 - widget/src/container.rs | 2 +- widget/src/keyed/column.rs | 1 - widget/src/list.rs | 2 +- widget/src/mouse_area.rs | 1 - widget/src/pane_grid/content.rs | 2 +- widget/src/scrollable.rs | 3 +- widget/src/stack.rs | 1 - widget/src/themer.rs | 1 - widget/src/toggler.rs | 5 +- 16 files changed, 32 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f6f716f..b5d874b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1076,7 +1076,6 @@ dependencies = [ [[package]] name = "clipboard_macos" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c" dependencies = [ "objc", "objc-foundation", @@ -1086,22 +1085,12 @@ dependencies = [ [[package]] name = "clipboard_wayland" version = "0.2.2" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c" dependencies = [ "dnd", "mime 0.1.0", "smithay-clipboard", ] -[[package]] -name = "clipboard_x11" -version = "0.4.2" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c" -dependencies = [ - "thiserror 1.0.69", - "x11rb", -] - [[package]] name = "clock" version = "0.1.0" @@ -1294,7 +1283,6 @@ dependencies = [ [[package]] name = "cosmic-client-toolkit" version = "0.2.0" -source = "git+https://github.com/pop-os/cosmic-protocols?rev=a7d2d7a#a7d2d7a6188425f699eaddf7a025617ce56068ae" dependencies = [ "bitflags 2.11.0", "cosmic-protocols", @@ -1307,7 +1295,6 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.2.0" -source = "git+https://github.com/pop-os/cosmic-protocols?rev=a7d2d7a#a7d2d7a6188425f699eaddf7a025617ce56068ae" dependencies = [ "bitflags 2.11.0", "wayland-backend", @@ -1321,8 +1308,6 @@ dependencies = [ [[package]] name = "cosmic-text" version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73" dependencies = [ "bitflags 2.11.0", "fontdb", @@ -1340,6 +1325,7 @@ dependencies = [ "unicode-linebreak", "unicode-script", "unicode-segmentation", + "unicode-width", ] [[package]] @@ -1446,7 +1432,6 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "cryoglyph" version = "0.1.0" -source = "git+https://github.com/iced-rs/cryoglyph.git?rev=e429a025df36ab8145708acb309080ae3deec17a#e429a025df36ab8145708acb309080ae3deec17a" dependencies = [ "cosmic-text", "etagere", @@ -1623,7 +1608,6 @@ dependencies = [ [[package]] name = "dnd" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c" dependencies = [ "bitflags 2.11.0", "mime 0.1.0", @@ -1659,45 +1643,12 @@ dependencies = [ name = "dpi" version = "0.1.2" -[[package]] -name = "drm" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde" -dependencies = [ - "bitflags 2.11.0", - "bytemuck", - "drm-ffi", - "drm-fourcc", - "rustix 0.38.44", -] - -[[package]] -name = "drm-ffi" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6" -dependencies = [ - "drm-sys", - "rustix 0.38.44", -] - [[package]] name = "drm-fourcc" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" -[[package]] -name = "drm-sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d09ff881f92f118b11105ba5e34ff8f4adf27b30dae8f12e28c193af1c83176" -dependencies = [ - "libc", - "linux-raw-sys 0.6.5", -] - [[package]] name = "editor" version = "0.1.0" @@ -2420,7 +2371,7 @@ dependencies = [ "log", "presser", "thiserror 2.0.18", - "windows 0.57.0", + "windows 0.62.2", ] [[package]] @@ -3785,14 +3736,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ "bitflags 2.11.0", "libc", "plain", - "redox_syscall 0.7.4", + "redox_syscall 0.9.0", ] [[package]] @@ -3822,12 +3773,6 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" -[[package]] -name = "linux-raw-sys" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -4053,7 +3998,6 @@ dependencies = [ [[package]] name = "mime" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c" dependencies = [ "smithay-clipboard", ] @@ -5479,6 +5423,16 @@ dependencies = [ "font-types", ] +[[package]] +name = "redox_event" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5018d583d6d2f5499352aea8d177e9067d1eb03ab17c78169d5ba7a30001b15" +dependencies = [ + "bitflags 2.11.0", + "libredox", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -5490,9 +5444,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" dependencies = [ "bitflags 2.11.0", ] @@ -6337,7 +6291,6 @@ dependencies = [ "cfg_aliases", "cocoa", "core-graphics", - "drm", "fastrand", "foreign-types 0.5.0", "js-sys", @@ -8005,12 +7958,10 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window_clipboard" version = "0.4.1" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c" dependencies = [ "clipboard-win", "clipboard_macos", "clipboard_wayland", - "clipboard_x11", "dnd", "mime 0.1.0", "raw-window-handle", @@ -8607,7 +8558,7 @@ dependencies = [ "libredox", "orbclient", "raw-window-handle", - "redox_syscall 0.7.4", + "redox_event", "smol_str", "tracing", "winit-core", @@ -9240,3 +9191,7 @@ dependencies = [ "syn", "winnow 0.7.15", ] + +[[patch.unused]] +name = "cosmic-freedesktop-icons" +version = "0.4.0" diff --git a/core/src/element.rs b/core/src/element.rs index f97ed6bc..dbe449c5 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -1,4 +1,4 @@ -use crate::event::{self, Event}; +use crate::event::Event; use crate::id::Id; use crate::layout; use crate::mouse; diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index ec0906e7..1a4ce672 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -3,7 +3,7 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget; -use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size}; +use crate::{Clipboard, Event, Layout, Overlay, Shell, Size}; /// An [`Overlay`] container that displays multiple overlay [`overlay::Element`] /// children. diff --git a/core/src/widget/operation/search_id.rs b/core/src/widget/operation/search_id.rs index 1286abaf..e03fac8a 100644 --- a/core/src/widget/operation/search_id.rs +++ b/core/src/widget/operation/search_id.rs @@ -4,7 +4,7 @@ use super::Operation; use crate::{ Rectangle, id::Id, - widget::operation::{Outcome, focusable::Count}, + widget::operation::Outcome, }; /// Produces an [`Operation`] that searches for the Id diff --git a/graphics/src/image.rs b/graphics/src/image.rs index e5fe9281..d9b23216 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -2,8 +2,6 @@ #[cfg(feature = "image")] use crate::core::Bytes; -use crate::core::Color; -use crate::core::Radians; use crate::core::Rectangle; use crate::core::image; use crate::core::svg; diff --git a/runtime/src/dnd.rs b/runtime/src/dnd.rs index 2a0db116..2e526eb6 100644 --- a/runtime/src/dnd.rs +++ b/runtime/src/dnd.rs @@ -1,10 +1,8 @@ //! Access the clipboard. -use std::any::Any; use dnd::{DndDestinationRectangle, DndSurface}; -use iced_core::clipboard::DndSource; -use window_clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; +use window_clipboard::mime::AllowedMimeTypes; use crate::{Action, Task, oneshot, task}; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 22da3d66..68034bfc 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -28,7 +28,6 @@ pub use task::Task; pub use user_interface::UserInterface; pub use window::Window; -use crate::core::Color; use crate::futures::futures::channel::oneshot; use std::borrow::Cow; diff --git a/widget/src/container.rs b/widget/src/container.rs index e4aeabf2..a264007a 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -35,7 +35,7 @@ use crate::core::{ color, }; -use iced_runtime::{Action, Task, task}; +use iced_runtime::{Task, task}; /// A widget that aligns its contents inside of its boundaries. /// diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index ef7d9d9a..bb98d606 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -1,7 +1,6 @@ //! Keyed columns distribute content vertically while keeping continuity. //! Distribute content vertically. -use crate::core::event; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; diff --git a/widget/src/list.rs b/widget/src/list.rs index d7cadd50..9f33244e 100644 --- a/widget/src/list.rs +++ b/widget/src/list.rs @@ -1,5 +1,5 @@ #![allow(missing_docs)] -use crate::core::event::{self, Event}; +use crate::core::event::Event; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 3d355c52..4d808348 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -2,7 +2,6 @@ use iced_renderer::core::mouse::Click; -use crate::core::event; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index a76010e7..4c41e3a6 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -6,7 +6,7 @@ use crate::core::renderer; use crate::core::widget::{self, Tree}; use crate::core::{ self, Clipboard, Element, Event, Layout, Point, Rectangle, Shell, Size, - Vector, event, + Vector, }; use crate::pane_grid::{Draggable, TitleBar}; diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index e005a7b8..c906b66e 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -27,7 +27,6 @@ use iced_runtime::core::widget::Id; #[cfg(feature = "a11y")] use std::borrow::Cow; -use crate::core::event; use crate::core::keyboard; use crate::core::layout; use crate::core::mouse; @@ -933,7 +932,7 @@ where let had_input_method = shell.input_method().is_enabled(); - let mut c_event = match event.clone() { + let c_event = match event.clone() { Event::Dnd(dnd::DndEvent::Offer( id, dnd::OfferEvent::Enter { diff --git a/widget/src/stack.rs b/widget/src/stack.rs index e85bf2af..cd30a886 100644 --- a/widget/src/stack.rs +++ b/widget/src/stack.rs @@ -1,6 +1,5 @@ //! Display content on top of other content. -use crate::core::event; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; diff --git a/widget/src/themer.rs b/widget/src/themer.rs index 306c8580..76448ae1 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -1,6 +1,5 @@ use crate::container; -use crate::core::event; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 6709f1dc..b4e66887 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -37,7 +37,6 @@ use std::borrow::Cow; use iced_runtime::core::border::Radius; use crate::core::alignment; -use crate::core::border; use crate::core::layout; use crate::core::mouse; use crate::core::renderer; @@ -510,7 +509,7 @@ where let space = style.handle_margin; - let toggler_background_bounds = Rectangle { + let _toggler_background_bounds = Rectangle { x: bounds.x, y: bounds.y, width: bounds.width, @@ -530,7 +529,7 @@ where style.background, ); - let padding = (style.padding_ratio * bounds.height).round(); + let _padding = (style.padding_ratio * bounds.height).round(); let toggler_foreground_bounds = Rectangle { x: bounds.x + if self.is_toggled {