Skip to main content

compose_rust/
windows.rs

1//! Several windows in one process.
2//!
3//! A window is a node: a `Window` child of the root, whose children are that window's
4//! content. All the windows share one composition, one Host and one node table, so a state
5//! one window's handler writes is read by every window that reads it, and the batch that
6//! results carries every window's change.
7//!
8//! This module holds what is not a node. A [`WindowHandle`] names the window the caller is
9//! in and carries the commands that happen once (bring it forward, give it a theme, change
10//! its zoom). [`WindowEvent`] is what a window reports. The root hears two events that
11//! belong to no window, a window the system made or restored and the application being
12//! opened again with none, through the registrations at the bottom.
13//!
14//! Everything is keyed by the id of the `Window` node, the number the Renderer reports a
15//! window's events under, so one window's size, zoom and theme never reach another's.
16
17use crate::schema::{EventPayload, Theme};
18use std::cell::{Cell, RefCell};
19use std::rc::Rc;
20
21pub use crate::schema::{LastWindowClosed, WindowMode};
22
23/// The phase of a torn-off window being dragged.
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum WindowDragPhase {
26    /// The window moved with the pointer.
27    Move,
28    /// The pointer was let go.
29    End,
30    /// The system took the drag away, or `Esc` was pressed.
31    Cancel,
32}
33
34impl WindowDragPhase {
35    fn from_wire(value: u32) -> Option<Self> {
36        match value {
37            crate::protocol::WINDOW_DRAG_MOVE => Some(Self::Move),
38            crate::protocol::WINDOW_DRAG_END => Some(Self::End),
39            crate::protocol::WINDOW_DRAG_CANCEL => Some(Self::Cancel),
40            _ => None,
41        }
42    }
43}
44
45/// What a dragged window is doing over a node that listens for it.
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47pub enum WindowDragOverKind {
48    Enter,
49    Move,
50    Leave,
51    /// The pointer was let go over the node: the place to move the tab into this window.
52    Drop,
53}
54
55impl WindowDragOverKind {
56    fn from_wire(value: u32) -> Option<Self> {
57        match value {
58            crate::protocol::WINDOW_DRAG_OVER_ENTER => Some(Self::Enter),
59            crate::protocol::WINDOW_DRAG_OVER_MOVE => Some(Self::Move),
60            crate::protocol::WINDOW_DRAG_OVER_LEAVE => Some(Self::Leave),
61            crate::protocol::WINDOW_DRAG_OVER_DROP => Some(Self::Drop),
62            _ => None,
63        }
64    }
65}
66
67/// What a window reports to the handler registered for it.
68#[derive(Clone, Copy, Debug, PartialEq)]
69pub enum WindowEvent {
70    /// The user asked to close the window. Nothing closes until the application removes
71    /// the `Window` node.
72    CloseRequested,
73    /// The window became the key window, or lost that place.
74    FocusChanged { focused: bool },
75    /// The window's outer frame moved or changed size, in screen dp.
76    Moved {
77        x: f32,
78        y: f32,
79        width: f32,
80        height: f32,
81    },
82    /// A torn-off window is being dragged. `over_window` is the id of the window under the
83    /// pointer, or 0.
84    Drag {
85        phase: WindowDragPhase,
86        screen_x: f32,
87        screen_y: f32,
88        over_window: u32,
89    },
90    /// A dragged window is passing over a node with a drag-over handler. The position is
91    /// inside that node, in dp.
92    DragOver {
93        kind: WindowDragOverKind,
94        window: u32,
95        x: f32,
96        y: f32,
97    },
98    /// The window could not be opened on this platform.
99    Unavailable,
100    /// The system closed the window without asking.
101    Closed,
102}
103
104impl WindowEvent {
105    /// The event a payload is, or `None` for a payload that is not a window's.
106    pub(crate) fn from_payload(payload: &EventPayload<'_>) -> Option<Self> {
107        Some(match *payload {
108            EventPayload::WindowCloseRequested => Self::CloseRequested,
109            EventPayload::WindowFocusChanged { focused } => Self::FocusChanged { focused },
110            EventPayload::WindowMoved {
111                x,
112                y,
113                width,
114                height,
115            } => Self::Moved {
116                x,
117                y,
118                width,
119                height,
120            },
121            EventPayload::WindowDrag {
122                phase,
123                screen_x,
124                screen_y,
125                over_window,
126            } => Self::Drag {
127                phase: WindowDragPhase::from_wire(phase)?,
128                screen_x,
129                screen_y,
130                over_window,
131            },
132            EventPayload::WindowDragOver { kind, window, x, y } => Self::DragOver {
133                kind: WindowDragOverKind::from_wire(kind)?,
134                window,
135                x,
136                y,
137            },
138            EventPayload::WindowUnavailable => Self::Unavailable,
139            EventPayload::WindowClosed => Self::Closed,
140            _ => return None,
141        })
142    }
143}
144
145/// A window's outer frame, in screen dp.
146#[derive(Clone, Copy, Debug, PartialEq)]
147pub struct WindowFrame {
148    pub x: f32,
149    pub y: f32,
150    pub width: f32,
151    pub height: f32,
152}
153
154/// A torn-off window being dragged.
155#[derive(Clone, Copy, Debug, PartialEq)]
156pub struct WindowDragEvent {
157    pub phase: WindowDragPhase,
158    /// Where the pointer is, in screen dp.
159    pub screen_x: f32,
160    pub screen_y: f32,
161    /// The id of the window under the pointer, or 0.
162    pub over_window: u32,
163}
164
165/// A dragged window passing over a node that listens for it.
166#[derive(Clone, Copy, Debug, PartialEq)]
167pub struct WindowDragOver {
168    pub kind: WindowDragOverKind,
169    /// The id of the window being dragged.
170    pub window: u32,
171    /// The position inside the node, in dp.
172    pub x: f32,
173    pub y: f32,
174}
175
176/// Opens a window under a pointer that is held down and has it follow the pointer until it
177/// is let go.
178#[derive(Clone, Copy, Debug, PartialEq)]
179pub struct DragFromPointer {
180    pub pointer_id: u32,
181    /// The point of the new window's content, in dp, that sits under the pointer.
182    pub anchor_x: f32,
183    pub anchor_y: f32,
184}
185
186impl DragFromPointer {
187    /// The 12 bytes the property travels as: the pointer, then the anchor.
188    pub(crate) fn to_bytes(self) -> Vec<u8> {
189        let mut bytes = Vec::with_capacity(12);
190        bytes.extend_from_slice(&self.pointer_id.to_le_bytes());
191        bytes.extend_from_slice(&self.anchor_x.to_bits().to_le_bytes());
192        bytes.extend_from_slice(&self.anchor_y.to_bits().to_le_bytes());
193        bytes
194    }
195}
196
197/// A window the application has no node for yet: the system made it, or brought
198/// it back after the process was killed, or the user asked for a new one.
199#[derive(Clone, Debug, Eq, PartialEq)]
200pub struct WindowRequest {
201    /// The `RestoreKey` the window was made under, or empty for a new window.
202    pub key: String,
203    /// The Renderer's number for the request.
204    pub request: u32,
205}
206
207/// A command that happens once and so is a record and not a property.
208pub(crate) enum Command {
209    Focus(u32),
210    Theme(u32, Theme),
211    ZoomLevel(u32, i8),
212}
213
214type RequestHandler = Rc<dyn Fn(WindowRequest)>;
215type ReopenHandler = Rc<dyn Fn()>;
216
217thread_local! {
218    /// The window whose node a handler is being called for, so a call to `window_size()`
219    /// or `zoom()` from inside the handler means that window.
220    static DISPATCH_WINDOW: Cell<u32> = const { Cell::new(0) };
221    static COMMANDS: RefCell<Vec<Command>> = const { RefCell::new(Vec::new()) };
222    /// The theme each window was last given, so a theme composed every frame is sent once.
223    static APPLIED_THEMES: RefCell<Vec<(u32, Theme)>> = const { RefCell::new(Vec::new()) };
224    static REQUEST_HANDLERS: RefCell<Vec<(u64, RequestHandler)>> =
225        const { RefCell::new(Vec::new()) };
226    static REOPEN_HANDLERS: RefCell<Vec<(u64, ReopenHandler)>> =
227        const { RefCell::new(Vec::new()) };
228    static NEXT_ID: Cell<u64> = const { Cell::new(1) };
229}
230
231/// Marks the window a handler is called for, until dropped.
232pub(crate) struct DispatchWindow {
233    previous: u32,
234}
235
236impl DispatchWindow {
237    pub(crate) fn enter(window: u32) -> Self {
238        let previous = DISPATCH_WINDOW.with(|current| current.replace(window));
239        Self { previous }
240    }
241}
242
243impl Drop for DispatchWindow {
244    fn drop(&mut self) {
245        let _ = DISPATCH_WINDOW.try_with(|current| current.set(self.previous));
246    }
247}
248
249/// The id of the `Window` node the caller is in, or 0 outside any window.
250///
251/// While composing it is the window the composable being composed sits in. Inside a
252/// handler it is the window the handler's node sits in.
253pub(crate) fn current_window_id() -> u32 {
254    let composing = crate::runtime::with_composer_if_idle(|composer| {
255        composer.composing().then(|| composer.enclosing_window())
256    })
257    .flatten();
258    composing.unwrap_or_else(|| DISPATCH_WINDOW.with(Cell::get))
259}
260
261/// A window of the application, named by the id of its `Window` node.
262///
263/// `Copy`, so it can move into any number of handlers. [`current_window`] hands out the
264/// one the caller is in.
265#[derive(Clone, Copy, Debug, Eq, PartialEq)]
266pub struct WindowHandle {
267    node: u32,
268}
269
270/// The window the caller is in, or `None` outside any `Window` (and so always in an
271/// application that has one window).
272pub fn current_window() -> Option<WindowHandle> {
273    match current_window_id() {
274        0 => None,
275        node => Some(WindowHandle { node }),
276    }
277}
278
279impl WindowHandle {
280    /// The id of the window's node, the number its events are reported under.
281    pub const fn id(&self) -> u32 {
282        self.node
283    }
284
285    /// Brings the window forward and makes it the key window. A platform that does not let
286    /// an application take the focus makes the window flash in the task bar instead.
287    pub fn focus(&self) {
288        COMMANDS.with_borrow_mut(|commands| commands.push(Command::Focus(self.node)));
289        crate::boundary::request_frame_from_worker();
290    }
291
292    /// Gives this window a theme of its own over the application's.
293    pub fn set_theme(&self, theme: Theme) {
294        apply_theme(self.node, theme);
295    }
296
297    /// What this window last measured. Not recorded as a read; a composable that has to
298    /// run again when the class changes reads `current_window_size()`.
299    pub fn size(&self) -> crate::window::WindowSize {
300        crate::window::window_size_of(self.node)
301    }
302
303    /// What this window is drawing at.
304    pub fn zoom(&self) -> crate::zoom::Zoom {
305        crate::zoom::zoom_of(self.node)
306    }
307
308    /// Asks for an application zoom level for this window, kept inside its range.
309    pub fn set_zoom_level(&self, level: i8) {
310        queue_zoom_level(self.node, crate::zoom::clamp_level(level));
311    }
312}
313
314/// Queues a theme for a window unless it already has exactly that one.
315pub(crate) fn apply_theme(window: u32, theme: Theme) {
316    let changed = APPLIED_THEMES.with_borrow_mut(|applied| {
317        match applied.iter_mut().find(|(id, _)| *id == window) {
318            Some(entry) => {
319                if entry.1 == theme {
320                    return false;
321                }
322                entry.1 = theme;
323                true
324            }
325            None => {
326                applied.push((window, theme));
327                true
328            }
329        }
330    });
331    if !changed {
332        return;
333    }
334    COMMANDS.with_borrow_mut(|commands| {
335        commands.retain(|command| !matches!(command, Command::Theme(id, _) if *id == window));
336        commands.push(Command::Theme(window, theme));
337    });
338    crate::boundary::request_frame_from_worker();
339}
340
341/// Queues a zoom level for a window, replacing one that is still waiting.
342pub(crate) fn queue_zoom_level(window: u32, level: i8) {
343    COMMANDS.with_borrow_mut(|commands| {
344        commands.retain(|command| !matches!(command, Command::ZoomLevel(id, _) if *id == window));
345        commands.push(Command::ZoomLevel(window, level));
346    });
347    crate::boundary::request_frame_from_worker();
348}
349
350/// The level a window was asked for since the last batch, if it was.
351pub(crate) fn pending_zoom_level(window: u32) -> Option<i8> {
352    COMMANDS.with_borrow(|commands| {
353        commands.iter().find_map(|command| match command {
354            Command::ZoomLevel(id, level) if *id == window => Some(*level),
355            _ => None,
356        })
357    })
358}
359
360/// Hands the waiting commands over, oldest first, and empties the queue.
361pub(crate) fn drain_commands(mut each: impl FnMut(Command)) {
362    let commands = COMMANDS.with_borrow_mut(std::mem::take);
363    for command in commands {
364        each(command);
365    }
366}
367
368/// Forgets everything kept for a window that left the tree.
369pub(crate) fn forget_window(window: u32) {
370    crate::window::forget_window(window);
371    crate::zoom::forget_window(window);
372    APPLIED_THEMES.with_borrow_mut(|applied| applied.retain(|(id, _)| *id != window));
373    COMMANDS.with_borrow_mut(|commands| {
374        commands.retain(|command| match command {
375            Command::Focus(id) | Command::Theme(id, _) | Command::ZoomLevel(id, _) => *id != window,
376        });
377    });
378}
379
380/// How many entries the per-window registries hold: sizes, zooms, subscriptions, themes and
381/// waiting commands. Zero once every window has closed. For the test that opens and closes
382/// windows to watch it not grow.
383#[doc(hidden)]
384pub fn window_state_len() -> usize {
385    crate::window::window_state_len()
386        + crate::zoom::window_state_len()
387        + APPLIED_THEMES.with_borrow(Vec::len)
388        + COMMANDS.with_borrow(Vec::len)
389}
390
391/// Clears the commands and the registrations. Used when a Host is made and between tests.
392#[doc(hidden)]
393pub fn reset_windows() {
394    DISPATCH_WINDOW.with(|current| current.set(0));
395    COMMANDS.with_borrow_mut(Vec::clear);
396    APPLIED_THEMES.with_borrow_mut(Vec::clear);
397    REQUEST_HANDLERS.with_borrow_mut(Vec::clear);
398    REOPEN_HANDLERS.with_borrow_mut(Vec::clear);
399}
400
401fn next_id() -> u64 {
402    NEXT_ID.with(|next| {
403        let id = next.get();
404        next.set(id + 1);
405        id
406    })
407}
408
409/// A registration for windows the application has no node for. Dropping it removes the
410/// handler.
411pub struct WindowRequestSubscription {
412    id: u64,
413}
414
415impl Drop for WindowRequestSubscription {
416    fn drop(&mut self) {
417        REQUEST_HANDLERS.with_borrow_mut(|handlers| handlers.retain(|(id, _)| *id != self.id));
418    }
419}
420
421/// Runs `handler` for each window the system or the user made that the application has no
422/// node for, for as long as the returned registration is kept. The handler puts a `Window`
423/// node with that `RestoreKey` into the tree in the same call, or the Renderer closes the
424/// window.
425pub fn on_window_request(handler: Rc<dyn Fn(WindowRequest)>) -> WindowRequestSubscription {
426    let id = next_id();
427    REQUEST_HANDLERS.with_borrow_mut(|handlers| handlers.push((id, handler)));
428    WindowRequestSubscription { id }
429}
430
431/// Hands a request to the handlers. Returns whether anyone was listening.
432pub(crate) fn deliver_request(request: &WindowRequest) -> bool {
433    // Copied out first: a handler may mount or unmount a component that listens.
434    let handlers: Vec<Rc<dyn Fn(WindowRequest)>> = REQUEST_HANDLERS.with_borrow(|handlers| {
435        handlers
436            .iter()
437            .map(|(_, handler)| handler.clone())
438            .collect()
439    });
440    for handler in &handlers {
441        handler(request.clone());
442    }
443    !handlers.is_empty()
444}
445
446/// A registration for the application being opened again while it has no window. Dropping
447/// it removes the handler.
448pub struct ApplicationReopenSubscription {
449    id: u64,
450}
451
452impl Drop for ApplicationReopenSubscription {
453    fn drop(&mut self) {
454        REOPEN_HANDLERS.with_borrow_mut(|handlers| handlers.retain(|(id, _)| *id != self.id));
455    }
456}
457
458/// Runs `handler` when the user opens the application again while it has no window, which
459/// is the Dock icon on macOS.
460pub fn on_application_reopen_handler(handler: Rc<dyn Fn()>) -> ApplicationReopenSubscription {
461    let id = next_id();
462    REOPEN_HANDLERS.with_borrow_mut(|handlers| handlers.push((id, handler)));
463    ApplicationReopenSubscription { id }
464}
465
466/// Tells the handlers. Returns whether anyone was listening.
467pub(crate) fn deliver_reopen() -> bool {
468    let handlers: Vec<Rc<dyn Fn()>> = REOPEN_HANDLERS.with_borrow(|handlers| {
469        handlers
470            .iter()
471            .map(|(_, handler)| handler.clone())
472            .collect()
473    });
474    for handler in &handlers {
475        handler();
476    }
477    !handlers.is_empty()
478}