1use embedded_graphics::{
2 Drawable,
3 draw_target::{DrawTarget, DrawTargetExt},
4 image::{Image, ImageDrawable},
5 prelude::{OriginDimensions, PixelColor, Point, Primitive, Size},
6 primitives::{PrimitiveStyleBuilder, Rectangle, RoundedRectangle},
7};
8
9use crate::EdgeInsets;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum ImageAlignment {
14 TopLeading,
16 Top,
18 TopTrailing,
20 Leading,
22 Center,
24 Trailing,
26 BottomLeading,
28 Bottom,
30 BottomTrailing,
32}
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub struct ImageViewStyle<C> {
37 pub alignment: ImageAlignment,
39 pub insets: EdgeInsets,
41 pub background: Option<C>,
43 pub border: Option<C>,
45 pub border_width: u32,
47 pub corner_radius: u32,
49}
50
51impl<C> ImageViewStyle<C> {
52 pub const fn new() -> Self {
54 Self {
55 alignment: ImageAlignment::Center,
56 insets: EdgeInsets::all(0),
57 background: None,
58 border: None,
59 border_width: 0,
60 corner_radius: 0,
61 }
62 }
63
64 pub fn with_alignment(mut self, alignment: ImageAlignment) -> Self {
66 self.alignment = alignment;
67 self
68 }
69
70 pub fn with_insets(mut self, insets: EdgeInsets) -> Self {
72 self.insets = insets;
73 self
74 }
75
76 pub fn with_background(mut self, background: C) -> Self {
78 self.background = Some(background);
79 self
80 }
81
82 pub fn with_border(mut self, border: C, border_width: u32) -> Self {
84 self.border = Some(border);
85 self.border_width = border_width;
86 self
87 }
88
89 pub fn with_corner_radius(mut self, corner_radius: u32) -> Self {
91 self.corner_radius = corner_radius;
92 self
93 }
94}
95
96impl<C> Default for ImageViewStyle<C> {
97 fn default() -> Self {
98 Self::new()
99 }
100}
101
102pub struct ImageView<'a, T, C>
104where
105 T: ImageDrawable<Color = C> + OriginDimensions,
106 C: PixelColor,
107{
108 pub frame: Rectangle,
110 pub image: &'a T,
112 pub style: ImageViewStyle<C>,
114}
115
116impl<'a, T, C> ImageView<'a, T, C>
117where
118 T: ImageDrawable<Color = C> + OriginDimensions,
119 C: PixelColor,
120{
121 pub const fn new(frame: Rectangle, image: &'a T) -> Self {
123 Self {
124 frame,
125 image,
126 style: ImageViewStyle::new(),
127 }
128 }
129
130 pub fn with_style(mut self, style: ImageViewStyle<C>) -> Self {
132 self.style = style;
133 self
134 }
135
136 pub fn draw<D>(&self, display: &mut D)
138 where
139 D: DrawTarget<Color = C>,
140 {
141 draw_shell(
142 display,
143 self.frame,
144 self.style.background,
145 self.style.border,
146 self.style.border_width,
147 self.style.corner_radius,
148 );
149 let content = self.style.insets.inset_rect(self.frame);
150 if content.size.width == 0 || content.size.height == 0 {
151 return;
152 }
153
154 let image_size = self.image.size();
155 let origin = aligned_origin(content, image_size, self.style.alignment);
156 let mut clipped = display.clipped(&content);
157 Image::new(self.image, origin).draw(&mut clipped).ok();
158 }
159}
160
161fn draw_shell<D, C>(
162 display: &mut D,
163 frame: Rectangle,
164 background: Option<C>,
165 border: Option<C>,
166 border_width: u32,
167 corner_radius: u32,
168) where
169 D: DrawTarget<Color = C>,
170 C: PixelColor,
171{
172 if background.is_none() && border.is_none() {
173 return;
174 }
175
176 let mut style = PrimitiveStyleBuilder::new();
177 if let Some(background) = background {
178 style = style.fill_color(background);
179 }
180 if let Some(border) = border {
181 style = style.stroke_color(border).stroke_width(border_width.max(1));
182 }
183
184 RoundedRectangle::with_equal_corners(frame, Size::new(corner_radius, corner_radius))
185 .into_styled(style.build())
186 .draw(display)
187 .ok();
188}
189
190fn aligned_origin(frame: Rectangle, image_size: Size, alignment: ImageAlignment) -> Point {
191 let dx = frame.size.width as i32 - image_size.width as i32;
192 let dy = frame.size.height as i32 - image_size.height as i32;
193 let offset = match alignment {
194 ImageAlignment::TopLeading => Point::new(0, 0),
195 ImageAlignment::Top => Point::new(dx / 2, 0),
196 ImageAlignment::TopTrailing => Point::new(dx, 0),
197 ImageAlignment::Leading => Point::new(0, dy / 2),
198 ImageAlignment::Center => Point::new(dx / 2, dy / 2),
199 ImageAlignment::Trailing => Point::new(dx, dy / 2),
200 ImageAlignment::BottomLeading => Point::new(0, dy),
201 ImageAlignment::Bottom => Point::new(dx / 2, dy),
202 ImageAlignment::BottomTrailing => Point::new(dx, dy),
203 };
204 frame.top_left + offset
205}