OXYGENHUB
# 943c54cf6a56 oxygenhub-android / rust-core / src / backdrop / adrift.rs
4.2 KiB · Rust · last changed The hub's living backgrounds in the app: the mark at sign-in, Your sky on the dashboard, live runs pulse (refs #23) 8 days ago source blame history raw
1dfa6314 Andy McCall ↶ 1
// Copied from OxygenHub crates/backdrop/src/adrift.rs (6dc080f6) by
2
// scripts/sync-backdrop.sh: change it there, never here.
3
//! Adrift (docs/backgrounds.md §2): the not-found and server-error pages.
4
//! A quiet cluster in the margin, and one mark drifting away from it, as
5
//! the page drifted from what was asked for. It drifts for a minute, fades
6
//! and sets out again. No data.
7
8
use crate::backdrop::field::{knot, margins, unit, Dot, Field, Orbit, ZOOM};
9
use crate::backdrop::scene::Scene;
10
use crate::backdrop::sky::{hash, Colours};
11
12
const CLUSTER: usize = 160;
13
/// The lost mark's speed, and how long one drift lasts, fade included.
14
const DRIFT_SPEED: f32 = 7.0;
15
const DRIFT_S: f32 = 70.0;
16
const FADE_S: f32 = 5.0;
17
18
pub struct Adrift {
19
    field: Field,
20
    orbits: Vec<Orbit>,
21
    centre: (f32, f32),
22
    w: f32,
23
    h: f32,
24
    t: f32,
25
}
26
27
impl Adrift {
28
    pub fn new(still: bool) -> Adrift {
29
        let mut field = Field::new(still);
30
        let orbits = knot("adrift", CLUSTER);
31
        for k in 0..CLUSTER {
32
            let h = hash(&format!("adrift@{k}"));
33
            let mut d = Dot::new(0.0, 0.0, 2.0 * ZOOM, if k % 7 == 0 { 2.0 } else { 0.0 });
34
            d.bright = 0.5 + 0.5 * unit(h, 48);
35
            d.group = 0;
36
            d.order = k as u32;
37
            field.dots.push(d);
38
        }
39
        // The cluster's core, and the lost mark, a little larger.
40
        field.dots.push(Dot::new(0.0, 0.0, 8.0, 4.0));
41
        let mut lost = Dot::new(0.0, 0.0, 3.4 * ZOOM, 0.0);
42
        lost.bright = 1.0;
43
        field.dots.push(lost);
44
        let mut a = Adrift { field, orbits, centre: (0.0, 0.0), w: 0.0, h: 0.0, t: 12.0 };
45
        a.place();
46
        a
47
    }
48
49
    fn place(&mut self) {
50
        let (cx, cy) = self.centre;
51
        for (i, o) in self.orbits.iter().enumerate() {
52
            let (x, y) = o.at((cx, cy), self.t);
53
            self.field.dots[i].x = x;
54
            self.field.dots[i].y = y;
55
        }
56
        let core = &mut self.field.dots[CLUSTER];
57
        core.x = cx;
58
        core.y = cy;
59
        // The lost mark leaves up and to the right, fading at the end of
60
        // each drift and setting out again from the cluster's edge.
61
        let p = self.t % DRIFT_S;
62
        let dir: f32 = -0.35;
63
        let from = self.orbits.iter().map(|o| o.radius).fold(0.0, f32::max) + 14.0;
64
        let lost = &mut self.field.dots[CLUSTER + 1];
65
        lost.x = cx + dir.cos() * (from + p * DRIFT_SPEED);
66
        lost.y = cy + dir.sin() * (from + p * DRIFT_SPEED);
67
        lost.bright = ((DRIFT_S - p) / FADE_S).min(p / FADE_S).clamp(0.0, 1.0);
68
    }
69
}
70
71
impl Scene for Adrift {
72
    fn resize(&mut self, w: f32, h: f32, avoid: Option<(f32, f32)>) {
73
        self.w = w;
74
        self.h = h;
75
        self.centre = match margins(w, avoid) {
76
            Some((l, _)) => (l * 0.45, h * 0.62),
77
            None => (w * 0.15, h * 0.7),
78
        };
79
        self.place();
80
    }
81
    fn point(&mut self, at: Option<(f32, f32)>) {
82
        self.field.point(at);
83
    }
84
    fn step(&mut self, dt: f32) {
85
        if !self.field.still {
86
            self.t += dt.clamp(0.0, 0.1);
87
        }
88
        self.place();
89
        self.field.light(dt);
90
    }
91
    fn instances(&self, c: &Colours, out: &mut Vec<f32>) {
92
        self.field.instances(c, out)
93
    }
94
    fn links(&self, c: &Colours, out: &mut Vec<f32>) {
95
        self.field.links(c, out)
96
    }
97
    fn label(&self) -> Option<(&str, f32, f32)> {
98
        None
99
    }
100
    fn busy(&self) -> bool {
101
        self.field.easing()
102
    }
103
    fn still(&self) -> bool {
104
        self.field.still
105
    }
106
    fn size(&self) -> (f32, f32) {
107
        (self.w, self.h)
108
    }
109
}
110
111
#[cfg(test)]
112
mod tests {
113
    use super::*;
114
115
    #[test]
116
    fn one_mark_drifts_away_from_the_cluster_and_sets_out_again() {
117
        let mut a = Adrift::new(false);
118
        a.resize(2560.0, 1270.0, Some((707.0, 1838.0)));
119
        assert!(a.centre.0 < 707.0, "the cluster sits in the margin");
120
        let dist = |a: &Adrift| {
121
            let l = &a.field.dots[CLUSTER + 1];
122
            (l.x - a.centre.0).hypot(l.y - a.centre.1)
123
        };
124
        let early = dist(&a);
125
        for _ in 0..200 {
126
            a.step(0.1);
127
        }
128
        assert!(dist(&a) > early + 100.0, "it drifts away");
129
        for _ in 0..500 {
130
            a.step(0.1);
131
        }
132
        assert!(dist(&a) < early + 100.0, "and sets out again");
133
    }
134
}