diff --git a/crates/adapters/src/rendering/femtovg/popup_window.rs b/crates/adapters/src/rendering/femtovg/popup_window.rs index 9cdb337..b44b844 100644 --- a/crates/adapters/src/rendering/femtovg/popup_window.rs +++ b/crates/adapters/src/rendering/femtovg/popup_window.rs @@ -21,6 +21,8 @@ pub struct PopupWindow { popup_handle: Cell>, on_close: OnceCell, configured: Cell, + repositioning: Cell, + needs_relayout: Cell, component_instance: RefCell>, } @@ -38,6 +40,8 @@ impl PopupWindow { popup_handle: Cell::new(None), on_close: OnceCell::new(), configured: Cell::new(false), + repositioning: Cell::new(false), + needs_relayout: Cell::new(false), component_instance: RefCell::new(None), } }) @@ -113,6 +117,15 @@ impl PopupWindow { self.set_size(WindowSize::Logical(slint::LogicalSize::new(width, height))); RenderableWindow::request_redraw(self); } + + pub fn begin_repositioning(&self) { + self.repositioning.set(true); + } + + pub fn end_repositioning(&self) { + self.repositioning.set(false); + self.needs_relayout.set(true); + } } impl RenderableWindow for PopupWindow { @@ -122,6 +135,11 @@ impl RenderableWindow for PopupWindow { return Ok(()); } + if self.repositioning.get() { + info!("Popup repositioning in progress, skipping render"); + return Ok(()); + } + if matches!( self.render_state.replace(RenderState::Clean), RenderState::Dirty @@ -137,6 +155,12 @@ impl RenderableWindow for PopupWindow { message: format!("Error rendering popup frame: {e}"), })?; info!("Popup frame rendered successfully"); + + if self.needs_relayout.get() { + info!("Popup needs relayout, requesting additional render"); + self.needs_relayout.set(false); + RenderableWindow::request_redraw(self); + } } Ok(()) } diff --git a/crates/adapters/src/wayland/event_handling/app_dispatcher.rs b/crates/adapters/src/wayland/event_handling/app_dispatcher.rs index ec6f60d..7175e49 100644 --- a/crates/adapters/src/wayland/event_handling/app_dispatcher.rs +++ b/crates/adapters/src/wayland/event_handling/app_dispatcher.rs @@ -336,6 +336,17 @@ impl Dispatch for AppState { } xdg_popup::Event::Repositioned { token } => { info!("XdgPopup repositioned with token {token}"); + + let popup_id = xdg_popup.id(); + for window in state.all_outputs_mut() { + if let Some(popup_manager) = window.popup_manager() { + if let Some(handle) = popup_manager.find_by_xdg_popup(&popup_id) { + info!("Committing popup surface after reposition"); + popup_manager.commit_popup_surface(handle.key()); + break; + } + } + } } _ => {} } diff --git a/crates/adapters/src/wayland/surfaces/popup_manager.rs b/crates/adapters/src/wayland/surfaces/popup_manager.rs index d3d4e78..9aa1e4f 100644 --- a/crates/adapters/src/wayland/surfaces/popup_manager.rs +++ b/crates/adapters/src/wayland/surfaces/popup_manager.rs @@ -445,12 +445,22 @@ impl PopupManager { pub fn update_popup_viewport(&self, key: usize, logical_width: i32, logical_height: i32) { let id = PopupId(key); if let Some(popup) = self.state.borrow().popups.get(&id) { + popup.window.begin_repositioning(); popup .surface .update_viewport_size(logical_width, logical_height); } } + pub fn commit_popup_surface(&self, key: usize) { + let id = PopupId(key); + if let Some(popup) = self.state.borrow().popups.get(&id) { + popup.surface.surface.commit(); + popup.window.end_repositioning(); + popup.window.request_redraw(); + } + } + pub fn get_popup_info(&self, key: usize) -> Option<(PopupRequest, u32)> { let id = PopupId(key); self.state diff --git a/crates/adapters/src/wayland/surfaces/popup_surface.rs b/crates/adapters/src/wayland/surfaces/popup_surface.rs index ee72abb..243028f 100644 --- a/crates/adapters/src/wayland/surfaces/popup_surface.rs +++ b/crates/adapters/src/wayland/surfaces/popup_surface.rs @@ -1,7 +1,10 @@ +use layer_shika_domain::dimensions::{LogicalSize as DomainLogicalSize, ScaleFactor}; +use layer_shika_domain::surface_dimensions::SurfaceDimensions; use layer_shika_domain::value_objects::popup_config::PopupConfig; use log::info; use slint::PhysicalSize; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; +use std::cell::Cell; use std::rc::Rc; use wayland_client::{ protocol::{wl_compositor::WlCompositor, wl_seat::WlSeat, wl_surface::WlSurface}, @@ -41,6 +44,10 @@ pub struct PopupSurface { pub xdg_popup: Rc, pub fractional_scale: Option>, pub viewport: Option>, + popup_config: PopupConfig, + xdg_wm_base: Rc, + queue_handle: QueueHandle, + scale_factor: Cell, } impl PopupSurface { @@ -95,6 +102,10 @@ impl PopupSurface { xdg_popup, fractional_scale, viewport, + popup_config: params.popup_config, + xdg_wm_base: Rc::new(params.xdg_wm_base.clone()), + queue_handle: params.queue_handle.clone(), + scale_factor: Cell::new(params.scale_factor), } } @@ -147,10 +158,52 @@ impl PopupSurface { logical_height ); vp.set_destination(logical_width, logical_height); - self.surface.commit(); + + self.reposition_popup(logical_width, logical_height); } } + #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_possible_wrap)] + #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_precision_loss)] + fn reposition_popup(&self, logical_width: i32, logical_height: i32) { + let scale_factor = self.scale_factor.get(); + + let updated_config = PopupConfig::new( + self.popup_config.reference_x(), + self.popup_config.reference_y(), + SurfaceDimensions::from_logical( + DomainLogicalSize::from_raw(logical_width as f32, logical_height as f32), + ScaleFactor::from_raw(scale_factor), + ), + self.popup_config.positioning_mode(), + self.popup_config.output_bounds(), + ); + + let calculated_x = updated_config.calculated_top_left_x() as i32; + let calculated_y = updated_config.calculated_top_left_y() as i32; + + info!( + "Repositioning popup: reference=({}, {}), new_size=({}x{}), new_top_left=({}, {})", + self.popup_config.reference_x(), + self.popup_config.reference_y(), + logical_width, + logical_height, + calculated_x, + calculated_y + ); + + let positioner = self.xdg_wm_base.create_positioner(&self.queue_handle, ()); + positioner.set_anchor_rect(calculated_x, calculated_y, 1, 1); + positioner.set_size(logical_width, logical_height); + positioner.set_anchor(Anchor::TopLeft); + positioner.set_gravity(Gravity::BottomRight); + positioner.set_constraint_adjustment(ConstraintAdjustment::None); + + self.xdg_popup.reposition(&positioner, 0); + } + pub fn destroy(&self) { info!("Destroying popup surface"); self.xdg_popup.destroy(); diff --git a/crates/composition/src/popup_builder.rs b/crates/composition/src/popup_builder.rs index b62ecf4..22c693d 100644 --- a/crates/composition/src/popup_builder.rs +++ b/crates/composition/src/popup_builder.rs @@ -197,6 +197,7 @@ impl<'a> PopupBuilder<'a> { Ok(()) } + #[allow(clippy::too_many_lines)] pub fn bind_anchored(self, trigger_callback: &str, strategy: AnchorStrategy) -> Result<()> { let component_name = self.component.clone(); let grab = self.grab; @@ -245,28 +246,48 @@ impl<'a> PopupBuilder<'a> { anchor_h ); - let mut builder = PopupRequest::builder(component_clone.clone()) - .at(PopupAt::AnchorRect { - x: anchor_x, - y: anchor_y, - w: anchor_w, - h: anchor_h, - }) - .size(PopupSize::Content) - .grab(grab); - - let mode = match strategy { - AnchorStrategy::CenterBottom => PopupPositioningMode::TopCenter, - AnchorStrategy::CenterTop => PopupPositioningMode::BottomCenter, - AnchorStrategy::RightBottom => PopupPositioningMode::TopRight, - AnchorStrategy::LeftTop => PopupPositioningMode::BottomLeft, - AnchorStrategy::RightTop => PopupPositioningMode::BottomRight, - AnchorStrategy::LeftBottom | AnchorStrategy::Cursor => { - PopupPositioningMode::TopLeft + let (reference_x, reference_y, mode) = match strategy { + AnchorStrategy::CenterBottom => { + let center_x = anchor_x + anchor_w / 2.0; + let bottom_y = anchor_y + anchor_h; + (center_x, bottom_y, PopupPositioningMode::TopCenter) } + AnchorStrategy::CenterTop => { + let center_x = anchor_x + anchor_w / 2.0; + (center_x, anchor_y, PopupPositioningMode::BottomCenter) + } + AnchorStrategy::RightBottom => { + let right_x = anchor_x + anchor_w; + let bottom_y = anchor_y + anchor_h; + (right_x, bottom_y, PopupPositioningMode::TopRight) + } + AnchorStrategy::LeftTop => { + (anchor_x, anchor_y, PopupPositioningMode::BottomLeft) + } + AnchorStrategy::RightTop => { + let right_x = anchor_x + anchor_w; + (right_x, anchor_y, PopupPositioningMode::BottomRight) + } + AnchorStrategy::LeftBottom => { + let bottom_y = anchor_y + anchor_h; + (anchor_x, bottom_y, PopupPositioningMode::TopLeft) + } + AnchorStrategy::Cursor => (anchor_x, anchor_y, PopupPositioningMode::TopLeft), }; - builder = builder.mode(mode); + log::debug!( + "Resolved anchored popup reference for '{}' -> ({}, {}), mode: {:?}", + component_clone, + reference_x, + reference_y, + mode + ); + + let mut builder = PopupRequest::builder(component_clone.clone()) + .at(PopupAt::absolute(reference_x, reference_y)) + .size(PopupSize::Content) + .grab(grab) + .mode(mode); if let Some(ref close_cb) = close_cb { builder = builder.close_on(close_cb.clone()); diff --git a/crates/composition/src/system.rs b/crates/composition/src/system.rs index 6d2b64c..2444f76 100644 --- a/crates/composition/src/system.rs +++ b/crates/composition/src/system.rs @@ -356,6 +356,10 @@ impl ShellContext<'_> { popup_key_cell.set(popup_handle.key()); if let Some(popup_window) = popup_manager.get_popup_window(popup_handle.key()) { + if matches!(req.size, PopupSize::Content) { + log::debug!("Marking content-sized popup as repositioning from creation"); + popup_window.begin_repositioning(); + } popup_window.set_component_instance(instance); } else { return Err(Error::Domain(DomainError::Configuration {