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