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}