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