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)]
326pub struct Padding {
327 pub vertical: u32,
329 pub horizontal: u32,
331}
332
333#[derive(Clone, Copy, Debug, Default, PartialEq)]
354pub struct Style {
355 pub background: Option<Color>,
357 pub gradient: Option<Gradient>,
359 pub shadow: Option<Shadow>,
361 pub color: Option<Color>,
363 pub font: Option<Font>,
365 pub padding: Option<Padding>,
367 pub radius: Option<f32>,
369 pub border: Option<Border>,
371 pub opacity: Option<f32>,
374 pub clip: Option<bool>,
377 pub width: Option<f32>,
379 pub height: Option<f32>,
381 pub transition: Option<Transition>,
385}
386
387impl Style {
388 pub const fn new() -> Style {
390 Style {
391 background: None,
392 gradient: None,
393 shadow: None,
394 color: None,
395 font: None,
396 padding: None,
397 radius: None,
398 border: None,
399 opacity: None,
400 clip: None,
401 width: None,
402 height: None,
403 transition: None,
404 }
405 }
406
407 pub const fn background(self, color: Color) -> Style {
409 Style {
410 background: Some(color),
411 ..self
412 }
413 }
414
415 pub const fn gradient(self, gradient: Gradient) -> Style {
417 Style {
418 gradient: Some(gradient),
419 ..self
420 }
421 }
422
423 pub const fn shadow(self, shadow: Shadow) -> Style {
425 Style {
426 shadow: Some(shadow),
427 ..self
428 }
429 }
430
431 pub const fn color(self, color: Color) -> Style {
433 Style {
434 color: Some(color),
435 ..self
436 }
437 }
438
439 pub const fn font(self, font: Font) -> Style {
441 Style {
442 font: Some(font),
443 ..self
444 }
445 }
446
447 pub const fn padding(self, vertical: u32, horizontal: u32) -> Style {
450 Style {
451 padding: Some(Padding {
452 vertical,
453 horizontal,
454 }),
455 ..self
456 }
457 }
458
459 pub const fn radius(self, radius: f32) -> Style {
461 Style {
462 radius: Some(radius),
463 ..self
464 }
465 }
466
467 pub const fn border(self, width: f32, color: Color) -> Style {
469 Style {
470 border: Some(Border { width, color }),
471 ..self
472 }
473 }
474
475 pub const fn opacity(self, opacity: f32) -> Style {
477 Style {
478 opacity: Some(opacity),
479 ..self
480 }
481 }
482
483 pub const fn clip(self, clip: bool) -> Style {
486 Style {
487 clip: Some(clip),
488 ..self
489 }
490 }
491
492 pub const fn width(self, width: f32) -> Style {
494 Style {
495 width: Some(width),
496 ..self
497 }
498 }
499
500 pub const fn height(self, height: f32) -> Style {
503 Style {
504 height: Some(height),
505 ..self
506 }
507 }
508
509 pub const fn transition(self, transition: Transition) -> Style {
514 Style {
515 transition: Some(transition),
516 ..self
517 }
518 }
519
520 pub const fn merge(self, over: Style) -> Style {
524 Style {
525 background: or(over.background, self.background),
526 gradient: or(over.gradient, self.gradient),
527 shadow: or(over.shadow, self.shadow),
528 color: or(over.color, self.color),
529 font: or(over.font, self.font),
530 padding: or(over.padding, self.padding),
531 radius: or(over.radius, self.radius),
532 border: or(over.border, self.border),
533 opacity: or(over.opacity, self.opacity),
534 clip: or(over.clip, self.clip),
535 width: or(over.width, self.width),
536 height: or(over.height, self.height),
537 transition: or(over.transition, self.transition),
538 }
539 }
540
541 pub fn clips(&self) -> bool {
543 self.clip == Some(true)
544 }
545
546 pub(crate) fn same_layout(&self, other: &Style) -> bool {
549 self.font == other.font
550 && self.padding == other.padding
551 && self.width == other.width
552 && self.height == other.height
553 && self.clips() == other.clips()
554 }
555}
556
557const fn or<T: Copy>(first: Option<T>, second: Option<T>) -> Option<T> {
559 match first {
560 Some(value) => Some(value),
561 None => second,
562 }
563}
564
565impl Style {
566 pub(crate) fn lerp(&self, to: &Style, t: f32) -> Style {
573 let color = |from: Option<Color>, to: Option<Color>| match (from, to) {
574 (Some(from), Some(to)) => Some(from.lerp(&to, t)),
575 (None, Some(to)) => Some(Color::rgba(to.r, to.g, to.b, 0).lerp(&to, t)),
576 (Some(from), None) => Some(from.lerp(&Color::rgba(from.r, from.g, from.b, 0), t)),
577 (None, None) => None,
578 };
579 let border = match (self.border, to.border) {
580 (None, None) => None,
581 (from, to) => {
582 let from = from.or(to.map(|b| Border {
583 width: 0.0,
584 color: Color::rgba(b.color.r, b.color.g, b.color.b, 0),
585 }));
586 let to = to.or(from.map(|b| Border {
587 width: 0.0,
588 color: Color::rgba(b.color.r, b.color.g, b.color.b, 0),
589 }));
590 match (from, to) {
591 (Some(from), Some(to)) => Some(Border {
592 width: from.width.lerp(&to.width, t),
593 color: from.color.lerp(&to.color, t),
594 }),
595 _ => None,
596 }
597 }
598 };
599 let number = |from: Option<f32>, to: Option<f32>, none: f32| match (from, to) {
600 (None, None) => None,
601 (from, to) => Some(from.unwrap_or(none).lerp(&to.unwrap_or(none), t)),
602 };
603 let gradient = match (self.gradient, to.gradient) {
604 (Some(from), Some(end)) => from.lerp(&end, t).or(to.gradient),
605 (None, Some(end)) => end.clear().lerp(&end, t),
606 (Some(from), None) => from.lerp(&from.clear(), t),
607 (None, None) => None,
608 };
609 let shadow = match (self.shadow, to.shadow) {
610 (Some(from), Some(end)) => Some(from.lerp(&end, t)),
611 (None, Some(end)) => Some(end.clear().lerp(&end, t)),
612 (Some(from), None) => Some(from.lerp(&from.clear(), t)),
613 (None, None) => None,
614 };
615 Style {
616 background: color(self.background, to.background),
617 gradient,
618 shadow,
619 color: match (self.color, to.color) {
620 (Some(from), Some(to)) => Some(from.lerp(&to, t)),
621 _ => to.color,
622 },
623 border,
624 radius: number(self.radius, to.radius, 0.0),
625 opacity: number(self.opacity, to.opacity, 1.0),
626 ..*to
627 }
628 }
629}