diff --git a/Cargo.lock b/Cargo.lock index b309db1d..c28424ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3206,7 +3206,6 @@ dependencies = [ "resvg", "rustc-hash 2.1.2", "rustix 0.38.44", - "smithay-client-toolkit 0.20.0", "thiserror 2.0.18", "tiny-xlib", "wayland-backend", diff --git a/graphics/src/image.rs b/graphics/src/image.rs index d9b23216..8ef868cd 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -1,6 +1,8 @@ //! Load and operate on images. #[cfg(feature = "image")] use crate::core::Bytes; +#[cfg(feature = "image")] +use crate::core::Size; use crate::core::Rectangle; use crate::core::image; @@ -178,3 +180,105 @@ fn to_error(error: ::image::ImageError) -> image::Error { error => image::Error::Invalid(Arc::new(error)), } } + +/// resample a raster image to this target +#[cfg(feature = "image")] +pub fn downsample_target( + native: Size, + bounds: Size, +) -> Option> { + if !(bounds.width >= 1.0 && bounds.height >= 1.0) { + return None; + } + + // Round up to 4px so an animated resize does not make a copy per pixel + // TODO: maybe find a better approach + let quantize = |length: f32| (length.ceil() as u32).div_ceil(4) * 4; + + let target = Size::new( + quantize(bounds.width).min(native.width), + quantize(bounds.height).min(native.height), + ); + + let minified = native.width as f32 >= target.width as f32 * 1.25 + || native.height as f32 >= target.height as f32 * 1.25; + + // If the target is big then Lanczos will be too expensive, and sampler is good enough + let small = u64::from(target.width) * u64::from(target.height) <= 1 << 18; + + (minified && small).then_some(target) +} + +/// Resamples premultiplied pixels down to `target`. +#[cfg(feature = "image")] +pub fn downsample_premultiplied( + pixels: &[u8], + size: Size, + target: Size, +) -> Vec { + let image = + ::image::RgbaImage::from_raw(size.width, size.height, pixels.to_vec()) + .expect("pixels hold width * height RGBA pixels"); + + resize(&image, target).into_raw() +} + +#[cfg(feature = "image")] +fn resize(image: &::image::RgbaImage, target: Size) -> ::image::RgbaImage { + use ::image::imageops::{self, FilterType}; + use std::borrow::Cow; + + // if image is too big compared to target, box average it to double the target + // and then do Lanczos. The final result is almost the same, but lanczos is too + // expensive on large images. + let image = if image.width() >= target.width * 4 + && image.height() >= target.height * 4 + { + Cow::Owned(imageops::thumbnail( + image, + target.width * 2, + target.height * 2, + )) + } else { + Cow::Borrowed(image) + }; + + imageops::resize(&*image, target.width, target.height, FilterType::Lanczos3) +} + +/// Resamples RGBA pixels down to `target`. +/// +/// Premultiplies the image, to avoid fringing the edges of an icon when interpolating +/// transparent pixels. +#[cfg(feature = "image")] +pub fn downsample(image: &Buffer, target: Size) -> ::image::RgbaImage { + let mut image = ::image::RgbaImage::from_raw( + image.width(), + image.height(), + image.as_raw().to_vec(), + ) + .expect("buffer holds width * height RGBA pixels"); + + for pixel in image.pixels_mut() { + let alpha = u32::from(pixel[3]); + + for channel in &mut pixel.0[..3] { + *channel = ((u32::from(*channel) * alpha + 127) / 255) as u8; + } + } + + let mut image = resize(&image, target); + + for pixel in image.pixels_mut() { + let alpha = u32::from(pixel[3]); + + if alpha > 0 { + for channel in &mut pixel.0[..3] { + *channel = ((u32::from(*channel) * 255 + alpha / 2) / alpha) + .min(255) as u8; + } + } + } + + image +} diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index 7c820917..0535158a 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -51,7 +51,21 @@ impl Pipeline { ) { let mut cache = self.cache.borrow_mut(); - let Ok(mut image) = cache.allocate(handle) else { + let target = { + let Ok(image) = cache.allocate(handle) else { + return; + }; + + graphics::image::downsample_target( + Size::new(image.width(), image.height()), + bounds.size(), + ) + }; + + let Ok(mut image) = (match target { + Some(target) => cache.allocate_resampled(handle, target), + None => cache.allocate(handle), + }) else { return; }; @@ -110,6 +124,8 @@ impl Pipeline { struct Cache { entries: FxHashMap>, hits: FxHashSet, + resampled: FxHashMap<(raster::Id, u32, u32), Entry>, + resampled_hits: FxHashSet<(raster::Id, u32, u32)>, } impl Cache { @@ -166,9 +182,59 @@ impl Cache { Ok(ret) } + /// Like [`Self::allocate`], resampled to `target`. Call after + /// [`Self::allocate`] has decoded the image. + pub fn allocate_resampled( + &mut self, + handle: &raster::Handle, + target: Size, + ) -> Result, raster::Error> { + let key = (handle.id(), target.width, target.height); + + if !self.resampled.contains_key(&key) { + let native = self + .entries + .get(&handle.id()) + .and_then(Option::as_ref) + .ok_or(raster::Error::Empty)?; + + // Stored pixels are already premultiplied, so resample them as is. + let pixels = graphics::image::downsample_premultiplied( + bytemuck::cast_slice(&native.pixels), + Size::new(native.width, native.height), + target, + ); + + let _ = self.resampled.insert( + key, + Entry { + width: target.width, + height: target.height, + pixels: pixels + .chunks_exact(4) + .map(|p| u32::from_ne_bytes([p[0], p[1], p[2], p[3]])) + .collect(), + }, + ); + } + + let _ = self.resampled_hits.insert(key); + let entry = &self.resampled[&key]; + + Ok(tiny_skia::PixmapRef::from_bytes( + bytemuck::cast_slice(&entry.pixels), + entry.width, + entry.height, + ) + .expect("Build pixmap from image bytes")) + } + fn trim(&mut self) { self.entries.retain(|key, _| self.hits.contains(key)); + self.resampled + .retain(|key, _| self.resampled_hits.contains(key)); self.hits.clear(); + self.resampled_hits.clear(); } } diff --git a/wgpu/src/image/cache.rs b/wgpu/src/image/cache.rs index 88a48640..37f1e33d 100644 --- a/wgpu/src/image/cache.rs +++ b/wgpu/src/image/cache.rs @@ -5,6 +5,8 @@ use crate::image::atlas::{self, Atlas}; #[cfg(all(feature = "image", not(target_arch = "wasm32")))] use worker::Worker; +#[cfg(feature = "image")] +use rustc_hash::{FxHashMap, FxHashSet}; #[cfg(feature = "image")] use std::collections::HashMap; @@ -38,6 +40,9 @@ impl Cache { raster: Raster { cache: crate::image::raster::Cache::default(), pending: HashMap::new(), + resampled: FxHashMap::default(), + resampled_hits: FxHashSet::default(), + should_trim: false, belt: wgpu::util::StagingBelt::new( device.clone(), 2 * 1024 * 1024, @@ -209,7 +214,9 @@ impl Cache { encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, handle: &core::image::Handle, + bounds: Size, ) -> Option<(&atlas::Entry, &Arc)> { + use crate::graphics::image::{downsample, downsample_target, load}; use crate::image::raster::Memory; self.receive(); @@ -223,6 +230,34 @@ impl Cache { None, )?; + if let Some(target) = downsample_target(memory.dimensions(), bounds) { + let key = (handle.id(), target.width, target.height); + + if !self.raster.resampled.contains_key(&key) { + let image = memory.host().or_else(|| load(handle).ok())?; + let image = downsample(&image, target); + + let entry = self.atlas.upload( + device, + encoder, + belt, + target.width, + target.height, + &image, + )?; + + let _ = self.raster.resampled.insert(key, entry); + self.raster.should_trim = true; + } + + let _ = self.raster.resampled_hits.insert(key); + + return Some(( + self.raster.resampled.get(&key)?, + self.atlas.bind_group(), + )); + } + if let Memory::Device { entry, bind_group, .. } = memory @@ -303,6 +338,23 @@ impl Cache { #[cfg(not(target_arch = "wasm32"))] self.worker.drop(_bind_group); }); + + if self.raster.should_trim { + let hits = &self.raster.resampled_hits; + let atlas = &mut self.atlas; + + self.raster.resampled.retain(|key, entry| { + let retain = hits.contains(key); + + if !retain { + atlas.remove(entry); + } + + retain + }); + self.raster.resampled_hits.clear(); + self.raster.should_trim = false; + } } #[cfg(feature = "svg")] @@ -376,6 +428,9 @@ impl Drop for Cache { struct Raster { cache: crate::image::raster::Cache, pending: HashMap>, + resampled: FxHashMap<(core::image::Id, u32, u32), atlas::Entry>, + resampled_hits: FxHashSet<(core::image::Id, u32, u32)>, + should_trim: bool, belt: wgpu::util::StagingBelt, } diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index d0665ee4..2b5f7ade 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -267,7 +267,13 @@ impl State { clip_bounds, } => { if let Some((atlas_entry, bind_group)) = cache - .upload_raster(device, encoder, belt, &image.handle) + .upload_raster( + device, + encoder, + belt, + &image.handle, + bounds.size() * scale, + ) { match atlas.as_mut() { None => { diff --git a/winit/src/platform_specific/wayland/commands/layer_surface.rs b/winit/src/platform_specific/wayland/commands/layer_surface.rs index cdb4e93d..5d8a4588 100644 --- a/winit/src/platform_specific/wayland/commands/layer_surface.rs +++ b/winit/src/platform_specific/wayland/commands/layer_surface.rs @@ -1,5 +1,6 @@ //! Interact with the window of your application. +use crate::core::Rectangle; use crate::core::window::Id as SurfaceId; use iced_runtime::{ self, Action, Task, @@ -125,3 +126,15 @@ pub fn set_padding( )), )) } + +/// +pub fn set_input_zone( + id: SurfaceId, + zone: Option>, +) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::InputZone { id, zone }, + )), + )) +} diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index b4589840..1926e391 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -1,4 +1,5 @@ pub mod control_flow; +pub mod popup; pub mod proxy; pub mod state; @@ -6,6 +7,7 @@ pub mod state; use crate::platform_specific::SurfaceIdWrapper; use crate::{ Control, + event_loop::popup::PopupManager, futures::futures::channel::mpsc, handlers::{ ext_background_effect, overlap::OverlapNotifyV1, @@ -177,13 +179,41 @@ impl SctkEventLoop { } } crate::platform_specific::Action::SetCursor( + surface, icon, ) => { - if let Some(seat) = state.seats.get_mut(0) { + state.record_cursor(surface.clone(), |r| { + r.icon = Some(icon); + r.hidden = false; + }); + if let Some(seat) = state + .seats + .get_mut(0) + .filter(|s| s.is_over(&surface)) + { seat.icon = Some(icon); + seat.hidden = false; seat.set_cursor(&state.connection, icon); } } + crate::platform_specific::Action::SetCursorVisible( + surface, + visible, + ) => { + state.record_cursor(surface.clone(), |r| { + r.hidden = !visible; + }); + if let Some(seat) = state + .seats + .get_mut(0) + .filter(|s| s.is_over(&surface)) + { + seat.set_cursor_visible( + &state.connection, + visible, + ); + } + } crate::platform_specific::Action::RequestRedraw( id, ) => { @@ -377,12 +407,13 @@ impl SctkEventLoop { blur_surfaces: HashMap::new(), corner_radii: HashMap::new(), layer_surfaces: Vec::new(), - popups: Vec::new(), + popmgr: PopupManager::default(), lock_surfaces: Vec::new(), subsurfaces: Vec::new(), touch_points: HashMap::new(), sctk_events: Vec::new(), frame_status: HashMap::new(), + cursor_requests: HashMap::new(), fractional_scaling_manager, viewporter_state, compositor_updates: Default::default(), @@ -514,8 +545,8 @@ impl SctkEventLoop { .chain( state .state - .popups - .iter() + .popmgr + .popups() .map(|s| s.popup.wl_surface()), ) .chain( diff --git a/winit/src/platform_specific/wayland/event_loop/popup.rs b/winit/src/platform_specific/wayland/event_loop/popup.rs new file mode 100644 index 00000000..109e9af6 --- /dev/null +++ b/winit/src/platform_specific/wayland/event_loop/popup.rs @@ -0,0 +1,189 @@ +use std::sync::Mutex; +use std::sync::Arc; +use cctk::sctk::shell::xdg::popup::PopupConfigure; +use cctk::sctk::shell::xdg::popup::Popup; +use iced_runtime::{platform_specific, core::window::Id}; +use wayland_client::protocol::wl_surface::WlSurface; +use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; + +use crate::event_loop::state::Common; +use crate::event_loop::state::SctkPopupData; + +#[derive(Debug, Default)] +pub struct PopupManager { + chains: Vec>, +} + +impl PopupManager { + pub(crate) fn popups(&self) -> impl Iterator { + self.chains.iter().map(|c| c.iter()).flatten() + } + + pub(crate) fn chain_for_popup_mut( + &mut self, + id: &WlSurface, + ) -> Option<(&mut Vec, usize)> { + let pos = self.chains.iter().enumerate().find_map(move |(pos, c)| { + if let Some(pop_pos) = + c.into_iter().position(|p| p.popup.wl_surface() == id) + { + Some((pos, pop_pos)) + } else { + None + } + }); + + pos.map(|pos| (&mut self.chains[pos.0], pos.1)) + } + + pub(crate) fn chain_for_popup( + &self, + id: &WlSurface, + ) -> Option<(&Vec, usize)> { + let pos = self.chains.iter().enumerate().find_map(move |(pos, c)| { + if let Some(pop_pos) = + c.into_iter().position(|p| p.popup.wl_surface() == id) + { + Some((pos, pop_pos)) + } else { + None + } + }); + + pos.map(|pos| (&self.chains[pos.0], pos.1)) + } + + pub(crate) fn popup(&self, id: &WlSurface) -> Option<&SctkPopup> { + self.chains.iter().find_map(move |c| { + c.into_iter().find(|p| p.popup.wl_surface() == id) + }) + } + + pub(crate) fn popup_mut( + &mut self, + id: &WlSurface, + ) -> Option<&mut SctkPopup> { + self.chains.iter_mut().find_map(move |c| { + c.into_iter().find(|p| p.popup.wl_surface() == id) + }) + } + + pub(crate) fn popup_id(&self, id: Id) -> Option<&SctkPopup> { + self.chains + .iter() + .find_map(move |c| c.into_iter().find(|p| p.data.id == id)) + } + + pub(crate) fn popup_id_mut(&mut self, id: Id) -> Option<&mut SctkPopup> { + self.chains + .iter_mut() + .find_map(move |c| c.into_iter().find(|p| p.data.id == id)) + } + + pub(crate) fn active_grab(&self) -> Option<&SctkPopup> { + self.chains + .iter() + .find_map(|c| c.into_iter().rev().find(|p| p.data.grab)) + } + + pub(crate) fn root_grab(&self) -> Option<&SctkPopup> { + self.chains + .iter() + .find_map(|c| c.into_iter().find(|p| p.data.grab)) + } + + pub(crate) fn push(&mut self, popup: SctkPopup) { + if let Some((chain, _pos)) = + self.chain_for_popup_mut(popup.data.parent.wl_surface()) + { + // TODO should we return an error if a popup is attempted to be added to a non-leaf popup of a chain? + chain.push(popup); + } else { + if let Some(empty) = self.chains.iter_mut().find(|c| c.is_empty()) { + empty.push(popup); + } else { + self.chains.push(vec![popup]); + } + } + } + + pub(crate) fn remove( + &mut self, + popup: &WlSurface, + ) -> Option> { + // must perform cleanup so that the popups are dropped in the correct order + let ret = if let Some((chain, mut pos)) = + self.chain_for_popup_mut(popup) + { + // TODO should we return an error if a popup is attempted to be added to a non-leaf popup of a chain? + while let Some(p) = pos.checked_sub(1).and_then(|p| chain.get(p)) { + if p.close_with_children { + pos -= 1; + } else { + break; + } + } + + Some(chain.drain(pos..).rev()) + } else { + None + }; + + return ret; + } + + pub(crate) fn remove_ignore_children( + &mut self, + popup: &WlSurface, + ) -> Option> { + // must perform cleanup so that the popups are dropped in the correct order + let ret = if let Some((chain, pos)) = self.chain_for_popup_mut(popup) { + Some(chain.drain(pos..).rev()) + } else { + None + }; + + return ret; + } +} + +#[derive(Debug)] +pub struct SctkPopup { + pub(crate) popup: Popup, + pub(crate) last_configure: Option, + pub(crate) _pending_requests: + Vec, + pub(crate) data: SctkPopupData, + pub(crate) common: Arc>, + pub(crate) wp_fractional_scale: Option, + pub(crate) close_with_children: bool, +} + +impl SctkPopup { + pub(crate) fn set_size(&mut self, w: u32, h: u32, token: u32) { + let guard = self.common.lock().unwrap(); + if guard.size.width == w && guard.size.height == h { + return; + } + drop(guard); + // update geometry + self.popup + .xdg_surface() + .set_window_geometry(0, 0, w as i32, h as i32); + self.update_viewport(w, h); + // update positioner + self.data.positioner.set_size(w as i32, h as i32); + self.popup.reposition(&self.data.positioner, token); + } + + pub(crate) fn update_viewport(&mut self, w: u32, h: u32) { + let common = self.common.lock().unwrap(); + if common.size.width == w && common.size.height == h { + return; + } + if let Some(viewport) = common.wp_viewport.as_ref() { + // Set inner size without the borders. + viewport.set_destination(w as i32, h as i32); + } + } +} diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index f4a8c5d7..80914ffb 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1,5 +1,6 @@ use crate::{ Control, + event_loop::popup::{self, PopupManager}, handlers::{ activation::IcedRequestData, ext_background_effect, @@ -82,10 +83,7 @@ use cctk::{ Anchor, KeyboardInteractivity, Layer, LayerShell, LayerSurface, LayerSurfaceConfigure, SurfaceKind, }, - xdg::{ - XdgPositioner, XdgShell, - popup::{Popup, PopupConfigure}, - }, + xdg::{XdgPositioner, XdgShell, popup::Popup}, }, shm::{Shm, multi::MultiPool}, }, @@ -94,6 +92,7 @@ use cctk::{ }; use iced_runtime::{ core::{self, Point, touch}, + keyboard::key::Named::Settings, platform_specific::{ self, wayland::{ @@ -123,6 +122,13 @@ use wayland_protocols::{ pub static TOKEN_CTR: AtomicU32 = AtomicU32::new(0); +/// The requested cursor for a surface +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub(crate) struct CursorRequest { + pub(crate) icon: Option, + pub(crate) hidden: bool, +} + #[derive(Debug)] pub(crate) struct SctkSeat { pub(crate) seat: WlSeat, @@ -139,6 +145,8 @@ pub(crate) struct SctkSeat { pub(crate) active_icon: Option, // Cursor icon set by application pub(crate) icon: Option, + // Application asked for cursor to hide + pub(crate) hidden: bool, } impl SctkSeat { @@ -148,6 +156,35 @@ impl SctkSeat { self.active_icon = Some(icon); } } + + /// Is the pointer currently over `surface`. + pub(crate) fn is_over(&self, surface: &ObjectId) -> bool { + self.ptr_focus.as_ref().is_some_and(|s| &s.id() == surface) + } + + /// Set cursor visibility over this surfaces. + pub(crate) fn set_cursor_visible( + &mut self, + conn: &Connection, + visible: bool, + ) { + let Some(ptr) = self.ptr.as_ref() else { + return; + }; + + self.hidden = !visible; + + if visible { + let icon = self.icon.unwrap_or(CursorIcon::Default); + _ = ptr.set_cursor(conn, icon); + self.active_icon = Some(icon); + } else { + // Ignoring the error + // It only fails when the pointer is not on our surface (due to serial error) + _ = ptr.hide_cursor(); + self.active_icon = None; + } + } } #[derive(Debug, Clone)] @@ -263,47 +300,6 @@ impl From> for Common { } } -#[derive(Debug)] -pub struct SctkPopup { - pub(crate) popup: Popup, - pub(crate) last_configure: Option, - pub(crate) _pending_requests: - Vec, - pub(crate) data: SctkPopupData, - pub(crate) common: Arc>, - pub(crate) wp_fractional_scale: Option, - pub(crate) close_with_children: bool, -} - -impl SctkPopup { - pub(crate) fn set_size(&mut self, w: u32, h: u32, token: u32) { - let guard = self.common.lock().unwrap(); - if guard.size.width == w && guard.size.height == h { - return; - } - drop(guard); - // update geometry - self.popup - .xdg_surface() - .set_window_geometry(0, 0, w as i32, h as i32); - self.update_viewport(w, h); - // update positioner - self.data.positioner.set_size(w as i32, h as i32); - self.popup.reposition(&self.data.positioner, token); - } - - pub(crate) fn update_viewport(&mut self, w: u32, h: u32) { - let common = self.common.lock().unwrap(); - if common.size.width == w && common.size.height == h { - return; - } - if let Some(viewport) = common.wp_viewport.as_ref() { - // Set inner size without the borders. - viewport.set_destination(w as i32, h as i32); - } - } -} - #[derive(Debug)] pub struct SctkLockSurface { pub(crate) id: core::window::Id, @@ -337,6 +333,7 @@ pub struct SctkSubsurface { pub struct SctkPopupData { pub(crate) id: core::window::Id, pub(crate) parent: PopupParent, + pub(crate) parent_window: core::window::Id, pub(crate) toplevel: WlSurface, pub(crate) positioner: Arc, pub(crate) grab: bool, @@ -431,7 +428,7 @@ pub struct SctkState { /// `WindowUpdate` or buffer on the associated with it `WindowHandle`. pub(crate) windows: Vec, pub(crate) layer_surfaces: Vec, - pub(crate) popups: Vec, + pub(crate) popmgr: PopupManager, pub(crate) subsurfaces: Vec, pub(crate) lock_surfaces: Vec, pub(crate) blur_surfaces: HashMap, @@ -471,6 +468,7 @@ pub struct SctkState { pub(crate) session_lock_state: SessionLockState, pub(crate) session_lock: Option, pub(crate) id_map: HashMap, + pub(crate) cursor_requests: HashMap, pub(crate) to_commit: HashMap, pub(crate) destroyed: HashSet, pub(crate) pending_popup: Option<(SctkPopupSettings, usize)>, @@ -573,6 +571,17 @@ pub(crate) fn receive_frame( } impl SctkState { + /// What a surface wants the pointer over it to be. + pub(crate) fn record_cursor( + &mut self, + surface: ObjectId, + amend: impl FnOnce(&mut CursorRequest), + ) { + self.cursor_requests + .retain(|id, _| id == &surface || self.id_map.contains_key(id)); + amend(self.cursor_requests.entry(surface).or_default()); + } + pub fn request_redraw(&mut self, surface: &WlSurface) { let e = self .frame_status @@ -609,11 +618,7 @@ impl SctkState { } } - if let Some(popup) = self - .popups - .iter_mut() - .find(|p| p.popup.wl_surface() == surface) - { + if let Some(popup) = self.popmgr.popup(surface) { id = Some(popup.data.id); if legacy && popup.wp_fractional_scale.is_some() { return; @@ -689,7 +694,7 @@ impl SctkState { > { 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.popmgr.popup_id(settings.id).is_some() || self.subsurfaces.iter().any(|s| s.id == settings.id) { log::warn!( @@ -713,12 +718,7 @@ impl SctkState { PopupParent::Window(parent.wl_surface(&self.connection)), parent.wl_surface(&self.connection), ) - } else if let Some(i) = self - .popups - .iter() - .position(|p| p.data.id == settings.parent) - { - let parent = &self.popups[i]; + } else if let Some(parent) = self.popmgr.popup_id(settings.parent) { ( PopupParent::Popup(parent.popup.wl_surface().clone()), parent.data.toplevel.clone(), @@ -756,6 +756,7 @@ impl SctkState { if positioner.version() >= 3 && settings.positioner.reactive { positioner.set_reactive(); } + positioner.set_size(size.0 as i32, size.1 as i32); let grab = settings.grab; @@ -820,10 +821,9 @@ impl SctkState { ) } PopupParent::Popup(parent) => { - let Some(parent_xdg) = self.popups.iter().find_map(|p| { - (p.popup.wl_surface() == parent) - .then(|| p.popup.xdg_surface()) - }) else { + let Some(parent_xdg) = + self.popmgr.popup(parent).map(|p| p.popup.xdg_surface()) + else { return Err(PopupCreationError::ParentMissing); }; @@ -898,11 +898,12 @@ impl SctkState { let common = Arc::new(Mutex::new(common)); let positioner = Arc::new(positioner); - self.popups.push(SctkPopup { + self.popmgr.push(popup::SctkPopup { popup: popup.clone(), data: SctkPopupData { id: settings.id, parent: parent.clone(), + parent_window: settings.parent, toplevel: toplevel.clone(), positioner: positioner.clone(), grab: settings.grab, @@ -957,7 +958,7 @@ impl SctkState { 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.popmgr.popup_id(id).is_some() || self.subsurfaces.iter().any(|s| s.id == id) { log::warn!("Layer surface with id {id:?} already exists"); @@ -1071,7 +1072,7 @@ impl SctkState { ) -> 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.popmgr.popup_id(id).is_some() || self.subsurfaces.iter().any(|s| s.id == id) { log::warn!("Lock surface with id {id:?} already exists"); @@ -1150,12 +1151,9 @@ 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(id) = self.layer_surfaces.iter().find_map(|l| if l.id == id {self.popmgr.popups().find_map(|p| (p.data.parent.wl_surface() == l.surface.wl_surface()).then(|| p.data.id))} else {None}).clone() { + _ = self.handle_action(Action::Popup(platform_specific::wayland::popup::Action::Destroy { id })); + } if let Some(i) = self.layer_surfaces.iter().position(|l| l.id == id) { let l = self.layer_surfaces.remove(i); @@ -1269,245 +1267,200 @@ impl SctkState { } }, }, - Action::Popup(action) => match action { - platform_specific::wayland::popup::Action::Popup { popup: settings } => { - // first check existing popup - if let Some(existing) = self.popups.iter().position(|p| p.data.id == settings.id - && ( - self.popups.iter().any(|parent| parent.popup.wl_surface() == p.data.parent.wl_surface() && parent.data.id == settings.parent) - || self.windows.iter().any(|w| w.id == settings.parent && *p.data.parent.wl_surface() == w.wl_surface(&self.connection)) - || self.layer_surfaces.iter().any(|l| l.id == settings.parent && p.data.parent.wl_surface() == l.surface.wl_surface())) - ) { - let existing = &mut self.popups[existing]; - let size = if let Some(size) = settings.positioner.size { - size - } else { - let guard = existing.common.lock().unwrap(); - (guard.size.width, guard.size.height) - }; - let Ok(positioner) = XdgPositioner::new(&self.xdg_shell_state) - .map_err(PopupCreationError::PositionerCreationFailed) else { - log::error!("Failed to create popup positioner"); - return Ok(()); - }; - positioner.set_anchor(settings.positioner.anchor); - positioner.set_anchor_rect( - settings.positioner.anchor_rect.x, - settings.positioner.anchor_rect.y, - settings.positioner.anchor_rect.width, - settings.positioner.anchor_rect.height, - ); - if let Ok(constraint_adjustment) = - settings.positioner.constraint_adjustment.try_into() + Action::Popup(action) => { + match action { + platform_specific::wayland::popup::Action::Popup { popup: settings } => { + // first check existing popup + // if an existing popup with the same id exists, we just update the positioner. + if let Some((chain, existing)) = self.popmgr.popup_id(settings.id).and_then(|p| + (p.data.grab == settings.grab && self.popmgr.popups().any(|parent| parent.popup.wl_surface() == p.data.parent.wl_surface() && parent.data.id == settings.parent) + || self.windows.iter().any(|w| w.id == settings.parent && *p.data.parent.wl_surface() == w.wl_surface(&self.connection)) + || self.layer_surfaces.iter().any(|l| l.id == settings.parent && p.data.parent.wl_surface() == l.surface.wl_surface())).then(|| + { + p.popup.wl_surface().clone() + })).and_then(|s| self.popmgr.chain_for_popup_mut(&s)) { - positioner.set_constraint_adjustment(constraint_adjustment); - } - positioner.set_gravity(settings.positioner.gravity); - positioner.set_offset( - settings.positioner.offset.0, - settings.positioner.offset.1, - ); - if settings.positioner.reactive { - positioner.set_reactive(); - } - positioner.set_size(size.0 as i32, size.1 as i32); - existing.data.positioner = Arc::new(positioner); - existing.set_size(size.0, size.1, TOKEN_CTR.fetch_add(1, std::sync::atomic::Ordering::Relaxed)); - _ = send_event(&self.events_sender, &self.proxy, - SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Size(size.0, size.1), toplevel_id: existing.data.parent.wl_surface().clone(), parent_id: existing.data.parent.wl_surface().clone(), id: existing.popup.wl_surface().clone() }); - return Ok(()); - } - let mut parent_mismatch = !self.popups.is_empty(); - for p in &self.popups { - parent_mismatch = p.data.id != settings.parent; - } - if !self.destroyed.is_empty() || parent_mismatch { - if parent_mismatch { - let mut found = false; - for p in std::mem::take(&mut self.popups).into_iter().rev() { - let id = p.data.id; - self.popups.insert(0, p); - - found |= id == settings.parent; - if !found { - _ = self.handle_action(Action::Popup(platform_specific::wayland::popup::Action::Destroy{id})); - } - - } - } - if self.pending_popup.replace((settings, 0)).is_none() { - - let timer = cctk::sctk::reexports::calloop::timer::Timer::from_duration(Duration::from_millis(30)); - let queue_handle = self.queue_handle.clone(); - _ = self.loop_handle.insert_source(timer, move |_, _, state| { - let Some((mut popup, attempt)) = state.pending_popup.take() else { - return TimeoutAction::Drop; + let existing = &mut chain[existing]; + let size = if let Some(size) = settings.positioner.size { + size + } else { + let guard = existing.common.lock().unwrap(); + (guard.size.width, guard.size.height) + }; + let Ok(positioner) = XdgPositioner::new(&self.xdg_shell_state) + .map_err(PopupCreationError::PositionerCreationFailed) else { + log::error!("Failed to create popup positioner"); + return Ok(()); }; + positioner.set_anchor(settings.positioner.anchor); + positioner.set_anchor_rect( + settings.positioner.anchor_rect.x, + settings.positioner.anchor_rect.y, + settings.positioner.anchor_rect.width, + settings.positioner.anchor_rect.height, + ); + if let Ok(constraint_adjustment) = + settings.positioner.constraint_adjustment.try_into() + { + positioner.set_constraint_adjustment(constraint_adjustment); + } + positioner.set_gravity(settings.positioner.gravity); + positioner.set_offset( + settings.positioner.offset.0, + settings.positioner.offset.1, + ); + if settings.positioner.reactive { + positioner.set_reactive(); + } + positioner.set_size(size.0 as i32, size.1 as i32); + existing.data.positioner = Arc::new(positioner); + existing.set_size(size.0, size.1, TOKEN_CTR.fetch_add(1, std::sync::atomic::Ordering::Relaxed)); + _ = send_event(&self.events_sender, &self.proxy, + SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Size(size.0, size.1), toplevel_id: existing.data.parent.wl_surface().clone(), parent_id: existing.data.parent.wl_surface().clone(), id: existing.popup.wl_surface().clone(), parent_window: existing.data.parent_window }); + return Ok(()); + } else if !self.destroyed.is_empty() || self.popmgr.popup_id(settings.id).is_some() || self.popmgr.active_grab().is_some() + { + // must clean up popup chains so that there is just one grab chain... + let to_destroy = if settings.grab { + if let Some((grab_chain, p_i)) = self.popmgr.active_grab().and_then(|g| self.popmgr.chain_for_popup(g.popup.wl_surface())) { - if !state.destroyed.is_empty() || state.popups.last().is_some_and(|p| { - state.id_map.get(&p.popup.wl_surface().id()).map_or(true, |p| *p != popup.parent) - }) { - if attempt < 5 { - state.pending_popup = Some((popup, attempt+1)); - TimeoutAction::ToDuration(Duration::from_millis(30)) + if let Some(pos) = grab_chain.iter().position(|p| p.data.id == settings.parent) { + // destroy children of parent + grab_chain.get(pos + 1).map(|g| g.data.id) } else { - TimeoutAction::Drop + // destroy whole chain... + grab_chain.get(0).map(|g| g.data.id) } } else { - match state.get_popup(popup) { - Ok((id, parent_id, toplevel_id, surface, common)) => { - let wl_surface = surface.wl_surface().clone(); - receive_frame(&mut state.frame_status, &wl_surface); - send_event(&state.events_sender, &state.proxy, - 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 }); - } - Err(err) => { - log::error!("Failed to create popup. {err:?}"); - } - }; - TimeoutAction::Drop + None } - }); - } - } else { - self.pending_popup = None; - match self.get_popup(settings) { - Ok((id, parent_id, toplevel_id, surface, common)) => { - let wl_surface = surface.wl_surface().clone(); - send_event(&self.events_sender, &self.proxy, - 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 }); + } else { + None + }; + if let Some(to_destroy) = to_destroy { + _ = self.destroy_popup(to_destroy, true); } - Err(err) => { - log::error!("Failed to create popup. {err:?}"); + + if self.popmgr.popup_id(settings.id).is_some() { + let _ = self.handle_action(Action::Popup(platform_specific::wayland::popup::Action::Destroy { id: settings.id })); + } + if self.pending_popup.replace((settings, 0)).is_none() { + let timer = cctk::sctk::reexports::calloop::timer::Timer::from_duration(Duration::from_millis(30)); + let queue_handle = self.queue_handle.clone(); + _ = self.loop_handle.insert_source(timer, move |_, _, state| { + let Some((settings, attempt)) = state.pending_popup.take() else { + return TimeoutAction::Drop; + }; + // check if the mismatch still exists... + if !state.destroyed.is_empty() || state.popmgr.popup_id(settings.id).is_some_and(|p| + state.popmgr.popups().any(|parent| parent.popup.wl_surface() == p.data.parent.wl_surface() && parent.data.id == settings.parent)) + { + if attempt < 5 { + state.pending_popup = Some((settings, attempt+1)); + TimeoutAction::ToDuration(Duration::from_millis(30)) + } + else { + TimeoutAction::Drop + } + } else { + let parent_window = settings.parent; + match state.get_popup(settings) { + Ok((id, parent_id, toplevel_id, surface, common)) => { + let wl_surface = surface.wl_surface().clone(); + receive_frame(&mut state.frame_status, &wl_surface); + send_event(&state.events_sender, &state.proxy, + 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, parent_window }); + } + Err(err) => { + log::error!("Failed to create popup. {err:?}"); + } + }; + TimeoutAction::Drop + } + }); + } + } else { + let parent_window = settings.parent; + match self.get_popup(settings) { + Ok((id, parent_id, toplevel_id, surface, common)) => { + let wl_surface = surface.wl_surface().clone(); + send_event(&self.events_sender, &self.proxy, + 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, parent_window } + ); + } + Err(err) => { + log::error!("Failed to create popup. {err:?}"); + } } } - } - }, - // XXX popup destruction must be done carefully - // first destroy the uppermost popup, then work down to the requested popup - platform_specific::wayland::popup::Action::Destroy { id } => { - let sctk_popup = match self - .popups - .iter() - .position(|s| s.data.id == id) - { - Some(p) => self.popups.remove(p), - None => { - log::info!("No popup to destroy"); - return Ok(()); - }, - }; - let mut to_destroy = vec![sctk_popup]; - - while let Some(popup_to_destroy_last) = to_destroy.last().and_then(|popup| self - .popups - .iter() - .position(|p| popup.data.parent.wl_surface() == p.popup.wl_surface() && p.close_with_children)) { - let popup_to_destroy_last = self.popups.remove(popup_to_destroy_last); - to_destroy.push(popup_to_destroy_last); - } - to_destroy.reverse(); - - while let Some(popup_to_destroy_first) = to_destroy.last().and_then(|popup| self - .popups - .iter() - .position(|p| p.data.parent.wl_surface() == popup.popup.wl_surface())) { - let popup_to_destroy_first = self.popups.remove(popup_to_destroy_first); - to_destroy.push(popup_to_destroy_first); - } - for popup in to_destroy.into_iter().rev() { - if let Some(id) = self.id_map.remove(&popup.popup.wl_surface().id()) { - _ = self.destroyed.insert(id); + }, + // XXX popup destruction must be done carefully + // first destroy the uppermost popup, then work down to the requested popup + platform_specific::wayland::popup::Action::Destroy { id } => { + if let Some(value) = self.destroy_popup(id, false) { + return value; } - - if let Some(blurred) = self.blur_surfaces.remove(&id) { - blurred.destroy(); - } - _ = self.corner_radii.remove(&id); - - - let (removed, remaining): (Vec<_>, Vec<_>) = self - .subsurfaces - .drain(..) - .partition(|s| { - s.instance.parent == *popup.popup.wl_surface() - }); - - self.subsurfaces = remaining; - for s in removed + }, + platform_specific::wayland::popup::Action::Size { id, width, height } => { + if let Some(sctk_popup) = self + .popmgr.popup_id_mut(id) { - crate::subsurface_widget::remove_iced_subsurface( - &s.instance.wl_surface, - ); - send_event(&self.events_sender, &self.proxy, - SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(s.instance) ) - ); + // update geometry + // update positioner + sctk_popup.set_size(width, height, TOKEN_CTR.fetch_add(1, std::sync::atomic::Ordering::Relaxed)); + let surface = sctk_popup.popup.wl_surface().clone(); + _ = send_event(&self.events_sender, &self.proxy, + 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, parent_window: sctk_popup.data.parent_window }); } - _ = send_event(&self.events_sender, &self.proxy, - SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Done, toplevel_id: popup.data.toplevel.clone(), parent_id: popup.data.parent.wl_surface().clone(), id: popup.popup.wl_surface().clone() }); - } - }, - platform_specific::wayland::popup::Action::Size { id, width, height } => { - if let Some(sctk_popup) = self - .popups - .iter_mut() - .find(|s| s.data.id == id) - { - // update geometry - // update positioner - sctk_popup.set_size(width, height, TOKEN_CTR.fetch_add(1, std::sync::atomic::Ordering::Relaxed)); - let surface = sctk_popup.popup.wl_surface().clone(); - _ = send_event(&self.events_sender, &self.proxy, - 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() + }, + platform_specific::wayland::popup::Action::Reposition { id, positioner } => { + if let Some(sctk_popup) = self + .popmgr.popup_id_mut(id) { - sctk_popup.data.positioner.set_constraint_adjustment(constraint_adjustment); + 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(); + _ = 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, parent_window: sctk_popup.data.parent_window }); } - 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(); - _ = 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 } => { @@ -1718,7 +1671,7 @@ impl SctkState { } 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) { + } else if let Some(p) = self.popmgr.popup_id_mut(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) { @@ -1822,7 +1775,7 @@ impl SctkState { return Ok(()); } - let s = if let Some(s) = self.popups.iter().find(|s| s.data.id == id) { + let s = if let Some(s) = self.popmgr.popup_id(id) { s.popup.wl_surface() } else if let Some(s) = self.layer_surfaces.iter().find(|s| s.id == id) { s.surface.wl_surface() @@ -1843,6 +1796,76 @@ impl SctkState { Ok(()) } + fn destroy_popup( + &mut self, + id: core::window::Id, + ignore_children: bool, + ) -> Option> { + let Some(to_destroy) = self.popmgr.popup_id(id) else { + if self.pending_popup.take().is_none_or(|p_id| p_id.0.id != id) { + log::warn!( + "Destroyed popup does not exist. {id:?}, existing: {:?}", + self.popmgr.popups().map(|p| p.data.id).collect::>() + ); + } + return Some(Ok(())); + }; + let s = to_destroy.popup.wl_surface().clone(); + let d: Option> = if ignore_children { + self.popmgr.remove(&s).map(|v| v.collect()) + } else { + self.popmgr.remove_ignore_children(&s).map(|v| v.collect()) + }; + let Some(to_destroy) = d else { + return Some(Ok(())); + }; + + for popup in to_destroy { + if let Some(id) = self.id_map.remove(&popup.popup.wl_surface().id()) + { + _ = self.destroyed.insert(id); + } + + if let Some(blurred) = self.blur_surfaces.remove(&id) { + blurred.destroy(); + } + _ = self.corner_radii.remove(&id); + + let (removed, remaining): (Vec<_>, Vec<_>) = self + .subsurfaces + .drain(..) + .partition(|s| s.instance.parent == *popup.popup.wl_surface()); + + self.subsurfaces = remaining; + for s in removed { + crate::subsurface_widget::remove_iced_subsurface( + &s.instance.wl_surface, + ); + send_event( + &self.events_sender, + &self.proxy, + SctkEvent::SubsurfaceEvent( + crate::sctk_event::SubsurfaceEventVariant::Destroyed( + s.instance, + ), + ), + ); + } + _ = send_event( + &self.events_sender, + &self.proxy, + SctkEvent::PopupEvent { + variant: crate::sctk_event::PopupEventVariant::Done, + parent_window: popup.data.parent_window, + toplevel_id: popup.data.toplevel.clone(), + parent_id: popup.data.parent.wl_surface().clone(), + id: popup.popup.wl_surface().clone(), + }, + ); + } + None + } + fn apply_blur( &mut self, id: core::window::Id, @@ -1922,7 +1945,7 @@ impl SctkState { }; 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.popmgr.popup_id(settings.id).is_some() || self.subsurfaces.iter().any(|s| s.id == settings.id) { log::warn!("Subsurface with id {:?} already exists", settings.id); @@ -1977,12 +2000,7 @@ impl SctkState { self.windows.iter().find(|w| w.id == settings.parent) { PopupParent::Window(parent.wl_surface(&self.connection)) - } else if let Some(i) = self - .popups - .iter() - .position(|p| p.data.id == settings.parent) - { - let parent = &self.popups[i]; + } else if let Some(parent) = self.popmgr.popup_id(settings.parent) { PopupParent::Popup(parent.popup.wl_surface().clone()) } else if let Some(i) = self .lock_surfaces diff --git a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs index b40912ec..ac0bd4a9 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs @@ -31,7 +31,7 @@ impl PointerHandler for SctkState { // track events, but only forward for the active seat for e in events { - if my_seat.active_icon != my_seat.icon { + if !my_seat.hidden && my_seat.active_icon != my_seat.icon { // Restore cursor that was set by appliction, or default my_seat.set_cursor( conn, @@ -60,10 +60,19 @@ impl PointerHandler for SctkState { match e.kind { PointerEventKind::Enter { .. } => { _ = my_seat.ptr_focus.replace(e.surface.clone()); + if let Some(want) = + self.cursor_requests.get(&e.surface.id()) + { + my_seat.icon = want.icon; + my_seat.set_cursor_visible(conn, !want.hidden); + } } PointerEventKind::Leave { .. } => { _ = my_seat.ptr_focus.take(); _ = my_seat.active_icon = None; + // The next surface the pointer lands on decides what it + // looks like there + my_seat.hidden = false; } PointerEventKind::Press { time, diff --git a/winit/src/platform_specific/wayland/handlers/seat/seat.rs b/winit/src/platform_specific/wayland/handlers/seat/seat.rs index ee6b0e6e..ec2b187c 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/seat.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/seat.rs @@ -38,6 +38,7 @@ impl SeatHandler for SctkState { last_touch_down: None, icon: None, active_icon: None, + hidden: false, }); } @@ -65,6 +66,7 @@ impl SeatHandler for SctkState { last_touch_down: None, icon: None, active_icon: None, + hidden: false, }); self.seats.last_mut().unwrap() } diff --git a/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs b/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs index 432f5bde..4d11aac8 100644 --- a/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs +++ b/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs @@ -1,6 +1,6 @@ use crate::event_loop::state::receive_frame; use crate::platform_specific::wayland::{ - event_loop::state::{self, PopupParent, SctkState}, + event_loop::state::{PopupParent, SctkState}, sctk_event::{PopupEventVariant, SctkEvent}, }; use cctk::sctk::{ @@ -18,11 +18,11 @@ impl PopupHandler for SctkState { configure: cctk::sctk::shell::xdg::popup::PopupConfigure, ) { self.request_redraw(popup.wl_surface()); - let sctk_popup = match self.popups.iter_mut().find(|s| { - s.popup.wl_surface().clone() == popup.wl_surface().clone() - }) { + let sctk_popup = match self.popmgr.popup_mut(popup.wl_surface()) { Some(p) => p, - None => return, + None => { + return; + } }; let first = sctk_popup.last_configure.is_none(); _ = sctk_popup.last_configure.replace(configure.clone()); @@ -38,6 +38,7 @@ impl PopupHandler for SctkState { first, ), id: popup.wl_surface().clone(), + parent_window: sctk_popup.data.parent_window, toplevel_id: sctk_popup.data.toplevel.clone(), parent_id: match &sctk_popup.data.parent { PopupParent::LayerSurface(s) => s.clone(), @@ -53,35 +54,11 @@ impl PopupHandler for SctkState { _qh: &cctk::sctk::reexports::client::QueueHandle, popup: &cctk::sctk::shell::xdg::popup::Popup, ) { - let sctk_popup = match self.popups.iter().position(|s| { - s.popup.wl_surface().clone() == popup.wl_surface().clone() - }) { - Some(p) => self.popups.remove(p), - None => return, + let Some(to_destroy) = self.popmgr.remove(popup.wl_surface()) else { + return; }; - let mut to_destroy = vec![sctk_popup]; - while let Some(popup_to_destroy) = to_destroy.last() { - match popup_to_destroy.data.parent.clone() { - state::PopupParent::LayerSurface(_) - | state::PopupParent::Window(_) => { - break; - } - state::PopupParent::Popup(popup_to_destroy_first) => { - let Some(popup_to_destroy_first) = - self.popups.iter().position(|p| { - p.popup.wl_surface() == &popup_to_destroy_first - }) - else { - log::warn!("could not find popup to destroy first."); - return; - }; - let popup_to_destroy_first = - self.popups.remove(popup_to_destroy_first); - to_destroy.push(popup_to_destroy_first); - } - } - } - for popup in to_destroy.into_iter().rev() { + + for popup in to_destroy { if let Some(id) = self.id_map.remove(&popup.popup.wl_surface().id()) { if let Some(blurred) = self.blur_surfaces.remove(&id) { @@ -94,6 +71,7 @@ impl PopupHandler for SctkState { self.sctk_events.push(SctkEvent::PopupEvent { variant: PopupEventVariant::Done, + parent_window: popup.data.parent_window, toplevel_id: popup.data.toplevel.clone(), parent_id: popup.data.parent.wl_surface().clone(), id: popup.popup.wl_surface().clone(), diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index e98316ce..289d410d 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -32,7 +32,8 @@ use winit::window::ImePurpose; pub(crate) enum Action { Action(iced_runtime::platform_specific::wayland::Action), - SetCursor(CursorIcon), + SetCursor(ObjectId, CursorIcon), + SetCursorVisible(ObjectId, bool), RequestRedraw(ObjectId), TrackWindow(Arc, window::Id), ResizeWindow(window::Id), @@ -48,9 +49,14 @@ impl std::fmt::Debug for Action { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Action(arg0) => f.debug_tuple("Action").field(arg0).finish(), - Self::SetCursor(arg0) => { - f.debug_tuple("SetCursor").field(arg0).finish() + Self::SetCursor(arg0, arg1) => { + f.debug_tuple("SetCursor").field(arg0).field(arg1).finish() } + Self::SetCursorVisible(arg0, arg1) => f + .debug_tuple("SetCursorVisible") + .field(arg0) + .field(arg1) + .finish(), Self::RequestRedraw(arg0) => { f.debug_tuple("RequestRedraw").field(arg0).finish() } diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index 2ac54bed..64609b58 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -155,6 +155,9 @@ pub enum SctkEvent { parent_id: WlSurface, /// the id of this popup id: WlSurface, + /// Window id of the parent surface, as passed in the popup settings. Needed to route + /// `Done` to the parent's widgets. + parent_window: SurfaceId, }, SubsurfaceEvent(SubsurfaceEventVariant), @@ -920,6 +923,7 @@ impl SctkEvent { SctkEvent::PopupEvent { variant, id: surface, + parent_window, .. } => { match variant { @@ -958,6 +962,8 @@ impl SctkEvent { ) }) { + // tell the parent's widgets so menus can reset when the compositor dismisses them. + events.push((Some(parent_window), e.1.clone())); events.push(e) } } diff --git a/winit/src/platform_specific/wayland/winit_window.rs b/winit/src/platform_specific/wayland/winit_window.rs index b33b7f9e..2751b31c 100644 --- a/winit/src/platform_specific/wayland/winit_window.rs +++ b/winit/src/platform_specific/wayland/winit_window.rs @@ -83,7 +83,10 @@ impl winit::window::Window for SctkWinitWindow { fn set_cursor(&self, cursor: winit_core::cursor::Cursor) { match cursor { winit_core::cursor::Cursor::Icon(icon) => { - _ = self.tx.send(Action::SetCursor(icon)); + _ = self.tx.send(Action::SetCursor( + self.surface.wl_surface().id(), + icon, + )); } winit_core::cursor::Cursor::Custom(_) => { // TODO @@ -92,7 +95,10 @@ impl winit::window::Window for SctkWinitWindow { } fn set_cursor_visible(&self, visible: bool) { - // TODO + _ = self.tx.send(Action::SetCursorVisible( + self.surface.wl_surface().id(), + visible, + )); } fn surface_size(&self) -> winit::dpi::PhysicalSize { diff --git a/winit/src/window.rs b/winit/src/window.rs index e56483f4..18f5f41f 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -291,11 +291,11 @@ where pub fn update_mouse(&mut self, interaction: mouse::Interaction) { if interaction != self.mouse_interaction { if let Some(icon) = conversion::mouse_interaction(interaction) { - self.raw.set_cursor(icon.into()); - if self.mouse_interaction == mouse::Interaction::Hidden { self.raw.set_cursor_visible(true); } + + self.raw.set_cursor(icon.into()); } else { self.raw.set_cursor_visible(false); }