blizz_ui/components/
text.rs1use std::borrow::Cow;
2use std::io::Write;
3
4use crossterm::{
5 queue,
6 style::{Attribute, Color, Print, ResetColor, SetAttribute, SetForegroundColor},
7};
8
9use crate::decode;
10use crate::{Component, Renderer};
11
12pub struct TextComponent {
14 pub text: String,
15 pub reveal: f64,
16 pub visible: bool,
17}
18
19impl TextComponent {
20 pub fn new(text: impl Into<String>) -> Self {
21 Self {
22 text: text.into(),
23 reveal: 0.0,
24 visible: true,
25 }
26 }
27}
28
29#[cfg(not(tarpaulin_include))]
30impl Component for TextComponent {
31 fn render<W: Write>(&self, renderer: &mut Renderer<W>) -> std::io::Result<u16> {
32 if !self.visible || self.reveal <= 0.0 {
33 return Ok(0);
34 }
35 renderer.with_panel(|writer, _panel, rng| {
36 let display: Cow<str> = if self.reveal >= 1.0 {
37 Cow::Borrowed(&self.text)
38 } else {
39 let revealed = (self.text.chars().count() as f64 * self.reveal).round() as usize;
40 Cow::Owned(decode::decode_frame(&self.text, revealed, rng))
41 };
42 queue!(
43 writer,
44 SetForegroundColor(Color::White),
45 SetAttribute(Attribute::Bold),
46 Print(&*display),
47 SetAttribute(Attribute::Reset),
48 ResetColor
49 )?;
50 Ok(1)
51 })
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use crate::LayoutPanel;
59 use crate::test_helpers::seeded_test_renderer;
60
61 fn panel(row: u16) -> LayoutPanel {
62 LayoutPanel::centered(80, 20, row)
63 }
64
65 #[test]
66 fn render_skips_when_invisible() {
67 let mut t = TextComponent::new("hi");
68 t.visible = false;
69 let mut renderer = seeded_test_renderer(123);
70 renderer.draw(&t, panel(0)).unwrap();
71 assert!(renderer.writer.is_empty());
72 }
73
74 #[test]
75 fn render_skips_when_reveal_zero() {
76 let t = TextComponent::new("hi");
77 let mut renderer = seeded_test_renderer(123);
78 renderer.draw(&t, panel(0)).unwrap();
79 assert!(renderer.writer.is_empty());
80 }
81
82 #[test]
83 fn render_full_reveal_writes_text() {
84 let mut t = TextComponent::new("hello");
85 t.reveal = 1.0;
86 let mut renderer = seeded_test_renderer(123);
87 renderer.draw(&t, panel(5)).unwrap();
88 let s = String::from_utf8(renderer.writer).unwrap();
89 assert!(s.contains("hello"));
90 }
91
92 #[test]
93 fn render_partial_writes_output() {
94 let mut t = TextComponent::new("hello world");
95 t.reveal = 0.3;
96 let mut renderer = seeded_test_renderer(123);
97 renderer.draw(&t, panel(5)).unwrap();
98 assert!(
99 !renderer.writer.is_empty(),
100 "partial reveal should emit terminal sequences"
101 );
102 }
103
104 #[test]
105 fn new_sets_visible_defaults() {
106 let t = TextComponent::new("q");
107 assert!(t.visible);
108 assert_eq!(t.reveal, 0.0);
109 assert_eq!(t.text, "q");
110 }
111}