ferroui_controls/primitives/
popup_root.rs1use super::i_popup_host::subscribe_template_applied;
2use super::popup_positioning::PopupPositionRequest;
3use super::{IPopupHost, Popup, TemplateAppliedEventArgs, TemplatedControl, TemplatedControlImpl};
4use crate::platform::{IPopupImpl, IWindowBaseImpl};
5use crate::presenters::ContentPresenter;
6use crate::{
7 ContentControlImpl, Control, ControlImpl, PlacementMode, TopLevel, TopLevelImpl, WindowBase, WindowBaseImpl,
8};
9use ferroui_base::input::{IFocusScope, InputElement, InputElementImpl};
10use ferroui_base::interactivity::{Interactive, InteractiveImpl};
11use ferroui_base::layout::{LayoutableImpl, LayoutableImplExt};
12use ferroui_base::media::{Brushes, IBrush, Transform};
13use ferroui_base::reactive::IDisposable;
14use ferroui_base::styling::StyleHostRef;
15use ferroui_base::visual_tree::IHostedVisualTreeRoot;
16use ferroui_base::{
17 ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
18 FerroPropertyChangedEventArgs, IFerroDependencyResolver, Nullable, Ref, Size, StyledElementImpl, StyledProperty,
19 Thickness, Visual, VisualImpl, WeakRef,
20};
21use std::cell::{Cell, RefCell};
22use std::rc::Rc;
23
24#[repr(C)]
26pub struct PopupRoot {
27 base: WindowBase,
28 popup_impl: RefCell<Option<Rc<dyn IPopupImpl>>>,
29 parent_top_level: WeakRef<TopLevel>,
30 popup_position_request: RefCell<Option<PopupPositionRequest>>,
31 popup_size: Cell<Size>,
32 child_margin: Cell<Thickness>,
33 needs_update: Cell<bool>,
34 has_initial_position: Cell<bool>,
35}
36
37ferro_class!(PopupRoot: WindowBase);
38ferro_impl_classes!(PopupRoot: VisualImpl, TemplatedControlImpl, ContentControlImpl);
39
40impl ControlImpl for PopupRoot {
41 fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
42 crate::automation::peers::PopupRootAutomationPeer::new(this).upcast()
43 }
44}
45
46impl TopLevelImpl for PopupRoot {
47 fn opened_popups(this: &Self) -> Vec<Ref<Popup>> {
50 this.parent().and_then(|parent| parent.cast::<Popup>()).map(|popup| popup.opened_popups()).unwrap_or_default()
51 }
52}
53
54impl IFocusScope for PopupRoot {}
55
56impl FerroObjectImpl for PopupRoot {
57 fn constructed(this: &Self) {
58 Self::parent_constructed(this);
59
60 if let Some(platform_impl) = this.platform_impl() {
61 platform_impl.set_window_manager_add_shadow_hint(this.window_manager_add_shadow_hint());
62 platform_impl.set_hit_test_visible(this.is_hit_test_visible());
63 }
64 }
65
66 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
67 Self::parent_on_property_changed(this, change);
68
69 if change.property() == Self::window_manager_add_shadow_hint_property().as_property() {
70 let (_, new_value) = change.get_old_and_new_value::<bool>();
71 if let Some(platform_impl) = this.platform_impl() {
72 platform_impl.set_window_manager_add_shadow_hint(new_value);
73 }
74 } else if change.property() == WindowBase::topmost_property().as_property() {
75 let (_, new_value) = change.get_old_and_new_value::<bool>();
76 if let Some(platform_impl) = this.platform_impl() {
77 platform_impl.set_topmost(new_value);
78 }
79 } else if change.property() == InputElement::is_hit_test_visible_property().as_property() {
80 let (_, new_value) = change.get_old_and_new_value::<bool>();
81 if let Some(platform_impl) = this.platform_impl() {
82 platform_impl.set_hit_test_visible(new_value);
83 }
84 }
85 }
86}
87
88impl StyledElementImpl for PopupRoot {
89 fn styling_parent(this: &Self) -> Option<StyleHostRef> {
91 this.parent().map(StyleHostRef::Element)
92 }
93}
94
95impl InteractiveImpl for PopupRoot {
96 fn interactive_parent(this: &Self) -> Option<Ref<Interactive>> {
101 this.parent().and_then(|parent| parent.cast::<Interactive>())
102 }
103}
104
105impl InputElementImpl for PopupRoot {
106 fn is_focus_scope(_this: &Self) -> bool {
107 true
108 }
109
110 fn as_hosted_visual_tree_root(this: &Self) -> Option<&dyn IHostedVisualTreeRoot> {
111 Some(this)
112 }
113}
114
115impl IHostedVisualTreeRoot for PopupRoot {
116 fn host(&self) -> Option<Ref<Visual>> {
118 let parent_visual = self.parent().and_then(|parent| parent.cast::<Visual>());
124 if let Some(parent_visual) = &parent_visual {
125 if parent_visual.is_attached_to_visual_tree() {
126 return Some(parent_visual.clone());
127 }
128 }
129 self.parent_top_level().map(Ref::upcast).or(parent_visual)
130 }
131}
132
133impl LayoutableImpl for PopupRoot {
134 fn measure_override(this: &Self, available_size: Size) -> Size {
135 let max_auto_size =
136 this.platform_impl().map(|platform_impl| platform_impl.max_auto_size_hint()).unwrap_or(Size::INFINITY);
137 let mut constraint = available_size;
138
139 if constraint.width.is_infinite() {
140 constraint = constraint.with_width(max_auto_size.width);
141 }
142
143 if constraint.height.is_infinite() {
144 constraint = constraint.with_height(max_auto_size.height);
145 }
146
147 let measured = Self::parent_measure_override(this, constraint);
148 let mut width = measured.width;
149 let mut height = measured.height;
150 let width_cache = this.width();
151 let height_cache = this.height();
152
153 if !width_cache.is_nan() {
154 width = width_cache;
155 }
156
157 width = width.min(this.max_width());
158 width = width.max(this.min_width());
159
160 if !height_cache.is_nan() {
161 height = height_cache;
162 }
163
164 height = height.min(this.max_height());
165 height = height.max(this.min_height());
166
167 Size::new(width, height)
168 }
169}
170
171impl WindowBaseImpl for PopupRoot {
172 fn arrange_set_bounds(this: &Self, size: Size) -> Size {
173 let mut reposition = false;
174
175 let new_margin = this
176 .presenter()
177 .and_then(|presenter| presenter.child())
178 .map(|child| child.margin())
179 .unwrap_or_default();
180 if new_margin != this.child_margin.get() {
181 this.child_margin.set(new_margin);
182 reposition = true;
183 }
184
185 if this.popup_size.get() != size {
186 this.popup_size.set(size);
187 reposition = true;
188 }
189
190 if this.has_initial_position.get()
191 && this.popup_position_request.borrow().as_ref().is_some_and(|request| request.placement() == PlacementMode::Pointer)
192 {
193 reposition = false;
194 }
195
196 if reposition {
197 if this.popup_position_request.borrow().is_some() {
198 this.has_initial_position.set(true);
199 }
200
201 this.needs_update.set(true);
202 this.update_position();
203 }
204
205 this.client_size()
206 }
207}
208
209ferroui_base::ferro_properties! { impl PopupRoot {
210 ferro_property!(
211 pub fn transform_property() -> StyledProperty<Option<Ref<Transform>>> {
213 FerroProperty::register::<PopupRoot, _>("Transform", None)
214 }
215 );
216
217 ferro_property!(
218 pub fn window_manager_add_shadow_hint_property() -> StyledProperty<bool> {
220 Popup::window_manager_add_shadow_hint_property().add_owner::<PopupRoot>()
221 }
222 );
223} }
224
225impl PopupRoot {
226 fn static_constructor() {
227 let white: Rc<dyn IBrush> = Brushes::white();
228 TemplatedControl::background_property().override_default_value::<PopupRoot>(Some(white));
229 }
230
231 pub fn new(parent: &Ref<TopLevel>, platform_impl: Rc<dyn IPopupImpl>) -> Ref<Self> {
234 Self::new_with_resolver(parent, platform_impl, None)
235 }
236
237 pub fn new_with_resolver(
242 parent: &Ref<TopLevel>,
243 platform_impl: Rc<dyn IPopupImpl>,
244 dependency_resolver: Option<Rc<dyn IFerroDependencyResolver>>,
245 ) -> Ref<Self> {
246 let window_base_impl: Rc<dyn IWindowBaseImpl> = platform_impl.clone();
247 instantiate(Self {
248 base: WindowBase::construct_with_resolver(window_base_impl, dependency_resolver),
249 popup_impl: RefCell::new(Some(platform_impl)),
250 parent_top_level: parent.downgrade(),
251 popup_position_request: RefCell::new(None),
252 popup_size: Cell::new(Size::default()),
253 child_margin: Cell::new(Thickness::default()),
254 needs_update: Cell::new(false),
255 has_initial_position: Cell::new(false),
256 })
257 }
258
259 pub fn platform_impl(&self) -> Option<Rc<dyn IPopupImpl>> {
262 TopLevel::platform_impl(self)?;
263 self.popup_impl.borrow().clone()
264 }
265
266 pub fn transform(&self) -> Option<Ref<Transform>> {
268 self.get_value(Self::transform_property())
269 }
270
271 pub fn set_transform(&self, value: impl Into<Nullable<Transform>>) {
272 self.set_value(Self::transform_property(), value.into().0)
273 }
274
275 pub fn window_manager_add_shadow_hint(&self) -> bool {
278 self.get_value(Self::window_manager_add_shadow_hint_property())
279 }
280
281 pub fn set_window_manager_add_shadow_hint(&self, value: bool) {
282 self.set_value(Self::window_manager_add_shadow_hint_property(), value)
283 }
284
285 pub fn parent_top_level(&self) -> Option<Ref<TopLevel>> {
288 self.parent_top_level.upgrade()
289 }
290
291 pub fn dispose(&self) {
293 if let Some(platform_impl) = self.platform_impl() {
294 platform_impl.dispose();
295 }
296 self.has_initial_position.set(false);
297 self.ensure_closed();
298 }
299
300 fn update_position(&self) {
301 if !self.needs_update.get() {
302 return;
303 }
304 let Some(popup_position_request) = self.popup_position_request.borrow().clone() else { return };
305
306 self.needs_update.set(false);
307 let positioner = self.platform_impl().and_then(|platform_impl| platform_impl.popup_positioner());
308 if let (Some(positioner), Some(parent_top_level)) = (positioner, self.parent_top_level()) {
309 positioner.update_request(
310 &parent_top_level,
311 &popup_position_request,
312 self.popup_size.get(),
313 self.child_margin.get(),
314 self.flow_direction(),
315 );
316 }
317 }
318
319 pub fn configure_position(&self, request: PopupPositionRequest) {
322 *self.popup_position_request.borrow_mut() = Some(request);
323 self.needs_update.set(true);
324 self.update_position();
325 }
326
327 pub fn set_child(&self, control: impl Into<Nullable<Control>>) {
329 self.set_content(control.into().0.map(Control::boxed));
330 }
331
332 pub fn take_focus(&self) {
334 if let Some(platform_impl) = self.platform_impl() {
335 platform_impl.take_focus();
336 }
337 }
338
339 pub fn to_popup_host(&self) -> Rc<dyn IPopupHost> {
342 Rc::new(PopupRootHost(self.to_ref()))
343 }
344}
345
346struct PopupRootHost(Ref<PopupRoot>);
348
349impl IFocusScope for PopupRootHost {}
350
351impl IDisposable for PopupRootHost {
352 fn dispose(&self) {
353 self.0.dispose();
354 }
355}
356
357impl IPopupHost for PopupRootHost {
358 fn width(&self) -> f64 {
359 self.0.width()
360 }
361
362 fn set_width(&self, value: f64) {
363 self.0.set_width(value)
364 }
365
366 fn min_width(&self) -> f64 {
367 self.0.min_width()
368 }
369
370 fn set_min_width(&self, value: f64) {
371 self.0.set_min_width(value)
372 }
373
374 fn max_width(&self) -> f64 {
375 self.0.max_width()
376 }
377
378 fn set_max_width(&self, value: f64) {
379 self.0.set_max_width(value)
380 }
381
382 fn height(&self) -> f64 {
383 self.0.height()
384 }
385
386 fn set_height(&self, value: f64) {
387 self.0.set_height(value)
388 }
389
390 fn min_height(&self) -> f64 {
391 self.0.min_height()
392 }
393
394 fn set_min_height(&self, value: f64) {
395 self.0.set_min_height(value)
396 }
397
398 fn max_height(&self) -> f64 {
399 self.0.max_height()
400 }
401
402 fn set_max_height(&self, value: f64) {
403 self.0.set_max_height(value)
404 }
405
406 fn presenter(&self) -> Option<Ref<ContentPresenter>> {
407 self.0.presenter()
408 }
409
410 fn topmost(&self) -> bool {
411 self.0.topmost()
412 }
413
414 fn set_topmost(&self, value: bool) {
415 self.0.set_topmost(value)
416 }
417
418 fn is_hit_test_visible(&self) -> bool {
419 self.0.is_hit_test_visible()
420 }
421
422 fn set_is_hit_test_visible(&self, value: bool) {
423 self.0.set_is_hit_test_visible(value)
424 }
425
426 fn transform(&self) -> Option<Ref<Transform>> {
427 self.0.transform()
428 }
429
430 fn set_transform(&self, value: Option<Ref<Transform>>) {
431 self.0.set_transform(Nullable(value))
432 }
433
434 fn hosted_visual_tree_root(&self) -> Option<Ref<Visual>> {
435 Some(self.0.clone().upcast())
436 }
437
438 fn template_applied(&self, handler: Rc<dyn Fn(&TemplateAppliedEventArgs)>) -> Rc<dyn IDisposable> {
439 subscribe_template_applied(&self.0.clone().upcast(), handler)
440 }
441
442 fn configure_position(&self, position_request: PopupPositionRequest) {
443 self.0.configure_position(position_request)
444 }
445
446 fn set_child(&self, control: Option<Ref<Control>>) {
447 self.0.set_child(Nullable(control))
448 }
449
450 fn show(&self) {
451 self.0.show()
452 }
453
454 fn hide(&self) {
455 self.0.hide()
456 }
457
458 fn take_focus(&self) {
459 self.0.take_focus()
460 }
461
462 fn as_control(&self) -> Ref<Control> {
463 self.0.clone().upcast()
464 }
465
466 fn as_popup_root(&self) -> Option<Ref<PopupRoot>> {
467 Some(self.0.clone())
468 }
469}