1use std::ops::{Add, Sub};
4
5use palette::{FromColor, Hsl, Hsv, LinSrgba, Srgba, WithAlpha};
6
7pub use kurbo::Affine;
8use kurbo::{Point, Vec2};
9
10#[derive(Clone, Copy, Debug, Default, PartialEq)]
11pub struct Size {
12 pub width: f32,
13 pub height: f32,
14}
15
16impl Size {
17 pub const ZERO: Self = Self {
18 width: 0.0,
19 height: 0.0,
20 };
21
22 #[must_use]
23 pub fn new(width: f32, height: f32) -> Self {
24 assert!(
25 width.is_finite() && height.is_finite() && width >= 0.0 && height >= 0.0,
26 "sizes must be finite and non-negative"
27 );
28 Self { width, height }
29 }
30}
31
32#[derive(Clone, Copy, Debug, Default, PartialEq)]
33pub struct Offset {
34 pub x: f32,
35 pub y: f32,
36}
37
38impl Offset {
39 pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
40
41 #[must_use]
42 pub const fn new(x: f32, y: f32) -> Self {
43 Self { x, y }
44 }
45
46 #[must_use]
48 pub fn finite_or_zero(self) -> Self {
49 Self::new(finite_or_zero(self.x), finite_or_zero(self.y))
50 }
51}
52
53#[inline]
56#[must_use]
57pub fn finite_or_zero(value: f32) -> f32 {
58 if value.is_finite() { value } else { 0.0 }
59}
60
61#[inline]
63#[must_use]
64pub fn finite_non_negative(value: f32) -> f32 {
65 finite_or_zero(value).max(0.0)
66}
67
68impl Add for Offset {
69 type Output = Self;
70
71 fn add(self, rhs: Self) -> Self {
72 Self::new(self.x + rhs.x, self.y + rhs.y)
73 }
74}
75
76impl Sub for Offset {
77 type Output = Self;
78
79 fn sub(self, rhs: Self) -> Self {
80 Self::new(self.x - rhs.x, self.y - rhs.y)
81 }
82}
83
84#[derive(Clone, Copy, Debug, Default, PartialEq)]
85pub struct Rect {
86 pub origin: Offset,
87 pub size: Size,
88}
89
90impl Rect {
91 #[must_use]
92 pub fn from_origin_size(origin: Offset, size: Size) -> Self {
93 Self { origin, size }
94 }
95
96 #[must_use]
97 pub fn contains(self, point: Offset) -> bool {
98 point.x >= self.origin.x
99 && point.x <= self.origin.x + self.size.width
100 && point.y >= self.origin.y
101 && point.y <= self.origin.y + self.size.height
102 }
103
104 #[must_use]
105 pub fn intersects(self, other: Self) -> bool {
106 self.origin.x < other.origin.x + other.size.width
107 && other.origin.x < self.origin.x + self.size.width
108 && self.origin.y < other.origin.y + other.size.height
109 && other.origin.y < self.origin.y + self.size.height
110 }
111
112 #[must_use]
113 pub fn intersection(self, other: Self) -> Option<Self> {
114 let left = self.origin.x.max(other.origin.x);
115 let top = self.origin.y.max(other.origin.y);
116 let right = (self.origin.x + self.size.width).min(other.origin.x + other.size.width);
117 let bottom = (self.origin.y + self.size.height).min(other.origin.y + other.size.height);
118 (right >= left && bottom >= top).then(|| {
119 Self::from_origin_size(
120 Offset::new(left, top),
121 Size::new(right - left, bottom - top),
122 )
123 })
124 }
125}
126
127#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
128pub struct Color {
129 pub red: u8,
130 pub green: u8,
131 pub blue: u8,
132 pub alpha: u8,
133}
134
135impl Color {
136 pub const TRANSPARENT: Self = Self::rgba(0, 0, 0, 0);
137 pub const WHITE: Self = Self::rgba(255, 255, 255, 255);
138 pub const BLACK: Self = Self::rgba(0, 0, 0, 255);
139
140 #[must_use]
141 pub const fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
142 Self {
143 red,
144 green,
145 blue,
146 alpha,
147 }
148 }
149
150 #[must_use]
154 pub fn to_linear_rgba(self) -> [f32; 4] {
155 let linear = Srgba::new(
156 self.red as f32 / 255.0,
157 self.green as f32 / 255.0,
158 self.blue as f32 / 255.0,
159 self.alpha as f32 / 255.0,
160 )
161 .into_linear();
162 [
163 linear.color.red,
164 linear.color.green,
165 linear.color.blue,
166 linear.alpha,
167 ]
168 }
169
170 #[must_use]
175 pub fn from_linear_rgba([red, green, blue, alpha]: [f32; 4]) -> Self {
176 let encoded = Srgba::<u8>::from_linear(LinSrgba::new(
177 unit(red),
178 unit(green),
179 unit(blue),
180 unit(alpha),
181 ));
182 Self::rgba(
183 encoded.color.red,
184 encoded.color.green,
185 encoded.color.blue,
186 encoded.alpha,
187 )
188 }
189
190 #[must_use]
193 pub fn to_hsl(self) -> HslColor {
194 HslColor::from_color(self)
195 }
196
197 #[must_use]
200 pub fn to_hsv(self) -> HsvColor {
201 HsvColor::from_color(self)
202 }
203}
204
205#[derive(Clone, Copy, Debug, PartialEq)]
211pub struct HslColor {
212 pub hue: f32,
213 pub saturation: f32,
214 pub lightness: f32,
215 pub alpha: f32,
216}
217
218impl HslColor {
219 #[must_use]
220 pub fn new(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self {
221 Self {
222 hue: normalize_hue(hue),
223 saturation: unit(saturation),
224 lightness: unit(lightness),
225 alpha: unit(alpha),
226 }
227 }
228
229 #[must_use]
230 pub fn from_color(color: Color) -> Self {
231 let source = palette_color(color);
232 let hsl = Hsl::from_color(source.color);
233 Self::new(
234 hsl.hue.into_degrees(),
235 hsl.saturation,
236 hsl.lightness,
237 source.alpha,
238 )
239 }
240
241 #[must_use]
242 pub fn to_color(self) -> Color {
243 let hsl = Hsl::new_srgb(
244 normalize_hue(self.hue),
245 unit(self.saturation),
246 unit(self.lightness),
247 );
248 color_from_palette(Srgba::from_color(hsl).with_alpha(unit(self.alpha)))
249 }
250}
251
252#[derive(Clone, Copy, Debug, PartialEq)]
254pub struct HsvColor {
255 pub hue: f32,
256 pub saturation: f32,
257 pub value: f32,
258 pub alpha: f32,
259}
260
261impl HsvColor {
262 #[must_use]
263 pub fn new(hue: f32, saturation: f32, value: f32, alpha: f32) -> Self {
264 Self {
265 hue: normalize_hue(hue),
266 saturation: unit(saturation),
267 value: unit(value),
268 alpha: unit(alpha),
269 }
270 }
271
272 #[must_use]
273 pub fn from_color(color: Color) -> Self {
274 let source = palette_color(color);
275 let hsv = Hsv::from_color(source.color);
276 Self::new(
277 hsv.hue.into_degrees(),
278 hsv.saturation,
279 hsv.value,
280 source.alpha,
281 )
282 }
283
284 #[must_use]
285 pub fn to_color(self) -> Color {
286 let hsv = Hsv::new_srgb(
287 normalize_hue(self.hue),
288 unit(self.saturation),
289 unit(self.value),
290 );
291 color_from_palette(Srgba::from_color(hsv).with_alpha(unit(self.alpha)))
292 }
293}
294
295fn unit(value: f32) -> f32 {
296 if value.is_finite() {
297 value.clamp(0.0, 1.0)
298 } else {
299 0.0
300 }
301}
302
303fn normalize_hue(hue: f32) -> f32 {
304 if hue.is_finite() {
305 hue.rem_euclid(360.0)
306 } else {
307 0.0
308 }
309}
310
311fn palette_color(color: Color) -> Srgba {
312 Srgba::new(
313 color.red as f32 / 255.0,
314 color.green as f32 / 255.0,
315 color.blue as f32 / 255.0,
316 color.alpha as f32 / 255.0,
317 )
318}
319
320fn color_from_palette(color: Srgba) -> Color {
321 let byte = |value: f32| (unit(value) * 255.0).round() as u8;
322 Color::rgba(
323 byte(color.color.red),
324 byte(color.color.green),
325 byte(color.color.blue),
326 byte(color.alpha),
327 )
328}
329
330#[derive(Clone, Copy, Debug, PartialEq)]
337pub struct Transform(Affine);
338
339impl Default for Transform {
340 fn default() -> Self {
341 Self::IDENTITY
342 }
343}
344
345impl Transform {
346 pub const IDENTITY: Self = Self(Affine::IDENTITY);
347
348 #[must_use]
349 pub fn translation(offset: Offset) -> Self {
350 Self(Affine::translate(Vec2::new(
351 f64::from(offset.x),
352 f64::from(offset.y),
353 )))
354 }
355
356 #[must_use]
357 pub const fn scale(scale: f32) -> Self {
358 Self(Affine::scale(scale as f64))
359 }
360
361 #[must_use]
362 pub const fn scale_non_uniform(x: f32, y: f32) -> Self {
363 Self(Affine::scale_non_uniform(x as f64, y as f64))
364 }
365
366 #[must_use]
368 pub fn rotation(radians: f32) -> Self {
369 Self(Affine::rotate(f64::from(radians)))
370 }
371
372 #[must_use]
373 pub const fn skew(x: f32, y: f32) -> Self {
374 Self(Affine::skew(x as f64, y as f64))
375 }
376
377 #[must_use]
378 pub const fn from_kurbo(affine: Affine) -> Self {
379 Self(affine)
380 }
381
382 #[must_use]
383 pub const fn to_kurbo(self) -> Affine {
384 self.0
385 }
386
387 #[must_use]
389 pub fn then(self, other: Self) -> Self {
390 Self(self.0 * other.0)
391 }
392
393 #[must_use]
394 pub fn inverse(self) -> Option<Self> {
395 let inverse = self.0.inverse();
396 inverse.is_finite().then_some(Self(inverse))
397 }
398
399 #[must_use]
400 pub fn inverse_translation(self) -> Self {
401 self.inverse().unwrap_or(Self::IDENTITY)
402 }
403
404 #[must_use]
405 pub fn translation_offset(self) -> Offset {
406 let coefficients = self.0.as_coeffs();
407 Offset::new(coefficients[4] as f32, coefficients[5] as f32)
408 }
409
410 #[must_use]
411 pub fn is_translation(self) -> bool {
412 let [a, b, c, d, _, _] = self.0.as_coeffs();
413 a == 1. && b == 0. && c == 0. && d == 1.
414 }
415
416 #[must_use]
427 pub fn magnification_bound(self) -> f32 {
428 let [a, b, c, d, _, _] = self.0.as_coeffs();
429 let bound = a.hypot(b).hypot(c.hypot(d));
430 if !bound.is_finite() {
431 f32::INFINITY
432 } else if bound == 0.0 {
433 0.0
436 } else {
437 (bound as f32).next_up()
438 }
439 }
440
441 #[must_use]
442 pub fn transform_point(self, point: Offset) -> Offset {
443 let point = self.0 * Point::new(f64::from(point.x), f64::from(point.y));
444 Offset::new(point.x as f32, point.y as f32)
445 }
446
447 #[must_use]
448 pub fn inverse_transform_point(self, point: Offset) -> Option<Offset> {
449 self.inverse().map(|inverse| inverse.transform_point(point))
450 }
451
452 #[must_use]
453 pub fn transform_rect_bbox(self, rect: Rect) -> Rect {
454 let rect = kurbo::Rect::new(
455 f64::from(rect.origin.x),
456 f64::from(rect.origin.y),
457 f64::from(rect.origin.x + rect.size.width),
458 f64::from(rect.origin.y + rect.size.height),
459 );
460 let rect = self.0.transform_rect_bbox(rect);
461 Rect::from_origin_size(
462 Offset::new(rect.x0 as f32, rect.y0 as f32),
463 Size::new((rect.x1 - rect.x0) as f32, (rect.y1 - rect.y0) as f32),
464 )
465 }
466}
467
468#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
470pub struct Invalidation(u16);
471
472impl Invalidation {
473 pub const NONE: Self = Self(0);
475 pub const BUILD: Self = Self(1 << 0);
477 pub const LAYOUT: Self = Self(1 << 1);
479 pub const PAINT: Self = Self(1 << 2);
481 pub const COMPOSITE: Self = Self(1 << 3);
483 pub const SEMANTICS: Self = Self(1 << 4);
485 pub const HIT_TEST: Self = Self(1 << 5);
487
488 #[must_use]
490 pub const fn is_empty(self) -> bool {
491 self.0 == 0
492 }
493
494 #[must_use]
496 pub const fn contains(self, other: Self) -> bool {
497 (self.0 & other.0) == other.0
498 }
499
500 #[must_use]
502 pub const fn intersects(self, other: Self) -> bool {
503 (self.0 & other.0) != 0
504 }
505
506 pub fn insert(&mut self, other: Self) {
508 self.0 |= other.0;
509 }
510
511 pub fn remove(&mut self, other: Self) {
513 self.0 &= !other.0;
514 }
515
516 #[must_use]
518 pub const fn union(self, other: Self) -> Self {
519 Self(self.0 | other.0)
520 }
521
522 #[must_use]
524 pub const fn intersection(self, other: Self) -> Self {
525 Self(self.0 & other.0)
526 }
527}
528
529pub type DirtyFlags = Invalidation;
531
532impl std::ops::BitOr for Invalidation {
533 type Output = Self;
534
535 fn bitor(self, rhs: Self) -> Self {
536 Self(self.0 | rhs.0)
537 }
538}
539
540impl std::ops::BitOrAssign for Invalidation {
541 fn bitor_assign(&mut self, rhs: Self) {
542 self.0 |= rhs.0;
543 }
544}
545
546impl std::ops::BitAnd for Invalidation {
547 type Output = Self;
548
549 fn bitand(self, rhs: Self) -> Self {
550 Self(self.0 & rhs.0)
551 }
552}
553
554impl std::ops::BitAndAssign for Invalidation {
555 fn bitand_assign(&mut self, rhs: Self) {
556 self.0 &= rhs.0;
557 }
558}
559
560impl std::ops::Not for Invalidation {
561 type Output = Self;
562
563 fn not(self) -> Self {
564 Self(!self.0)
565 }
566}
567
568pub trait Lerp {
570 #[must_use]
572 fn lerp(&self, other: &Self, t: f32) -> Self;
573}
574
575impl Lerp for f32 {
576 fn lerp(&self, other: &Self, t: f32) -> Self {
577 self + (other - self) * t
578 }
579}
580
581impl Lerp for f64 {
582 fn lerp(&self, other: &Self, t: f32) -> Self {
583 self + (other - self) * f64::from(t)
584 }
585}
586
587impl Lerp for Offset {
588 fn lerp(&self, other: &Self, t: f32) -> Self {
589 Self {
590 x: self.x.lerp(&other.x, t),
591 y: self.y.lerp(&other.y, t),
592 }
593 }
594}
595
596impl Lerp for Size {
597 fn lerp(&self, other: &Self, t: f32) -> Self {
598 Self {
599 width: self.width.lerp(&other.width, t).max(0.0),
600 height: self.height.lerp(&other.height, t).max(0.0),
601 }
602 }
603}
604
605impl Lerp for Rect {
606 fn lerp(&self, other: &Self, t: f32) -> Self {
607 Self {
608 origin: self.origin.lerp(&other.origin, t),
609 size: self.size.lerp(&other.size, t),
610 }
611 }
612}
613
614impl Lerp for Color {
615 fn lerp(&self, other: &Self, t: f32) -> Self {
616 let t = t.clamp(0.0, 1.0);
617 let mix = |a: u8, b: u8| -> u8 {
618 (f32::from(a) + (f32::from(b) - f32::from(a)) * t)
619 .round()
620 .clamp(0.0, 255.0) as u8
621 };
622 Self::rgba(
623 mix(self.red, other.red),
624 mix(self.green, other.green),
625 mix(self.blue, other.blue),
626 mix(self.alpha, other.alpha),
627 )
628 }
629}
630
631impl Lerp for HslColor {
632 fn lerp(&self, other: &Self, t: f32) -> Self {
633 let t = t.clamp(0.0, 1.0);
634 let mix = |a: f32, b: f32| a + (b - a) * t;
635 Self::new(
636 mix(self.hue, other.hue),
637 mix(self.saturation, other.saturation),
638 mix(self.lightness, other.lightness),
639 mix(self.alpha, other.alpha),
640 )
641 }
642}
643
644impl Lerp for HsvColor {
645 fn lerp(&self, other: &Self, t: f32) -> Self {
646 let t = t.clamp(0.0, 1.0);
647 let mix = |a: f32, b: f32| a + (b - a) * t;
648 Self::new(
649 mix(self.hue, other.hue),
650 mix(self.saturation, other.saturation),
651 mix(self.value, other.value),
652 mix(self.alpha, other.alpha),
653 )
654 }
655}
656
657#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
659pub enum ChangeImpact {
660 #[default]
662 None,
663 Composite,
665 Paint,
667 Layout,
669 Build,
671}