embedded_graphics/primitives/
primitive_style.rs1use crate::{pixelcolor::PixelColor, primitives::OffsetOutline};
2use az::SaturatingAs;
3
4#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
17#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
18#[non_exhaustive]
19pub struct PrimitiveStyle<C>
20where
21 C: PixelColor,
22{
23 pub fill_color: Option<C>,
27
28 pub stroke_color: Option<C>,
33
34 pub stroke_width: u32,
36
37 pub stroke_alignment: StrokeAlignment,
45}
46
47impl<C> PrimitiveStyle<C>
48where
49 C: PixelColor,
50{
51 pub const fn new() -> Self {
53 Self::const_default()
54 }
55
56 pub const fn with_stroke(stroke_color: C, stroke_width: u32) -> Self {
60 Self {
61 stroke_color: Some(stroke_color),
62 stroke_width,
63 ..PrimitiveStyle::const_default()
64 }
65 }
66
67 pub const fn with_fill(fill_color: C) -> Self {
69 Self {
70 fill_color: Some(fill_color),
71 ..PrimitiveStyle::const_default()
72 }
73 }
74
75 pub(crate) const fn outside_stroke_width(&self) -> u32 {
79 match self.stroke_alignment {
80 StrokeAlignment::Inside => 0,
81 StrokeAlignment::Center => self.stroke_width / 2,
82 StrokeAlignment::Outside => self.stroke_width,
83 }
84 }
85
86 pub(crate) const fn inside_stroke_width(&self) -> u32 {
90 match self.stroke_alignment {
91 StrokeAlignment::Inside => self.stroke_width,
92 StrokeAlignment::Center => self.stroke_width.saturating_add(1) / 2,
93 StrokeAlignment::Outside => 0,
94 }
95 }
96
97 pub const fn is_transparent(&self) -> bool {
99 (self.stroke_color.is_none() || self.stroke_width == 0) && self.fill_color.is_none()
100 }
101
102 pub(crate) fn effective_stroke_color(&self) -> Option<C> {
107 self.stroke_color.filter(|_| self.stroke_width > 0)
108 }
109
110 pub(in crate::primitives) fn stroke_area<P: OffsetOutline>(&self, primitive: &P) -> P {
112 let offset = self.outside_stroke_width().saturating_as();
114
115 primitive.offset(offset)
116 }
117
118 pub(in crate::primitives) fn fill_area<P: OffsetOutline>(&self, primitive: &P) -> P {
120 let offset = -self.inside_stroke_width().saturating_as::<i32>();
122
123 primitive.offset(offset)
124 }
125
126 const fn const_default() -> Self {
129 Self {
130 fill_color: None,
131 stroke_color: None,
132 stroke_width: 0,
133 stroke_alignment: StrokeAlignment::Center,
134 }
135 }
136}
137
138impl<C> Default for PrimitiveStyle<C>
139where
140 C: PixelColor,
141{
142 fn default() -> Self {
143 Self::const_default()
144 }
145}
146
147#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
196#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
197pub struct PrimitiveStyleBuilder<C>
198where
199 C: PixelColor,
200{
201 style: PrimitiveStyle<C>,
202}
203
204impl<C> PrimitiveStyleBuilder<C>
205where
206 C: PixelColor,
207{
208 pub const fn new() -> Self {
210 Self {
211 style: PrimitiveStyle::const_default(),
212 }
213 }
214
215 pub const fn fill_color(mut self, fill_color: C) -> Self {
217 self.style.fill_color = Some(fill_color);
218
219 self
220 }
221
222 pub const fn reset_fill_color(mut self) -> Self {
224 self.style.fill_color = None;
225
226 self
227 }
228
229 pub const fn stroke_color(mut self, stroke_color: C) -> Self {
231 self.style.stroke_color = Some(stroke_color);
232
233 self
234 }
235
236 pub const fn reset_stroke_color(mut self) -> Self {
238 self.style.stroke_color = None;
239
240 self
241 }
242
243 pub const fn stroke_width(mut self, stroke_width: u32) -> Self {
245 self.style.stroke_width = stroke_width;
246
247 self
248 }
249
250 pub const fn stroke_alignment(mut self, stroke_alignment: StrokeAlignment) -> Self {
252 self.style.stroke_alignment = stroke_alignment;
253
254 self
255 }
256
257 pub const fn build(self) -> PrimitiveStyle<C> {
259 self.style
260 }
261}
262
263impl<C> From<&PrimitiveStyle<C>> for PrimitiveStyleBuilder<C>
264where
265 C: PixelColor,
266{
267 fn from(style: &PrimitiveStyle<C>) -> Self {
268 Self { style: *style }
269 }
270}
271
272#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
274#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
275pub enum StrokeAlignment {
276 Inside,
278 Center,
280 Outside,
282}
283
284impl Default for StrokeAlignment {
285 fn default() -> Self {
286 Self::Center
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use crate::pixelcolor::{BinaryColor, Rgb888, RgbColor};
294
295 #[test]
296 fn default_style() {
297 assert_eq!(
298 PrimitiveStyle::<BinaryColor>::default(),
299 PrimitiveStyle {
300 fill_color: None,
301 stroke_color: None,
302 stroke_width: 0,
303 stroke_alignment: StrokeAlignment::Center,
304 }
305 );
306
307 assert_eq!(
308 PrimitiveStyle::<BinaryColor>::default(),
309 PrimitiveStyle::new()
310 );
311 }
312
313 #[test]
314 fn constructors() {
315 let style = PrimitiveStyle::with_fill(Rgb888::RED);
316 assert_eq!(style.fill_color, Some(Rgb888::RED));
317 assert_eq!(style.stroke_color, None);
318
319 let style = PrimitiveStyle::with_stroke(Rgb888::GREEN, 123);
320 assert_eq!(style.fill_color, None);
321 assert_eq!(style.stroke_color, Some(Rgb888::GREEN));
322 assert_eq!(style.stroke_width, 123);
323 }
324
325 #[test]
326 fn stroke_alignment_1px() {
327 let mut style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
328
329 style.stroke_alignment = StrokeAlignment::Inside;
330 assert_eq!(style.inside_stroke_width(), 1);
331 assert_eq!(style.outside_stroke_width(), 0);
332
333 style.stroke_alignment = StrokeAlignment::Center;
334 assert_eq!(style.inside_stroke_width(), 1);
335 assert_eq!(style.outside_stroke_width(), 0);
336
337 style.stroke_alignment = StrokeAlignment::Outside;
338 assert_eq!(style.inside_stroke_width(), 0);
339 assert_eq!(style.outside_stroke_width(), 1);
340 }
341
342 #[test]
343 fn stroke_alignment_2px() {
344 let mut style = PrimitiveStyle::with_stroke(BinaryColor::On, 2);
345
346 style.stroke_alignment = StrokeAlignment::Inside;
347 assert_eq!(style.inside_stroke_width(), 2);
348 assert_eq!(style.outside_stroke_width(), 0);
349
350 style.stroke_alignment = StrokeAlignment::Center;
351 assert_eq!(style.inside_stroke_width(), 1);
352 assert_eq!(style.outside_stroke_width(), 1);
353
354 style.stroke_alignment = StrokeAlignment::Outside;
355 assert_eq!(style.inside_stroke_width(), 0);
356 assert_eq!(style.outside_stroke_width(), 2);
357 }
358
359 #[test]
360 fn builder_default() {
361 assert_eq!(
362 PrimitiveStyleBuilder::<BinaryColor>::new().build(),
363 PrimitiveStyle::<BinaryColor>::default()
364 );
365 }
366
367 #[test]
368 fn builder_stroke() {
369 assert_eq!(
370 PrimitiveStyleBuilder::new()
371 .stroke_color(BinaryColor::On)
372 .stroke_width(10)
373 .build(),
374 PrimitiveStyle::with_stroke(BinaryColor::On, 10)
375 );
376 }
377
378 #[test]
379 fn builder_reset_stroke_color() {
380 assert_eq!(
381 PrimitiveStyleBuilder::new()
382 .stroke_color(BinaryColor::On)
383 .stroke_width(10)
384 .fill_color(BinaryColor::Off)
385 .reset_stroke_color()
386 .build(),
387 PrimitiveStyleBuilder::new()
388 .stroke_width(10)
389 .fill_color(BinaryColor::Off)
390 .build()
391 );
392 }
393
394 #[test]
395 fn builder_fill() {
396 assert_eq!(
397 PrimitiveStyleBuilder::new()
398 .fill_color(BinaryColor::On)
399 .build(),
400 PrimitiveStyle::with_fill(BinaryColor::On)
401 );
402 }
403
404 #[test]
405 fn builder_reset_fill_color() {
406 assert_eq!(
407 PrimitiveStyleBuilder::new()
408 .fill_color(BinaryColor::On)
409 .stroke_color(BinaryColor::Off)
410 .reset_fill_color()
411 .build(),
412 PrimitiveStyleBuilder::new()
413 .stroke_color(BinaryColor::Off)
414 .build(),
415 );
416 }
417
418 #[test]
419 fn effective_stroke_color() {
420 assert_eq!(
421 PrimitiveStyle::with_stroke(BinaryColor::On, 1).effective_stroke_color(),
422 Some(BinaryColor::On)
423 );
424
425 assert_eq!(
426 PrimitiveStyle::with_stroke(BinaryColor::On, 0).effective_stroke_color(),
427 None
428 );
429 }
430
431 #[test]
432 fn stroke_width_max_value() {
433 assert_eq!(
434 PrimitiveStyleBuilder::from(&PrimitiveStyle::with_stroke(
435 BinaryColor::On,
436 core::u32::MAX
437 ))
438 .stroke_alignment(StrokeAlignment::Center)
439 .build()
440 .inside_stroke_width(),
441 core::u32::MAX / 2
442 );
443 }
444}