tachyonfx/pattern/
diagonal.rs

1#[cfg(feature = "dsl")]
2use compact_str::{format_compact, CompactString, ToCompactString};
3use ratatui::layout::{Position, Rect};
4
5#[cfg(feature = "dsl")]
6use crate::dsl::DslFormat;
7use crate::{
8    math,
9    pattern::{InstancedPattern, Pattern, PreparedPattern, TransitionProgress},
10};
11
12#[derive(Clone, Debug, Copy, PartialEq)]
13pub struct DiagonalPattern {
14    direction: DiagonalDirection,
15    transition_width: f32,
16}
17
18/// Direction variants for diagonal sweep patterns.
19#[derive(Clone, Debug, Copy, PartialEq)]
20pub enum DiagonalDirection {
21    /// Sweeps diagonally from top-left corner to bottom-right corner
22    TopLeftToBottomRight,
23    /// Sweeps diagonally from top-right corner to bottom-left corner
24    TopRightToBottomLeft,
25    /// Sweeps diagonally from bottom-left corner to top-right corner
26    BottomLeftToTopRight,
27    /// Sweeps diagonally from bottom-right corner to top-left corner
28    BottomRightToTopLeft,
29}
30
31impl DiagonalPattern {
32    pub fn top_left_to_bottom_right() -> Self {
33        Self {
34            direction: DiagonalDirection::TopLeftToBottomRight,
35            transition_width: 2.0, // Default to 2 terminal cells
36        }
37    }
38
39    pub fn top_right_to_bottom_left() -> Self {
40        Self {
41            direction: DiagonalDirection::TopRightToBottomLeft,
42            transition_width: 2.0, // Default to 2 terminal cells
43        }
44    }
45
46    pub fn bottom_left_to_top_right() -> Self {
47        Self {
48            direction: DiagonalDirection::BottomLeftToTopRight,
49            transition_width: 2.0, // Default to 2 terminal cells
50        }
51    }
52
53    pub fn bottom_right_to_top_left() -> Self {
54        Self {
55            direction: DiagonalDirection::BottomRightToTopLeft,
56            transition_width: 2.0, // Default to 2 terminal cells
57        }
58    }
59
60    /// Creates a diagonal pattern with specified direction and transition width.
61    ///
62    /// # Arguments
63    /// * `direction` - Direction of the diagonal sweep
64    /// * `transition_width` - Width of gradient transition zone in terminal cells
65    ///   (minimum 0.1)
66    pub fn new(direction: DiagonalDirection, transition_width: f32) -> Self {
67        Self {
68            direction,
69            transition_width: transition_width.max(0.1),
70        }
71    }
72
73    /// Sets the transition width for gradient smoothing along the diagonal edge.
74    ///
75    /// # Arguments
76    /// * `width` - Width of gradient transition zone in terminal cells (minimum 0.1)
77    pub fn with_transition_width(mut self, width: f32) -> Self {
78        self.transition_width = width.max(0.1);
79        self
80    }
81}
82
83impl Pattern for DiagonalPattern {
84    type Context = (f32, Rect);
85
86    fn for_frame(self, alpha: f32, area: Rect) -> PreparedPattern<Self::Context, Self>
87    where
88        Self: Sized,
89    {
90        PreparedPattern { pattern: self, context: (alpha, area) }
91    }
92}
93
94impl InstancedPattern for PreparedPattern<(f32, Rect), DiagonalPattern> {
95    fn map_alpha(&mut self, pos: Position) -> f32 {
96        let pattern = &self.pattern;
97        let (global_alpha, area) = self.context;
98
99        // Normalize position to 0.0-1.0 range
100        let norm_x = (pos.x - area.x) as f32 / area.width as f32;
101        let norm_y = (pos.y - area.y) as f32 / area.height as f32;
102
103        // Calculate diagonal progress based on direction
104        use DiagonalDirection::*;
105        let diagonal_progress = match pattern.direction {
106            TopLeftToBottomRight => (norm_x + norm_y) / 2.0,
107            TopRightToBottomLeft => ((1.0 - norm_x) + norm_y) / 2.0,
108            BottomLeftToTopRight => (norm_x + (1.0 - norm_y)) / 2.0,
109            BottomRightToTopLeft => ((1.0 - norm_x) + (1.0 - norm_y)) / 2.0,
110        };
111
112        // Use TransitionProgress with inverse spatial mapping for correct character evolution
113        // Convert cell-based transition width to normalized units using diagonal length
114        let diagonal_length =
115            math::sqrt(math::powi(area.width as f32, 2) + math::powi(area.height as f32, 2));
116        let normalized_transition_width = pattern.transition_width / diagonal_length;
117        TransitionProgress::from(normalized_transition_width).map_spatial(
118            global_alpha,
119            diagonal_progress,
120            1.0,
121        )
122    }
123}
124
125#[cfg(feature = "dsl")]
126impl DslFormat for DiagonalDirection {
127    fn dsl_format(&self) -> CompactString {
128        match self {
129            DiagonalDirection::TopLeftToBottomRight => {
130                "DiagonalDirection::TopLeftToBottomRight".to_compact_string()
131            },
132            DiagonalDirection::TopRightToBottomLeft => {
133                "DiagonalDirection::TopRightToBottomLeft".to_compact_string()
134            },
135            DiagonalDirection::BottomLeftToTopRight => {
136                "DiagonalDirection::BottomLeftToTopRight".to_compact_string()
137            },
138            DiagonalDirection::BottomRightToTopLeft => {
139                "DiagonalDirection::BottomRightToTopLeft".to_compact_string()
140            },
141        }
142    }
143}
144
145#[cfg(feature = "dsl")]
146impl DslFormat for DiagonalPattern {
147    fn dsl_format(&self) -> CompactString {
148        if (self.transition_width - 2.0).abs() < f32::EPSILON {
149            // Use named constructor for default transition width
150            match self.direction {
151                DiagonalDirection::TopLeftToBottomRight => {
152                    "DiagonalPattern::top_left_to_bottom_right()".to_compact_string()
153                },
154                DiagonalDirection::TopRightToBottomLeft => {
155                    "DiagonalPattern::top_right_to_bottom_left()".to_compact_string()
156                },
157                DiagonalDirection::BottomLeftToTopRight => {
158                    "DiagonalPattern::bottom_left_to_top_right()".to_compact_string()
159                },
160                DiagonalDirection::BottomRightToTopLeft => {
161                    "DiagonalPattern::bottom_right_to_top_left()".to_compact_string()
162                },
163            }
164        } else {
165            // Use with_transition_width for custom transition width
166            let base = match self.direction {
167                DiagonalDirection::TopLeftToBottomRight => {
168                    "DiagonalPattern::top_left_to_bottom_right()"
169                },
170                DiagonalDirection::TopRightToBottomLeft => {
171                    "DiagonalPattern::top_right_to_bottom_left()"
172                },
173                DiagonalDirection::BottomLeftToTopRight => {
174                    "DiagonalPattern::bottom_left_to_top_right()"
175                },
176                DiagonalDirection::BottomRightToTopLeft => {
177                    "DiagonalPattern::bottom_right_to_top_left()"
178                },
179            };
180            if self.transition_width.fract() == 0.0 {
181                format_compact!(
182                    "{}.with_transition_width({})",
183                    base,
184                    self.transition_width as u32
185                )
186            } else {
187                format_compact!("{}.with_transition_width({})", base, self.transition_width)
188            }
189        }
190    }
191}