From 39d3c99d1315e9a3db76fa8b32aa91e439c38b32 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 27 Aug 2026 15:37:57 -0400 Subject: [PATCH] improv: allow multiple popup chains multiple grabs are still not allowed, but this seems to be working well with a tooltip, nested popup chains, and hopefully a context drawer popup --- Cargo.lock | 1 - .../wayland/event_loop/mod.rs | 8 +- .../wayland/event_loop/popup.rs | 158 ++++++ .../wayland/event_loop/state.rs | 519 ++++++++---------- .../wayland/handlers/shell/xdg_popup.rs | 42 +- 5 files changed, 387 insertions(+), 341 deletions(-) create mode 100644 winit/src/platform_specific/wayland/event_loop/popup.rs 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/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index 56911e42..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, @@ -405,7 +407,7 @@ 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(), @@ -543,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..09cfe96c --- /dev/null +++ b/winit/src/platform_specific/wayland/event_loop/popup.rs @@ -0,0 +1,158 @@ +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 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; + } +} + +#[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 a3a41ccd..12bb75d0 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::{ @@ -301,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, @@ -469,7 +427,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, @@ -659,11 +617,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; @@ -739,7 +693,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!( @@ -763,12 +717,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(), @@ -806,6 +755,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; @@ -870,10 +820,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); }; @@ -948,7 +897,7 @@ 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, @@ -1007,7 +956,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"); @@ -1121,7 +1070,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"); @@ -1200,12 +1149,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); @@ -1319,245 +1265,215 @@ 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(()); }; - - 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)) - } - else { + 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() }); + return Ok(()); + } else if !self.destroyed.is_empty() || self.popmgr.popup_id(settings.id).is_some() || self.popmgr.active_grab().is_some() + { + let active_grab = self.popmgr.active_grab(); + if let Some(grab) = self.popmgr.root_grab().zip(active_grab).and_then(|(root, active)| (active.data.id != settings.parent).then_some(root.data.id)) { + let _ = self.handle_action(Action::Popup(platform_specific::wayland::popup::Action::Destroy { id: grab })); + } + 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 { + 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 }); + } + Err(err) => { + log::error!("Failed to create popup. {err:?}"); + } + }; TimeoutAction::Drop } - } 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 + }); + } + } else { + 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 }); + } + Err(err) => { + log::error!("Failed to create popup. {err:?}"); } - }); - } - } 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 }); - } - 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"); + }, + // 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 Some(to_destroy) = self.popmgr.popup_id(id) else { + log::warn!("Destroyed popup does not exist."); return Ok(()); - }, - }; - let mut to_destroy = vec![sctk_popup]; + }; + let s = to_destroy.popup.wl_surface().clone(); - 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(); + let Some(to_destroy) = self.popmgr.remove(&s) else { + return Ok(()); + }; - 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); + 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, toplevel_id: popup.data.toplevel.clone(), parent_id: popup.data.parent.wl_surface().clone(), id: popup.popup.wl_surface().clone() }); } - - 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 }); } - _ = 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 }); } - 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 } => { @@ -1768,7 +1684,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) { @@ -1872,7 +1788,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() @@ -1972,7 +1888,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); @@ -2027,12 +1943,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/shell/xdg_popup.rs b/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs index 432f5bde..4266e196 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()); @@ -53,35 +53,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) {