fyrox_ui/popup.rs
1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Popup is used to display other widgets in floating panel, that could lock input in self bounds. See [`Popup`] docs
22//! for more info and usage examples.
23
24#![warn(missing_docs)]
25
26use crate::style::resource::StyleResourceExt;
27use crate::style::Style;
28use crate::{
29 border::BorderBuilder,
30 core::{
31 algebra::Vector2, math::Rect, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
32 uuid_provider, variable::InheritableVariable, visitor::prelude::*,
33 },
34 define_constructor,
35 message::{ButtonState, KeyCode, MessageDirection, OsEvent, UiMessage},
36 widget::{Widget, WidgetBuilder, WidgetMessage},
37 BuildContext, Control, RestrictionEntry, Thickness, UiNode, UserInterface,
38};
39use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
40use fyrox_graph::BaseSceneGraph;
41use std::ops::{Deref, DerefMut};
42
43/// A set of messages for [`Popup`] widget.
44#[derive(Debug, Clone, PartialEq)]
45pub enum PopupMessage {
46 /// Used to open a [`Popup`] widgets. Use [`PopupMessage::open`] to create the message.
47 Open,
48 /// Used to close a [`Popup`] widgets. Use [`PopupMessage::close`] to create the message.
49 Close,
50 /// Used to change the content of a [`Popup`] widgets. Use [`PopupMessage::content`] to create the message.
51 Content(Handle<UiNode>),
52 /// Used to change popup's placement. Use [`PopupMessage::placement`] to create the message.
53 Placement(Placement),
54 /// Used to adjust position of a popup widget, so it will be on screen. Use [`PopupMessage::adjust_position`] to create
55 /// the message.
56 AdjustPosition,
57 /// Used to set the owner of a Popup. The owner will receive Event messages.
58 Owner(Handle<UiNode>),
59 /// Sent by the Popup to its owner when handling messages from the Popup's children.
60 RelayedMessage(UiMessage),
61}
62
63impl PopupMessage {
64 define_constructor!(
65 /// Creates [`PopupMessage::Open`] message.
66 PopupMessage:Open => fn open(), layout: false
67 );
68 define_constructor!(
69 /// Creates [`PopupMessage::Close`] message.
70 PopupMessage:Close => fn close(), layout: false
71 );
72 define_constructor!(
73 /// Creates [`PopupMessage::Content`] message.
74 PopupMessage:Content => fn content(Handle<UiNode>), layout: false
75 );
76 define_constructor!(
77 /// Creates [`PopupMessage::Placement`] message.
78 PopupMessage:Placement => fn placement(Placement), layout: false
79 );
80 define_constructor!(
81 /// Creates [`PopupMessage::AdjustPosition`] message.
82 PopupMessage:AdjustPosition => fn adjust_position(), layout: true
83 );
84 define_constructor!(
85 /// Creates [`PopupMessage::Owner`] message.
86 PopupMessage:Owner => fn owner(Handle<UiNode>), layout: false
87 );
88 define_constructor!(
89 /// Creates [`PopupMessage::RelayedMessage`] message.
90 PopupMessage:RelayedMessage => fn relayed_message(UiMessage), layout: false
91 );
92}
93
94/// Defines a method of popup placement.
95#[derive(Copy, Clone, PartialEq, Debug, Visit, Reflect)]
96pub enum Placement {
97 /// A popup should be placed relative to given widget at the left top corner of the widget screen bounds.
98 /// Widget handle could be [`Handle::NONE`], in this case the popup will be placed at the left top corner of the screen.
99 LeftTop(Handle<UiNode>),
100
101 /// A popup should be placed relative to given widget at the right top corner of the widget screen bounds.
102 /// Widget handle could be [`Handle::NONE`], in this case the popup will be placed at the right top corner of the screen.
103 RightTop(Handle<UiNode>),
104
105 /// A popup should be placed relative to given widget at the center of the widget screen bounds.
106 /// Widget handle could be [`Handle::NONE`], in this case the popup will be placed at the center of the screen.
107 Center(Handle<UiNode>),
108
109 /// A popup should be placed relative to given widget at the left bottom corner of the widget screen bounds.
110 /// Widget handle could be [`Handle::NONE`], in this case the popup will be placed at the left bottom corner of the screen.
111 LeftBottom(Handle<UiNode>),
112
113 /// A popup should be placed relative to given widget at the right bottom corner of the widget screen bounds.
114 /// Widget handle could be [`Handle::NONE`], in this case the popup will be placed at the right bottom corner of the screen.
115 RightBottom(Handle<UiNode>),
116
117 /// A popup should be placed at the cursor position. The widget handle could be either [`Handle::NONE`] or a handle of a
118 /// widget that is directly behind the cursor.
119 Cursor(Handle<UiNode>),
120
121 /// A popup should be placed at given screen-space position.
122 Position {
123 /// Screen-space position.
124 position: Vector2<f32>,
125
126 /// A handle of the node that is located behind the given position. Could be [`Handle::NONE`] if there is nothing behind
127 /// given position.
128 target: Handle<UiNode>,
129 },
130}
131
132impl Default for Placement {
133 fn default() -> Self {
134 Self::LeftTop(Default::default())
135 }
136}
137
138impl Placement {
139 /// Returns a handle of the node to which this placement corresponds to.
140 pub fn target(&self) -> Handle<UiNode> {
141 match self {
142 Placement::LeftTop(target)
143 | Placement::RightTop(target)
144 | Placement::Center(target)
145 | Placement::LeftBottom(target)
146 | Placement::RightBottom(target)
147 | Placement::Cursor(target)
148 | Placement::Position { target, .. } => *target,
149 }
150 }
151}
152
153/// Popup is used to display other widgets in floating panel, that could lock input in self bounds.
154///
155/// ## How to create
156///
157/// A simple popup with a button could be created using the following code:
158///
159/// ```rust
160/// # use fyrox_ui::{
161/// # button::ButtonBuilder, core::pool::Handle, popup::PopupBuilder, widget::WidgetBuilder,
162/// # BuildContext, UiNode,
163/// # };
164/// fn create_popup_with_button(ctx: &mut BuildContext) -> Handle<UiNode> {
165/// PopupBuilder::new(WidgetBuilder::new())
166/// .with_content(
167/// ButtonBuilder::new(WidgetBuilder::new())
168/// .with_text("Click Me!")
169/// .build(ctx),
170/// )
171/// .build(ctx)
172/// }
173/// ```
174///
175/// Keep in mind, that the popup is closed by default. You need to open it explicitly by sending a [`PopupMessage::Open`] to it,
176/// otherwise you won't see it:
177///
178/// ```rust
179/// # use fyrox_ui::{
180/// # button::ButtonBuilder,
181/// # core::pool::Handle,
182/// # message::MessageDirection,
183/// # popup::{Placement, PopupBuilder, PopupMessage},
184/// # widget::WidgetBuilder,
185/// # UiNode, UserInterface,
186/// # };
187/// fn create_popup_with_button_and_open_it(ui: &mut UserInterface) -> Handle<UiNode> {
188/// let popup = PopupBuilder::new(WidgetBuilder::new())
189/// .with_content(
190/// ButtonBuilder::new(WidgetBuilder::new())
191/// .with_text("Click Me!")
192/// .build(&mut ui.build_ctx()),
193/// )
194/// .build(&mut ui.build_ctx());
195///
196/// // Open the popup explicitly.
197/// ui.send_message(PopupMessage::open(popup, MessageDirection::ToWidget));
198///
199/// popup
200/// }
201/// ```
202///
203/// ## Placement
204///
205/// Since popups are usually used to show useful context-specific information (like context menus, drop-down lists, etc.), they're usually
206/// open above some other widget with specific alignment (right, left, center, etc.).
207///
208/// ```rust
209/// # use fyrox_ui::{
210/// # button::ButtonBuilder,
211/// # core::pool::Handle,
212/// # message::MessageDirection,
213/// # popup::{Placement, PopupBuilder, PopupMessage},
214/// # widget::WidgetBuilder,
215/// # UiNode, UserInterface,
216/// # };
217/// fn create_popup_with_button_and_open_it(ui: &mut UserInterface) -> Handle<UiNode> {
218/// let popup = PopupBuilder::new(WidgetBuilder::new())
219/// .with_content(
220/// ButtonBuilder::new(WidgetBuilder::new())
221/// .with_text("Click Me!")
222/// .build(&mut ui.build_ctx()),
223/// )
224/// // Set the placement. For simplicity it is just a cursor position with Handle::NONE as placement target.
225/// .with_placement(Placement::Cursor(Handle::NONE))
226/// .build(&mut ui.build_ctx());
227///
228/// // Open the popup explicitly at the current placement.
229/// ui.send_message(PopupMessage::open(popup, MessageDirection::ToWidget));
230///
231/// popup
232/// }
233/// ```
234///
235/// The example uses [`Placement::Cursor`] with [`Handle::NONE`] placement target for simplicity reasons, however in
236/// the real-world usages this handle must be a handle of some widget that is located under the popup. It is very
237/// important to specify it correctly, otherwise you will lost the built-in ability to fetch the actual placement target.
238/// For example, imagine that you're building your own custom [`crate::dropdown_list::DropdownList`] widget and the popup
239/// is used to display content of the list. In this case you could specify the placement target like this:
240///
241/// ```rust
242/// # use fyrox_ui::{
243/// # button::ButtonBuilder,
244/// # core::pool::Handle,
245/// # message::MessageDirection,
246/// # popup::{Placement, PopupBuilder, PopupMessage},
247/// # widget::WidgetBuilder,
248/// # UiNode, UserInterface,
249/// # };
250/// fn create_popup_with_button_and_open_it(
251/// dropdown_list: Handle<UiNode>,
252/// ui: &mut UserInterface,
253/// ) -> Handle<UiNode> {
254/// let popup = PopupBuilder::new(WidgetBuilder::new())
255/// .with_content(
256/// ButtonBuilder::new(WidgetBuilder::new())
257/// .with_text("Click Me!")
258/// .build(&mut ui.build_ctx()),
259/// )
260/// // Set the placement to the dropdown list.
261/// .with_placement(Placement::LeftBottom(dropdown_list))
262/// .build(&mut ui.build_ctx());
263///
264/// // Open the popup explicitly at the current placement.
265/// ui.send_message(PopupMessage::open(popup, MessageDirection::ToWidget));
266///
267/// popup
268/// }
269/// ```
270///
271/// In this case, the popup will open at the left bottom corner of the dropdown list automatically. Placement target is also
272/// useful to build context menus, especially for lists with multiple items. Each item in the list usually have the same context
273/// menu, and this is ideal use case for popups, since the single context menu can be shared across multiple list items. To find
274/// which item cause the context menu to open, catch [`PopupMessage::Placement`] and extract the node handle - this will be your
275/// actual item.
276///
277/// ## Opening mode
278///
279/// By default, when you click outside of your popup it will automatically close. It is pretty common behaviour in the UI, you
280/// can see it almost everytime you use context menus in various apps. There are cases when this behaviour is undesired and it
281/// can be turned off:
282///
283/// ```rust
284/// # use fyrox_ui::{
285/// # button::ButtonBuilder, core::pool::Handle, popup::PopupBuilder, widget::WidgetBuilder,
286/// # BuildContext, UiNode,
287/// # };
288/// fn create_popup_with_button(ctx: &mut BuildContext) -> Handle<UiNode> {
289/// PopupBuilder::new(WidgetBuilder::new())
290/// .with_content(
291/// ButtonBuilder::new(WidgetBuilder::new())
292/// .with_text("Click Me!")
293/// .build(ctx),
294/// )
295/// // This forces the popup to stay open when clicked outside of its bounds
296/// .stays_open(true)
297/// .build(ctx)
298/// }
299/// ```
300///
301/// ## Smart placement
302///
303/// Popup widget can automatically adjust its position to always remain on screen, which is useful for tooltips, dropdown lists,
304/// etc. To enable this option, use [`PopupBuilder::with_smart_placement`] with `true` as the first argument.
305#[derive(Default, Clone, Visit, Debug, Reflect, ComponentProvider)]
306pub struct Popup {
307 /// Base widget of the popup.
308 pub widget: Widget,
309 /// Current placement of the popup.
310 pub placement: InheritableVariable<Placement>,
311 /// A flag, that defines whether the popup will stay open if a user click outside of its bounds.
312 pub stays_open: InheritableVariable<bool>,
313 /// A flag, that defines whether the popup is open or not.
314 pub is_open: InheritableVariable<bool>,
315 /// Current content of the popup.
316 pub content: InheritableVariable<Handle<UiNode>>,
317 /// Background widget of the popup. It is used as a container for the content.
318 pub body: InheritableVariable<Handle<UiNode>>,
319 /// Smart placement prevents the popup from going outside of the screen bounds. It is usually used for tooltips,
320 /// dropdown lists, etc. to prevent the content from being outside of the screen.
321 pub smart_placement: InheritableVariable<bool>,
322 /// The destination for Event messages that relay messages from the children of this popup.
323 pub owner: Handle<UiNode>,
324}
325
326impl ConstructorProvider<UiNode, UserInterface> for Popup {
327 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
328 GraphNodeConstructor::new::<Self>()
329 .with_variant("Popup", |ui| {
330 PopupBuilder::new(WidgetBuilder::new().with_name("Popup"))
331 .build(&mut ui.build_ctx())
332 .into()
333 })
334 .with_group("Layout")
335 }
336}
337
338crate::define_widget_deref!(Popup);
339
340fn adjust_placement_position(
341 node_screen_bounds: Rect<f32>,
342 screen_size: Vector2<f32>,
343) -> Vector2<f32> {
344 let mut new_position = node_screen_bounds.position;
345 let right_bottom = node_screen_bounds.right_bottom_corner();
346 if right_bottom.x > screen_size.x {
347 new_position.x -= right_bottom.x - screen_size.x;
348 }
349 if right_bottom.y > screen_size.y {
350 new_position.y -= right_bottom.y - screen_size.y;
351 }
352 new_position
353}
354
355impl Popup {
356 fn left_top_placement(&self, ui: &UserInterface, target: Handle<UiNode>) -> Vector2<f32> {
357 ui.try_get(target)
358 .map(|n| n.screen_position())
359 .unwrap_or_default()
360 }
361
362 fn right_top_placement(&self, ui: &UserInterface, target: Handle<UiNode>) -> Vector2<f32> {
363 ui.try_get(target)
364 .map(|n| n.screen_position() + Vector2::new(n.actual_global_size().x, 0.0))
365 .unwrap_or_else(|| {
366 Vector2::new(ui.screen_size().x - self.widget.actual_global_size().x, 0.0)
367 })
368 }
369
370 fn center_placement(&self, ui: &UserInterface, target: Handle<UiNode>) -> Vector2<f32> {
371 ui.try_get(target)
372 .map(|n| n.screen_position() + n.actual_global_size().scale(0.5))
373 .unwrap_or_else(|| (ui.screen_size - self.widget.actual_global_size()).scale(0.5))
374 }
375
376 fn left_bottom_placement(&self, ui: &UserInterface, target: Handle<UiNode>) -> Vector2<f32> {
377 ui.try_get(target)
378 .map(|n| n.screen_position() + Vector2::new(0.0, n.actual_global_size().y))
379 .unwrap_or_else(|| {
380 Vector2::new(0.0, ui.screen_size().y - self.widget.actual_global_size().y)
381 })
382 }
383
384 fn right_bottom_placement(&self, ui: &UserInterface, target: Handle<UiNode>) -> Vector2<f32> {
385 ui.try_get(target)
386 .map(|n| n.screen_position() + n.actual_global_size())
387 .unwrap_or_else(|| ui.screen_size - self.widget.actual_global_size())
388 }
389}
390
391uuid_provider!(Popup = "1c641540-59eb-4ccd-a090-2173dab02245");
392
393impl Control for Popup {
394 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
395 self.widget.handle_routed_message(ui, message);
396
397 if let Some(msg) = message.data::<PopupMessage>() {
398 if message.destination() == self.handle() {
399 match msg {
400 PopupMessage::Open => {
401 if !*self.is_open {
402 self.is_open.set_value_and_mark_modified(true);
403 ui.send_message(WidgetMessage::visibility(
404 self.handle(),
405 MessageDirection::ToWidget,
406 true,
407 ));
408 ui.push_picking_restriction(RestrictionEntry {
409 handle: self.handle(),
410 stop: false,
411 });
412 ui.send_message(WidgetMessage::topmost(
413 self.handle(),
414 MessageDirection::ToWidget,
415 ));
416 let position = match *self.placement {
417 Placement::LeftTop(target) => self.left_top_placement(ui, target),
418 Placement::RightTop(target) => self.right_top_placement(ui, target),
419 Placement::Center(target) => self.center_placement(ui, target),
420 Placement::LeftBottom(target) => {
421 self.left_bottom_placement(ui, target)
422 }
423 Placement::RightBottom(target) => {
424 self.right_bottom_placement(ui, target)
425 }
426 Placement::Cursor(_) => ui.cursor_position(),
427 Placement::Position { position, .. } => position,
428 };
429
430 ui.send_message(WidgetMessage::desired_position(
431 self.handle(),
432 MessageDirection::ToWidget,
433 ui.screen_to_root_canvas_space(position),
434 ));
435 ui.send_message(WidgetMessage::focus(
436 if self.content.is_some() {
437 *self.content
438 } else {
439 self.handle
440 },
441 MessageDirection::ToWidget,
442 ));
443 if *self.smart_placement {
444 ui.send_message(PopupMessage::adjust_position(
445 self.handle,
446 MessageDirection::ToWidget,
447 ));
448 }
449 }
450 }
451 PopupMessage::Close => {
452 if *self.is_open {
453 self.is_open.set_value_and_mark_modified(false);
454 ui.send_message(WidgetMessage::visibility(
455 self.handle(),
456 MessageDirection::ToWidget,
457 false,
458 ));
459 ui.remove_picking_restriction(self.handle());
460
461 if let Some(top) = ui.top_picking_restriction() {
462 ui.send_message(WidgetMessage::focus(
463 top.handle,
464 MessageDirection::ToWidget,
465 ));
466 }
467
468 if ui.captured_node() == self.handle() {
469 ui.release_mouse_capture();
470 }
471 }
472 }
473 PopupMessage::Content(content) => {
474 if self.content.is_some() {
475 ui.send_message(WidgetMessage::remove(
476 *self.content,
477 MessageDirection::ToWidget,
478 ));
479 }
480 self.content.set_value_and_mark_modified(*content);
481
482 ui.send_message(WidgetMessage::link(
483 *self.content,
484 MessageDirection::ToWidget,
485 *self.body,
486 ));
487 }
488 PopupMessage::Placement(placement) => {
489 self.placement.set_value_and_mark_modified(*placement);
490 self.invalidate_layout();
491 }
492 PopupMessage::AdjustPosition => {
493 let new_position =
494 adjust_placement_position(self.screen_bounds(), ui.screen_size());
495
496 if new_position != self.screen_position() {
497 ui.send_message(WidgetMessage::desired_position(
498 self.handle,
499 MessageDirection::ToWidget,
500 ui.screen_to_root_canvas_space(new_position),
501 ));
502 }
503 }
504 PopupMessage::Owner(owner) => {
505 self.owner = *owner;
506 }
507 PopupMessage::RelayedMessage(_) => (),
508 }
509 }
510 } else if let Some(WidgetMessage::KeyDown(key)) = message.data() {
511 if !message.handled() && *key == KeyCode::Escape {
512 ui.send_message(PopupMessage::close(self.handle, MessageDirection::ToWidget));
513 message.set_handled(true);
514 }
515 }
516 if ui.is_valid_handle(self.owner) && !message.handled() {
517 ui.send_message(PopupMessage::relayed_message(
518 self.owner,
519 MessageDirection::ToWidget,
520 message.clone(),
521 ));
522 }
523 }
524
525 fn handle_os_event(
526 &mut self,
527 self_handle: Handle<UiNode>,
528 ui: &mut UserInterface,
529 event: &OsEvent,
530 ) {
531 if let OsEvent::MouseInput { state, .. } = event {
532 if let Some(top_restriction) = ui.top_picking_restriction() {
533 if *state == ButtonState::Pressed
534 && top_restriction.handle == self_handle
535 && *self.is_open
536 {
537 let pos = ui.cursor_position();
538 if !self.widget.screen_bounds().contains(pos) && !*self.stays_open {
539 ui.send_message(PopupMessage::close(
540 self.handle(),
541 MessageDirection::ToWidget,
542 ));
543 }
544 }
545 }
546 }
547 }
548}
549
550/// Popup widget builder is used to create [`Popup`] widget instances and add them to the user interface.
551pub struct PopupBuilder {
552 widget_builder: WidgetBuilder,
553 placement: Placement,
554 stays_open: bool,
555 content: Handle<UiNode>,
556 smart_placement: bool,
557 owner: Handle<UiNode>,
558}
559
560impl PopupBuilder {
561 /// Creates new builder instance.
562 pub fn new(widget_builder: WidgetBuilder) -> Self {
563 Self {
564 widget_builder,
565 placement: Placement::Cursor(Default::default()),
566 stays_open: false,
567 content: Default::default(),
568 smart_placement: true,
569 owner: Default::default(),
570 }
571 }
572
573 /// Sets the desired popup placement.
574 pub fn with_placement(mut self, placement: Placement) -> Self {
575 self.placement = placement;
576 self
577 }
578
579 /// Enables or disables smart placement.
580 pub fn with_smart_placement(mut self, smart_placement: bool) -> Self {
581 self.smart_placement = smart_placement;
582 self
583 }
584
585 /// Defines whether to keep the popup open when user clicks outside of its content or not.
586 pub fn stays_open(mut self, value: bool) -> Self {
587 self.stays_open = value;
588 self
589 }
590
591 /// Sets the content of the popup.
592 pub fn with_content(mut self, content: Handle<UiNode>) -> Self {
593 self.content = content;
594 self
595 }
596
597 /// Sets the desired owner of the popup, to which the popup will relay its own messages.
598 pub fn with_owner(mut self, owner: Handle<UiNode>) -> Self {
599 self.owner = owner;
600 self
601 }
602
603 /// Builds the popup widget, but does not add it to the user interface. Could be useful if you're making your
604 /// own derived version of the popup.
605 pub fn build_popup(self, ctx: &mut BuildContext) -> Popup {
606 let style = &ctx.style;
607
608 let body = BorderBuilder::new(
609 WidgetBuilder::new()
610 .with_background(style.property(Style::BRUSH_PRIMARY))
611 .with_foreground(style.property(Style::BRUSH_DARKEST))
612 .with_child(self.content),
613 )
614 .with_stroke_thickness(Thickness::uniform(1.0).into())
615 .build(ctx);
616
617 Popup {
618 widget: self
619 .widget_builder
620 .with_child(body)
621 .with_visibility(false)
622 .with_handle_os_events(true)
623 .build(ctx),
624 placement: self.placement.into(),
625 stays_open: self.stays_open.into(),
626 is_open: false.into(),
627 content: self.content.into(),
628 smart_placement: self.smart_placement.into(),
629 body: body.into(),
630 owner: self.owner,
631 }
632 }
633
634 /// Finishes building the [`Popup`] instance and adds to the user interface and returns its handle.
635 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
636 let popup = self.build_popup(ctx);
637 ctx.add_node(UiNode::new(popup))
638 }
639}
640
641#[cfg(test)]
642mod test {
643 use crate::popup::PopupBuilder;
644 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
645
646 #[test]
647 fn test_deletion() {
648 test_widget_deletion(|ctx| PopupBuilder::new(WidgetBuilder::new()).build(ctx));
649 }
650}