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}