use crate::{pixelcolor::PixelColor, primitives::OffsetOutline};
use az::SaturatingAs;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
#[non_exhaustive]
pub struct PrimitiveStyle<C>
where
C: PixelColor,
{
pub fill_color: Option<C>,
pub stroke_color: Option<C>,
pub stroke_width: u32,
pub stroke_alignment: StrokeAlignment,
}
impl<C> PrimitiveStyle<C>
where
C: PixelColor,
{
pub const fn new() -> Self {
Self::const_default()
}
pub const fn with_stroke(stroke_color: C, stroke_width: u32) -> Self {
Self {
stroke_color: Some(stroke_color),
stroke_width,
..PrimitiveStyle::const_default()
}
}
pub const fn with_fill(fill_color: C) -> Self {
Self {
fill_color: Some(fill_color),
..PrimitiveStyle::const_default()
}
}
pub(crate) const fn outside_stroke_width(&self) -> u32 {
match self.stroke_alignment {
StrokeAlignment::Inside => 0,
StrokeAlignment::Center => self.stroke_width / 2,
StrokeAlignment::Outside => self.stroke_width,
}
}
pub(crate) const fn inside_stroke_width(&self) -> u32 {
match self.stroke_alignment {
StrokeAlignment::Inside => self.stroke_width,
StrokeAlignment::Center => self.stroke_width.saturating_add(1) / 2,
StrokeAlignment::Outside => 0,
}
}
pub const fn is_transparent(&self) -> bool {
(self.stroke_color.is_none() || self.stroke_width == 0) && self.fill_color.is_none()
}
pub(crate) fn effective_stroke_color(&self) -> Option<C> {
self.stroke_color.filter(|_| self.stroke_width > 0)
}
pub(in crate::primitives) fn stroke_area<P: OffsetOutline>(&self, primitive: &P) -> P {
let offset = self.outside_stroke_width().saturating_as();
primitive.offset(offset)
}
pub(in crate::primitives) fn fill_area<P: OffsetOutline>(&self, primitive: &P) -> P {
let offset = -self.inside_stroke_width().saturating_as::<i32>();
primitive.offset(offset)
}
const fn const_default() -> Self {
Self {
fill_color: None,
stroke_color: None,
stroke_width: 0,
stroke_alignment: StrokeAlignment::Center,
}
}
}
impl<C> Default for PrimitiveStyle<C>
where
C: PixelColor,
{
fn default() -> Self {
Self::const_default()
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct PrimitiveStyleBuilder<C>
where
C: PixelColor,
{
style: PrimitiveStyle<C>,
}
impl<C> PrimitiveStyleBuilder<C>
where
C: PixelColor,
{
pub const fn new() -> Self {
Self {
style: PrimitiveStyle::const_default(),
}
}
pub const fn fill_color(mut self, fill_color: C) -> Self {
self.style.fill_color = Some(fill_color);
self
}
pub const fn reset_fill_color(mut self) -> Self {
self.style.fill_color = None;
self
}
pub const fn stroke_color(mut self, stroke_color: C) -> Self {
self.style.stroke_color = Some(stroke_color);
self
}
pub const fn reset_stroke_color(mut self) -> Self {
self.style.stroke_color = None;
self
}
pub const fn stroke_width(mut self, stroke_width: u32) -> Self {
self.style.stroke_width = stroke_width;
self
}
pub const fn stroke_alignment(mut self, stroke_alignment: StrokeAlignment) -> Self {
self.style.stroke_alignment = stroke_alignment;
self
}
pub const fn build(self) -> PrimitiveStyle<C> {
self.style
}
}
impl<C> From<&PrimitiveStyle<C>> for PrimitiveStyleBuilder<C>
where
C: PixelColor,
{
fn from(style: &PrimitiveStyle<C>) -> Self {
Self { style: *style }
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub enum StrokeAlignment {
Inside,
Center,
Outside,
}
impl Default for StrokeAlignment {
fn default() -> Self {
Self::Center
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pixelcolor::{BinaryColor, Rgb888, RgbColor};
#[test]
fn default_style() {
assert_eq!(
PrimitiveStyle::<BinaryColor>::default(),
PrimitiveStyle {
fill_color: None,
stroke_color: None,
stroke_width: 0,
stroke_alignment: StrokeAlignment::Center,
}
);
assert_eq!(
PrimitiveStyle::<BinaryColor>::default(),
PrimitiveStyle::new()
);
}
#[test]
fn constructors() {
let style = PrimitiveStyle::with_fill(Rgb888::RED);
assert_eq!(style.fill_color, Some(Rgb888::RED));
assert_eq!(style.stroke_color, None);
let style = PrimitiveStyle::with_stroke(Rgb888::GREEN, 123);
assert_eq!(style.fill_color, None);
assert_eq!(style.stroke_color, Some(Rgb888::GREEN));
assert_eq!(style.stroke_width, 123);
}
#[test]
fn stroke_alignment_1px() {
let mut style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
style.stroke_alignment = StrokeAlignment::Inside;
assert_eq!(style.inside_stroke_width(), 1);
assert_eq!(style.outside_stroke_width(), 0);
style.stroke_alignment = StrokeAlignment::Center;
assert_eq!(style.inside_stroke_width(), 1);
assert_eq!(style.outside_stroke_width(), 0);
style.stroke_alignment = StrokeAlignment::Outside;
assert_eq!(style.inside_stroke_width(), 0);
assert_eq!(style.outside_stroke_width(), 1);
}
#[test]
fn stroke_alignment_2px() {
let mut style = PrimitiveStyle::with_stroke(BinaryColor::On, 2);
style.stroke_alignment = StrokeAlignment::Inside;
assert_eq!(style.inside_stroke_width(), 2);
assert_eq!(style.outside_stroke_width(), 0);
style.stroke_alignment = StrokeAlignment::Center;
assert_eq!(style.inside_stroke_width(), 1);
assert_eq!(style.outside_stroke_width(), 1);
style.stroke_alignment = StrokeAlignment::Outside;
assert_eq!(style.inside_stroke_width(), 0);
assert_eq!(style.outside_stroke_width(), 2);
}
#[test]
fn builder_default() {
assert_eq!(
PrimitiveStyleBuilder::<BinaryColor>::new().build(),
PrimitiveStyle::<BinaryColor>::default()
);
}
#[test]
fn builder_stroke() {
assert_eq!(
PrimitiveStyleBuilder::new()
.stroke_color(BinaryColor::On)
.stroke_width(10)
.build(),
PrimitiveStyle::with_stroke(BinaryColor::On, 10)
);
}
#[test]
fn builder_reset_stroke_color() {
assert_eq!(
PrimitiveStyleBuilder::new()
.stroke_color(BinaryColor::On)
.stroke_width(10)
.fill_color(BinaryColor::Off)
.reset_stroke_color()
.build(),
PrimitiveStyleBuilder::new()
.stroke_width(10)
.fill_color(BinaryColor::Off)
.build()
);
}
#[test]
fn builder_fill() {
assert_eq!(
PrimitiveStyleBuilder::new()
.fill_color(BinaryColor::On)
.build(),
PrimitiveStyle::with_fill(BinaryColor::On)
);
}
#[test]
fn builder_reset_fill_color() {
assert_eq!(
PrimitiveStyleBuilder::new()
.fill_color(BinaryColor::On)
.stroke_color(BinaryColor::Off)
.reset_fill_color()
.build(),
PrimitiveStyleBuilder::new()
.stroke_color(BinaryColor::Off)
.build(),
);
}
#[test]
fn effective_stroke_color() {
assert_eq!(
PrimitiveStyle::with_stroke(BinaryColor::On, 1).effective_stroke_color(),
Some(BinaryColor::On)
);
assert_eq!(
PrimitiveStyle::with_stroke(BinaryColor::On, 0).effective_stroke_color(),
None
);
}
#[test]
fn stroke_width_max_value() {
assert_eq!(
PrimitiveStyleBuilder::from(&PrimitiveStyle::with_stroke(
BinaryColor::On,
core::u32::MAX
))
.stroke_alignment(StrokeAlignment::Center)
.build()
.inside_stroke_width(),
core::u32::MAX / 2
);
}
}