1use alloc::boxed::Box;
8use rlvgl_core::{
9 event::Event,
10 font::{FontMetrics, WidgetFont},
11 renderer::Renderer,
12 widget::{Color, Rect, Widget},
13};
14use rlvgl_widgets::dropdown::Dropdown;
15
16use crate::theme::{ColorScheme, ComponentSize, Theme, Variant};
17
18pub use rlvgl_widgets::dropdown::DropdownDir as SelectDirection;
19
20type ChangeHandler = dyn FnMut(usize, &str);
22
23pub struct Select {
25 inner: Dropdown,
26 on_change: Option<Box<ChangeHandler>>,
27}
28
29impl Select {
30 pub fn new(bounds: Rect) -> Self {
32 Self {
33 inner: Dropdown::new(bounds),
34 on_change: None,
35 }
36 }
37
38 pub fn with_options(mut self, options: &[impl AsRef<str>]) -> Self {
40 self.inner.set_options(options);
41 self
42 }
43
44 pub fn set_options(&mut self, options: &[impl AsRef<str>]) {
46 self.inner.set_options(options);
47 }
48
49 pub fn option_count(&self) -> usize {
51 self.inner.option_count()
52 }
53
54 pub fn selected(&self) -> usize {
56 self.inner.selected()
57 }
58
59 pub fn with_selected_index(mut self, index: usize) -> Self {
61 self.inner.set_selected(index);
62 self
63 }
64
65 pub fn set_selected(&mut self, index: usize) {
67 self.inner.set_selected(index);
68 }
69
70 pub fn selected_text(&self) -> &str {
72 self.inner.selected_text()
73 }
74
75 pub fn on_change<F: FnMut(usize, &str) + 'static>(mut self, handler: F) -> Self {
77 self.on_change = Some(Box::new(handler));
78 self
79 }
80
81 pub fn open(&mut self) {
83 self.inner.open();
84 }
85
86 pub fn close(&mut self) {
88 self.inner.close();
89 }
90
91 pub fn toggle(&mut self) {
93 self.inner.toggle();
94 }
95
96 pub fn is_open(&self) -> bool {
98 self.inner.is_open()
99 }
100
101 pub fn direction(mut self, direction: SelectDirection) -> Self {
103 self.inner.set_dir(direction);
104 self
105 }
106
107 pub fn direction_value(&self) -> SelectDirection {
109 self.inner.dir()
110 }
111
112 pub fn set_direction(&mut self, direction: SelectDirection) {
114 self.inner.set_dir(direction);
115 }
116
117 pub fn symbol(mut self, symbol: Option<&str>) -> Self {
119 self.inner.set_symbol(symbol);
120 self
121 }
122
123 pub fn symbol_value(&self) -> Option<&str> {
125 self.inner.symbol()
126 }
127
128 pub fn set_symbol(&mut self, symbol: Option<&str>) {
130 self.inner.set_symbol(symbol);
131 }
132
133 pub fn selected_highlight(mut self, enable: bool) -> Self {
135 self.inner.set_selected_highlight(enable);
136 self
137 }
138
139 pub fn selected_highlight_value(&self) -> bool {
141 self.inner.selected_highlight()
142 }
143
144 pub fn set_selected_highlight(&mut self, enable: bool) {
146 self.inner.set_selected_highlight(enable);
147 }
148
149 pub fn selected_color(mut self, color: Color) -> Self {
151 self.inner.selected_color = color;
152 self
153 }
154
155 pub fn set_selected_color(&mut self, color: Color) {
157 self.inner.selected_color = color;
158 }
159
160 pub fn text_color(mut self, color: Color) -> Self {
162 self.inner.text_color = color;
163 self
164 }
165
166 pub fn set_text_color(&mut self, color: Color) {
168 self.inner.text_color = color;
169 }
170
171 pub fn themed(
173 mut self,
174 theme: &Theme,
175 scheme: ColorScheme,
176 variant: Variant,
177 size: ComponentSize,
178 ) -> Self {
179 let resolved = theme.component_style(scheme, variant, size);
180 let colors = theme.scheme(scheme);
181 self.inner.style = resolved.style;
182 self.inner.item_style = resolved.style;
183 self.inner.item_style.bg_color = match variant {
184 Variant::Solid => colors.muted,
185 Variant::Subtle => colors.subtle,
186 Variant::Outline | Variant::Ghost => theme.tokens.colors.background,
187 };
188 self.inner.text_color = resolved.text_color;
189 self.inner.selected_color = resolved.accent_color;
190 self
191 }
192
193 pub fn set_font(&mut self, font: &'static dyn FontMetrics) {
195 self.inner.set_font(font);
196 }
197
198 pub fn style(&self) -> &rlvgl_core::style::Style {
200 &self.inner.style
201 }
202
203 pub fn style_mut(&mut self) -> &mut rlvgl_core::style::Style {
205 &mut self.inner.style
206 }
207
208 pub fn item_style(&self) -> &rlvgl_core::style::Style {
210 &self.inner.item_style
211 }
212
213 pub fn item_style_mut(&mut self) -> &mut rlvgl_core::style::Style {
215 &mut self.inner.item_style
216 }
217}
218
219impl Widget for Select {
220 fn bounds(&self) -> Rect {
221 self.inner.bounds()
222 }
223
224 fn widget_font_mut(&mut self) -> Option<&mut WidgetFont> {
225 self.inner.widget_font_mut()
226 }
227
228 fn set_bounds(&mut self, bounds: Rect) {
229 self.inner.set_bounds(bounds);
230 }
231
232 fn draw(&self, renderer: &mut dyn Renderer) {
233 self.inner.draw(renderer);
234 }
235
236 fn handle_event(&mut self, event: &Event) -> bool {
237 let before = self.inner.selected();
238 let handled = self.inner.handle_event(event);
239 let after = self.inner.selected();
240 if handled
241 && before != after
242 && let Some(cb) = self.on_change.as_mut()
243 {
244 cb(after, self.inner.selected_text());
245 }
246 handled
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253 use alloc::rc::Rc;
254 use core::cell::Cell;
255
256 #[test]
257 fn select_options_and_callback_work() {
258 let selected = Rc::new(Cell::new(0usize));
259 let flag = selected.clone();
260 let mut select = Select::new(rect(0, 0, 120, 80))
261 .with_options(&["A", "B"])
262 .on_change(move |idx, _| flag.set(idx));
263
264 select.open();
265 assert!(select.handle_event(&Event::PressRelease { x: 5, y: 38 }));
266
267 assert_eq!(selected.get(), 1);
268 assert_eq!(select.selected_text(), "B");
269 }
270
271 #[test]
272 fn select_themed_sets_trigger_and_selected_color() {
273 let theme = Theme::material_light();
274 let select = Select::new(rect(0, 0, 120, 80)).themed(
275 &theme,
276 ColorScheme::Primary,
277 Variant::Subtle,
278 ComponentSize::Md,
279 );
280
281 assert_eq!(
282 select.item_style().bg_color,
283 theme.scheme(ColorScheme::Primary).subtle
284 );
285 assert_eq!(select.style().radius, theme.tokens.radii.md);
286 }
287
288 fn rect(x: i32, y: i32, width: i32, height: i32) -> Rect {
289 Rect {
290 x,
291 y,
292 width,
293 height,
294 }
295 }
296}