use crate::core::buffer::Buffer;
use crate::core::color::Color;
use crate::core::rect::Rect;
use aisling::color::{Color as AislingColor, Gradient as AislingGradient, GradientDirection};
use aisling::effects::{Effect, EffectConfig, EffectKind};
pub struct EffectPlayer {
kind: EffectKind,
text: String,
config: EffectConfig,
frames: Vec<aisling::frame::Frame>,
current_frame: usize,
accent: Color,
}
impl EffectPlayer {
pub fn new(kind: EffectKind, text: &str) -> Self {
let config = EffectConfig::default();
let frames = Effect::new(kind, text).frames();
Self {
kind,
text: text.to_string(),
config,
frames,
current_frame: 0,
accent: Color::rgb(88, 166, 255),
}
}
pub fn with_config(kind: EffectKind, text: &str, config: EffectConfig) -> Self {
let frames = Effect::with_config(kind, text, config.clone()).frames();
Self {
kind,
text: text.to_string(),
config,
frames,
current_frame: 0,
accent: Color::rgb(88, 166, 255),
}
}
pub fn with_accent(mut self, accent: Color) -> Self {
self.accent = accent;
self
}
pub fn with_duration(mut self, duration: usize) -> Self {
self.config = self.config.with_duration(duration);
let frames = Effect::with_config(self.kind, &self.text, self.config.clone()).frames();
self.frames = frames;
self
}
pub fn with_size(mut self, width: usize, height: usize) -> Self {
self.config = self.config.with_canvas_size(width, height);
let frames = Effect::with_config(self.kind, &self.text, self.config.clone()).frames();
self.frames = frames;
self
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.config = self.config.with_seed(seed);
let frames = Effect::with_config(self.kind, &self.text, self.config.clone()).frames();
self.frames = frames;
self
}
pub fn with_gradient_colors(mut self, colors: Vec<Color>, angle: f32) -> Self {
let a_colors: Vec<AislingColor> = colors
.into_iter()
.map(|c| AislingColor::rgb(c.r, c.g, c.b))
.collect();
let grad = AislingGradient::new(a_colors, 32);
let dir = match angle as u32 {
0 => GradientDirection::Horizontal,
90 => GradientDirection::Vertical,
45 => GradientDirection::Diagonal,
_ => GradientDirection::Diagonal,
};
self.config = self.config.with_gradient(grad, dir);
let frames = Effect::with_config(self.kind, &self.text, self.config.clone()).frames();
self.frames = frames;
self
}
pub fn total_frames(&self) -> usize {
self.frames.len()
}
pub fn current_frame_index(&self) -> usize {
self.current_frame
}
pub fn advance(&mut self) {
if !self.frames.is_empty() {
self.current_frame = (self.current_frame + 1) % self.frames.len();
}
}
pub fn advance_n(&mut self, n: usize) {
if !self.frames.is_empty() {
self.current_frame = (self.current_frame + n) % self.frames.len();
}
}
pub fn set_frame(&mut self, frame: usize) {
if !self.frames.is_empty() {
self.current_frame = frame % self.frames.len();
}
}
pub fn progress(&self) -> f32 {
if self.frames.is_empty() {
1.0
} else {
self.current_frame as f32 / self.frames.len() as f32
}
}
pub fn is_complete(&self) -> bool {
self.current_frame >= self.frames.len().saturating_sub(1)
}
pub fn render_to_buffer(&self, buffer: &mut Buffer, area: Rect) {
if self.frames.is_empty() {
return;
}
let frame = &self.frames[self.current_frame];
let fw = frame.width();
let fh = frame.height();
for fy in 0..fh.min(area.height as usize) {
for fx in 0..fw.min(area.width as usize) {
if let Some(cell) = frame.cell(fx, fy) {
let x = area.x as usize + fx;
let y = area.y as usize + fy;
if x < buffer.width && y < buffer.height {
let mapped = map_aisling_cell(cell, self.accent);
buffer.set(x, y, mapped);
}
}
}
}
}
pub fn render_frame_to_buffer(&self, frame_idx: usize, buffer: &mut Buffer, area: Rect) {
if self.frames.is_empty() {
return;
}
let idx = frame_idx % self.frames.len();
let frame = &self.frames[idx];
let fw = frame.width();
let fh = frame.height();
for fy in 0..fh.min(area.height as usize) {
for fx in 0..fw.min(area.width as usize) {
if let Some(cell) = frame.cell(fx, fy) {
let x = area.x as usize + fx;
let y = area.y as usize + fy;
if x < buffer.width && y < buffer.height {
let mapped = map_aisling_cell(cell, self.accent);
buffer.set(x, y, mapped);
}
}
}
}
}
pub fn get_ansi_string(&self) -> String {
if self.frames.is_empty() {
return String::new();
}
self.frames[self.current_frame].to_ansi_string()
}
pub fn kind(&self) -> EffectKind {
self.kind
}
pub fn name(&self) -> &'static str {
self.kind.name()
}
pub fn all_kinds() -> &'static [EffectKind] {
EffectKind::all()
}
}
fn map_aisling_cell(cell: &aisling::frame::Cell, accent: Color) -> crate::core::buffer::Cell {
let fg = if let Some(c) = cell.colors.fg {
Color::rgb(c.r, c.g, c.b)
} else {
accent
};
let bg = cell.colors.bg.map(|c| Color::rgb(c.r, c.g, c.b));
crate::core::buffer::Cell {
ch: cell.ch,
fg,
bg,
bold: cell.bold,
italic: false,
underlined: false,
}
}
pub fn effect_kind_from_name(name: &str) -> Option<EffectKind> {
name.parse().ok()
}