Skip to main content

fui/
popup_presenter.rs

1use crate::bindings::ui;
2use crate::ffi::{PositionType, Unit};
3use crate::node::{portal, FlexBox, Node, NodeHandle, WeakFlexBox};
4use std::cell::RefCell;
5use std::rc::Rc;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum PopupPlacement {
9    Auto = 0,
10    Bottom = 1,
11    Top = 2,
12    Overlap = 3,
13}
14
15#[derive(Clone)]
16#[doc(hidden)]
17pub struct PopupPresenter {
18    root: FlexBox,
19    overlay_node: FlexBox,
20    surface_node: FlexBox,
21    semantic_scope_node: Option<FlexBox>,
22    state: Rc<RefCell<PopupPresenterState>>,
23}
24
25#[derive(Clone)]
26pub(crate) struct WeakPopupPresenter {
27    root: WeakFlexBox,
28    overlay_node: FlexBox,
29    surface_node: FlexBox,
30    semantic_scope_node: Option<FlexBox>,
31    state: Rc<RefCell<PopupPresenterState>>,
32}
33
34#[derive(Clone)]
35pub(crate) struct PopupPresenterEventTarget {
36    root: WeakFlexBox,
37    overlay_node: WeakFlexBox,
38    state: Rc<RefCell<PopupPresenterState>>,
39}
40
41#[derive(Clone, Copy)]
42struct PopupPresenterState {
43    open: bool,
44    semantic_scope_token: u32,
45    edge_padding: f32,
46    anchor_gap: f32,
47    placement: PopupPlacement,
48    backdrop_color: u32,
49    background_blur_sigma: f32,
50    surface_x: f32,
51    surface_y: f32,
52}
53
54impl Default for PopupPresenterState {
55    fn default() -> Self {
56        Self {
57            open: false,
58            semantic_scope_token: 0,
59            edge_padding: 8.0,
60            anchor_gap: 4.0,
61            placement: PopupPlacement::Auto,
62            backdrop_color: 0x00000000,
63            background_blur_sigma: 0.0,
64            surface_x: 0.0,
65            surface_y: 0.0,
66        }
67    }
68}
69
70impl PopupPresenter {
71    pub fn new(root: FlexBox, surface_node: FlexBox) -> Self {
72        Self::new_with_semantic_scope(root, surface_node.clone(), Some(surface_node))
73    }
74
75    pub fn new_with_semantic_scope(
76        root: FlexBox,
77        surface_node: FlexBox,
78        semantic_scope_node: Option<FlexBox>,
79    ) -> Self {
80        let overlay_node = FlexBox::default();
81        overlay_node
82            .position_type(PositionType::Absolute)
83            .position(0.0, 0.0)
84            .width(100.0, Unit::Percent)
85            .height(100.0, Unit::Percent)
86            .child(&surface_node);
87        surface_node.position_type(PositionType::Absolute);
88        let presenter = Self {
89            root,
90            overlay_node,
91            surface_node,
92            semantic_scope_node,
93            state: Rc::new(RefCell::new(PopupPresenterState::default())),
94        };
95        presenter.apply_backdrop_style();
96        presenter
97    }
98
99    pub fn with_default_root(surface_node: FlexBox) -> Self {
100        Self::new(portal(), surface_node)
101    }
102
103    pub fn root(&self) -> FlexBox {
104        self.root.clone()
105    }
106
107    pub fn overlay_node(&self) -> FlexBox {
108        self.overlay_node.clone()
109    }
110
111    pub fn surface_node(&self) -> FlexBox {
112        self.surface_node.clone()
113    }
114
115    pub fn is_open(&self) -> bool {
116        self.state.borrow().open
117    }
118
119    pub(crate) fn downgrade(&self) -> WeakPopupPresenter {
120        WeakPopupPresenter {
121            root: self.root.downgrade(),
122            overlay_node: self.overlay_node.clone(),
123            surface_node: self.surface_node.clone(),
124            semantic_scope_node: self.semantic_scope_node.clone(),
125            state: self.state.clone(),
126        }
127    }
128
129    pub fn surface_x(&self) -> f32 {
130        self.state.borrow().surface_x
131    }
132
133    pub fn surface_y(&self) -> f32 {
134        self.state.borrow().surface_y
135    }
136
137    pub fn semantic_scope_token(&self) -> u32 {
138        self.state.borrow().semantic_scope_token
139    }
140
141    pub(crate) fn event_target(&self) -> PopupPresenterEventTarget {
142        PopupPresenterEventTarget {
143            root: self.root.downgrade(),
144            overlay_node: self.overlay_node.downgrade(),
145            state: self.state.clone(),
146        }
147    }
148
149    pub fn placement(&self, value: PopupPlacement) -> &Self {
150        self.state.borrow_mut().placement = value;
151        self
152    }
153
154    pub fn edge_padding(&self, value: f32) -> &Self {
155        self.state.borrow_mut().edge_padding = value.max(0.0);
156        self
157    }
158
159    pub fn anchor_gap(&self, value: f32) -> &Self {
160        self.state.borrow_mut().anchor_gap = value.max(0.0);
161        self
162    }
163
164    pub fn backdrop_color(&self, color: u32) -> &Self {
165        self.state.borrow_mut().backdrop_color = color;
166        self.apply_backdrop_style();
167        self
168    }
169
170    pub fn background_blur(&self, sigma: f32) -> &Self {
171        self.state.borrow_mut().background_blur_sigma = sigma.max(0.0);
172        self.apply_backdrop_style();
173        self
174    }
175
176    pub fn sync_overlay_bounds(&self) {
177        let popup_bounds = if self.root.handle() != NodeHandle::INVALID {
178            ui::get_bounds(self.root.handle().raw())
179        } else {
180            None
181        };
182        let overlay_x = popup_bounds.map(|bounds| -bounds[0]).unwrap_or(0.0);
183        let overlay_y = popup_bounds.map(|bounds| -bounds[1]).unwrap_or(0.0);
184        self.overlay_node
185            .position(overlay_x, overlay_y)
186            .width(ui::get_viewport_width(), Unit::Pixel)
187            .height(ui::get_viewport_height(), Unit::Pixel);
188    }
189
190    pub fn show_anchored(
191        &self,
192        anchor_x: f32,
193        anchor_y: f32,
194        anchor_width: f32,
195        anchor_height: f32,
196        surface_width: f32,
197        surface_height: f32,
198    ) {
199        let placement = self.state.borrow().placement;
200        self.show_anchored_with_placement(
201            anchor_x,
202            anchor_y,
203            anchor_width,
204            anchor_height,
205            surface_width,
206            surface_height,
207            placement,
208        );
209    }
210
211    #[allow(clippy::too_many_arguments)]
212    pub fn show_anchored_with_placement(
213        &self,
214        anchor_x: f32,
215        anchor_y: f32,
216        _anchor_width: f32,
217        anchor_height: f32,
218        surface_width: f32,
219        surface_height: f32,
220        placement: PopupPlacement,
221    ) {
222        if self.root.handle() == NodeHandle::INVALID {
223            return;
224        }
225        self.sync_overlay_bounds();
226        let state = *self.state.borrow();
227        let clamped_width = if surface_width > 0.0 {
228            surface_width
229        } else {
230            1.0
231        };
232        let clamped_height = if surface_height > 0.0 {
233            surface_height
234        } else {
235            1.0
236        };
237        let viewport_width = ui::get_viewport_width();
238        let viewport_height = ui::get_viewport_height();
239        let max_x = state
240            .edge_padding
241            .max(viewport_width - clamped_width - state.edge_padding);
242        let max_y = state
243            .edge_padding
244            .max(viewport_height - clamped_height - state.edge_padding);
245        let below_y = anchor_y + anchor_height + state.anchor_gap;
246        let above_y = anchor_y - clamped_height - state.anchor_gap;
247        let fits_below = below_y <= max_y;
248        let fits_above = above_y >= state.edge_padding;
249        let mut panel_y = below_y;
250        if placement == PopupPlacement::Top {
251            panel_y = above_y;
252        } else if placement == PopupPlacement::Overlap {
253            panel_y = anchor_y;
254        } else if placement == PopupPlacement::Auto && !fits_below && fits_above {
255            panel_y = above_y;
256        }
257        self.set_surface_position(
258            anchor_x.clamp(state.edge_padding, max_x),
259            panel_y.clamp(state.edge_padding, max_y),
260        );
261        self.attach();
262    }
263
264    pub fn show_at_point(&self, x: f32, y: f32, surface_width: f32, surface_height: f32) {
265        if self.root.handle() == NodeHandle::INVALID {
266            return;
267        }
268        self.sync_overlay_bounds();
269        let state = *self.state.borrow();
270        let clamped_width = if surface_width > 0.0 {
271            surface_width
272        } else {
273            1.0
274        };
275        let clamped_height = if surface_height > 0.0 {
276            surface_height
277        } else {
278            1.0
279        };
280        let max_x = state
281            .edge_padding
282            .max(ui::get_viewport_width() - clamped_width - state.edge_padding);
283        let max_y = state
284            .edge_padding
285            .max(ui::get_viewport_height() - clamped_height - state.edge_padding);
286        self.set_surface_position(
287            x.clamp(state.edge_padding, max_x),
288            y.clamp(state.edge_padding, max_y),
289        );
290        self.attach();
291    }
292
293    pub fn hide(&self) {
294        if !self.is_open() && self.overlay_node.parent_handle().is_none() {
295            return;
296        }
297        self.root.remove_child(&self.overlay_node);
298        let mut state = self.state.borrow_mut();
299        state.open = false;
300        if state.semantic_scope_token != 0 {
301            ui::remove_semantic_scope(state.semantic_scope_token);
302            state.semantic_scope_token = 0;
303        }
304    }
305
306    pub fn dispose(&self) {
307        self.hide();
308        if self.overlay_node.handle() != NodeHandle::INVALID {
309            self.overlay_node.dispose();
310        }
311    }
312
313    fn attach(&self) {
314        self.root.child(&self.overlay_node);
315        self.state.borrow_mut().open = true;
316        let semantic_scope_handle = self
317            .semantic_scope_node
318            .as_ref()
319            .map(|node| node.handle())
320            .unwrap_or(NodeHandle::INVALID);
321        let mut state = self.state.borrow_mut();
322        if state.semantic_scope_token == 0 && semantic_scope_handle != NodeHandle::INVALID {
323            state.semantic_scope_token = ui::push_semantic_scope(semantic_scope_handle.raw());
324        }
325    }
326
327    fn apply_backdrop_style(&self) {
328        let state = *self.state.borrow();
329        self.overlay_node
330            .bg_color(state.backdrop_color)
331            .background_blur(state.background_blur_sigma);
332    }
333
334    fn set_surface_position(&self, x: f32, y: f32) {
335        {
336            let mut state = self.state.borrow_mut();
337            state.surface_x = x;
338            state.surface_y = y;
339        }
340        self.surface_node.position(x, y);
341    }
342}
343
344impl WeakPopupPresenter {
345    pub(crate) fn upgrade(&self) -> Option<PopupPresenter> {
346        Some(PopupPresenter {
347            root: self.root.upgrade()?,
348            overlay_node: self.overlay_node.clone(),
349            surface_node: self.surface_node.clone(),
350            semantic_scope_node: self.semantic_scope_node.clone(),
351            state: self.state.clone(),
352        })
353    }
354}
355
356impl PopupPresenterEventTarget {
357    pub(crate) fn is_open(&self) -> bool {
358        self.state.borrow().open
359    }
360
361    pub(crate) fn surface_x(&self) -> f32 {
362        self.state.borrow().surface_x
363    }
364
365    pub(crate) fn surface_y(&self) -> f32 {
366        self.state.borrow().surface_y
367    }
368
369    pub(crate) fn backdrop_color(&self, color: u32) {
370        self.state.borrow_mut().backdrop_color = color;
371        self.apply_backdrop_style();
372    }
373
374    pub(crate) fn background_blur(&self, sigma: f32) {
375        self.state.borrow_mut().background_blur_sigma = sigma.max(0.0);
376        self.apply_backdrop_style();
377    }
378
379    fn apply_backdrop_style(&self) {
380        let Some(overlay) = self.overlay_node.upgrade() else {
381            return;
382        };
383        let state = *self.state.borrow();
384        overlay
385            .bg_color(state.backdrop_color)
386            .background_blur(state.background_blur_sigma);
387    }
388
389    pub(crate) fn hide(&self) {
390        let Some(root) = self.root.upgrade() else {
391            return;
392        };
393        let Some(overlay_node) = self.overlay_node.upgrade() else {
394            return;
395        };
396        let open = self.state.borrow().open;
397        if !open && overlay_node.parent_handle().is_none() {
398            return;
399        }
400        root.remove_child(&overlay_node);
401        let mut state = self.state.borrow_mut();
402        state.open = false;
403        if state.semantic_scope_token != 0 {
404            ui::remove_semantic_scope(state.semantic_scope_token);
405            state.semantic_scope_token = 0;
406        }
407    }
408}