use embedded_graphics::{
Drawable,
draw_target::{DrawTarget, DrawTargetExt},
image::{Image, ImageDrawable},
prelude::{OriginDimensions, PixelColor, Point, Primitive, Size},
primitives::{PrimitiveStyleBuilder, Rectangle, RoundedRectangle},
};
use crate::EdgeInsets;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ImageAlignment {
TopLeading,
Top,
TopTrailing,
Leading,
Center,
Trailing,
BottomLeading,
Bottom,
BottomTrailing,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ImageViewStyle<C> {
pub alignment: ImageAlignment,
pub insets: EdgeInsets,
pub background: Option<C>,
pub border: Option<C>,
pub border_width: u32,
pub corner_radius: u32,
}
impl<C> ImageViewStyle<C> {
pub const fn new() -> Self {
Self {
alignment: ImageAlignment::Center,
insets: EdgeInsets::all(0),
background: None,
border: None,
border_width: 0,
corner_radius: 0,
}
}
pub fn with_alignment(mut self, alignment: ImageAlignment) -> Self {
self.alignment = alignment;
self
}
pub fn with_insets(mut self, insets: EdgeInsets) -> Self {
self.insets = insets;
self
}
pub fn with_background(mut self, background: C) -> Self {
self.background = Some(background);
self
}
pub fn with_border(mut self, border: C, border_width: u32) -> Self {
self.border = Some(border);
self.border_width = border_width;
self
}
pub fn with_corner_radius(mut self, corner_radius: u32) -> Self {
self.corner_radius = corner_radius;
self
}
}
impl<C> Default for ImageViewStyle<C> {
fn default() -> Self {
Self::new()
}
}
pub struct ImageView<'a, T, C>
where
T: ImageDrawable<Color = C> + OriginDimensions,
C: PixelColor,
{
pub frame: Rectangle,
pub image: &'a T,
pub style: ImageViewStyle<C>,
}
impl<'a, T, C> ImageView<'a, T, C>
where
T: ImageDrawable<Color = C> + OriginDimensions,
C: PixelColor,
{
pub const fn new(frame: Rectangle, image: &'a T) -> Self {
Self {
frame,
image,
style: ImageViewStyle::new(),
}
}
pub fn with_style(mut self, style: ImageViewStyle<C>) -> Self {
self.style = style;
self
}
pub fn draw<D>(&self, display: &mut D)
where
D: DrawTarget<Color = C>,
{
draw_shell(
display,
self.frame,
self.style.background,
self.style.border,
self.style.border_width,
self.style.corner_radius,
);
let content = self.style.insets.inset_rect(self.frame);
if content.size.width == 0 || content.size.height == 0 {
return;
}
let image_size = self.image.size();
let origin = aligned_origin(content, image_size, self.style.alignment);
let mut clipped = display.clipped(&content);
Image::new(self.image, origin).draw(&mut clipped).ok();
}
}
fn draw_shell<D, C>(
display: &mut D,
frame: Rectangle,
background: Option<C>,
border: Option<C>,
border_width: u32,
corner_radius: u32,
) where
D: DrawTarget<Color = C>,
C: PixelColor,
{
if background.is_none() && border.is_none() {
return;
}
let mut style = PrimitiveStyleBuilder::new();
if let Some(background) = background {
style = style.fill_color(background);
}
if let Some(border) = border {
style = style.stroke_color(border).stroke_width(border_width.max(1));
}
RoundedRectangle::with_equal_corners(frame, Size::new(corner_radius, corner_radius))
.into_styled(style.build())
.draw(display)
.ok();
}
fn aligned_origin(frame: Rectangle, image_size: Size, alignment: ImageAlignment) -> Point {
let dx = frame.size.width as i32 - image_size.width as i32;
let dy = frame.size.height as i32 - image_size.height as i32;
let offset = match alignment {
ImageAlignment::TopLeading => Point::new(0, 0),
ImageAlignment::Top => Point::new(dx / 2, 0),
ImageAlignment::TopTrailing => Point::new(dx, 0),
ImageAlignment::Leading => Point::new(0, dy / 2),
ImageAlignment::Center => Point::new(dx / 2, dy / 2),
ImageAlignment::Trailing => Point::new(dx, dy / 2),
ImageAlignment::BottomLeading => Point::new(0, dy),
ImageAlignment::Bottom => Point::new(dx / 2, dy),
ImageAlignment::BottomTrailing => Point::new(dx, dy),
};
frame.top_left + offset
}