Skip to main content

gpui_macos/
loading.rs

1//! The radial macOS activity indicator on GPUI Kit's animation primitive.
2use crate::{ActiveTheme, kit};
3use gpui_kit::App;
4use std::sync::LazyLock;
5
6static RADIAL_ARTWORK: LazyLock<String> = LazyLock::new(|| {
7    let mut svg = String::from(
8        r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="white" stroke-width="2" stroke-linecap="round">"#,
9    );
10    for ray in 0..12 {
11        svg.push_str(&format!(
12            r#"<path d="M12 3v4" transform="rotate({} 12 12)" opacity="{}"/>"#,
13            ray * 30,
14            0.15 + ray as f32 / 11. * 0.85,
15        ));
16    }
17    svg.push_str("</g></svg>");
18    svg
19});
20
21/// A system SF activity indicator on macOS, with a twelve-ray fallback.
22/// Keeps Kit's size, color, custom-icon and easing APIs, and its static Reduce
23/// Motion fallback.
24pub fn native_spinner(cx: &App) -> kit::spinner::Spinner {
25    #[cfg(target_os = "macos")]
26    let artwork = crate::native_symbols::svg_for_path(&crate::IconName::Loader.path());
27    #[cfg(target_os = "macos")]
28    let artwork = artwork
29        .as_deref()
30        .unwrap_or_else(|| RADIAL_ARTWORK.as_bytes());
31    #[cfg(not(target_os = "macos"))]
32    let artwork = RADIAL_ARTWORK.as_bytes();
33    kit::spinner::Spinner::new()
34        .icon(kit::Icon::default().data(artwork))
35        .color(cx.macos().secondary)
36        .ease(|progress| progress)
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use gpui_kit::{Context, IntoElement, Render, TestAppContext, Window, px, size};
43
44    struct Indicator;
45    impl Render for Indicator {
46        fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
47            native_spinner(cx)
48        }
49    }
50
51    #[gpui_kit::test]
52    fn radial_indicator_respects_reduce_motion(cx: &mut TestAppContext) {
53        cx.update(|cx| {
54            crate::init(cx);
55            cx.set_reduce_motion(true);
56        });
57        let window = cx.open_window(size(px(80.), px(80.)), |_, _| Indicator);
58        cx.run_until_parked();
59        assert_eq!(
60            window
61                .update(cx, |_, window, cx| window.simulate_next_frame(cx))
62                .unwrap(),
63            0
64        );
65    }
66}