whiskers.git / web / cat / src / art_ginger.rs

The ginger cat, ported from the Android app's drawing.

Differences from the app, all in how motion is expressed, none in the poses: the tail wag slides the tail's upright instead of stretching it, and the blink scales the eyes about their middle.

7use whiskers_art::decor::{INK, spiral};
8use crate::mood::Mood;
9use crate::motion::Motion;
10use crate::node::{Color, Look, Node, eyes, moving, r, ra};
12const FUR: Color = Color::hex(0xDE886D);
13const INNER_EAR: Color = Color::hex(0xF2A7B0);
14const WHISKER: Color = Color::hex(0xF5EDE6);
15
16pub fn figure(mood: Mood) -> Vec<Node> {
17    if mood == Mood::Sleeping {
18        return sleeping();
19    }
20    standing(mood)
21}
22
23fn standing(mood: Mood) -> Vec<Node> {
24    let mut out = Vec::new();
25    // Legs.
26    for x in [3.0, 5.0, 9.0, 11.0] {
27        out.push(r(x, 13.0, 1.0, 2.0, FUR));
28    }
29    // Tail: the elbow stays, the upright wags.
30    out.push(r(13.0, 12.0, 2.0, 1.0, FUR));
31    out.push(moving(Motion::TailWag, (0.0, 0.0), vec![r(14.0, 10.5, 1.0, 1.5, FUR)]));
32    // Ears: bottoms fixed on the head, tops perk up when listening.
33    let lift = if mood == Mood::Listening { -1.0 } else { 0.0 };
34    for (x, top) in [(2.0, 4.0), (3.0, 3.0), (11.0, 3.0), (12.0, 4.0)] {
35        out.push(r(x, top + lift, 1.0, 6.0 - (top + lift), FUR));
36    }
37    out.push(r(3.0, 4.0 + lift, 1.0, 2.0 - lift, INNER_EAR));
38    out.push(r(11.0, 4.0 + lift, 1.0, 2.0 - lift, INNER_EAR));
39    // Arms, then the torso over them.
40    out.push(r(0.0, 9.0, 2.0, 2.0, FUR));
41    out.push(r(13.0, 9.0, 2.0, 2.0, FUR));
42    out.push(r(2.0, 6.0, 11.0, 7.0, FUR));
43    // Nose and whiskers.
44    out.push(r(7.0, 9.5, 1.0, 1.0, INNER_EAR));
45    for y in [8.0, 11.3] {
46        out.push(r(-1.0, y, 4.0, 0.35, WHISKER));
47        out.push(r(12.0, y, 4.0, 0.35, WHISKER));
48    }
49    // Eyes.
50    match mood {
51        Mood::Dizzy => {
52            out.extend(spiral(4.5, 9.0, INK));
53            out.extend(spiral(10.5, 9.0, INK));
54        }
55        _ => {
56            let (height, top, look) = match mood {
57                Mood::Listening => (3.0, 7.5, Look::Follow),
58                Mood::Thinking => (2.0, 8.0, Look::Fixed(0.8, -0.8)),
59                _ => (2.0, 8.0, Look::Follow),
60            };
61            let centre = top + height / 2.0;
62            let pupil = |x| moving(Motion::Blink, (x, centre), vec![r(x, top, 1.0, height, INK)]);
63            out.push(eyes(look, vec![pupil(4.0), pupil(10.0)]));
64            if mood == Mood::Speaking {
65                out.push(moving(Motion::MouthFlap, (7.5, 10.8), vec![ra(6.6, 10.8, 1.8, 1.0, INK, 0.85)]));
66            }
67        }
68    }
69    out
70}
71
72fn sleeping() -> Vec<Node> {
73    let mut out = Vec::new();
74    for x in [3.0, 5.0, 9.0, 11.0] {
75        out.push(r(x, 9.0, 1.0, 1.0, FUR));
76    }
77    out.push(r(14.0, 12.5, 2.0, 1.0, FUR)); // tail
78    out.extend([r(2.0, 9.0, 1.0, 1.0, FUR), r(3.0, 8.0, 1.0, 2.0, FUR), r(11.0, 8.0, 1.0, 2.0, FUR), r(12.0, 9.0, 1.0, 1.0, FUR)]);
79    out.extend([r(3.0, 9.0, 1.0, 1.0, INNER_EAR), r(11.0, 9.0, 1.0, 1.0, INNER_EAR)]);
80    out.push(r(1.0, 10.0, 13.0, 5.0, FUR));
81    out.push(r(-1.0, 13.0, 2.0, 2.0, FUR));
82    out.push(r(14.0, 13.0, 2.0, 2.0, FUR));
83    out.push(r(7.0, 12.6, 1.0, 1.0, INNER_EAR));
84    out.push(r(3.5, 12.0, 2.0, 0.4, INK));
85    out.push(r(9.5, 12.0, 2.0, 0.4, INK));
86    for y in [11.5, 14.0] {
87        out.push(r(-1.0, y, 3.0, 0.35, WHISKER));
88        out.push(r(13.0, y, 4.0, 0.35, WHISKER));
89    }
90    out
91}