1use alloc::vec::Vec;
2use embedded_graphics::{
3 geometry::Point,
4 primitives::{PrimitiveStyle, Rectangle, StyledDrawable},
5};
6
7use crate::{
8 block::{Block, Border, BorderRadius},
9 color::UiColor,
10 el::{El, ElId},
11 event::{Capture, CommonEvent, Event, Propagate},
12 icons::IconKind,
13 layout::{Layout, LayoutNode, Limits, Viewport},
14 padding::Padding,
15 render::Renderer,
16 size::{Length, Size},
17 state::{State, StateNode, StateTag},
18 style::component_style,
19 ui::UiCtx,
20 widget::Widget,
21};
22
23use super::icon::Icon;
24
25pub struct SelectState {
26 is_pressed: bool,
27 is_active: bool,
28}
29
30impl Default for SelectState {
31 fn default() -> Self {
32 Self { is_pressed: false, is_active: false }
33 }
34}
35
36#[derive(Clone, Copy)]
37pub enum SelectStatus {
38 Normal,
39 Active,
40 Pressed,
41 Focused,
42}
43
44component_style! {
47 pub SelectStyle: SelectStyler(SelectStatus) {
48 background: background,
49 border: border,
50 }
51}
52
53pub struct Select<'a, Message, R, E, S>
54where
55 R: Renderer,
56 E: Event,
57 S: SelectStyler<R::Color>,
58{
59 id: ElId,
60 size: Size<Length>,
61 icon_left: IconKind,
62 icon_right: IconKind,
63 options: Vec<El<'a, Message, R, E, S>>,
64 chosen: usize,
65 class: S::Class<'a>,
66 cycle: bool,
67}
68
69impl<'a, Message, R, E, S> Select<'a, Message, R, E, S>
70where
71 R: Renderer,
72 E: Event,
73 S: SelectStyler<R::Color>,
74{
75 pub fn new(options: Vec<El<'a, Message, R, E, S>>) -> Self {
76 Self {
77 id: ElId::unique(),
78 size: Size::fill(),
79 icon_left: IconKind::ArrowLeft,
80 icon_right: IconKind::ArrowRight,
81 options,
82 chosen: 0,
83 class: S::default(),
84 cycle: false,
85 }
86 }
87
88 pub fn initial(mut self, index: impl Into<usize>) -> Self {
89 self.chosen = index.into();
90 self
91 }
92
93 pub fn width(mut self, width: impl Into<Length>) -> Self {
94 self.size.width = width.into();
95 self
96 }
97
98 pub fn height(mut self, height: impl Into<Length>) -> Self {
99 self.size.height = height.into();
100 self
101 }
102
103 pub fn cycle(mut self, cycle: bool) -> Self {
104 self.cycle = cycle;
105 self
106 }
107
108 pub fn icon_left(mut self, icon_left: IconKind) -> Self {
109 self.icon_left = icon_left;
110 self
111 }
112
113 pub fn icon_right(mut self, icon_right: IconKind) -> Self {
114 self.icon_right = icon_right;
115 self
116 }
117
118 fn current(&self) -> &El<'a, Message, R, E, S> {
120 &self.options[self.chosen]
121 }
122
123 fn arrow_icon_size(&self, limits: &Limits) -> u32 {
124 5
127 }
128
129 fn status(&self, ctx: &UiCtx<Message>, state: &StateNode) -> SelectStatus {
130 match state.get::<SelectState>() {
131 SelectState { is_active: true, .. } => return SelectStatus::Active,
132 SelectState { is_pressed: true, .. } => return SelectStatus::Pressed,
133 SelectState { is_pressed: false, is_active: false } => {},
134 }
135
136 if ctx.is_focused(self) {
137 return SelectStatus::Focused;
138 }
139
140 SelectStatus::Normal
141 }
142}
143
144impl<'a, Message, R, E, S> Widget<Message, R, E, S> for Select<'a, Message, R, E, S>
145where
146 R: Renderer,
147 E: Event,
148 S: SelectStyler<R::Color>,
149{
150 fn id(&self) -> Option<crate::el::ElId> {
151 Some(self.id)
152 }
153
154 fn tree_ids(&self) -> Vec<crate::el::ElId> {
155 vec![self.id]
156 }
159
160 fn size(&self) -> Size<Length> {
161 self.size
162 }
163
164 fn state_tag(&self) -> crate::state::StateTag {
165 StateTag::of::<SelectState>()
166 }
167
168 fn state(&self) -> State {
169 State::new(SelectState::default())
170 }
171
172 fn state_children(&self) -> Vec<StateNode> {
173 vec![StateNode::new(self.current())]
175 }
176
177 fn on_event(
178 &mut self,
179 ctx: &mut crate::ui::UiCtx<Message>,
180 event: E,
181 state: &mut StateNode,
182 ) -> crate::event::EventResponse<E> {
183 let focused = ctx.is_focused(self);
186 let current_state = state.get::<SelectState>();
187
188 if let Some(offset) = event.as_select_shift() {
189 if focused && current_state.is_active {
190 if self.cycle {
191 let len = self.options.len() as i32;
192 self.chosen = ((self.chosen as i32 + offset % len + len) % len) as usize;
193 } else {
194 self.chosen = (self.chosen as i32 + offset)
195 .clamp(0, self.options.len() as i32 - 1)
196 as usize;
197 }
198 return Capture::Captured.into();
199 }
200 }
201
202 if let Some(common) = event.as_common() {
203 match common {
204 CommonEvent::FocusMove(_) if focused => {
205 return Propagate::BubbleUp(self.id, event).into()
206 },
207 CommonEvent::FocusClickDown if focused => {
208 state.get_mut::<SelectState>().is_pressed = true;
209 return Capture::Captured.into();
210 },
211 CommonEvent::FocusClickUp if focused => {
212 let was_pressed = current_state.is_pressed;
213
214 state.get_mut::<SelectState>().is_pressed = false;
215
216 if was_pressed {
217 state.get_mut::<SelectState>().is_active =
218 !state.get::<SelectState>().is_active;
219 return Capture::Captured.into();
220 }
221 },
222 CommonEvent::FocusClickDown
223 | CommonEvent::FocusClickUp
224 | CommonEvent::FocusMove(_) => {
225 state.state.reset::<SelectState>();
227 },
228 }
229 }
230
231 Propagate::Ignored.into()
232 }
233
234 fn layout(
235 &self,
236 ctx: &mut crate::ui::UiCtx<Message>,
237 state: &mut crate::state::StateNode,
238 styler: &S,
239 limits: &crate::layout::Limits,
240 viewport: &Viewport,
241 ) -> crate::layout::LayoutNode {
242 let style = styler.style(&self.class, self.status(ctx, state));
251
252 let padding_for_icons = self.arrow_icon_size(limits);
254
255 Layout::container(
256 limits,
257 self.size,
258 crate::layout::Position::Relative,
259 viewport,
260 Padding::new_axis(0, padding_for_icons),
261 style.border.width,
262 crate::align::Alignment::Center,
263 crate::align::Alignment::Center,
264 |limits| {
265 self.options[self.chosen].layout(
266 ctx,
267 &mut state.children[0],
268 styler,
269 limits,
270 viewport,
271 )
272 },
273 )
274 }
275
276 fn draw(
277 &self,
278 ctx: &mut crate::ui::UiCtx<Message>,
279 state: &mut crate::state::StateNode,
280 renderer: &mut R,
281 styler: &S,
282 layout: crate::layout::Layout,
283 ) {
284 let bounds = layout.bounds();
285 let icons_limits = Limits::new(Size::zero(), Size::new_equal(bounds.size.height));
286 let icons_node = LayoutNode::new(self.arrow_icon_size(&icons_limits).into());
287 let icons_vertical_center =
288 bounds.size.height as i32 / 2 - icons_node.size().height as i32 / 2;
289
290 let style = styler.style(&self.class, self.status(ctx, state));
291
292 renderer.block(Block {
293 border: style.border,
294 rect: bounds.into(),
295 background: style.background,
296 });
297
298 if self.cycle || self.chosen != 0 {
299 Widget::<Message, R, E, S>::draw(
300 &Icon::new(self.icon_left),
301 ctx,
302 &mut StateNode::stateless(),
303 renderer,
304 styler,
305 Layout::with_offset(
306 bounds.position + Point::new(style.border.width as i32, icons_vertical_center),
307 &icons_node,
308 ),
309 );
310 }
311
312 self.current().draw(
313 ctx,
314 &mut state.children[0],
315 renderer,
316 styler,
317 layout.children().next().unwrap(),
318 );
319
320 if self.cycle || self.chosen != self.options.len() - 1 {
321 Widget::<Message, R, E, S>::draw(
322 &Icon::new(self.icon_right),
323 ctx,
324 &mut StateNode::stateless(),
325 renderer,
326 styler,
327 Layout::with_offset(
328 bounds.position
329 + Point::new(
330 bounds.size.width as i32
331 - icons_node.size().width as i32
332 - style.border.width as i32,
333 icons_vertical_center,
334 ),
335 &icons_node,
336 ),
337 );
338 }
339 }
340}
341
342impl<'a, Message, R, E, S> From<Select<'a, Message, R, E, S>> for El<'a, Message, R, E, S>
343where
344 Message: Clone + 'a,
345 R: Renderer + 'a,
346 E: Event + 'a,
347 S: 'a,
348 S: SelectStyler<R::Color> + 'a,
349{
350 fn from(value: Select<'a, Message, R, E, S>) -> Self {
351 El::new(value)
352 }
353}