motion/app.rs
1//! [`AppExt`] — the app-level motion settings, reached on the `App` itself the
2//! way component groups are reached on the theme.
3//!
4//! What belongs here is a setting the `App` holds. [`crate::set_speed`] does
5//! not: the catalog's timelines are read from free functions deep inside
6//! element builders that have no `cx` to hand, which is why speed is a
7//! process-wide mirror instead.
8
9use gpui::{App, Global};
10
11/// Whether animation stops while the app is not frontmost. Absent means on —
12/// a global nobody installed is the default, not the opposite of it.
13struct PauseWhenInactive(bool);
14
15impl Global for PauseWhenInactive {}
16
17/// The motion settings an app carries.
18///
19/// ```ignore
20/// use motion::AppExt as _;
21///
22/// cx.set_pause_when_inactive(false); // a HUD that must keep moving unfocused
23/// ```
24pub trait AppExt {
25 /// gpui snaps every `with_animation` element when this is set — end state
26 /// for oneshots, rest state for loops — and schedules no frames.
27 fn reduced_motion(&self) -> bool;
28
29 fn set_reduced_motion(&mut self, reduced: bool);
30
31 /// Whether animation stops while the app is not frontmost. On by default.
32 fn pause_when_inactive(&self) -> bool;
33
34 /// An app in the background that keeps animating is spending a core on
35 /// frames nobody is looking at. Nothing below this stops on its own: one
36 /// spinner in a backgrounded window held 30fps and 22% of a core
37 /// indefinitely (2026-08, debug build, M-series laptop), against 2% with
38 /// this on. Turn it off for a window that must keep moving while something
39 /// else has focus — a side panel, a floating HUD.
40 ///
41 /// The claim is refused rather than cancelled, so nothing has to resume it:
42 /// gpui refreshes a window when it becomes active, and the render that
43 /// follows takes the claim again.
44 fn set_pause_when_inactive(&mut self, pause: bool);
45}
46
47impl AppExt for App {
48 fn reduced_motion(&self) -> bool {
49 self.reduce_motion()
50 }
51
52 fn set_reduced_motion(&mut self, reduced: bool) {
53 self.set_reduce_motion(reduced);
54 }
55
56 fn pause_when_inactive(&self) -> bool {
57 self.try_global::<PauseWhenInactive>()
58 .is_none_or(|pause| pause.0)
59 }
60
61 fn set_pause_when_inactive(&mut self, pause: bool) {
62 self.set_global(PauseWhenInactive(pause));
63 }
64}