windjammer_ui/components/generated/
curve_editor.rs

1#![allow(clippy::all)]
2#![allow(noop_method_call)]
3use super::traits::Renderable;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub struct CurvePoint {
7    pub time: f32,
8    pub value: f32,
9    pub in_tangent: f32,
10    pub out_tangent: f32,
11}
12
13impl CurvePoint {
14    #[inline]
15    pub fn new(time: f32, value: f32) -> CurvePoint {
16        CurvePoint {
17            time,
18            value,
19            in_tangent: 0.0,
20            out_tangent: 0.0,
21        }
22    }
23    #[inline]
24    pub fn tangents(mut self, in_tan: f32, out_tan: f32) -> CurvePoint {
25        self.in_tangent = in_tan;
26        self.out_tangent = out_tan;
27        self
28    }
29}
30
31#[derive(Clone, Debug, PartialEq, Copy)]
32pub enum EasingPreset {
33    Linear,
34    EaseIn,
35    EaseOut,
36    EaseInOut,
37    Bounce,
38    Elastic,
39    Custom,
40}
41
42#[derive(Debug, Clone, PartialEq)]
43pub struct CurveEditor {
44    pub width: i32,
45    pub height: i32,
46    pub points: Vec<CurvePoint>,
47    pub min_value: f32,
48    pub max_value: f32,
49    pub grid_enabled: bool,
50    pub on_change: String,
51}
52
53impl CurveEditor {
54    #[inline]
55    pub fn new() -> CurveEditor {
56        let mut points = Vec::new();
57        points.push(CurvePoint::new(0.0, 0.0));
58        points.push(CurvePoint::new(1.0, 1.0));
59        CurveEditor {
60            width: 300,
61            height: 200,
62            points,
63            min_value: 0.0,
64            max_value: 1.0,
65            grid_enabled: true,
66            on_change: "".to_string(),
67        }
68    }
69    #[inline]
70    pub fn size(mut self, width: i32, height: i32) -> CurveEditor {
71        self.width = width;
72        self.height = height;
73        self
74    }
75    #[inline]
76    pub fn range(mut self, min: f32, max: f32) -> CurveEditor {
77        self.min_value = min;
78        self.max_value = max;
79        self
80    }
81    #[inline]
82    pub fn points(mut self, points: Vec<CurvePoint>) -> CurveEditor {
83        self.points = points;
84        self
85    }
86    #[inline]
87    pub fn add_point(mut self, point: CurvePoint) -> CurveEditor {
88        self.points.push(point);
89        self
90    }
91    #[inline]
92    pub fn grid(mut self, enabled: bool) -> CurveEditor {
93        self.grid_enabled = enabled;
94        self
95    }
96    #[inline]
97    pub fn on_change(mut self, handler: String) -> CurveEditor {
98        self.on_change = handler;
99        self
100    }
101    #[inline]
102    pub fn apply_preset(&mut self, preset: EasingPreset) {
103        self.points.clear();
104        match preset {
105            EasingPreset::Linear => {
106                self.points.push(CurvePoint::new(0.0, 0.0));
107                self.points.push(CurvePoint::new(1.0, 1.0))
108            }
109            EasingPreset::EaseIn => {
110                self.points
111                    .push(CurvePoint::new(0.0, 0.0).tangents(0.0, 0.5));
112                self.points
113                    .push(CurvePoint::new(1.0, 1.0).tangents(0.5, 0.0))
114            }
115            EasingPreset::EaseOut => {
116                self.points
117                    .push(CurvePoint::new(0.0, 0.0).tangents(0.0, 2.0));
118                self.points
119                    .push(CurvePoint::new(1.0, 1.0).tangents(2.0, 0.0))
120            }
121            EasingPreset::EaseInOut => {
122                self.points
123                    .push(CurvePoint::new(0.0, 0.0).tangents(0.0, 0.5));
124                self.points.push(CurvePoint::new(0.5, 0.5));
125                self.points
126                    .push(CurvePoint::new(1.0, 1.0).tangents(0.5, 0.0))
127            }
128            _ => {
129                self.points.push(CurvePoint::new(0.0, 0.0));
130                self.points.push(CurvePoint::new(1.0, 1.0))
131            }
132        }
133    }
134}
135
136impl Renderable for CurveEditor {
137    #[inline]
138    fn render(self) -> String {
139        let mut path_d = "".to_string();
140        let range = self.max_value - self.min_value;
141        for i in 0..self.points.len() {
142            let p = self.points.get(i);
143            match p {
144                Some(point) => {
145                    let x = point.time * self.width as f32;
146                    let y = self.height as f32
147                        - (point.value - self.min_value) / range * self.height as f32;
148                    if i == 0 {
149                        path_d = format!("M {} {}", x, y);
150                    } else {
151                        path_d = format!("{} L {} {}", path_d, x, y);
152                    }
153                }
154                None => {}
155            }
156        }
157        let mut points_html = "".to_string();
158        for i in 0..self.points.len() {
159            let p = self.points.get(i);
160            match p {
161                Some(point) => {
162                    let x = point.time * self.width as f32;
163                    let y = self.height as f32
164                        - (point.value - self.min_value) / range * self.height as f32;
165                    points_html = format!(
166                        "{}<circle cx='{}' cy='{}' r='6' class='curve-point' data-index='{}'/>",
167                        points_html, x, y, i
168                    );
169                }
170                None => {}
171            }
172        }
173        let grid_html = {
174            if self.grid_enabled {
175                format!(
176                    "
177                <line x1='0' y1='{}' x2='{}' y2='{}' class='grid-line'/>
178                <line x1='0' y1='{}' x2='{}' y2='{}' class='grid-line'/>
179                <line x1='{}' y1='0' x2='{}' y2='{}' class='grid-line'/>
180                <line x1='{}' y1='0' x2='{}' y2='{}' class='grid-line'/>
181            ",
182                    self.height / 4,
183                    self.width,
184                    self.height / 4,
185                    self.height * 3 / 4,
186                    self.width,
187                    self.height * 3 / 4,
188                    self.width / 4,
189                    self.width / 4,
190                    self.height,
191                    self.width * 3 / 4,
192                    self.width * 3 / 4,
193                    self.height
194                )
195            } else {
196                "".to_string()
197            }
198        };
199        format!(
200            "
201            <div class='curve-editor'>
202                <div class='curve-toolbar'>
203                    <button onclick='setCurvePreset(\"linear\")'>Linear</button>
204                    <button onclick='setCurvePreset(\"easeIn\")'>Ease In</button>
205                    <button onclick='setCurvePreset(\"easeOut\")'>Ease Out</button>
206                    <button onclick='setCurvePreset(\"easeInOut\")'>Ease In/Out</button>
207                </div>
208                <svg class='curve-canvas' width='{}' height='{}' viewBox='0 0 {} {}'>
209                    <rect class='curve-bg' width='100%' height='100%'/>
210                    {}
211                    <path d='{}' class='curve-line'/>
212                    {}
213                </svg>
214                <div class='curve-values'>
215                    <span>{:.2}</span>
216                    <span>{:.2}</span>
217                </div>
218            </div>
219        ",
220            self.width,
221            self.height,
222            self.width,
223            self.height,
224            grid_html,
225            path_d,
226            points_html,
227            self.max_value,
228            self.min_value
229        )
230    }
231}
232
233#[derive(Debug, Clone, PartialEq)]
234pub struct GradientStop {
235    pub position: f32,
236    pub color: String,
237}
238
239impl GradientStop {
240    #[inline]
241    pub fn new(position: f32, color: String) -> GradientStop {
242        GradientStop { position, color }
243    }
244}
245
246#[derive(Debug, Clone, PartialEq)]
247pub struct GradientEditor {
248    pub width: i32,
249    pub height: i32,
250    pub stops: Vec<GradientStop>,
251    pub on_change: String,
252}
253
254impl GradientEditor {
255    #[inline]
256    pub fn new() -> GradientEditor {
257        let mut stops = Vec::new();
258        stops.push(GradientStop::new(0.0, "#000000".to_string()));
259        stops.push(GradientStop::new(1.0, "#ffffff".to_string()));
260        GradientEditor {
261            width: 300,
262            height: 40,
263            stops,
264            on_change: "".to_string(),
265        }
266    }
267    #[inline]
268    pub fn add_stop(mut self, stop: GradientStop) -> GradientEditor {
269        self.stops.push(stop);
270        self
271    }
272    #[inline]
273    pub fn on_change(mut self, handler: String) -> GradientEditor {
274        self.on_change = handler;
275        self
276    }
277}
278
279impl Renderable for GradientEditor {
280    #[inline]
281    fn render(self) -> String {
282        let mut gradient_stops = "".to_string();
283        for i in 0..self.stops.len() {
284            let s = self.stops.get(i);
285            match s {
286                Some(stop) => {
287                    if i > 0 {
288                        gradient_stops += ", ";
289                    }
290                    gradient_stops = format!(
291                        "{}{}{}{}",
292                        gradient_stops,
293                        stop.color.as_str(),
294                        " ",
295                        format!("{}%", (stop.position * 100.0) as i32).as_str()
296                    );
297                }
298                None => {}
299            }
300        }
301        let mut markers_html = "".to_string();
302        for i in 0..self.stops.len() {
303            let s = self.stops.get(i);
304            match s {
305                Some(stop) => {
306                    let left = (stop.position * 100.0) as i32;
307                    markers_html = format!(
308                        "{}
309                        <div class='gradient-stop' style='left: {}%; background: {};' 
310                             data-index='{}'></div>
311                    ",
312                        markers_html, left, stop.color, i
313                    );
314                }
315                None => {}
316            }
317        }
318        format!(
319            "
320            <div class='gradient-editor'>
321                <div class='gradient-bar' style='background: linear-gradient(to right, {});'>
322                    {}
323                </div>
324                <div class='gradient-controls'>
325                    <button onclick='addGradientStop()'>+ Add Stop</button>
326                </div>
327            </div>
328        ",
329            gradient_stops, markers_html
330        )
331    }
332}
333
334#[inline]
335pub fn curve_editor_styles() -> String {
336    "
337    .curve-editor {
338        background: #16213e;
339        border-radius: 8px;
340        padding: 12px;
341    }
342    
343    .curve-toolbar {
344        display: flex;
345        gap: 4px;
346        margin-bottom: 12px;
347    }
348    
349    .curve-toolbar button {
350        padding: 4px 8px;
351        border: none;
352        border-radius: 4px;
353        background: #0f3460;
354        color: #888;
355        font-size: 11px;
356        cursor: pointer;
357    }
358    
359    .curve-toolbar button:hover {
360        background: #1a4a8a;
361        color: #e0e0e0;
362    }
363    
364    .curve-canvas {
365        display: block;
366        border-radius: 4px;
367        overflow: hidden;
368    }
369    
370    .curve-bg {
371        fill: #0a0a1a;
372    }
373    
374    .grid-line {
375        stroke: #1a1a3a;
376        stroke-width: 1;
377    }
378    
379    .curve-line {
380        fill: none;
381        stroke: #e94560;
382        stroke-width: 2;
383    }
384    
385    .curve-point {
386        fill: #e94560;
387        stroke: white;
388        stroke-width: 2;
389        cursor: move;
390    }
391    
392    .curve-point:hover {
393        fill: #ff6b8a;
394        r: 8;
395    }
396    
397    .curve-values {
398        display: flex;
399        justify-content: space-between;
400        margin-top: 4px;
401        font-size: 10px;
402        color: #666;
403    }
404    
405    /* Gradient editor */
406    .gradient-editor {
407        background: #16213e;
408        border-radius: 8px;
409        padding: 12px;
410    }
411    
412    .gradient-bar {
413        position: relative;
414        height: 32px;
415        border-radius: 4px;
416        cursor: crosshair;
417    }
418    
419    .gradient-stop {
420        position: absolute;
421        top: 100%;
422        width: 12px;
423        height: 12px;
424        margin-left: -6px;
425        margin-top: 4px;
426        border-radius: 50%;
427        border: 2px solid white;
428        cursor: move;
429        box-shadow: 0 2px 4px rgba(0,0,0,0.3);
430    }
431    
432    .gradient-controls {
433        margin-top: 16px;
434    }
435    
436    .gradient-controls button {
437        padding: 6px 12px;
438        border: 1px dashed #333;
439        border-radius: 4px;
440        background: transparent;
441        color: #888;
442        cursor: pointer;
443    }
444    
445    .gradient-controls button:hover {
446        border-color: #e94560;
447        color: #e94560;
448    }
449    "
450    .to_string()
451}