fyrox_ui/
window.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//! The Window widget provides a standard window that can contain another widget. See [`Window`] docs
22//! for more info and usage examples.
23
24use crate::style::StyledProperty;
25use crate::{
26    border::BorderBuilder,
27    brush::Brush,
28    button::{ButtonBuilder, ButtonMessage},
29    core::{
30        algebra::Vector2, color::Color, math::Rect, pool::Handle, reflect::prelude::*,
31        type_traits::prelude::*, uuid_provider, visitor::prelude::*,
32    },
33    decorator::DecoratorBuilder,
34    define_constructor,
35    font::FontResource,
36    grid::{Column, GridBuilder, Row},
37    message::{CursorIcon, KeyCode, MessageDirection, UiMessage},
38    navigation::NavigationLayerBuilder,
39    style::resource::StyleResourceExt,
40    style::Style,
41    text::{Text, TextBuilder, TextMessage},
42    vector_image::{Primitive, VectorImageBuilder},
43    widget::{Widget, WidgetBuilder, WidgetMessage},
44    BuildContext, Control, HorizontalAlignment, RestrictionEntry, Thickness, UiNode, UserInterface,
45    VerticalAlignment,
46};
47use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
48use fyrox_graph::{BaseSceneGraph, SceneGraph};
49use std::{
50    cell::RefCell,
51    ops::{Deref, DerefMut},
52};
53
54/// A set of possible messages that can be used to modify the state of a window or listen to changes
55/// in the window.
56#[derive(Debug, Clone, PartialEq)]
57pub enum WindowMessage {
58    /// Opens a window.
59    Open {
60        /// A flag that defines whether the window should be centered or not.
61        center: bool,
62        /// A flag that defines whether the content of the window should be focused when the window
63        /// is opening.
64        focus_content: bool,
65    },
66
67    /// Opens a window at the given local coordinates.
68    OpenAt {
69        position: Vector2<f32>,
70        /// A flag that defines whether the content of the window should be focused when the window
71        /// is opening.
72        focus_content: bool,
73    },
74
75    /// Opens a window (optionally modal) and aligns it relative the to the given node.
76    OpenAndAlign {
77        /// A handle of a node to which the sender of this message should be aligned to.
78        relative_to: Handle<UiNode>,
79        /// Horizontal alignment of the widget.
80        horizontal_alignment: HorizontalAlignment,
81        /// Vertical alignment of the widget.
82        vertical_alignment: VerticalAlignment,
83        /// Margins for each side.
84        margin: Thickness,
85        /// Should the window be opened in modal mode or not.
86        modal: bool,
87        /// A flag that defines whether the content of the window should be focused when the window
88        /// is opening.
89        focus_content: bool,
90    },
91
92    /// Opens window in modal mode. Modal mode does **not** blocks current thread, instead
93    /// it just restricts mouse and keyboard events only to window so other content is not
94    /// clickable/type-able. Closing a window removes that restriction.
95    OpenModal {
96        /// A flag that defines whether the window should be centered or not.
97        center: bool,
98        /// A flag that defines whether the content of the window should be focused when the window
99        /// is opening.
100        focus_content: bool,
101    },
102
103    /// Closes a window.
104    Close,
105
106    /// Minimizes a window - it differs from classic minimization in window managers,
107    /// instead of putting window in system tray, it just collapses internal content panel.
108    Minimize(bool),
109
110    /// Forces the window to take the inner size of main application window.
111    Maximize,
112
113    /// Whether or not window can be minimized by _ mark. false hides _ mark.
114    CanMinimize(bool),
115
116    /// Whether or not window can be closed by X mark. false hides X mark.
117    CanClose(bool),
118
119    /// Whether or not window can be resized by resize grips.
120    CanResize(bool),
121
122    /// Indicates that move has been started. You should never send this message by hand.
123    MoveStart,
124
125    /// Moves window to a new position in local coordinates.
126    Move(Vector2<f32>),
127
128    /// Indicated that move has ended. You should never send this message by hand.
129    MoveEnd,
130
131    /// Sets new window title.
132    Title(WindowTitle),
133
134    /// Safe border size defines "part" of a window that should always be on screen when dragged.
135    /// It is used to prevent moving window outside of main application window bounds, to still
136    /// be able to drag it.
137    SafeBorderSize(Option<Vector2<f32>>),
138}
139
140impl WindowMessage {
141    define_constructor!(
142        /// Creates [`WindowMessage::Open`] message.
143        WindowMessage:Open => fn open(center: bool, focus_content: bool), layout: false
144    );
145    define_constructor!(
146        /// Creates [`WindowMessage::OpenAt`] message.
147        WindowMessage:OpenAt => fn open_at(position: Vector2<f32>, focus_content: bool), layout: false
148    );
149    define_constructor!(
150        /// Creates [`WindowMessage::OpenAndAlign`] message.
151        WindowMessage:OpenAndAlign => fn open_and_align(
152            relative_to: Handle<UiNode>,
153            horizontal_alignment: HorizontalAlignment,
154            vertical_alignment: VerticalAlignment,
155            margin: Thickness,
156            modal: bool,
157            focus_content: bool
158        ), layout: false
159    );
160    define_constructor!(
161        /// Creates [`WindowMessage::OpenModal`] message.
162        WindowMessage:OpenModal => fn open_modal(center: bool, focus_content: bool), layout: false
163    );
164    define_constructor!(
165        /// Creates [`WindowMessage::Close`] message.
166        WindowMessage:Close => fn close(), layout: false
167    );
168    define_constructor!(
169        /// Creates [`WindowMessage::Minimize`] message.
170        WindowMessage:Minimize => fn minimize(bool), layout: false
171    );
172    define_constructor!(
173        /// Creates [`WindowMessage::Maximize`] message.
174        WindowMessage:Maximize => fn maximize(), layout: false
175    );
176    define_constructor!(
177        /// Creates [`WindowMessage::CanMinimize`] message.
178        WindowMessage:CanMinimize => fn can_minimize(bool), layout: false
179    );
180    define_constructor!(
181        /// Creates [`WindowMessage::CanClose`] message.
182        WindowMessage:CanClose => fn can_close(bool), layout: false
183    );
184    define_constructor!(
185        /// Creates [`WindowMessage::CanResize`] message.
186        WindowMessage:CanResize => fn can_resize(bool), layout: false
187    );
188    define_constructor!(
189        /// Creates [`WindowMessage::MoveStart`] message.
190        WindowMessage:MoveStart => fn move_start(), layout: false
191    );
192    define_constructor!(
193        /// Creates [`WindowMessage::Move`] message.
194        WindowMessage:Move => fn move_to(Vector2<f32>), layout: false
195    );
196    define_constructor!(
197        /// Creates [`WindowMessage::MoveEnd`] message.
198        WindowMessage:MoveEnd => fn move_end(), layout: false
199    );
200    define_constructor!(
201        /// Creates [`WindowMessage::Title`] message.
202        WindowMessage:Title => fn title(WindowTitle), layout: false
203    );
204    define_constructor!(
205        /// Creates [`WindowMessage::SafeBorderSize`] message.
206        WindowMessage:SafeBorderSize => fn safe_border_size(Option<Vector2<f32>>), layout: false
207    );
208}
209
210/// The Window widget provides a standard window that can contain another widget. Based on setting
211/// windows can be configured so users can do any of the following:
212///
213/// * Movable by the user. Not configurable.
214/// * Have title text on the title bar. Set by the *with_title* function.
215/// * Able to be exited by the user. Set by the *can_close* function.
216/// * Able to be minimized to just the Title bar, and of course maximized again. Set by the
217/// *can_minimize* function.
218/// * Able to resize the window. Set by the *can_resize* function.
219///
220/// As with other UI elements, you create and configure the window using the WindowBuilder.
221///
222/// ```rust,no_run
223/// # use fyrox_ui::{
224/// #     core::{pool::Handle, algebra::Vector2},
225/// #     window::{WindowBuilder, WindowTitle},
226/// #     text::TextBuilder,
227/// #     widget::WidgetBuilder,
228/// #     UiNode,
229/// #     UserInterface
230/// # };
231/// fn create_window(ui: &mut UserInterface) {
232///     WindowBuilder::new(
233///         WidgetBuilder::new()
234///             .with_desired_position(Vector2::new(300.0, 0.0))
235///             .with_width(300.0),
236///     )
237///     .with_content(
238///         TextBuilder::new(WidgetBuilder::new())
239///             .with_text("Example Window content.")
240///             .build(&mut ui.build_ctx())
241///     )
242///     .with_title(WindowTitle::text("Window"))
243///     .can_close(true)
244///     .can_minimize(true)
245///     .open(true)
246///     .can_resize(false)
247///     .build(&mut ui.build_ctx());
248/// }
249/// ```
250///
251/// You will likely want to constrain the initial size of the window to something as shown in the
252/// example by providing a set width and/or height to the base WidgetBuilder. Otherwise it will
253/// expand to fit it's content.
254///
255/// You may also want to set an initial position with the *with_desired_position* function called
256/// on the base WidgetBuilder which sets the position of the window's top-left corner. Otherwise all
257/// your windows will start with it's top-left corner at 0,0 and be stacked on top of each other.
258///
259/// Windows can only contain a single direct child widget, set by using the *with_content* function.
260/// Additional calls to *with_content* replaces the widgets given in previous calls, and the old
261/// widgets exist outside the window, so you should delete old widgets before changing a window's
262/// widget. If you want multiple widgets, you need to use one of the layout container widgets like
263/// the Grid, Stack Panel, etc then add the additional widgets to that widget as needed.
264///
265/// The Window is a user editable object, but can only be affected by UI Messages they trigger if
266/// the message's corresponding variable has been set to true aka what is set by the *can_close*,
267/// *can_minimize*, and *can_resize* functions.
268///
269/// ## Initial Open State
270///
271/// By default, the window will be created in the open, or maximized, state. You can manually set
272/// this state via the *open* function providing a true or false as desired.
273///
274/// ## Styling the Buttons
275///
276/// The window close and minimise buttons can be configured with the *with_close_button* and
277/// *with_minimize_button* functions. You will want to pass them a button widget, but can do anything
278/// else you like past that.
279///
280/// ## Modal (AKA Forced Focus)
281///
282/// A Modal in UI design terms indicates a window or box that has forced focus. The user is not able
283/// to interact with anything else until the modal is dismissed.
284///
285/// Any window can be set and unset as a modal via the *modal* function.
286#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
287pub struct Window {
288    /// Base widget of the window.
289    pub widget: Widget,
290    /// Mouse click position.
291    pub mouse_click_pos: Vector2<f32>,
292    /// Initial mouse position.
293    pub initial_position: Vector2<f32>,
294    /// Initial size of the window.
295    pub initial_size: Vector2<f32>,
296    /// Whether the window is being dragged or not.
297    pub is_dragging: bool,
298    /// Whether the window is minimized or not.
299    pub minimized: bool,
300    /// Whether the window can be minimized or not.
301    pub can_minimize: bool,
302    /// Whether the window can be maximized or not.
303    pub can_maximize: bool,
304    /// Whether the window can be closed or not.
305    pub can_close: bool,
306    /// Whether the window can be resized or not.
307    pub can_resize: bool,
308    /// Handle of a header widget.
309    pub header: Handle<UiNode>,
310    /// Handle of a minimize button.
311    pub minimize_button: Handle<UiNode>,
312    /// Handle of a maximize button.
313    pub maximize_button: Handle<UiNode>,
314    /// Handle of a close button.
315    pub close_button: Handle<UiNode>,
316    /// A distance per each axis when the dragging starts.
317    pub drag_delta: Vector2<f32>,
318    /// Handle of a current content.
319    pub content: Handle<UiNode>,
320    /// Eight grips of the window that are used to resize the window.
321    pub grips: RefCell<[Grip; 8]>,
322    /// Handle of a title widget of the window.
323    pub title: Handle<UiNode>,
324    /// Handle of a container widget of the title.
325    pub title_grid: Handle<UiNode>,
326    /// Optional size of the border around the screen in which the window will be forced to stay.
327    pub safe_border_size: Option<Vector2<f32>>,
328    /// Bounds of the window before maximization, it is used to return the window to previous
329    /// size when it is either "unmaximized" or dragged.
330    pub prev_bounds: Option<Rect<f32>>,
331    /// If `true`, then the window can be closed using `Esc` key. Default is `true`. Works only if
332    /// `can_close` is also `true`.
333    #[visit(optional)] // Backward compatibility
334    pub close_by_esc: bool,
335    /// If `true`, then the window will be deleted after closing.
336    #[visit(optional)] // Backward compatibility
337    pub remove_on_close: bool,
338}
339
340impl ConstructorProvider<UiNode, UserInterface> for Window {
341    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
342        GraphNodeConstructor::new::<Self>()
343            .with_variant("Window", |ui| {
344                WindowBuilder::new(WidgetBuilder::new().with_name("Window"))
345                    .build(&mut ui.build_ctx())
346                    .into()
347            })
348            .with_group("Layout")
349    }
350}
351
352const GRIP_SIZE: f32 = 6.0;
353const CORNER_GRIP_SIZE: f32 = GRIP_SIZE * 2.0;
354
355/// Kind of a resizing grip.
356#[derive(Copy, Clone, Debug, Visit, Reflect, Default)]
357pub enum GripKind {
358    /// Left-top corner grip.
359    #[default]
360    LeftTopCorner = 0,
361    /// Right-top corner grip.
362    RightTopCorner = 1,
363    /// Right-bottom corner grip.
364    RightBottomCorner = 2,
365    /// Left-bottom corner grip.
366    LeftBottomCorner = 3,
367    /// Left corner grip.
368    Left = 4,
369    /// Top corner grip.
370    Top = 5,
371    /// Right corner grip.
372    Right = 6,
373    /// Bottom corner grip.
374    Bottom = 7,
375}
376
377/// Resizing grip.
378#[derive(Clone, Visit, Default, Debug, Reflect)]
379pub struct Grip {
380    /// Kind of the grip.
381    pub kind: GripKind,
382    /// Bounds of the grip in local-space.
383    pub bounds: Rect<f32>,
384    /// A flag, that is raised when the grip is being dragged.
385    pub is_dragging: bool,
386    /// Cursor type of the grip.
387    pub cursor: CursorIcon,
388}
389
390impl Grip {
391    fn new(kind: GripKind, cursor: CursorIcon) -> Self {
392        Self {
393            kind,
394            bounds: Default::default(),
395            is_dragging: false,
396            cursor,
397        }
398    }
399}
400
401crate::define_widget_deref!(Window);
402
403uuid_provider!(Window = "9331bf32-8614-4005-874c-5239e56bb15e");
404
405impl Control for Window {
406    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
407        let size = self.widget.arrange_override(ui, final_size);
408
409        let mut grips = self.grips.borrow_mut();
410
411        // Adjust grips.
412        grips[GripKind::Left as usize].bounds =
413            Rect::new(0.0, GRIP_SIZE, GRIP_SIZE, final_size.y - GRIP_SIZE * 2.0);
414        grips[GripKind::Top as usize].bounds =
415            Rect::new(GRIP_SIZE, 0.0, final_size.x - GRIP_SIZE * 2.0, GRIP_SIZE);
416        grips[GripKind::Right as usize].bounds = Rect::new(
417            final_size.x - GRIP_SIZE,
418            GRIP_SIZE,
419            GRIP_SIZE,
420            final_size.y - GRIP_SIZE * 2.0,
421        );
422        grips[GripKind::Bottom as usize].bounds = Rect::new(
423            GRIP_SIZE,
424            final_size.y - GRIP_SIZE,
425            final_size.x - GRIP_SIZE * 2.0,
426            GRIP_SIZE,
427        );
428
429        // Corners have different size to improve usability.
430        grips[GripKind::LeftTopCorner as usize].bounds =
431            Rect::new(0.0, 0.0, CORNER_GRIP_SIZE, CORNER_GRIP_SIZE);
432        grips[GripKind::RightTopCorner as usize].bounds = Rect::new(
433            final_size.x - GRIP_SIZE,
434            0.0,
435            CORNER_GRIP_SIZE,
436            CORNER_GRIP_SIZE,
437        );
438        grips[GripKind::RightBottomCorner as usize].bounds = Rect::new(
439            final_size.x - CORNER_GRIP_SIZE,
440            final_size.y - CORNER_GRIP_SIZE,
441            CORNER_GRIP_SIZE,
442            CORNER_GRIP_SIZE,
443        );
444        grips[GripKind::LeftBottomCorner as usize].bounds = Rect::new(
445            0.0,
446            final_size.y - CORNER_GRIP_SIZE,
447            CORNER_GRIP_SIZE,
448            CORNER_GRIP_SIZE,
449        );
450
451        size
452    }
453
454    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
455        self.widget.handle_routed_message(ui, message);
456
457        if let Some(msg) = message.data::<WidgetMessage>() {
458            // Grip interaction have higher priority than other actions.
459            if self.can_resize && !self.is_dragging {
460                match msg {
461                    &WidgetMessage::MouseDown { pos, .. } => {
462                        ui.send_message(WidgetMessage::topmost(
463                            self.handle(),
464                            MessageDirection::ToWidget,
465                        ));
466
467                        // Check grips.
468                        for grip in self.grips.borrow_mut().iter_mut() {
469                            let screen_bounds = grip.bounds.transform(&self.visual_transform);
470                            if screen_bounds.contains(pos) {
471                                grip.is_dragging = true;
472                                self.initial_position = self.screen_position();
473                                self.initial_size = self.actual_local_size();
474                                self.mouse_click_pos = pos;
475                                ui.capture_mouse(self.handle());
476                                break;
477                            }
478                        }
479                    }
480                    WidgetMessage::MouseUp { .. } => {
481                        for grip in self.grips.borrow_mut().iter_mut() {
482                            if grip.is_dragging {
483                                ui.release_mouse_capture();
484                                grip.is_dragging = false;
485                                break;
486                            }
487                        }
488                    }
489                    &WidgetMessage::MouseMove { pos, .. } => {
490                        let mut new_cursor = None;
491
492                        for grip in self.grips.borrow().iter() {
493                            let screen_bounds = grip.bounds.transform(&self.visual_transform);
494                            if grip.is_dragging || screen_bounds.contains(pos) {
495                                new_cursor = Some(grip.cursor);
496                            }
497
498                            if grip.is_dragging {
499                                let delta = self.mouse_click_pos - pos;
500                                let (dx, dy, dw, dh) = match grip.kind {
501                                    GripKind::Left => (-1.0, 0.0, 1.0, 0.0),
502                                    GripKind::Top => (0.0, -1.0, 0.0, 1.0),
503                                    GripKind::Right => (0.0, 0.0, -1.0, 0.0),
504                                    GripKind::Bottom => (0.0, 0.0, 0.0, -1.0),
505                                    GripKind::LeftTopCorner => (-1.0, -1.0, 1.0, 1.0),
506                                    GripKind::RightTopCorner => (0.0, -1.0, -1.0, 1.0),
507                                    GripKind::RightBottomCorner => (0.0, 0.0, -1.0, -1.0),
508                                    GripKind::LeftBottomCorner => (-1.0, 0.0, 1.0, -1.0),
509                                };
510
511                                let new_pos = self.initial_position
512                                    + Vector2::new(delta.x * dx, delta.y * dy);
513                                let new_size =
514                                    self.initial_size + Vector2::new(delta.x * dw, delta.y * dh);
515
516                                if new_size.x > self.min_width()
517                                    && new_size.x < self.max_width()
518                                    && new_size.y > self.min_height()
519                                    && new_size.y < self.max_height()
520                                {
521                                    ui.send_message(WidgetMessage::desired_position(
522                                        self.handle(),
523                                        MessageDirection::ToWidget,
524                                        ui.screen_to_root_canvas_space(new_pos),
525                                    ));
526                                    ui.send_message(WidgetMessage::width(
527                                        self.handle(),
528                                        MessageDirection::ToWidget,
529                                        new_size.x,
530                                    ));
531                                    ui.send_message(WidgetMessage::height(
532                                        self.handle(),
533                                        MessageDirection::ToWidget,
534                                        new_size.y,
535                                    ));
536                                }
537
538                                break;
539                            }
540                        }
541
542                        self.set_cursor(new_cursor);
543                    }
544                    _ => {}
545                }
546            } else {
547                // The window cannot be resized, so leave the cursor unset.
548                self.set_cursor(None);
549            }
550
551            if (message.destination() == self.header
552                || ui
553                    .node(self.header)
554                    .has_descendant(message.destination(), ui))
555                && !message.handled()
556                && !self.has_active_grip()
557            {
558                match msg {
559                    WidgetMessage::MouseDown { pos, .. } => {
560                        self.mouse_click_pos = *pos;
561                        ui.send_message(WindowMessage::move_start(
562                            self.handle,
563                            MessageDirection::ToWidget,
564                        ));
565                        message.set_handled(true);
566                    }
567                    WidgetMessage::MouseUp { .. } => {
568                        ui.send_message(WindowMessage::move_end(
569                            self.handle,
570                            MessageDirection::ToWidget,
571                        ));
572                        message.set_handled(true);
573                    }
574                    WidgetMessage::MouseMove { pos, .. } => {
575                        if self.is_dragging {
576                            self.drag_delta = *pos - self.mouse_click_pos;
577                            let new_pos = self.initial_position + self.drag_delta;
578                            ui.send_message(WindowMessage::move_to(
579                                self.handle(),
580                                MessageDirection::ToWidget,
581                                ui.screen_to_root_canvas_space(new_pos),
582                            ));
583                        }
584                        message.set_handled(true);
585                    }
586                    _ => (),
587                }
588            }
589            match msg {
590                WidgetMessage::Unlink => {
591                    if message.destination() == self.handle() {
592                        self.initial_position = self.screen_position();
593                    }
594                }
595                WidgetMessage::KeyDown(key_code)
596                    if self.close_by_esc
597                        && !self.is_docked(ui)
598                        && self.can_close
599                        && *key_code == KeyCode::Escape
600                        && !message.handled() =>
601                {
602                    ui.send_message(WindowMessage::close(
603                        self.handle,
604                        MessageDirection::ToWidget,
605                    ));
606                    message.set_handled(true);
607                }
608                _ => {}
609            }
610        } else if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
611            if message.destination() == self.minimize_button {
612                ui.send_message(WindowMessage::minimize(
613                    self.handle(),
614                    MessageDirection::ToWidget,
615                    !self.minimized,
616                ));
617            } else if message.destination() == self.maximize_button {
618                ui.send_message(WindowMessage::maximize(
619                    self.handle(),
620                    MessageDirection::ToWidget,
621                ));
622            } else if message.destination() == self.close_button {
623                ui.send_message(WindowMessage::close(
624                    self.handle(),
625                    MessageDirection::ToWidget,
626                ));
627            }
628        } else if let Some(msg) = message.data::<WindowMessage>() {
629            if message.destination() == self.handle()
630                && message.direction() == MessageDirection::ToWidget
631            {
632                match msg {
633                    &WindowMessage::Open {
634                        center,
635                        focus_content,
636                    } => {
637                        if !self.visibility() {
638                            ui.send_message(WidgetMessage::visibility(
639                                self.handle(),
640                                MessageDirection::ToWidget,
641                                true,
642                            ));
643                            ui.send_message(WidgetMessage::topmost(
644                                self.handle(),
645                                MessageDirection::ToWidget,
646                            ));
647                            if center {
648                                ui.send_message(WidgetMessage::center(
649                                    self.handle(),
650                                    MessageDirection::ToWidget,
651                                ));
652                            }
653                            if focus_content {
654                                ui.send_message(WidgetMessage::focus(
655                                    self.content_to_focus(),
656                                    MessageDirection::ToWidget,
657                                ));
658                            }
659                        }
660                    }
661                    &WindowMessage::OpenAt {
662                        position,
663                        focus_content,
664                    } => {
665                        if !self.visibility() {
666                            ui.send_message(WidgetMessage::visibility(
667                                self.handle(),
668                                MessageDirection::ToWidget,
669                                true,
670                            ));
671                            ui.send_message(WidgetMessage::topmost(
672                                self.handle(),
673                                MessageDirection::ToWidget,
674                            ));
675                            ui.send_message(WidgetMessage::desired_position(
676                                self.handle(),
677                                MessageDirection::ToWidget,
678                                position,
679                            ));
680                            if focus_content {
681                                ui.send_message(WidgetMessage::focus(
682                                    self.content_to_focus(),
683                                    MessageDirection::ToWidget,
684                                ));
685                            }
686                        }
687                    }
688                    &WindowMessage::OpenAndAlign {
689                        relative_to,
690                        horizontal_alignment,
691                        vertical_alignment,
692                        margin,
693                        modal,
694                        focus_content,
695                    } => {
696                        if !self.visibility() {
697                            ui.send_message(WidgetMessage::visibility(
698                                self.handle(),
699                                MessageDirection::ToWidget,
700                                true,
701                            ));
702                            ui.send_message(WidgetMessage::topmost(
703                                self.handle(),
704                                MessageDirection::ToWidget,
705                            ));
706                            ui.send_message(WidgetMessage::align(
707                                self.handle(),
708                                MessageDirection::ToWidget,
709                                relative_to,
710                                horizontal_alignment,
711                                vertical_alignment,
712                                margin,
713                            ));
714                            if modal {
715                                ui.push_picking_restriction(RestrictionEntry {
716                                    handle: self.handle(),
717                                    stop: true,
718                                });
719                            }
720                            if focus_content {
721                                ui.send_message(WidgetMessage::focus(
722                                    self.content_to_focus(),
723                                    MessageDirection::ToWidget,
724                                ));
725                            }
726                        }
727                    }
728                    &WindowMessage::OpenModal {
729                        center,
730                        focus_content,
731                    } => {
732                        if !self.visibility() {
733                            ui.send_message(WidgetMessage::visibility(
734                                self.handle(),
735                                MessageDirection::ToWidget,
736                                true,
737                            ));
738                            ui.send_message(WidgetMessage::topmost(
739                                self.handle(),
740                                MessageDirection::ToWidget,
741                            ));
742                            if center {
743                                ui.send_message(WidgetMessage::center(
744                                    self.handle(),
745                                    MessageDirection::ToWidget,
746                                ));
747                            }
748                            ui.push_picking_restriction(RestrictionEntry {
749                                handle: self.handle(),
750                                stop: true,
751                            });
752                            if focus_content {
753                                ui.send_message(WidgetMessage::focus(
754                                    self.content_to_focus(),
755                                    MessageDirection::ToWidget,
756                                ));
757                            }
758                        }
759                    }
760                    WindowMessage::Close => {
761                        if self.visibility() {
762                            ui.send_message(WidgetMessage::visibility(
763                                self.handle(),
764                                MessageDirection::ToWidget,
765                                false,
766                            ));
767                            ui.remove_picking_restriction(self.handle());
768                            if self.remove_on_close {
769                                ui.send_message(WidgetMessage::remove(
770                                    self.handle,
771                                    MessageDirection::ToWidget,
772                                ));
773                            }
774                        }
775                    }
776                    &WindowMessage::Minimize(minimized) => {
777                        if self.minimized != minimized {
778                            self.minimized = minimized;
779                            if self.content.is_some() {
780                                ui.send_message(WidgetMessage::visibility(
781                                    self.content,
782                                    MessageDirection::ToWidget,
783                                    !minimized,
784                                ));
785                            }
786                        }
787                    }
788                    WindowMessage::Maximize => {
789                        let current_size = self.actual_local_size();
790                        let current_position = self.actual_local_position();
791                        let new_bounds = self
792                            .prev_bounds
793                            .replace(Rect::new(
794                                current_position.x,
795                                current_position.y,
796                                current_size.x,
797                                current_size.y,
798                            ))
799                            .unwrap_or_else(|| {
800                                Rect::new(0.0, 0.0, ui.screen_size.x, ui.screen_size.y)
801                            });
802
803                        ui.send_message(WidgetMessage::desired_position(
804                            self.handle,
805                            MessageDirection::ToWidget,
806                            new_bounds.position,
807                        ));
808                        ui.send_message(WidgetMessage::width(
809                            self.handle,
810                            MessageDirection::ToWidget,
811                            new_bounds.w(),
812                        ));
813                        ui.send_message(WidgetMessage::height(
814                            self.handle,
815                            MessageDirection::ToWidget,
816                            new_bounds.h(),
817                        ));
818                    }
819                    &WindowMessage::CanMinimize(value) => {
820                        if self.can_minimize != value {
821                            self.can_minimize = value;
822                            if self.minimize_button.is_some() {
823                                ui.send_message(WidgetMessage::visibility(
824                                    self.minimize_button,
825                                    MessageDirection::ToWidget,
826                                    value,
827                                ));
828                            }
829                        }
830                    }
831                    &WindowMessage::CanClose(value) => {
832                        if self.can_close != value {
833                            self.can_close = value;
834                            if self.close_button.is_some() {
835                                ui.send_message(WidgetMessage::visibility(
836                                    self.close_button,
837                                    MessageDirection::ToWidget,
838                                    value,
839                                ));
840                            }
841                        }
842                    }
843                    &WindowMessage::CanResize(value) => {
844                        if self.can_resize != value {
845                            self.can_resize = value;
846                            ui.send_message(message.reverse());
847                        }
848                    }
849                    &WindowMessage::Move(mut new_pos) => {
850                        if let Some(safe_border) = self.safe_border_size {
851                            // Clamp new position in allowed bounds. This will prevent moving the window outside of main
852                            // application window, thus leaving an opportunity to drag window to some other place.
853                            new_pos.x = new_pos.x.clamp(
854                                -(self.actual_local_size().x - safe_border.x).abs(),
855                                (ui.screen_size().x - safe_border.x).abs(),
856                            );
857                            new_pos.y = new_pos
858                                .y
859                                .clamp(0.0, (ui.screen_size().y - safe_border.y).abs());
860                        }
861
862                        if self.is_dragging && self.desired_local_position() != new_pos {
863                            ui.send_message(WidgetMessage::desired_position(
864                                self.handle(),
865                                MessageDirection::ToWidget,
866                                new_pos,
867                            ));
868
869                            ui.send_message(message.reverse());
870                        }
871                    }
872                    WindowMessage::MoveStart => {
873                        if !self.is_dragging {
874                            ui.capture_mouse(self.header);
875                            let initial_position = self.screen_position();
876                            self.initial_position = initial_position;
877                            self.is_dragging = true;
878
879                            if let Some(prev_bounds) = self.prev_bounds.take() {
880                                ui.send_message(WidgetMessage::width(
881                                    self.handle,
882                                    MessageDirection::ToWidget,
883                                    prev_bounds.w(),
884                                ));
885                                ui.send_message(WidgetMessage::height(
886                                    self.handle,
887                                    MessageDirection::ToWidget,
888                                    prev_bounds.h(),
889                                ));
890                            }
891
892                            ui.send_message(message.reverse());
893                        }
894                    }
895                    WindowMessage::MoveEnd => {
896                        if self.is_dragging {
897                            ui.release_mouse_capture();
898                            self.is_dragging = false;
899
900                            ui.send_message(message.reverse());
901                        }
902                    }
903                    WindowMessage::Title(title) => {
904                        match title {
905                            WindowTitle::Text {
906                                text,
907                                font,
908                                font_size,
909                            } => {
910                                if ui.try_get_of_type::<Text>(self.title).is_some() {
911                                    // Just modify existing text, this is much faster than
912                                    // re-create text everytime.
913                                    ui.send_message(TextMessage::text(
914                                        self.title,
915                                        MessageDirection::ToWidget,
916                                        text.clone(),
917                                    ));
918                                    if let Some(font) = font {
919                                        ui.send_message(TextMessage::font(
920                                            self.title,
921                                            MessageDirection::ToWidget,
922                                            font.clone(),
923                                        ))
924                                    }
925                                    if let Some(font_size) = font_size {
926                                        ui.send_message(TextMessage::font_size(
927                                            self.title,
928                                            MessageDirection::ToWidget,
929                                            font_size.clone(),
930                                        ));
931                                    }
932                                } else {
933                                    ui.send_message(WidgetMessage::remove(
934                                        self.title,
935                                        MessageDirection::ToWidget,
936                                    ));
937                                    let font =
938                                        font.clone().unwrap_or_else(|| ui.default_font.clone());
939                                    let ctx = &mut ui.build_ctx();
940                                    self.title = make_text_title(
941                                        ctx,
942                                        text,
943                                        font,
944                                        font_size.clone().unwrap_or_else(|| {
945                                            ctx.style.property(Style::FONT_SIZE)
946                                        }),
947                                    );
948                                    ui.send_message(WidgetMessage::link(
949                                        self.title,
950                                        MessageDirection::ToWidget,
951                                        self.title_grid,
952                                    ));
953                                }
954                            }
955                            WindowTitle::Node(node) => {
956                                if self.title.is_some() {
957                                    // Remove old title.
958                                    ui.send_message(WidgetMessage::remove(
959                                        self.title,
960                                        MessageDirection::ToWidget,
961                                    ));
962                                }
963
964                                if node.is_some() {
965                                    self.title = *node;
966
967                                    // Attach new one.
968                                    ui.send_message(WidgetMessage::link(
969                                        self.title,
970                                        MessageDirection::ToWidget,
971                                        self.title_grid,
972                                    ));
973                                }
974                            }
975                        }
976                    }
977                    WindowMessage::SafeBorderSize(size) => {
978                        if &self.safe_border_size != size {
979                            self.safe_border_size = *size;
980                            ui.send_message(message.reverse());
981                        }
982                    }
983                }
984            }
985        }
986    }
987}
988
989impl Window {
990    /// Checks whether any resizing grip is active or not.
991    pub fn has_active_grip(&self) -> bool {
992        for grip in self.grips.borrow().iter() {
993            if grip.is_dragging {
994                return true;
995            }
996        }
997        false
998    }
999
1000    fn content_to_focus(&self) -> Handle<UiNode> {
1001        if self.content.is_some() {
1002            self.content
1003        } else {
1004            self.handle
1005        }
1006    }
1007
1008    fn is_docked(&self, ui: &UserInterface) -> bool {
1009        self.parent() != ui.root_canvas
1010    }
1011}
1012
1013/// Window builder creates [`Window`] instances and adds them to the user interface.
1014pub struct WindowBuilder {
1015    /// Base widget builder.
1016    pub widget_builder: WidgetBuilder,
1017    /// Content of the window.
1018    pub content: Handle<UiNode>,
1019    /// Optional title of the window.
1020    pub title: Option<WindowTitle>,
1021    /// Whether the window can be closed or not.
1022    pub can_close: bool,
1023    /// Whether the window can be minimized or not.
1024    pub can_minimize: bool,
1025    /// Whether the window can be maximized or not.
1026    pub can_maximize: bool,
1027    /// Whether the window should be created open or not.
1028    pub open: bool,
1029    /// Optional custom closing button, if not specified, then a default button will be created.
1030    pub close_button: Option<Handle<UiNode>>,
1031    /// Optional custom minimization button, if not specified, then a default button will be created.
1032    pub minimize_button: Option<Handle<UiNode>>,
1033    /// Optional custom maximization button, if not specified, then a default button will be created.
1034    pub maximize_button: Option<Handle<UiNode>>,
1035    /// Whether the window should be created as modal or not. Warning: Any dependant builders must
1036    /// take this into account!
1037    pub modal: bool,
1038    /// Whether the window should be resizable or not.
1039    pub can_resize: bool,
1040    /// Optional size of the border around the screen in which the window will be forced to stay.
1041    pub safe_border_size: Option<Vector2<f32>>,
1042    /// If `true`, then the window can be closed using `Esc` key. Default is `true`. Works only if
1043    /// `can_close` is also `true`.
1044    pub close_by_esc: bool,
1045    /// If `true`, then the window will be deleted after closing.
1046    pub remove_on_close: bool,
1047}
1048
1049/// Window title can be either text or node.
1050///
1051/// If `Text` is used, then builder will automatically create [`Text`] node with specified text,
1052/// but with default font.
1053///
1054/// If you need more flexibility (i.e. put a picture near text) then [`WindowTitle::Node`] option
1055/// is for you: it allows to put any UI node hierarchy you want to.
1056#[derive(Debug, Clone, PartialEq)]
1057pub enum WindowTitle {
1058    Text {
1059        /// Actual text of the title.
1060        text: String,
1061        /// Optional font, if [`None`], then the default font will be used.
1062        font: Option<FontResource>,
1063        /// Optional size of the text. Default is [`None`] (in this case default size defined by the
1064        /// current style will be used).
1065        font_size: Option<StyledProperty<f32>>,
1066    },
1067    Node(Handle<UiNode>),
1068}
1069
1070impl WindowTitle {
1071    /// A shortcut to create [`WindowTitle::Text`]
1072    pub fn text<P: AsRef<str>>(text: P) -> Self {
1073        WindowTitle::Text {
1074            text: text.as_ref().to_owned(),
1075            font: None,
1076            font_size: None,
1077        }
1078    }
1079
1080    /// A shortcut to create [`WindowTitle::Text`] with custom font.
1081    pub fn text_with_font<P: AsRef<str>>(text: P, font: FontResource) -> Self {
1082        WindowTitle::Text {
1083            text: text.as_ref().to_owned(),
1084            font: Some(font),
1085            font_size: None,
1086        }
1087    }
1088
1089    /// A shortcut to create [`WindowTitle::Text`] with custom font and size.
1090    pub fn text_with_font_size<P: AsRef<str>>(
1091        text: P,
1092        font: FontResource,
1093        size: StyledProperty<f32>,
1094    ) -> Self {
1095        WindowTitle::Text {
1096            text: text.as_ref().to_owned(),
1097            font: Some(font),
1098            font_size: Some(size),
1099        }
1100    }
1101
1102    /// A shortcut to create [`WindowTitle::Node`]
1103    pub fn node(node: Handle<UiNode>) -> Self {
1104        Self::Node(node)
1105    }
1106}
1107
1108fn make_text_title(
1109    ctx: &mut BuildContext,
1110    text: &str,
1111    font: FontResource,
1112    size: StyledProperty<f32>,
1113) -> Handle<UiNode> {
1114    TextBuilder::new(
1115        WidgetBuilder::new()
1116            .with_margin(Thickness::left(5.0))
1117            .on_row(0)
1118            .on_column(0),
1119    )
1120    .with_font_size(size)
1121    .with_font(font)
1122    .with_vertical_text_alignment(VerticalAlignment::Center)
1123    .with_horizontal_text_alignment(HorizontalAlignment::Left)
1124    .with_text(text)
1125    .build(ctx)
1126}
1127
1128enum HeaderButton {
1129    Close,
1130    Minimize,
1131    Maximize,
1132}
1133
1134fn make_mark(ctx: &mut BuildContext, button: HeaderButton) -> Handle<UiNode> {
1135    let size = 12.0;
1136
1137    VectorImageBuilder::new(
1138        WidgetBuilder::new()
1139            .with_horizontal_alignment(HorizontalAlignment::Center)
1140            .with_vertical_alignment(match button {
1141                HeaderButton::Close => VerticalAlignment::Center,
1142                HeaderButton::Minimize => VerticalAlignment::Bottom,
1143                HeaderButton::Maximize => VerticalAlignment::Center,
1144            })
1145            .with_width(size)
1146            .with_height(size)
1147            .with_foreground(ctx.style.property(Style::BRUSH_BRIGHT)),
1148    )
1149    .with_primitives(match button {
1150        HeaderButton::Close => {
1151            vec![
1152                Primitive::Line {
1153                    begin: Vector2::new(0.0, 0.0),
1154                    end: Vector2::new(size, size),
1155                    thickness: 1.0,
1156                },
1157                Primitive::Line {
1158                    begin: Vector2::new(size, 0.0),
1159                    end: Vector2::new(0.0, size),
1160                    thickness: 1.0,
1161                },
1162            ]
1163        }
1164        HeaderButton::Minimize => {
1165            let bottom_spacing = 3.0;
1166
1167            vec![Primitive::Line {
1168                begin: Vector2::new(0.0, size - bottom_spacing),
1169                end: Vector2::new(size, size - bottom_spacing),
1170                thickness: 1.0,
1171            }]
1172        }
1173        HeaderButton::Maximize => {
1174            let thickness = 1.25;
1175            let half_thickness = thickness * 0.5;
1176
1177            vec![
1178                Primitive::Line {
1179                    begin: Vector2::new(0.0, half_thickness),
1180                    end: Vector2::new(size, half_thickness),
1181                    thickness,
1182                },
1183                Primitive::Line {
1184                    begin: Vector2::new(size - half_thickness, 0.0),
1185                    end: Vector2::new(size - half_thickness, size),
1186                    thickness,
1187                },
1188                Primitive::Line {
1189                    begin: Vector2::new(size, size - half_thickness),
1190                    end: Vector2::new(0.0, size - half_thickness),
1191                    thickness,
1192                },
1193                Primitive::Line {
1194                    begin: Vector2::new(half_thickness, size),
1195                    end: Vector2::new(half_thickness, 0.0),
1196                    thickness,
1197                },
1198            ]
1199        }
1200    })
1201    .build(ctx)
1202}
1203
1204fn make_header_button(ctx: &mut BuildContext, button: HeaderButton) -> Handle<UiNode> {
1205    ButtonBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(2.0)))
1206        .with_back(
1207            DecoratorBuilder::new(
1208                BorderBuilder::new(WidgetBuilder::new())
1209                    .with_stroke_thickness(Thickness::uniform(0.0).into())
1210                    .with_pad_by_corner_radius(false)
1211                    .with_corner_radius(4.0f32.into()),
1212            )
1213            .with_normal_brush(Brush::Solid(Color::TRANSPARENT).into())
1214            .with_hover_brush(ctx.style.property(Style::BRUSH_LIGHT))
1215            .with_pressed_brush(ctx.style.property(Style::BRUSH_LIGHTEST))
1216            .build(ctx),
1217        )
1218        .with_content(make_mark(ctx, button))
1219        .build(ctx)
1220}
1221
1222impl WindowBuilder {
1223    /// Creates new window builder.
1224    pub fn new(widget_builder: WidgetBuilder) -> Self {
1225        Self {
1226            widget_builder,
1227            content: Handle::NONE,
1228            title: None,
1229            can_close: true,
1230            can_minimize: true,
1231            can_maximize: true,
1232            open: true,
1233            close_button: None,
1234            minimize_button: None,
1235            maximize_button: None,
1236            modal: false,
1237            can_resize: true,
1238            safe_border_size: Some(Vector2::new(25.0, 20.0)),
1239            close_by_esc: true,
1240            remove_on_close: false,
1241        }
1242    }
1243
1244    /// Sets a desired window content.
1245    pub fn with_content(mut self, content: Handle<UiNode>) -> Self {
1246        self.content = content;
1247        self
1248    }
1249
1250    /// Sets a desired window title.
1251    pub fn with_title(mut self, title: WindowTitle) -> Self {
1252        self.title = Some(title);
1253        self
1254    }
1255
1256    /// Sets a desired minimization button.
1257    pub fn with_minimize_button(mut self, button: Handle<UiNode>) -> Self {
1258        self.minimize_button = Some(button);
1259        self
1260    }
1261
1262    /// Sets a desired maximization button.
1263    pub fn with_maximize_button(mut self, button: Handle<UiNode>) -> Self {
1264        self.minimize_button = Some(button);
1265        self
1266    }
1267
1268    /// Sets a desired closing button.
1269    pub fn with_close_button(mut self, button: Handle<UiNode>) -> Self {
1270        self.close_button = Some(button);
1271        self
1272    }
1273
1274    /// Sets whether the window can be closed or not.
1275    pub fn can_close(mut self, can_close: bool) -> Self {
1276        self.can_close = can_close;
1277        self
1278    }
1279
1280    /// Sets whether the window can be minimized or not.
1281    pub fn can_minimize(mut self, can_minimize: bool) -> Self {
1282        self.can_minimize = can_minimize;
1283        self
1284    }
1285
1286    /// Sets whether the window can be maximized or not.
1287    pub fn can_maximize(mut self, can_minimize: bool) -> Self {
1288        self.can_maximize = can_minimize;
1289        self
1290    }
1291
1292    /// Sets whether the window should be open or not.
1293    pub fn open(mut self, open: bool) -> Self {
1294        self.open = open;
1295        self
1296    }
1297
1298    /// Sets whether the window should be modal or not.
1299    pub fn modal(mut self, modal: bool) -> Self {
1300        self.modal = modal;
1301        self
1302    }
1303
1304    /// Sets whether the window can be resized or not.
1305    pub fn can_resize(mut self, can_resize: bool) -> Self {
1306        self.can_resize = can_resize;
1307        self
1308    }
1309
1310    /// Sets a desired safe border size.
1311    pub fn with_safe_border_size(mut self, size: Option<Vector2<f32>>) -> Self {
1312        self.safe_border_size = size.map(|s| Vector2::new(s.x.abs(), s.y.abs()));
1313        self
1314    }
1315
1316    /// Defines, whether the window can be closed using `Esc` key or not. Works only if `can_close`
1317    /// is also `true`.
1318    pub fn with_close_by_esc(mut self, close: bool) -> Self {
1319        self.close_by_esc = close;
1320        self
1321    }
1322
1323    /// Defines, whether the window should be deleted after closing or not. Default is `false`.
1324    pub fn with_remove_on_close(mut self, close: bool) -> Self {
1325        self.remove_on_close = close;
1326        self
1327    }
1328
1329    /// Finishes window building and returns its instance.
1330    pub fn build_window(self, ctx: &mut BuildContext) -> Window {
1331        let minimize_button;
1332        let maximize_button;
1333        let close_button;
1334        let title;
1335        let title_grid;
1336        let header = BorderBuilder::new(
1337            WidgetBuilder::new()
1338                .with_horizontal_alignment(HorizontalAlignment::Stretch)
1339                .with_height(22.0)
1340                .with_background(ctx.style.property(Style::BRUSH_DARKER))
1341                .with_child({
1342                    title_grid = GridBuilder::new(
1343                        WidgetBuilder::new()
1344                            .with_child({
1345                                title = match self.title {
1346                                    None => Handle::NONE,
1347                                    Some(window_title) => match window_title {
1348                                        WindowTitle::Node(node) => node,
1349                                        WindowTitle::Text {
1350                                            text,
1351                                            font,
1352                                            font_size,
1353                                        } => make_text_title(
1354                                            ctx,
1355                                            &text,
1356                                            font.unwrap_or_else(|| ctx.default_font()),
1357                                            font_size.unwrap_or_else(|| {
1358                                                ctx.style.property(Style::FONT_SIZE)
1359                                            }),
1360                                        ),
1361                                    },
1362                                };
1363                                title
1364                            })
1365                            .with_child({
1366                                minimize_button = self.minimize_button.unwrap_or_else(|| {
1367                                    make_header_button(ctx, HeaderButton::Minimize)
1368                                });
1369                                ctx[minimize_button]
1370                                    .set_visibility(self.can_minimize)
1371                                    .set_width(20.0)
1372                                    .set_row(0)
1373                                    .set_column(1);
1374                                minimize_button
1375                            })
1376                            .with_child({
1377                                maximize_button = self.maximize_button.unwrap_or_else(|| {
1378                                    make_header_button(ctx, HeaderButton::Maximize)
1379                                });
1380                                ctx[maximize_button]
1381                                    .set_visibility(self.can_maximize)
1382                                    .set_width(20.0)
1383                                    .set_row(0)
1384                                    .set_column(2);
1385                                maximize_button
1386                            })
1387                            .with_child({
1388                                close_button = self.close_button.unwrap_or_else(|| {
1389                                    make_header_button(ctx, HeaderButton::Close)
1390                                });
1391                                ctx[close_button]
1392                                    .set_width(20.0)
1393                                    .set_visibility(self.can_close)
1394                                    .set_row(0)
1395                                    .set_column(3);
1396                                close_button
1397                            }),
1398                    )
1399                    .add_column(Column::stretch())
1400                    .add_column(Column::auto())
1401                    .add_column(Column::auto())
1402                    .add_column(Column::auto())
1403                    .add_row(Row::stretch())
1404                    .build(ctx);
1405                    title_grid
1406                })
1407                .on_row(0),
1408        )
1409        .with_pad_by_corner_radius(false)
1410        .with_corner_radius(4.0f32.into())
1411        .with_stroke_thickness(Thickness::uniform(0.0).into())
1412        .build(ctx);
1413
1414        let border = BorderBuilder::new(
1415            WidgetBuilder::new()
1416                .with_foreground(ctx.style.property(Style::BRUSH_DARKER))
1417                .with_child(
1418                    GridBuilder::new(
1419                        WidgetBuilder::new()
1420                            .with_child(
1421                                NavigationLayerBuilder::new(
1422                                    WidgetBuilder::new().on_row(1).with_child(self.content),
1423                                )
1424                                .build(ctx),
1425                            )
1426                            .with_child(header),
1427                    )
1428                    .add_column(Column::stretch())
1429                    .add_row(Row::auto())
1430                    .add_row(Row::stretch())
1431                    .build(ctx),
1432                ),
1433        )
1434        .with_pad_by_corner_radius(false)
1435        .with_corner_radius(4.0f32.into())
1436        .with_stroke_thickness(Thickness::uniform(1.0).into())
1437        .build(ctx);
1438
1439        Window {
1440            widget: self
1441                .widget_builder
1442                .with_visibility(self.open)
1443                .with_child(border)
1444                .build(ctx),
1445            mouse_click_pos: Vector2::default(),
1446            initial_position: Vector2::default(),
1447            initial_size: Default::default(),
1448            is_dragging: false,
1449            minimized: false,
1450            can_minimize: self.can_minimize,
1451            can_maximize: self.can_maximize,
1452            can_close: self.can_close,
1453            can_resize: self.can_resize,
1454            header,
1455            minimize_button,
1456            maximize_button,
1457            close_button,
1458            drag_delta: Default::default(),
1459            content: self.content,
1460            safe_border_size: self.safe_border_size,
1461            grips: RefCell::new([
1462                // Corners have priority
1463                Grip::new(GripKind::LeftTopCorner, CursorIcon::NwResize),
1464                Grip::new(GripKind::RightTopCorner, CursorIcon::NeResize),
1465                Grip::new(GripKind::RightBottomCorner, CursorIcon::SeResize),
1466                Grip::new(GripKind::LeftBottomCorner, CursorIcon::SwResize),
1467                Grip::new(GripKind::Left, CursorIcon::WResize),
1468                Grip::new(GripKind::Top, CursorIcon::NResize),
1469                Grip::new(GripKind::Right, CursorIcon::EResize),
1470                Grip::new(GripKind::Bottom, CursorIcon::SResize),
1471            ]),
1472            title,
1473            title_grid,
1474            prev_bounds: None,
1475            close_by_esc: self.close_by_esc,
1476            remove_on_close: self.remove_on_close,
1477        }
1478    }
1479
1480    /// Finishes window building and returns its handle.
1481    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
1482        let modal = self.modal;
1483        let open = self.open;
1484
1485        let node = self.build_window(ctx);
1486        let handle = ctx.add_node(UiNode::new(node));
1487
1488        if modal && open {
1489            ctx.push_picking_restriction(RestrictionEntry { handle, stop: true });
1490        }
1491
1492        handle
1493    }
1494}
1495
1496#[cfg(test)]
1497mod test {
1498    use crate::window::WindowBuilder;
1499    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
1500
1501    #[test]
1502    fn test_deletion() {
1503        test_widget_deletion(|ctx| WindowBuilder::new(WidgetBuilder::new()).build(ctx));
1504    }
1505}