1use crate::animation::{Animate, Transition};
5use crate::geometry::{Affine, Point, Rect};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub struct Color {
10 pub r: u8,
12 pub g: u8,
14 pub b: u8,
16 pub a: u8,
18}
19
20impl Color {
21 pub const fn rgb(r: u8, g: u8, b: u8) -> Color {
23 Color { r, g, b, a: 255 }
24 }
25
26 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Color {
28 Color { r, g, b, a }
29 }
30
31 pub const fn hex(hex: &str) -> Color {
41 let bytes = hex.as_bytes();
42 if bytes.is_empty() || bytes[0] != b'#' || (bytes.len() != 7 && bytes.len() != 9) {
43 panic!("Color::hex takes \"#rrggbb\" or \"#rrggbbaa\"");
44 }
45 let a = if bytes.len() == 9 {
46 hex_byte(bytes[7], bytes[8])
47 } else {
48 255
49 };
50 Color {
51 r: hex_byte(bytes[1], bytes[2]),
52 g: hex_byte(bytes[3], bytes[4]),
53 b: hex_byte(bytes[5], bytes[6]),
54 a,
55 }
56 }
57}
58
59const fn hex_byte(high: u8, low: u8) -> u8 {
60 hex_digit(high) * 16 + hex_digit(low)
61}
62
63const fn hex_digit(digit: u8) -> u8 {
64 match digit {
65 b'0'..=b'9' => digit - b'0',
66 b'a'..=b'f' => digit - b'a' + 10,
67 b'A'..=b'F' => digit - b'A' + 10,
68 _ => panic!("Color::hex found a character that isn't a hex digit"),
69 }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq)]
77pub struct Font {
78 pub family: &'static str,
80 pub size: f32,
82 pub weight: u16,
85}
86
87impl Font {
88 pub const DEFAULT: Font = Font::new("sans-serif", 13.0);
90
91 pub const fn new(family: &'static str, size: f32) -> Font {
93 Font {
94 family,
95 size,
96 weight: 400,
97 }
98 }
99
100 pub const fn weight(self, weight: u16) -> Font {
102 Font { weight, ..self }
103 }
104}
105
106#[derive(Clone, Copy, Debug, PartialEq)]
108pub struct Border {
109 pub width: f32,
111 pub color: Color,
113}
114
115#[derive(Clone, Copy, Debug, PartialEq)]
127pub struct Gradient {
128 pub shape: GradientShape,
130 stops: [(f32, Color); 4],
133 count: u8,
134}
135
136#[derive(Clone, Copy, Debug, PartialEq)]
138pub enum GradientShape {
139 Linear(f32),
143 Radial,
145}
146
147impl Gradient {
148 pub const fn linear(angle: f32, from: Color, to: Color) -> Gradient {
151 Gradient::new(GradientShape::Linear(angle), from, to)
152 }
153
154 pub const fn radial(from: Color, to: Color) -> Gradient {
156 Gradient::new(GradientShape::Radial, from, to)
157 }
158
159 const fn new(shape: GradientShape, from: Color, to: Color) -> Gradient {
160 Gradient {
161 shape,
162 stops: [(0.0, from), (1.0, to), (1.0, to), (1.0, to)],
163 count: 2,
164 }
165 }
166
167 pub const fn stop(self, at: f32, color: Color) -> Gradient {
171 assert!(self.count < 4, "a gradient has at most four colors");
172 let mut stops = self.stops;
173 let last = self.count as usize - 1;
174 stops[last + 1] = stops[last];
175 stops[last] = (at, color);
176 Gradient {
177 stops,
178 count: self.count + 1,
179 ..self
180 }
181 }
182
183 pub fn stops(&self) -> &[(f32, Color)] {
185 &self.stops[..usize::from(self.count)]
186 }
187
188 fn lerp(&self, to: &Gradient, t: f32) -> Option<Gradient> {
191 let shape = match (self.shape, to.shape) {
192 (GradientShape::Linear(from), GradientShape::Linear(to)) => {
193 GradientShape::Linear(from.lerp(&to, t))
194 }
195 (GradientShape::Radial, GradientShape::Radial) => GradientShape::Radial,
196 _ => return None,
197 };
198 if self.count != to.count {
199 return None;
200 }
201 let mut stops = to.stops;
202 for (stop, (from, to)) in stops.iter_mut().zip(self.stops.iter().zip(to.stops)) {
203 *stop = (from.0.lerp(&to.0, t), from.1.lerp(&to.1, t));
204 }
205 Some(Gradient {
206 shape,
207 stops,
208 count: to.count,
209 })
210 }
211
212 fn clear(mut self) -> Gradient {
214 for (_, color) in &mut self.stops {
215 color.a = 0;
216 }
217 self
218 }
219}
220
221#[derive(Clone, Copy, Debug, PartialEq)]
236pub struct Shadow {
237 pub x: f32,
239 pub y: f32,
241 pub blur: f32,
243 pub spread: f32,
246 pub color: Color,
248 pub cut_out: bool,
250}
251
252impl Shadow {
253 pub const fn new(x: f32, y: f32, blur: f32, color: Color) -> Shadow {
256 Shadow {
257 x,
258 y,
259 blur,
260 spread: 0.0,
261 color,
262 cut_out: false,
263 }
264 }
265
266 pub const fn spread(self, spread: f32) -> Shadow {
268 Shadow { spread, ..self }
269 }
270
271 pub const fn cut_out(self) -> Shadow {
276 Shadow {
277 cut_out: true,
278 ..self
279 }
280 }
281
282 pub(crate) fn reach(&self, rect: Rect) -> Rect {
288 let grow = self.spread + 1.5 * self.blur.max(0.0);
289 if ![self.x, self.y, grow].iter().all(|n| n.is_finite()) {
290 return rect;
291 }
292 let (left, right) = (rect.x + self.x - grow, rect.right() + self.x + grow);
293 let (top, bottom) = (rect.y + self.y - grow, rect.bottom() + self.y + grow);
294 Rect::new(
295 left.min(right),
296 top.min(bottom),
297 (right - left).abs(),
298 (bottom - top).abs(),
299 )
300 }
301
302 fn lerp(&self, to: &Shadow, t: f32) -> Shadow {
304 Shadow {
305 x: self.x.lerp(&to.x, t),
306 y: self.y.lerp(&to.y, t),
307 blur: self.blur.lerp(&to.blur, t),
308 spread: self.spread.lerp(&to.spread, t),
309 color: self.color.lerp(&to.color, t),
310 cut_out: to.cut_out,
312 }
313 }
314
315 fn clear(self) -> Shadow {
317 Shadow {
318 color: Color::rgba(self.color.r, self.color.g, self.color.b, 0),
319 ..self
320 }
321 }
322}
323
324#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
327pub struct Padding {
328 pub top: u32,
330 pub right: u32,
332 pub bottom: u32,
334 pub left: u32,
336}
337
338#[derive(Clone, Copy, Debug, Default, PartialEq)]
364pub struct Style {
365 pub background: Option<Color>,
367 pub gradient: Option<Gradient>,
369 pub shadow: Option<Shadow>,
371 pub color: Option<Color>,
373 pub font: Option<Font>,
375 pub padding: Option<Padding>,
377 pub radius: Option<f32>,
379 pub border: Option<Border>,
381 pub opacity: Option<f32>,
384 pub clip: Option<bool>,
387 pub width: Option<f32>,
389 pub height: Option<f32>,
391 pub translate: Option<Point>,
394 pub scale: Option<f32>,
397 pub rotate: Option<f32>,
399 pub origin: Option<Point>,
403 pub transition: Option<Transition>,
407}
408
409impl Style {
410 pub const fn new() -> Style {
412 Style {
413 background: None,
414 gradient: None,
415 shadow: None,
416 color: None,
417 font: None,
418 padding: None,
419 radius: None,
420 border: None,
421 opacity: None,
422 clip: None,
423 width: None,
424 height: None,
425 translate: None,
426 scale: None,
427 rotate: None,
428 origin: None,
429 transition: None,
430 }
431 }
432
433 pub const fn background(self, color: Color) -> Style {
435 Style {
436 background: Some(color),
437 ..self
438 }
439 }
440
441 pub const fn gradient(self, gradient: Gradient) -> Style {
443 Style {
444 gradient: Some(gradient),
445 ..self
446 }
447 }
448
449 pub const fn shadow(self, shadow: Shadow) -> Style {
451 Style {
452 shadow: Some(shadow),
453 ..self
454 }
455 }
456
457 pub const fn color(self, color: Color) -> Style {
459 Style {
460 color: Some(color),
461 ..self
462 }
463 }
464
465 pub const fn font(self, font: Font) -> Style {
467 Style {
468 font: Some(font),
469 ..self
470 }
471 }
472
473 pub const fn padding(self, vertical: u32, horizontal: u32) -> Style {
476 Style {
477 padding: Some(Padding {
478 top: vertical,
479 right: horizontal,
480 bottom: vertical,
481 left: horizontal,
482 }),
483 ..self
484 }
485 }
486
487 pub const fn padding_top(self, top: u32) -> Style {
498 let mut padding = self.sides();
499 padding.top = top;
500 Style {
501 padding: Some(padding),
502 ..self
503 }
504 }
505
506 pub const fn padding_right(self, right: u32) -> Style {
509 let mut padding = self.sides();
510 padding.right = right;
511 Style {
512 padding: Some(padding),
513 ..self
514 }
515 }
516
517 pub const fn padding_bottom(self, bottom: u32) -> Style {
520 let mut padding = self.sides();
521 padding.bottom = bottom;
522 Style {
523 padding: Some(padding),
524 ..self
525 }
526 }
527
528 pub const fn padding_left(self, left: u32) -> Style {
531 let mut padding = self.sides();
532 padding.left = left;
533 Style {
534 padding: Some(padding),
535 ..self
536 }
537 }
538
539 const fn sides(self) -> Padding {
541 match self.padding {
542 Some(padding) => padding,
543 None => Padding {
544 top: 0,
545 right: 0,
546 bottom: 0,
547 left: 0,
548 },
549 }
550 }
551
552 pub const fn radius(self, radius: f32) -> Style {
554 Style {
555 radius: Some(radius),
556 ..self
557 }
558 }
559
560 pub const fn border(self, width: f32, color: Color) -> Style {
562 Style {
563 border: Some(Border { width, color }),
564 ..self
565 }
566 }
567
568 pub const fn opacity(self, opacity: f32) -> Style {
570 Style {
571 opacity: Some(opacity),
572 ..self
573 }
574 }
575
576 pub const fn clip(self, clip: bool) -> Style {
579 Style {
580 clip: Some(clip),
581 ..self
582 }
583 }
584
585 pub const fn width(self, width: f32) -> Style {
587 Style {
588 width: Some(width),
589 ..self
590 }
591 }
592
593 pub const fn height(self, height: f32) -> Style {
596 Style {
597 height: Some(height),
598 ..self
599 }
600 }
601
602 pub const fn translate(self, x: f32, y: f32) -> Style {
612 Style {
613 translate: Some(Point::new(x, y)),
614 ..self
615 }
616 }
617
618 pub const fn scale(self, scale: f32) -> Style {
623 Style {
624 scale: Some(scale),
625 ..self
626 }
627 }
628
629 pub const fn rotate(self, degrees: f32) -> Style {
633 Style {
634 rotate: Some(degrees),
635 ..self
636 }
637 }
638
639 pub const fn origin(self, x: f32, y: f32) -> Style {
643 Style {
644 origin: Some(Point::new(x, y)),
645 ..self
646 }
647 }
648
649 pub const fn transition(self, transition: Transition) -> Style {
654 Style {
655 transition: Some(transition),
656 ..self
657 }
658 }
659
660 pub const fn merge(self, over: Style) -> Style {
664 Style {
665 background: or(over.background, self.background),
666 gradient: or(over.gradient, self.gradient),
667 shadow: or(over.shadow, self.shadow),
668 color: or(over.color, self.color),
669 font: or(over.font, self.font),
670 padding: or(over.padding, self.padding),
671 radius: or(over.radius, self.radius),
672 border: or(over.border, self.border),
673 opacity: or(over.opacity, self.opacity),
674 clip: or(over.clip, self.clip),
675 width: or(over.width, self.width),
676 height: or(over.height, self.height),
677 translate: or(over.translate, self.translate),
678 scale: or(over.scale, self.scale),
679 rotate: or(over.rotate, self.rotate),
680 origin: or(over.origin, self.origin),
681 transition: or(over.transition, self.transition),
682 }
683 }
684
685 pub fn clips(&self) -> bool {
687 self.clip == Some(true)
688 }
689
690 pub(crate) fn same_layout(&self, other: &Style) -> bool {
693 self.font == other.font
694 && self.padding == other.padding
695 && self.width == other.width
696 && self.height == other.height
697 && self.clips() == other.clips()
698 }
699
700 pub(crate) fn same_transform(&self, other: &Style) -> bool {
703 self.translate == other.translate
704 && self.scale == other.scale
705 && self.rotate == other.rotate
706 && self.origin == other.origin
707 }
708
709 pub(crate) fn transform(&self, rect: Rect) -> Option<Affine> {
714 let finite = |value: f32, default: f32| if value.is_finite() { value } else { default };
715 let translate = self.translate.unwrap_or_default();
716 let (x, y) = (finite(translate.x, 0.0), finite(translate.y, 0.0));
717 let scale = finite(self.scale.unwrap_or(1.0), 1.0);
718 let degrees = finite(self.rotate.unwrap_or(0.0), 0.0).rem_euclid(360.0);
719 if x == 0.0 && y == 0.0 && scale == 1.0 && degrees == 0.0 {
720 return None;
721 }
722 let (sin, cos) = match degrees {
724 90.0 => (1.0, 0.0),
725 180.0 => (0.0, -1.0),
726 270.0 => (-1.0, 0.0),
727 _ => degrees.to_radians().sin_cos(),
728 };
729 let origin = self.origin.unwrap_or(Point::new(0.5, 0.5));
730 let ox = rect.x + finite(origin.x, 0.5) * rect.width;
731 let oy = rect.y + finite(origin.y, 0.5) * rect.height;
732 let (sx, ky, kx, sy) = (cos * scale, sin * scale, -sin * scale, cos * scale);
734 Some(Affine {
737 sx,
738 ky,
739 kx,
740 sy,
741 tx: ox + x - (sx * ox + kx * oy),
742 ty: oy + y - (ky * ox + sy * oy),
743 })
744 }
745}
746
747const fn or<T: Copy>(first: Option<T>, second: Option<T>) -> Option<T> {
749 match first {
750 Some(value) => Some(value),
751 None => second,
752 }
753}
754
755impl Style {
756 pub(crate) fn lerp(&self, to: &Style, t: f32) -> Style {
764 let color = |from: Option<Color>, to: Option<Color>| match (from, to) {
765 (Some(from), Some(to)) => Some(from.lerp(&to, t)),
766 (None, Some(to)) => Some(Color::rgba(to.r, to.g, to.b, 0).lerp(&to, t)),
767 (Some(from), None) => Some(from.lerp(&Color::rgba(from.r, from.g, from.b, 0), t)),
768 (None, None) => None,
769 };
770 let border = match (self.border, to.border) {
771 (None, None) => None,
772 (from, to) => {
773 let from = from.or(to.map(|b| Border {
774 width: 0.0,
775 color: Color::rgba(b.color.r, b.color.g, b.color.b, 0),
776 }));
777 let to = to.or(from.map(|b| Border {
778 width: 0.0,
779 color: Color::rgba(b.color.r, b.color.g, b.color.b, 0),
780 }));
781 match (from, to) {
782 (Some(from), Some(to)) => Some(Border {
783 width: from.width.lerp(&to.width, t),
784 color: from.color.lerp(&to.color, t),
785 }),
786 _ => None,
787 }
788 }
789 };
790 let number = |from: Option<f32>, to: Option<f32>, none: f32| match (from, to) {
791 (None, None) => None,
792 (from, to) => Some(from.unwrap_or(none).lerp(&to.unwrap_or(none), t)),
793 };
794 let point = |from: Option<Point>, to: Option<Point>, none: Point| match (from, to) {
795 (None, None) => None,
796 (from, to) => Some(from.unwrap_or(none).lerp(&to.unwrap_or(none), t)),
797 };
798 let gradient = match (self.gradient, to.gradient) {
799 (Some(from), Some(end)) => from.lerp(&end, t).or(to.gradient),
800 (None, Some(end)) => end.clear().lerp(&end, t),
801 (Some(from), None) => from.lerp(&from.clear(), t),
802 (None, None) => None,
803 };
804 let shadow = match (self.shadow, to.shadow) {
805 (Some(from), Some(end)) => Some(from.lerp(&end, t)),
806 (None, Some(end)) => Some(end.clear().lerp(&end, t)),
807 (Some(from), None) => Some(from.lerp(&from.clear(), t)),
808 (None, None) => None,
809 };
810 Style {
811 background: color(self.background, to.background),
812 gradient,
813 shadow,
814 color: match (self.color, to.color) {
815 (Some(from), Some(to)) => Some(from.lerp(&to, t)),
816 _ => to.color,
817 },
818 border,
819 radius: number(self.radius, to.radius, 0.0),
820 opacity: number(self.opacity, to.opacity, 1.0),
821 translate: point(self.translate, to.translate, Point::default()),
822 scale: number(self.scale, to.scale, 1.0),
823 rotate: number(self.rotate, to.rotate, 0.0),
824 origin: point(self.origin, to.origin, Point::new(0.5, 0.5)),
825 ..*to
826 }
827 }
828}