use crate::{error::{FtuiError, FtuiResult}, util::ansi};
use bitflags::bitflags;
use unicode_segmentation::UnicodeSegmentation;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TextFlags: u32 {
const NONE = 1 << 0;
const ALIGN_RIGHT = 1 << 1;
const ALIGN_MIDDLE = 1 << 2;
const ALIGN_BOTTOM = 1 << 3;
const COLOR_BACK = 1 << 4;
const COLOR_BLACK = 1 << 6;
const COLOR_RED = 1 << 7;
const COLOR_GREEN = 1 << 8;
const COLOR_YELLOW = 1 << 9;
const COLOR_BLUE = 1 << 10;
const COLOR_MAGENTA = 1 << 11;
const COLOR_CYAN = 1 << 12;
const COLOR_WHITE = 1 << 13;
const COLOR_BLACK_BACK =
Self::COLOR_BLACK.bits() | Self::COLOR_BACK.bits();
const COLOR_RED_BACK =
Self::COLOR_RED.bits() | Self::COLOR_BACK.bits();
const COLOR_GREEN_BACK =
Self::COLOR_GREEN.bits() | Self::COLOR_BACK.bits();
const COLOR_YELLOW_BACK =
Self::COLOR_YELLOW.bits() | Self::COLOR_BACK.bits();
const COLOR_BLUE_BACK =
Self::COLOR_BLUE.bits() | Self::COLOR_BACK.bits();
const COLOR_MAGENTA_BACK =
Self::COLOR_MAGENTA.bits() | Self::COLOR_BACK.bits();
const COLOR_CYAN_BACK =
Self::COLOR_CYAN.bits() | Self::COLOR_BACK.bits();
const COLOR_WHITE_BACK =
Self::COLOR_WHITE.bits() | Self::COLOR_BACK.bits();
const STYLE_BOLD = 1 << 14;
const STYLE_DIM = 1 << 15;
const STYLE_ITALIC = 1 << 16;
const STYLE_UNDER = 1 << 17;
const STYLE_STRIKE = 1 << 18;
}
}
impl Default for TextFlags {
fn default() -> Self {
Self::NONE
}
}
#[derive(Debug, Clone)]
pub struct Text {
label: String,
len: usize,
id: u16,
line: u16,
flags: TextFlags,
pos: u16,
style: Vec<&'static str>,
}
impl PartialEq for Text {
fn eq(&self, others: &Self) -> bool {
self.label == others.label &&
self.flags == others.flags
}
}
impl Text {
pub fn new(label: &str, flags: impl Into<Option<TextFlags>>) -> FtuiResult<Self> {
let flags = flags.into().unwrap_or(TextFlags::NONE);
Self::ensure_compatible_flags(&flags)?;
Ok(Text {
label: label.to_string(),
len: label.graphemes(true).count(),
id: 0,
line: 0,
flags,
pos: 0,
style: Self::resolve_style(flags),
})
}
pub(crate) fn with_id(
label: &str, flags: impl Into<Option<TextFlags>>, id: u16
) -> FtuiResult<Self> {
let mut text = Text::new(label, flags)?;
text.set_id(id);
Ok(text)
}
pub(crate) fn ensure_compatible_flags(flags: &TextFlags) -> FtuiResult<()> {
if *flags == TextFlags::NONE {
return Ok(());
}
if flags.contains(TextFlags::NONE) && *flags != TextFlags::NONE {
return Err(FtuiError::TextFlagNoneWithOther);
}
if flags
.intersection(
TextFlags::COLOR_BLACK |
TextFlags::COLOR_RED |
TextFlags::COLOR_GREEN |
TextFlags::COLOR_YELLOW |
TextFlags::COLOR_BLUE |
TextFlags::COLOR_MAGENTA |
TextFlags::COLOR_CYAN |
TextFlags::COLOR_WHITE)
.bits()
.count_ones() > 1
{
return Err(FtuiError::TextFlagMultipleColor);
}
Ok(())
}
#[inline]
fn color_f_or_b(flags: TextFlags, b: &'static str, f: &'static str) -> &'static str {
if flags.contains(TextFlags::COLOR_BACK) { b } else { f }
}
fn resolve_color(flags: TextFlags) -> Option<&'static str> {
if flags.contains(TextFlags::COLOR_BLACK) {
Some(Self::color_f_or_b(flags, ansi::ESC_BLACK_B, ansi::ESC_BLACK_F))
} else if flags.contains(TextFlags::COLOR_RED) {
Some(Self::color_f_or_b(flags, ansi::ESC_RED_B, ansi::ESC_RED_F))
} else if flags.contains(TextFlags::COLOR_GREEN) {
Some(Self::color_f_or_b(flags, ansi::ESC_GREEN_B, ansi::ESC_GREEN_F))
} else if flags.contains(TextFlags::COLOR_YELLOW) {
Some(Self::color_f_or_b(flags, ansi::ESC_YELLOW_B, ansi::ESC_YELLOW_F))
} else if flags.contains(TextFlags::COLOR_BLUE) {
Some(Self::color_f_or_b(flags, ansi::ESC_BLUE_B, ansi::ESC_BLUE_F))
} else if flags.contains(TextFlags::COLOR_MAGENTA) {
Some(Self::color_f_or_b(flags, ansi::ESC_MAGENTA_B, ansi::ESC_MAGENTA_F))
} else if flags.contains(TextFlags::COLOR_CYAN) {
Some(Self::color_f_or_b(flags, ansi::ESC_CYAN_B, ansi::ESC_CYAN_F))
} else if flags.contains(TextFlags::COLOR_WHITE) {
Some(Self::color_f_or_b(flags, ansi::ESC_WHITE_B, ansi::ESC_WHITE_F))
} else {
None
}
}
fn resolve_style(flags: TextFlags) -> Vec<&'static str> {
let mut style: Vec<&'static str> = vec![];
if let Some(color) = Self::resolve_color(flags) {
style.push(color);
}
if flags.contains(TextFlags::STYLE_BOLD) {
style.push(ansi::ESC_BOLD);
}
if flags.contains(TextFlags::STYLE_DIM) {
style.push(ansi::ESC_DIM);
}
if flags.contains(TextFlags::STYLE_ITALIC) {
style.push(ansi::ESC_ITALIC);
}
if flags.contains(TextFlags::STYLE_UNDER) {
style.push(ansi::ESC_UNDERLINE);
}
if flags.contains(TextFlags::STYLE_STRIKE) {
style.push(ansi::ESC_STRIKETHROUGH);
}
return style;
}
pub fn label(&self) -> &String {
return &self.label;
}
pub fn set_label(&mut self, label: impl Into<String>) {
let label = label.into();
self.len = label.graphemes(true).count();
self.label = label;
}
pub(crate) fn set_line(&mut self, line: u16) {
self.line = line;
}
pub(crate) fn line(&self) -> u16 {
return self.line;
}
pub(crate) fn len(&self) -> usize {
return self.len;
}
pub(crate) fn set_pos(&mut self, pos: u16) {
self.pos = pos;
}
pub(crate) fn pos(&self) -> u16 {
return self.pos;
}
pub(crate) fn flags(&self) -> &TextFlags {
return &self.flags;
}
pub(crate) fn styles(&self) -> &[&'static str] {
return &self.style;
}
pub(crate) fn id(&self) -> u16 {
self.id
}
pub(crate) fn set_id(&mut self, value: u16) {
self.id = value;
}
}