rg3d_ui/
window.rs

1use crate::{
2    border::BorderBuilder,
3    brush::{Brush, GradientPoint},
4    button::{ButtonBuilder, ButtonMessage},
5    core::{algebra::Vector2, color::Color, math::Rect, pool::Handle},
6    decorator::DecoratorBuilder,
7    define_constructor,
8    grid::{Column, GridBuilder, Row},
9    message::{CursorIcon, MessageDirection, UiMessage},
10    text::{Text, TextBuilder, TextMessage},
11    vector_image::{Primitive, VectorImageBuilder},
12    widget::{Widget, WidgetBuilder, WidgetMessage},
13    BuildContext, Control, HorizontalAlignment, NodeHandleMapping, RestrictionEntry, Thickness,
14    UiNode, UserInterface, VerticalAlignment, BRUSH_BRIGHT, BRUSH_LIGHT, BRUSH_LIGHTER,
15    BRUSH_LIGHTEST, COLOR_DARK, COLOR_DARKEST,
16};
17use std::{
18    any::{Any, TypeId},
19    cell::RefCell,
20    ops::{Deref, DerefMut},
21};
22
23#[derive(Debug, Clone, PartialEq)]
24pub enum WindowMessage {
25    /// Opens a window.
26    Open { center: bool },
27
28    /// Opens window in modal mode. Modal mode does **not** blocks current thread, instead
29    /// it just restricts mouse and keyboard events only to window so other content is not
30    /// clickable/type-able. Closing a window removes that restriction.
31    OpenModal { center: bool },
32
33    /// Closes a window.
34    Close,
35
36    /// Minimizes a window - it differs from classic minimization in window managers,
37    /// instead of putting window in system tray, it just collapses internal content panel.
38    Minimize(bool),
39
40    /// Whether or not window can be minimized by _ mark. false hides _ mark.
41    CanMinimize(bool),
42
43    /// Whether or not window can be closed by X mark. false hides X mark.
44    CanClose(bool),
45
46    /// Whether or not window can be resized by resize grips.
47    CanResize(bool),
48
49    /// Indicates that move has been started. You should never send this message by hand.
50    MoveStart,
51
52    /// Moves window to a new position in local coordinates.
53    Move(Vector2<f32>),
54
55    /// Indicated that move has ended. You should never send this message by hand.
56    MoveEnd,
57
58    /// Sets new window title.
59    Title(WindowTitle),
60}
61
62impl WindowMessage {
63    define_constructor!(WindowMessage:Open => fn open(center: bool), layout: false);
64    define_constructor!(WindowMessage:OpenModal => fn open_modal(center: bool), layout: false);
65    define_constructor!(WindowMessage:Close => fn close(), layout: false);
66    define_constructor!(WindowMessage:Minimize => fn minimize(bool), layout: false);
67    define_constructor!(WindowMessage:CanMinimize => fn can_minimize(bool), layout: false);
68    define_constructor!(WindowMessage:CanClose => fn can_close(bool), layout: false);
69    define_constructor!(WindowMessage:CanResize => fn can_resize(bool), layout: false);
70    define_constructor!(WindowMessage:MoveStart => fn move_start(), layout: false);
71    define_constructor!(WindowMessage:Move => fn move_to(Vector2<f32>), layout: false);
72    define_constructor!(WindowMessage:MoveEnd => fn move_end(), layout: false);
73    define_constructor!(WindowMessage:Title => fn title(WindowTitle), layout: false);
74}
75
76/// Represents a widget looking as window in Windows - with title, minimize and close buttons.
77/// It has scrollable region for content, content can be any desired node or even other window.
78/// Window can be dragged by its title.
79#[derive(Clone)]
80pub struct Window {
81    widget: Widget,
82    mouse_click_pos: Vector2<f32>,
83    initial_position: Vector2<f32>,
84    initial_size: Vector2<f32>,
85    is_dragging: bool,
86    minimized: bool,
87    can_minimize: bool,
88    can_close: bool,
89    can_resize: bool,
90    header: Handle<UiNode>,
91    minimize_button: Handle<UiNode>,
92    close_button: Handle<UiNode>,
93    drag_delta: Vector2<f32>,
94    content: Handle<UiNode>,
95    grips: RefCell<[Grip; 8]>,
96    title: Handle<UiNode>,
97    title_grid: Handle<UiNode>,
98}
99
100const GRIP_SIZE: f32 = 6.0;
101const CORNER_GRIP_SIZE: f32 = GRIP_SIZE * 2.0;
102
103#[derive(Copy, Clone, Debug)]
104enum GripKind {
105    LeftTopCorner = 0,
106    RightTopCorner = 1,
107    RightBottomCorner = 2,
108    LeftBottomCorner = 3,
109    Left = 4,
110    Top = 5,
111    Right = 6,
112    Bottom = 7,
113}
114
115#[derive(Clone)]
116struct Grip {
117    kind: GripKind,
118    bounds: Rect<f32>,
119    is_dragging: bool,
120    cursor: CursorIcon,
121}
122
123impl Grip {
124    fn new(kind: GripKind, cursor: CursorIcon) -> Self {
125        Self {
126            kind,
127            bounds: Default::default(),
128            is_dragging: false,
129            cursor,
130        }
131    }
132}
133
134crate::define_widget_deref!(Window);
135
136impl Control for Window {
137    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
138        if type_id == TypeId::of::<Self>() {
139            Some(self)
140        } else {
141            None
142        }
143    }
144
145    fn resolve(&mut self, node_map: &NodeHandleMapping) {
146        node_map.resolve(&mut self.header);
147        node_map.resolve(&mut self.minimize_button);
148        node_map.resolve(&mut self.close_button);
149        node_map.resolve(&mut self.title);
150        node_map.resolve(&mut self.title_grid);
151        node_map.resolve(&mut self.content);
152    }
153
154    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
155        let size = self.widget.arrange_override(ui, final_size);
156
157        let mut grips = self.grips.borrow_mut();
158
159        // Adjust grips.
160        grips[GripKind::Left as usize].bounds =
161            Rect::new(0.0, GRIP_SIZE, GRIP_SIZE, final_size.y - GRIP_SIZE * 2.0);
162        grips[GripKind::Top as usize].bounds =
163            Rect::new(GRIP_SIZE, 0.0, final_size.x - GRIP_SIZE * 2.0, GRIP_SIZE);
164        grips[GripKind::Right as usize].bounds = Rect::new(
165            final_size.x - GRIP_SIZE,
166            GRIP_SIZE,
167            GRIP_SIZE,
168            final_size.y - GRIP_SIZE * 2.0,
169        );
170        grips[GripKind::Bottom as usize].bounds = Rect::new(
171            GRIP_SIZE,
172            final_size.y - GRIP_SIZE,
173            final_size.x - GRIP_SIZE * 2.0,
174            GRIP_SIZE,
175        );
176
177        // Corners have different size to improve usability.
178        grips[GripKind::LeftTopCorner as usize].bounds =
179            Rect::new(0.0, 0.0, CORNER_GRIP_SIZE, CORNER_GRIP_SIZE);
180        grips[GripKind::RightTopCorner as usize].bounds = Rect::new(
181            final_size.x - GRIP_SIZE,
182            0.0,
183            CORNER_GRIP_SIZE,
184            CORNER_GRIP_SIZE,
185        );
186        grips[GripKind::RightBottomCorner as usize].bounds = Rect::new(
187            final_size.x - CORNER_GRIP_SIZE,
188            final_size.y - CORNER_GRIP_SIZE,
189            CORNER_GRIP_SIZE,
190            CORNER_GRIP_SIZE,
191        );
192        grips[GripKind::LeftBottomCorner as usize].bounds = Rect::new(
193            0.0,
194            final_size.y - CORNER_GRIP_SIZE,
195            CORNER_GRIP_SIZE,
196            CORNER_GRIP_SIZE,
197        );
198
199        size
200    }
201
202    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
203        self.widget.handle_routed_message(ui, message);
204
205        if let Some(msg) = message.data::<WidgetMessage>() {
206            // Grip interaction have higher priority than other actions.
207            if self.can_resize {
208                match msg {
209                    &WidgetMessage::MouseDown { pos, .. } => {
210                        ui.send_message(WidgetMessage::topmost(
211                            self.handle(),
212                            MessageDirection::ToWidget,
213                        ));
214
215                        // Check grips.
216                        for grip in self.grips.borrow_mut().iter_mut() {
217                            let offset = self.screen_position;
218                            let screen_bounds = grip.bounds.translate(offset);
219                            if screen_bounds.contains(pos) {
220                                grip.is_dragging = true;
221                                self.initial_position = self.actual_local_position();
222                                self.initial_size = self.actual_size();
223                                self.mouse_click_pos = pos;
224                                ui.capture_mouse(self.handle());
225                                break;
226                            }
227                        }
228                    }
229                    WidgetMessage::MouseUp { .. } => {
230                        for grip in self.grips.borrow_mut().iter_mut() {
231                            if grip.is_dragging {
232                                ui.release_mouse_capture();
233                                grip.is_dragging = false;
234                                break;
235                            }
236                        }
237                    }
238                    &WidgetMessage::MouseMove { pos, .. } => {
239                        let mut new_cursor = None;
240
241                        for grip in self.grips.borrow().iter() {
242                            let offset = self.screen_position;
243                            let screen_bounds = grip.bounds.translate(offset);
244                            if screen_bounds.contains(pos) {
245                                new_cursor = Some(grip.cursor);
246                            }
247
248                            if grip.is_dragging {
249                                let delta = self.mouse_click_pos - pos;
250                                let (dx, dy, dw, dh) = match grip.kind {
251                                    GripKind::Left => (-1.0, 0.0, 1.0, 0.0),
252                                    GripKind::Top => (0.0, -1.0, 0.0, 1.0),
253                                    GripKind::Right => (0.0, 0.0, -1.0, 0.0),
254                                    GripKind::Bottom => (0.0, 0.0, 0.0, -1.0),
255                                    GripKind::LeftTopCorner => (-1.0, -1.0, 1.0, 1.0),
256                                    GripKind::RightTopCorner => (0.0, -1.0, -1.0, 1.0),
257                                    GripKind::RightBottomCorner => (0.0, 0.0, -1.0, -1.0),
258                                    GripKind::LeftBottomCorner => (-1.0, 0.0, 1.0, -1.0),
259                                };
260
261                                let new_pos = self.initial_position
262                                    + Vector2::new(delta.x * dx, delta.y * dy);
263                                let new_size =
264                                    self.initial_size + Vector2::new(delta.x * dw, delta.y * dh);
265
266                                if new_size.x > self.min_width()
267                                    && new_size.x < self.max_width()
268                                    && new_size.y > self.min_height()
269                                    && new_size.y < self.max_height()
270                                {
271                                    ui.send_message(WidgetMessage::desired_position(
272                                        self.handle(),
273                                        MessageDirection::ToWidget,
274                                        new_pos,
275                                    ));
276                                    ui.send_message(WidgetMessage::width(
277                                        self.handle(),
278                                        MessageDirection::ToWidget,
279                                        new_size.x,
280                                    ));
281                                    ui.send_message(WidgetMessage::height(
282                                        self.handle(),
283                                        MessageDirection::ToWidget,
284                                        new_size.y,
285                                    ));
286                                }
287
288                                break;
289                            }
290                        }
291
292                        self.set_cursor(new_cursor);
293                    }
294                    _ => {}
295                }
296            }
297
298            if (message.destination() == self.header
299                || ui
300                    .node(self.header)
301                    .has_descendant(message.destination(), ui))
302                && !message.handled()
303                && !self.has_active_grip()
304            {
305                match msg {
306                    WidgetMessage::MouseDown { pos, .. } => {
307                        self.mouse_click_pos = *pos;
308                        ui.send_message(WindowMessage::move_start(
309                            self.handle,
310                            MessageDirection::ToWidget,
311                        ));
312                        message.set_handled(true);
313                    }
314                    WidgetMessage::MouseUp { .. } => {
315                        ui.send_message(WindowMessage::move_end(
316                            self.handle,
317                            MessageDirection::ToWidget,
318                        ));
319                        message.set_handled(true);
320                    }
321                    WidgetMessage::MouseMove { pos, .. } => {
322                        if self.is_dragging {
323                            self.drag_delta = *pos - self.mouse_click_pos;
324                            let new_pos = self.initial_position + self.drag_delta;
325                            ui.send_message(WindowMessage::move_to(
326                                self.handle(),
327                                MessageDirection::ToWidget,
328                                new_pos,
329                            ));
330                        }
331                        message.set_handled(true);
332                    }
333                    _ => (),
334                }
335            }
336            if let WidgetMessage::Unlink = msg {
337                if message.destination() == self.handle() {
338                    self.initial_position = self.screen_position;
339                }
340            }
341        } else if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
342            if message.destination() == self.minimize_button {
343                ui.send_message(WindowMessage::minimize(
344                    self.handle(),
345                    MessageDirection::ToWidget,
346                    !self.minimized,
347                ));
348            } else if message.destination() == self.close_button {
349                ui.send_message(WindowMessage::close(
350                    self.handle(),
351                    MessageDirection::ToWidget,
352                ));
353            }
354        } else if let Some(msg) = message.data::<WindowMessage>() {
355            if message.destination() == self.handle()
356                && message.direction() == MessageDirection::ToWidget
357            {
358                match msg {
359                    &WindowMessage::Open { center } => {
360                        if !self.visibility() {
361                            ui.send_message(WidgetMessage::visibility(
362                                self.handle(),
363                                MessageDirection::ToWidget,
364                                true,
365                            ));
366                            ui.send_message(WidgetMessage::topmost(
367                                self.handle(),
368                                MessageDirection::ToWidget,
369                            ));
370                            if center {
371                                ui.send_message(WidgetMessage::center(
372                                    self.handle(),
373                                    MessageDirection::ToWidget,
374                                ));
375                            }
376                        }
377                    }
378                    &WindowMessage::OpenModal { center } => {
379                        if !self.visibility() {
380                            ui.send_message(WidgetMessage::visibility(
381                                self.handle(),
382                                MessageDirection::ToWidget,
383                                true,
384                            ));
385                            ui.send_message(WidgetMessage::topmost(
386                                self.handle(),
387                                MessageDirection::ToWidget,
388                            ));
389                            if center {
390                                ui.send_message(WidgetMessage::center(
391                                    self.handle(),
392                                    MessageDirection::ToWidget,
393                                ));
394                            }
395                            ui.push_picking_restriction(RestrictionEntry {
396                                handle: self.handle(),
397                                stop: true,
398                            });
399                        }
400                    }
401                    WindowMessage::Close => {
402                        if self.visibility() {
403                            ui.send_message(WidgetMessage::visibility(
404                                self.handle(),
405                                MessageDirection::ToWidget,
406                                false,
407                            ));
408                            ui.remove_picking_restriction(self.handle());
409                        }
410                    }
411                    &WindowMessage::Minimize(minimized) => {
412                        if self.minimized != minimized {
413                            self.minimized = minimized;
414                            if self.content.is_some() {
415                                ui.send_message(WidgetMessage::visibility(
416                                    self.content,
417                                    MessageDirection::ToWidget,
418                                    !minimized,
419                                ));
420                            }
421                        }
422                    }
423                    &WindowMessage::CanMinimize(value) => {
424                        if self.can_minimize != value {
425                            self.can_minimize = value;
426                            if self.minimize_button.is_some() {
427                                ui.send_message(WidgetMessage::visibility(
428                                    self.minimize_button,
429                                    MessageDirection::ToWidget,
430                                    value,
431                                ));
432                            }
433                        }
434                    }
435                    &WindowMessage::CanClose(value) => {
436                        if self.can_close != value {
437                            self.can_close = value;
438                            if self.close_button.is_some() {
439                                ui.send_message(WidgetMessage::visibility(
440                                    self.close_button,
441                                    MessageDirection::ToWidget,
442                                    value,
443                                ));
444                            }
445                        }
446                    }
447                    &WindowMessage::CanResize(value) => {
448                        if self.can_resize != value {
449                            self.can_resize = value;
450                            ui.send_message(message.reverse());
451                        }
452                    }
453                    &WindowMessage::Move(new_pos) => {
454                        if self.desired_local_position() != new_pos {
455                            ui.send_message(WidgetMessage::desired_position(
456                                self.handle(),
457                                MessageDirection::ToWidget,
458                                new_pos,
459                            ));
460
461                            ui.send_message(message.reverse());
462                        }
463                    }
464                    WindowMessage::MoveStart => {
465                        if !self.is_dragging {
466                            ui.capture_mouse(self.header);
467                            let initial_position = self.actual_local_position();
468                            self.initial_position = initial_position;
469                            self.is_dragging = true;
470
471                            ui.send_message(message.reverse());
472                        }
473                    }
474                    WindowMessage::MoveEnd => {
475                        if self.is_dragging {
476                            ui.release_mouse_capture();
477                            self.is_dragging = false;
478
479                            ui.send_message(message.reverse());
480                        }
481                    }
482                    WindowMessage::Title(title) => {
483                        match title {
484                            WindowTitle::Text(text) => {
485                                if ui.node(self.title).cast::<Text>().is_some() {
486                                    // Just modify existing text, this is much faster than
487                                    // re-create text everytime.
488                                    ui.send_message(TextMessage::text(
489                                        self.title,
490                                        MessageDirection::ToWidget,
491                                        text.clone(),
492                                    ));
493                                } else {
494                                    ui.send_message(WidgetMessage::remove(
495                                        self.title,
496                                        MessageDirection::ToWidget,
497                                    ));
498                                    self.title = make_text_title(&mut ui.build_ctx(), text);
499                                }
500                            }
501                            WindowTitle::Node(node) => {
502                                if self.title.is_some() {
503                                    // Remove old title.
504                                    ui.send_message(WidgetMessage::remove(
505                                        self.title,
506                                        MessageDirection::ToWidget,
507                                    ));
508                                }
509
510                                if node.is_some() {
511                                    self.title = *node;
512
513                                    // Attach new one.
514                                    ui.send_message(WidgetMessage::link(
515                                        self.title,
516                                        MessageDirection::ToWidget,
517                                        self.title_grid,
518                                    ));
519                                }
520                            }
521                        }
522                    }
523                }
524            }
525        }
526    }
527}
528
529impl Window {
530    pub fn is_dragging(&self) -> bool {
531        self.is_dragging
532    }
533
534    pub fn drag_delta(&self) -> Vector2<f32> {
535        self.drag_delta
536    }
537
538    pub fn has_active_grip(&self) -> bool {
539        for grip in self.grips.borrow().iter() {
540            if grip.is_dragging {
541                return true;
542            }
543        }
544        false
545    }
546
547    pub fn set_can_resize(&mut self, value: bool) {
548        self.can_resize = value;
549    }
550
551    pub fn can_resize(&self) -> bool {
552        self.can_resize
553    }
554}
555
556pub struct WindowBuilder {
557    pub widget_builder: WidgetBuilder,
558    pub content: Handle<UiNode>,
559    pub title: Option<WindowTitle>,
560    pub can_close: bool,
561    pub can_minimize: bool,
562    pub open: bool,
563    pub close_button: Option<Handle<UiNode>>,
564    pub minimize_button: Option<Handle<UiNode>>,
565    // Warning: Any dependant builders must take this into account!
566    pub modal: bool,
567    pub can_resize: bool,
568}
569
570/// Window title can be either text or node.
571///
572/// If `Text` is used, then builder will automatically create Text node with specified text,
573/// but with default font.
574///
575/// If you need more flexibility (i.e. put a picture near text) then `Node` option is for you:
576/// it allows to put any UI node hierarchy you want to.
577#[derive(Debug, Clone, PartialEq)]
578pub enum WindowTitle {
579    Text(String),
580    Node(Handle<UiNode>),
581}
582
583impl WindowTitle {
584    pub fn text<P: AsRef<str>>(text: P) -> Self {
585        WindowTitle::Text(text.as_ref().to_owned())
586    }
587
588    pub fn node(node: Handle<UiNode>) -> Self {
589        Self::Node(node)
590    }
591}
592
593fn make_text_title(ctx: &mut BuildContext, text: &str) -> Handle<UiNode> {
594    TextBuilder::new(
595        WidgetBuilder::new()
596            .with_margin(Thickness::uniform(5.0))
597            .on_row(0)
598            .on_column(0),
599    )
600    .with_text(text)
601    .build(ctx)
602}
603
604enum HeaderButton {
605    Close,
606    Minimize,
607}
608
609fn make_mark(ctx: &mut BuildContext, button: HeaderButton) -> Handle<UiNode> {
610    VectorImageBuilder::new(
611        WidgetBuilder::new()
612            .with_horizontal_alignment(HorizontalAlignment::Center)
613            .with_vertical_alignment(match button {
614                HeaderButton::Close => VerticalAlignment::Center,
615                HeaderButton::Minimize => VerticalAlignment::Bottom,
616            })
617            .with_margin(match button {
618                HeaderButton::Close => Thickness::uniform(0.0),
619                HeaderButton::Minimize => Thickness::bottom(3.0),
620            })
621            .with_foreground(BRUSH_BRIGHT),
622    )
623    .with_primitives(match button {
624        HeaderButton::Close => {
625            vec![
626                Primitive::Line {
627                    begin: Vector2::new(0.0, 0.0),
628                    end: Vector2::new(12.0, 12.0),
629                    thickness: 3.0,
630                },
631                Primitive::Line {
632                    begin: Vector2::new(12.0, 0.0),
633                    end: Vector2::new(0.0, 12.0),
634                    thickness: 3.0,
635                },
636            ]
637        }
638        HeaderButton::Minimize => {
639            vec![Primitive::Line {
640                begin: Vector2::new(0.0, 0.0),
641                end: Vector2::new(12.0, 0.0),
642                thickness: 3.0,
643            }]
644        }
645    })
646    .build(ctx)
647}
648
649fn make_header_button(ctx: &mut BuildContext, button: HeaderButton) -> Handle<UiNode> {
650    ButtonBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(2.0)))
651        .with_back(
652            DecoratorBuilder::new(
653                BorderBuilder::new(WidgetBuilder::new())
654                    .with_stroke_thickness(Thickness::uniform(0.0)),
655            )
656            .with_normal_brush(Brush::Solid(Color::TRANSPARENT))
657            .with_hover_brush(BRUSH_LIGHT)
658            .with_pressed_brush(BRUSH_LIGHTEST)
659            .build(ctx),
660        )
661        .with_content(make_mark(ctx, button))
662        .build(ctx)
663}
664
665impl<'a> WindowBuilder {
666    pub fn new(widget_builder: WidgetBuilder) -> Self {
667        Self {
668            widget_builder,
669            content: Handle::NONE,
670            title: None,
671            can_close: true,
672            can_minimize: true,
673            open: true,
674            close_button: None,
675            minimize_button: None,
676            modal: false,
677            can_resize: true,
678        }
679    }
680
681    pub fn with_content(mut self, content: Handle<UiNode>) -> Self {
682        self.content = content;
683        self
684    }
685
686    pub fn with_title(mut self, title: WindowTitle) -> Self {
687        self.title = Some(title);
688        self
689    }
690
691    pub fn with_minimize_button(mut self, button: Handle<UiNode>) -> Self {
692        self.minimize_button = Some(button);
693        self
694    }
695
696    pub fn with_close_button(mut self, button: Handle<UiNode>) -> Self {
697        self.close_button = Some(button);
698        self
699    }
700
701    pub fn can_close(mut self, can_close: bool) -> Self {
702        self.can_close = can_close;
703        self
704    }
705
706    pub fn can_minimize(mut self, can_minimize: bool) -> Self {
707        self.can_minimize = can_minimize;
708        self
709    }
710
711    pub fn open(mut self, open: bool) -> Self {
712        self.open = open;
713        self
714    }
715
716    pub fn modal(mut self, modal: bool) -> Self {
717        self.modal = modal;
718        self
719    }
720
721    pub fn can_resize(mut self, can_resize: bool) -> Self {
722        self.can_resize = can_resize;
723        self
724    }
725
726    pub fn build_window(self, ctx: &mut BuildContext) -> Window {
727        let minimize_button;
728        let close_button;
729
730        let title;
731        let title_grid;
732        let header = BorderBuilder::new(
733            WidgetBuilder::new()
734                .with_horizontal_alignment(HorizontalAlignment::Stretch)
735                .with_height(30.0)
736                .with_background(Brush::LinearGradient {
737                    from: Vector2::new(0.5, 0.0),
738                    to: Vector2::new(0.5, 1.0),
739                    stops: vec![
740                        GradientPoint {
741                            stop: 0.0,
742                            color: COLOR_DARK,
743                        },
744                        GradientPoint {
745                            stop: 0.85,
746                            color: COLOR_DARK,
747                        },
748                        GradientPoint {
749                            stop: 1.0,
750                            color: COLOR_DARKEST,
751                        },
752                    ],
753                })
754                .with_child({
755                    title_grid = GridBuilder::new(
756                        WidgetBuilder::new()
757                            .with_child({
758                                title = match self.title {
759                                    None => Handle::NONE,
760                                    Some(window_title) => match window_title {
761                                        WindowTitle::Node(node) => node,
762                                        WindowTitle::Text(text) => make_text_title(ctx, &text),
763                                    },
764                                };
765                                title
766                            })
767                            .with_child({
768                                minimize_button = self.minimize_button.unwrap_or_else(|| {
769                                    make_header_button(ctx, HeaderButton::Minimize)
770                                });
771                                ctx[minimize_button]
772                                    .set_visibility(self.can_minimize)
773                                    .set_width(30.0)
774                                    .set_row(0)
775                                    .set_column(1);
776                                minimize_button
777                            })
778                            .with_child({
779                                close_button = self.close_button.unwrap_or_else(|| {
780                                    make_header_button(ctx, HeaderButton::Close)
781                                });
782                                ctx[close_button]
783                                    .set_width(30.0)
784                                    .set_visibility(self.can_close)
785                                    .set_row(0)
786                                    .set_column(2);
787                                close_button
788                            }),
789                    )
790                    .add_column(Column::stretch())
791                    .add_column(Column::auto())
792                    .add_column(Column::auto())
793                    .add_row(Row::stretch())
794                    .build(ctx);
795                    title_grid
796                })
797                .on_row(0),
798        )
799        .build(ctx);
800
801        if self.content.is_some() {
802            ctx[self.content].set_row(1);
803        }
804        Window {
805            widget: self
806                .widget_builder
807                .with_visibility(self.open)
808                .with_child(
809                    BorderBuilder::new(
810                        WidgetBuilder::new()
811                            .with_foreground(BRUSH_LIGHTER)
812                            .with_child(
813                                GridBuilder::new(
814                                    WidgetBuilder::new()
815                                        .with_child(self.content)
816                                        .with_child(header),
817                                )
818                                .add_column(Column::stretch())
819                                .add_row(Row::auto())
820                                .add_row(Row::stretch())
821                                .build(ctx),
822                            ),
823                    )
824                    .with_stroke_thickness(Thickness::uniform(1.0))
825                    .build(ctx),
826                )
827                .build(),
828            mouse_click_pos: Vector2::default(),
829            initial_position: Vector2::default(),
830            initial_size: Default::default(),
831            is_dragging: false,
832            minimized: false,
833            can_minimize: self.can_minimize,
834            can_close: self.can_close,
835            can_resize: self.can_resize,
836            header,
837            minimize_button,
838            close_button,
839            drag_delta: Default::default(),
840            content: self.content,
841            grips: RefCell::new([
842                // Corners have priority
843                Grip::new(GripKind::LeftTopCorner, CursorIcon::NwResize),
844                Grip::new(GripKind::RightTopCorner, CursorIcon::NeResize),
845                Grip::new(GripKind::RightBottomCorner, CursorIcon::SeResize),
846                Grip::new(GripKind::LeftBottomCorner, CursorIcon::SwResize),
847                Grip::new(GripKind::Left, CursorIcon::WResize),
848                Grip::new(GripKind::Top, CursorIcon::NResize),
849                Grip::new(GripKind::Right, CursorIcon::EResize),
850                Grip::new(GripKind::Bottom, CursorIcon::SResize),
851            ]),
852            title,
853            title_grid,
854        }
855    }
856
857    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
858        let modal = self.modal;
859        let open = self.open;
860
861        let node = self.build_window(ctx);
862        let handle = ctx.add_node(UiNode::new(node));
863
864        if modal && open {
865            ctx.ui
866                .push_picking_restriction(RestrictionEntry { handle, stop: true });
867        }
868
869        handle
870    }
871}