ferroui_controls/primitives/
overlay_popup_host.rs1use super::i_popup_host::subscribe_template_applied;
2use super::popup_positioning::{
3 IManagedPopupPositionerPopup, IPopupPositioner, ManagedPopupPositioner, ManagedPopupPositionerScreenInfo,
4 PopupPositionRequest,
5};
6use super::{IPopupHost, PopupOverlayLayer, PopupRoot, TemplateAppliedEventArgs, TemplatedControlImpl};
7use crate::presenters::ContentPresenter;
8use crate::{Canvas, ContentControl, ContentControlImpl, Control, ControlImpl, TopLevel};
9use ferroui_base::input::{
10 IFocusScope, IKeyboardNavigationHandler, InputElementImpl, KeyboardNavigation, KeyboardNavigationMode,
11};
12use ferroui_base::interactivity::{Interactive, InteractiveImpl};
13use ferroui_base::layout::{LayoutableImpl, LayoutableImplExt};
14use ferroui_base::media::{MediaContext, Transform};
15use ferroui_base::reactive::IDisposable;
16use ferroui_base::{
17 ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroLocator, FerroObjectImpl, FerroObjectImplExt,
18 IFerroDependencyResolver, LocatorExtensions, Nullable, Point, Rect, Ref, Size, StyledElementImpl, StyledProperty,
19 Thickness, Visual, VisualImpl, WeakRef,
20};
21use std::cell::{Cell, OnceCell, RefCell};
22use std::rc::Rc;
23
24#[repr(C)]
27pub struct OverlayPopupHost {
28 base: ContentControl,
29 overlay_layer: WeakRef<PopupOverlayLayer>,
30 positioner: OnceCell<Rc<dyn IPopupPositioner>>,
31 keyboard_navigation_handler: RefCell<Option<Rc<dyn IKeyboardNavigationHandler>>>,
32 last_requested_position: Cell<Point>,
33 popup_position_request: RefCell<Option<PopupPositionRequest>>,
34 popup_size: Cell<Size>,
35 child_margin: Cell<Thickness>,
36 needs_update: Cell<bool>,
37}
38
39ferro_class!(OverlayPopupHost: ContentControl);
40ferro_impl_classes!(
41 OverlayPopupHost: StyledElementImpl,
42 VisualImpl,
43 ControlImpl,
44 TemplatedControlImpl,
45 ContentControlImpl
46);
47
48impl IFocusScope for OverlayPopupHost {}
49
50impl FerroObjectImpl for OverlayPopupHost {
51 fn constructed(this: &Self) {
52 Self::parent_constructed(this);
53
54 let popup: Rc<dyn IManagedPopupPositionerPopup> =
55 Rc::new(OverlayPopupHostPositionerPopup(this.to_ref().downgrade()));
56 let positioner: Rc<dyn IPopupPositioner> = Rc::new(ManagedPopupPositioner::new(popup));
57 if this.positioner.set(positioner).is_err() {
58 unreachable!("the host is constructed once");
59 }
60
61 let keyboard_navigation_handler = FerroLocator::current().get_service::<dyn IKeyboardNavigationHandler>();
62 if let Some(handler) = &keyboard_navigation_handler {
63 handler.set_owner(&this.to_ref().upcast());
64 }
65 *this.keyboard_navigation_handler.borrow_mut() = keyboard_navigation_handler;
66 }
67}
68
69impl InteractiveImpl for OverlayPopupHost {
70 fn interactive_parent(this: &Self) -> Option<Ref<Interactive>> {
71 this.parent().and_then(|parent| parent.cast::<Interactive>())
72 }
73}
74
75impl InputElementImpl for OverlayPopupHost {
76 fn is_focus_scope(_this: &Self) -> bool {
77 true
78 }
79}
80
81impl LayoutableImpl for OverlayPopupHost {
82 fn arrange_override(this: &Self, final_size: Size) -> Size {
83 let mut reposition = false;
84
85 let new_margin = this
86 .presenter()
87 .and_then(|presenter| presenter.child())
88 .map(|child| child.margin())
89 .unwrap_or_default();
90 if new_margin != this.child_margin.get() {
91 this.child_margin.set(new_margin);
92 reposition = true;
93 }
94
95 if this.popup_size.get() != final_size {
96 this.popup_size.set(final_size);
97 reposition = true;
98 }
99
100 if reposition {
101 this.needs_update.set(true);
102 this.update_position();
103 }
104 Self::parent_arrange_override(this, final_size)
105 }
106}
107
108ferroui_base::ferro_properties! { impl OverlayPopupHost {
109 ferro_property!(
110 pub fn transform_property() -> StyledProperty<Option<Ref<Transform>>> {
112 PopupRoot::transform_property().add_owner::<OverlayPopupHost>()
113 }
114 );
115} }
116
117impl OverlayPopupHost {
118 fn static_constructor() {
119 KeyboardNavigation::tab_navigation_property()
120 .override_default_value::<OverlayPopupHost>(KeyboardNavigationMode::Cycle);
121 }
122
123 pub fn new(overlay_layer: &Ref<PopupOverlayLayer>) -> Ref<Self> {
125 instantiate(Self {
126 base: ContentControl::construct(),
127 overlay_layer: overlay_layer.downgrade(),
128 positioner: OnceCell::new(),
129 keyboard_navigation_handler: RefCell::new(None),
130 last_requested_position: Cell::new(Point::default()),
131 popup_position_request: RefCell::new(None),
132 popup_size: Cell::new(Size::default()),
133 child_margin: Cell::new(Thickness::default()),
134 needs_update: Cell::new(false),
135 })
136 }
137
138 pub fn tests_keyboard_navigation_handler(&self) -> Option<Rc<dyn IKeyboardNavigationHandler>> {
140 self.keyboard_navigation_handler.borrow().clone()
141 }
142
143 pub fn set_child(&self, control: impl Into<Nullable<Control>>) {
145 self.set_content(control.into().0.map(Control::boxed));
146 }
147
148 pub fn hosted_visual_tree_root(&self) -> Option<Ref<Visual>> {
151 None
152 }
153
154 pub fn transform(&self) -> Option<Ref<Transform>> {
156 self.get_value(Self::transform_property())
157 }
158
159 pub fn set_transform(&self, value: impl Into<Nullable<Transform>>) {
160 self.set_value(Self::transform_property(), value.into().0)
161 }
162
163 pub fn dispose(&self) {
165 self.hide();
166 }
167
168 pub fn show(&self) {
170 if let Some(overlay_layer) = self.overlay_layer.upgrade() {
171 overlay_layer.children().add(self.to_ref());
172 }
173
174 let content = self.content().and_then(|content| Control::from_boxed(&content));
175 if content.is_some_and(|content| !content.is_attached_to_visual_tree()) {
176 self.update_layout();
178 }
179 }
180
181 pub fn hide(&self) {
183 if let Some(overlay_layer) = self.overlay_layer.upgrade() {
184 overlay_layer.children().remove(self.to_ref());
185 }
186 }
187
188 pub fn take_focus(&self) {
190 }
192
193 pub fn configure_position(&self, position_request: PopupPositionRequest) {
196 *self.popup_position_request.borrow_mut() = Some(position_request);
197 self.needs_update.set(true);
198 self.update_position();
199 }
200
201 fn update_position(&self) {
202 if !self.needs_update.get() {
203 return;
204 }
205 let Some(popup_position_request) = self.popup_position_request.borrow().clone() else { return };
206
207 self.needs_update.set(false);
208 let overlay_layer = self.overlay_layer.upgrade();
209 let top_level = overlay_layer
210 .and_then(|overlay_layer| TopLevel::get_top_level(Some(&overlay_layer)))
211 .expect("the popup overlay layer is in a top-level");
212 let positioner = self.positioner.get().expect("the positioner is created with the host");
213 positioner.update_request(
214 &top_level,
215 &popup_position_request,
216 self.popup_size.get(),
217 self.child_margin.get(),
218 self.flow_direction(),
219 );
220 }
221
222 pub fn to_popup_host(&self) -> Rc<dyn IPopupHost> {
225 Rc::new(OverlayPopupHostHost(self.to_ref()))
226 }
227
228 pub fn create_popup_host(
238 target: &Visual,
239 dependency_resolver: Option<Rc<dyn IFerroDependencyResolver>>,
240 should_use_overlay_layer: bool,
241 ) -> Rc<dyn IPopupHost> {
242 if !should_use_overlay_layer {
243 if let Some(top_level) = TopLevel::get_top_level(Some(target)) {
244 let popup_impl = top_level.platform_impl().and_then(|platform_impl| platform_impl.create_popup());
245 if let Some(popup_impl) = popup_impl {
246 return PopupRoot::new_with_resolver(&top_level, popup_impl, dependency_resolver).to_popup_host();
247 }
248 }
249 }
250
251 if let Some(overlay_layer) = PopupOverlayLayer::get_popup_overlay_layer(target) {
252 return OverlayPopupHost::new(&overlay_layer).to_popup_host();
253 }
254
255 panic!("Unable to create IPopupImpl and no overlay layer is found for the target control");
256 }
257}
258
259struct OverlayPopupHostPositionerPopup(WeakRef<OverlayPopupHost>);
262
263impl IManagedPopupPositionerPopup for OverlayPopupHostPositionerPopup {
264 fn screens(&self) -> Vec<ManagedPopupPositionerScreenInfo> {
265 let Some(host) = self.0.upgrade() else { return Vec::new() };
266 let available_size =
267 host.overlay_layer.upgrade().map(|overlay_layer| overlay_layer.available_size()).unwrap_or_default();
268 let mut rc = Rect::from_size(available_size);
269 if let Some(top_level) = TopLevel::get_top_level(Some(&host)) {
270 let padding =
271 top_level.insets_manager().map(|insets_manager| insets_manager.safe_area_padding()).unwrap_or_default();
272 rc = rc.deflate_thickness(padding);
273 }
274
275 vec![ManagedPopupPositionerScreenInfo::new(rc, rc)]
276 }
277
278 fn parent_client_area_screen_geometry(&self) -> Rect {
279 let size = self
280 .0
281 .upgrade()
282 .and_then(|host| host.overlay_layer.upgrade())
283 .map(|overlay_layer| overlay_layer.bounds().size())
284 .unwrap_or_default();
285 Rect::from_size(size)
286 }
287
288 fn move_and_resize(&self, device_point: Point, _virtual_size: Size) {
289 let Some(host) = self.0.upgrade() else { return };
290 host.last_requested_position.set(device_point);
291 let weak = self.0.clone();
292 MediaContext::instance().begin_invoke_on_render(Rc::new(move || {
293 if let Some(host) = weak.upgrade() {
294 let position = host.last_requested_position.get();
295 Canvas::set_left(&host, position.x);
296 Canvas::set_top(&host, position.y);
297 }
298 }));
299 }
300
301 fn scaling(&self) -> f64 {
302 1.0
303 }
304}
305
306struct OverlayPopupHostHost(Ref<OverlayPopupHost>);
308
309impl IFocusScope for OverlayPopupHostHost {}
310
311impl IDisposable for OverlayPopupHostHost {
312 fn dispose(&self) {
313 self.0.dispose();
314 }
315}
316
317impl IPopupHost for OverlayPopupHostHost {
318 fn width(&self) -> f64 {
319 self.0.width()
320 }
321
322 fn set_width(&self, value: f64) {
323 self.0.set_width(value)
324 }
325
326 fn min_width(&self) -> f64 {
327 self.0.min_width()
328 }
329
330 fn set_min_width(&self, value: f64) {
331 self.0.set_min_width(value)
332 }
333
334 fn max_width(&self) -> f64 {
335 self.0.max_width()
336 }
337
338 fn set_max_width(&self, value: f64) {
339 self.0.set_max_width(value)
340 }
341
342 fn height(&self) -> f64 {
343 self.0.height()
344 }
345
346 fn set_height(&self, value: f64) {
347 self.0.set_height(value)
348 }
349
350 fn min_height(&self) -> f64 {
351 self.0.min_height()
352 }
353
354 fn set_min_height(&self, value: f64) {
355 self.0.set_min_height(value)
356 }
357
358 fn max_height(&self) -> f64 {
359 self.0.max_height()
360 }
361
362 fn set_max_height(&self, value: f64) {
363 self.0.set_max_height(value)
364 }
365
366 fn presenter(&self) -> Option<Ref<ContentPresenter>> {
367 self.0.presenter()
368 }
369
370 fn topmost(&self) -> bool {
371 false
372 }
373
374 fn set_topmost(&self, _value: bool) {
375 }
377
378 fn is_hit_test_visible(&self) -> bool {
379 self.0.is_hit_test_visible()
380 }
381
382 fn set_is_hit_test_visible(&self, value: bool) {
383 self.0.set_is_hit_test_visible(value)
384 }
385
386 fn transform(&self) -> Option<Ref<Transform>> {
387 self.0.transform()
388 }
389
390 fn set_transform(&self, value: Option<Ref<Transform>>) {
391 self.0.set_transform(Nullable(value))
392 }
393
394 fn hosted_visual_tree_root(&self) -> Option<Ref<Visual>> {
395 self.0.hosted_visual_tree_root()
396 }
397
398 fn template_applied(&self, handler: Rc<dyn Fn(&TemplateAppliedEventArgs)>) -> Rc<dyn IDisposable> {
399 subscribe_template_applied(&self.0.clone().upcast(), handler)
400 }
401
402 fn configure_position(&self, position_request: PopupPositionRequest) {
403 self.0.configure_position(position_request)
404 }
405
406 fn set_child(&self, control: Option<Ref<Control>>) {
407 self.0.set_child(Nullable(control))
408 }
409
410 fn show(&self) {
411 self.0.show()
412 }
413
414 fn hide(&self) {
415 self.0.hide()
416 }
417
418 fn take_focus(&self) {
419 self.0.take_focus()
420 }
421
422 fn as_control(&self) -> Ref<Control> {
423 self.0.clone().upcast()
424 }
425
426 fn as_overlay_popup_host(&self) -> Option<Ref<OverlayPopupHost>> {
427 Some(self.0.clone())
428 }
429}