thyme/
window.rs

1use crate::{Frame, widget::WidgetBuilder, WidgetState, Point};
2
3/**
4A [`WidgetBuilder`](struct.WidgetBuilder.html) specifically for creating windows.
5
6Windows can have a titlebar, close button, move, and resizing capabilities.  Each window
7is automatically part of its own [`render group`](struct.WidgetBuilder.html#method.new_render_group)
8and will automatically come on top of other widgets when clicked on.  You can create a `WindowBuilder`
9from a [`WidgetBuilder`](struct.WidgetBuilder.html) by calling [`window`](struct.WidgetBuilder.html#method.window)
10after any calls to general purpose widget layout.
11
12There is also a [`window method on Frame`](struct.Frame.html#method.window) as a convenience for simple cases.
13
14Once you are finished setting up the window, you call [`children`](#method.children) to add children and add the widget
15to the frame.
16
17# Example
18```
19fn create_window(ui: &mut Frame, unique_id: &str) {
20    ui.start("window")
21    .window(unique_id)
22    .title("My Window")
23    .resizable(false)
24    .children(|ui| {
25        // window content here
26    });
27}
28```
29
30# Theme definition
31An example of a theme definition for a window:
32
33```yaml
34  window:
35    background: gui/window_bg
36    wants_mouse: true
37    layout: Vertical
38    layout_spacing: [5, 5]
39    border: { left: 5, right: 5, top: 35, bot: 5 }
40    size: [300, 400]
41    child_align: Top
42    children:
43      titlebar:
44        wants_mouse: true
45        background: gui/small_button
46        size: [10, 30]
47        pos: [-6, -36]
48        border: { all: 5 }
49        width_from: Parent
50        child_align: Center
51        align: TopLeft
52        children:
53          title:
54            from: label
55            text: "Main Window"
56            font: medium
57            width_from: Parent
58          close:
59            wants_mouse: true
60            background: gui/small_button
61            foreground: gui/close_icon
62            size: [20, 20]
63            border: { all: 4 }
64            align: TopRight
65      handle:
66        wants_mouse: true
67        background: gui/window_handle
68        size: [12, 12]
69        align: BotRight
70        pos: [-1, -1]
71```
72*/
73pub struct WindowBuilder<'a> {
74    builder: WidgetBuilder<'a>,
75    state: WindowState,
76}
77
78impl<'a> WindowBuilder<'a> {
79    pub(crate) fn new(builder: WidgetBuilder<'a>) -> WindowBuilder<'a> {
80        WindowBuilder {
81            builder,
82            state: WindowState::default(),
83        }
84    }
85
86    /// Specifies that this window will not use a new render group.  This can
87    /// be useful in some cases where you want to handle grouping yourself.
88    /// See [`WidgetBuilder.new_render_group`](struct.WidgetBuilder.html#method.new_render_group)
89    #[must_use]
90    pub fn cancel_render_group(mut self) -> WindowBuilder<'a> {
91        self.builder.set_next_render_group(None);
92        self
93    }
94
95    /// Specifies whether the created window should show a titlebar.
96    #[must_use]
97    pub fn with_titlebar(mut self, with_titlebar: bool) -> WindowBuilder<'a> {
98        self.state.with_titlebar = with_titlebar;
99        self
100    }
101
102    /// Specify a title to show in the window's titlebar, if it is present.  If the
103    /// titlebar is not present, does nothing.  This will override any text set in the theme.
104    #[must_use]
105    pub fn title<T: Into<String>>(mut self, title: T) -> WindowBuilder<'a> {
106        self.state.title = Some(title.into());
107        self
108    }
109
110    /// Specifies whether the created window should have a close button.
111    #[must_use]
112    pub fn with_close_button(mut self, with_close_button: bool) -> WindowBuilder<'a> {
113        self.state.with_close_button = with_close_button;
114        self
115    }
116
117    /// Specifies whether the user should be able to move the created window
118    /// by dragging the mouse.  Note that if the [`titlebar`](#method.with_titlebar) is not shown, there
119    /// will be no way to move the window regardless of this setting.
120    #[must_use]
121    pub fn moveable(mut self, moveable: bool) -> WindowBuilder<'a> {
122        self.state.moveable = moveable;
123        self
124    }
125
126    /// Specifies whether the user should be able to resize the created window.
127    /// If false, the resize handle will not be shown.
128    #[must_use]
129    pub fn resizable(mut self, resizable: bool) -> WindowBuilder<'a> {
130        self.state.resizable = resizable;
131        self
132    }
133
134    /// Consumes the builder and adds a widget to the current frame.  The
135    /// returned data includes information about the animation state and
136    /// mouse interactions of the created element.
137    /// The provided closure is called to enable adding children to this window.
138    pub fn children<F: FnOnce(&mut Frame)>(self, children: F) -> WidgetState {
139        let builder = self.builder;
140        let state = self.state;
141        let id = builder.widget.id().to_string();
142
143        builder.children(|ui| {
144            (children)(ui);
145
146            let drag_move = if state.with_titlebar {
147                let result = ui.start("titlebar")
148                .children(|ui| {
149                    if let Some(title) = state.title.as_ref() {
150                        ui.start("title").text(title).finish();
151                    } else {
152                        ui.start("title").finish();
153                    }
154                    
155                    if state.with_close_button {
156                        let clicked = ui.child("close").clicked;
157
158                        if clicked {
159                            ui.close(&id);
160                        }
161                    }
162                });
163
164                if state.moveable && result.pressed {
165                    result.moved
166                } else {
167                    Point::default()
168                }
169            } else {
170                Point::default()
171            };
172
173            if drag_move != Point::default() {
174                ui.modify(&id, |state| {
175                    state.moved = state.moved + drag_move;
176                });
177            }
178
179            if state.resizable {
180                let result = ui.button("handle", "");
181                if result.pressed {
182                    ui.modify(&id, |state| {
183                        state.resize = state.resize + result.moved;
184                    });
185                }
186            }
187        })
188    }
189}
190
191struct WindowState {
192    with_titlebar: bool,
193    with_close_button: bool,
194    moveable: bool,
195    resizable: bool,
196    title: Option<String>,
197}
198
199impl Default for WindowState {
200    fn default() -> Self {
201        Self {
202            with_titlebar: true,
203            with_close_button: true,
204            moveable: true,
205            resizable: true,
206            title: None,
207        }
208    }
209}