use embedded_graphics::{
pixelcolor::Rgb565,
prelude::{PixelColor, Size},
};
use crate::Constraints;
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Insets {
pub top: u32,
pub right: u32,
pub bottom: u32,
pub left: u32,
}
impl Insets {
pub const ZERO: Self = Self::uniform(0);
pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
Self { top, right, bottom, left }
}
pub const fn uniform(value: u32) -> Self {
Self::new(value, value, value, value)
}
pub const fn zero() -> Self {
Self::ZERO
}
pub const fn horizontal(self) -> u32 {
self.left.saturating_add(self.right)
}
pub const fn vertical(self) -> u32 {
self.top.saturating_add(self.bottom)
}
pub const fn total_size(self) -> Size {
Size::new(self.horizontal(), self.vertical())
}
pub const fn saturating_add(self, other: Self) -> Self {
Self::new(
self.top.saturating_add(other.top),
self.right.saturating_add(other.right),
self.bottom.saturating_add(other.bottom),
self.left.saturating_add(other.left),
)
}
}
macro_rules! impl_insets_from {
($type:ty, $convert:expr) => {
impl From<$type> for Insets {
fn from(value: $type) -> Self {
let convert = $convert;
Self::uniform(convert(value))
}
}
impl From<($type, $type)> for Insets {
fn from((vertical, horizontal): ($type, $type)) -> Self {
let convert = $convert;
Self::new(
convert(vertical),
convert(horizontal),
convert(vertical),
convert(horizontal),
)
}
}
impl From<($type, $type, $type)> for Insets {
fn from((top, horizontal, bottom): ($type, $type, $type)) -> Self {
let convert = $convert;
Self::new(convert(top), convert(horizontal), convert(bottom), convert(horizontal))
}
}
impl From<($type, $type, $type, $type)> for Insets {
fn from((top, right, bottom, left): ($type, $type, $type, $type)) -> Self {
let convert = $convert;
Self::new(convert(top), convert(right), convert(bottom), convert(left))
}
}
};
}
impl_insets_from!(u32, |value: u32| value);
impl_insets_from!(usize, |value: usize| u32::try_from(value).unwrap_or(u32::MAX));
impl_insets_from!(i32, |value: i32| {
assert!(value >= 0, "insets cannot be negative");
value as u32
});
pub(crate) struct BoxStyle {
pub margin: Insets,
pub border: Insets,
pub padding: Insets,
pub size: Option<Size>,
}
impl BoxStyle {
pub(crate) const fn border_constraints(&self, constraints: Constraints) -> Constraints {
constraints.deflate(self.margin.total_size())
}
pub(crate) fn content_constraints(&self, constraints: Constraints) -> Constraints {
let border_constraints = self.border_constraints(constraints);
let content_size = self.content_insets().total_size();
let border_constraints = match self.size {
Some(size) => {
let desired = Size::new(
size.width.max(content_size.width),
size.height.max(content_size.height),
);
let border_size = border_constraints.constrain(desired);
Constraints::exact(border_size)
}
None => border_constraints,
};
border_constraints.deflate(content_size).loosen()
}
pub(crate) const fn content_insets(&self) -> Insets {
self.border.saturating_add(self.padding)
}
}
#[derive(PartialEq, Eq)]
pub struct Style<S: Default, C = Rgb565> {
pub margin: Insets,
pub padding: Insets,
pub border: Insets,
pub border_color: Option<C>,
pub background: Option<C>,
pub size: Option<Size>,
pub specific: S,
}
impl<S: Default, C> Default for Style<S, C> {
fn default() -> Self {
Self {
margin: Insets::ZERO,
padding: Insets::ZERO,
border: Insets::ZERO,
border_color: None,
background: None,
size: None,
specific: S::default(),
}
}
}
impl<S: Default, C> Style<S, C> {
pub(crate) const fn box_style(&self) -> BoxStyle {
BoxStyle {
margin: self.margin,
padding: self.padding,
border: self.border,
size: self.size,
}
}
}
impl<S: Default, C> core::ops::Deref for Style<S, C> {
type Target = S;
fn deref(&self) -> &Self::Target {
&self.specific
}
}
impl<S: Default, C> core::ops::DerefMut for Style<S, C> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.specific
}
}
pub trait StyledElement: Sized {
type Color: PixelColor;
type Specific: Default;
fn style(&self) -> &Style<Self::Specific, Self::Color>;
fn style_mut(&mut self) -> &mut Style<Self::Specific, Self::Color>;
fn padding(mut self, padding: impl Into<Insets>) -> Self {
self.style_mut().padding = padding.into();
self
}
fn margin(mut self, margin: impl Into<Insets>) -> Self {
self.style_mut().margin = margin.into();
self
}
fn background(mut self, color: Self::Color) -> Self {
self.style_mut().background = Some(color);
self
}
fn bg(self, color: Self::Color) -> Self {
self.background(color)
}
fn border(mut self, border: impl Into<Insets>) -> Self {
self.style_mut().border = border.into();
self
}
fn size(mut self, size: Size) -> Self {
self.style_mut().size = Some(size);
self
}
fn border_color(mut self, color: Self::Color) -> Self {
self.style_mut().border_color = Some(color);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn css_shorthands_expand_in_trbl_order() {
assert_eq!(Insets::from(10), Insets::new(10, 10, 10, 10));
assert_eq!(Insets::from((4, 8)), Insets::new(4, 8, 4, 8));
assert_eq!(Insets::from((4, 8, 12)), Insets::new(4, 8, 12, 8));
assert_eq!(Insets::from((4, 8, 12, 16)), Insets::new(4, 8, 12, 16));
}
#[test]
fn typed_unsigned_inputs_are_supported() {
assert_eq!(Insets::from(3_u32), Insets::uniform(3));
assert_eq!(Insets::from((1_usize, 2, 3)), Insets::new(1, 2, 3, 2));
}
#[test]
fn usize_conversion_saturates() {
assert_eq!(Insets::from(usize::MAX), Insets::uniform(u32::MAX));
}
#[test]
#[should_panic(expected = "insets cannot be negative")]
fn signed_conversion_rejects_negative_values() {
let _ = Insets::from((1, -2));
}
#[test]
fn inset_arithmetic_saturates() {
let insets = Insets::new(u32::MAX, u32::MAX, 3, 4);
assert_eq!(insets.horizontal(), u32::MAX);
assert_eq!(insets.vertical(), u32::MAX);
assert_eq!(
insets.saturating_add(Insets::new(1, 2, u32::MAX, u32::MAX)),
Insets::new(u32::MAX, u32::MAX, u32::MAX, u32::MAX),
);
}
}