windjammer_ui/components/generated/
curve_editor.rs1#![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}