use std::hash::{DefaultHasher, Hash, Hasher};
use crate::{
buffer::Buffer,
enums::{Color, Modifier, RGB, Wrap},
geometry::{Direction, TextAlign, Vec2},
style::Style,
text::{
GradStyle, Line, StrStyle, Text, TextParser, get_step, text_render,
},
widgets::layout::LayoutNode,
};
use super::{Element, widget::Widget};
pub struct Grad {
text: String,
fg_start: RGB,
fg_end: RGB,
direction: Direction,
bg: Option<Color>,
modifier: Modifier,
align: TextAlign,
wrap: Wrap,
ellipsis: String,
}
impl Grad {
#[must_use]
pub fn new<T, R, S>(text: T, start: R, end: S) -> Self
where
T: Into<String>,
R: Into<RGB>,
S: Into<RGB>,
{
Self {
text: text.into(),
fg_start: start.into(),
fg_end: end.into(),
direction: Direction::Horizontal,
bg: None,
modifier: Modifier::empty(),
align: Default::default(),
wrap: Default::default(),
ellipsis: "...".to_string(),
}
}
#[must_use]
pub fn direction(mut self, direction: Direction) -> Self {
self.direction = direction;
self
}
#[must_use]
pub fn bg<T>(mut self, bg: T) -> Self
where
T: Into<Option<Color>>,
{
self.bg = bg.into();
self
}
#[must_use]
pub fn modifier(mut self, modifier: Modifier) -> Self {
self.modifier = Modifier::empty();
self.modifier.insert(modifier);
self
}
#[must_use]
pub fn add_modifier(mut self, flag: Modifier) -> Self {
self.modifier.insert(flag);
self
}
#[must_use]
pub fn remove_modifier(mut self, flag: Modifier) -> Self {
self.modifier.remove(flag);
self
}
#[must_use]
pub fn align(mut self, align: TextAlign) -> Self {
self.align = align;
self
}
#[must_use]
pub fn wrap(mut self, wrap: Wrap) -> Self {
self.wrap = wrap;
self
}
#[must_use]
pub fn ellipsis(mut self, ellipsis: &str) -> Self {
self.ellipsis = ellipsis.to_string();
self
}
}
impl<M: Clone + 'static> Widget<M> for Grad {
fn render(&self, buffer: &mut Buffer, layout: &LayoutNode) {
text_render(self, buffer, layout.area, &self.ellipsis, self.align);
}
fn layout_hash(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.text.hash(&mut hasher);
self.wrap.hash(&mut hasher);
hasher.finish()
}
fn height(&self, size: &Vec2) -> usize {
let mut parser = TextParser::new(&self.text).wrap(self.wrap);
parser.height(size)
}
fn width(&self, size: &Vec2) -> usize {
let mut parser = TextParser::new(&self.text).wrap(self.wrap);
parser.width(size)
}
}
impl Text for Grad {
fn append_lines<'a>(
&'a self,
lines: &mut Vec<Line<'a>>,
size: &Vec2,
wrap: Option<Wrap>,
) -> bool {
let wrap = wrap.unwrap_or(self.wrap);
let mut parser = TextParser::new(&self.text).wrap(wrap);
let frags = self.get_frags(&mut parser, lines, size);
if frags.is_empty() {
return true;
}
let fit = parser.is_end();
match self.direction {
Direction::Vertical => {
self.get_lines_vert(lines, frags, parser, size)
}
Direction::Horizontal => self.get_lines_hor(lines, frags, fit),
}
fit
}
fn get_align(&self) -> TextAlign {
self.align
}
}
impl Grad {
fn get_frags<'a>(
&self,
parser: &mut TextParser<'a>,
lines: &mut Vec<Line<'a>>,
size: &Vec2,
) -> Vec<(&'a str, usize)> {
let height = lines.len().saturating_sub(1);
if size.x == 0 || height >= size.y || parser.is_end() {
return vec![];
}
let mut frags = Vec::new();
let last_width = lines.last().map(|l| l.width).unwrap_or_default();
let mut fwidth = size.x.saturating_sub(last_width);
for _ in height..size.y {
let Some(line) = parser.next_line(fwidth) else {
break;
};
frags.push(line);
fwidth = size.x;
}
frags
}
fn get_lines_vert<'a>(
&self,
lines: &mut Vec<Line<'a>>,
frags: Vec<(&'a str, usize)>,
mut parser: TextParser<'a>,
size: &Vec2,
) {
let mut height = frags.len();
while parser.next_line(size.x).is_some() {
height += 1;
}
let ((mut r, mut g, mut b), (rs, gs, bs)) =
get_step(&self.fg_start, &self.fg_end, height);
let base_style = Style::new().bg(self.bg).modifier(self.modifier);
let mut line = lines.pop().unwrap_or_else(Line::empty);
for (text, len) in frags {
let col = Color::Rgb(r as u8, g as u8, b as u8);
let style = StrStyle::Static(base_style.fg(col));
line.push(text, len, style);
lines.push(line);
line = Line::empty();
(r, g, b) = (r + rs, g + gs, b + bs);
}
}
fn get_lines_hor<'a>(
&self,
lines: &mut Vec<Line<'a>>,
frags: Vec<(&'a str, usize)>,
fits: bool,
) {
let gstyle = GradStyle::new(self.fg_start, self.fg_end)
.bg(self.bg)
.modifier(self.modifier);
let style = if frags.len() <= 1 && fits {
StrStyle::LocalGrad(gstyle)
} else {
StrStyle::GlobalGrad(gstyle)
};
let mut line = lines.pop().unwrap_or_else(Line::empty);
for (text, len) in frags {
line.push(text, len, style.clone());
lines.push(line);
line = Line::empty();
}
}
}
impl<M: Clone + 'static> From<Grad> for Box<dyn Widget<M>> {
fn from(value: Grad) -> Self {
Box::new(value)
}
}
impl<M: Clone + 'static> From<Grad> for Element<M> {
fn from(value: Grad) -> Self {
Element::new(value)
}
}
impl From<Grad> for Box<dyn Text> {
fn from(value: Grad) -> Self {
Box::new(value)
}
}