use crate::component::Component;
use crate::context::{RenderContext, UseTheme};
use crate::event::EventHandler;
use crate::i18n::TextDirection;
use crate::layout::Rect;
use crate::render::Renderer;
use anyhow::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextAlign {
Start,
End,
Center,
ForceLeft,
ForceRight,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PhysicalAlign {
Left,
Center,
Right,
}
impl TextAlign {
pub fn resolve(&self, direction: TextDirection) -> PhysicalAlign {
match (self, direction) {
(TextAlign::Start, TextDirection::LeftToRight) => PhysicalAlign::Left,
(TextAlign::Start, TextDirection::RightToLeft) => PhysicalAlign::Right,
(TextAlign::End, TextDirection::LeftToRight) => PhysicalAlign::Right,
(TextAlign::End, TextDirection::RightToLeft) => PhysicalAlign::Left,
(TextAlign::Center, _) => PhysicalAlign::Center,
(TextAlign::ForceLeft, _) => PhysicalAlign::Left,
(TextAlign::ForceRight, _) => PhysicalAlign::Right,
}
}
}
pub struct Text {
pub(crate) content: String,
pub(crate) style: String,
pub(crate) align: TextAlign,
pub(crate) dirty: bool,
}
impl Text {
pub fn new(content: impl Into<String>) -> Self {
Text {
content: content.into(),
style: String::new(),
align: TextAlign::Start,
dirty: true,
}
}
pub fn with_style(mut self, style: impl Into<String>) -> Self {
self.style = style.into();
self.dirty = true;
self
}
pub fn with_align(mut self, align: TextAlign) -> Self {
self.align = align;
self.dirty = true;
self
}
pub fn set_text(&mut self, content: impl Into<String>) {
self.content = content.into();
self.dirty = true;
}
pub fn text(&self) -> &str {
&self.content
}
}
impl EventHandler for Text {}
impl Component for Text {
fn render(&mut self, renderer: &mut Renderer, bounds: Rect, ctx: &RenderContext) -> Result<()> {
if self.content.is_empty() {
return Ok(());
}
let text_direction = self.use_text_direction(ctx);
let physical_align = self.align.resolve(text_direction);
let text_len = self.content.len() as u16;
let x = match physical_align {
PhysicalAlign::Left => bounds.x,
PhysicalAlign::Center => {
let offset = (bounds.width.saturating_sub(text_len)) / 2;
bounds.x.saturating_add(offset)
}
PhysicalAlign::Right => {
let offset = bounds.width.saturating_sub(text_len);
bounds.x.saturating_add(offset)
}
};
renderer.move_cursor(x, bounds.y)?;
if self.style.is_empty() {
renderer.write_text(&self.content)?;
} else {
renderer.write_styled(&self.content, &self.style)?;
}
self.dirty = false;
Ok(())
}
fn min_size(&self) -> (u16, u16) {
(self.content.len() as u16, 1)
}
fn mark_dirty(&mut self) {
self.dirty = true;
}
fn is_dirty(&self) -> bool {
self.dirty
}
fn name(&self) -> &str {
"Text"
}
}
pub mod styles {
pub const BOLD: &str = "\x1b[1m";
pub const DIM: &str = "\x1b[2m";
pub const ITALIC: &str = "\x1b[3m";
pub const UNDERLINE: &str = "\x1b[4m";
pub const BLACK: &str = "\x1b[30m";
pub const RED: &str = "\x1b[31m";
pub const GREEN: &str = "\x1b[32m";
pub const YELLOW: &str = "\x1b[33m";
pub const BLUE: &str = "\x1b[34m";
pub const MAGENTA: &str = "\x1b[35m";
pub const CYAN: &str = "\x1b[36m";
pub const WHITE: &str = "\x1b[37m";
pub const BG_BLACK: &str = "\x1b[40m";
pub const BG_WHITE: &str = "\x1b[47m";
pub const BG_BLUE: &str = "\x1b[44m";
}
#[cfg(test)]
mod tests {
use super::*;
use crate::i18n::TextDirection;
#[test]
fn test_text_align_resolve_ltr() {
assert_eq!(
TextAlign::Start.resolve(TextDirection::LeftToRight),
PhysicalAlign::Left
);
assert_eq!(
TextAlign::End.resolve(TextDirection::LeftToRight),
PhysicalAlign::Right
);
assert_eq!(
TextAlign::Center.resolve(TextDirection::LeftToRight),
PhysicalAlign::Center
);
}
#[test]
fn test_text_align_resolve_rtl() {
assert_eq!(
TextAlign::Start.resolve(TextDirection::RightToLeft),
PhysicalAlign::Right
);
assert_eq!(
TextAlign::End.resolve(TextDirection::RightToLeft),
PhysicalAlign::Left
);
assert_eq!(
TextAlign::Center.resolve(TextDirection::RightToLeft),
PhysicalAlign::Center
);
}
#[test]
fn test_text_align_force_ignores_direction() {
assert_eq!(
TextAlign::ForceLeft.resolve(TextDirection::RightToLeft),
PhysicalAlign::Left
);
assert_eq!(
TextAlign::ForceRight.resolve(TextDirection::LeftToRight),
PhysicalAlign::Right
);
}
}