blob: 69da6c93ec65f6921a5b0e344dbb89fc8e320420 [file] [log] [blame]
mod test_env;
#[cfg(test)]
mod tests {
use super::test_env::*;
use forma::{
BlendMode, Color, Fill, FillRule, Func, Gradient, GradientBuilder, Order, Path,
PathBuilder, Point, Props, Style,
};
fn triangle() -> Path {
PathBuilder::new()
.move_to(Point { x: PADDING, y: PADDING })
.line_to(Point { x: WIDTH - PADDING, y: PADDING })
.line_to(Point { x: WIDTH - PADDING, y: HEIGHT - PADDING })
.build()
}
fn square() -> Path {
PathBuilder::new()
.move_to(Point { x: PADDING, y: PADDING })
.line_to(Point { x: WIDTH - PADDING, y: PADDING })
.line_to(Point { x: WIDTH - PADDING, y: HEIGHT - PADDING })
.line_to(Point { x: PADDING, y: HEIGHT - PADDING })
.build()
}
fn inner_square() -> Path {
PathBuilder::new()
.move_to(Point { x: PADDING * 2.0, y: PADDING * 2.0 })
.line_to(Point { x: WIDTH - PADDING * 2.0, y: PADDING * 2.0 })
.line_to(Point { x: WIDTH - PADDING * 2.0, y: HEIGHT - PADDING * 2.0 })
.line_to(Point { x: PADDING * 2.0, y: HEIGHT - PADDING * 2.0 })
.build()
}
fn circle() -> Path {
let weight = 2.0f32.sqrt() / 2.0;
let x = WIDTH * 0.5;
let y = HEIGHT * 0.5;
let radius = WIDTH * 0.5 - PADDING;
PathBuilder::new()
.move_to(Point::new(x + radius, y))
.rat_quad_to(Point::new(x + radius, y - radius), Point::new(x, y - radius), weight)
.rat_quad_to(Point::new(x - radius, y - radius), Point::new(x - radius, y), weight)
.rat_quad_to(Point::new(x - radius, y + radius), Point::new(x, y + radius), weight)
.rat_quad_to(Point::new(x + radius, y + radius), Point::new(x + radius, y), weight)
.build()
}
fn rainbow_colors(gradient_builder: &mut GradientBuilder) {
gradient_builder
.color(Color { r: 1.00, g: 0.00, b: 0.00, a: 1.0 })
.color(Color { r: 1.00, g: 0.32, b: 0.00, a: 1.0 })
.color(Color { r: 0.63, g: 0.73, b: 0.02, a: 1.0 })
.color(Color { r: 0.08, g: 0.72, b: 0.07, a: 1.0 })
.color(Color { r: 0.05, g: 0.70, b: 0.69, a: 1.0 })
.color(Color { r: 0.03, g: 0.58, b: 0.76, a: 1.0 })
.color(Color { r: 0.01, g: 0.21, b: 0.85, a: 1.0 })
.color(Color { r: 0.11, g: 0.01, b: 0.89, a: 1.0 })
.color(Color { r: 0.49, g: 0.00, b: 0.94, a: 1.0 })
.color(Color { r: 0.96, g: 0.00, b: 0.69, a: 1.0 })
.color(Color { r: 1.00, g: 0.00, b: 0.00, a: 1.0 });
}
fn vertical_rainbow() -> Gradient {
let mut gradient_builder = GradientBuilder::new(
Point { x: PADDING, y: 0.0 },
Point { x: WIDTH - PADDING, y: 0.0 },
);
rainbow_colors(&mut gradient_builder);
gradient_builder.build().unwrap()
}
fn horizontal_rainbow() -> Gradient {
let mut gradient_builder = GradientBuilder::new(
Point { x: 0.0, y: PADDING },
Point { x: 0.0, y: WIDTH - PADDING },
);
rainbow_colors(&mut gradient_builder);
gradient_builder.build().unwrap()
}
fn solid_color_props(color: Color) -> Props {
Props {
func: Func::Draw(Style { fill: Fill::Solid(color), ..Default::default() }),
..Default::default()
}
}
#[test]
fn linear_gradient() {
let test_env = test_env!();
test_env.test_render(|composition| {
let mut gradient_builder = GradientBuilder::new(
Point { x: PADDING, y: 0.0 },
Point { x: WIDTH - PADDING, y: 0.0 },
);
gradient_builder
.color(Color { r: 0.0, g: 0.0, b: 1.0, a: 1.0 })
.color(Color { r: 1.0, g: 1.0, b: 1.0, a: 1.0 })
.color(Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0 });
let props = Props {
func: Func::Draw(Style {
fill: Fill::Gradient(gradient_builder.build().unwrap()),
..Default::default()
}),
..Default::default()
};
composition
.get_mut_or_insert_default(Order::new(1).unwrap())
.insert(&triangle())
.set_props(props);
});
}
#[test]
fn radial_gradient() {
let test_env = test_env!();
test_env.test_render(|composition| {
let mut gradient_builder = GradientBuilder::new(
Point { x: WIDTH * 0.5, y: HEIGHT * 0.5 },
Point { x: WIDTH - PADDING * 2.0, y: HEIGHT * 0.5 },
);
gradient_builder
.r#type(forma::GradientType::Radial)
.color(Color { r: 0.0, g: 0.0, b: 1.0, a: 1.0 })
.color(Color { r: 1.0, g: 1.0, b: 1.0, a: 1.0 })
.color(Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0 });
let props = Props {
func: Func::Draw(Style {
fill: Fill::Gradient(gradient_builder.build().unwrap()),
..Default::default()
}),
..Default::default()
};
composition
.get_mut_or_insert_default(Order::new(1).unwrap())
.insert(&circle())
.set_props(props);
});
}
#[test]
fn solid_color() {
let test_env = test_env!();
let colors = vec![
(Color { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }, "blue"),
(Color { r: 0.0, g: 0.0, b: 0.5, a: 1.0 }, "dark_blue"),
(Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, "red"),
(Color { r: 0.5, g: 0.0, b: 0.0, a: 1.0 }, "dark_red"),
(Color { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }, "green"),
(Color { r: 0.0, g: 0.5, b: 0.0, a: 1.0 }, "dark_green"),
(Color { r: 0.0, g: 0.0, b: 0.0, a: 0.5 }, "transparent_black"),
];
for (color, name) in colors {
test_env.test_render_param(
|composition| {
composition
.get_mut_or_insert_default(Order::new(1).unwrap())
.insert(&square())
.set_props(solid_color_props(color));
},
name,
);
}
}
#[test]
fn pixel() {
// This test is useful when the reasterizer is brocken as it emmits 2 pixel segments.
test_env!().test_render(|composition| {
composition
.get_mut_or_insert_default(Order::new(1).unwrap())
.insert(
&PathBuilder::new()
.move_to(Point { x: PADDING, y: PADDING })
.line_to(Point { x: PADDING + 1.0, y: PADDING })
.line_to(Point { x: PADDING + 1.0, y: PADDING + 1.0 })
.line_to(Point { x: PADDING, y: PADDING + 1.0 })
.build(),
)
.set_props(solid_color_props(Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }));
});
}
#[test]
fn blend_modes() {
let test_env = test_env!();
let blend_modes = [
BlendMode::Over,
BlendMode::Multiply,
BlendMode::Screen,
BlendMode::Overlay,
BlendMode::Darken,
BlendMode::Lighten,
BlendMode::ColorDodge,
BlendMode::ColorBurn,
BlendMode::HardLight,
BlendMode::SoftLight,
BlendMode::Difference,
BlendMode::Exclusion,
BlendMode::Hue,
BlendMode::Saturation,
BlendMode::Color,
BlendMode::Luminosity,
];
for blend_mode in blend_modes {
test_env.test_render_param(
|composition| {
composition
.get_mut_or_insert_default(Order::new(0).unwrap())
.insert(&square())
.set_props(Props {
func: Func::Draw(Style {
fill: Fill::Gradient(horizontal_rainbow()),
..Default::default()
}),
..Default::default()
});
composition
.get_mut_or_insert_default(Order::new(1).unwrap())
.insert(&triangle())
.set_props(Props {
func: Func::Draw(Style {
fill: Fill::Gradient(vertical_rainbow()),
blend_mode,
..Default::default()
}),
..Default::default()
});
},
blend_mode,
);
}
}
#[test]
fn fill_rules() {
let test_env = test_env!();
let fill_rules = [FillRule::EvenOdd, FillRule::NonZero];
for fill_rule in fill_rules {
test_env.test_render_param(
|composition| {
let path = PathBuilder::new()
.move_to(Point { x: PADDING, y: PADDING })
.line_to(Point { x: WIDTH / 2.0 + PADDING, y: HEIGHT / 2.0 + PADDING })
.line_to(Point { x: WIDTH / 2.0 - PADDING, y: HEIGHT / 2.0 + PADDING })
.line_to(Point { x: WIDTH - PADDING, y: PADDING })
.line_to(Point { x: WIDTH - PADDING, y: HEIGHT - PADDING })
.line_to(Point { x: PADDING, y: HEIGHT - PADDING })
.build();
composition
.get_mut_or_insert_default(Order::new(0).unwrap())
.insert(&path)
.set_props(Props {
fill_rule,
func: Func::Draw(Style {
fill: Fill::Solid(Color { r: 0.0, g: 0.0, b: 0.0, a: 0.8 }),
..Default::default()
}),
});
},
fill_rule,
);
}
}
#[test]
fn clipping() {
let test_env = test_env!();
test_env.test_render(|composition| {
// First layer is not clipped.
composition
.get_mut_or_insert_default(Order::new(0).unwrap())
.insert(&square())
.set_props(solid_color_props(Color { r: 0.0, g: 0.0, b: 0.0, a: 0.2 }));
// Triangular clip shape applies to the next 3 layers ids.
let props = Props { func: Func::Clip(3), ..Default::default() };
composition
.get_mut_or_insert_default(Order::new(1).unwrap())
.insert(&triangle())
.set_props(props);
// The blue square is clipped.
composition
.get_mut_or_insert_default(Order::new(2).unwrap())
.insert(&square())
.set_props(Props {
func: Func::Draw(Style {
fill: Fill::Solid(Color { r: 0.5, g: 0.5, b: 1.0, a: 0.6 }),
is_clipped: true,
..Default::default()
}),
..Default::default()
});
// Order No. 3 is intentionnaly left empty to test the clip implementation.
// The pink circle is clipped.
composition
.get_mut_or_insert_default(Order::new(4).unwrap())
.insert(&circle())
.set_props(Props {
func: Func::Draw(Style {
fill: Fill::Solid(Color { r: 1.0, g: 0.5, b: 0.5, a: 0.6 }),
is_clipped: true,
..Default::default()
}),
..Default::default()
});
// This is not drawn given that `is_clipped: true` and no clipping
// is active at order 5.
composition
.get_mut_or_insert_default(Order::new(5).unwrap())
.insert(&inner_square())
.set_props(Props {
func: Func::Draw(Style {
fill: Fill::Solid(Color { r: 0.5, g: 0.5, b: 1.0, a: 0.6 }),
is_clipped: true,
..Default::default()
}),
..Default::default()
});
});
}
}