Skip to main content

fui/controls/
popup.rs

1use super::*;
2use crate::ffi::{FlexDirection, PositionType, Unit};
3use crate::node::portal;
4use crate::popup_presenter::{PopupPlacement, PopupPresenter};
5use std::cell::Cell;
6
7#[derive(Clone)]
8pub struct Popup {
9    root: FlexBox,
10    surface_node: FlexBox,
11    presenter: PopupPresenter,
12    dismiss_on_backdrop_click: Rc<Cell<bool>>,
13    appearance_value: Rc<RefCell<Option<PopupAppearance>>>,
14}
15
16impl Default for Popup {
17    fn default() -> Self {
18        Self::new()
19    }
20}
21
22impl Popup {
23    pub fn new() -> Self {
24        let root = portal();
25        let surface_node = flex_box();
26        surface_node
27            .position_type(PositionType::Absolute)
28            .flex_direction(FlexDirection::Column);
29        let presenter = PopupPresenter::new(root.clone(), surface_node.clone());
30        root.position_type(PositionType::Absolute)
31            .position(0.0, 0.0)
32            .width(100.0, Unit::Percent)
33            .height(100.0, Unit::Percent);
34        let dismiss_on_backdrop_click = Rc::new(Cell::new(true));
35        let dismiss_flag = dismiss_on_backdrop_click.clone();
36        let presenter_target = presenter.event_target();
37        presenter
38            .overlay_node()
39            .interactive(true)
40            .on_pointer_click(move |_event| {
41                if dismiss_flag.get() {
42                    presenter_target.hide();
43                }
44            });
45        Self {
46            root,
47            surface_node,
48            presenter,
49            dismiss_on_backdrop_click,
50            appearance_value: Rc::new(RefCell::new(None)),
51        }
52    }
53
54    pub fn is_open(&self) -> bool {
55        self.presenter.is_open()
56    }
57
58    /// Returns the presented content panel. Inherited `child`/`children` calls
59    /// are routed here rather than into the portal's overlay root.
60    pub fn surface(&self) -> FlexBox {
61        self.surface_node.clone()
62    }
63
64    pub fn placement(&self, value: PopupPlacement) -> &Self {
65        self.presenter.placement(value);
66        self
67    }
68
69    pub fn edge_padding(&self, value: f32) -> &Self {
70        self.presenter.edge_padding(value);
71        self
72    }
73
74    pub fn anchor_gap(&self, value: f32) -> &Self {
75        self.presenter.anchor_gap(value);
76        self
77    }
78
79    pub fn dismiss_on_backdrop_click(&self, flag: bool) -> &Self {
80        self.dismiss_on_backdrop_click.set(flag);
81        self
82    }
83
84    pub fn appearance(&self, appearance: PopupAppearance) -> &Self {
85        self.appearance_value.replace(Some(appearance));
86        self.sync_appearance();
87        self
88    }
89
90    pub fn clear_appearance(&self) -> &Self {
91        self.appearance_value.replace(None);
92        self.sync_appearance();
93        self
94    }
95
96    fn sync_appearance(&self) {
97        let appearance = self.appearance_value.borrow().clone().unwrap_or_default();
98        let panel = appearance.panel.unwrap_or_default();
99        let backdrop = appearance.backdrop.unwrap_or_default();
100        self.surface_node
101            .apply_presenter_style(panel.presenter_host_style())
102            .background_blur(panel.background_blur.unwrap_or(0.0));
103        self.presenter
104            .backdrop_color(backdrop.color.unwrap_or(0x00000000))
105            .background_blur(backdrop.blur.unwrap_or(0.0));
106    }
107
108    pub fn show_anchored(
109        &self,
110        anchor_x: f32,
111        anchor_y: f32,
112        anchor_width: f32,
113        anchor_height: f32,
114        width: f32,
115        height: f32,
116    ) {
117        self.surface_node.width(width, Unit::Pixel);
118        self.surface_node.height(height, Unit::Pixel);
119        self.presenter.show_anchored(
120            anchor_x,
121            anchor_y,
122            anchor_width,
123            anchor_height,
124            width,
125            height,
126        );
127    }
128
129    pub fn show_at_point(&self, x: f32, y: f32, width: f32, height: f32) {
130        self.surface_node.width(width, Unit::Pixel);
131        self.surface_node.height(height, Unit::Pixel);
132        self.presenter.show_at_point(x, y, width, height);
133    }
134
135    pub fn hide(&self) {
136        self.presenter.hide();
137    }
138}
139
140impl Node for Popup {
141    fn retained_node_ref(&self) -> NodeRef {
142        self.root.retained_node_ref()
143    }
144
145    fn build_self(&self) {
146        self.root.build_self();
147    }
148
149    fn dispose(&self) {
150        self.presenter.dispose();
151        self.root.dispose();
152    }
153}
154
155impl crate::node::HasFlexBoxRoot for Popup {
156    fn flex_box_root(&self) -> &FlexBox {
157        &self.root
158    }
159
160    fn append_flex_box_surface_child(&self, child: NodeRef) {
161        self.surface_node
162            .retained_node_ref()
163            .append_child_ref(&child);
164    }
165}
166
167impl crate::node::ThemeBindable for Popup {
168    fn theme_binding_node(&self) -> NodeRef {
169        self.root.retained_node_ref()
170    }
171
172    fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
173        let root = self.root.downgrade();
174        let surface_node = self.surface_node.downgrade();
175        let presenter = self.presenter.downgrade();
176        let dismiss_on_backdrop_click = Rc::downgrade(&self.dismiss_on_backdrop_click);
177        // Appearance state does not own the portal root. Keep it alive with the
178        // retained theme binding so a dropped wrapper remains reconstructible.
179        let appearance_value = self.appearance_value.clone();
180        Box::new(move || {
181            Some(Self {
182                root: root.upgrade()?,
183                surface_node: surface_node.upgrade()?,
184                presenter: presenter.upgrade()?,
185                dismiss_on_backdrop_click: dismiss_on_backdrop_click.upgrade()?,
186                appearance_value: appearance_value.clone(),
187            })
188        })
189    }
190}