fix: anchoring and repositioning popup

This commit is contained in:
drendog 2025-11-30 05:46:55 +01:00
parent 64a18153b6
commit 73f1b52890
Signed by: dwenya
GPG key ID: 8DD77074645332D0
6 changed files with 143 additions and 20 deletions

View file

@ -21,6 +21,8 @@ pub struct PopupWindow {
popup_handle: Cell<Option<PopupHandle>>,
on_close: OnceCell<OnCloseCallback>,
configured: Cell<bool>,
repositioning: Cell<bool>,
needs_relayout: Cell<bool>,
component_instance: RefCell<Option<ComponentInstance>>,
}
@ -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(())
}

View file

@ -336,6 +336,17 @@ impl Dispatch<XdgPopup, ()> 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;
}
}
}
}
_ => {}
}

View file

@ -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

View file

@ -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<XdgPopup>,
pub fractional_scale: Option<Rc<WpFractionalScaleV1>>,
pub viewport: Option<Rc<WpViewport>>,
popup_config: PopupConfig,
xdg_wm_base: Rc<XdgWmBase>,
queue_handle: QueueHandle<AppState>,
scale_factor: Cell<f32>,
}
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();

View file

@ -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());

View file

@ -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 {