1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use slint::{Model, VecModel};
5
6use crate::preview::ui;
7
8use std::rc::Rc;
9
10fn find_index_for_position(model: &slint::ModelRc<ui::GradientStop>, position: f32) -> usize {
11 let position: f32 = position.clamp(min:0.0, max:1.0);
12
13 model
14 .iter()
15 .position(|gs| gs.position.total_cmp(&position) != std::cmp::Ordering::Less)
16 .unwrap_or(default:model.row_count())
17}
18
19pub fn add_gradient_stop(model: slint::ModelRc<ui::GradientStop>, value: ui::GradientStop) -> i32 {
20 let insert_pos: usize = find_index_for_position(&model, value.position);
21 let m: &VecModel = model.as_any().downcast_ref::<VecModel<_>>().unwrap();
22 m.insert(index:insert_pos, value);
23 (insert_pos) as i32
24}
25
26pub fn remove_gradient_stop(model: slint::ModelRc<ui::GradientStop>, row: i32) {
27 if row < 0 {
28 return;
29 }
30 let row: usize = row as usize;
31 if row < model.row_count() {
32 model.as_any().downcast_ref::<VecModel<ui::GradientStop>>().unwrap().remove(index:row);
33 }
34}
35
36pub fn move_gradient_stop(
37 model: slint::ModelRc<ui::GradientStop>,
38 row: i32,
39 new_position: f32,
40) -> i32 {
41 let mut row_usize = row as usize;
42 if row < 0 || row_usize >= model.row_count() {
43 return row;
44 }
45
46 let m = model.as_any().downcast_ref::<VecModel<ui::GradientStop>>().unwrap();
47
48 let mut gs = model.row_data(row_usize).unwrap();
49 gs.position = new_position;
50 model.set_row_data(row_usize, gs);
51
52 fn swap_direction(
53 model: &VecModel<ui::GradientStop>,
54 row: usize,
55 value: f32,
56 ) -> Option<(usize, usize)> {
57 let previous = model.row_data(row.saturating_sub(1));
58 let next = model.row_data(row + 1);
59 let previous_order = previous.map(|gs| value.total_cmp(&gs.position));
60 let next_order = next.map(|gs| value.total_cmp(&gs.position));
61
62 match (previous_order, next_order) {
63 (Some(std::cmp::Ordering::Less), _) => Some((row, row - 1)),
64 (_, Some(std::cmp::Ordering::Greater)) => Some((row, row + 1)),
65 _ => None,
66 }
67 }
68
69 while let Some((old_row, new_row)) = swap_direction(m, row_usize, new_position) {
70 m.swap(old_row, new_row);
71 row_usize = new_row;
72 }
73
74 row_usize as i32
75}
76
77fn interpolate(
78 previous: ui::GradientStop,
79 next: ui::GradientStop,
80 factor: f32,
81) -> ui::GradientStop {
82 let position: f32 = (previous.position + (next.position - previous.position) * factor)
83 .clamp(min:previous.position, max:next.position);
84 let color: Color = next.color.mix(&previous.color, factor);
85
86 ui::GradientStop { position, color }
87}
88
89fn fallback_gradient_stop(position: f32) -> ui::GradientStop {
90 ui::GradientStop { position, color: slint::Color::from_argb_u8(alpha:0xff, red:0x80, green:0x80, blue:0x80) }
91}
92
93pub fn suggest_gradient_stop_at_row(
94 model: slint::ModelRc<ui::GradientStop>,
95 row: i32,
96) -> ui::GradientStop {
97 let row_usize: usize = row as usize;
98 if row < 0 || row_usize > model.row_count() {
99 return fallback_gradient_stop(position:0.0);
100 }
101
102 let (prev: GradientStop, next: GradientStop) = if row_usize == 0 {
103 let first_stop: GradientStop = model.row_data(0).unwrap_or(default:fallback_gradient_stop(position:0.0));
104 let very_first_stop: GradientStop = ui::GradientStop { position: 0.0, color: first_stop.color };
105 (very_first_stop.clone(), very_first_stop)
106 } else if row_usize == model.row_count() {
107 let last_stop: GradientStop = model.row_data(row_usize - 1).unwrap_or(default:fallback_gradient_stop(position:1.0));
108 let very_last_stop: GradientStop = ui::GradientStop { position: 1.0, color: last_stop.color };
109 (very_last_stop.clone(), very_last_stop)
110 } else {
111 (
112 model.row_data(row_usize - 1).expect(msg:"Index was tested to be valid"),
113 model.row_data(row_usize).expect(msg:"index was tested to be valid"),
114 )
115 };
116
117 interpolate(previous:prev, next, factor:0.5)
118}
119
120pub fn suggest_gradient_stop_at_position(
121 model: slint::ModelRc<ui::GradientStop>,
122 position: f32,
123) -> ui::GradientStop {
124 let position = position.clamp(0.0, 1.0);
125
126 if model.row_count() == 0 {
127 return fallback_gradient_stop(position);
128 }
129
130 let mut prev = model.row_data(0).expect("Not empty");
131 prev.position = 0.0;
132 let mut next = model.row_data(model.row_count() - 1).expect("Not empty");
133 next.position = 1.0;
134
135 for current in model.iter() {
136 if current.position > position {
137 next = current;
138 break;
139 }
140
141 if current.position <= position {
142 prev = current;
143 }
144 }
145
146 let factor = (position - prev.position) / (next.position - prev.position);
147
148 interpolate(prev, next, factor)
149}
150
151pub fn clone_gradient_stops(
152 model: slint::ModelRc<ui::GradientStop>,
153) -> slint::ModelRc<ui::GradientStop> {
154 let cloned_data: Vec = model.iter().collect::<Vec<_>>();
155 Rc::new(VecModel::from(cloned_data)).into()
156}
157
158#[cfg(test)]
159mod tests {
160 use crate::preview::ui;
161
162 use slint::{Model, ModelRc, VecModel};
163
164 use std::rc::Rc;
165
166 fn make_empty_model() -> ModelRc<ui::GradientStop> {
167 Rc::new(VecModel::default()).into()
168 }
169
170 #[test]
171 fn test_add_and_remove_gradient_stops() {
172 let model = make_empty_model();
173
174 super::remove_gradient_stop(model.clone(), 0);
175
176 let mut it = model.iter();
177 assert_eq!(it.next(), None);
178
179 super::add_gradient_stop(
180 model.clone(),
181 ui::GradientStop { position: 1.0, color: slint::Color::from_argb_encoded(0xff010101) },
182 );
183
184 super::remove_gradient_stop(model.clone(), 0);
185 let mut it = model.iter();
186 assert_eq!(it.next(), None);
187
188 super::add_gradient_stop(
189 model.clone(),
190 ui::GradientStop { position: 1.0, color: slint::Color::from_argb_encoded(0xff010101) },
191 );
192
193 super::add_gradient_stop(
194 model.clone(),
195 ui::GradientStop { position: 1.0, color: slint::Color::from_argb_encoded(0xff020202) },
196 );
197 super::add_gradient_stop(
198 model.clone(),
199 ui::GradientStop { position: 0.0, color: slint::Color::from_argb_encoded(0xff030303) },
200 );
201 super::add_gradient_stop(
202 model.clone(),
203 ui::GradientStop { position: 0.5, color: slint::Color::from_argb_encoded(0xff050505) },
204 );
205 super::add_gradient_stop(
206 model.clone(),
207 ui::GradientStop { position: 0.0, color: slint::Color::from_argb_encoded(0xff040404) },
208 );
209 super::add_gradient_stop(
210 model.clone(),
211 ui::GradientStop {
212 position: 0.1445,
213 color: slint::Color::from_argb_encoded(0xff060606),
214 },
215 );
216
217 let mut it = model.iter();
218
219 assert_eq!(
220 it.next(),
221 Some(ui::GradientStop {
222 position: 0.0,
223 color: slint::Color::from_argb_encoded(0xff040404)
224 })
225 );
226 assert_eq!(
227 it.next(),
228 Some(ui::GradientStop {
229 position: 0.0,
230 color: slint::Color::from_argb_encoded(0xff030303)
231 })
232 );
233 assert_eq!(
234 it.next(),
235 Some(ui::GradientStop {
236 position: 0.1445,
237 color: slint::Color::from_argb_encoded(0xff060606)
238 })
239 );
240 assert_eq!(
241 it.next(),
242 Some(ui::GradientStop {
243 position: 0.5,
244 color: slint::Color::from_argb_encoded(0xff050505)
245 })
246 );
247 assert_eq!(
248 it.next(),
249 Some(ui::GradientStop {
250 position: 1.0,
251 color: slint::Color::from_argb_encoded(0xff020202)
252 })
253 );
254 assert_eq!(
255 it.next(),
256 Some(ui::GradientStop {
257 position: 1.0,
258 color: slint::Color::from_argb_encoded(0xff010101)
259 })
260 );
261 assert_eq!(it.next(), None);
262
263 super::remove_gradient_stop(model.clone(), 2);
264
265 let mut it = model.iter();
266
267 assert_eq!(
268 it.next(),
269 Some(ui::GradientStop {
270 position: 0.0,
271 color: slint::Color::from_argb_encoded(0xff040404)
272 })
273 );
274 assert_eq!(
275 it.next(),
276 Some(ui::GradientStop {
277 position: 0.0,
278 color: slint::Color::from_argb_encoded(0xff030303)
279 })
280 );
281 assert_eq!(
282 it.next(),
283 Some(ui::GradientStop {
284 position: 0.5,
285 color: slint::Color::from_argb_encoded(0xff050505)
286 })
287 );
288 assert_eq!(
289 it.next(),
290 Some(ui::GradientStop {
291 position: 1.0,
292 color: slint::Color::from_argb_encoded(0xff020202)
293 })
294 );
295 assert_eq!(
296 it.next(),
297 Some(ui::GradientStop {
298 position: 1.0,
299 color: slint::Color::from_argb_encoded(0xff010101)
300 })
301 );
302 assert_eq!(it.next(), None);
303
304 super::remove_gradient_stop(model.clone(), -1);
305
306 let mut it = model.iter();
307
308 assert_eq!(
309 it.next(),
310 Some(ui::GradientStop {
311 position: 0.0,
312 color: slint::Color::from_argb_encoded(0xff040404)
313 })
314 );
315 assert_eq!(
316 it.next(),
317 Some(ui::GradientStop {
318 position: 0.0,
319 color: slint::Color::from_argb_encoded(0xff030303)
320 })
321 );
322 assert_eq!(
323 it.next(),
324 Some(ui::GradientStop {
325 position: 0.5,
326 color: slint::Color::from_argb_encoded(0xff050505)
327 })
328 );
329 assert_eq!(
330 it.next(),
331 Some(ui::GradientStop {
332 position: 1.0,
333 color: slint::Color::from_argb_encoded(0xff020202)
334 })
335 );
336 assert_eq!(
337 it.next(),
338 Some(ui::GradientStop {
339 position: 1.0,
340 color: slint::Color::from_argb_encoded(0xff010101)
341 })
342 );
343 assert_eq!(it.next(), None);
344
345 super::remove_gradient_stop(model.clone(), 42);
346
347 let mut it = model.iter();
348
349 assert_eq!(
350 it.next(),
351 Some(ui::GradientStop {
352 position: 0.0,
353 color: slint::Color::from_argb_encoded(0xff040404)
354 })
355 );
356 assert_eq!(
357 it.next(),
358 Some(ui::GradientStop {
359 position: 0.0,
360 color: slint::Color::from_argb_encoded(0xff030303)
361 })
362 );
363 assert_eq!(
364 it.next(),
365 Some(ui::GradientStop {
366 position: 0.5,
367 color: slint::Color::from_argb_encoded(0xff050505)
368 })
369 );
370 assert_eq!(
371 it.next(),
372 Some(ui::GradientStop {
373 position: 1.0,
374 color: slint::Color::from_argb_encoded(0xff020202)
375 })
376 );
377 assert_eq!(
378 it.next(),
379 Some(ui::GradientStop {
380 position: 1.0,
381 color: slint::Color::from_argb_encoded(0xff010101)
382 })
383 );
384 assert_eq!(it.next(), None);
385
386 super::remove_gradient_stop(model.clone(), 0);
387
388 let mut it = model.iter();
389
390 assert_eq!(
391 it.next(),
392 Some(ui::GradientStop {
393 position: 0.0,
394 color: slint::Color::from_argb_encoded(0xff030303)
395 })
396 );
397 assert_eq!(
398 it.next(),
399 Some(ui::GradientStop {
400 position: 0.5,
401 color: slint::Color::from_argb_encoded(0xff050505)
402 })
403 );
404 assert_eq!(
405 it.next(),
406 Some(ui::GradientStop {
407 position: 1.0,
408 color: slint::Color::from_argb_encoded(0xff020202)
409 })
410 );
411 assert_eq!(
412 it.next(),
413 Some(ui::GradientStop {
414 position: 1.0,
415 color: slint::Color::from_argb_encoded(0xff010101)
416 })
417 );
418 assert_eq!(it.next(), None);
419
420 super::remove_gradient_stop(model.clone(), 3);
421
422 let mut it = model.iter();
423
424 assert_eq!(
425 it.next(),
426 Some(ui::GradientStop {
427 position: 0.0,
428 color: slint::Color::from_argb_encoded(0xff030303)
429 })
430 );
431 assert_eq!(
432 it.next(),
433 Some(ui::GradientStop {
434 position: 0.5,
435 color: slint::Color::from_argb_encoded(0xff050505)
436 })
437 );
438 assert_eq!(
439 it.next(),
440 Some(ui::GradientStop {
441 position: 1.0,
442 color: slint::Color::from_argb_encoded(0xff020202)
443 })
444 );
445 assert_eq!(it.next(), None);
446 }
447
448 fn make_model() -> ModelRc<ui::GradientStop> {
449 let model = make_empty_model();
450 super::add_gradient_stop(
451 model.clone(),
452 ui::GradientStop { position: 0.0, color: slint::Color::from_argb_encoded(0xff040404) },
453 );
454 super::add_gradient_stop(
455 model.clone(),
456 ui::GradientStop { position: 0.0, color: slint::Color::from_argb_encoded(0xff030303) },
457 );
458 super::add_gradient_stop(
459 model.clone(),
460 ui::GradientStop {
461 position: 0.1445,
462 color: slint::Color::from_argb_encoded(0xff060606),
463 },
464 );
465 super::add_gradient_stop(
466 model.clone(),
467 ui::GradientStop { position: 0.5, color: slint::Color::from_argb_encoded(0xff050505) },
468 );
469 super::add_gradient_stop(
470 model.clone(),
471 ui::GradientStop { position: 1.0, color: slint::Color::from_argb_encoded(0xff020202) },
472 );
473 super::add_gradient_stop(
474 model.clone(),
475 ui::GradientStop { position: 1.0, color: slint::Color::from_argb_encoded(0xff010101) },
476 );
477
478 model
479 }
480
481 #[test]
482 fn test_move_gradient_stop() {
483 let model = make_model();
484
485 assert_eq!(super::move_gradient_stop(model.clone(), 3, 0.4), 3);
486 let mut it = model.iter();
487
488 assert_eq!(
489 it.next(),
490 Some(ui::GradientStop {
491 position: 0.0,
492 color: slint::Color::from_argb_encoded(0xff030303)
493 })
494 );
495 assert_eq!(
496 it.next(),
497 Some(ui::GradientStop {
498 position: 0.0,
499 color: slint::Color::from_argb_encoded(0xff040404)
500 })
501 );
502 assert_eq!(
503 it.next(),
504 Some(ui::GradientStop {
505 position: 0.1445,
506 color: slint::Color::from_argb_encoded(0xff060606),
507 }),
508 );
509 assert_eq!(
510 it.next(),
511 Some(ui::GradientStop {
512 position: 0.4,
513 color: slint::Color::from_argb_encoded(0xff050505)
514 })
515 );
516 assert_eq!(
517 it.next(),
518 Some(ui::GradientStop {
519 position: 1.0,
520 color: slint::Color::from_argb_encoded(0xff010101)
521 })
522 );
523 assert_eq!(
524 it.next(),
525 Some(ui::GradientStop {
526 position: 1.0,
527 color: slint::Color::from_argb_encoded(0xff020202)
528 })
529 );
530 assert_eq!(it.next(), None);
531
532 let model = make_model();
533
534 assert_eq!(super::move_gradient_stop(model.clone(), 3, 0.1), 2);
535 let mut it = model.iter();
536
537 assert_eq!(
538 it.next(),
539 Some(ui::GradientStop {
540 position: 0.0,
541 color: slint::Color::from_argb_encoded(0xff030303)
542 })
543 );
544 assert_eq!(
545 it.next(),
546 Some(ui::GradientStop {
547 position: 0.0,
548 color: slint::Color::from_argb_encoded(0xff040404)
549 })
550 );
551 assert_eq!(
552 it.next(),
553 Some(ui::GradientStop {
554 position: 0.1,
555 color: slint::Color::from_argb_encoded(0xff050505)
556 })
557 );
558 assert_eq!(
559 it.next(),
560 Some(ui::GradientStop {
561 position: 0.1445,
562 color: slint::Color::from_argb_encoded(0xff060606),
563 }),
564 );
565 assert_eq!(
566 it.next(),
567 Some(ui::GradientStop {
568 position: 1.0,
569 color: slint::Color::from_argb_encoded(0xff010101)
570 })
571 );
572 assert_eq!(
573 it.next(),
574 Some(ui::GradientStop {
575 position: 1.0,
576 color: slint::Color::from_argb_encoded(0xff020202)
577 })
578 );
579 assert_eq!(it.next(), None);
580
581 let model = make_model();
582
583 assert_eq!(super::move_gradient_stop(model.clone(), 0, 0.05), 1);
584 let mut it = model.iter();
585
586 assert_eq!(
587 it.next(),
588 Some(ui::GradientStop {
589 position: 0.0,
590 color: slint::Color::from_argb_encoded(0xff040404)
591 })
592 );
593 assert_eq!(
594 it.next(),
595 Some(ui::GradientStop {
596 position: 0.05,
597 color: slint::Color::from_argb_encoded(0xff030303)
598 })
599 );
600 assert_eq!(
601 it.next(),
602 Some(ui::GradientStop {
603 position: 0.1445,
604 color: slint::Color::from_argb_encoded(0xff060606),
605 }),
606 );
607 assert_eq!(
608 it.next(),
609 Some(ui::GradientStop {
610 position: 0.5,
611 color: slint::Color::from_argb_encoded(0xff050505)
612 })
613 );
614 assert_eq!(
615 it.next(),
616 Some(ui::GradientStop {
617 position: 1.0,
618 color: slint::Color::from_argb_encoded(0xff010101)
619 })
620 );
621 assert_eq!(
622 it.next(),
623 Some(ui::GradientStop {
624 position: 1.0,
625 color: slint::Color::from_argb_encoded(0xff020202)
626 })
627 );
628 assert_eq!(it.next(), None);
629
630 let model = make_model();
631
632 assert_eq!(super::move_gradient_stop(model.clone(), 3, 0.0), 2);
633 let mut it = model.iter();
634
635 assert_eq!(
636 it.next(),
637 Some(ui::GradientStop {
638 position: 0.0,
639 color: slint::Color::from_argb_encoded(0xff030303)
640 })
641 );
642 assert_eq!(
643 it.next(),
644 Some(ui::GradientStop {
645 position: 0.0,
646 color: slint::Color::from_argb_encoded(0xff040404)
647 })
648 );
649 assert_eq!(
650 it.next(),
651 Some(ui::GradientStop {
652 position: 0.0,
653 color: slint::Color::from_argb_encoded(0xff050505)
654 })
655 );
656 assert_eq!(
657 it.next(),
658 Some(ui::GradientStop {
659 position: 0.1445,
660 color: slint::Color::from_argb_encoded(0xff060606),
661 }),
662 );
663 assert_eq!(
664 it.next(),
665 Some(ui::GradientStop {
666 position: 1.0,
667 color: slint::Color::from_argb_encoded(0xff010101)
668 })
669 );
670 assert_eq!(
671 it.next(),
672 Some(ui::GradientStop {
673 position: 1.0,
674 color: slint::Color::from_argb_encoded(0xff020202)
675 })
676 );
677 assert_eq!(it.next(), None);
678
679 let model = make_model();
680
681 assert_eq!(super::move_gradient_stop(model.clone(), 3, 1.0), 3);
682 let mut it = model.iter();
683
684 assert_eq!(
685 it.next(),
686 Some(ui::GradientStop {
687 position: 0.0,
688 color: slint::Color::from_argb_encoded(0xff030303)
689 })
690 );
691 assert_eq!(
692 it.next(),
693 Some(ui::GradientStop {
694 position: 0.0,
695 color: slint::Color::from_argb_encoded(0xff040404)
696 })
697 );
698 assert_eq!(
699 it.next(),
700 Some(ui::GradientStop {
701 position: 0.1445,
702 color: slint::Color::from_argb_encoded(0xff060606),
703 }),
704 );
705 assert_eq!(
706 it.next(),
707 Some(ui::GradientStop {
708 position: 1.0,
709 color: slint::Color::from_argb_encoded(0xff050505)
710 })
711 );
712 assert_eq!(
713 it.next(),
714 Some(ui::GradientStop {
715 position: 1.0,
716 color: slint::Color::from_argb_encoded(0xff010101)
717 })
718 );
719 assert_eq!(
720 it.next(),
721 Some(ui::GradientStop {
722 position: 1.0,
723 color: slint::Color::from_argb_encoded(0xff020202)
724 })
725 );
726 assert_eq!(it.next(), None);
727 }
728}
729