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
92pub 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 pub const fn instance_id(&self) -> &SurfaceInstanceId {
116 &self.instance_id
117 }
118
119 pub const fn surface_handle(&self) -> SurfaceHandle {
121 self.instance_id.surface()
122 }
123
124 pub const fn output_handle(&self) -> OutputHandle {
126 self.instance_id.output()
127 }
128
129 pub fn surface_name(&self) -> &str {
131 &self.surface_name
132 }
133
134 pub const fn control(&self) -> &ShellControl {
136 &self.control
137 }
138
139 pub fn this_instance(&self) -> SurfaceControlHandle {
141 self.control.surface_instance(&self.instance_id)
142 }
143
144 pub fn all_surface_instances(&self) -> SurfaceControlHandle {
146 self.control.surface_by_handle(self.surface_handle())
147 }
148
149 pub fn all_named(&self) -> SurfaceControlHandle {
151 self.control.surface_by_name(&self.surface_name)
152 }
153
154 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#[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 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 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 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 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 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 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 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 pub fn surface_instance(&self, id: &SurfaceInstanceId) -> SurfaceControlHandle {
252 SurfaceControlHandle {
253 target: SurfaceTarget::ByInstance(*id),
254 sender: self.sender.clone(),
255 }
256 }
257
258 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 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 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 pub fn surface(&self, name: impl Into<String>) -> SurfaceControlHandle {
291 self.surface_by_name(name)
292 }
293}
294
295pub struct SurfaceControlHandle {
300 target: SurfaceTarget,
301 sender: channel::Sender<ShellCommand>,
302}
303
304impl SurfaceControlHandle {
305 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 pub fn set_width(&self, width: u32) -> Result<()> {
322 self.resize(width, 0)
323 }
324
325 pub fn set_height(&self, height: u32) -> Result<()> {
327 self.resize(0, height)
328 }
329
330 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 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 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 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 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 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 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 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 pub fn configure(self) -> RuntimeSurfaceConfigBuilder {
454 RuntimeSurfaceConfigBuilder {
455 handle: self,
456 config: SurfaceConfig::new(),
457 }
458 }
459}
460
461pub struct RuntimeSurfaceConfigBuilder {
469 handle: SurfaceControlHandle,
470 config: SurfaceConfig,
471}
472
473impl RuntimeSurfaceConfigBuilder {
474 #[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 #[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 #[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 #[must_use]
497 pub const fn layer(mut self, layer: Layer) -> Self {
498 self.config.layer = layer;
499 self
500 }
501
502 #[must_use]
504 pub fn margins(mut self, margins: impl Into<Margins>) -> Self {
505 self.config.margin = margins.into();
506 self
507 }
508
509 #[must_use]
511 pub const fn anchor(mut self, anchor: AnchorEdges) -> Self {
512 self.config.anchor = anchor;
513 self
514 }
515
516 #[must_use]
518 pub const fn exclusive_zone(mut self, zone: i32) -> Self {
519 self.config.exclusive_zone = zone;
520 self
521 }
522
523 #[must_use]
525 pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
526 self.config.namespace = namespace.into();
527 self
528 }
529
530 #[must_use]
532 pub const fn keyboard_interactivity(mut self, mode: KeyboardInteractivity) -> Self {
533 self.config.keyboard_interactivity = mode;
534 self
535 }
536
537 #[must_use]
539 pub fn output_policy(mut self, policy: OutputPolicy) -> Self {
540 self.config.output_policy = policy;
541 self
542 }
543
544 #[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 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 #[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 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 pub const fn output_registry(&self) -> &OutputRegistry {
656 self.app_state.output_registry()
657 }
658
659 #[must_use]
661 pub fn primary_output_handle(&self) -> Option<OutputHandle> {
662 self.app_state.primary_output_handle()
663 }
664
665 #[must_use]
667 pub fn active_output_handle(&self) -> Option<OutputHandle> {
668 self.app_state.active_output_handle()
669 }
670
671 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 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 pub fn get_output_info(&self, handle: OutputHandle) -> Option<&OutputInfo> {
687 self.app_state.get_output_info(handle)
688 }
689
690 pub fn all_output_info(&self) -> impl Iterator<Item = &OutputInfo> {
692 self.app_state.all_output_info()
693 }
694
695 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 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 #[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 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 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 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 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}