Skip to main content

nifty_runtime/reactive/
windows.rs

1//! The app's windows: which are open, building a window's content when it opens, and
2//! closing it when code or the user asks.
3
4use std::rc::Rc;
5
6use super::{Engine, Logic, Mounted, WindowAction, WindowRequest, WindowTemplate};
7use crate::backend::Backend;
8use crate::geometry::Size;
9use crate::ui::{Node, WindowCloseHandler, WindowId};
10
11pub(super) struct MountedWindow<L: Logic> {
12    pub(super) window: WindowId,
13    /// The size its template asked for, which a reload compares against.
14    pub(super) declared_size: Size,
15    pub(super) content: Node,
16    pub(super) children: Vec<Mounted<L>>,
17}
18
19/// An open window of the app being replaced, and the size its template asked for.
20#[derive(Clone, Copy)]
21pub(super) struct ReusedWindow {
22    window: WindowId,
23    declared_size: Size,
24}
25
26impl ReusedWindow {
27    pub(super) fn of<L: Logic>(mounted: &MountedWindow<L>) -> Self {
28        Self {
29            window: mounted.window,
30            declared_size: mounted.declared_size,
31        }
32    }
33
34    pub(super) fn window(self) -> WindowId {
35        self.window
36    }
37}
38
39/// A window whose content is built but not yet showing, and its template's position.
40pub(super) struct PreparedWindow<L: Logic> {
41    index: usize,
42    mounted: MountedWindow<L>,
43    reused: Option<ReusedWindow>,
44}
45
46impl<L: Logic, B: Backend> Engine<L, B> {
47    /// Which windows to open, by template: those the replaced app had open, `reused`, and
48    /// new ones that start open. When none would be open, those that start open are, so
49    /// the app keeps running.
50    pub(super) fn windows_to_open(&self, reused: &[Option<ReusedWindow>]) -> Vec<bool> {
51        let starts_open = |template: &WindowTemplate<L>| template.starts_open;
52        let opening: Vec<bool> = self
53            .templates
54            .iter()
55            .enumerate()
56            .map(|(index, template)| {
57                reused
58                    .get(index)
59                    .map_or_else(|| starts_open(template), Option::is_some)
60            })
61            .collect();
62        if opening.contains(&true) {
63            opening
64        } else {
65            self.templates.iter().map(starts_open).collect()
66        }
67    }
68
69    /// Builds the content of the window at `index`, in the window `reused` if given or a
70    /// new one, without showing it yet.
71    pub(super) fn prepare_window(
72        &self,
73        index: usize,
74        reused: Option<ReusedWindow>,
75    ) -> Result<PreparedWindow<L>, L::Error> {
76        let template = &self.templates[index];
77        let window = match reused {
78            Some(reused) => reused.window,
79            None => self.ui.create_window(&template.title, template.size),
80        };
81        match self.mount_window(window, template) {
82            Ok(mounted) => Ok(PreparedWindow {
83                index,
84                mounted,
85                reused,
86            }),
87            Err(error) => {
88                if reused.is_none() {
89                    self.ui.close(window);
90                }
91                Err(error)
92            }
93        }
94    }
95
96    /// Puts built content in its window and shows it.
97    pub(super) fn show_prepared(&self, prepared: PreparedWindow<L>) {
98        let PreparedWindow {
99            index,
100            mounted,
101            reused,
102        } = prepared;
103        let template = &self.templates[index];
104        self.ui.set_backdrop(mounted.window, template.backdrop);
105        self.ui.set_content(mounted.window, mounted.content);
106        match reused {
107            Some(reused) => {
108                self.ui.set_title(mounted.window, &template.title);
109                if reused.declared_size != mounted.declared_size {
110                    self.ui.set_size(mounted.window, mounted.declared_size);
111                }
112            }
113            None => self.ui.show(mounted.window),
114        }
115        self.windows.borrow_mut()[index] = Some(mounted);
116    }
117
118    /// Throws away content that was built but never shown, closing windows opened for it.
119    pub(super) fn discard(&self, prepared: Vec<PreparedWindow<L>>) {
120        for PreparedWindow {
121            mounted, reused, ..
122        } in prepared
123        {
124            self.ui.remove(mounted.content);
125            if reused.is_none() {
126                self.ui.close(mounted.window);
127            }
128        }
129    }
130
131    /// Builds a window's content without placing it in the window yet.
132    fn mount_window(
133        &self,
134        window: WindowId,
135        template: &WindowTemplate<L>,
136    ) -> Result<MountedWindow<L>, L::Error> {
137        let content = self.ui.content(window, &template.style);
138        match self.mount_list(&template.children, &template.frame, content, &mut 0) {
139            Ok(children) => Ok(MountedWindow {
140                window,
141                declared_size: template.size,
142                content,
143                children,
144            }),
145            Err(error) => {
146                self.ui.remove(content);
147                Err(error)
148            }
149        }
150    }
151
152    /// Opens and closes windows as `requests` ask, the last request for a window deciding
153    /// what happens to it. Windows open before others close, so the app, which quits with
154    /// its last window, keeps running when code swaps one window for another.
155    pub(super) fn apply_window_requests(&self, requests: &[WindowRequest]) -> Result<(), L::Error> {
156        let mut decided: Vec<WindowRequest> = Vec::new();
157        for request in requests {
158            match decided
159                .iter_mut()
160                .find(|decided| decided.window == request.window)
161            {
162                Some(decided) => decided.action = request.action,
163                None => decided.push(*request),
164            }
165        }
166        for request in &decided {
167            if request.action == WindowAction::Open {
168                self.open_window(request.window)?;
169            }
170        }
171        for request in &decided {
172            if request.action == WindowAction::Close {
173                self.close_window(request.window);
174            }
175        }
176        Ok(())
177    }
178
179    /// Opens the window at `index`, or brings it to the front if it is open.
180    fn open_window(&self, index: usize) -> Result<(), L::Error> {
181        let open = self.windows.borrow()[index]
182            .as_ref()
183            .map(|mounted| mounted.window);
184        if let Some(window) = open {
185            self.ui.focus(window);
186            return Ok(());
187        }
188        let prepared = self.prepare_window(index, None)?;
189        self.show_prepared(prepared);
190        Ok(())
191    }
192
193    /// Closes the window at `index`, if it is open.
194    fn close_window(&self, index: usize) {
195        let closed = self.windows.borrow_mut()[index].take();
196        if let Some(mounted) = closed {
197            self.ui.close(mounted.window);
198        }
199    }
200
201    /// Closes a window the user asked to close, as code closing it would.
202    pub(super) fn on_close(&self) -> WindowCloseHandler {
203        let engine = self.this.clone();
204        Rc::new(move |window| {
205            let Some(engine) = engine.upgrade() else {
206                return;
207            };
208            let index = engine.windows.borrow().iter().position(|mounted| {
209                mounted
210                    .as_ref()
211                    .is_some_and(|mounted| mounted.window == window)
212            });
213            match index {
214                Some(index) => engine.close_window(index),
215                None => engine.ui.close(window),
216            }
217        })
218    }
219}