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}