Skip to content

Fix closing and opening a popup in the same eventloop run - #4834

Open
Murmele wants to merge 1 commit into
rust-windowing:masterfrom
Murmele:mm/popup-destroy-closed
Open

Murmele wants to merge 1 commit into
rust-windowing:masterfrom
Murmele:mm/popup-destroy-closed

Conversation

@Murmele

@Murmele Murmele commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Reason: When a popup object is removed in the client application it requests a close which was executed the next eventloop run. If in the same eventloop run a new popup with the same parent is tried to open, the creation will fail because there is still a popup open and we cannot open another one with the same parent

  • Tested on all platforms changed

  • Added an entry to the changelog module if knowledge of this change could be valuable to users

  • Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior

  • Created or updated an example program if it would help users understand this functionality

  • Based on wayland: share window setup, handle the parent window #4819

Reproducable with the following adapted popup example (Destroy popup before creating a new one in the same event loop run):

//! This example shows the capabilities of popups
//! Move the mouse on the window and press 'A' to create a new Popup and 'D' to delete it again.
//! Move the Window to the border so see how the constraint adjustments behave like flipping or
//! sliding. See `spawn_popup` and play with the various properties

#[cfg(any(x11_platform, macos_platform, windows_platform, wayland_platform))]
#[allow(deprecated)]
fn main() -> Result<(), impl std::error::Error> {
    use std::collections::HashMap;

    use softbuffer::{Context, Surface};
    use tracing::info;
    use winit::application::ApplicationHandler;
    use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, Position};
    use winit::event::{ElementState, KeyEvent, WindowEvent};
    use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
    use winit::raw_window_handle::HasRawWindowHandle;
    use winit::window::{Window, WindowAttributes, WindowId};

    #[path = "util/fill.rs"]
    mod fill;

    #[derive(Debug)]
    struct WindowData {
        surface: Surface<OwnedDisplayHandle, Box<dyn Window>>,
        color: u32,
    }

    impl WindowData {
        fn new(context: &Context<OwnedDisplayHandle>, window: Box<dyn Window>, color: u32) -> Self {
            let surface = Surface::new(context, window).unwrap();
            Self { surface, color }
        }
    }

    #[derive(Debug)]
    struct Application {
        parent_window_id: Option<WindowId>,
        main_window: Option<WindowId>,
        windows: HashMap<WindowId, WindowData>,
        popups: Vec<WindowId>,
        position: Option<PhysicalPosition<f64>>,
        context: Context<OwnedDisplayHandle>,
    }

    impl ApplicationHandler for Application {
        fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
            let attributes = WindowAttributes::default()
                .with_title("parent window")
                .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
                .with_surface_size(LogicalSize::new(600.0f32, 600.0f32))
                .with_decorations(true);
            let window = event_loop.create_window(attributes).unwrap();
            self.parent_window_id = Some(window.id());

            if self.main_window.is_none() {
                self.main_window = Some(window.id());
            }
            self.windows.insert(window.id(), WindowData::new(&self.context, window, 0xffbbbbbb));
        }

        fn window_event(
            &mut self,
            event_loop: &dyn ActiveEventLoop,
            window_id: winit::window::WindowId,
            event: WindowEvent,
        ) {
            use winit::keyboard::{KeyCode, PhysicalKey};

            match event {
                WindowEvent::CloseRequested => {
                    self.windows.remove(&window_id);
                    self.popups.retain_mut(|id| id != &window_id);
                    if self.windows.is_empty() {
                        event_loop.exit();
                    }
                },
                WindowEvent::PointerEntered { .. } => {
                    // On x11, println when the cursor entered in a window even if the child window
                    // is created by some key inputs.
                    // the child windows are always placed at (0, 0) with size (200, 200) in the
                    // parent window, so we also can see this log when we move
                    // the cursor around (200, 200) in parent window.
                    info!("cursor entered in the window {window_id:?}");
                },
                WindowEvent::PointerMoved { position, .. } => {
                    let tracked = self.popups.last().copied().or(self.main_window);
                    self.position = (tracked == Some(window_id)).then(|| {
                        info!("Physical position: {position:?}");
                        position
                    });
                },
                WindowEvent::KeyboardInput {
                    event:
                        KeyEvent {
                            state: ElementState::Released,
                            physical_key: PhysicalKey::Code(code),
                            ..
                        },
                    ..
                } => {
                    match code {
                        KeyCode::KeyA => {
                            let window_id = if let Some(popup_id) = self.popups.last() {
                                popup_id
                            } else {
                                // The mainwindow must exist, otherwise the event_loop was ended
                                &self.main_window.unwrap()
                            };

                            // Add a new Popup
                            let child_index = self.windows.len() - 1;
                            let child_color =
                                0xff000000 + 3_u32.pow((child_index + 2).rem_euclid(16) as u32);

                            // Destroy previous item
                            if let Some(popup) = self.popups.pop() {
                                self.windows.remove(&popup);
                            }

                            let parent_window =
                                self.windows.get(&self.main_window.unwrap()).unwrap();
                            let child_window = spawn_popup(
                                parent_window.surface.window().as_ref(),
                                event_loop,
                                child_index,
                                self.position,
                            );
                            let child_id = child_window.id();
                            self.popups.push(child_id);
                            self.windows.insert(
                                child_id,
                                WindowData::new(&self.context, child_window, child_color),
                            );
                        },
                        KeyCode::KeyD => {
                            // Delete
                            if let Some(l) = self.popups.pop() {
                                self.windows.remove(&l);
                            }

                            // // When deleting the first, it should not lead to a wayland protocol
                            // // error
                            // if let Some(l) = self.popups.first() {
                            //     self.windows.remove(&l);
                            // }
                        },
                        _ => (),
                    }
                },
                WindowEvent::RedrawRequested => {
                    if let Some(window) = self.windows.get_mut(&window_id) {
                        if window_id == self.parent_window_id.unwrap() {
                            fill::fill(&mut window.surface);
                        } else {
                            fill::fill_with_color(&mut window.surface, window.color);
                        }
                    }
                },
                _ => (),
            }
        }
    }

    fn spawn_popup(
        parent: &dyn Window,
        event_loop: &dyn ActiveEventLoop,
        _child_count: usize,
        position: Option<PhysicalPosition<f64>>,
    ) -> Box<dyn Window> {
        use winit::dpi::Size;
        use winit::window::{
            WindowAnchor, WindowConstraintAdjustment, WindowGravity, WindowPositioner, WindowType,
        };

        let parent = parent.raw_window_handle().unwrap();

        let mut window_attributes = WindowAttributes::default()
            .with_title("child window")
            .with_surface_size(LogicalSize::new(300.0f32, 300.0))
            .with_decorations(false)
            .with_visible(true)
            .with_active(true) // Grab keyboard
            .with_window_type(WindowType::Popup)
            .with_positioner(WindowPositioner::new(
                WindowAnchor::TopLeft,
                (
                    Position::Physical(position.unwrap_or_default().cast()),
                    Size::Logical(LogicalSize { width: 1., height: 1. }),
                ),
                Position::Logical(LogicalPosition { x: 0., y: 0. }),
                WindowGravity::BottomRight,
                WindowConstraintAdjustment::all(),
            ));

        // `with_parent_window` is unsafe. Parent window must be a valid window.
        window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) };

        event_loop.create_window(window_attributes).unwrap()
    }

    let event_loop = EventLoop::new().unwrap();
    let context = Context::new(event_loop.owned_display_handle()).unwrap();
    event_loop.run_app(Application {
        context,
        parent_window_id: None,
        windows: HashMap::new(),
        popups: Vec::default(),
        position: Default::default(),
        main_window: None,
    })
}

#[cfg(not(any(x11_platform, macos_platform, windows_platform, wayland_platform)))]
fn main() {
    panic!(
        "This example is supported only on wayland, x11, macOS, and Windows, with the `rwh_06` \
         feature enabled."
    );
}

@Murmele
Murmele requested a review from kchibisov as a code owner November 11, 2026 12:52
Reason: When a popup object is removed in the client application it requests a close which was executed the next eventloop run. If in the same eventloop run a new popup with the same parent is tried to open, the creation will fail because there is still a popup open and we cannot open another one with the same parent
@Murmele
Murmele force-pushed the mm/popup-destroy-closed branch from 89c8177 to 1ccab2f Compare November 11, 2026 13:01

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant