use crate::render::Cell;
use crate::style::Color;
use crate::utils::gradient::{
fill_gradient_horizontal, fill_gradient_vertical, Gradient, GradientDirection,
};
use crate::widget::traits::{RenderContext, View, WidgetProps};
use crate::{impl_props_builders, impl_styled_view};
use std::time::Duration;
pub struct GradientBox {
gradient: Gradient,
direction: GradientDirection,
width: u16,
height: u16,
fill_char: char,
half_block: bool,
props: WidgetProps,
offset: f32,
speed: f32,
animated: bool,
}
impl GradientBox {
pub fn new(width: u16, height: u16) -> Self {
Self {
gradient: Gradient::default(),
direction: GradientDirection::ToRight,
width,
height,
fill_char: '█',
half_block: false,
props: WidgetProps::new(),
offset: 0.0,
speed: 1.0,
animated: false,
}
}
pub fn gradient(mut self, gradient: Gradient) -> Self {
self.gradient = gradient;
self
}
pub fn direction(mut self, direction: GradientDirection) -> Self {
self.direction = direction;
self
}
pub fn fill_char(mut self, ch: char) -> Self {
self.fill_char = ch;
self
}
pub fn half_block(mut self, half: bool) -> Self {
self.half_block = half;
self
}
pub fn size(mut self, width: u16, height: u16) -> Self {
self.width = width;
self.height = height;
self
}
pub fn horizontal(from: Color, to: Color, width: u16, height: u16) -> Self {
Self::new(width, height)
.gradient(Gradient::linear(from, to))
.direction(GradientDirection::ToRight)
}
pub fn vertical(from: Color, to: Color, width: u16, height: u16) -> Self {
Self::new(width, height)
.gradient(Gradient::linear(from, to))
.direction(GradientDirection::ToBottom)
}
pub fn diagonal(from: Color, to: Color, width: u16, height: u16) -> Self {
Self::new(width, height)
.gradient(Gradient::linear(from, to))
.direction(GradientDirection::ToBottomRight)
}
pub fn animate(mut self, enabled: bool) -> Self {
self.animated = enabled;
self
}
pub fn speed(mut self, speed: f32) -> Self {
self.speed = speed.max(0.0);
self
}
pub fn offset(mut self, offset: f32) -> Self {
self.offset = offset.clamp(0.0, 1.0);
self
}
pub fn update_animation(&mut self, delta: Duration) {
if !self.animated || self.speed <= 0.0 {
return;
}
let delta_secs = delta.as_secs_f32();
let increment = (delta_secs * self.speed / 5.0) % 1.0;
self.offset = (self.offset + increment) % 1.0;
}
pub fn reset_animation(&mut self) {
self.offset = 0.0;
}
}
impl Default for GradientBox {
fn default() -> Self {
Self::new(10, 5)
}
}
pub fn gradient_box(width: u16, height: u16) -> GradientBox {
GradientBox::new(width, height)
}
impl View for GradientBox {
crate::impl_view_meta!("GradientBox");
fn render(&self, ctx: &mut RenderContext) {
let area = ctx.area;
if area.width < 1 || area.height < 1 {
return;
}
let width = self.width.min(area.width);
let height = self.height.min(area.height);
if self.animated && self.offset > 0.0 {
self.render_animated(ctx, width, height);
} else {
match self.direction {
GradientDirection::ToRight | GradientDirection::ToLeft => {
fill_gradient_horizontal(
&self.gradient,
ctx.buffer,
area.x,
area.y,
width,
height,
);
}
GradientDirection::ToBottom | GradientDirection::ToTop => {
fill_gradient_vertical(
&self.gradient,
ctx.buffer,
area.x,
area.y,
width,
height,
);
}
GradientDirection::ToBottomRight
| GradientDirection::ToTopRight
| GradientDirection::Angle(_) => {
self.fill_diagonal(ctx, width, height);
}
}
}
if self.fill_char != ' ' {
for y in 0..height {
for x in 0..width {
let mut cell = Cell::new(self.fill_char);
cell.fg = Some(self.get_contrast_color_at(x, y, width, height));
ctx.set(x, y, cell);
}
}
}
}
}
impl GradientBox {
fn get_contrast_color_at(&self, x: u16, y: u16, width: u16, height: u16) -> Color {
let mut t = match self.direction {
GradientDirection::ToRight => x as f32 / width.max(1) as f32,
GradientDirection::ToLeft => 1.0 - (x as f32 / width.max(1) as f32),
GradientDirection::ToBottom => y as f32 / height.max(1) as f32,
GradientDirection::ToTop => 1.0 - (y as f32 / height.max(1) as f32),
_ => {
let t_x = x as f32 / width.max(1) as f32;
let t_y = y as f32 / height.max(1) as f32;
(t_x + t_y) / 2.0
}
};
if self.animated {
t = (t + self.offset) % 1.0;
}
let bg_color = self.gradient.at(t);
let luminance =
(bg_color.r as f32 * 0.299 + bg_color.g as f32 * 0.587 + bg_color.b as f32 * 0.114)
/ 255.0;
if luminance > 0.5 {
Color::BLACK
} else {
Color::WHITE
}
}
fn fill_diagonal(&self, ctx: &mut RenderContext, width: u16, height: u16) {
for py in 0..height {
for px in 0..width {
let t = (px as f32 / width.max(1) as f32 + py as f32 / height.max(1) as f32) / 2.0;
let color = self.gradient.at(t);
ctx.set_bg(px, py, color);
if self.fill_char != ' ' {
let mut cell = Cell::new(self.fill_char);
cell.fg = Some(self.get_contrast_color_at(px, py, width, height));
ctx.set(px, py, cell);
}
}
}
}
fn render_animated(&self, ctx: &mut RenderContext, width: u16, height: u16) {
let offset = self.offset;
match self.direction {
GradientDirection::ToRight | GradientDirection::ToLeft => {
for py in 0..height {
for px in 0..width {
let t = ((px as f32 / width.max(1) as f32) + offset) % 1.0;
let color = self.gradient.at(t);
ctx.set_bg(px, py, color);
}
}
}
GradientDirection::ToBottom | GradientDirection::ToTop => {
for py in 0..height {
for px in 0..width {
let t = ((py as f32 / height.max(1) as f32) + offset) % 1.0;
let color = self.gradient.at(t);
ctx.set_bg(px, py, color);
}
}
}
GradientDirection::ToBottomRight
| GradientDirection::ToTopRight
| GradientDirection::Angle(_) => {
for py in 0..height {
for px in 0..width {
let t = ((px as f32 / width.max(1) as f32
+ py as f32 / height.max(1) as f32)
/ 2.0
+ offset)
% 1.0;
let color = self.gradient.at(t);
ctx.set_bg(px, py, color);
}
}
}
}
}
}
impl_styled_view!(GradientBox);
impl_props_builders!(GradientBox);
#[cfg(test)]
mod tests {
use super::*;
use crate::style::Color;
#[test]
fn test_gradient_box_creation() {
let box_widget = GradientBox::horizontal(Color::BLUE, Color::RED, 20, 5);
assert_eq!(box_widget.width, 20);
assert_eq!(box_widget.height, 5);
}
#[test]
fn test_gradient_box_vertical() {
let box_widget = GradientBox::vertical(Color::BLACK, Color::WHITE, 10, 10);
assert_eq!(box_widget.direction, GradientDirection::ToBottom);
}
#[test]
fn test_gradient_box_diagonal() {
let box_widget = GradientBox::diagonal(Color::GREEN, Color::YELLOW, 15, 10);
assert_eq!(box_widget.direction, GradientDirection::ToBottomRight);
}
#[test]
fn test_gradient_box_fill_char() {
let mut box_widget = GradientBox::horizontal(Color::BLUE, Color::RED, 20, 5);
box_widget = box_widget.fill_char('░');
assert_eq!(box_widget.fill_char, '░');
}
#[test]
fn test_gradient_box_half_block() {
let box_widget = GradientBox::new(20, 10).half_block(true);
assert!(box_widget.half_block);
}
#[test]
fn test_gradient_box_animate() {
let box_widget = GradientBox::new(20, 10).animate(true);
assert!(box_widget.animated);
}
#[test]
fn test_gradient_box_speed() {
let box_widget = GradientBox::new(20, 10).speed(2.5);
assert_eq!(box_widget.speed, 2.5);
}
#[test]
fn test_gradient_box_offset() {
let box_widget = GradientBox::new(20, 10).offset(0.5);
assert_eq!(box_widget.offset, 0.5);
}
#[test]
fn test_gradient_box_update_animation() {
let mut box_widget = GradientBox::new(20, 10).animate(true).speed(1.0);
let initial_offset = box_widget.offset;
box_widget.update_animation(Duration::from_secs_f32(0.1));
assert!(box_widget.offset > initial_offset);
}
#[test]
fn test_gradient_box_reset_animation() {
let mut box_widget = GradientBox::new(20, 10).offset(0.8);
box_widget.reset_animation();
assert_eq!(box_widget.offset, 0.0);
}
#[test]
fn test_gradient_box_animation_disabled() {
let mut box_widget = GradientBox::new(20, 10).speed(1.0);
let initial_offset = box_widget.offset;
box_widget.update_animation(Duration::from_secs_f32(0.1));
assert_eq!(box_widget.offset, initial_offset); }
}