layer_shika_composition/
system.rs

1use crate::event_loop::FromAppState;
2use crate::layer_surface::LayerSurfaceHandle;
3use crate::{Error, Result};
4use layer_shika_adapters::platform::calloop::channel;
5use layer_shika_adapters::platform::slint::ComponentHandle;
6use layer_shika_adapters::platform::slint_interpreter::{
7    CompilationResult, ComponentDefinition, ComponentInstance, Value,
8};
9use layer_shika_adapters::{AppState, PopupManager, SurfaceState};
10use layer_shika_domain::config::SurfaceConfig;
11use layer_shika_domain::entities::output_registry::OutputRegistry;
12use layer_shika_domain::errors::DomainError;
13use layer_shika_domain::prelude::{
14    AnchorEdges, KeyboardInteractivity, Layer, Margins, OutputPolicy, ScaleFactor,
15};
16use layer_shika_domain::value_objects::dimensions::{PopupDimensions, SurfaceDimension};
17use layer_shika_domain::value_objects::handle::PopupHandle;
18use layer_shika_domain::value_objects::handle::SurfaceHandle;
19use layer_shika_domain::value_objects::lock_config::LockConfig;
20use layer_shika_domain::value_objects::output_handle::OutputHandle;
21use layer_shika_domain::value_objects::output_info::OutputInfo;
22use layer_shika_domain::value_objects::popup_config::PopupConfig;
23use layer_shika_domain::value_objects::popup_position::PopupPosition;
24use layer_shika_domain::value_objects::popup_size::PopupSize;
25use layer_shika_domain::value_objects::surface_instance_id::SurfaceInstanceId;
26use std::cell::Cell;
27use std::rc::Rc;
28
29pub enum PopupCommand {
30    Show {
31        handle: PopupHandle,
32        config: PopupConfig,
33    },
34    Close(PopupHandle),
35    Resize {
36        handle: PopupHandle,
37        width: f32,
38        height: f32,
39    },
40}
41
42#[derive(Debug, Clone)]
43pub enum SurfaceTarget {
44    ByInstance(SurfaceInstanceId),
45    ByHandle(SurfaceHandle),
46    ByName(String),
47    ByNameAndOutput { name: String, output: OutputHandle },
48}
49
50pub enum SurfaceCommand {
51    Resize {
52        target: SurfaceTarget,
53        width: u32,
54        height: u32,
55    },
56    SetAnchor {
57        target: SurfaceTarget,
58        anchor: AnchorEdges,
59    },
60    SetExclusiveZone {
61        target: SurfaceTarget,
62        zone: i32,
63    },
64    SetMargins {
65        target: SurfaceTarget,
66        margins: Margins,
67    },
68    SetLayer {
69        target: SurfaceTarget,
70        layer: Layer,
71    },
72    SetOutputPolicy {
73        target: SurfaceTarget,
74        policy: OutputPolicy,
75    },
76    SetScaleFactor {
77        target: SurfaceTarget,
78        factor: ScaleFactor,
79    },
80    SetKeyboardInteractivity {
81        target: SurfaceTarget,
82        mode: KeyboardInteractivity,
83    },
84    ApplyConfig {
85        target: SurfaceTarget,
86        config: SurfaceConfig,
87    },
88}
89
90pub enum SessionLockCommand {
91    Activate {
92        component_name: String,
93        config: LockConfig,
94    },
95    Deactivate,
96}
97
98pub enum ShellCommand {
99    Popup(PopupCommand),
100    Surface(SurfaceCommand),
101    SessionLock(SessionLockCommand),
102    Render,
103}
104
105/// Context provided to callback handlers
106///
107/// # Callback Signatures
108///
109/// - Basic: `Fn(CallbackContext) -> impl IntoValue`
110/// - With args: `Fn(&[Value], CallbackContext) -> impl IntoValue`
111pub struct CallbackContext {
112    instance_id: SurfaceInstanceId,
113    surface_name: String,
114    control: ShellControl,
115}
116
117impl CallbackContext {
118    pub fn new(
119        instance_id: SurfaceInstanceId,
120        surface_name: String,
121        control: ShellControl,
122    ) -> Self {
123        Self {
124            instance_id,
125            surface_name,
126            control,
127        }
128    }
129
130    /// Returns the surface instance identifier
131    pub const fn instance_id(&self) -> &SurfaceInstanceId {
132        &self.instance_id
133    }
134
135    /// Returns the surface handle
136    pub const fn surface_handle(&self) -> SurfaceHandle {
137        self.instance_id.surface()
138    }
139
140    /// Returns the output handle
141    pub const fn output_handle(&self) -> OutputHandle {
142        self.instance_id.output()
143    }
144
145    /// Returns the surface name
146    pub fn surface_name(&self) -> &str {
147        &self.surface_name
148    }
149
150    /// Returns a reference to the shell control handle
151    pub const fn control(&self) -> &ShellControl {
152        &self.control
153    }
154
155    /// Returns a control handle for this specific surface instance
156    pub fn this_instance(&self) -> SurfaceControlHandle {
157        self.control.surface_instance(&self.instance_id)
158    }
159
160    /// Returns a control handle for all instances of this surface
161    pub fn all_surface_instances(&self) -> SurfaceControlHandle {
162        self.control.surface_by_handle(self.surface_handle())
163    }
164
165    /// Returns a control handle for all surfaces with this name
166    pub fn all_named(&self) -> SurfaceControlHandle {
167        self.control.surface_by_name(&self.surface_name)
168    }
169
170    /// Returns a control handle for all surfaces with this name on the current output
171    pub fn all_named_on_this_output(&self) -> SurfaceControlHandle {
172        self.control
173            .surface_by_name_and_output(&self.surface_name, self.output_handle())
174    }
175
176    #[must_use]
177    pub fn popups(&self) -> crate::PopupShell {
178        self.control.popups()
179    }
180
181    /// Closes a specific popup by its handle
182    ///
183    /// Convenience method that forwards to the underlying `ShellControl`.
184    /// See [`ShellControl::close_popup`] for full documentation.
185    pub fn close_popup(&self, handle: PopupHandle) -> Result<()> {
186        self.control.close_popup(handle)
187    }
188
189    /// Resizes a popup to the specified dimensions
190    ///
191    /// Convenience method that forwards to the underlying `ShellControl`.
192    /// See [`ShellControl::resize_popup`] for full documentation.
193    pub fn resize_popup(&self, handle: PopupHandle, width: f32, height: f32) -> Result<()> {
194        self.control.resize_popup(handle, width, height)
195    }
196}
197
198/// Handle for runtime control of shell operations
199///
200/// Cloneable and can be sent across threads for triggering shell operations.
201#[derive(Clone)]
202pub struct ShellControl {
203    sender: channel::Sender<ShellCommand>,
204}
205
206impl ShellControl {
207    pub fn new(sender: channel::Sender<ShellCommand>) -> Self {
208        Self { sender }
209    }
210
211    #[must_use]
212    pub fn popups(&self) -> crate::PopupShell {
213        crate::PopupShell::new(self.sender.clone())
214    }
215
216    /// Closes a specific popup by its handle
217    ///
218    /// Use this when you need to close a specific popup that you opened previously.
219    /// The handle is returned by [`crate::PopupShell::show`].
220    ///
221    /// # Example
222    ///
223    /// ```rust,ignore
224    /// // Store handle when showing popup
225    /// let handle = context.popups().builder("MenuPopup").show()?;
226    ///
227    /// // Later, close it
228    /// control.close_popup(handle)?;
229    /// ```
230    pub fn close_popup(&self, handle: PopupHandle) -> Result<()> {
231        self.sender
232            .send(ShellCommand::Popup(PopupCommand::Close(handle)))
233            .map_err(|_| Error::Domain(DomainError::ChannelClosed))
234    }
235
236    /// Resizes a popup to the specified dimensions
237    ///
238    /// Dynamically changes the size of an active popup. This is typically used
239    /// in response to content changes or user interaction.
240    ///
241    /// For automatic content-based sizing, use `PopupSize::Content` with the
242    /// `resize_on` callback configuration in [`PopupConfig`].
243    ///
244    /// # Example
245    ///
246    /// ```rust,ignore
247    /// shell.on("Main", "expand_menu", |control| {
248    ///     // Assuming we have the popup handle stored somewhere
249    ///     control.resize_popup(menu_handle, 400.0, 600.0)?;
250    ///     Value::Void
251    /// });
252    /// ```
253    pub fn resize_popup(&self, handle: PopupHandle, width: f32, height: f32) -> Result<()> {
254        self.sender
255            .send(ShellCommand::Popup(PopupCommand::Resize {
256                handle,
257                width,
258                height,
259            }))
260            .map_err(|_| Error::Domain(DomainError::ChannelClosed))
261    }
262
263    /// Requests a redraw of all surfaces
264    pub fn request_redraw(&self) -> Result<()> {
265        self.sender
266            .send(ShellCommand::Render)
267            .map_err(|_| Error::Domain(DomainError::ChannelClosed))
268    }
269
270    /// Returns a control handle for a specific surface instance
271    pub fn surface_instance(&self, id: &SurfaceInstanceId) -> SurfaceControlHandle {
272        SurfaceControlHandle {
273            target: SurfaceTarget::ByInstance(*id),
274            sender: self.sender.clone(),
275        }
276    }
277
278    /// Returns a control handle for all instances of a surface by handle
279    pub fn surface_by_handle(&self, handle: SurfaceHandle) -> SurfaceControlHandle {
280        SurfaceControlHandle {
281            target: SurfaceTarget::ByHandle(handle),
282            sender: self.sender.clone(),
283        }
284    }
285
286    /// Returns a control handle for all surfaces with the given name
287    pub fn surface_by_name(&self, name: impl Into<String>) -> SurfaceControlHandle {
288        SurfaceControlHandle {
289            target: SurfaceTarget::ByName(name.into()),
290            sender: self.sender.clone(),
291        }
292    }
293
294    /// Returns a control handle for surfaces with the given name on a specific output
295    pub fn surface_by_name_and_output(
296        &self,
297        name: impl Into<String>,
298        output: OutputHandle,
299    ) -> SurfaceControlHandle {
300        SurfaceControlHandle {
301            target: SurfaceTarget::ByNameAndOutput {
302                name: name.into(),
303                output,
304            },
305            sender: self.sender.clone(),
306        }
307    }
308
309    /// Alias for `surface_by_name`
310    pub fn surface(&self, name: impl Into<String>) -> SurfaceControlHandle {
311        self.surface_by_name(name)
312    }
313}
314
315/// Handle for runtime control of a specific surface
316///
317/// Operations apply to all matching instances. Changes are queued and applied asynchronously.
318/// Obtained via `ShellControl::surface()`.
319pub struct SurfaceControlHandle {
320    target: SurfaceTarget,
321    sender: channel::Sender<ShellCommand>,
322}
323
324impl SurfaceControlHandle {
325    /// Resizes the surface to the specified dimensions
326    pub fn resize(&self, width: u32, height: u32) -> Result<()> {
327        self.sender
328            .send(ShellCommand::Surface(SurfaceCommand::Resize {
329                target: self.target.clone(),
330                width,
331                height,
332            }))
333            .map_err(|_| Error::Domain(DomainError::ChannelClosed))
334    }
335
336    /// Sets the surface width
337    pub fn set_width(&self, width: u32) -> Result<()> {
338        self.resize(width, 0)
339    }
340
341    /// Sets the surface height
342    pub fn set_height(&self, height: u32) -> Result<()> {
343        self.resize(0, height)
344    }
345
346    /// Sets the anchor edges for the surface
347    pub fn set_anchor(&self, anchor: AnchorEdges) -> Result<()> {
348        self.sender
349            .send(ShellCommand::Surface(SurfaceCommand::SetAnchor {
350                target: self.target.clone(),
351                anchor,
352            }))
353            .map_err(|_| Error::Domain(DomainError::ChannelClosed))
354    }
355
356    /// Sets the exclusive zone for the surface
357    pub fn set_exclusive_zone(&self, zone: i32) -> Result<()> {
358        self.sender
359            .send(ShellCommand::Surface(SurfaceCommand::SetExclusiveZone {
360                target: self.target.clone(),
361                zone,
362            }))
363            .map_err(|_| Error::Domain(DomainError::ChannelClosed))
364    }
365
366    /// Sets the margins for the surface
367    pub fn set_margins(&self, margins: impl Into<Margins>) -> Result<()> {
368        self.sender
369            .send(ShellCommand::Surface(SurfaceCommand::SetMargins {
370                target: self.target.clone(),
371                margins: margins.into(),
372            }))
373            .map_err(|_| Error::Domain(DomainError::ChannelClosed))
374    }
375
376    /// Sets the layer for the surface
377    pub fn set_layer(&self, layer: Layer) -> Result<()> {
378        self.sender
379            .send(ShellCommand::Surface(SurfaceCommand::SetLayer {
380                target: self.target.clone(),
381                layer,
382            }))
383            .map_err(|_| Error::Domain(DomainError::ChannelClosed))
384    }
385
386    /// Sets the output policy for the surface
387    pub fn set_output_policy(&self, policy: OutputPolicy) -> Result<()> {
388        self.sender
389            .send(ShellCommand::Surface(SurfaceCommand::SetOutputPolicy {
390                target: self.target.clone(),
391                policy,
392            }))
393            .map_err(|_| Error::Domain(DomainError::ChannelClosed))
394    }
395
396    /// Sets the scale factor for the surface
397    pub fn set_scale_factor(&self, factor: ScaleFactor) -> Result<()> {
398        self.sender
399            .send(ShellCommand::Surface(SurfaceCommand::SetScaleFactor {
400                target: self.target.clone(),
401                factor,
402            }))
403            .map_err(|_| Error::Domain(DomainError::ChannelClosed))
404    }
405
406    /// Sets the keyboard interactivity mode for the surface
407    pub fn set_keyboard_interactivity(&self, mode: KeyboardInteractivity) -> Result<()> {
408        self.sender
409            .send(ShellCommand::Surface(
410                SurfaceCommand::SetKeyboardInteractivity {
411                    target: self.target.clone(),
412                    mode,
413                },
414            ))
415            .map_err(|_| Error::Domain(DomainError::ChannelClosed))
416    }
417
418    /// Applies a complete surface configuration
419    pub fn apply_config(&self, config: SurfaceConfig) -> Result<()> {
420        self.sender
421            .send(ShellCommand::Surface(SurfaceCommand::ApplyConfig {
422                target: self.target.clone(),
423                config,
424            }))
425            .map_err(|_| Error::Domain(DomainError::ChannelClosed))
426    }
427
428    /// Returns a builder for configuring multiple properties at once
429    pub fn configure(self) -> RuntimeSurfaceConfigBuilder {
430        RuntimeSurfaceConfigBuilder {
431            handle: self,
432            config: SurfaceConfig::new(),
433        }
434    }
435}
436
437/// Builder for applying multiple configuration changes to a surface at once
438///
439/// Created via `SurfaceControlHandle::configure()`. Chain configuration methods
440/// and call `.apply()` to commit all changes atomically.
441/// Builder for applying multiple configuration changes to a surface at once
442///
443/// All changes are committed together in one compositor round-trip for efficiency.
444pub struct RuntimeSurfaceConfigBuilder {
445    handle: SurfaceControlHandle,
446    config: SurfaceConfig,
447}
448
449impl RuntimeSurfaceConfigBuilder {
450    /// Sets the surface size
451    #[must_use]
452    pub fn size(mut self, width: u32, height: u32) -> Self {
453        self.config.dimensions = SurfaceDimension::from_raw(width, height);
454        self
455    }
456
457    /// Sets the surface width
458    #[must_use]
459    pub fn width(mut self, width: u32) -> Self {
460        self.config.dimensions = SurfaceDimension::from_raw(width, self.config.dimensions.height());
461        self
462    }
463
464    /// Sets the surface height
465    #[must_use]
466    pub fn height(mut self, height: u32) -> Self {
467        self.config.dimensions = SurfaceDimension::from_raw(self.config.dimensions.width(), height);
468        self
469    }
470
471    /// Sets the layer
472    #[must_use]
473    pub const fn layer(mut self, layer: Layer) -> Self {
474        self.config.layer = layer;
475        self
476    }
477
478    /// Sets the margins
479    #[must_use]
480    pub fn margins(mut self, margins: impl Into<Margins>) -> Self {
481        self.config.margin = margins.into();
482        self
483    }
484
485    /// Sets the anchor edges
486    #[must_use]
487    pub const fn anchor(mut self, anchor: AnchorEdges) -> Self {
488        self.config.anchor = anchor;
489        self
490    }
491
492    /// Sets the exclusive zone
493    #[must_use]
494    pub const fn exclusive_zone(mut self, zone: i32) -> Self {
495        self.config.exclusive_zone = zone;
496        self
497    }
498
499    /// Sets the namespace
500    #[must_use]
501    pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
502        self.config.namespace = namespace.into();
503        self
504    }
505
506    /// Sets the keyboard interactivity mode
507    #[must_use]
508    pub const fn keyboard_interactivity(mut self, mode: KeyboardInteractivity) -> Self {
509        self.config.keyboard_interactivity = mode;
510        self
511    }
512
513    /// Sets the output policy
514    #[must_use]
515    pub fn output_policy(mut self, policy: OutputPolicy) -> Self {
516        self.config.output_policy = policy;
517        self
518    }
519
520    /// Sets the scale factor
521    #[must_use]
522    pub fn scale_factor(mut self, sf: impl TryInto<ScaleFactor, Error = DomainError>) -> Self {
523        self.config.scale_factor = sf.try_into().unwrap_or_default();
524        self
525    }
526
527    /// Applies the configured changes to the surface
528    pub fn apply(self) -> Result<()> {
529        self.handle.apply_config(self.config)
530    }
531}
532
533pub struct EventDispatchContext<'a> {
534    app_state: &'a mut AppState,
535}
536
537impl<'a> FromAppState<'a> for EventDispatchContext<'a> {
538    fn from_app_state(app_state: &'a mut AppState) -> Self {
539        Self { app_state }
540    }
541}
542
543fn extract_dimensions_from_callback(args: &[Value]) -> PopupDimensions {
544    let defaults = PopupDimensions::default();
545    PopupDimensions::new(
546        args.first()
547            .and_then(|v| v.clone().try_into().ok())
548            .unwrap_or(defaults.width),
549        args.get(1)
550            .and_then(|v| v.clone().try_into().ok())
551            .unwrap_or(defaults.height),
552    )
553}
554
555fn initial_dimensions_from_size(size: &PopupSize) -> (f32, f32) {
556    match *size {
557        PopupSize::Fixed { width, height } | PopupSize::Minimum { width, height } => {
558            (width, height)
559        }
560        PopupSize::Range {
561            min_width,
562            min_height,
563            ..
564        } => (min_width, min_height),
565        PopupSize::Content | PopupSize::Maximum { .. } | PopupSize::MatchParent => {
566            // Start with minimal size. Consumer app should register a callback to
567            // call resize_popup() with the desired dimensions.
568            (2.0, 2.0)
569        }
570    }
571}
572
573impl EventDispatchContext<'_> {
574    pub(crate) fn surface_by_instance_mut(
575        &mut self,
576        surface_handle: SurfaceHandle,
577        output_handle: OutputHandle,
578    ) -> Option<&mut SurfaceState> {
579        self.app_state
580            .get_surface_by_instance_mut(surface_handle, output_handle)
581    }
582
583    pub(crate) fn surfaces_by_handle_mut(
584        &mut self,
585        handle: SurfaceHandle,
586    ) -> Vec<&mut SurfaceState> {
587        self.app_state.surfaces_by_handle_mut(handle)
588    }
589
590    pub(crate) fn surfaces_by_name_mut(&mut self, name: &str) -> Vec<&mut SurfaceState> {
591        self.app_state.surfaces_by_name_mut(name)
592    }
593
594    pub(crate) fn surfaces_by_name_and_output_mut(
595        &mut self,
596        name: &str,
597        output: OutputHandle,
598    ) -> Vec<&mut SurfaceState> {
599        self.app_state.surfaces_by_name_and_output_mut(name, output)
600    }
601
602    pub fn with_surface<F, R>(&self, name: &str, f: F) -> Result<R>
603    where
604        F: FnOnce(&ComponentInstance) -> R,
605    {
606        let component = self.get_surface_component(name).ok_or_else(|| {
607            Error::Domain(DomainError::SurfaceNotFound {
608                message: format!("Surface '{}' not found", name),
609            })
610        })?;
611        Ok(f(component))
612    }
613
614    pub fn with_output<F, R>(&self, handle: OutputHandle, f: F) -> Result<R>
615    where
616        F: FnOnce(&ComponentInstance) -> R,
617    {
618        let component = self.get_output_component(handle).ok_or_else(|| {
619            Error::Domain(DomainError::OutputNotFound {
620                message: format!("Output with handle {:?} not found", handle),
621            })
622        })?;
623        Ok(f(component))
624    }
625
626    fn get_surface_component(&self, name: &str) -> Option<&ComponentInstance> {
627        self.app_state
628            .surfaces_by_name(name)
629            .first()
630            .map(|s| s.component_instance())
631    }
632
633    /// Returns the primary component instance
634    #[must_use]
635    pub fn component_instance(&self) -> Option<&ComponentInstance> {
636        self.app_state
637            .primary_output()
638            .map(SurfaceState::component_instance)
639    }
640
641    /// Returns all component instances across all outputs
642    pub fn all_component_instances(&self) -> impl Iterator<Item = &ComponentInstance> {
643        self.app_state
644            .all_outputs()
645            .map(SurfaceState::component_instance)
646    }
647
648    /// Returns the output registry
649    pub const fn output_registry(&self) -> &OutputRegistry {
650        self.app_state.output_registry()
651    }
652
653    /// Returns the primary output handle
654    #[must_use]
655    pub fn primary_output_handle(&self) -> Option<OutputHandle> {
656        self.app_state.primary_output_handle()
657    }
658
659    /// Returns the active output handle
660    #[must_use]
661    pub fn active_output_handle(&self) -> Option<OutputHandle> {
662        self.app_state.active_output_handle()
663    }
664
665    /// Returns all outputs with their handles and components
666    pub fn outputs(&self) -> impl Iterator<Item = (OutputHandle, &ComponentInstance)> {
667        self.app_state
668            .outputs_with_handles()
669            .map(|(handle, surface)| (handle, surface.component_instance()))
670    }
671
672    /// Returns the component for a specific output
673    pub fn get_output_component(&self, handle: OutputHandle) -> Option<&ComponentInstance> {
674        self.app_state
675            .get_output_by_handle(handle)
676            .map(SurfaceState::component_instance)
677    }
678
679    /// Returns information about a specific output
680    pub fn get_output_info(&self, handle: OutputHandle) -> Option<&OutputInfo> {
681        self.app_state.get_output_info(handle)
682    }
683
684    /// Returns information about all outputs
685    pub fn all_output_info(&self) -> impl Iterator<Item = &OutputInfo> {
686        self.app_state.all_output_info()
687    }
688
689    /// Returns all outputs with their info and components
690    pub fn outputs_with_info(&self) -> impl Iterator<Item = (&OutputInfo, &ComponentInstance)> {
691        self.app_state
692            .outputs_with_info()
693            .map(|(info, surface)| (info, surface.component_instance()))
694    }
695
696    fn active_or_primary_output(&self) -> Option<&SurfaceState> {
697        self.app_state
698            .active_output()
699            .or_else(|| self.app_state.primary_output())
700    }
701
702    /// Renders a new frame for all dirty surfaces
703    pub fn render_frame_if_dirty(&mut self) -> Result<()> {
704        for surface in self.app_state.all_outputs() {
705            surface.render_frame_if_dirty()?;
706        }
707        Ok(())
708    }
709
710    /// Activates the session lock
711    pub(crate) fn activate_session_lock(
712        &mut self,
713        component_name: &str,
714        config: LockConfig,
715    ) -> Result<()> {
716        self.app_state
717            .activate_session_lock(component_name, config)
718            .map_err(Error::Adapter)
719    }
720
721    /// Deactivates the session lock
722    pub(crate) fn deactivate_session_lock(&mut self) -> Result<()> {
723        self.app_state
724            .deactivate_session_lock()
725            .map_err(Error::Adapter)
726    }
727
728    /// Returns the compilation result if available
729    #[must_use]
730    pub fn compilation_result(&self) -> Option<Rc<CompilationResult>> {
731        self.app_state
732            .primary_output()
733            .and_then(SurfaceState::compilation_result)
734    }
735
736    /// Shows a popup from a popup request
737    ///
738    /// Resize callbacks (if configured via `resize_on()`) will operate directly
739    /// on the popup manager for immediate updates.
740    #[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
741    pub fn show_popup(&mut self, handle: PopupHandle, config: &PopupConfig) -> Result<PopupHandle> {
742        log::info!("show_popup called for component '{}'", config.component);
743
744        let compilation_result = self.compilation_result().ok_or_else(|| {
745            log::error!("No compilation result available");
746            Error::Domain(DomainError::Configuration {
747                message: "No compilation result available for popup creation".to_string(),
748            })
749        })?;
750
751        log::debug!(
752            "Got compilation result, looking for component '{}'",
753            config.component
754        );
755
756        let definition = compilation_result
757            .component(&config.component)
758            .ok_or_else(|| {
759                log::error!(
760                    "Component '{}' not found in compilation result",
761                    config.component
762                );
763                Error::Domain(DomainError::ComponentNotFound {
764                    name: config.component.clone(),
765                })
766            })?;
767
768        log::debug!("Found component definition for '{}'", config.component);
769
770        let is_using_active = self.app_state.active_output().is_some();
771        let active_surface = self.active_or_primary_output().ok_or_else(|| {
772            log::error!("No active or primary output available");
773            Error::Domain(DomainError::OutputNotFound {
774                message: "No active or primary output available".to_string(),
775            })
776        })?;
777
778        log::info!(
779            "Creating popup on {} output",
780            if is_using_active { "active" } else { "primary" }
781        );
782
783        let popup_manager = active_surface.popup_manager().ok_or_else(|| {
784            Error::Domain(DomainError::SurfaceNotFound {
785                message: "No popup manager available".to_string(),
786            })
787        })?;
788
789        // For content-based sizing, we need to query the component's preferred size first
790        let initial_dimensions = initial_dimensions_from_size(&config.size);
791        let mut resolved_position = config.position.clone();
792        if let PopupPosition::Cursor { offset } = config.position {
793            let cursor_pos = active_surface.current_pointer_position();
794            resolved_position = PopupPosition::Absolute {
795                x: cursor_pos.x + offset.x,
796                y: cursor_pos.y + offset.y,
797            };
798        }
799
800        log::debug!(
801            "Creating popup for '{}' with dimensions {}x{} at position {:?}",
802            config.component,
803            initial_dimensions.0,
804            initial_dimensions.1,
805            resolved_position
806        );
807
808        let resolved_config = PopupConfig {
809            component: config.component.clone(),
810            position: resolved_position,
811            size: config.size.clone(),
812            behavior: config.behavior.clone(),
813            output: config.output.clone(),
814            parent: config.parent,
815            z_index: config.z_index,
816            close_callback: config.close_callback.clone(),
817            resize_callback: config.resize_callback.clone(),
818        };
819
820        popup_manager.request_popup(
821            handle,
822            resolved_config,
823            initial_dimensions.0,
824            initial_dimensions.1,
825        );
826
827        let (instance, popup_key_cell) =
828            Self::create_popup_instance(&definition, &popup_manager, config)?;
829
830        popup_key_cell.set(handle.key());
831
832        if let Some(popup_surface) = popup_manager.get_popup_window(handle.key()) {
833            popup_surface.set_component_instance(instance);
834        } else {
835            return Err(Error::Domain(DomainError::SurfaceNotFound {
836                message: "Popup window not found after creation".to_string(),
837            }));
838        }
839
840        Ok(handle)
841    }
842
843    /// Closes a popup by its handle
844    pub fn close_popup(&mut self, handle: PopupHandle) -> Result<()> {
845        if let Some(active_surface) = self.active_or_primary_output() {
846            if let Some(popup_manager) = active_surface.popup_manager() {
847                popup_manager.close(handle)?;
848            }
849        }
850        Ok(())
851    }
852
853    /// Resizes a popup to the specified dimensions
854    pub fn resize_popup(&mut self, handle: PopupHandle, width: f32, height: f32) -> Result<()> {
855        let active_surface = self.active_or_primary_output().ok_or_else(|| {
856            Error::Domain(DomainError::OutputNotFound {
857                message: "No active or primary output available".to_string(),
858            })
859        })?;
860
861        let popup_manager = active_surface.popup_manager().ok_or_else(|| {
862            Error::Domain(DomainError::SurfaceNotFound {
863                message: "No popup manager available".to_string(),
864            })
865        })?;
866
867        if let Some(popup_surface) = popup_manager.get_popup_window(handle.key()) {
868            let size_changed = popup_surface.request_resize(width, height);
869
870            if size_changed {
871                #[allow(clippy::cast_possible_truncation)]
872                #[allow(clippy::cast_possible_wrap)]
873                let logical_width = width as i32;
874                #[allow(clippy::cast_possible_truncation)]
875                #[allow(clippy::cast_possible_wrap)]
876                let logical_height = height as i32;
877
878                popup_manager.update_popup_viewport(handle.key(), logical_width, logical_height);
879                popup_manager.commit_popup_surface(handle.key());
880                log::debug!(
881                    "Updated popup viewport to logical size: {}x{} (from resize to {}x{})",
882                    logical_width,
883                    logical_height,
884                    width,
885                    height
886                );
887            }
888        }
889
890        Ok(())
891    }
892
893    fn create_popup_instance(
894        definition: &ComponentDefinition,
895        popup_manager: &Rc<PopupManager>,
896        config: &PopupConfig,
897    ) -> Result<(ComponentInstance, Rc<Cell<usize>>)> {
898        let instance = definition.create().map_err(|e| {
899            Error::Domain(DomainError::Configuration {
900                message: format!("Failed to create popup instance: {}", e),
901            })
902        })?;
903
904        let popup_key_cell = Rc::new(Cell::new(0));
905
906        Self::register_popup_callbacks(&instance, popup_manager, &popup_key_cell, config)?;
907
908        instance.show().map_err(|e| {
909            Error::Domain(DomainError::Configuration {
910                message: format!("Failed to show popup instance: {}", e),
911            })
912        })?;
913
914        Ok((instance, popup_key_cell))
915    }
916
917    fn register_popup_callbacks(
918        instance: &ComponentInstance,
919        popup_manager: &Rc<PopupManager>,
920        popup_key_cell: &Rc<Cell<usize>>,
921        config: &PopupConfig,
922    ) -> Result<()> {
923        if let Some(close_callback_name) = &config.close_callback {
924            Self::register_close_callback(
925                instance,
926                popup_manager,
927                popup_key_cell,
928                close_callback_name,
929            )?;
930        }
931
932        if let Some(resize_callback_name) = &config.resize_callback {
933            Self::register_resize_direct(
934                instance,
935                popup_manager,
936                popup_key_cell,
937                resize_callback_name,
938            )?;
939        }
940
941        Ok(())
942    }
943
944    fn register_close_callback(
945        instance: &ComponentInstance,
946        popup_manager: &Rc<PopupManager>,
947        popup_key_cell: &Rc<Cell<usize>>,
948        callback_name: &str,
949    ) -> Result<()> {
950        let popup_manager_weak = Rc::downgrade(popup_manager);
951        let key_cell = Rc::clone(popup_key_cell);
952        instance
953            .set_callback(callback_name, move |_| {
954                if let Some(popup_manager) = popup_manager_weak.upgrade() {
955                    let key = key_cell.get();
956                    popup_manager.close(PopupHandle::from_raw(key)).ok();
957                }
958                Value::Void
959            })
960            .map_err(|e| {
961                Error::Domain(DomainError::Configuration {
962                    message: format!("Failed to set '{}' callback: {}", callback_name, e),
963                })
964            })
965    }
966
967    fn register_resize_direct(
968        instance: &ComponentInstance,
969        popup_manager: &Rc<PopupManager>,
970        popup_key_cell: &Rc<Cell<usize>>,
971        callback_name: &str,
972    ) -> Result<()> {
973        let popup_manager_weak = Rc::downgrade(popup_manager);
974        let key_cell = Rc::clone(popup_key_cell);
975        instance
976            .set_callback(callback_name, move |args| {
977                let dimensions = extract_dimensions_from_callback(args);
978                let popup_key = key_cell.get();
979
980                log::info!(
981                    "Resize callback invoked: {}x{} for key {}",
982                    dimensions.width,
983                    dimensions.height,
984                    popup_key
985                );
986
987                if let Some(popup_manager) = popup_manager_weak.upgrade() {
988                    if let Some(popup_surface) = popup_manager.get_popup_window(popup_key) {
989                        let size_changed = popup_surface.request_resize(dimensions.width, dimensions.height);
990
991                        if size_changed {
992                            #[allow(clippy::cast_possible_truncation)]
993                            #[allow(clippy::cast_possible_wrap)]
994                            let logical_width = dimensions.width as i32;
995                            #[allow(clippy::cast_possible_truncation)]
996                            #[allow(clippy::cast_possible_wrap)]
997                            let logical_height = dimensions.height as i32;
998
999                            popup_manager.update_popup_viewport(
1000                                popup_key,
1001                                logical_width,
1002                                logical_height,
1003                            );
1004                            log::debug!(
1005                                "Updated popup viewport to logical size: {}x{} (from direct resize to {}x{})",
1006                                logical_width,
1007                                logical_height,
1008                                dimensions.width,
1009                                dimensions.height
1010                            );
1011                        }
1012                    }
1013                }
1014                Value::Void
1015            })
1016            .map_err(|e| {
1017                Error::Domain(DomainError::Configuration {
1018                    message: format!("Failed to set '{}' callback: {}", callback_name, e),
1019                })
1020            })
1021    }
1022
1023    pub fn configure_surface<F>(&mut self, name: &str, f: F) -> Result<()>
1024    where
1025        F: FnOnce(&ComponentInstance, LayerSurfaceHandle<'_>),
1026    {
1027        let surfaces = self.app_state.surfaces_by_name(name);
1028        let surface = surfaces.first().ok_or_else(|| {
1029            Error::Domain(DomainError::SurfaceNotFound {
1030                message: format!("Surface '{}' not found", name),
1031            })
1032        })?;
1033
1034        let handle = LayerSurfaceHandle::from_window_state(surface);
1035        let component = surface.component_instance();
1036        f(component, handle);
1037        Ok(())
1038    }
1039
1040    pub fn configure_all_surfaces<F>(&mut self, mut f: F)
1041    where
1042        F: FnMut(&ComponentInstance, LayerSurfaceHandle<'_>),
1043    {
1044        for surface in self.app_state.all_outputs() {
1045            let handle = LayerSurfaceHandle::from_window_state(surface);
1046            let component = surface.component_instance();
1047            f(component, handle);
1048        }
1049    }
1050}