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}