refactor: popup lifecycle and add more docs

This commit is contained in:
drendog 2025-12-15 02:34:50 +01:00
parent 95fb71dfb8
commit fefb5a4ef3
Signed by: dwenya
GPG key ID: 8DD77074645332D0
10 changed files with 329 additions and 306 deletions

View file

@ -12,6 +12,21 @@ use slint_interpreter::ComponentInstance;
use std::cell::{Cell, OnceCell, RefCell};
use std::rc::{Rc, Weak};
/// Represents the rendering lifecycle state of a popup window
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PopupRenderState {
/// Awaiting Wayland configure event before rendering can begin
Unconfigured,
/// Wayland is recalculating geometry; rendering is paused
Repositioning,
/// Ready to render, no pending changes
ReadyClean,
/// Ready to render, frame is dirty and needs redraw
ReadyDirty,
/// Needs an additional layout pass after the next render
NeedsRelayout,
}
pub struct PopupWindow {
window: Window,
renderer: FemtoVGRenderer,
@ -20,9 +35,7 @@ pub struct PopupWindow {
scale_factor: Cell<f32>,
popup_handle: Cell<Option<PopupHandle>>,
on_close: OnceCell<OnCloseCallback>,
configured: Cell<bool>,
repositioning: Cell<bool>,
needs_relayout: Cell<bool>,
popup_render_state: Cell<PopupRenderState>,
component_instance: RefCell<Option<ComponentInstance>>,
}
@ -39,9 +52,7 @@ impl PopupWindow {
scale_factor: Cell::new(1.),
popup_handle: Cell::new(None),
on_close: OnceCell::new(),
configured: Cell::new(false),
repositioning: Cell::new(false),
needs_relayout: Cell::new(false),
popup_render_state: Cell::new(PopupRenderState::Unconfigured),
component_instance: RefCell::new(None),
}
})
@ -96,11 +107,26 @@ impl PopupWindow {
pub fn mark_configured(&self) {
info!("Popup window marked as configured");
self.configured.set(true);
if matches!(
self.popup_render_state.get(),
PopupRenderState::Unconfigured
) {
info!("Transitioning from Unconfigured to ReadyDirty state");
self.popup_render_state.set(PopupRenderState::ReadyDirty);
} else {
info!(
"Preserving current render state to avoid overwriting: {:?}",
self.popup_render_state.get()
);
}
}
pub fn is_configured(&self) -> bool {
self.configured.get()
!matches!(
self.popup_render_state.get(),
PopupRenderState::Unconfigured
)
}
pub fn set_component_instance(&self, instance: ComponentInstance) {
@ -110,6 +136,9 @@ impl PopupWindow {
info!("Component instance already set for popup window - replacing");
}
*comp = Some(instance);
self.window()
.dispatch_event(WindowEvent::WindowActiveChanged(true));
}
pub fn request_resize(&self, width: f32, height: f32) {
@ -119,26 +148,33 @@ impl PopupWindow {
}
pub fn begin_repositioning(&self) {
self.repositioning.set(true);
self.popup_render_state.set(PopupRenderState::Repositioning);
}
pub fn end_repositioning(&self) {
self.repositioning.set(false);
self.needs_relayout.set(true);
self.popup_render_state.set(PopupRenderState::NeedsRelayout);
}
}
impl RenderableWindow for PopupWindow {
fn render_frame_if_dirty(&self) -> Result<()> {
if !self.configured.get() {
match self.popup_render_state.get() {
PopupRenderState::Unconfigured => {
info!("Popup not yet configured, skipping render");
return Ok(());
}
if self.repositioning.get() {
PopupRenderState::Repositioning => {
info!("Popup repositioning in progress, skipping render");
return Ok(());
}
PopupRenderState::ReadyClean => {
// Nothing to render
return Ok(());
}
PopupRenderState::ReadyDirty | PopupRenderState::NeedsRelayout => {
// Proceed with rendering
}
}
if matches!(
self.render_state.replace(RenderState::Clean),
@ -156,10 +192,15 @@ impl RenderableWindow for PopupWindow {
})?;
info!("Popup frame rendered successfully");
if self.needs_relayout.get() {
if matches!(
self.popup_render_state.get(),
PopupRenderState::NeedsRelayout
) {
info!("Popup needs relayout, requesting additional render");
self.needs_relayout.set(false);
self.popup_render_state.set(PopupRenderState::ReadyDirty);
RenderableWindow::request_redraw(self);
} else {
self.popup_render_state.set(PopupRenderState::ReadyClean);
}
}
Ok(())
@ -203,6 +244,9 @@ impl WindowAdapter for PopupWindow {
}
fn request_redraw(&self) {
if matches!(self.popup_render_state.get(), PopupRenderState::ReadyClean) {
self.popup_render_state.set(PopupRenderState::ReadyDirty);
}
RenderableWindow::request_redraw(self);
}
}

View file

@ -142,15 +142,29 @@ impl EventContext {
self.active_surface = ActiveWindow::None;
}
pub const fn is_popup_active(&self) -> bool {
matches!(self.active_surface, ActiveWindow::Popup(_))
}
pub fn dispatch_to_active_window(&self, event: WindowEvent) {
match self.active_surface {
ActiveWindow::Main => {
self.main_window.window().dispatch_event(event);
}
ActiveWindow::Popup(handle) => {
let is_pointer_event = matches!(
event,
WindowEvent::PointerMoved { .. }
| WindowEvent::PointerPressed { .. }
| WindowEvent::PointerReleased { .. }
);
if let Some(popup_manager) = &self.popup_manager {
if let Some(popup_surface) = popup_manager.get_popup_window(handle.key()) {
popup_surface.dispatch_event(event);
if is_pointer_event {
popup_surface.request_redraw();
}
}
}
}

View file

@ -109,6 +109,8 @@ struct ActivePopup {
impl Drop for ActivePopup {
fn drop(&mut self) {
info!("ActivePopup being dropped - cleaning up resources");
self.window.cleanup_resources();
self.surface.destroy();
}
}
@ -424,10 +426,9 @@ impl PopupManager {
}
fn destroy_popup(&self, id: PopupId) {
if let Some(popup) = self.state.borrow_mut().popups.remove(&id) {
if let Some(_popup) = self.state.borrow_mut().popups.remove(&id) {
info!("Destroying popup with id {:?}", id);
popup.window.cleanup_resources();
popup.surface.destroy();
// cleanup happens automatically via ActivePopup::drop()
}
}

View file

@ -264,6 +264,10 @@ impl SurfaceState {
self.event_context.borrow_mut().clear_entered_surface();
}
pub fn is_popup_active(&self) -> bool {
self.event_context.borrow().is_popup_active()
}
pub fn dispatch_to_active_window(&self, event: WindowEvent) {
self.event_context.borrow().dispatch_to_active_window(event);
}

View file

@ -451,7 +451,7 @@ impl Runtime {
match command {
PopupCommand::Show(request) => {
if let Err(e) = ctx.show_popup(&request, Some(control.clone())) {
if let Err(e) = ctx.show_popup(&request) {
log::error!("Failed to show popup: {}", e);
}
}
@ -582,11 +582,6 @@ impl Runtime {
&self.compilation_result
}
#[must_use]
pub fn popup(&self, component_name: impl Into<String>) -> PopupBuilder<'_> {
PopupBuilder::new(self, component_name.into())
}
pub fn on<F, R>(&self, surface_name: &str, callback_name: &str, handler: F) -> Result<()>
where
F: Fn(ShellControl) -> R + 'static,

View file

@ -1,15 +1,26 @@
use crate::Result;
use crate::shell::Shell;
use layer_shika_adapters::platform::slint_interpreter::Value;
use layer_shika_domain::prelude::AnchorStrategy;
use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode;
use layer_shika_domain::value_objects::popup_request::{PopupPlacement, PopupRequest, PopupSize};
/// Builder for configuring and displaying popup windows
/// Builder for configuring popup windows
///
/// Useful for context menus, tooltips, dropdowns, and other transient UI.
pub struct PopupBuilder<'a> {
shell: &'a Shell,
/// This is a convenience wrapper around `PopupRequest::builder()` that provides
/// a fluent API for configuring popups. Once built, pass the resulting `PopupRequest`
/// to `ShellControl::show_popup()` from within a callback.
///
/// # Example
/// ```rust,ignore
/// shell.on("Main", "open_menu", |control| {
/// let request = PopupBuilder::new("MenuPopup")
/// .relative_to_cursor()
/// .anchor_top_left()
/// .grab(true)
/// .close_on("menu_closed")
/// .build();
///
/// control.show_popup(&request)?;
/// });
/// ```
pub struct PopupBuilder {
component: String,
reference: PopupPlacement,
anchor: PopupPositioningMode,
@ -19,11 +30,12 @@ pub struct PopupBuilder<'a> {
resize_callback: Option<String>,
}
impl<'a> PopupBuilder<'a> {
pub(crate) fn new(shell: &'a Shell, component: String) -> Self {
impl PopupBuilder {
/// Creates a new popup builder for the specified component
#[must_use]
pub fn new(component: impl Into<String>) -> Self {
Self {
shell,
component,
component: component.into(),
reference: PopupPlacement::AtCursor,
anchor: PopupPositioningMode::TopLeft,
size: PopupSize::Content,
@ -168,181 +180,11 @@ impl<'a> PopupBuilder<'a> {
self
}
/// Binds the popup to show when the specified Slint callback is triggered
pub fn bind(self, trigger_callback: &str) -> Result<()> {
let request = self.build_request();
let control = self.shell.control();
self.shell.with_all_surfaces(|_name, instance| {
let request_clone = request.clone();
let control_clone = control.clone();
if let Err(e) = instance.set_callback(trigger_callback, move |_args| {
if let Err(e) = control_clone.show_popup(&request_clone) {
log::error!("Failed to show popup: {}", e);
}
Value::Void
}) {
log::error!(
"Failed to bind popup callback '{}': {}",
trigger_callback,
e
);
}
});
Ok(())
}
/// Binds the popup to toggle visibility when the specified callback is triggered
pub fn toggle(self, trigger_callback: &str) -> Result<()> {
let request = self.build_request();
let control = self.shell.control();
let component_name = request.component.clone();
self.shell.with_all_surfaces(|_name, instance| {
let request_clone = request.clone();
let control_clone = control.clone();
let component_clone = component_name.clone();
if let Err(e) = instance.set_callback(trigger_callback, move |_args| {
log::debug!("Toggle callback for component: {}", component_clone);
if let Err(e) = control_clone.show_popup(&request_clone) {
log::error!("Failed to toggle popup: {}", e);
}
Value::Void
}) {
log::error!(
"Failed to bind toggle popup callback '{}': {}",
trigger_callback,
e
);
}
});
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;
let close_callback = self.close_callback.clone();
let resize_callback = self.resize_callback.clone();
let control = self.shell.control();
self.shell.with_all_surfaces(|_name, instance| {
let component_clone = component_name.clone();
let control_clone = control.clone();
let close_cb = close_callback.clone();
let resize_cb = resize_callback.clone();
if let Err(e) = instance.set_callback(trigger_callback, move |args| {
if args.len() < 4 {
log::error!(
"bind_anchored callback expects 4 arguments (x, y, width, height), got {}",
args.len()
);
return Value::Void;
}
let anchor_x = args
.first()
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(0.0);
let anchor_y = args
.get(1)
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(0.0);
let anchor_w = args
.get(2)
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(0.0);
let anchor_h = args
.get(3)
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(0.0);
log::debug!(
"Anchored popup triggered for '{}' at rect: ({}, {}, {}, {})",
component_clone,
anchor_x,
anchor_y,
anchor_w,
anchor_h
);
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),
};
log::debug!(
"Resolved anchored popup reference for '{}' -> ({}, {}), mode: {:?}",
component_clone,
reference_x,
reference_y,
mode
);
let mut builder = PopupRequest::builder(component_clone.clone())
.placement(PopupPlacement::at_position(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());
}
if let Some(ref resize_cb) = resize_cb {
builder = builder.resize_on(resize_cb.clone());
}
let request = builder.build();
if let Err(e) = control_clone.show_popup(&request) {
log::error!("Failed to show anchored popup: {}", e);
}
Value::Void
}) {
log::error!(
"Failed to bind anchored popup callback '{}': {}",
trigger_callback,
e
);
}
});
Ok(())
}
fn build_request(&self) -> PopupRequest {
/// Builds the popup request
///
/// After building, pass the request to `ShellControl::show_popup()` to display the popup.
#[must_use]
pub fn build(self) -> PopupRequest {
let mut builder = PopupRequest::builder(self.component.clone())
.placement(self.reference)
.size(self.size)

View file

@ -1,6 +1,5 @@
use crate::event_loop::{EventLoopHandle, FromAppState};
use crate::layer_surface::LayerSurfaceHandle;
use crate::popup_builder::PopupBuilder;
use crate::shell_config::{CompiledUiSource, ShellConfig};
use crate::shell_runtime::ShellRuntime;
use crate::surface_registry::{SurfaceDefinition, SurfaceEntry, SurfaceRegistry};
@ -580,11 +579,11 @@ impl Shell {
fn handle_popup_command(
command: PopupCommand,
ctx: &mut EventDispatchContext<'_>,
control: &ShellControl,
_control: &ShellControl,
) {
match command {
PopupCommand::Show(request) => {
if let Err(e) = ctx.show_popup(&request, Some(control.clone())) {
if let Err(e) = ctx.show_popup(&request) {
log::error!("Failed to show popup: {}", e);
}
}
@ -963,12 +962,6 @@ impl Shell {
&self.compilation_result
}
/// Creates a popup builder for showing a popup window
#[must_use]
pub fn popup(&self, component_name: impl Into<String>) -> PopupBuilder<'_> {
PopupBuilder::new(self, component_name.into())
}
/// Returns the registry of all connected outputs
pub fn output_registry(&self) -> OutputRegistry {
let system = self.inner.borrow();

View file

@ -156,6 +156,59 @@ impl CallbackContext {
self.control
.surface_by_name_and_output(&self.surface_name, self.output_handle())
}
/// Shows a popup from a popup request
///
/// Convenience method that forwards to the underlying `ShellControl`.
/// See [`ShellControl::show_popup`] for full documentation.
pub fn show_popup(&self, request: &PopupRequest) -> Result<()> {
self.control.show_popup(request)
}
/// Shows a popup at the current cursor position
///
/// Convenience method that forwards to the underlying `ShellControl`.
/// See [`ShellControl::show_popup_at_cursor`] for full documentation.
pub fn show_popup_at_cursor(&self, component: impl Into<String>) -> Result<()> {
self.control.show_popup_at_cursor(component)
}
/// Shows a popup centered on screen
///
/// Convenience method that forwards to the underlying `ShellControl`.
/// See [`ShellControl::show_popup_centered`] for full documentation.
pub fn show_popup_centered(&self, component: impl Into<String>) -> Result<()> {
self.control.show_popup_centered(component)
}
/// Shows a popup at the specified absolute position
///
/// Convenience method that forwards to the underlying `ShellControl`.
/// See [`ShellControl::show_popup_at_position`] for full documentation.
pub fn show_popup_at_position(
&self,
component: impl Into<String>,
x: f32,
y: f32,
) -> Result<()> {
self.control.show_popup_at_position(component, x, y)
}
/// Closes a specific popup by its handle
///
/// Convenience method that forwards to the underlying `ShellControl`.
/// See [`ShellControl::close_popup`] for full documentation.
pub fn close_popup(&self, handle: PopupHandle) -> Result<()> {
self.control.close_popup(handle)
}
/// Resizes a popup to the specified dimensions
///
/// Convenience method that forwards to the underlying `ShellControl`.
/// See [`ShellControl::resize_popup`] for full documentation.
pub fn resize_popup(&self, handle: PopupHandle, width: f32, height: f32) -> Result<()> {
self.control.resize_popup(handle, width, height)
}
}
/// Handle for runtime control of shell operations
@ -172,6 +225,41 @@ impl ShellControl {
}
/// Shows a popup from a popup request
///
/// This is the primary API for showing popups from Slint callbacks. Popups are
/// transient windows that appear above the main surface, commonly used for menus,
/// tooltips, dropdowns, and other temporary UI elements.
///
/// # Content-Based Sizing
///
/// When using `PopupSize::Content`, you must configure a resize callback via
/// `resize_on()` to enable automatic resizing. The popup component should use a
/// `Timer` with `interval: 1ms` to invoke the resize callback after initialization,
/// ensuring the component is initialized before callback invocation. This allows the
/// popup to reposition itself to fit the content. See the `popup-demo` example.
///
/// # Example
///
/// ```rust,ignore
/// shell.on("Main", "open_menu", |control| {
/// let request = PopupRequest::builder("MenuPopup")
/// .placement(PopupPlacement::at_cursor())
/// .grab(true)
/// .close_on("menu_closed")
/// .build();
///
/// control.show_popup(&request)?;
/// Value::Void
/// });
/// ```
///
/// # See Also
///
/// - [`show_popup_at_cursor`](Self::show_popup_at_cursor) - Convenience method for cursor-positioned popups
/// - [`show_popup_centered`](Self::show_popup_centered) - Convenience method for centered popups
/// - [`show_popup_at_position`](Self::show_popup_at_position) - Convenience method for absolute positioning
/// - [`PopupRequest`] - Full popup configuration options
/// - [`PopupBuilder`] - Fluent API for building popup requests
pub fn show_popup(&self, request: &PopupRequest) -> Result<()> {
self.sender
.send(ShellCommand::Popup(PopupCommand::Show(request.clone())))
@ -183,6 +271,19 @@ impl ShellControl {
}
/// Shows a popup at the current cursor position
///
/// Convenience method for showing a popup at the cursor with default settings.
/// For more control over popup positioning, sizing, and behavior, use
/// [`show_popup`](Self::show_popup) with a [`PopupRequest`].
///
/// # Example
///
/// ```rust,ignore
/// shell.on("Main", "context_menu", |control| {
/// control.show_popup_at_cursor("ContextMenu")?;
/// Value::Void
/// });
/// ```
pub fn show_popup_at_cursor(&self, component: impl Into<String>) -> Result<()> {
let request = PopupRequest::builder(component.into())
.placement(PopupPlacement::AtCursor)
@ -191,6 +292,18 @@ impl ShellControl {
}
/// Shows a popup centered on screen
///
/// Convenience method for showing a centered popup. Useful for dialogs
/// and modal content that should appear in the middle of the screen.
///
/// # Example
///
/// ```rust,ignore
/// shell.on("Main", "show_dialog", |control| {
/// control.show_popup_centered("ConfirmDialog")?;
/// Value::Void
/// });
/// ```
pub fn show_popup_centered(&self, component: impl Into<String>) -> Result<()> {
let request = PopupRequest::builder(component.into())
.placement(PopupPlacement::AtCursor)
@ -199,7 +312,19 @@ impl ShellControl {
self.show_popup(&request)
}
/// Shows a popup at the specified position
/// Shows a popup at the specified absolute position
///
/// Convenience method for showing a popup at an exact screen coordinate.
/// The position is in logical pixels relative to the surface origin.
///
/// # Example
///
/// ```rust,ignore
/// shell.on("Main", "show_tooltip", |control| {
/// control.show_popup_at_position("Tooltip", 100.0, 50.0)?;
/// Value::Void
/// });
/// ```
pub fn show_popup_at_position(
&self,
component: impl Into<String>,
@ -212,7 +337,23 @@ impl ShellControl {
self.show_popup(&request)
}
/// Closes a popup by its handle
/// Closes a specific popup by its handle
///
/// Use this when you need to close a specific popup that you opened previously.
/// The handle is returned by [`show_popup`](Self::show_popup) and related methods.
///
/// For closing popups from within the popup itself, consider using the
/// `close_on` callback configuration in [`PopupRequest`] instead.
///
/// # Example
///
/// ```rust,ignore
/// // Store handle when showing popup
/// let handle = context.show_popup(&request)?;
///
/// // Later, close it
/// control.close_popup(handle)?;
/// ```
pub fn close_popup(&self, handle: PopupHandle) -> Result<()> {
self.sender
.send(ShellCommand::Popup(PopupCommand::Close(handle)))
@ -224,6 +365,22 @@ impl ShellControl {
}
/// Resizes a popup to the specified dimensions
///
/// Dynamically changes the size of an active popup. This is typically used
/// in response to content changes or user interaction.
///
/// For automatic content-based sizing, use `PopupSize::Content` with the
/// `resize_on` callback configuration in [`PopupRequest`] instead.
///
/// # Example
///
/// ```rust,ignore
/// shell.on("Main", "expand_menu", |control| {
/// // Assuming we have the popup handle stored somewhere
/// control.resize_popup(menu_handle, 400.0, 600.0)?;
/// Value::Void
/// });
/// ```
pub fn resize_popup(&self, handle: PopupHandle, width: f32, height: f32) -> Result<()> {
self.sender
.send(ShellCommand::Popup(PopupCommand::Resize {
@ -722,11 +879,11 @@ impl EventDispatchContext<'_> {
}
/// Shows a popup from a popup request
pub fn show_popup(
&mut self,
req: &PopupRequest,
resize_control: Option<ShellControl>,
) -> Result<PopupHandle> {
///
/// Resize callbacks (if configured via `resize_on()`) will operate directly
/// on the popup manager for immediate updates.
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn show_popup(&mut self, req: &PopupRequest) -> Result<PopupHandle> {
log::info!("show_popup called for component '{}'", req.component);
let compilation_result = self.compilation_result().ok_or_else(|| {
@ -779,32 +936,67 @@ impl EventDispatchContext<'_> {
})
})?;
// For content-based sizing, we need to query the component's preferred size first
let initial_dimensions = match req.size {
PopupSize::Fixed { w, h } => {
log::debug!("Using fixed popup size: {}x{}", w, h);
(w, h)
}
PopupSize::Content => {
log::debug!("Using content-based sizing - will measure after instance creation");
log::debug!("Using content-based sizing - starting at 2×2");
// Start with minimal size. Consumer app should register a callback to
// call resize_popup() with the desired dimensions.
(2.0, 2.0)
}
};
let resolved_placement = match req.placement {
PopupPlacement::AtCursor => {
let cursor_pos = active_surface.current_pointer_position();
log::debug!(
"Resolving AtCursor placement to actual cursor position: ({}, {})",
cursor_pos.x,
cursor_pos.y
);
PopupPlacement::AtPosition {
x: cursor_pos.x,
y: cursor_pos.y,
}
}
other => other,
};
let (ref_x, ref_y) = resolved_placement.position();
log::debug!(
"Creating popup for '{}' with dimensions {}x{} at position ({}, {}), mode: {:?}",
req.component,
initial_dimensions.0,
initial_dimensions.1,
req.placement.position().0,
req.placement.position().1,
ref_x,
ref_y,
req.mode
);
let popup_handle =
popup_manager.request_popup(req.clone(), initial_dimensions.0, initial_dimensions.1);
// Create a new request with resolved placement
let resolved_request = PopupRequest {
component: req.component.clone(),
placement: resolved_placement,
size: req.size,
mode: req.mode,
grab: req.grab,
close_callback: req.close_callback.clone(),
resize_callback: req.resize_callback.clone(),
};
let popup_handle = popup_manager.request_popup(
resolved_request,
initial_dimensions.0,
initial_dimensions.1,
);
let (instance, popup_key_cell) =
Self::create_popup_instance(&definition, &popup_manager, resize_control, req)?;
Self::create_popup_instance(&definition, &popup_manager, req)?;
popup_key_cell.set(popup_handle.key());
@ -894,7 +1086,6 @@ impl EventDispatchContext<'_> {
fn create_popup_instance(
definition: &ComponentDefinition,
popup_manager: &Rc<PopupManager>,
resize_control: Option<ShellControl>,
req: &PopupRequest,
) -> Result<(ComponentInstance, Rc<Cell<usize>>)> {
let instance = definition.create().map_err(|e| {
@ -905,13 +1096,7 @@ impl EventDispatchContext<'_> {
let popup_key_cell = Rc::new(Cell::new(0));
Self::register_popup_callbacks(
&instance,
popup_manager,
resize_control,
&popup_key_cell,
req,
)?;
Self::register_popup_callbacks(&instance, popup_manager, &popup_key_cell, req)?;
instance.show().map_err(|e| {
Error::Domain(DomainError::Configuration {
@ -925,7 +1110,6 @@ impl EventDispatchContext<'_> {
fn register_popup_callbacks(
instance: &ComponentInstance,
popup_manager: &Rc<PopupManager>,
resize_control: Option<ShellControl>,
popup_key_cell: &Rc<Cell<usize>>,
req: &PopupRequest,
) -> Result<()> {
@ -934,10 +1118,9 @@ impl EventDispatchContext<'_> {
}
if let Some(resize_callback_name) = &req.resize_callback {
Self::register_resize_callback(
Self::register_resize_direct(
instance,
popup_manager,
resize_control,
popup_key_cell,
resize_callback_name,
)?;
@ -966,59 +1149,6 @@ impl EventDispatchContext<'_> {
})
}
fn register_resize_callback(
instance: &ComponentInstance,
popup_manager: &Rc<PopupManager>,
resize_control: Option<ShellControl>,
popup_key_cell: &Rc<Cell<usize>>,
callback_name: &str,
) -> Result<()> {
if let Some(control) = resize_control {
Self::register_resize_with_control(instance, popup_key_cell, &control, callback_name)
} else {
Self::register_resize_direct(instance, popup_manager, popup_key_cell, callback_name)
}
}
fn register_resize_with_control(
instance: &ComponentInstance,
popup_key_cell: &Rc<Cell<usize>>,
control: &ShellControl,
callback_name: &str,
) -> Result<()> {
let key_cell = Rc::clone(popup_key_cell);
let control = control.clone();
instance
.set_callback(callback_name, move |args| {
let dimensions = extract_dimensions_from_callback(args);
let popup_key = key_cell.get();
log::info!(
"Resize callback invoked: {}x{} for key {}",
dimensions.width,
dimensions.height,
popup_key
);
if control
.resize_popup(
PopupHandle::from_raw(popup_key),
dimensions.width,
dimensions.height,
)
.is_err()
{
log::error!("Failed to resize popup through control");
}
Value::Void
})
.map_err(|e| {
Error::Domain(DomainError::Configuration {
message: format!("Failed to set '{}' callback: {}", callback_name, e),
})
})
}
fn register_resize_direct(
instance: &ComponentInstance,
popup_manager: &Rc<PopupManager>,

View file

@ -143,7 +143,7 @@ pub use shell::{
};
pub use window::{
AnchorEdges, AnchorStrategy, KeyboardInteractivity, Layer, PopupHandle, PopupPlacement,
AnchorEdges, AnchorStrategy, KeyboardInteractivity, Layer, PopupBuilder, PopupHandle, PopupPlacement,
PopupPositioningMode, PopupRequest, PopupSize,
};

View file

@ -1,4 +1,4 @@
pub use layer_shika_composition::{
AnchorEdges, AnchorStrategy, KeyboardInteractivity, Layer, PopupHandle, PopupPlacement,
PopupPositioningMode, PopupRequest, PopupSize,
AnchorEdges, AnchorStrategy, KeyboardInteractivity, Layer, PopupBuilder, PopupHandle,
PopupPlacement, PopupPositioningMode, PopupRequest, PopupSize,
};