dental_notation/display/
cli.rs

1use super::JawKind;
2use crate::display::ToothDisplay;
3use crate::{NotationKind, QuadrantKind, Tooth, ToothType};
4use alloc::format;
5use alloc::string::String;
6use owo_colors::{OwoColorize, Style};
7
8/// Formatter used to display a complete jaw in a CLI.
9pub struct ToothCliFormatter;
10
11impl ToothCliFormatter {
12    fn format_tooth(
13        tooth: &Tooth,
14        notation: &NotationKind,
15        selected_value: &Option<Tooth>,
16    ) -> String {
17        let tooth_label = tooth.to(notation);
18
19        let mut style = Style::new();
20        style = match &tooth.get_type() {
21            ToothType::Canine => style.yellow(),
22            ToothType::Incisor => style.green(),
23            ToothType::Premolar => style.cyan(),
24            ToothType::Molar => style.blue(),
25        };
26
27        if let Some(t) = selected_value {
28            if t == tooth {
29                style = style.reversed();
30            }
31        }
32        format!(" {}", tooth_label.style(style))
33    }
34
35    fn format_quadrant(
36        notation: &NotationKind,
37
38        permanent: bool,
39        quadrant: QuadrantKind,
40        selected_value: &Option<Tooth>,
41    ) -> String {
42        let max = Tooth::quadrant_max(permanent);
43        let format_tooth = |i| {
44            let tooth = Tooth::new(i, quadrant, permanent).unwrap();
45            ToothCliFormatter::format_tooth(&tooth, notation, selected_value)
46        };
47        if quadrant == QuadrantKind::TopLeft || quadrant == QuadrantKind::BottomLeft {
48            (1..=max).rev().map(format_tooth).collect()
49        } else {
50            (1..=max).map(format_tooth).collect()
51        }
52    }
53
54    fn format_jaw(
55        notation: &NotationKind,
56        permanent: bool,
57        jaw: JawKind,
58        selected_value: &Option<Tooth>,
59    ) -> String {
60        let (quadrant_1, quadrant_2) = match jaw {
61            JawKind::Top => (QuadrantKind::TopLeft, QuadrantKind::TopRight),
62            JawKind::Bottom => (QuadrantKind::BottomLeft, QuadrantKind::BottomRight),
63        };
64        format!(
65            "  {} |{}",
66            ToothCliFormatter::format_quadrant(notation, permanent, quadrant_1, selected_value),
67            ToothCliFormatter::format_quadrant(notation, permanent, quadrant_2, selected_value)
68        )
69    }
70
71    fn calculate_separator_len(notation: &NotationKind, permanent: bool) -> u8 {
72        let item_count = Tooth::quadrant_max(permanent);
73        let item_width = match (notation, permanent) {
74            (NotationKind::Iso, _) => 2,
75            (NotationKind::Uns, true) => 2,
76            (NotationKind::Uns, false) => 1,
77            (NotationKind::Alphanumeric, _) => 3,
78        };
79        (item_width + 1) * (item_count * 2 + 1)
80    }
81}
82
83impl ToothDisplay for ToothCliFormatter {
84    fn format(notation: &NotationKind, permanent: bool, selected_value: &Option<Tooth>) -> String {
85        let jaw_len = ToothCliFormatter::calculate_separator_len(notation, permanent);
86        let mut result = String::with_capacity((jaw_len * 4).into());
87        result.push_str(&ToothCliFormatter::format_jaw(
88            notation,
89            permanent,
90            JawKind::Top,
91            selected_value,
92        ));
93        result.push_str("\nR ");
94        result.push_str(&"-".repeat(jaw_len.into()));
95        result.push_str(" L\n");
96        result.push_str(&ToothCliFormatter::format_jaw(
97            notation,
98            permanent,
99            JawKind::Bottom,
100            selected_value,
101        ));
102
103        result
104    }
105}