embedded_menu/items/
mod.rs

1pub mod menu_item;
2
3pub use menu_item::MenuItem;
4
5use embedded_graphics::{
6    draw_target::DrawTarget,
7    mono_font::MonoTextStyle,
8    pixelcolor::BinaryColor,
9    prelude::{Point, Size},
10    primitives::Rectangle,
11    text::{renderer::TextRenderer, Baseline},
12    Drawable,
13};
14use embedded_layout::prelude::*;
15use embedded_text::{alignment::HorizontalAlignment, style::TextBoxStyleBuilder, TextBox};
16
17/// Marker trait necessary to avoid a "conflicting implementations" error.
18pub trait Marker {}
19
20pub trait MenuListItem<R>: Marker + View {
21    /// Returns the value of the selected item, without interacting with it.
22    fn value_of(&self) -> R;
23
24    fn interact(&mut self) -> R;
25
26    fn set_style(&mut self, text_style: &MonoTextStyle<'_, BinaryColor>);
27
28    /// Returns whether the list item is selectable.
29    ///
30    /// If this returns false, the list item will not be interactable and user navigation will skip
31    /// over it.
32    fn selectable(&self) -> bool {
33        true
34    }
35
36    fn draw_styled<D>(
37        &self,
38        text_style: &MonoTextStyle<'static, BinaryColor>,
39        display: &mut D,
40    ) -> Result<(), D::Error>
41    where
42        D: DrawTarget<Color = BinaryColor>;
43}
44
45/// Helper struct to draw a menu line that has a title and some additional marker.
46pub struct MenuLine {
47    bounds: Rectangle,
48    value_width: u32,
49}
50
51impl MenuLine {
52    pub fn new(longest_value: &str, text_style: &MonoTextStyle<'_, BinaryColor>) -> Self {
53        let value_width = text_style
54            .measure_string(longest_value, Point::zero(), Baseline::Top)
55            .bounding_box
56            .size
57            .width;
58
59        MenuLine {
60            bounds: Rectangle::new(
61                Point::zero(),
62                Size::new(1, text_style.font.character_size.height - 1),
63            ),
64            value_width,
65        }
66    }
67
68    pub fn empty() -> Self {
69        MenuLine {
70            bounds: Rectangle::new(Point::zero(), Size::new(1, 0)),
71            value_width: 0,
72        }
73    }
74
75    pub fn draw_styled<D>(
76        &self,
77        title: &str,
78        value_text: &str,
79        text_style: &MonoTextStyle<'static, BinaryColor>, // TODO: allow non-mono fonts
80        display: &mut D,
81    ) -> Result<(), D::Error>
82    where
83        D: DrawTarget<Color = BinaryColor>,
84    {
85        let display_area = display.bounding_box();
86
87        if self.bounds.intersection(&display_area).size.height == 0 {
88            return Ok(());
89        }
90
91        let mut text_bounds = Rectangle::new(
92            self.bounds.top_left,
93            Size::new(display_area.size.width, self.bounds.size.height + 1),
94        );
95
96        TextBox::with_textbox_style(
97            value_text,
98            text_bounds,
99            *text_style,
100            TextBoxStyleBuilder::new()
101                .alignment(HorizontalAlignment::Right)
102                .build(),
103        )
104        .draw(display)?;
105
106        text_bounds.size.width -= self.value_width;
107        TextBox::new(title, text_bounds, *text_style).draw(display)?;
108
109        Ok(())
110    }
111}
112
113impl View for MenuLine {
114    fn translate_impl(&mut self, by: Point) {
115        self.bounds.translate_mut(by);
116    }
117
118    fn bounds(&self) -> Rectangle {
119        self.bounds
120    }
121}