use crate::layout::{Axis, Constraints, Size};
use crate::renderer::PaintContext;
use crate::tree::{LayoutCtx, Tree, WidgetId};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl Color {
pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
Self { r, g, b, a: 1.0 }
}
pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
Self { r, g, b, a }
}
pub const fn from_hex(hex: u32) -> Self {
Self {
r: ((hex >> 16) & 0xFF) as f32 / 255.0,
g: ((hex >> 8) & 0xFF) as f32 / 255.0,
b: (hex & 0xFF) as f32 / 255.0,
a: 1.0,
}
}
pub const WHITE: Color = Color::rgb(1.0, 1.0, 1.0);
pub const BLACK: Color = Color::rgb(0.0, 0.0, 0.0);
pub const TRANSPARENT: Color = Color::rgba(0.0, 0.0, 0.0, 0.0);
pub const RED: Color = Color::rgb(1.0, 0.0, 0.0);
pub const GREEN: Color = Color::rgb(0.0, 1.0, 0.0);
pub const BLUE: Color = Color::rgb(0.0, 0.0, 1.0);
pub const YELLOW: Color = Color::rgb(1.0, 1.0, 0.0);
pub const CYAN: Color = Color::rgb(0.0, 1.0, 1.0);
pub const MAGENTA: Color = Color::rgb(1.0, 0.0, 1.0);
pub const GRAY: Color = Color::rgb(0.5, 0.5, 0.5);
pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Self {
Self {
r: r as f32 / 255.0,
g: g as f32 / 255.0,
b: b as f32 / 255.0,
a: 1.0,
}
}
pub const fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
Self {
r: r as f32 / 255.0,
g: g as f32 / 255.0,
b: b as f32 / 255.0,
a: a as f32 / 255.0,
}
}
pub fn to_rgba8(self) -> (u8, u8, u8, u8) {
(
(self.r * 255.0 + 0.5) as u8,
(self.g * 255.0 + 0.5) as u8,
(self.b * 255.0 + 0.5) as u8,
(self.a * 255.0 + 0.5) as u8,
)
}
pub fn lighter(self, amount: f32) -> Self {
Self {
r: self.r + (1.0 - self.r) * amount,
g: self.g + (1.0 - self.g) * amount,
b: self.b + (1.0 - self.b) * amount,
a: self.a,
}
}
pub fn darker(self, amount: f32) -> Self {
Self {
r: self.r * (1.0 - amount),
g: self.g * (1.0 - amount),
b: self.b * (1.0 - amount),
a: self.a,
}
}
pub fn mix(self, other: Color, t: f32) -> Self {
Self {
r: self.r + (other.r - self.r) * t,
g: self.g + (other.g - self.g) * t,
b: self.b + (other.b - self.b) * t,
a: self.a + (other.a - self.a) * t,
}
}
pub fn invert(self) -> Self {
Self {
r: 1.0 - self.r,
g: 1.0 - self.g,
b: 1.0 - self.b,
a: self.a,
}
}
pub fn luminance(self) -> f32 {
0.2126 * self.r + 0.7152 * self.g + 0.0722 * self.b
}
pub fn grayscale(self) -> Self {
let l = self.luminance();
Self {
r: l,
g: l,
b: l,
a: self.a,
}
}
pub fn with_alpha(self, alpha: f32) -> Self {
Self { a: alpha, ..self }
}
pub fn scale_alpha(self, factor: f32) -> Self {
Self {
a: self.a * factor,
..self
}
}
}
impl Default for Color {
fn default() -> Self {
Self::TRANSPARENT
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Rect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point {
pub x: f32,
pub y: f32,
}
impl Point {
#[inline]
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
#[inline]
pub fn offset(self, dx: f32, dy: f32) -> Self {
Self::new(self.x + dx, self.y + dy)
}
#[inline]
pub(crate) fn along(self, axis: crate::layout::Axis) -> f32 {
match axis {
crate::layout::Axis::Horizontal => self.x,
crate::layout::Axis::Vertical => self.y,
}
}
}
fn superellipse_length(x: f32, y: f32, n: f32) -> f32 {
if (n - 1.0).abs() < 0.01 {
x.abs() + y.abs()
} else if (n - 2.0).abs() < 0.01 {
x.hypot(y)
} else {
let (ax, ay) = (x.abs(), y.abs());
let m = ax.max(ay);
if m <= 0.0 {
return 0.0;
}
let t = ax.min(ay) / m;
m * (1.0 + t.powf(n)).powf(1.0 / n)
}
}
impl Rect {
pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
Self {
x,
y,
width,
height,
}
}
pub(crate) fn span(&self, axis: Axis) -> (f32, f32) {
match axis {
Axis::Horizontal => (self.x, self.x + self.width),
Axis::Vertical => (self.y, self.y + self.height),
}
}
pub fn from_size(size: Size) -> Self {
Self {
x: 0.0,
y: 0.0,
width: size.width,
height: size.height,
}
}
pub fn offset(&self, dx: f32, dy: f32) -> Self {
Self {
x: self.x + dx,
y: self.y + dy,
..*self
}
}
pub fn inset(&self, amount: f32) -> Self {
Self {
x: self.x + amount,
y: self.y + amount,
width: (self.width - amount * 2.0).max(0.0),
height: (self.height - amount * 2.0).max(0.0),
}
}
pub fn outset(&self, amount: f32) -> Self {
self.inset(-amount)
}
pub fn outset_beyond(&self, inner: Rect) -> f32 {
[
inner.x - self.x,
inner.y - self.y,
(self.x + self.width) - (inner.x + inner.width),
(self.y + self.height) - (inner.y + inner.height),
]
.into_iter()
.fold(0.0f32, f32::max)
}
pub fn intersection(&self, other: &Rect) -> Option<Rect> {
let x = self.x.max(other.x);
let y = self.y.max(other.y);
let right = (self.x + self.width).min(other.x + other.width);
let bottom = (self.y + self.height).min(other.y + other.height);
(right > x && bottom > y).then(|| Rect::new(x, y, right - x, bottom - y))
}
pub fn intersects(&self, other: &Rect) -> bool {
self.x < other.x + other.width
&& self.x + self.width > other.x
&& self.y < other.y + other.height
&& self.y + self.height > other.y
}
pub fn contains(&self, x: f32, y: f32) -> bool {
x >= self.x && x < self.x + self.width && y >= self.y && y < self.y + self.height
}
#[inline]
pub fn contains_at(&self, at: Option<Point>) -> bool {
at.is_some_and(|at| self.contains(at.x, at.y))
}
#[inline]
pub fn contains_shape_at(&self, at: Option<Point>, corners: crate::widgets::Corners) -> bool {
at.is_some_and(|at| self.contains_shape(at.x, at.y, corners))
}
pub fn contains_shape(&self, x: f32, y: f32, corners: crate::widgets::Corners) -> bool {
self.contains(x, y) && self.shape_distance(x, y, corners) <= 0.0
}
pub(crate) fn shape_distance(&self, x: f32, y: f32, corners: crate::widgets::Corners) -> f32 {
let (cx, cy) = (self.x + self.width * 0.5, self.y + self.height * 0.5);
let (hx, hy) = (self.width * 0.5, self.height * 0.5);
let (rx, ry) = (x - cx, y - cy);
let radii = corners.radii;
let radius = match (rx < 0.0, ry < 0.0) {
(true, true) => radii.top_left,
(false, true) => radii.top_right,
(false, false) => radii.bottom_right,
(true, false) => radii.bottom_left,
};
let r = radius.min(hx.min(hy));
let (px, py) = (rx.abs(), ry.abs());
if r <= 0.0 {
return (px - hx).max(py - hy);
}
if corners.curvature < 0.0 {
let box_sdf = (px - hx).max(py - hy);
let circle_sdf = ((px - hx).hypot(py - hy)) - r;
return box_sdf.max(-circle_sdf);
}
let n = 2f32.powf(corners.curvature);
let (qx, qy) = (px - hx + r, py - hy + r);
let (mx, my) = (qx.max(0.0), qy.max(0.0));
let inside = qx.max(qy).min(0.0);
inside + superellipse_length(mx, my, n) - r
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Padding {
pub top: f32,
pub right: f32,
pub bottom: f32,
pub left: f32,
}
impl Padding {
pub fn all(value: f32) -> Self {
Self {
top: value,
right: value,
bottom: value,
left: value,
}
}
pub fn horizontal_total(&self) -> f32 {
self.left + self.right
}
pub fn vertical_total(&self) -> f32 {
self.top + self.bottom
}
pub fn with_top(mut self, v: f32) -> Self {
self.top = v;
self
}
pub fn with_bottom(mut self, v: f32) -> Self {
self.bottom = v;
self
}
pub fn with_left(mut self, v: f32) -> Self {
self.left = v;
self
}
pub fn with_right(mut self, v: f32) -> Self {
self.right = v;
self
}
}
impl From<f32> for Padding {
fn from(v: f32) -> Self {
Padding::all(v)
}
}
impl From<f64> for Padding {
fn from(v: f64) -> Self {
Padding::all(v as f32)
}
}
impl From<u16> for Padding {
fn from(v: u16) -> Self {
Padding::all(f32::from(v))
}
}
impl From<u32> for Padding {
fn from(v: u32) -> Self {
Padding::all(v as f32)
}
}
impl From<i32> for Padding {
fn from(v: i32) -> Self {
Padding::all(v as f32)
}
}
impl From<[f32; 2]> for Padding {
fn from(v: [f32; 2]) -> Self {
Padding {
top: v[0],
right: v[1],
bottom: v[0],
left: v[1],
}
}
}
impl From<[f32; 4]> for Padding {
fn from(v: [f32; 4]) -> Self {
Padding {
top: v[0],
right: v[1],
bottom: v[2],
left: v[3],
}
}
}
impl From<[i32; 2]> for Padding {
fn from(v: [i32; 2]) -> Self {
Padding {
top: v[0] as f32,
right: v[1] as f32,
bottom: v[0] as f32,
left: v[1] as f32,
}
}
}
impl From<[i32; 4]> for Padding {
fn from(v: [i32; 4]) -> Self {
Padding {
top: v[0] as f32,
right: v[1] as f32,
bottom: v[2] as f32,
left: v[3] as f32,
}
}
}
impl Default for Padding {
fn default() -> Self {
Self {
top: 0.0,
right: 0.0,
bottom: 0.0,
left: 0.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MouseButton {
Left,
Right,
Middle,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollSource {
Wheel,
Finger,
Continuous,
}
impl ScrollSource {
pub fn is_continuous(self) -> bool {
matches!(self, ScrollSource::Finger | ScrollSource::Continuous)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PointerKind {
Mouse,
Finger,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Modifiers {
pub ctrl: bool,
pub alt: bool,
pub shift: bool,
pub logo: bool,
pub caps_lock: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Key {
Backspace,
Delete,
Enter,
Tab,
Escape,
Left,
Right,
Up,
Down,
Home,
End,
Char(char),
}
#[derive(Debug, Clone)]
pub enum Event {
MouseMove {
at: Option<Point>,
pointer: PointerKind,
},
MouseDown {
at: Option<Point>,
button: MouseButton,
pointer: PointerKind,
},
MouseUp {
at: Option<Point>,
button: MouseButton,
},
MouseEnter { at: Option<Point> },
MouseLeave,
Scroll {
at: Option<Point>,
delta_x: f32,
delta_y: f32,
source: ScrollSource,
},
ScrollEnd {
at: Option<Point>,
},
KeyDown {
key: Key,
modifiers: Modifiers,
repeat: bool,
},
KeyUp {
key: Key,
modifiers: Modifiers,
},
FocusIn,
FocusOut,
Pasted(crate::reactive::PastedText),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventResponse {
Ignored,
Hit,
Handled,
}
impl Event {
pub fn mouse_move(x: f32, y: f32) -> Self {
Event::MouseMove {
at: Some(Point::new(x, y)),
pointer: PointerKind::Mouse,
}
}
pub fn mouse_down(x: f32, y: f32, button: MouseButton) -> Self {
Event::MouseDown {
at: Some(Point::new(x, y)),
button,
pointer: PointerKind::Mouse,
}
}
pub fn finger_move(x: f32, y: f32) -> Self {
Event::MouseMove {
at: Some(Point::new(x, y)),
pointer: PointerKind::Finger,
}
}
pub fn finger_down(x: f32, y: f32) -> Self {
Event::MouseDown {
at: Some(Point::new(x, y)),
button: MouseButton::Left,
pointer: PointerKind::Finger,
}
}
pub fn mouse_up(x: f32, y: f32, button: MouseButton) -> Self {
Event::MouseUp {
at: Some(Point::new(x, y)),
button,
}
}
pub fn mouse_enter(x: f32, y: f32) -> Self {
Event::MouseEnter {
at: Some(Point::new(x, y)),
}
}
pub fn scroll(x: f32, y: f32, delta_x: f32, delta_y: f32, source: ScrollSource) -> Self {
Event::Scroll {
at: Some(Point::new(x, y)),
delta_x,
delta_y,
source,
}
}
pub fn scroll_end(x: f32, y: f32) -> Self {
Event::ScrollEnd {
at: Some(Point::new(x, y)),
}
}
#[inline]
pub fn coords(&self) -> Option<Point> {
match self {
Event::MouseMove { at, .. }
| Event::MouseDown { at, .. }
| Event::MouseUp { at, .. }
| Event::MouseEnter { at }
| Event::Scroll { at, .. }
| Event::ScrollEnd { at } => *at,
Event::MouseLeave
| Event::KeyDown { .. }
| Event::KeyUp { .. }
| Event::FocusIn
| Event::FocusOut
| Event::Pasted(_) => None,
}
}
pub(crate) fn follows_the_focus(&self) -> bool {
match self {
Event::KeyDown { .. } | Event::KeyUp { .. } | Event::FocusIn | Event::FocusOut => true,
Event::MouseMove { .. }
| Event::MouseDown { .. }
| Event::MouseUp { .. }
| Event::MouseEnter { .. }
| Event::MouseLeave
| Event::Scroll { .. }
| Event::ScrollEnd { .. }
| Event::Pasted(_) => false,
}
}
pub fn with_coords(&self, at: Option<Point>) -> Self {
match self {
Event::MouseMove { pointer, .. } => Event::MouseMove {
at,
pointer: *pointer,
},
Event::MouseDown {
button, pointer, ..
} => Event::MouseDown {
at,
button: *button,
pointer: *pointer,
},
Event::MouseUp { button, .. } => Event::MouseUp {
at,
button: *button,
},
Event::MouseEnter { .. } => Event::MouseEnter { at },
Event::Scroll {
delta_x,
delta_y,
source,
..
} => Event::Scroll {
at,
delta_x: *delta_x,
delta_y: *delta_y,
source: *source,
},
Event::ScrollEnd { .. } => Event::ScrollEnd { at },
Event::MouseLeave => Event::MouseLeave,
Event::KeyDown { .. }
| Event::KeyUp { .. }
| Event::FocusIn
| Event::FocusOut
| Event::Pasted(_) => self.clone(),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct LayoutHints {
pub fill_width: bool,
pub fill_height: bool,
pub hidden: bool,
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Placement {
pub own: crate::transform::Transform,
pub scroll: (f32, f32),
}
impl Placement {
pub const NONE: Self = Self {
own: crate::transform::Transform::IDENTITY,
scroll: (0.0, 0.0),
};
}
pub trait Widget {
fn advance_animations(&mut self, tree: &mut Tree, id: WidgetId) -> bool {
let _ = (tree, id);
false
}
fn reconcile_children(&mut self, tree: &mut Tree, id: WidgetId) -> bool {
let _ = (tree, id);
false
}
fn begin_exit(&mut self, tree: &mut Tree, id: WidgetId) -> bool {
let _ = (tree, id);
false
}
fn is_exiting(&self) -> bool {
false
}
fn cancel_exit(&mut self, tree: &mut Tree, id: WidgetId) {
let _ = (tree, id);
}
#[doc(hidden)]
fn owned_scope(&self) -> Option<crate::reactive::__internal::OwnerId> {
None
}
fn refresh_paint_bounds(&self, tree: &mut Tree, id: WidgetId) {
let _ = (tree, id);
}
#[doc(hidden)]
fn placement(&self, tree: &Tree, id: WidgetId) -> Placement {
let _ = (tree, id);
Placement::NONE
}
fn layout_hints(&self) -> LayoutHints {
LayoutHints::default()
}
fn layout(&mut self, ctx: &mut LayoutCtx, constraints: Constraints) -> Size;
fn paint(&self, ctx: &mut PaintContext);
fn event(&mut self, tree: &mut Tree, id: WidgetId, event: &Event) -> EventResponse {
let _ = (tree, id, event);
EventResponse::Ignored
}
fn register_children(&mut self, _tree: &mut Tree, _id: WidgetId) {}
fn into_any(self) -> AnyWidget
where
Self: Sized + 'static,
{
Box::new(self)
}
}
#[cfg(test)]
mod rect_tests {
use super::*;
#[test]
fn a_rect_reports_how_far_it_stands_outside_another() {
let inner = Rect::new(0.0, 0.0, 100.0, 100.0);
assert_eq!(inner.outset_beyond(inner), 0.0, "itself");
assert_eq!(
inner.inset(10.0).outset_beyond(inner),
0.0,
"wholly inside stands outside by nothing"
);
assert_eq!(inner.offset(-30.0, 0.0).outset_beyond(inner), 30.0, "left");
assert_eq!(inner.offset(0.0, -30.0).outset_beyond(inner), 30.0, "top");
assert_eq!(inner.offset(30.0, 0.0).outset_beyond(inner), 30.0, "right");
assert_eq!(inner.offset(0.0, 30.0).outset_beyond(inner), 30.0, "bottom");
assert_eq!(inner.outset(7.0).outset_beyond(inner), 7.0, "all four");
assert_eq!(
Rect::new(-5.0, -40.0, 110.0, 180.0).outset_beyond(inner),
40.0,
"the widest, not the first"
);
}
}
pub type AnyWidget = Box<dyn Widget>;
impl Widget for Box<dyn Widget> {
fn advance_animations(&mut self, tree: &mut Tree, id: WidgetId) -> bool {
(**self).advance_animations(tree, id)
}
fn reconcile_children(&mut self, tree: &mut Tree, id: WidgetId) -> bool {
(**self).reconcile_children(tree, id)
}
fn begin_exit(&mut self, tree: &mut Tree, id: WidgetId) -> bool {
(**self).begin_exit(tree, id)
}
fn is_exiting(&self) -> bool {
(**self).is_exiting()
}
fn cancel_exit(&mut self, tree: &mut Tree, id: WidgetId) {
(**self).cancel_exit(tree, id)
}
fn owned_scope(&self) -> Option<crate::reactive::__internal::OwnerId> {
(**self).owned_scope()
}
fn refresh_paint_bounds(&self, tree: &mut Tree, id: WidgetId) {
(**self).refresh_paint_bounds(tree, id)
}
fn placement(&self, tree: &Tree, id: WidgetId) -> Placement {
(**self).placement(tree, id)
}
fn layout_hints(&self) -> LayoutHints {
(**self).layout_hints()
}
fn layout(&mut self, ctx: &mut LayoutCtx, constraints: Constraints) -> Size {
(**self).layout(ctx, constraints)
}
fn paint(&self, ctx: &mut PaintContext) {
(**self).paint(ctx)
}
fn event(&mut self, tree: &mut Tree, id: WidgetId, event: &Event) -> EventResponse {
(**self).event(tree, id, event)
}
fn register_children(&mut self, tree: &mut Tree, id: WidgetId) {
(**self).register_children(tree, id)
}
}
#[cfg(test)]
mod tests {
use crate::widgets::Corners;
#[test]
fn an_intersection_is_an_area_both_rects_hold() {
let a = Rect::new(0.0, 0.0, 10.0, 10.0);
let inside = a.intersection(&Rect::new(5.0, 2.0, 10.0, 4.0));
assert_eq!(
inside.map(|r| (r.x, r.y, r.width, r.height)),
Some((5.0, 2.0, 5.0, 4.0))
);
assert!(
a.intersection(&Rect::new(10.0, 0.0, 5.0, 10.0)).is_none(),
"touching on the right"
);
assert!(
a.intersection(&Rect::new(0.0, 10.0, 10.0, 5.0)).is_none(),
"touching below"
);
assert!(
a.intersection(&Rect::new(2.0, 20.0, 4.0, 4.0)).is_none(),
"overlapping across, apart down"
);
assert!(
a.intersection(&Rect::new(20.0, 2.0, 4.0, 4.0)).is_none(),
"overlapping down, apart across"
);
}
#[test]
fn a_click_asks_the_corner_it_landed_in() {
let r = Rect::new(0.0, 0.0, 100.0, 100.0);
let radii = Corners::rounded([20.0, 0.0]);
assert!(
!r.contains_shape(2.0, 2.0, radii),
"the top-left corner is cut away"
);
assert!(
!r.contains_shape(98.0, 2.0, radii),
"and so is the top-right"
);
assert!(
r.contains_shape(2.0, 98.0, radii),
"the bottom-left is square, so the same offset is inside"
);
assert!(
r.contains_shape(98.0, 98.0, radii),
"and the bottom-right too"
);
}
#[test]
fn a_bevel_is_clicked_as_a_straight_cut() {
let r = Rect::new(0.0, 0.0, 200.0, 200.0);
assert!(
!r.contains_shape(30.0, 30.0, Corners::bevel(80.0)),
"the chord has already cut this away"
);
assert!(
r.contains_shape(30.0, 30.0, Corners::rounded(80.0)),
"while the arc of the same radius still keeps it"
);
assert!(!r.contains_shape(5.0, 5.0, Corners::bevel(80.0)));
assert!(r.contains_shape(100.0, 100.0, Corners::bevel(80.0)));
}
#[test]
fn the_far_edge_is_not_inside() {
let r = Rect::new(0.0, 0.0, 100.0, 40.0);
for corners in [Corners::SQUARE, Corners::rounded(8.0)] {
assert!(
!r.contains_shape(100.0, 20.0, corners),
"the right edge is out"
);
assert!(
!r.contains_shape(50.0, 40.0, corners),
"the bottom edge is out"
);
assert!(r.contains_shape(0.0, 20.0, corners), "the left edge is in");
assert!(
r.contains_shape(99.0, 20.0, corners),
"and so is the pixel before"
);
}
}
#[test]
fn a_scoop_is_clicked_as_a_hollow() {
let r = Rect::new(0.0, 0.0, 200.0, 200.0);
assert!(
!r.contains_shape(10.0, 10.0, Corners::scoop(80.0)),
"the hollow is not part of the box"
);
assert!(r.contains_shape(100.0, 100.0, Corners::scoop(80.0)));
}
#[test]
fn a_uniform_radius_cuts_all_four_corners() {
let r = Rect::new(0.0, 0.0, 100.0, 100.0);
let radii = Corners::rounded(20.0);
for (x, y) in [(2.0, 2.0), (98.0, 2.0), (2.0, 98.0), (98.0, 98.0)] {
assert!(!r.contains_shape(x, y, radii), "({x}, {y}) is cut away");
}
assert!(r.contains_shape(50.0, 50.0, radii), "the middle is not");
}
use super::*;
#[test]
fn test_color_rgb() {
let color = Color::rgb(0.5, 0.6, 0.7);
assert_eq!(color.r, 0.5);
assert_eq!(color.g, 0.6);
assert_eq!(color.b, 0.7);
assert_eq!(color.a, 1.0);
}
#[test]
fn test_color_rgba() {
let color = Color::rgba(0.1, 0.2, 0.3, 0.5);
assert_eq!(color.r, 0.1);
assert_eq!(color.g, 0.2);
assert_eq!(color.b, 0.3);
assert_eq!(color.a, 0.5);
}
#[test]
fn test_color_from_hex() {
let color = Color::from_hex(0xFF0000);
assert_eq!(color.r, 1.0);
assert_eq!(color.g, 0.0);
assert_eq!(color.b, 0.0);
assert_eq!(color.a, 1.0);
let color = Color::from_hex(0x00FF00);
assert_eq!(color.r, 0.0);
assert_eq!(color.g, 1.0);
assert_eq!(color.b, 0.0);
let color = Color::from_hex(0x0000FF);
assert_eq!(color.r, 0.0);
assert_eq!(color.g, 0.0);
assert_eq!(color.b, 1.0);
}
#[test]
fn test_color_constants() {
assert_eq!(Color::WHITE, Color::rgb(1.0, 1.0, 1.0));
assert_eq!(Color::BLACK, Color::rgb(0.0, 0.0, 0.0));
assert_eq!(Color::TRANSPARENT, Color::rgba(0.0, 0.0, 0.0, 0.0));
}
#[test]
fn test_color_default() {
let color = Color::default();
assert_eq!(color, Color::TRANSPARENT);
}
#[test]
fn test_color_named_constants() {
assert_eq!(Color::RED, Color::rgb(1.0, 0.0, 0.0));
assert_eq!(Color::GREEN, Color::rgb(0.0, 1.0, 0.0));
assert_eq!(Color::BLUE, Color::rgb(0.0, 0.0, 1.0));
assert_eq!(Color::YELLOW, Color::rgb(1.0, 1.0, 0.0));
assert_eq!(Color::CYAN, Color::rgb(0.0, 1.0, 1.0));
assert_eq!(Color::MAGENTA, Color::rgb(1.0, 0.0, 1.0));
assert_eq!(Color::GRAY, Color::rgb(0.5, 0.5, 0.5));
}
#[test]
fn test_color_from_rgb8() {
let color = Color::from_rgb8(255, 128, 0);
assert!((color.r - 1.0).abs() < 0.01);
assert!((color.g - 0.502).abs() < 0.01);
assert!((color.b - 0.0).abs() < 0.01);
assert_eq!(color.a, 1.0);
}
#[test]
fn test_color_from_rgba8() {
let color = Color::from_rgba8(255, 0, 0, 128);
assert!((color.r - 1.0).abs() < 0.01);
assert!((color.g - 0.0).abs() < 0.01);
assert!((color.b - 0.0).abs() < 0.01);
assert!((color.a - 0.502).abs() < 0.01);
}
#[test]
fn test_color_to_rgba8() {
let (r, g, b, a) = Color::rgb(1.0, 0.0, 0.5).to_rgba8();
assert_eq!(r, 255);
assert_eq!(g, 0);
assert_eq!(b, 128);
assert_eq!(a, 255);
}
#[test]
fn test_color_rgb8_roundtrip() {
let original = Color::from_rgb8(100, 200, 50);
let (r, g, b, a) = original.to_rgba8();
assert_eq!(r, 100);
assert_eq!(g, 200);
assert_eq!(b, 50);
assert_eq!(a, 255);
}
#[test]
fn test_lighter_no_change() {
let c = Color::rgb(0.5, 0.5, 0.5);
assert_eq!(c.lighter(0.0), c);
}
#[test]
fn test_lighter_full() {
let c = Color::rgba(0.2, 0.4, 0.6, 0.8);
let result = c.lighter(1.0);
assert!((result.r - 1.0).abs() < 1e-6);
assert!((result.g - 1.0).abs() < 1e-6);
assert!((result.b - 1.0).abs() < 1e-6);
assert_eq!(result.a, 0.8); }
#[test]
fn test_darker_no_change() {
let c = Color::rgb(0.5, 0.5, 0.5);
assert_eq!(c.darker(0.0), c);
}
#[test]
fn test_darker_full() {
let c = Color::rgba(0.2, 0.4, 0.6, 0.8);
let result = c.darker(1.0);
assert!((result.r).abs() < 1e-6);
assert!((result.g).abs() < 1e-6);
assert!((result.b).abs() < 1e-6);
assert_eq!(result.a, 0.8); }
#[test]
fn test_mix_endpoints() {
let a = Color::RED;
let b = Color::BLUE;
assert_eq!(a.mix(b, 0.0), a);
assert_eq!(a.mix(b, 1.0), b);
}
#[test]
fn test_mix_midpoint() {
let a = Color::rgb(0.0, 0.0, 0.0);
let b = Color::rgb(1.0, 1.0, 1.0);
let mid = a.mix(b, 0.5);
assert!((mid.r - 0.5).abs() < 1e-6);
assert!((mid.g - 0.5).abs() < 1e-6);
assert!((mid.b - 0.5).abs() < 1e-6);
}
#[test]
fn test_invert_white_is_black() {
assert_eq!(Color::WHITE.invert(), Color::rgb(0.0, 0.0, 0.0));
}
#[test]
fn test_invert_black_is_white() {
assert_eq!(Color::BLACK.invert(), Color::rgb(1.0, 1.0, 1.0));
}
#[test]
fn test_invert_roundtrip() {
let c = Color::rgb(0.2, 0.5, 0.8);
let roundtrip = c.invert().invert();
assert!((roundtrip.r - c.r).abs() < 1e-6);
assert!((roundtrip.g - c.g).abs() < 1e-6);
assert!((roundtrip.b - c.b).abs() < 1e-6);
}
#[test]
fn test_invert_preserves_alpha() {
let c = Color::rgba(0.5, 0.5, 0.5, 0.3);
assert_eq!(c.invert().a, 0.3);
}
#[test]
fn test_luminance_white() {
assert!((Color::WHITE.luminance() - 1.0).abs() < 1e-6);
}
#[test]
fn test_luminance_black() {
assert!(Color::BLACK.luminance().abs() < 1e-6);
}
#[test]
fn test_luminance_green_brightest() {
assert!(Color::GREEN.luminance() > Color::RED.luminance());
assert!(Color::GREEN.luminance() > Color::BLUE.luminance());
}
#[test]
fn test_grayscale_white() {
assert_eq!(Color::WHITE.grayscale(), Color::WHITE);
}
#[test]
fn test_grayscale_black() {
assert_eq!(Color::BLACK.grayscale(), Color::BLACK);
}
#[test]
fn test_grayscale_preserves_alpha() {
let c = Color::rgba(1.0, 0.0, 0.0, 0.5);
assert_eq!(c.grayscale().a, 0.5);
}
#[test]
fn test_grayscale_uniform_channels() {
let g = Color::RED.grayscale();
assert_eq!(g.r, g.g);
assert_eq!(g.g, g.b);
}
#[test]
fn test_with_alpha() {
let c = Color::RED.with_alpha(0.5);
assert_eq!(c.r, 1.0);
assert_eq!(c.g, 0.0);
assert_eq!(c.b, 0.0);
assert_eq!(c.a, 0.5);
}
#[test]
fn test_scale_alpha() {
let c = Color::rgba(1.0, 0.0, 0.0, 0.8);
let scaled = c.scale_alpha(0.5);
assert!((scaled.a - 0.4).abs() < 1e-6);
assert_eq!(scaled.r, 1.0); }
#[test]
fn test_rect_new() {
let rect = Rect::new(10.0, 20.0, 100.0, 200.0);
assert_eq!(rect.x, 10.0);
assert_eq!(rect.y, 20.0);
assert_eq!(rect.width, 100.0);
assert_eq!(rect.height, 200.0);
}
#[test]
fn test_rect_from_size() {
let size = Size::new(50.0, 75.0);
let rect = Rect::from_size(size);
assert_eq!(rect.x, 0.0);
assert_eq!(rect.y, 0.0);
assert_eq!(rect.width, 50.0);
assert_eq!(rect.height, 75.0);
}
#[test]
fn test_rect_offset() {
let rect = Rect::new(10.0, 20.0, 100.0, 200.0);
let offset_rect = rect.offset(5.0, 10.0);
assert_eq!(offset_rect.x, 15.0);
assert_eq!(offset_rect.y, 30.0);
assert_eq!(offset_rect.width, 100.0);
assert_eq!(offset_rect.height, 200.0);
}
#[test]
fn test_rect_inset() {
let rect = Rect::new(0.0, 0.0, 100.0, 100.0);
let inset_rect = rect.inset(10.0);
assert_eq!(inset_rect.x, 10.0);
assert_eq!(inset_rect.y, 10.0);
assert_eq!(inset_rect.width, 80.0);
assert_eq!(inset_rect.height, 80.0);
let small_rect = Rect::new(0.0, 0.0, 10.0, 10.0);
let over_inset = small_rect.inset(20.0);
assert_eq!(over_inset.width, 0.0);
assert_eq!(over_inset.height, 0.0);
}
#[test]
fn test_rect_contains() {
let rect = Rect::new(10.0, 20.0, 100.0, 50.0);
assert!(rect.contains(50.0, 40.0));
assert!(rect.contains(10.0, 20.0)); assert!(!rect.contains(110.0, 70.0));
assert!(!rect.contains(5.0, 40.0));
assert!(!rect.contains(150.0, 40.0));
assert!(!rect.contains(50.0, 10.0));
assert!(!rect.contains(50.0, 100.0));
}
#[test]
fn test_padding_all() {
let padding = Padding::all(10.0);
assert_eq!(padding.top, 10.0);
assert_eq!(padding.right, 10.0);
assert_eq!(padding.bottom, 10.0);
assert_eq!(padding.left, 10.0);
}
#[test]
fn test_padding_two_value_shorthand() {
let padding = Padding::from([30.0, 20.0]);
assert_eq!(padding.top, 30.0);
assert_eq!(padding.right, 20.0);
assert_eq!(padding.bottom, 30.0);
assert_eq!(padding.left, 20.0);
}
#[test]
fn test_padding_totals_are_sums() {
let padding = Padding::from([10.0, 15.0]);
assert_eq!(padding.horizontal_total(), 30.0);
assert_eq!(padding.vertical_total(), 20.0);
}
#[test]
fn test_padding_default() {
let padding = Padding::default();
assert_eq!(padding.top, 0.0);
assert_eq!(padding.right, 0.0);
assert_eq!(padding.bottom, 0.0);
assert_eq!(padding.left, 0.0);
}
#[test]
fn test_padding_builder_methods() {
let padding = Padding::all(8.0).with_top(20.0).with_left(0.0);
assert_eq!(padding.top, 20.0);
assert_eq!(padding.right, 8.0);
assert_eq!(padding.bottom, 8.0);
assert_eq!(padding.left, 0.0);
}
#[test]
fn test_padding_from_f32() {
let padding = Padding::from(10.0);
assert_eq!(padding, Padding::all(10.0));
}
#[test]
fn test_padding_from_i32() {
let padding = Padding::from(10i32);
assert_eq!(padding, Padding::all(10.0));
}
#[test]
fn test_padding_from_u16() {
let padding = Padding::from(10u16);
assert_eq!(padding, Padding::all(10.0));
}
#[test]
fn test_padding_from_u32() {
let padding = Padding::from(10u32);
assert_eq!(padding, Padding::all(10.0));
}
#[test]
fn test_padding_from_array_2() {
let padding = Padding::from([8.0, 16.0]);
assert_eq!(padding.top, 8.0);
assert_eq!(padding.right, 16.0);
assert_eq!(padding.bottom, 8.0);
assert_eq!(padding.left, 16.0);
}
#[test]
fn test_padding_from_array_4() {
let padding = Padding::from([1.0, 2.0, 3.0, 4.0]);
assert_eq!(padding.top, 1.0);
assert_eq!(padding.right, 2.0);
assert_eq!(padding.bottom, 3.0);
assert_eq!(padding.left, 4.0);
}
}