| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
use crate::backdrop::adrift::Adrift; |
| 7 |
use crate::backdrop::mark::Mark; |
| 8 |
use crate::backdrop::scene::Scene; |
| 9 |
use crate::backdrop::sky::{Colours, Event, Kind, Repo, Sky}; |
| 10 |
use std::sync::Mutex; |
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
#[derive(uniffi::Record)] |
| 17 |
pub struct SkyEvent { |
| 18 |
pub repo: u32, |
| 19 |
pub kind: u32, |
| 20 |
pub age_days: f32, |
| 21 |
pub n: u32, |
| 22 |
} |
| 23 |
|
| 24 |
|
| 25 |
|
| 26 |
|
| 27 |
|
| 28 |
#[derive(uniffi::Record)] |
| 29 |
pub struct BackdropFrame { |
| 30 |
pub marks: Vec<u8>, |
| 31 |
pub links: Vec<u8>, |
| 32 |
} |
| 33 |
|
| 34 |
fn bytes(v: &[f32]) -> Vec<u8> { |
| 35 |
v.iter().flat_map(|f| f.to_le_bytes()).collect() |
| 36 |
} |
| 37 |
|
| 38 |
|
| 39 |
#[derive(uniffi::Record)] |
| 40 |
pub struct BackdropLabel { |
| 41 |
pub text: String, |
| 42 |
pub x: f32, |
| 43 |
pub y: f32, |
| 44 |
} |
| 45 |
|
| 46 |
|
| 47 |
#[derive(uniffi::Object)] |
| 48 |
pub struct Backdrop { |
| 49 |
scene: Mutex<Box<dyn Scene + Send>>, |
| 50 |
colours: Mutex<Colours>, |
| 51 |
} |
| 52 |
|
| 53 |
fn rgba(c: &[f32], i: usize) -> [f32; 4] { |
| 54 |
[c[i * 4], c[i * 4 + 1], c[i * 4 + 2], c[i * 4 + 3]] |
| 55 |
} |
| 56 |
|
| 57 |
impl Backdrop { |
| 58 |
fn of(scene: Box<dyn Scene + Send>) -> std::sync::Arc<Backdrop> { |
| 59 |
let grey = [0.5, 0.55, 0.65, 0.5]; |
| 60 |
let colours = Colours { mark: grey, lit: grey, link: grey, ok: grey, warn: grey, bad: grey }; |
| 61 |
std::sync::Arc::new(Backdrop { scene: Mutex::new(scene), colours: Mutex::new(colours) }) |
| 62 |
} |
| 63 |
} |
| 64 |
|
| 65 |
#[uniffi::export] |
| 66 |
impl Backdrop { |
| 67 |
|
| 68 |
#[uniffi::constructor] |
| 69 |
pub fn mark(still: bool) -> std::sync::Arc<Backdrop> { |
| 70 |
Backdrop::of(Box::new(Mark::new(still))) |
| 71 |
} |
| 72 |
|
| 73 |
|
| 74 |
#[uniffi::constructor] |
| 75 |
pub fn adrift(still: bool) -> std::sync::Arc<Backdrop> { |
| 76 |
Backdrop::of(Box::new(Adrift::new(still))) |
| 77 |
} |
| 78 |
|
| 79 |
|
| 80 |
|
| 81 |
#[uniffi::constructor] |
| 82 |
pub fn sky(repos: Vec<String>, events: Vec<SkyEvent>, w: f32, h: f32, still: bool) -> std::sync::Arc<Backdrop> { |
| 83 |
let repos: Vec<Repo> = repos.into_iter().map(|name| Repo { name }).collect(); |
| 84 |
let events: Vec<Event> = events |
| 85 |
.into_iter() |
| 86 |
.filter(|e| (e.repo as usize) < repos.len()) |
| 87 |
.map(|e| Event { repo: e.repo as usize, kind: Kind::from_code(e.kind), age_days: e.age_days, n: e.n.max(1) }) |
| 88 |
.collect(); |
| 89 |
Backdrop::of(Box::new(Sky::new(&repos, &events, w, h, still))) |
| 90 |
} |
| 91 |
|
| 92 |
|
| 93 |
|
| 94 |
pub fn colours(&self, rgba_x6: Vec<f32>) { |
| 95 |
if rgba_x6.len() < 24 { |
| 96 |
return; |
| 97 |
} |
| 98 |
let c = &rgba_x6; |
| 99 |
*self.colours.lock().unwrap() = |
| 100 |
Colours { mark: rgba(c, 0), lit: rgba(c, 1), link: rgba(c, 2), ok: rgba(c, 3), warn: rgba(c, 4), bad: rgba(c, 5) }; |
| 101 |
} |
| 102 |
|
| 103 |
|
| 104 |
pub fn resize(&self, w: f32, h: f32) { |
| 105 |
self.scene.lock().unwrap().resize(w, h, None); |
| 106 |
} |
| 107 |
|
| 108 |
|
| 109 |
pub fn point(&self, x: f32, y: f32, down: bool) { |
| 110 |
self.scene.lock().unwrap().point(down.then_some((x, y))); |
| 111 |
} |
| 112 |
|
| 113 |
|
| 114 |
pub fn step(&self, dt: f32) -> bool { |
| 115 |
let mut s = self.scene.lock().unwrap(); |
| 116 |
s.step(dt.clamp(0.0, 0.1)); |
| 117 |
s.busy() |
| 118 |
} |
| 119 |
|
| 120 |
|
| 121 |
pub fn frame(&self) -> BackdropFrame { |
| 122 |
let (s, c) = (self.scene.lock().unwrap(), self.colours.lock().unwrap()); |
| 123 |
let (mut marks, mut links) = (Vec::new(), Vec::new()); |
| 124 |
s.instances(&c, &mut marks); |
| 125 |
s.links(&c, &mut links); |
| 126 |
BackdropFrame { marks: bytes(&marks), links: bytes(&links) } |
| 127 |
} |
| 128 |
|
| 129 |
|
| 130 |
pub fn label(&self) -> Option<BackdropLabel> { |
| 131 |
self.scene.lock().unwrap().label().map(|(t, x, y)| BackdropLabel { text: t.to_string(), x, y }) |
| 132 |
} |
| 133 |
} |
| 134 |
|
| 135 |
#[cfg(test)] |
| 136 |
mod tests { |
| 137 |
use super::*; |
| 138 |
|
| 139 |
#[test] |
| 140 |
fn a_sky_of_real_repositories_draws_marks_and_moves() { |
| 141 |
let b = Backdrop::sky( |
| 142 |
vec!["andy/oxygen".into(), "andy/hub".into()], |
| 143 |
vec![SkyEvent { repo: 0, kind: 0, age_days: 1.0, n: 5 }, SkyEvent { repo: 1, kind: 4, age_days: 2.0, n: 1 }, SkyEvent { repo: 9, kind: 0, age_days: 1.0, n: 1 }], |
| 144 |
400.0, |
| 145 |
800.0, |
| 146 |
false, |
| 147 |
); |
| 148 |
b.colours(vec![0.6; 24]); |
| 149 |
b.step(0.016); |
| 150 |
let f = b.frame(); |
| 151 |
assert!(!f.marks.is_empty() && f.marks.len() % 32 == 0, "{} bytes", f.marks.len()); |
| 152 |
assert_eq!(f.links.len() % 48, 0); |
| 153 |
let first = f.marks.clone(); |
| 154 |
for _ in 0..30 { |
| 155 |
b.step(0.033); |
| 156 |
} |
| 157 |
assert_ne!(b.frame().marks, first, "it drifts"); |
| 158 |
} |
| 159 |
|
| 160 |
#[test] |
| 161 |
fn the_mark_and_the_field_draw_too() { |
| 162 |
for b in [Backdrop::mark(false), Backdrop::adrift(true)] { |
| 163 |
b.resize(360.0, 640.0); |
| 164 |
b.step(0.016); |
| 165 |
assert!(!b.frame().marks.is_empty()); |
| 166 |
} |
| 167 |
} |
| 168 |
} |