1use std::borrow::Cow;
4
5use super::GlyphMode;
6use crate::geometry::{Rect, Size};
7use crate::style::CellStyle;
8use crate::text;
9use crate::widget::{MeasureCx, PaintCx, Widget};
10
11const GAP: u16 = 2;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct GlyphSample {
33 mode: GlyphMode,
34 keys: Vec<String>,
35}
36
37impl GlyphSample {
38 pub const KEYS: [&str; 4] = ["folder", "check", "search", "settings"];
41
42 #[must_use]
44 pub fn new(mode: GlyphMode) -> Self {
45 Self { mode, keys: Self::KEYS.iter().map(|key| (*key).to_owned()).collect() }
46 }
47
48 #[must_use]
50 pub fn keys(mut self, keys: impl IntoIterator<Item = impl Into<String>>) -> Self {
51 self.keys = keys.into_iter().map(Into::into).collect();
52 self
53 }
54
55 fn glyphs<'a>(&'a self, icons: &'a super::Icons) -> impl Iterator<Item = Cow<'a, str>> + 'a {
56 self.keys.iter().filter_map(|key| icons.glyphs(key)).map(|glyphs| Cow::Borrowed(glyphs.for_mode(self.mode)))
57 }
58}
59
60impl<Msg: 'static> Widget<Msg> for GlyphSample {
61 fn measure(&self, cx: &mut MeasureCx<'_>, available: Size) -> Size {
62 let widths: Vec<u16> = self.glyphs(cx.env().icons()).map(|glyph| text::width(&glyph)).collect();
63 if widths.is_empty() {
64 return Size::new(0, 0);
65 }
66 let gaps = u16::try_from(widths.len() - 1).unwrap_or(u16::MAX).saturating_mul(GAP);
67 let width = widths.into_iter().fold(gaps, u16::saturating_add);
68 Size::new(width, 1).min(available)
69 }
70
71 fn paint(&self, cx: &mut PaintCx<'_>, area: Rect) {
72 if area.is_empty() {
73 return;
74 }
75 let glyphs: Vec<String> = self.glyphs(cx.env().icons()).map(Cow::into_owned).collect();
76 let style = CellStyle::fg(cx.color("text"));
77 let right = area.x + i32::from(area.width);
78 let mut x = area.x;
79 for glyph in glyphs {
80 let room = crate::geometry::clamp_u16(right - x);
81 let width = text::width(&glyph);
82 if width > room {
84 break;
85 }
86 cx.text(x, area.y, &glyph, style, room);
87 x += i32::from(width + GAP);
88 }
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::runtime::{App, Command, Harness};
96 use crate::widget::View;
97
98 struct Demo(Vec<GlyphSample>);
99
100 impl App for Demo {
101 type Msg = ();
102 fn update(&mut self, _: ()) -> Command<()> {
103 Command::none()
104 }
105 fn view(&self, ui: &mut View<'_, ()>) {
106 ui.column(|ui| {
107 for sample in &self.0 {
108 ui.add(sample.clone());
109 }
110 });
111 }
112 }
113
114 #[test]
115 fn draws_each_mode_whatever_the_application_draws_in() {
116 let samples = vec![
117 GlyphSample::new(GlyphMode::Nerd),
118 GlyphSample::new(GlyphMode::Unicode),
119 GlyphSample::new(GlyphMode::Ascii),
120 ];
121 let mut h = Harness::new(Demo(samples), 20, 3);
122 h.set_glyph_mode(GlyphMode::Ascii);
123 assert_eq!(h.screen(), "\u{f07b} \u{f00c} \u{f002} \u{f013}\n■ ✓ ⌕ ▤\n# v / *\n");
124 assert_eq!(h.fg(0, 1), h.env().theme().color("text"));
125 }
126
127 #[test]
128 fn chosen_keys_skip_unknown_ones_and_never_cut_a_glyph() {
129 let sample = GlyphSample::new(GlyphMode::Unicode).keys(["check", "no-such-icon", "folder"]);
130 let h = Harness::new(Demo(vec![sample.clone()]), 20, 1);
131 assert_eq!(h.screen(), "✓ ■\n");
132 let h = Harness::new(Demo(vec![sample]), 3, 1);
133 assert_eq!(h.screen(), "✓\n");
134 }
135}