1use crate::animation::{Animate, Transition};
5use crate::geometry::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)]
359pub struct Style {
360 pub background: Option<Color>,
362 pub gradient: Option<Gradient>,
364 pub shadow: Option<Shadow>,
366 pub color: Option<Color>,
368 pub font: Option<Font>,
370 pub padding: Option<Padding>,
372 pub radius: Option<f32>,
374 pub border: Option<Border>,
376 pub opacity: Option<f32>,
379 pub clip: Option<bool>,
382 pub width: Option<f32>,
384 pub height: Option<f32>,
386 pub transition: Option<Transition>,
390}
391
392impl Style {
393 pub const fn new() -> Style {
395 Style {
396 background: None,
397 gradient: None,
398 shadow: None,
399 color: None,
400 font: None,
401 padding: None,
402 radius: None,
403 border: None,
404 opacity: None,
405 clip: None,
406 width: None,
407 height: None,
408 transition: None,
409 }
410 }
411
412 pub const fn background(self, color: Color) -> Style {
414 Style {
415 background: Some(color),
416 ..self
417 }
418 }
419
420 pub const fn gradient(self, gradient: Gradient) -> Style {
422 Style {
423 gradient: Some(gradient),
424 ..self
425 }
426 }
427
428 pub const fn shadow(self, shadow: Shadow) -> Style {
430 Style {
431 shadow: Some(shadow),
432 ..self
433 }
434 }
435
436 pub const fn color(self, color: Color) -> Style {
438 Style {
439 color: Some(color),
440 ..self
441 }
442 }
443
444 pub const fn font(self, font: Font) -> Style {
446 Style {
447 font: Some(font),
448 ..self
449 }
450 }
451
452 pub const fn padding(self, vertical: u32, horizontal: u32) -> Style {
455 Style {
456 padding: Some(Padding {
457 top: vertical,
458 right: horizontal,
459 bottom: vertical,
460 left: horizontal,
461 }),
462 ..self
463 }
464 }
465
466 pub const fn padding_top(self, top: u32) -> Style {
477 let mut padding = self.sides();
478 padding.top = top;
479 Style {
480 padding: Some(padding),
481 ..self
482 }
483 }
484
485 pub const fn padding_right(self, right: u32) -> Style {
488 let mut padding = self.sides();
489 padding.right = right;
490 Style {
491 padding: Some(padding),
492 ..self
493 }
494 }
495
496 pub const fn padding_bottom(self, bottom: u32) -> Style {
499 let mut padding = self.sides();
500 padding.bottom = bottom;
501 Style {
502 padding: Some(padding),
503 ..self
504 }
505 }
506
507 pub const fn padding_left(self, left: u32) -> Style {
510 let mut padding = self.sides();
511 padding.left = left;
512 Style {
513 padding: Some(padding),
514 ..self
515 }
516 }
517
518 const fn sides(self) -> Padding {
520 match self.padding {
521 Some(padding) => padding,
522 None => Padding {
523 top: 0,
524 right: 0,
525 bottom: 0,
526 left: 0,
527 },
528 }
529 }
530
531 pub const fn radius(self, radius: f32) -> Style {
533 Style {
534 radius: Some(radius),
535 ..self
536 }
537 }
538
539 pub const fn border(self, width: f32, color: Color) -> Style {
541 Style {
542 border: Some(Border { width, color }),
543 ..self
544 }
545 }
546
547 pub const fn opacity(self, opacity: f32) -> Style {
549 Style {
550 opacity: Some(opacity),
551 ..self
552 }
553 }
554
555 pub const fn clip(self, clip: bool) -> Style {
558 Style {
559 clip: Some(clip),
560 ..self
561 }
562 }
563
564 pub const fn width(self, width: f32) -> Style {
566 Style {
567 width: Some(width),
568 ..self
569 }
570 }
571
572 pub const fn height(self, height: f32) -> Style {
575 Style {
576 height: Some(height),
577 ..self
578 }
579 }
580
581 pub const fn transition(self, transition: Transition) -> Style {
586 Style {
587 transition: Some(transition),
588 ..self
589 }
590 }
591
592 pub const fn merge(self, over: Style) -> Style {
596 Style {
597 background: or(over.background, self.background),
598 gradient: or(over.gradient, self.gradient),
599 shadow: or(over.shadow, self.shadow),
600 color: or(over.color, self.color),
601 font: or(over.font, self.font),
602 padding: or(over.padding, self.padding),
603 radius: or(over.radius, self.radius),
604 border: or(over.border, self.border),
605 opacity: or(over.opacity, self.opacity),
606 clip: or(over.clip, self.clip),
607 width: or(over.width, self.width),
608 height: or(over.height, self.height),
609 transition: or(over.transition, self.transition),
610 }
611 }
612
613 pub fn clips(&self) -> bool {
615 self.clip == Some(true)
616 }
617
618 pub(crate) fn same_layout(&self, other: &Style) -> bool {
621 self.font == other.font
622 && self.padding == other.padding
623 && self.width == other.width
624 && self.height == other.height
625 && self.clips() == other.clips()
626 }
627}
628
629const fn or<T: Copy>(first: Option<T>, second: Option<T>) -> Option<T> {
631 match first {
632 Some(value) => Some(value),
633 None => second,
634 }
635}
636
637impl Style {
638 pub(crate) fn lerp(&self, to: &Style, t: f32) -> Style {
645 let color = |from: Option<Color>, to: Option<Color>| match (from, to) {
646 (Some(from), Some(to)) => Some(from.lerp(&to, t)),
647 (None, Some(to)) => Some(Color::rgba(to.r, to.g, to.b, 0).lerp(&to, t)),
648 (Some(from), None) => Some(from.lerp(&Color::rgba(from.r, from.g, from.b, 0), t)),
649 (None, None) => None,
650 };
651 let border = match (self.border, to.border) {
652 (None, None) => None,
653 (from, to) => {
654 let from = from.or(to.map(|b| Border {
655 width: 0.0,
656 color: Color::rgba(b.color.r, b.color.g, b.color.b, 0),
657 }));
658 let to = to.or(from.map(|b| Border {
659 width: 0.0,
660 color: Color::rgba(b.color.r, b.color.g, b.color.b, 0),
661 }));
662 match (from, to) {
663 (Some(from), Some(to)) => Some(Border {
664 width: from.width.lerp(&to.width, t),
665 color: from.color.lerp(&to.color, t),
666 }),
667 _ => None,
668 }
669 }
670 };
671 let number = |from: Option<f32>, to: Option<f32>, none: f32| match (from, to) {
672 (None, None) => None,
673 (from, to) => Some(from.unwrap_or(none).lerp(&to.unwrap_or(none), t)),
674 };
675 let gradient = match (self.gradient, to.gradient) {
676 (Some(from), Some(end)) => from.lerp(&end, t).or(to.gradient),
677 (None, Some(end)) => end.clear().lerp(&end, t),
678 (Some(from), None) => from.lerp(&from.clear(), t),
679 (None, None) => None,
680 };
681 let shadow = match (self.shadow, to.shadow) {
682 (Some(from), Some(end)) => Some(from.lerp(&end, t)),
683 (None, Some(end)) => Some(end.clear().lerp(&end, t)),
684 (Some(from), None) => Some(from.lerp(&from.clear(), t)),
685 (None, None) => None,
686 };
687 Style {
688 background: color(self.background, to.background),
689 gradient,
690 shadow,
691 color: match (self.color, to.color) {
692 (Some(from), Some(to)) => Some(from.lerp(&to, t)),
693 _ => to.color,
694 },
695 border,
696 radius: number(self.radius, to.radius, 0.0),
697 opacity: number(self.opacity, to.opacity, 1.0),
698 ..*to
699 }
700 }
701}