1use super::popup_positioning::{
2 CustomPopupPlacementCallback, PopupAnchor, PopupGravity, PopupPositionRequest, PopupPositionerConstraintAdjustment,
3};
4use super::{
5 IPopupHost, LightDismissOverlayLayer, OverlayPopupHost, PopupRoot, TemplateAppliedEventArgs, TemplatedControl,
6};
7use crate::diagnostics::IPopupHostProvider;
8use crate::presenters::ContentPresenter;
9use ferroui_base::input::text_input::TransformTrackingHelper;
10use ferroui_base::data::core::ValueTypes;
11use ferroui_base::utilities::CancelEventArgs;
12use ferroui_base::ElementRef;
13use crate::{Control, ControlImpl, PixelPointEventArgs, PlacementMode, TopLevel, Window, WindowBase};
14use ferroui_base::data::BindingPriority;
15use ferroui_base::input::raw::{IRawInputEventArgs, RawPointerEventArgs, RawPointerEventType};
16use ferroui_base::input::{InputElement, InputElementImpl, InputManager, PointerPressedEventArgs};
17use ferroui_base::interactivity::InteractiveImpl;
18use ferroui_base::layout::{Layoutable, LayoutableImpl};
19use ferroui_base::logical_tree::LogicalTreeAttachmentEventArgs;
20use ferroui_base::media::{ScaleTransform, Transform};
21use ferroui_base::reactive::{AnonymousObserver, CompositeDisposable, Disposable, IDisposable, ObservableExt};
22use ferroui_base::styling::ThemeVariant;
23use ferroui_base::threading::{Dispatcher, DispatcherPriority};
24use ferroui_base::utilities::HandlerList;
25use ferroui_base::{
26 ferro_class, ferro_impl_classes, ferro_property, instantiate, AttachedProperty, DirectProperty,
27 FerroObject, FerroObjectExtensions, FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs,
28 IFerroDependencyResolver, Matrix, Nullable, PixelPoint, Point, Rect, Ref, Size, StyledElement,
29 StyledElementImpl, StyledElementImplExt, StyledProperty, Visual, VisualImpl, VisualImplExt,
30 VisualTreeAttachmentEventArgs, WeakRef,
31};
32use std::cell::{Cell, RefCell};
33use std::rc::Rc;
34
35#[derive(Clone)]
38pub struct CustomPopupPlacementCallbackValue(pub CustomPopupPlacementCallback);
39
40impl PartialEq for CustomPopupPlacementCallbackValue {
41 fn eq(&self, other: &Self) -> bool {
42 Rc::ptr_eq(&self.0, &other.0)
43 }
44}
45
46#[repr(C)]
48pub struct Popup {
49 base: Control,
50 dependency_resolver: RefCell<Option<Rc<dyn IFerroDependencyResolver>>>,
51 is_open_requested: Cell<bool>,
52 ignore_is_open_changed: Cell<bool>,
53 is_using_overlay_layer: Cell<bool>,
54 open_state: RefCell<Option<Rc<PopupOpenState>>>,
55 popup_host_changed_handler: HandlerList<dyn Fn(Option<&Rc<dyn IPopupHost>>)>,
56 opened_popups: RefCell<Vec<Ref<Popup>>>,
57 closed: HandlerList<dyn Fn()>,
58 opened: HandlerList<dyn Fn()>,
59 closing: HandlerList<dyn Fn(&CancelEventArgs)>,
60 placement_target_transform_subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
64 queued_for_placement_target_move: Cell<bool>,
65 last_placement_target_rect: Cell<Option<Rect>>,
68}
69
70ferro_class!(Popup: Control);
71ferroui_base::ferro_class_info!(Popup { new: Popup::new });
72ferro_impl_classes!(Popup: InteractiveImpl, InputElementImpl);
73
74impl ControlImpl for Popup {
75 fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
76 crate::automation::peers::PopupAutomationPeer::new(this).upcast()
77 }
78}
79
80impl FerroObjectImpl for Popup {
81 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
82 Self::parent_on_property_changed(this, change);
83 let Some(open_state) = this.open_state() else { return };
84
85 let property = change.property();
86 if property == Layoutable::width_property().as_property()
87 || property == Layoutable::min_width_property().as_property()
88 || property == Layoutable::max_width_property().as_property()
89 || property == Layoutable::height_property().as_property()
90 || property == Layoutable::min_height_property().as_property()
91 || property == Layoutable::max_height_property().as_property()
92 {
93 this.update_host_sizing(&open_state.popup_host, open_state.top_level(), &open_state.placement_target());
94 } else if property == Self::placement_target_property().as_property()
95 || property == Self::placement_property().as_property()
96 || property == Self::horizontal_offset_property().as_property()
97 || property == Self::vertical_offset_property().as_property()
98 || property == Self::placement_anchor_property().as_property()
99 || property == Self::placement_constraint_adjustment_property().as_property()
100 || property == Self::placement_rect_property().as_property()
101 {
102 if property == Self::placement_target_property().as_property() {
103 let new_target = ElementRef::resolve(&change.get_new_value::<Option<ElementRef<Control>>>())
104 .or_else(|| this.find_logical_ancestor_of_type::<Control>(false));
105
106 let Some(new_target) = new_target else {
107 this.close();
108 return;
109 };
110 if TopLevel::get_top_level(Some(&new_target)) != open_state.top_level() {
111 this.close();
112 return;
113 }
114
115 open_state.set_placement_target(new_target);
116 if this.placement() != PlacementMode::Pointer {
117 this.subscribe_placement_target_transforms();
118 }
119 }
120
121 this.update_host_position(&open_state.popup_host, &open_state.placement_target());
122 } else if property == Self::topmost_property().as_property() {
123 open_state.popup_host.set_topmost(change.get_new_value::<bool>());
124 } else if property == InputElement::is_hit_test_visible_property().as_property() {
125 open_state.popup_host.set_is_hit_test_visible(change.get_new_value::<bool>());
126 }
127 }
128}
129
130impl StyledElementImpl for Popup {
131 fn on_detached_from_logical_tree(this: &Self, e: &LogicalTreeAttachmentEventArgs) {
132 Self::parent_on_detached_from_logical_tree(this, e);
133 this.close();
134 }
135}
136
137impl VisualImpl for Popup {
138 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
139 Self::parent_on_attached_to_visual_tree(this, e);
140 if this.is_open_requested.get() {
141 this.open();
142 }
143 }
144}
145
146impl LayoutableImpl for Popup {
147 fn measure_core(_this: &Self, _available_size: Size) -> Size {
150 Size::default()
151 }
152}
153
154struct IgnoreIsOpenScope<'a> {
156 owner: &'a Popup,
157}
158
159impl Drop for IgnoreIsOpenScope<'_> {
160 fn drop(&mut self) {
161 self.owner.ignore_is_open_changed.set(false);
162 }
163}
164
165ferroui_base::ferro_properties! { impl Popup {
166 ferro_property!(
167 pub fn window_manager_add_shadow_hint_property() -> StyledProperty<bool> {
169 FerroProperty::register::<Popup, _>("WindowManagerAddShadowHint", false)
170 }
171 );
172
173 ferro_property!(
174 pub fn child_property() -> StyledProperty<Option<Ref<Control>>> {
176 FerroProperty::register::<Popup, _>("Child", None)
177 }
178 );
179
180 ferro_property!(
181 pub fn inherits_transform_property() -> StyledProperty<bool> {
183 FerroProperty::register::<Popup, _>("InheritsTransform", false)
184 }
185 );
186
187 ferro_property!(
188 pub fn is_open_property() -> StyledProperty<bool> {
190 FerroProperty::register::<Popup, _>("IsOpen", false)
191 }
192 );
193
194 ferro_property!(
195 pub fn placement_anchor_property() -> StyledProperty<PopupAnchor> {
197 FerroProperty::register::<Popup, _>("PlacementAnchor", PopupAnchor::NONE)
198 }
199 );
200
201 ferro_property!(
202 pub fn placement_constraint_adjustment_property() -> StyledProperty<PopupPositionerConstraintAdjustment> {
204 FerroProperty::register::<Popup, _>(
205 "PlacementConstraintAdjustment",
206 PopupPositionerConstraintAdjustment::FLIP_X
207 | PopupPositionerConstraintAdjustment::FLIP_Y
208 | PopupPositionerConstraintAdjustment::SLIDE_X
209 | PopupPositionerConstraintAdjustment::SLIDE_Y
210 | PopupPositionerConstraintAdjustment::RESIZE_X
211 | PopupPositionerConstraintAdjustment::RESIZE_Y,
212 )
213 }
214 );
215
216 ferro_property!(
217 pub fn placement_gravity_property() -> StyledProperty<PopupGravity> {
219 FerroProperty::register::<Popup, _>("PlacementGravity", PopupGravity::NONE)
220 }
221 );
222
223 ferro_property!(
224 pub fn placement_property() -> StyledProperty<PlacementMode> {
226 FerroProperty::register::<Popup, _>("Placement", PlacementMode::Bottom)
227 }
228 );
229
230 ferro_property!(
231 pub fn placement_rect_property() -> StyledProperty<Option<Rect>> {
233 FerroProperty::register::<Popup, _>("PlacementRect", None)
234 }
235 );
236
237 ferro_property!(
238 pub fn placement_target_property() -> StyledProperty<Option<ElementRef<Control>>> {
243 ValueTypes::register_element_ref::<Control>();
244 FerroProperty::register::<Popup, _>("PlacementTarget", None)
245 }
246 );
247
248 ferro_property!(
249 pub fn custom_popup_placement_callback_property() -> StyledProperty<Option<CustomPopupPlacementCallbackValue>> {
251 FerroProperty::register::<Popup, _>("CustomPopupPlacementCallback", None)
252 }
253 );
254
255 ferro_property!(
256 pub fn overlay_dismiss_event_pass_through_property() -> StyledProperty<bool> {
258 FerroProperty::register::<Popup, _>("OverlayDismissEventPassThrough", false)
259 }
260 );
261
262 ferro_property!(
263 pub fn overlay_input_pass_through_element_property() -> StyledProperty<Option<ElementRef<InputElement>>> {
265 ValueTypes::register_element_ref::<InputElement>();
266 FerroProperty::register::<Popup, _>("OverlayInputPassThroughElement", None)
267 }
268 );
269
270 ferro_property!(
271 pub fn horizontal_offset_property() -> StyledProperty<f64> {
273 FerroProperty::register::<Popup, _>("HorizontalOffset", 0.0)
274 }
275 );
276
277 ferro_property!(
278 pub fn is_light_dismiss_enabled_property() -> StyledProperty<bool> {
280 FerroProperty::register::<Popup, _>("IsLightDismissEnabled", false)
281 }
282 );
283
284 ferro_property!(
285 pub fn vertical_offset_property() -> StyledProperty<f64> {
287 FerroProperty::register::<Popup, _>("VerticalOffset", 0.0)
288 }
289 );
290
291 ferro_property!(
292 pub fn topmost_property() -> StyledProperty<bool> {
294 FerroProperty::register::<Popup, _>("Topmost", false)
295 }
296 );
297
298 ferro_property!(
299 pub fn takes_focus_from_native_control_property() -> AttachedProperty<bool> {
301 FerroProperty::register_attached::<Popup, Control, _>("TakesFocusFromNativeControl", true)
302 }
303 );
304
305 ferro_property!(
306 pub fn should_use_overlay_layer_property() -> StyledProperty<bool> {
308 FerroProperty::register::<Popup, _>("ShouldUseOverlayLayer", false)
309 }
310 );
311
312 ferro_property!(
313 pub fn is_using_overlay_layer_property() -> DirectProperty<Popup, bool> {
315 FerroProperty::register_direct::<Popup, _>("IsUsingOverlayLayer", |o| o.is_using_overlay_layer(), None, false)
316 }
317 );
318} }
319
320impl Popup {
321 fn static_constructor() {
322 Self::child_property().changed().add_class_handler::<Popup>(|x, e| x.child_changed(e));
323 Self::is_open_property().changed().add_class_handler::<Popup>(|x, e| x.is_open_changed(e));
324 }
325
326 pub fn construct() -> Self {
328 Self {
329 base: Control::construct(),
330 dependency_resolver: RefCell::new(None),
331 is_open_requested: Cell::new(false),
332 ignore_is_open_changed: Cell::new(false),
333 is_using_overlay_layer: Cell::new(false),
334 open_state: RefCell::new(None),
335 popup_host_changed_handler: HandlerList::new(),
336 opened_popups: RefCell::new(Vec::new()),
337 closed: HandlerList::new(),
338 opened: HandlerList::new(),
339 closing: HandlerList::new(),
340 placement_target_transform_subscriptions: RefCell::new(Vec::new()),
341 queued_for_placement_target_move: Cell::new(false),
342 last_placement_target_rect: Cell::new(None),
343 }
344 }
345
346 pub fn new() -> Ref<Self> {
347 instantiate(Self::construct())
348 }
349
350 fn subscribe<F: ?Sized + 'static>(&self, select: fn(&Popup) -> &HandlerList<F>, handler: Rc<F>) -> Rc<dyn IDisposable> {
351 let token = select(self).add(handler);
352 let weak = self.to_ref().downgrade();
353 Disposable::create(move || {
354 if let Some(this) = weak.upgrade() {
355 select(&this).remove(token);
356 }
357 })
358 }
359
360 pub fn closed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
363 self.subscribe::<dyn Fn()>(|popup| &popup.closed, Rc::new(handler))
364 }
365
366 pub fn opened(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
369 self.subscribe::<dyn Fn()>(|popup| &popup.opened, Rc::new(handler))
370 }
371
372 pub fn closing(&self, handler: impl Fn(&CancelEventArgs) + 'static) -> Rc<dyn IDisposable> {
375 self.subscribe::<dyn Fn(&CancelEventArgs)>(|popup| &popup.closing, Rc::new(handler))
376 }
377
378 pub fn popup_host_changed(
381 &self,
382 handler: impl Fn(Option<&Rc<dyn IPopupHost>>) + 'static,
383 ) -> Rc<dyn IDisposable> {
384 self.subscribe::<dyn Fn(Option<&Rc<dyn IPopupHost>>)>(|popup| &popup.popup_host_changed_handler, Rc::new(handler))
385 }
386
387 fn open_state(&self) -> Option<Rc<PopupOpenState>> {
388 self.open_state.borrow().clone()
389 }
390
391 pub fn host(&self) -> Option<Rc<dyn IPopupHost>> {
393 self.open_state().map(|open_state| open_state.popup_host.clone())
394 }
395
396 pub fn opened_popups(&self) -> Vec<Ref<Popup>> {
399 self.opened_popups.borrow().clone()
400 }
401
402 pub fn window_manager_add_shadow_hint(&self) -> bool {
405 self.get_value(Self::window_manager_add_shadow_hint_property())
406 }
407
408 pub fn set_window_manager_add_shadow_hint(&self, value: bool) {
409 self.set_value(Self::window_manager_add_shadow_hint_property(), value)
410 }
411
412 pub fn child(&self) -> Option<Ref<Control>> {
414 self.get_value(Self::child_property())
415 }
416
417 pub fn set_child(&self, value: impl Into<Nullable<Control>>) {
418 self.set_value(Self::child_property(), value.into().0)
419 }
420
421 pub fn dependency_resolver(&self) -> Option<Rc<dyn IFerroDependencyResolver>> {
427 self.dependency_resolver.borrow().clone()
428 }
429
430 pub fn set_dependency_resolver(&self, value: Option<Rc<dyn IFerroDependencyResolver>>) {
431 *self.dependency_resolver.borrow_mut() = value;
432 }
433
434 pub fn inherits_transform(&self) -> bool {
437 self.get_value(Self::inherits_transform_property())
438 }
439
440 pub fn set_inherits_transform(&self, value: bool) {
441 self.set_value(Self::inherits_transform_property(), value)
442 }
443
444 pub fn is_light_dismiss_enabled(&self) -> bool {
449 self.get_value(Self::is_light_dismiss_enabled_property())
450 }
451
452 pub fn set_is_light_dismiss_enabled(&self, value: bool) {
453 self.set_value(Self::is_light_dismiss_enabled_property(), value)
454 }
455
456 pub fn is_open(&self) -> bool {
458 self.get_value(Self::is_open_property())
459 }
460
461 pub fn set_is_open(&self, value: bool) {
462 self.set_value(Self::is_open_property(), value)
463 }
464
465 pub fn placement_anchor(&self) -> PopupAnchor {
468 self.get_value(Self::placement_anchor_property())
469 }
470
471 pub fn set_placement_anchor(&self, value: PopupAnchor) {
472 self.set_value(Self::placement_anchor_property(), value)
473 }
474
475 pub fn placement_constraint_adjustment(&self) -> PopupPositionerConstraintAdjustment {
478 self.get_value(Self::placement_constraint_adjustment_property())
479 }
480
481 pub fn set_placement_constraint_adjustment(&self, value: PopupPositionerConstraintAdjustment) {
482 self.set_value(Self::placement_constraint_adjustment_property(), value)
483 }
484
485 pub fn placement_gravity(&self) -> PopupGravity {
488 self.get_value(Self::placement_gravity_property())
489 }
490
491 pub fn set_placement_gravity(&self, value: PopupGravity) {
492 self.set_value(Self::placement_gravity_property(), value)
493 }
494
495 pub fn placement(&self) -> PlacementMode {
498 self.get_value(Self::placement_property())
499 }
500
501 pub fn set_placement(&self, value: PlacementMode) {
502 self.set_value(Self::placement_property(), value)
503 }
504
505 pub fn placement_rect(&self) -> Option<Rect> {
516 self.get_value(Self::placement_rect_property())
517 }
518
519 pub fn set_placement_rect(&self, value: Option<Rect>) {
520 self.set_value(Self::placement_rect_property(), value)
521 }
522
523 pub fn placement_target(&self) -> Option<Ref<Control>> {
525 ElementRef::resolve(&self.get_value(Self::placement_target_property()))
526 }
527
528 pub fn set_placement_target(&self, value: impl Into<Nullable<Control>>) {
529 self.set_value(Self::placement_target_property(), ElementRef::from_nullable(value.into().0))
530 }
531
532 pub fn custom_popup_placement_callback(&self) -> Option<CustomPopupPlacementCallback> {
535 self.get_value(Self::custom_popup_placement_callback_property()).map(|value| value.0)
536 }
537
538 pub fn set_custom_popup_placement_callback(&self, value: Option<CustomPopupPlacementCallback>) {
539 self.set_value(
540 Self::custom_popup_placement_callback_property(),
541 value.map(CustomPopupPlacementCallbackValue),
542 )
543 }
544
545 pub fn overlay_dismiss_event_pass_through(&self) -> bool {
553 self.get_value(Self::overlay_dismiss_event_pass_through_property())
554 }
555
556 pub fn set_overlay_dismiss_event_pass_through(&self, value: bool) {
557 self.set_value(Self::overlay_dismiss_event_pass_through_property(), value)
558 }
559
560 pub fn overlay_input_pass_through_element(&self) -> Option<Ref<InputElement>> {
563 ElementRef::resolve(&self.get_value(Self::overlay_input_pass_through_element_property()))
564 }
565
566 pub fn set_overlay_input_pass_through_element(&self, value: impl Into<Nullable<InputElement>>) {
567 self.set_value(Self::overlay_input_pass_through_element_property(), ElementRef::from_nullable(value.into().0))
568 }
569
570 pub fn horizontal_offset(&self) -> f64 {
573 self.get_value(Self::horizontal_offset_property())
574 }
575
576 pub fn set_horizontal_offset(&self, value: f64) {
577 self.set_value(Self::horizontal_offset_property(), value)
578 }
579
580 pub fn vertical_offset(&self) -> f64 {
583 self.get_value(Self::vertical_offset_property())
584 }
585
586 pub fn set_vertical_offset(&self, value: f64) {
587 self.set_value(Self::vertical_offset_property(), value)
588 }
589
590 pub fn topmost(&self) -> bool {
592 self.get_value(Self::topmost_property())
593 }
594
595 pub fn set_topmost(&self, value: bool) {
596 self.set_value(Self::topmost_property(), value)
597 }
598
599 pub fn takes_focus_from_native_control(&self) -> bool {
609 self.get_value(Self::takes_focus_from_native_control_property())
610 }
611
612 pub fn set_takes_focus_from_native_control(&self, value: bool) {
613 self.set_value(Self::takes_focus_from_native_control_property(), value)
614 }
615
616 pub fn should_use_overlay_layer(&self) -> bool {
624 self.get_value(Self::should_use_overlay_layer_property())
625 }
626
627 pub fn set_should_use_overlay_layer(&self, value: bool) {
628 self.set_value(Self::should_use_overlay_layer_property(), value)
629 }
630
631 pub fn is_using_overlay_layer(&self) -> bool {
634 self.is_using_overlay_layer.get()
635 }
636
637 fn set_is_using_overlay_layer(&self, value: bool) {
638 self.set_and_raise_cell(Self::is_using_overlay_layer_property(), &self.is_using_overlay_layer, value);
639 }
640
641 pub fn open(&self) {
643 if self.open_state.borrow().is_some() {
645 return;
646 }
647
648 let Some(placement_target) =
649 self.placement_target().or_else(|| self.find_logical_ancestor_of_type::<Control>(false))
650 else {
651 self.is_open_requested.set(true);
652 return;
653 };
654
655 let Some(top_level) = TopLevel::get_top_level(Some(&placement_target)) else {
656 self.is_open_requested.set(true);
657 return;
658 };
659
660 self.is_open_requested.set(false);
661
662 let this = self.to_ref();
663 let weak = this.downgrade();
664 let popup_host = OverlayPopupHost::create_popup_host(
665 &placement_target,
666 self.dependency_resolver(),
667 self.should_use_overlay_layer(),
668 );
669 let handler_cleanup = Rc::new(CompositeDisposable::with_capacity(7));
670
671 self.update_host_sizing(&popup_host, Some(top_level.clone()), &placement_target);
672 popup_host.set_topmost(self.topmost());
673 popup_host.set_is_hit_test_visible(self.is_hit_test_visible());
674 popup_host.set_child(self.child());
675 popup_host.as_control().set_parent(this.clone());
676
677 if self.inherits_transform() {
678 let weak = weak.clone();
679 handler_cleanup.add(TransformTrackingHelper::track(&placement_target.clone().upcast(), true, move |v, m| {
680 if let Some(this) = weak.upgrade() {
681 this.placement_target_transform_changed(v, m);
682 }
683 }));
684 } else {
685 popup_host.set_transform(None);
686 }
687
688 if let Some(top_level_popup) = popup_host.as_popup_root() {
689 handler_cleanup.add(top_level_popup.bind_typed_value(
690 ThemeVariant::actual_theme_variant_property(),
691 self.get_binding_observable(ThemeVariant::actual_theme_variant_property()),
692 BindingPriority::LocalValue,
693 ));
694 }
695
696 self.update_host_position(&popup_host, &placement_target);
697
698 let template_applied_subscription = popup_host.template_applied(Rc::new({
699 let weak = weak.clone();
700 move |e| {
701 if let Some(this) = weak.upgrade() {
702 this.root_template_applied(e);
703 }
704 }
705 }));
706 handler_cleanup.add(template_applied_subscription.clone());
707
708 handler_cleanup.add(placement_target.detached_from_visual_tree({
709 let weak = weak.clone();
710 move |e| {
711 if let Some(this) = weak.upgrade() {
712 this.target_detached(e);
713 }
714 }
715 }));
716
717 self.subscribe_to_top_level(&top_level, &placement_target, &handler_cleanup);
718
719 if self.placement() != PlacementMode::Pointer {
722 let weak = weak.clone();
723 handler_cleanup.add(Disposable::create(move || {
724 if let Some(this) = weak.upgrade() {
725 this.unsubscribe_placement_target_transforms();
726 }
727 }));
728 }
729
730 if let Some(input_manager) = InputManager::instance() {
731 let weak = weak.clone();
732 handler_cleanup.add(input_manager.process().subscribe(Rc::new(AnonymousObserver::new(
733 move |e: Rc<dyn IRawInputEventArgs>| {
734 if let Some(this) = weak.upgrade() {
735 this.listen_for_non_client_click(&*e);
736 }
737 },
738 ))));
739 }
740
741 let cleanup_popup = Disposable::create({
742 let popup_host = popup_host.clone();
743 let handler_cleanup = handler_cleanup.clone();
744 move || {
745 handler_cleanup.dispose();
746
747 popup_host.set_child(None);
748 popup_host.hide();
749
750 popup_host.as_control().set_parent(None);
751 popup_host.dispose();
752 }
753 });
754
755 if self.is_light_dismiss_enabled() {
756 let dismiss_layer = LightDismissOverlayLayer::get_light_dismiss_overlay_layer(&placement_target);
757
758 if let Some(dismiss_layer) = dismiss_layer {
759 handler_cleanup.add(dismiss_layer.register(self.overlay_input_pass_through_element().as_ref()));
760
761 let token = dismiss_layer.add_handler(InputElement::pointer_pressed_event(), {
762 let weak = weak.clone();
763 move |_, e: &PointerPressedEventArgs| {
764 if let Some(this) = weak.upgrade() {
765 this.pointer_pressed_dismiss_overlay(e);
766 }
767 }
768 });
769 let weak_layer = dismiss_layer.downgrade();
770 handler_cleanup.add(Disposable::create(move || {
771 if let Some(dismiss_layer) = weak_layer.upgrade() {
772 dismiss_layer.remove_handler(InputElement::pointer_pressed_event(), token);
773 }
774 }));
775 }
776 }
777
778 let parent_popup = find_parent_popup(&placement_target);
779 let open_state = Rc::new(PopupOpenState::new(
780 placement_target,
781 &top_level,
782 popup_host.clone(),
783 cleanup_popup,
784 parent_popup.as_ref(),
785 template_applied_subscription,
786 ));
787 *self.open_state.borrow_mut() = Some(open_state);
788
789 if self.placement() != PlacementMode::Pointer {
790 self.subscribe_placement_target_transforms();
791 }
792
793 if let Some(parent_popup) = parent_popup {
794 parent_popup.add_opened_popup(&this);
795 } else {
796 top_level.add_opened_popup(&this);
797 }
798
799 window_manager_add_shadow_hint_changed(&popup_host, self.window_manager_add_shadow_hint());
800
801 popup_host.show();
802 self.set_is_using_overlay_layer(popup_host.as_overlay_popup_host().is_some());
803
804 if self.takes_focus_from_native_control() {
805 popup_host.take_focus();
806 }
807
808 {
809 let _ignore = self.begin_ignoring_is_open();
810 self.set_current_value(Self::is_open_property(), true);
811 }
812
813 for (_, handler) in self.opened.snapshot().iter() {
814 handler();
815 }
816
817 let host = self.host();
818 for (_, handler) in self.popup_host_changed_handler.snapshot().iter() {
819 handler(host.as_ref());
820 }
821 }
822
823 fn subscribe_to_top_level(
826 &self,
827 top_level: &Ref<TopLevel>,
828 placement_target: &Ref<Control>,
829 handler_cleanup: &CompositeDisposable,
830 ) {
831 let weak = self.to_ref().downgrade();
832
833 if let Some(window) = top_level.cast::<Window>() {
834 let window: &WindowBase = &window;
835 if window.platform_impl().is_some() {
836 handler_cleanup.add(window.deactivated({
837 let weak = weak.clone();
838 move || {
839 if let Some(this) = weak.upgrade() {
840 this.window_deactivated();
841 }
842 }
843 }));
844
845 handler_cleanup.add(top_level.platform_lost_focus({
846 let weak = weak.clone();
847 move || {
848 if let Some(this) = weak.upgrade() {
849 this.window_lost_focus();
850 }
851 }
852 }));
853
854 if self.placement() != PlacementMode::Pointer {
856 handler_cleanup.add(top_level.platform_position_changed({
857 let weak = weak.clone();
858 move |pp| {
859 if let Some(this) = weak.upgrade() {
860 this.window_position_changed(pp);
861 }
862 }
863 }));
864
865 handler_cleanup.add(placement_target.layout_updated({
867 let weak = weak.clone();
868 move || {
869 if let Some(this) = weak.upgrade() {
870 this.placement_target_layout_updated();
871 }
872 }
873 }));
874 }
875
876 return;
877 }
878 }
879
880 if let Some(parent_popup_root) = top_level.cast::<PopupRoot>() {
881 handler_cleanup.add(parent_popup_root.position_changed({
882 let weak = weak.clone();
883 move |e| {
884 if let Some(this) = weak.upgrade() {
885 this.parent_popup_position_changed(e);
886 }
887 }
888 }));
889
890 if let Some(popup) = parent_popup_root.parent().and_then(|parent| parent.cast::<Popup>()) {
891 handler_cleanup.add(popup.closed({
892 let weak = weak.clone();
893 move || {
894 if let Some(this) = weak.upgrade() {
895 this.parent_closed();
896 }
897 }
898 }));
899 }
900 } else if let Some(platform_impl) = top_level.platform_impl() {
901 if platform_impl.as_window_base_impl().is_some() {
902 handler_cleanup.add(top_level.platform_lost_focus({
903 let weak = weak.clone();
904 move || {
905 if let Some(this) = weak.upgrade() {
906 this.top_level_lost_platform_focus();
907 }
908 }
909 }));
910
911 handler_cleanup.add(top_level.platform_deactivated({
912 let weak = weak.clone();
913 move || {
914 if let Some(this) = weak.upgrade() {
915 this.window_base_deactivated();
916 }
917 }
918 }));
919 } else {
920 handler_cleanup.add(top_level.platform_lost_focus({
921 let weak = weak.clone();
922 move || {
923 if let Some(this) = weak.upgrade() {
924 this.top_level_lost_platform_focus();
925 }
926 }
927 }));
928 }
929 }
930 }
931
932 pub fn close(&self) {
934 self.close_core()
935 }
936
937 pub fn get_takes_focus_from_native_control(control: &Control) -> bool {
940 control.get_value(Self::takes_focus_from_native_control_property())
941 }
942
943 pub fn set_takes_focus_from_native_control_on(control: &Control, value: bool) {
946 control.set_value(Self::takes_focus_from_native_control_property(), value)
947 }
948
949 pub fn set_popup_parent(&self, new_parent: Option<&Ref<Control>>) {
952 let new_parent_element: Option<Ref<StyledElement>> = new_parent.map(|parent| parent.clone().upcast());
953 if self.parent().is_some() && self.parent() != new_parent_element {
954 self.set_parent(None);
955 }
956
957 if self.parent().is_none() || self.placement_target().as_ref() != new_parent {
958 self.set_parent(Nullable(new_parent_element));
959 self.set_templated_parent(Nullable(new_parent.and_then(|parent| parent.templated_parent())));
960 }
961 }
962
963 fn update_host_position(&self, popup_host: &Rc<dyn IPopupHost>, placement_target: &Ref<Control>) {
964 popup_host.configure_position(PopupPositionRequest::new_with(
965 placement_target.clone().upcast(),
966 self.placement(),
967 Point::new(self.horizontal_offset(), self.vertical_offset()),
968 self.placement_anchor(),
969 self.placement_gravity(),
970 self.placement_constraint_adjustment(),
971 Some(self.placement_rect().unwrap_or_else(|| Rect::from_size(placement_target.bounds().size()))),
972 self.custom_popup_placement_callback(),
973 ));
974 }
975
976 fn update_host_sizing(
977 &self,
978 popup_host: &Rc<dyn IPopupHost>,
979 top_level: Option<Ref<TopLevel>>,
980 placement_target: &Ref<Control>,
981 ) {
982 let mut scale_x = 1.0;
983 let mut scale_y = 1.0;
984
985 let matrix = if self.inherits_transform() {
986 top_level.and_then(|top_level| placement_target.transform_to_visual(&top_level))
987 } else {
988 None
989 };
990
991 if let Some(m) = matrix {
992 scale_x = (m.m11 * m.m11 + m.m12 * m.m12).sqrt();
993 scale_y = (m.m11 * m.m11 + m.m12 * m.m12).sqrt();
994
995 let transform: Ref<Transform> = ScaleTransform::with_scale(scale_x, scale_y).upcast();
1000 popup_host.set_transform(Some(transform));
1001 } else {
1002 popup_host.set_transform(None);
1003 }
1004
1005 popup_host.set_width(self.width() * scale_x);
1006 popup_host.set_min_width(self.min_width() * scale_x);
1007 popup_host.set_max_width(self.max_width() * scale_x);
1008 popup_host.set_height(self.height() * scale_y);
1009 popup_host.set_min_height(self.min_height() * scale_y);
1010 popup_host.set_max_height(self.max_height() * scale_y);
1011 }
1012
1013 fn subscribe_placement_target_transforms(&self) {
1030 self.unsubscribe_placement_target_transforms();
1031 let Some(open_state) = self.open_state() else { return };
1032 let placement_target = open_state.placement_target();
1033 self.last_placement_target_rect.set(placement_target_rect_in_root(&placement_target));
1034
1035 let mut subscriptions = Vec::new();
1036 let mut visual = Some(placement_target.upcast::<Visual>());
1037 while let Some(current) = visual {
1038 let weak = self.to_ref().downgrade();
1039 subscriptions.push(current.property_changed(move |e| {
1040 if let Some(this) = weak.upgrade() {
1041 this.placement_target_ancestor_property_changed(e);
1042 }
1043 }));
1044 if let Some(mutable_transform) =
1045 current.render_transform().as_ref().and_then(|render_transform| render_transform.as_mutable_transform())
1046 {
1047 let weak = self.to_ref().downgrade();
1048 subscriptions.push(mutable_transform.changed(Rc::new(move || {
1049 if let Some(this) = weak.upgrade() {
1050 this.enqueue_for_placement_target_move();
1051 }
1052 })));
1053 }
1054
1055 visual = current.get_visual_parent();
1056 }
1057 *self.placement_target_transform_subscriptions.borrow_mut() = subscriptions;
1058 }
1059
1060 fn unsubscribe_placement_target_transforms(&self) {
1061 let subscriptions = std::mem::take(&mut *self.placement_target_transform_subscriptions.borrow_mut());
1062 for subscription in subscriptions {
1063 subscription.dispose();
1064 }
1065 }
1066
1067 fn placement_target_ancestor_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
1068 if !e.is_effective_value_change() {
1069 return;
1070 }
1071 if e.property() == Visual::render_transform_property().as_property() {
1072 let last = self.last_placement_target_rect.get();
1074 self.subscribe_placement_target_transforms();
1075 self.last_placement_target_rect.set(last);
1076 self.enqueue_for_placement_target_move();
1077 } else if e.property() == Visual::render_transform_origin_property().as_property() {
1078 self.enqueue_for_placement_target_move();
1079 }
1080 }
1081
1082 fn enqueue_for_placement_target_move(&self) {
1083 if self.queued_for_placement_target_move.get() {
1084 return;
1085 }
1086 self.queued_for_placement_target_move.set(true);
1087 let this = self.to_ref();
1088 Dispatcher::ui_thread().post_local(move || this.placement_target_moved(), DispatcherPriority::AFTER_RENDER);
1089 }
1090
1091 fn placement_target_moved(&self) {
1092 self.queued_for_placement_target_move.set(false);
1093 let Some(open_state) = self.open_state() else { return };
1094 let placement_target = open_state.placement_target();
1095 let rect = placement_target_rect_in_root(&placement_target);
1096 if rect == self.last_placement_target_rect.get() {
1097 return;
1098 }
1099 self.last_placement_target_rect.set(rect);
1100 self.update_host_position(&open_state.popup_host, &placement_target);
1101 }
1102
1103 fn handle_position_change(&self) {
1104 if let Some(open_state) = self.open_state() {
1105 self.update_host_position(&open_state.popup_host, &open_state.placement_target());
1106 }
1107 }
1108
1109 fn is_open_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
1111 if !self.ignore_is_open_changed.get() {
1112 if e.get_new_value::<bool>() {
1113 self.open();
1114 } else {
1115 self.close();
1116 }
1117 }
1118 }
1119
1120 fn child_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
1122 let (old_value, new_value) = e.get_old_and_new_value::<Option<Ref<Control>>>();
1123
1124 self.logical_children().clear();
1125
1126 if let Some(old_value) = old_value {
1127 old_value.set_parent(None);
1128 }
1129
1130 if let Some(new_value) = new_value {
1131 new_value.set_parent(self.to_ref());
1132 self.logical_children().add(new_value.upcast());
1133 }
1134 }
1135
1136 fn close_core(&self) {
1137 let closing_args = CancelEventArgs::new();
1138 for (_, handler) in self.closing.snapshot().iter() {
1139 handler(&closing_args);
1140 }
1141 if closing_args.cancel() {
1142 return;
1143 }
1144
1145 self.is_open_requested.set(false);
1146 let Some(open_state) = self.open_state() else {
1147 let _ignore = self.begin_ignoring_is_open();
1148 self.set_current_value(Self::is_open_property(), false);
1149 return;
1150 };
1151
1152 let this = self.to_ref();
1153 if let Some(parent_popup) = open_state.parent_popup() {
1154 parent_popup.remove_opened_popup(&this);
1155 } else if let Some(top_level) = open_state.top_level() {
1156 top_level.remove_opened_popup(&this);
1157 }
1158
1159 open_state.dispose();
1160 *self.open_state.borrow_mut() = None;
1161
1162 for (_, handler) in self.popup_host_changed_handler.snapshot().iter() {
1163 handler(None);
1164 }
1165
1166 {
1167 let _ignore = self.begin_ignoring_is_open();
1168 self.set_current_value(Self::is_open_property(), false);
1169 }
1170
1171 for (_, handler) in self.closed.snapshot().iter() {
1172 handler();
1173 }
1174 }
1175
1176 fn listen_for_non_client_click(&self, e: &dyn IRawInputEventArgs) {
1177 let mouse = e.downcast_ref::<RawPointerEventArgs>();
1178
1179 if self.is_light_dismiss_enabled()
1180 && mouse.is_some_and(|mouse| mouse.type_() == RawPointerEventType::NonClientLeftButtonDown)
1181 {
1182 self.close_core();
1183 }
1184 }
1185
1186 fn pointer_pressed_dismiss_overlay(&self, e: &PointerPressedEventArgs) {
1187 if !self.is_light_dismiss_enabled() {
1188 return;
1189 }
1190 let Some(v) = e.source().and_then(|source| source.cast::<Visual>()) else { return };
1191
1192 if !self.is_child_or_this(&v) {
1193 if self.overlay_dismiss_event_pass_through() {
1194 pass_through_event(e);
1195 }
1196
1197 if self.is_open() {
1199 self.close_core();
1200 }
1201 }
1202 }
1203
1204 fn target_detached(&self, _e: &VisualTreeAttachmentEventArgs) {
1205 self.close();
1206 }
1207
1208 fn root_template_applied(&self, _e: &TemplateAppliedEventArgs) {
1209 let Some(open_state) = self.open_state() else { return };
1210
1211 let popup_host = open_state.popup_host.clone();
1212
1213 open_state.unsubscribe_template_applied();
1214
1215 open_state.set_presenter_subscription(None);
1216
1217 if self.templated_parent().is_some() {
1221 if let Some(presenter) = popup_host.presenter() {
1222 presenter.apply_template();
1223
1224 let weak = self.to_ref().downgrade();
1225 let presenter_object: &FerroObject = &presenter;
1226 let presenter_subscription = FerroObjectExtensions::get_observable(
1227 presenter_object,
1228 ContentPresenter::child_property(),
1229 )
1230 .subscribe_fn(
1231 move |control: Option<Ref<Control>>| {
1232 if let Some(this) = weak.upgrade() {
1233 this.set_templated_parent_and_apply_child_templates(control.as_ref());
1234 }
1235 },
1236 );
1237
1238 open_state.set_presenter_subscription(Some(presenter_subscription));
1239 }
1240 }
1241 }
1242
1243 fn set_templated_parent_and_apply_child_templates(&self, control: Option<&Ref<Control>>) {
1244 if let Some(control) = control {
1245 TemplatedControl::apply_templated_parent(control, self.templated_parent().as_ref());
1246 }
1247 }
1248
1249 fn is_child_or_this(&self, child: &Visual) -> bool {
1250 let Some(open_state) = self.open_state() else { return false };
1251
1252 let popup_host: Ref<Visual> = open_state.popup_host.as_control().upcast();
1253
1254 let mut root = child.visual_root();
1255
1256 while let Some(current) = root {
1257 let Some(element) = current.downcast_ref::<InputElement>() else { break };
1258 let Some(hosted_root) = element.as_hosted_visual_tree_root() else { break };
1259
1260 if current == popup_host {
1261 return true;
1262 }
1263
1264 root = hosted_root.host().and_then(|host| host.visual_root());
1265 }
1266
1267 false
1268 }
1269
1270 pub fn is_inside_popup(&self, visual: &Visual) -> bool {
1272 let Some(open_state) = self.open_state() else { return false };
1273
1274 let popup_host = open_state.popup_host.as_control();
1275
1276 popup_host.is_visual_ancestor_of(visual)
1277 }
1278
1279 pub fn is_pointer_over_popup(&self) -> bool {
1281 self.open_state().is_some_and(|open_state| open_state.popup_host.as_control().is_pointer_over())
1282 }
1283
1284 fn window_deactivated(&self) {
1285 if self.is_light_dismiss_enabled() {
1286 self.close();
1287 }
1288 }
1289
1290 fn window_base_deactivated(&self) {
1291 if self.is_light_dismiss_enabled() {
1292 self.close();
1293 }
1294 }
1295
1296 fn parent_closed(&self) {
1297 if self.is_light_dismiss_enabled() {
1298 self.close();
1299 }
1300 }
1301
1302 fn top_level_lost_platform_focus(&self) {
1303 if self.is_light_dismiss_enabled() {
1304 self.close();
1305 }
1306 }
1307
1308 fn placement_target_transform_changed(&self, _v: &Ref<Visual>, _matrix: Option<Matrix>) {
1309 if let Some(open_state) = self.open_state() {
1310 self.update_host_sizing(&open_state.popup_host, open_state.top_level(), &open_state.placement_target());
1311 }
1312 }
1313
1314 fn window_lost_focus(&self) {
1315 if self.is_light_dismiss_enabled() {
1316 self.close();
1317 }
1318 }
1319
1320 fn window_position_changed(&self, _pp: PixelPoint) {
1321 self.handle_position_change();
1322 }
1323
1324 fn placement_target_layout_updated(&self) {
1325 let Some(open_state) = self.open_state() else { return };
1326
1327 let new_bounds = open_state.placement_target().bounds();
1330 if new_bounds == open_state.last_placement_target_bounds.get() {
1331 return;
1332 }
1333
1334 open_state.last_placement_target_bounds.set(new_bounds);
1335 self.update_host_position(&open_state.popup_host, &open_state.placement_target());
1336 }
1337
1338 fn parent_popup_position_changed(&self, _e: &PixelPointEventArgs) {
1339 self.handle_position_change();
1340 }
1341
1342 fn begin_ignoring_is_open(&self) -> IgnoreIsOpenScope<'_> {
1343 self.ignore_is_open_changed.set(true);
1344 IgnoreIsOpenScope { owner: self }
1345 }
1346
1347 pub fn to_popup_host_provider(&self) -> Rc<dyn IPopupHostProvider> {
1350 Rc::new(PopupHostProvider(self.to_ref()))
1351 }
1352
1353 pub fn add_opened_popup(&self, popup: &Ref<Popup>) {
1356 self.opened_popups.borrow_mut().push(popup.clone());
1357 }
1358
1359 pub fn remove_opened_popup(&self, popup: &Ref<Popup>) {
1362 let mut opened_popups = self.opened_popups.borrow_mut();
1363 if let Some(index) = opened_popups.iter().position(|p| p == popup) {
1364 opened_popups.remove(index);
1365 }
1366 }
1367}
1368
1369fn window_manager_add_shadow_hint_changed(host: &Rc<dyn IPopupHost>, hint: bool) {
1370 if let Some(pr) = host.as_popup_root() {
1371 pr.set_window_manager_add_shadow_hint(hint);
1372 }
1373}
1374
1375fn pass_through_event(e: &PointerPressedEventArgs) {
1376 let Some(layer) = e.source().and_then(|source| source.cast::<LightDismissOverlayLayer>()) else { return };
1377 let Some(root) = layer.visual_root().and_then(|root| root.cast::<InputElement>()) else { return };
1378
1379 let p = e.get_current_point(Some(&root));
1380 let layer_visual: Ref<Visual> = layer.upcast();
1381 let hit = root.input_hit_test_filtered(p.position, &|x: &Visual| !x.to_ref().ptr_eq(&layer_visual), true);
1382
1383 if let Some(hit) = hit {
1384 e.pointer().capture(Some(&hit));
1385 hit.raise_event(e);
1386 e.set_handled(true);
1387 }
1388}
1389
1390fn find_parent_popup(placement_target: &Visual) -> Option<Ref<Popup>> {
1391 for visual in placement_target.get_self_and_visual_ancestors() {
1392 if visual.is::<PopupRoot>() || visual.is::<OverlayPopupHost>() {
1393 return visual
1394 .cast::<StyledElement>()
1395 .and_then(|element| element.parent())
1396 .and_then(|parent| parent.cast::<Popup>());
1397 }
1398 }
1399
1400 None
1401}
1402
1403fn placement_target_rect_in_root(placement_target: &Ref<Control>) -> Option<Rect> {
1406 let visual: Ref<Visual> = placement_target.clone().upcast();
1407 let root = visual.visual_root()?;
1408 let matrix = visual.transform_to_visual(&root)?;
1409 Some(Rect::from_position_size(Point::default(), visual.bounds().size()).transform_to_aabb(matrix))
1410}
1411
1412struct PopupOpenState {
1414 cleanup: Rc<dyn IDisposable>,
1415 presenter_cleanup: RefCell<Option<Rc<dyn IDisposable>>>,
1416 template_applied_subscription: Rc<dyn IDisposable>,
1417 placement_target: RefCell<Ref<Control>>,
1418 last_placement_target_bounds: Cell<Rect>,
1419 top_level: WeakRef<TopLevel>,
1420 parent_popup: Option<WeakRef<Popup>>,
1421 popup_host: Rc<dyn IPopupHost>,
1422}
1423
1424impl PopupOpenState {
1425 fn new(
1426 placement_target: Ref<Control>,
1427 top_level: &Ref<TopLevel>,
1428 popup_host: Rc<dyn IPopupHost>,
1429 cleanup: Rc<dyn IDisposable>,
1430 parent_popup: Option<&Ref<Popup>>,
1431 template_applied_subscription: Rc<dyn IDisposable>,
1432 ) -> Self {
1433 Self {
1434 cleanup,
1435 presenter_cleanup: RefCell::new(None),
1436 template_applied_subscription,
1437 last_placement_target_bounds: Cell::new(placement_target.bounds()),
1438 placement_target: RefCell::new(placement_target),
1439 top_level: top_level.downgrade(),
1440 parent_popup: parent_popup.map(Ref::downgrade),
1441 popup_host,
1442 }
1443 }
1444
1445 fn top_level(&self) -> Option<Ref<TopLevel>> {
1447 self.top_level.upgrade()
1448 }
1449
1450 fn parent_popup(&self) -> Option<Ref<Popup>> {
1451 self.parent_popup.as_ref().and_then(WeakRef::upgrade)
1452 }
1453
1454 fn placement_target(&self) -> Ref<Control> {
1455 self.placement_target.borrow().clone()
1456 }
1457
1458 fn set_placement_target(&self, value: Ref<Control>) {
1459 self.last_placement_target_bounds.set(value.bounds());
1460 *self.placement_target.borrow_mut() = value;
1461 }
1462
1463 fn unsubscribe_template_applied(&self) {
1465 self.template_applied_subscription.dispose();
1466 }
1467
1468 fn set_presenter_subscription(&self, presenter_cleanup: Option<Rc<dyn IDisposable>>) {
1469 let old = self.presenter_cleanup.replace(presenter_cleanup);
1470 if let Some(old) = old {
1471 old.dispose();
1472 }
1473 }
1474}
1475
1476impl IDisposable for PopupOpenState {
1477 fn dispose(&self) {
1478 let presenter_cleanup = self.presenter_cleanup.borrow().clone();
1479 if let Some(presenter_cleanup) = presenter_cleanup {
1480 presenter_cleanup.dispose();
1481 }
1482
1483 self.cleanup.dispose();
1484 }
1485}
1486
1487struct PopupHostProvider(Ref<Popup>);
1489
1490impl IPopupHostProvider for PopupHostProvider {
1491 fn popup_host(&self) -> Option<Rc<dyn IPopupHost>> {
1492 self.0.host()
1493 }
1494
1495 fn popup_host_changed(&self, handler: Rc<dyn Fn(Option<&Rc<dyn IPopupHost>>)>) -> Rc<dyn IDisposable> {
1496 self.0.popup_host_changed(move |host| handler(host))
1497 }
1498}