Skip to main content

microui_redux/
window.rs

1//
2// Copyright 2022-Present (c) Raja Lehtihet & Wael El Oraiby
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are met:
6//
7// 1. Redistributions of source code must retain the above copyright notice,
8// this list of conditions and the following disclaimer.
9//
10// 2. Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13//
14// 3. Neither the name of the copyright holder nor the names of its contributors
15// may be used to endorse or promote products derived from this software without
16// specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28// POSSIBILITY OF SUCH DAMAGE.
29//
30// -----------------------------------------------------------------------------
31// Ported to rust from https://github.com/rxi/microui/ and the original license
32//
33// Copyright (c) 2020 rxi
34//
35// Permission is hereby granted, free of charge, to any person obtaining a copy
36// of this software and associated documentation files (the "Software"), to
37// deal in the Software without restriction, including without limitation the
38// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
39// sell copies of the Software, and to permit persons to whom the Software is
40// furnished to do so, subject to the following conditions:
41//
42// The above copyright notice and this permission notice shall be included in
43// all copies or substantial portions of the Software.
44//
45// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
48// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
50// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
51// IN THE SOFTWARE.
52//
53use super::*;
54use std::cell::{Ref, RefMut};
55
56#[derive(Clone, Copy, Debug)]
57/// Indicates whether a window should be rendered this frame.
58pub enum WindowState {
59    /// Window is visible and will receive input.
60    Open,
61    /// Window is hidden.
62    Closed,
63}
64
65#[derive(Clone, Copy, Debug)]
66pub(crate) enum Type {
67    Dialog,
68    Window,
69    Popup,
70}
71
72pub(crate) struct Window {
73    pub(crate) ty: Type,
74    pub(crate) win_state: WindowState,
75    pub(crate) main: Container,
76    /// Internal state for the window title bar.
77    pub(crate) title_state: Internal,
78    /// Internal state for the window close button.
79    pub(crate) close_state: Internal,
80    /// Internal state for the window resize handle.
81    pub(crate) resize_state: Internal,
82}
83
84impl Window {
85    /// Creates a dialog window that starts closed.
86    pub fn dialog(name: &str, atlas: AtlasHandle, style: Rc<Style>, input: Rc<RefCell<Input>>, initial_rect: Recti) -> Self {
87        let mut main = Container::new(name, atlas, style, input);
88        main.rect = initial_rect;
89
90        Self {
91            ty: Type::Dialog,
92            win_state: WindowState::Closed,
93            main,
94            title_state: Internal::new("!title"),
95            close_state: Internal::new("!close"),
96            resize_state: Internal::new("!resize"),
97        }
98    }
99
100    /// Creates a standard window that starts open.
101    pub fn window(name: &str, atlas: AtlasHandle, style: Rc<Style>, input: Rc<RefCell<Input>>, initial_rect: Recti) -> Self {
102        let mut main = Container::new(name, atlas, style, input);
103        main.rect = initial_rect;
104
105        Self {
106            ty: Type::Window,
107            win_state: WindowState::Open,
108            main,
109            title_state: Internal::new("!title"),
110            close_state: Internal::new("!close"),
111            resize_state: Internal::new("!resize"),
112        }
113    }
114
115    /// Creates a popup window that starts closed.
116    pub fn popup(name: &str, atlas: AtlasHandle, style: Rc<Style>, input: Rc<RefCell<Input>>, initial_rect: Recti) -> Self {
117        let mut main = Container::new(name, atlas, style, input);
118        main.rect = initial_rect;
119
120        Self {
121            ty: Type::Popup,
122            win_state: WindowState::Closed,
123            main,
124            title_state: Internal::new("!title"),
125            close_state: Internal::new("!close"),
126            resize_state: Internal::new("!resize"),
127        }
128    }
129
130    /// Returns `true` if this handle manages a popup window.
131    pub fn is_popup(&self) -> bool {
132        match self.ty {
133            Type::Popup => true,
134            _ => false,
135        }
136    }
137
138    #[inline(never)]
139    fn begin_window(&mut self, opt: ContainerOption, bopt: WidgetBehaviourOption) {
140        let is_popup = self.is_popup();
141        let Window {
142            win_state,
143            main: container,
144            title_state,
145            close_state,
146            resize_state,
147            ..
148        } = self;
149        let mut body = container.rect;
150        let r = body;
151        if !opt.has_no_frame() {
152            container.draw_frame(r, ControlColor::WindowBG);
153        }
154        if !opt.has_no_title() {
155            let mut tr: Recti = r;
156            // Guard against a too-small title bar: enforce enough room for the font plus padding so
157            // the title text/close button stay visible even if the style is set to zero.
158            let (font, padding, title_height, title_text_color) = {
159                let style = container.style.as_ref();
160                (
161                    style.font,
162                    style.padding.max(0),
163                    style.title_height,
164                    style.colors[ControlColor::TitleText as usize],
165                )
166            };
167            let font_height = container.atlas.get_font_height(font) as i32;
168            let min_title_h = font_height + (padding / 2).max(1) * 2;
169            tr.height = title_height.max(min_title_h);
170            container.draw_frame(tr, ControlColor::TitleBG);
171
172            // TODO: Is this necessary?
173            if !opt.has_no_title() {
174                let title_id = widget_id_of(title_state);
175                let control_state = (title_state.opt, title_state.bopt);
176                let control = container.update_control(title_id, tr, &control_state);
177                {
178                    let mut ctx = container.widget_ctx(title_id, tr, None);
179                    let _ = title_state.handle(&mut ctx, &control);
180                }
181                let name = container.name.clone(); // Necessary due to borrow checker limitations
182                container.draw_control_text(&name, tr, ControlColor::TitleText, WidgetOption::NONE);
183                if control.active {
184                    container.rect.x += container.input.borrow().mouse_delta.x;
185                    container.rect.y += container.input.borrow().mouse_delta.y;
186                }
187                body.y += tr.height;
188                body.height -= tr.height;
189            }
190            if !opt.has_no_close() {
191                let close_id = widget_id_of(close_state);
192                let r: Recti = rect(tr.x + tr.width - tr.height, tr.y, tr.height, tr.height);
193                tr.width -= r.width;
194                let color = title_text_color;
195                container.draw_icon(CLOSE_ICON, r, color);
196                let control_state = (close_state.opt, close_state.bopt);
197                let control = container.update_control(close_id, r, &control_state);
198                {
199                    let mut ctx = container.widget_ctx(close_id, r, None);
200                    let _ = close_state.handle(&mut ctx, &control);
201                }
202                if control.clicked {
203                    *win_state = WindowState::Closed;
204                }
205            }
206        }
207        container.push_container_body(body, opt, bopt);
208        if !opt.is_auto_sizing() {
209            let sz = container.style.as_ref().title_height;
210            let resize_id = widget_id_of(resize_state);
211            let r_0 = rect(r.x + r.width - sz, r.y + r.height - sz, sz, sz);
212            let control_state = (resize_state.opt, resize_state.bopt);
213            let control = container.update_control(resize_id, r_0, &control_state);
214            {
215                let mut ctx = container.widget_ctx(resize_id, r_0, None);
216                let _ = resize_state.handle(&mut ctx, &control);
217            }
218            if control.active {
219                container.rect.width = if 96 > container.rect.width + container.input.borrow().mouse_delta.x {
220                    96
221                } else {
222                    container.rect.width + container.input.borrow().mouse_delta.x
223                };
224                container.rect.height = if 64 > container.rect.height + container.input.borrow().mouse_delta.y {
225                    64
226                } else {
227                    container.rect.height + container.input.borrow().mouse_delta.y
228                };
229            }
230        }
231        if opt.is_auto_sizing() && (container.content_size.x > 0 || container.content_size.y > 0) {
232            let r_1 = container.layout.current_body();
233            container.rect.width = container.content_size.x + (container.rect.width - r_1.width);
234            container.rect.height = container.content_size.y + (container.rect.height - r_1.height);
235        }
236
237        if is_popup && container.popup_just_opened {
238            // Skip the auto-close check on the same frame the popup is opened.
239            container.popup_just_opened = false;
240        } else if is_popup && !container.input.borrow().mouse_pressed.is_none() && !container.in_hover_root {
241            *win_state = WindowState::Closed;
242        }
243        let body = container.body;
244        container.push_clip_rect(body);
245    }
246
247    fn end_window(&mut self) {
248        let container = &mut self.main;
249        container.pop_clip_rect();
250    }
251}
252
253#[derive(Clone)]
254/// Reference-counted handle to the internal window object.
255pub struct WindowHandle(Rc<RefCell<Window>>);
256
257impl WindowHandle {
258    pub(crate) fn window(name: &str, atlas: AtlasHandle, style: Rc<Style>, input: Rc<RefCell<Input>>, initial_rect: Recti) -> Self {
259        Self(Rc::new(RefCell::new(Window::window(name, atlas, style, input, initial_rect))))
260    }
261
262    pub(crate) fn dialog(name: &str, atlas: AtlasHandle, style: Rc<Style>, input: Rc<RefCell<Input>>, initial_rect: Recti) -> Self {
263        Self(Rc::new(RefCell::new(Window::dialog(name, atlas, style, input, initial_rect))))
264    }
265
266    pub(crate) fn popup(name: &str, atlas: AtlasHandle, style: Rc<Style>, input: Rc<RefCell<Input>>) -> Self {
267        Self(Rc::new(RefCell::new(Window::popup(name, atlas, style, input, Recti::new(0, 0, 0, 0)))))
268    }
269
270    /// Returns `true` if the window's state is `Open`.
271    pub fn is_open(&self) -> bool {
272        match self.0.borrow().win_state {
273            WindowState::Open => true,
274            _ => false,
275        }
276    }
277
278    pub(crate) fn inner_mut<'a>(&'a mut self) -> RefMut<'a, Window> {
279        self.0.borrow_mut()
280    }
281
282    pub(crate) fn inner<'a>(&'a self) -> Ref<'a, Window> {
283        self.0.borrow()
284    }
285
286    pub(crate) fn prepare(&mut self) {
287        self.inner_mut().main.prepare()
288    }
289
290    pub(crate) fn render<R: Renderer>(&mut self, canvas: &mut Canvas<R>) {
291        self.0.borrow_mut().main.render(canvas)
292    }
293
294    pub(crate) fn finish(&mut self) {
295        self.inner_mut().main.finish()
296    }
297
298    pub(crate) fn zindex(&self) -> i32 {
299        self.0.borrow().main.zindex
300    }
301
302    pub(crate) fn begin_window(&mut self, opt: ContainerOption, bopt: WidgetBehaviourOption) {
303        self.0.borrow_mut().begin_window(opt, bopt)
304    }
305
306    pub(crate) fn end_window(&mut self) {
307        self.inner_mut().end_window()
308    }
309
310    /// Resizes the underlying window rectangle.
311    pub fn set_size(&mut self, size: &Dimensioni) {
312        self.inner_mut().main.rect.width = size.width;
313        self.inner_mut().main.rect.height = size.height;
314    }
315}