1//! A mascot in a mood as a whole picture: shadow, the figure breathing, and what is round it. 2//! The assembly is `whiskers-art`'s, the same for every mascot; only the figure differs. 3 4use whiskers_art::assemble; 5pub use whiskers_art::scene::{CENTER, GAZE_REACH, GAZE_STEP, HALF_WIDTH, VIEW, gaze_offset}; 6 7use crate::art_bunny; 8use crate::mascot::Mascot; 9use crate::mood::Mood; 10use crate::node::Node; 11 12#[derive(Clone, Debug, PartialEq)] 13pub struct Scene { 14 pub mascot: Mascot, 15 pub mood: Mood, 16 pub nodes: Vec<Node>, 17} 18 19/// `mascot` in `mood`. An exhaustive match over both the mascot and (inside each art) the mood. 20pub fn scene(mascot: Mascot, mood: Mood) -> Scene { 21 let nodes = match mascot { 22 Mascot::Grey => whiskers_art::scene(mood.pet(), None), 23 Mascot::Bunny => assemble(mood.pet(), art_bunny::figure(mood)), 24 Mascot::Ginger => assemble(mood.pet(), crate::art_ginger::figure(mood)), 25 }; 26 Scene { mascot, mood, nodes } 27} 28 29#[cfg(test)] 30mod tests { 31 use super::*; 32 use crate::svg::{Mode, svg}; 33 use std::collections::HashSet; 34 35 fn count(nodes: &[Node], n: &mut usize) { 36 for node in nodes { 37 match node { 38 Node::Rect(_) => *n += 1, 39 Node::Group(g) => count(&g.children, n), 40 } 41 } 42 } 43 44 fn markup(mascot: Mascot, mood: Mood) -> String { 45 svg(&scene(mascot, mood), "x", &Mode::Frame { t: 0.4, gaze: (0.0, 0.0) }) 46 } 47 48 #[test] 49 fn every_mascot_draws_every_mood_and_no_two_pictures_are_the_same() { 50 let mut seen = HashSet::new(); 51 for &mascot in Mascot::ALL { 52 for mood in Mood::ALL { 53 let mut rects = 0; 54 count(&scene(mascot, mood).nodes, &mut rects); 55 assert!(rects > 12, "{mascot:?} {mood:?}: {rects} rectangles"); 56 assert!(seen.insert(markup(mascot, mood)), "{mascot:?} {mood:?} repeats another picture"); 57 } 58 } 59 assert_eq!(seen.len(), Mascot::ALL.len() * Mood::ALL.len()); 60 } 61 62 #[test] 63 fn every_mascots_eyes_follow_the_pointer_in_the_moods_that_look() { 64 for &mascot in Mascot::ALL { 65 for mood in [Mood::Idle, Mood::Listening, Mood::Speaking] { 66 let a = svg(&scene(mascot, mood), "x", &Mode::Frame { t: 0.4, gaze: (1.0, 0.0) }); 67 let b = svg(&scene(mascot, mood), "x", &Mode::Frame { t: 0.4, gaze: (-1.0, 0.0) }); 68 assert_ne!(a, b, "{mascot:?} {mood:?}"); 69 } 70 } 71 } 72 73 #[test] 74 fn the_scenes_stay_inside_the_drawing_area() { 75 use crate::node::{Group, Node}; 76 fn walk(nodes: &[Node], f: &mut impl FnMut(&crate::node::Rect)) { 77 for n in nodes { 78 match n { 79 Node::Rect(r) => f(r), 80 Node::Group(Group { children, .. }) => walk(children, f), 81 } 82 } 83 } 84 let (x0, y0, w, h) = VIEW; 85 for &mascot in Mascot::ALL { 86 for mood in Mood::ALL { 87 walk(&scene(mascot, mood).nodes, &mut |r| { 88 let slack = 6.0; 89 assert!(r.x >= x0 - slack && r.x + r.w <= x0 + w + slack, "{mascot:?} {mood:?} {r:?}"); 90 assert!(r.y >= y0 - slack && r.y + r.h <= y0 + h + slack, "{mascot:?} {mood:?} {r:?}"); 91 }); 92 } 93 } 94 } 95}