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
105pub 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 pub const fn instance_id(&self) -> &SurfaceInstanceId {
132 &self.instance_id
133 }
134
135 pub const fn surface_handle(&self) -> SurfaceHandle {
137 self.instance_id.surface()
138 }
139
140 pub const fn output_handle(&self) -> OutputHandle {
142 self.instance_id.output()
143 }
144
145 pub fn surface_name(&self) -> &str {
147 &self.surface_name
148 }
149
150 pub const fn control(&self) -> &ShellControl {
152 &self.control
153 }
154
155 pub fn this_instance(&self) -> SurfaceControlHandle {
157 self.control.surface_instance(&self.instance_id)
158 }
159
160 pub fn all_surface_instances(&self) -> SurfaceControlHandle {
162 self.control.surface_by_handle(self.surface_handle())
163 }
164
165 pub fn all_named(&self) -> SurfaceControlHandle {
167 self.control.surface_by_name(&self.surface_name)
168 }
169
170 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 pub fn close_popup(&self, handle: PopupHandle) -> Result<()> {
186 self.control.close_popup(handle)
187 }
188
189 pub fn resize_popup(&self, handle: PopupHandle, width: f32, height: f32) -> Result<()> {
194 self.control.resize_popup(handle, width, height)
195 }
196}
197
198#[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 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 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 pub fn request_redraw(&self) -> Result<()> {
265 self.sender
266 .send(ShellCommand::Render)
267 .map_err(|_| Error::Domain(DomainError::ChannelClosed))
268 }
269
270 pub fn surface_instance(&self, id: &SurfaceInstanceId) -> SurfaceControlHandle {
272 SurfaceControlHandle {
273 target: SurfaceTarget::ByInstance(*id),
274 sender: self.sender.clone(),
275 }
276 }
277
278 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 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 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 pub fn surface(&self, name: impl Into<String>) -> SurfaceControlHandle {
311 self.surface_by_name(name)
312 }
313}
314
315pub struct SurfaceControlHandle {
320 target: SurfaceTarget,
321 sender: channel::Sender<ShellCommand>,
322}
323
324impl SurfaceControlHandle {
325 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 pub fn set_width(&self, width: u32) -> Result<()> {
338 self.resize(width, 0)
339 }
340
341 pub fn set_height(&self, height: u32) -> Result<()> {
343 self.resize(0, height)
344 }
345
346 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 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 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 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 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 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 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 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 pub fn configure(self) -> RuntimeSurfaceConfigBuilder {
430 RuntimeSurfaceConfigBuilder {
431 handle: self,
432 config: SurfaceConfig::new(),
433 }
434 }
435}
436
437pub struct RuntimeSurfaceConfigBuilder {
445 handle: SurfaceControlHandle,
446 config: SurfaceConfig,
447}
448
449impl RuntimeSurfaceConfigBuilder {
450 #[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 #[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 #[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 #[must_use]
473 pub const fn layer(mut self, layer: Layer) -> Self {
474 self.config.layer = layer;
475 self
476 }
477
478 #[must_use]
480 pub fn margins(mut self, margins: impl Into<Margins>) -> Self {
481 self.config.margin = margins.into();
482 self
483 }
484
485 #[must_use]
487 pub const fn anchor(mut self, anchor: AnchorEdges) -> Self {
488 self.config.anchor = anchor;
489 self
490 }
491
492 #[must_use]
494 pub const fn exclusive_zone(mut self, zone: i32) -> Self {
495 self.config.exclusive_zone = zone;
496 self
497 }
498
499 #[must_use]
501 pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
502 self.config.namespace = namespace.into();
503 self
504 }
505
506 #[must_use]
508 pub const fn keyboard_interactivity(mut self, mode: KeyboardInteractivity) -> Self {
509 self.config.keyboard_interactivity = mode;
510 self
511 }
512
513 #[must_use]
515 pub fn output_policy(mut self, policy: OutputPolicy) -> Self {
516 self.config.output_policy = policy;
517 self
518 }
519
520 #[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 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 (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 #[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 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 pub const fn output_registry(&self) -> &OutputRegistry {
650 self.app_state.output_registry()
651 }
652
653 #[must_use]
655 pub fn primary_output_handle(&self) -> Option<OutputHandle> {
656 self.app_state.primary_output_handle()
657 }
658
659 #[must_use]
661 pub fn active_output_handle(&self) -> Option<OutputHandle> {
662 self.app_state.active_output_handle()
663 }
664
665 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 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 pub fn get_output_info(&self, handle: OutputHandle) -> Option<&OutputInfo> {
681 self.app_state.get_output_info(handle)
682 }
683
684 pub fn all_output_info(&self) -> impl Iterator<Item = &OutputInfo> {
686 self.app_state.all_output_info()
687 }
688
689 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 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 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 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 #[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 #[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 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 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 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}