use ratatui::prelude::*;
use ratatui::widgets::Paragraph;
use super::{Component, EventContext, RenderContext};
pub fn big_char(ch: char) -> [&'static str; 3] {
match ch {
'0' => ["█▀█", "█ █", "▀▀▀"],
'1' => ["▀█ ", " █ ", "▀▀▀"],
'2' => ["▀▀█", "█▀▀", "▀▀▀"],
'3' => ["▀▀█", " ▀█", "▀▀▀"],
'4' => ["█ █", "▀▀█", " ▀"],
'5' => ["█▀▀", "▀▀█", "▀▀▀"],
'6' => ["█▀▀", "█▀█", "▀▀▀"],
'7' => ["▀▀█", " █", " ▀"],
'8' => ["█▀█", "█▀█", "▀▀▀"],
'9' => ["█▀█", "▀▀█", "▀▀▀"],
'.' => [" ", "▄", " "],
':' => ["▄", " ", "▀"],
'-' => [" ", "▀▀▀", " "],
'/' => [" █", " █ ", "█ "],
'%' => ["█ █", " █ ", "█ █"],
' ' => [" ", " ", " "],
'A' => ["█▀█", "█▀█", "▀ ▀"],
'B' => ["█▀▄", "█▀█", "▀▀▀"],
'C' => ["█▀▀", "█ ", "▀▀▀"],
'D' => ["█▀▄", "█ █", "▀▀▀"],
'E' => ["█▀▀", "█▀▀", "▀▀▀"],
'F' => ["█▀▀", "█▀ ", "▀ "],
'G' => ["█▀▀", "█▀█", "▀▀▀"],
'H' => ["█ █", "█▀█", "▀ ▀"],
'I' => ["▀█▀", " █ ", "▀▀▀"],
'J' => [" █", " █", "▀▀▀"],
'K' => ["█ █", "█▀▄", "▀ ▀"],
'L' => ["█ ", "█ ", "▀▀▀"],
'M' => ["█▄█", "█ █", "▀ ▀"],
'N' => ["█▀█", "█ █", "▀ ▀"],
'O' => ["█▀█", "█ █", "▀▀▀"],
'P' => ["█▀█", "█▀▀", "▀ "],
'Q' => ["█▀█", "█▄█", "▀▀▀"],
'R' => ["█▀█", "█▀▄", "▀ ▀"],
'S' => ["█▀▀", "▀▀█", "▀▀▀"],
'T' => ["▀█▀", " █ ", " ▀ "],
'U' => ["█ █", "█ █", "▀▀▀"],
'V' => ["█ █", "█ █", " ▀ "],
'W' => ["█ █", "█ █", "▀▄▀"],
'X' => ["█ █", " █ ", "█ █"],
'Y' => ["█ █", " █ ", " ▀ "],
'Z' => ["▀▀█", " █ ", "▀▀▀"],
_ => ["▀▀▀", " ▀ ", " ▀ "],
}
}
pub fn big_char_width(ch: char) -> usize {
unicode_width::UnicodeWidthStr::width(big_char(ch)[0])
}
#[derive(Clone, Debug, PartialEq)]
pub enum BigTextMessage {
SetText(String),
SetColor(Option<Color>),
SetAlignment(Alignment),
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct BigTextState {
text: String,
color: Option<Color>,
#[cfg_attr(feature = "serialization", serde(skip))]
alignment: Alignment,
}
impl Default for BigTextState {
fn default() -> Self {
Self {
text: String::new(),
color: None,
alignment: Alignment::Center,
}
}
}
impl BigTextState {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
..Self::default()
}
}
pub fn with_color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
pub fn with_alignment(mut self, alignment: Alignment) -> Self {
self.alignment = alignment;
self
}
pub fn text(&self) -> &str {
&self.text
}
pub fn color(&self) -> Option<Color> {
self.color
}
pub fn alignment(&self) -> Alignment {
self.alignment
}
pub fn set_text(&mut self, text: impl Into<String>) {
self.text = text.into();
}
pub fn set_color(&mut self, color: Option<Color>) {
self.color = color;
}
pub fn set_alignment(&mut self, alignment: Alignment) {
self.alignment = alignment;
}
pub fn handle_event(&self, event: &crate::input::Event) -> Option<BigTextMessage> {
BigText::handle_event(self, event, &EventContext::default())
}
pub fn update(&mut self, msg: BigTextMessage) -> Option<()> {
BigText::update(self, msg)
}
pub fn dispatch_event(&mut self, event: &crate::input::Event) -> Option<()> {
BigText::dispatch_event(self, event, &EventContext::default())
}
}
fn build_row(text: &str, row: usize) -> String {
let mut result = String::new();
let chars: Vec<char> = text.chars().collect();
for (i, &ch) in chars.iter().enumerate() {
if i > 0 {
result.push(' ');
}
let glyph = big_char(ch.to_ascii_uppercase());
result.push_str(glyph[row]);
}
result
}
pub struct BigText;
impl Component for BigText {
type State = BigTextState;
type Message = BigTextMessage;
type Output = ();
fn init() -> Self::State {
BigTextState::default()
}
fn update(state: &mut Self::State, msg: Self::Message) -> Option<Self::Output> {
match msg {
BigTextMessage::SetText(text) => state.text = text,
BigTextMessage::SetColor(color) => state.color = color,
BigTextMessage::SetAlignment(alignment) => state.alignment = alignment,
}
None
}
fn view(state: &Self::State, ctx: &mut RenderContext<'_, '_>) {
crate::annotation::with_registry(|reg| {
reg.register(
ctx.area,
crate::annotation::Annotation::big_text("big_text")
.with_label(&state.text)
.with_disabled(ctx.disabled),
);
});
if ctx.area.height == 0 || ctx.area.width == 0 {
return;
}
let style = if ctx.disabled {
ctx.theme.disabled_style()
} else if let Some(color) = state.color {
Style::default().fg(color)
} else {
ctx.theme.normal_style()
};
let lines: Vec<Line<'_>> = (0..3)
.map(|row| {
let row_text = build_row(&state.text, row);
Line::from(Span::styled(row_text, style))
})
.collect();
let content_height: u16 = 3;
let vertical_offset = ctx.area.height.saturating_sub(content_height) / 2;
let render_area = Rect::new(
ctx.area.x,
ctx.area.y + vertical_offset,
ctx.area.width,
content_height.min(ctx.area.height.saturating_sub(vertical_offset)),
);
if render_area.height > 0 {
let paragraph = Paragraph::new(lines).alignment(state.alignment);
ctx.frame.render_widget(paragraph, render_area);
}
}
}
#[cfg(test)]
mod tests;