ascii_osd_hud/
hud.rs

1use enum_map::{enum_map, Enum, EnumMap};
2
3use crate::altitude::Altitude;
4use crate::aoa::AOA;
5use crate::battery::Battery;
6use crate::drawable::{Align, Drawable};
7use crate::g_force::GForce;
8use crate::heading_tape::HeadingTape;
9use crate::height::Height;
10use crate::note::note;
11use crate::pitch_ladder::Pitchladder;
12use crate::rssi::RSSI;
13use crate::speed::Speed;
14use crate::speed_vector::SpeedVector;
15use crate::steerpoint::Steerpoint;
16use crate::steerpoint_vector::SteerpointVector;
17use crate::symbol::SymbolTable;
18use crate::telemetry::Telemetry;
19use crate::vario::Vario;
20use crate::{AspectRatio, PixelRatio};
21
22#[derive(Enum)]
23pub enum Displayable {
24    // Bottom
25    Pitchladder,
26
27    // Center
28    SpeedVector,
29    SteerpointVector,
30
31    // TopLeft
32    RSSI,
33
34    // Top
35    HeadingTape,
36
37    // TopRight,
38    Battery,
39
40    // Left
41    Speed,
42    AOA,
43    GForce,
44
45    // Right
46    Altitude,
47    Vario,
48
49    // BottomRight
50    Steerpoint,
51    Height,
52}
53
54pub struct HUD {
55    altitude: Altitude,
56    aoa: AOA,
57    battery: Battery,
58    g_force: GForce,
59    heading_tape: HeadingTape,
60    height: Height,
61    pitch_ladder: Pitchladder,
62    rssi: RSSI,
63    speed: Speed,
64    vario: Vario,
65    speed_vector: SpeedVector,
66    steerpoint: Steerpoint,
67    steerpoint_vector: SteerpointVector,
68    aligns: EnumMap<Displayable, Option<Align>>,
69}
70
71impl HUD {
72    pub fn new(symbols: &SymbolTable, fov: u8, pixel: PixelRatio, aspect: AspectRatio) -> Self {
73        let fov = core::cmp::max(10, fov); // avoid divide zero
74        HUD {
75            altitude: Altitude::default(),
76            aoa: AOA::new(&symbols),
77            battery: Battery::new(&symbols),
78            g_force: GForce::new(&symbols),
79            heading_tape: HeadingTape::new(&symbols),
80            height: Height::default(),
81            pitch_ladder: Pitchladder::new(&symbols, fov, pixel, aspect),
82            rssi: RSSI::new(&symbols),
83            speed: Speed::default(),
84            vario: Vario::default(),
85            speed_vector: SpeedVector::new(&symbols, fov, aspect),
86            steerpoint_vector: SteerpointVector::new(&symbols, fov, aspect),
87            steerpoint: Steerpoint::new(&symbols),
88            aligns: enum_map! {
89                Displayable::Altitude => Some(Align::Right),
90                Displayable::AOA => Some(Align::Left),
91                Displayable::Battery => Some(Align::TopRight),
92                Displayable::GForce => Some(Align::Left),
93                Displayable::HeadingTape => Some(Align::Top),
94                Displayable::Height => Some(Align::Bottom),
95                Displayable::Pitchladder => Some(Align::Center),
96                Displayable::RSSI => Some(Align::TopLeft),
97                Displayable::Speed => Some(Align::Left),
98                Displayable::Vario => Some(Align::Right),
99                Displayable::SpeedVector => Some(Align::Center),
100                Displayable::Steerpoint => Some(Align::BottomRight),
101                Displayable::SteerpointVector => Some(Align::Center),
102            },
103        }
104    }
105
106    fn to_drawable<'b, B: AsMut<[u8]>>(&'b self, displayable: Displayable) -> &'b dyn Drawable<B> {
107        match displayable {
108            Displayable::Altitude => &self.altitude,
109            Displayable::AOA => &self.aoa,
110            Displayable::Battery => &self.battery,
111            Displayable::GForce => &self.g_force,
112            Displayable::HeadingTape => &self.heading_tape,
113            Displayable::Height => &self.height,
114            Displayable::Pitchladder => &self.pitch_ladder,
115            Displayable::RSSI => &self.rssi,
116            Displayable::Speed => &self.speed,
117            Displayable::Vario => &self.vario,
118            Displayable::SpeedVector => &self.speed_vector,
119            Displayable::Steerpoint => &self.steerpoint,
120            Displayable::SteerpointVector => &self.steerpoint_vector,
121        }
122    }
123
124    pub fn draw<'b, B: AsMut<[u8]>>(
125        &self,
126        telemetry: &Telemetry<'b>,
127        output: &'b mut [B],
128    ) -> &'b [B] {
129        output.iter_mut().for_each(|line| {
130            for x in line.as_mut() {
131                if *x == b' ' {
132                    *x = 0
133                } else if *x > 0 {
134                    *x = b' '
135                }
136            }
137        });
138        let output_len = output.len();
139        let mut indexes: EnumMap<Align, usize> = EnumMap::new();
140        for (display, align_option) in self.aligns.iter() {
141            let align = match align_option {
142                Some(align) => *align,
143                None => continue,
144            };
145            let drawable: &dyn Drawable<B> = self.to_drawable(display);
146            let region = match align {
147                Align::Top | Align::TopLeft | Align::TopRight => &mut output[indexes[align]..],
148                Align::Bottom | Align::BottomLeft | Align::BottomRight => {
149                    #[cfg(test)]
150                    println!("{}", indexes[align]);
151                    &mut output[..output_len - indexes[align]]
152                }
153                Align::Left | Align::Right => &mut output[output_len / 2 + indexes[align]..],
154                _ => output,
155            };
156            indexes[align] += drawable.draw(&telemetry, region);
157        }
158
159        indexes[Align::Center] = 2;
160        let region = &mut output[output_len / 2 + indexes[Align::Left]..];
161        indexes[Align::Left] += note(telemetry.notes.left, Align::Left, region);
162        let region = &mut output[output_len / 2 + indexes[Align::Center]..];
163        indexes[Align::Center] += note(telemetry.notes.center, Align::Center, region);
164        let region = &mut output[output_len / 2 + indexes[Align::Right]..];
165        indexes[Align::Right] += note(telemetry.notes.right, Align::Right, region);
166        output
167    }
168}
169
170#[cfg(test)]
171mod test {
172    use crate::symbol::default_symbol_table;
173    use crate::telemetry::{Attitude, Notes, SphericalCoordinate, Steerpoint, Telemetry};
174    use crate::test_utils::{fill_edge, to_utf8_string};
175    use crate::{AspectRatio, PixelRatio};
176
177    use super::HUD;
178
179    fn default_telemetry() -> Telemetry<'static> {
180        Telemetry {
181            altitude: 1000,
182            attitude: Attitude {
183                pitch: 10,
184                roll: 10,
185            },
186            heading: 10,
187            aoa: 31,
188            g_force: 11,
189            height: 99,
190            notes: Notes {
191                left: "MAN",
192                ..Default::default()
193            },
194            rssi: 100,
195            vario: 100,
196            speed_vector: SphericalCoordinate {
197                rho: 100, // speed
198                theta: 10,
199                phi: -5,
200            },
201            steerpoint: Steerpoint {
202                coordinate: SphericalCoordinate {
203                    rho: 47,
204                    theta: -10,
205                    phi: -14,
206                },
207                ..Default::default()
208            },
209            ..Default::default()
210        }
211    }
212
213    #[test]
214    fn test_hud() {
215        let mut buffer = [[0u8; 30]; 16];
216        let symbols = default_symbol_table();
217        let px_ratio = pixel_ratio!(16:30);
218        let hud = HUD::new(&symbols, 150, px_ratio, aspect_ratio!(16:9));
219        let telemetry = default_telemetry();
220        hud.draw(&telemetry, &mut buffer);
221        fill_edge(&mut buffer);
222
223        let expected = "⏉100    000 . 010 . 020   β100\
224                        .        ╵     ^             .\
225                        .                            .\
226                        .                            .\
227                        .                            .\
228                        ▔⎺⎺⎻⎻─⎼⎼⎽⎽▁                  .\
229                        .          ▔▔⎺⎺⎻──⎼⎼⎽▁▁      .\
230                        .                      ▔▔⎺⎻⎻──\
231                        . 100                     1000\
232                        ⍺  ⒊1            ⏂         100\
233                        G  ⒈1                        .\
234                        MAN          ☐               .\
235                        .                            .\
236                        .                       0/HOME\
237                        .                         ⒋7NM\
238                        .             99      00:02:49";
239        assert_eq!(expected, to_utf8_string(&buffer));
240    }
241}