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