Skip to main content

dashboard/
dashboard.rs

1//! A dashboard — the third shape, after a form and a list.
2//!
3//! Grid layout, gauges, shapes drawn from data, and a value that animates when
4//! Rust changes it. Where `settings.rs` is bound controls and `inbox.rs` is
5//! navigation, this is the read-only screen that has to *move*.
6//!
7//! ```sh
8//! cargo run -p swiftui-native --example dashboard
9//! cargo run -p swiftui-native --example dashboard -- --render
10//! ```
11
12use std::sync::atomic::{AtomicU64, Ordering};
13use swiftui_native::prelude::*;
14use swiftui_native::{state, views};
15
16/// The reading each tile shows, as a percentage. Rust owns it; the screen is a
17/// function of it.
18static LOAD: [AtomicU64; 3] = [AtomicU64::new(34), AtomicU64::new(71), AtomicU64::new(12)];
19
20const LABELS: [&str; 3] = ["CPU", "Memory", "Disk"];
21const TINTS: [SystemColor; 3] = [SystemColor::Blue, SystemColor::Orange, SystemColor::Green];
22
23fn reading(i: usize) -> f64 {
24    LOAD[i].load(Ordering::Relaxed) as f64 / 100.0
25}
26
27/// One tile: a ring showing the fraction, the number over it, and a caption.
28fn tile(i: usize) -> View {
29    let value = reading(i);
30    vstack_spacing(
31        &[
32            zstack![
33                circle_shape().stroke_system(SystemColor::Gray, 6.0).opacity(0.25),
34                // The filled part: the same circle trimmed to the fraction, and
35                // turned so the arc starts at the top rather than at three
36                // o'clock.
37                circle_shape()
38                    .trim(0.0, value)
39                    .stroke_system(TINTS[i], 6.0)
40                    .rotate(-90.0),
41                text!("{:.0}", value * 100.0).text_style(TextStyle::Title2).bold(),
42            ]
43            .frame(72.0, 72.0),
44            text(LABELS[i]).font_size(12.0).fg_system(SystemColor::Gray),
45        ],
46        8.0,
47    )
48    .padding(12.0)
49    .a11y_element(AccessibilityChildBehavior::Combine)
50    .a11y_label(LABELS[i])
51    .a11y_value(&format!("{:.0} percent", value * 100.0))
52}
53
54fn body() -> View {
55    vstack_aligned_spacing(
56        &[
57            text("System").text_style(TextStyle::LargeTitle).bold(),
58            grid(&[grid_row(&[tile(0), tile(1), tile(2)])]),
59            divider(),
60            // A gauge over the busiest reading, which is a different control
61            // from the rings and reads the same state.
62            vstack_aligned(
63                &[
64                    text("Busiest").font_size(12.0).fg_system(SystemColor::Gray),
65                    gauge(reading(1), 0.0, 1.0, text("Memory"))
66                        .gauge_style(views::GaugeStyle::AccessoryLinearCapacityGaugeStyle)
67                        .tint_system(SystemColor::Orange),
68                ],
69                HorizontalAlignment::Leading,
70            ),
71            spacer(),
72            button("Refresh", refresh)
73                .button_style(views::ButtonStyle::BorderedProminent)
74                .keyboard_shortcut(KeyboardShortcut::DefaultAction),
75        ],
76        HorizontalAlignment::Leading,
77        Some(16.0),
78    )
79    .padding(20.0)
80    // Animate whatever changed, rather than snapping to the new reading.
81    .animation(LOAD[0].load(Ordering::Relaxed) as i64)
82}
83
84/// New readings, applied inside an animation so the rings sweep.
85fn refresh() {
86    static SEED: AtomicU64 = AtomicU64::new(0x9E37_79B9);
87    for slot in &LOAD {
88        let mut x = SEED.load(Ordering::Relaxed);
89        x ^= x << 13;
90        x ^= x >> 7;
91        x ^= x << 17;
92        SEED.store(x, Ordering::Relaxed);
93        slot.store(x % 101, Ordering::Relaxed);
94    }
95    state::refresh_animated(state::ease_in_out(0.6));
96}
97
98fn content_view() -> swiftui_native::ViewHandle {
99    state::root_view(|_| body().into_handle())
100}
101
102#[cfg(target_os = "macos")]
103#[path = "common/simulator.rs"]
104mod simulator;
105
106#[cfg(target_os = "macos")]
107fn main() {
108    if std::env::args().any(|a| a == "--sim" || a == "device") {
109        return simulator::main(&simulator::Bundle {
110            example: "dashboard",
111            bundle_id: "dev.rswift.Dashboard",
112            executable: "Dashboard",
113            display_name: "Dashboard",
114            launch_args: &[],
115        });
116    }
117    if std::env::args().any(|a| a == "--render") {
118        let out = concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/dashboard.png");
119        swiftui_native::render::render(&content_view(), 420, 480).write_png(out);
120        eprintln!("wrote {out}");
121        return;
122    }
123    show_window(&content_view(), "Dashboard", 420.0, 480.0);
124}
125
126#[cfg(not(target_os = "macos"))]
127fn main() {
128    swiftui_native::app::run(content_view)
129}