embedded_menu/selection_indicator/
mod.rs1use crate::{
2 adapters::color_map::BinaryColorDrawTargetExt,
3 collection::MenuItemCollection,
4 interaction::{InputAdapterSource, InputState},
5 margin::Insets,
6 selection_indicator::style::IndicatorStyle,
7 theme::Theme,
8 MenuState, MenuStyle,
9};
10use embedded_graphics::{
11 prelude::{DrawTarget, DrawTargetExt, Point, Size},
12 primitives::Rectangle,
13 transform::Transform,
14};
15
16pub mod style;
17
18pub trait SelectionIndicatorController: Copy {
19 type State: Default + Copy;
20
21 fn update_target(&self, state: &mut Self::State, y: i32);
22 fn jump_to_target(&self, state: &mut Self::State);
23 fn offset(&self, state: &Self::State) -> i32;
24 fn update(&self, state: &mut Self::State);
25}
26
27#[derive(Clone, Copy, Default)]
28pub struct StaticState {
29 y_offset: i32,
30}
31
32#[derive(Clone, Copy)]
33pub struct StaticPosition;
34
35impl SelectionIndicatorController for StaticPosition {
36 type State = StaticState;
37
38 fn update_target(&self, state: &mut Self::State, y: i32) {
39 state.y_offset = y;
40 }
41
42 fn jump_to_target(&self, _state: &mut Self::State) {}
43
44 fn offset(&self, state: &Self::State) -> i32 {
45 state.y_offset
46 }
47
48 fn update(&self, _state: &mut Self::State) {}
49}
50
51#[derive(Clone, Copy)]
52pub struct AnimatedPosition {
53 frames: i32,
54}
55
56#[derive(Clone, Copy, Default)]
57pub struct AnimatedState {
58 current: i32,
59 target: i32,
60}
61
62impl AnimatedPosition {
63 pub const fn new(frames: i32) -> Self {
64 Self { frames }
65 }
66}
67
68impl SelectionIndicatorController for AnimatedPosition {
69 type State = AnimatedState;
70
71 fn update_target(&self, state: &mut Self::State, y: i32) {
72 state.target = y;
73 }
74
75 fn jump_to_target(&self, state: &mut Self::State) {
76 state.current = state.target;
77 }
78
79 fn offset(&self, state: &Self::State) -> i32 {
80 state.current
81 }
82
83 fn update(&self, state: &mut Self::State) {
84 let rounding = if state.current < state.target {
85 self.frames - 1
86 } else {
87 1 - self.frames
88 };
89
90 let distance = state.target - state.current;
91 state.current += (distance + rounding) / self.frames;
92 }
93}
94
95pub struct State<P, S>
96where
97 P: SelectionIndicatorController,
98 S: IndicatorStyle,
99{
100 position: P::State,
101 state: S::State,
102}
103
104impl<P, S> Default for State<P, S>
105where
106 P: SelectionIndicatorController,
107 S: IndicatorStyle,
108{
109 fn default() -> Self {
110 Self {
111 position: Default::default(),
112 state: Default::default(),
113 }
114 }
115}
116
117impl<P, S> Clone for State<P, S>
118where
119 P: SelectionIndicatorController,
120 S: IndicatorStyle,
121{
122 fn clone(&self) -> Self {
123 *self
124 }
125}
126
127impl<P, S> Copy for State<P, S>
128where
129 P: SelectionIndicatorController,
130 S: IndicatorStyle,
131{
132}
133
134#[derive(Clone, Copy, Debug)]
135pub(crate) struct Indicator<P, S> {
136 pub controller: P,
137 pub style: S,
138}
139
140impl<P, S> Indicator<P, S>
141where
142 P: SelectionIndicatorController,
143 S: IndicatorStyle,
144{
145 pub fn offset(&self, state: &State<P, S>) -> i32 {
146 self.controller.offset(&state.position)
147 }
148
149 pub fn change_selected_item(&self, pos: i32, state: &mut State<P, S>) {
150 self.controller.update_target(&mut state.position, pos);
151 self.style.on_target_changed(&mut state.state);
152 }
153
154 pub fn jump_to_target(&self, state: &mut State<P, S>) {
155 self.controller.jump_to_target(&mut state.position);
156 }
157
158 pub fn update(&self, input_state: InputState, state: &mut State<P, S>) {
159 self.controller.update(&mut state.position);
160 self.style.update(&mut state.state, input_state);
161 }
162
163 pub fn item_height(&self, menuitem_height: i32, state: &State<P, S>) -> i32 {
164 let indicator_insets = self.style.padding(&state.state, menuitem_height);
165 menuitem_height + indicator_insets.top + indicator_insets.bottom
166 }
167
168 pub fn draw<R, D, IT, C>(
169 &self,
170 selected_height: i32,
171 selected_offset: i32,
172 input_state: InputState,
173 mut display: D,
174 items: &impl MenuItemCollection<R>,
175 style: &MenuStyle<S, IT, P, R, C>,
176 menu_state: &MenuState<IT::InputAdapter, P, S>,
177 ) -> Result<(), D::Error>
178 where
179 D: DrawTarget<Color = C::Color>,
180 IT: InputAdapterSource<R>,
181 P: SelectionIndicatorController,
182 C: Theme,
183 S: IndicatorStyle,
184 {
185 let display_size = display.bounding_box().size;
186
187 let Insets {
190 left: padding_left,
191 top: padding_top,
192 right: padding_right,
193 bottom: padding_bottom,
194 } = self
195 .style
196 .padding(&menu_state.indicator_state.state, selected_height);
197
198 let selected_item_height = (selected_height + padding_top + padding_bottom) as u32;
200 let selected_item_area = Rectangle::new(
201 Point::new(0, selected_offset),
202 Size::new(display_size.width, selected_item_height),
203 );
204
205 let selection_area = self.style.draw(
206 &menu_state.indicator_state.state,
207 input_state,
208 &style.theme,
209 &mut display.cropped(&selected_item_area),
210 )?;
211
212 let mapping_area = selection_area.translate(selected_item_area.top_left);
214 let mut inverting = display.map_colors(
215 &mapping_area,
216 style.theme.text_color(),
217 style.theme.selected_text_color(),
218 );
219
220 let content_width = (display_size.width as i32 - padding_left - padding_right) as u32;
222 let content_area = Rectangle::new(
223 Point::new(padding_left, padding_top),
224 Size::new(content_width, display_size.height),
225 );
226
227 items.draw_styled(
228 &style.text_style(),
229 &mut inverting
230 .clipped(&content_area)
231 .translated(content_area.top_left - Point::new(0, menu_state.list_offset)),
232 )
233 }
234}