embedded_menu/selection_indicator/style/
triangle.rs

1use embedded_graphics::{
2    prelude::{DrawTarget, PixelColor, Point, Size},
3    primitives::{ContainsPoint, Primitive, PrimitiveStyle, Rectangle, Triangle as TriangleShape},
4    transform::Transform,
5    Drawable,
6};
7use embedded_layout::prelude::{horizontal::LeftToRight, vertical::Center, Align};
8
9use crate::{
10    interaction::InputState,
11    selection_indicator::{
12        style::{interpolate, IndicatorStyle},
13        Insets,
14    },
15    theme::Theme,
16};
17
18#[derive(Clone, Copy)]
19pub struct Triangle;
20
21impl IndicatorStyle for Triangle {
22    type Shape = Arrow;
23    type State = ();
24
25    fn padding(&self, _state: &Self::State, height: i32) -> Insets {
26        Insets {
27            left: height / 2 + 1,
28            top: 0,
29            right: 0,
30            bottom: 0,
31        }
32    }
33
34    fn shape(&self, _state: &Self::State, bounds: Rectangle, fill_width: u32) -> Self::Shape {
35        Arrow::new(bounds, fill_width)
36    }
37
38    fn draw<T, D>(
39        &self,
40        state: &Self::State,
41        input_state: InputState,
42        theme: &T,
43        display: &mut D,
44    ) -> Result<Self::Shape, D::Error>
45    where
46        T: Theme,
47        D: DrawTarget<Color = T::Color>,
48    {
49        let display_area = display.bounding_box();
50
51        let fill_width = if let InputState::InProgress(progress) = input_state {
52            interpolate(progress as u32, 0, 255, 0, display_area.size.width)
53        } else {
54            0
55        };
56
57        let shape = self.shape(state, display_area, fill_width);
58
59        shape.draw(theme.selection_color(), display)?;
60
61        Ok(shape)
62    }
63}
64
65#[derive(Clone, Copy)]
66pub struct Arrow {
67    body: Rectangle,
68    tip: TriangleShape,
69}
70
71const SHRINK: i32 = 1;
72
73impl Arrow {
74    pub fn new(bounds: Rectangle, fill_width: u32) -> Self {
75        let body = Rectangle::new(bounds.top_left, Size::new(fill_width, bounds.size.height));
76
77        let tip = TriangleShape::new(
78            Point::new(0, SHRINK),
79            Point::new(0, Self::tip_width(bounds)),
80            Point::new(
81                bounds.size.height as i32 / 2 - SHRINK,
82                bounds.size.height as i32 / 2,
83            ),
84        )
85        .align_to(&body, LeftToRight, Center)
86        // e-layout doesn't align well to 0 area rectangles
87        .translate(Point::new(if body.is_zero_sized() { -1 } else { 0 }, 0));
88
89        Self { body, tip }
90    }
91
92    pub fn tip_width(bounds: Rectangle) -> i32 {
93        bounds.size.height as i32 - 1 - SHRINK
94    }
95
96    pub fn draw<D, C>(&self, color: C, target: &mut D) -> Result<(), D::Error>
97    where
98        C: PixelColor,
99        D: DrawTarget<Color = C>,
100    {
101        let style = PrimitiveStyle::with_fill(color);
102
103        self.body.into_styled(style).draw(target)?;
104        self.tip.into_styled(style).draw(target)?;
105
106        Ok(())
107    }
108}
109
110impl ContainsPoint for Arrow {
111    fn contains(&self, point: Point) -> bool {
112        self.body.contains(point) || self.tip.contains(point)
113    }
114}
115
116impl Transform for Arrow {
117    fn translate(&self, by: Point) -> Self {
118        Self {
119            body: self.body.translate(by),
120            tip: self.tip.translate(by),
121        }
122    }
123
124    fn translate_mut(&mut self, by: Point) -> &mut Self {
125        self.body.translate_mut(by);
126        self.tip.translate_mut(by);
127        self
128    }
129}