fix: popup positioning

This commit is contained in:
drendog 2025-11-04 20:45:35 +01:00
parent bb26ce9e6b
commit f565dd556b
Signed by: dwenya
GPG key ID: 8DD77074645332D0
4 changed files with 137 additions and 27 deletions

View file

@ -179,7 +179,7 @@ impl WaylandWindowingSystem {
let serial = serial_holder.get(); let serial = serial_holder.get();
let params = if let Some((request, width, height)) = popup_manager_clone.take_pending_popup() { let (params, request) = if let Some((request, width, height)) = popup_manager_clone.take_pending_popup() {
log::info!( log::info!(
"Using popup request: component='{}', position=({}, {}), size={}x{}, mode={:?}", "Using popup request: component='{}', position=({}, {}), size={}x{}, mode={:?}",
request.component, request.component,
@ -190,14 +190,15 @@ impl WaylandWindowingSystem {
request.mode request.mode
); );
CreatePopupParams { let params = CreatePopupParams {
last_pointer_serial: serial, last_pointer_serial: serial,
reference_x: request.at.position().0, reference_x: request.at.position().0,
reference_y: request.at.position().1, reference_y: request.at.position().1,
width, width,
height, height,
positioning_mode: request.mode, positioning_mode: request.mode,
} };
(params, request)
} else { } else {
log::warn!("Popup creator called without pending popup request - aborting"); log::warn!("Popup creator called without pending popup request - aborting");
return Err(PlatformError::Other( return Err(PlatformError::Other(
@ -210,6 +211,7 @@ impl WaylandWindowingSystem {
&queue_handle, &queue_handle,
&layer_surface, &layer_surface,
params, params,
request,
) )
.map_err(|e| PlatformError::Other(format!("Failed to create popup: {e}")))?; .map_err(|e| PlatformError::Other(format!("Failed to create popup: {e}")))?;

View file

@ -72,6 +72,7 @@ impl<'a> PopupBuilder<'a> {
self.queue_handle, self.queue_handle,
self.parent_layer_surface, self.parent_layer_surface,
params, params,
request.clone(),
)?; )?;
let instance = Self::create_component_instance(component_def, &popup_window)?; let instance = Self::create_component_instance(component_def, &popup_window)?;

View file

@ -66,6 +66,8 @@ impl PopupContext {
struct ActivePopup { struct ActivePopup {
surface: PopupSurface, surface: PopupSurface,
window: Rc<PopupWindow>, window: Rc<PopupWindow>,
request: PopupRequest,
last_serial: u32,
} }
impl Drop for ActivePopup { impl Drop for ActivePopup {
@ -160,6 +162,7 @@ impl PopupManager {
queue_handle: &QueueHandle<WindowState>, queue_handle: &QueueHandle<WindowState>,
parent_layer_surface: &ZwlrLayerSurfaceV1, parent_layer_surface: &ZwlrLayerSurfaceV1,
params: CreatePopupParams, params: CreatePopupParams,
request: PopupRequest,
) -> Result<Rc<PopupWindow>> { ) -> Result<Rc<PopupWindow>> {
let xdg_wm_base = self.context.xdg_wm_base.as_ref().ok_or_else(|| { let xdg_wm_base = self.context.xdg_wm_base.as_ref().ok_or_else(|| {
LayerShikaError::WindowConfiguration { LayerShikaError::WindowConfiguration {
@ -227,6 +230,8 @@ impl PopupManager {
let key = self.popups.borrow_mut().insert(ActivePopup { let key = self.popups.borrow_mut().insert(ActivePopup {
surface: popup_surface, surface: popup_surface,
window: Rc::clone(&popup_window), window: Rc::clone(&popup_window),
request,
last_serial: params.last_pointer_serial,
}); });
popup_window.set_popup_manager(Rc::downgrade(self), key); popup_window.set_popup_manager(Rc::downgrade(self), key);
*self.current_popup_key.borrow_mut() = Some(key); *self.current_popup_key.borrow_mut() = Some(key);
@ -303,4 +308,11 @@ impl PopupManager {
popup.surface.update_viewport_size(logical_width, logical_height); popup.surface.update_viewport_size(logical_width, logical_height);
} }
} }
pub fn get_popup_info(&self, key: usize) -> Option<(PopupRequest, u32)> {
self.popups
.borrow()
.get(key)
.map(|popup| (popup.request.clone(), popup.last_serial))
}
} }

View file

@ -4,7 +4,7 @@ use layer_shika_adapters::platform::calloop::{
EventSource, Generic, Interest, Mode, PostAction, RegistrationToken, TimeoutAction, Timer, EventSource, Generic, Interest, Mode, PostAction, RegistrationToken, TimeoutAction, Timer,
channel, channel,
}; };
use layer_shika_adapters::platform::slint::ComponentHandle; use layer_shika_adapters::platform::slint::{ComponentHandle, SharedString};
use layer_shika_adapters::platform::slint_interpreter::{ use layer_shika_adapters::platform::slint_interpreter::{
CompilationResult, ComponentDefinition, ComponentInstance, Value, CompilationResult, ComponentDefinition, ComponentInstance, Value,
}; };
@ -28,6 +28,11 @@ use std::time::{Duration, Instant};
enum PopupCommand { enum PopupCommand {
Show(PopupRequest), Show(PopupRequest),
Close(PopupHandle), Close(PopupHandle),
Resize {
key: usize,
width: f32,
height: f32,
},
} }
pub struct EventLoopHandle { pub struct EventLoopHandle {
@ -137,7 +142,7 @@ impl RuntimeState<'_> {
self.window_state.compilation_result() self.window_state.compilation_result()
} }
pub fn show_popup(&mut self, req: PopupRequest) -> Result<PopupHandle> { pub fn show_popup(&mut self, req: PopupRequest, resize_sender: Option<channel::Sender<PopupCommand>>) -> Result<PopupHandle> {
let compilation_result = self.compilation_result().ok_or_else(|| { let compilation_result = self.compilation_result().ok_or_else(|| {
Error::Domain(DomainError::Configuration { Error::Domain(DomainError::Configuration {
message: "No compilation result available for popup creation".to_string(), message: "No compilation result available for popup creation".to_string(),
@ -175,7 +180,7 @@ impl RuntimeState<'_> {
} }
PopupSize::Content => { PopupSize::Content => {
log::debug!("Using content-based sizing - will measure after instance creation"); log::debug!("Using content-based sizing - will measure after instance creation");
(600.0, 300.0) (2.0, 2.0)
} }
}; };
@ -191,7 +196,7 @@ impl RuntimeState<'_> {
popup_manager.set_pending_popup(req, initial_dimensions.0, initial_dimensions.1); popup_manager.set_pending_popup(req, initial_dimensions.0, initial_dimensions.1);
let instance = Self::create_popup_instance(&definition, &popup_manager, 0)?; let instance = Self::create_popup_instance(&definition, &popup_manager, 0, resize_sender)?;
let popup_key = popup_manager.current_popup_key().ok_or_else(|| { let popup_key = popup_manager.current_popup_key().ok_or_else(|| {
Error::Domain(DomainError::Configuration { Error::Domain(DomainError::Configuration {
@ -224,10 +229,62 @@ impl RuntimeState<'_> {
Ok(()) Ok(())
} }
pub fn resize_popup(&mut self, key: usize, width: f32, height: f32, resize_sender: Option<channel::Sender<PopupCommand>>) -> Result<()> {
let popup_manager = self
.window_state
.popup_manager()
.as_ref()
.ok_or_else(|| {
Error::Domain(DomainError::Configuration {
message: "No popup manager available".to_string(),
})
})
.map(Rc::clone)?;
let (request, _serial) = popup_manager.get_popup_info(key).ok_or_else(|| {
Error::Domain(DomainError::Configuration {
message: format!("Popup with key {} not found", key),
})
})?;
let current_size = request.size.dimensions();
let size_changed = current_size.map_or(true, |(w, h)| {
(w - width).abs() > 0.01 || (h - height).abs() > 0.01
});
let needs_repositioning = request.mode.center_x() || request.mode.center_y();
if needs_repositioning && size_changed {
log::info!(
"Popup needs repositioning due to mode {:?} and size change - recreating with new size {}x{}",
request.mode,
width,
height
);
self.close_popup(PopupHandle::new(key))?;
let new_request = PopupRequest::builder(request.component)
.at(request.at)
.size(PopupSize::fixed(width, height))
.mode(request.mode)
.build();
self.show_popup(new_request, resize_sender)?;
} else if size_changed {
if let Some(popup_window) = popup_manager.get_popup_window(key) {
popup_window.request_resize(width, height);
}
}
Ok(())
}
fn create_popup_instance( fn create_popup_instance(
definition: &ComponentDefinition, definition: &ComponentDefinition,
popup_manager: &Rc<PopupManager>, popup_manager: &Rc<PopupManager>,
popup_key: usize, popup_key: usize,
resize_sender: Option<channel::Sender<PopupCommand>>,
) -> Result<ComponentInstance> { ) -> Result<ComponentInstance> {
let instance = definition.create().map_err(|e| { let instance = definition.create().map_err(|e| {
Error::Domain(DomainError::Configuration { Error::Domain(DomainError::Configuration {
@ -249,26 +306,54 @@ impl RuntimeState<'_> {
}) })
})?; })?;
let popup_manager_for_resize = Rc::downgrade(popup_manager); let result = if let Some(sender) = resize_sender {
let result = instance.set_callback("change_popup_size", move |args| { instance.set_callback("change_popup_size", move |args| {
let width: f32 = args let width: f32 = args
.first() .first()
.and_then(|v| v.clone().try_into().ok()) .and_then(|v| v.clone().try_into().ok())
.unwrap_or(200.0); .unwrap_or(200.0);
let height: f32 = args let height: f32 = args
.get(1) .get(1)
.and_then(|v| v.clone().try_into().ok()) .and_then(|v| v.clone().try_into().ok())
.unwrap_or(150.0); .unwrap_or(150.0);
log::info!("change_popup_size callback invoked: {}x{}", width, height); log::info!("change_popup_size callback invoked: {}x{}", width, height);
if let Some(popup_mgr) = popup_manager_for_resize.upgrade() { if sender
if let Some(popup_window) = popup_mgr.get_popup_window(popup_key) { .send(PopupCommand::Resize {
key: popup_key,
width,
height,
})
.is_err()
{
log::error!("Failed to send popup resize command through channel");
}
Value::Void
})
} else {
let popup_manager_for_resize = Rc::downgrade(popup_manager);
instance.set_callback("change_popup_size", move |args| {
let width: f32 = args
.first()
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(200.0);
let height: f32 = args
.get(1)
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(150.0);
log::info!("change_popup_size callback invoked: {}x{}", width, height);
if let Some(popup_window) = popup_manager_for_resize
.upgrade()
.and_then(|mgr| mgr.get_popup_window(popup_key))
{
popup_window.request_resize(width, height); popup_window.request_resize(width, height);
} }
} Value::Void
Value::Void })
}); };
if let Err(e) = result { if let Err(e) = result {
log::warn!("Failed to set change_popup_size callback: {}", e); log::warn!("Failed to set change_popup_size callback: {}", e);
@ -322,15 +407,16 @@ impl WindowingSystem {
fn setup_popup_command_handler(&self, receiver: channel::Channel<PopupCommand>) -> Result<()> { fn setup_popup_command_handler(&self, receiver: channel::Channel<PopupCommand>) -> Result<()> {
let loop_handle = self.inner.borrow().event_loop_handle(); let loop_handle = self.inner.borrow().event_loop_handle();
let sender_for_handler = self.popup_command_sender.clone();
loop_handle loop_handle
.insert_source(receiver, |event, (), window_state| { .insert_source(receiver, move |event, (), window_state| {
if let channel::Event::Msg(command) = event { if let channel::Event::Msg(command) = event {
let mut runtime_state = RuntimeState { window_state }; let mut runtime_state = RuntimeState { window_state };
match command { match command {
PopupCommand::Show(request) => { PopupCommand::Show(request) => {
if let Err(e) = runtime_state.show_popup(request) { if let Err(e) = runtime_state.show_popup(request, Some(sender_for_handler.clone())) {
log::error!("Failed to show popup: {}", e); log::error!("Failed to show popup: {}", e);
} }
} }
@ -339,6 +425,11 @@ impl WindowingSystem {
log::error!("Failed to close popup: {}", e); log::error!("Failed to close popup: {}", e);
} }
} }
PopupCommand::Resize { key, width, height } => {
if let Err(e) = runtime_state.resize_popup(key, width, height, Some(sender_for_handler.clone())) {
log::error!("Failed to resize popup: {}", e);
}
}
} }
} }
}) })
@ -371,6 +462,12 @@ impl WindowingSystem {
let mode = PopupPositioningMode::from_flags(center_x, center_y); let mode = PopupPositioningMode::from_flags(center_x, center_y);
*popup_mode_clone.borrow_mut() = mode; *popup_mode_clone.borrow_mut() = mode;
log::info!(
"Popup positioning mode set to: {:?} (center_x: {}, center_y: {})",
mode,
center_x,
center_y
);
Value::Void Value::Void
}) })
.map_err(|e| { .map_err(|e| {
@ -387,8 +484,6 @@ impl WindowingSystem {
component_instance component_instance
.set_callback("show_popup", move |args| { .set_callback("show_popup", move |args| {
use layer_shika_adapters::platform::slint::SharedString;
let component_name: SharedString = args let component_name: SharedString = args
.first() .first()
.and_then(|v| v.clone().try_into().ok()) .and_then(|v| v.clone().try_into().ok())