use std::borrow::Cow;
use std::io::Write;
use crossterm::{
queue,
style::{Attribute, Color, Print, ResetColor, SetAttribute, SetForegroundColor},
};
use crate::decode;
use crate::{Component, Renderer};
pub struct TextComponent {
pub text: String,
pub reveal: f64,
pub visible: bool,
}
impl TextComponent {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
reveal: 0.0,
visible: true,
}
}
}
#[cfg(not(tarpaulin_include))]
impl Component for TextComponent {
fn render<W: Write>(&self, renderer: &mut Renderer<W>) -> std::io::Result<u16> {
if !self.visible || self.reveal <= 0.0 {
return Ok(0);
}
renderer.with_panel(|writer, _panel, rng| {
let display: Cow<str> = if self.reveal >= 1.0 {
Cow::Borrowed(&self.text)
} else {
let revealed = (self.text.chars().count() as f64 * self.reveal).round() as usize;
Cow::Owned(decode::decode_frame(&self.text, revealed, rng))
};
queue!(
writer,
SetForegroundColor(Color::White),
SetAttribute(Attribute::Bold),
Print(&*display),
SetAttribute(Attribute::Reset),
ResetColor
)?;
Ok(1)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::LayoutPanel;
use crate::test_helpers::seeded_test_renderer;
fn panel(row: u16) -> LayoutPanel {
LayoutPanel::centered(80, 20, row)
}
#[test]
fn render_skips_when_invisible() {
let mut t = TextComponent::new("hi");
t.visible = false;
let mut renderer = seeded_test_renderer(123);
renderer.draw(&t, panel(0)).unwrap();
assert!(renderer.writer.is_empty());
}
#[test]
fn render_skips_when_reveal_zero() {
let t = TextComponent::new("hi");
let mut renderer = seeded_test_renderer(123);
renderer.draw(&t, panel(0)).unwrap();
assert!(renderer.writer.is_empty());
}
#[test]
fn render_full_reveal_writes_text() {
let mut t = TextComponent::new("hello");
t.reveal = 1.0;
let mut renderer = seeded_test_renderer(123);
renderer.draw(&t, panel(5)).unwrap();
let s = String::from_utf8(renderer.writer).unwrap();
assert!(s.contains("hello"));
}
#[test]
fn render_partial_writes_output() {
let mut t = TextComponent::new("hello world");
t.reveal = 0.3;
let mut renderer = seeded_test_renderer(123);
renderer.draw(&t, panel(5)).unwrap();
assert!(
!renderer.writer.is_empty(),
"partial reveal should emit terminal sequences"
);
}
#[test]
fn new_sets_visible_defaults() {
let t = TextComponent::new("q");
assert!(t.visible);
assert_eq!(t.reveal, 0.0);
assert_eq!(t.text, "q");
}
}