1use smallvec::smallvec;
2
3use crate::draw::Primitive;
4use crate::event::{Event, Key};
5use crate::layout::{Rectangle, Size};
6use crate::node::{GenericNode, IntoNode, Node};
7use crate::style::{StyleState, Stylesheet};
8use crate::widget::{Context, StateVec, Widget};
9
10pub struct Dropdown<'a, T, F> {
12 items: Vec<Node<'a, T>>,
13 default_selection: Option<usize>,
14 on_select: F,
15}
16
17pub struct State {
19 selected_item: Option<usize>,
20 hovered: bool,
21 inner: InnerState,
22}
23
24enum InnerState {
25 Idle,
26 Open { scroll: f32, hover_item: usize },
27 Pressed { scroll: f32, hover_item: usize },
28}
29
30impl<'a, T: 'a, F> Dropdown<'a, T, F> {
31 pub fn default_selection(mut self, item_index: usize) -> Self {
33 self.default_selection = Some(item_index);
34 self
35 }
36
37 pub fn on_select<N: Fn(usize) -> T>(self, on_select: N) -> Dropdown<'a, T, N> {
39 Dropdown {
40 items: self.items,
41 default_selection: self.default_selection,
42 on_select,
43 }
44 }
45
46 pub fn push(mut self, item: impl IntoNode<'a, T>) -> Self {
48 self.items.push(item.into_node());
49 self
50 }
51
52 pub fn extend(mut self, items: impl IntoIterator<Item = impl IntoNode<'a, T>>) -> Self {
54 self.items.extend(items.into_iter().map(IntoNode::into_node));
55 self
56 }
57}
58
59impl<'a, T: 'a> Default for Dropdown<'a, T, fn(usize) -> T> {
60 fn default() -> Self {
61 Self {
62 items: Vec::new(),
63 default_selection: None,
64 on_select: |_| panic!("on_select of `Dropdown` must be set"),
65 }
66 }
67}
68
69impl<'a, T: Send + 'a, F: Send + Fn(usize) -> T> Widget<'a, T> for Dropdown<'a, T, F> {
70 type State = State;
71
72 fn mount(&self) -> Self::State {
73 State {
74 selected_item: self.default_selection.map(|i| i.min(self.items.len() - 1)),
75 ..Default::default()
76 }
77 }
78
79 fn widget(&self) -> &'static str {
80 "dropdown"
81 }
82
83 fn state(&self, state: &State) -> StateVec {
84 match state.inner {
85 InnerState::Open { .. } | InnerState::Pressed { .. } => smallvec![StyleState::Open],
86 InnerState::Idle if state.hovered => smallvec![StyleState::Hover],
87 InnerState::Idle => StateVec::new(),
88 }
89 }
90
91 fn len(&self) -> usize {
92 self.items.len()
93 }
94
95 fn visit_children(&mut self, visitor: &mut dyn FnMut(&mut dyn GenericNode<'a, T>)) {
96 for item in self.items.iter_mut() {
97 visitor(&mut **item);
98 }
99 }
100
101 fn size(&self, _: &State, style: &Stylesheet) -> (Size, Size) {
102 let width = match style.width {
103 Size::Shrink => Size::Exact(self.items.iter().fold(0.0f32, |size, item| match item.size().0 {
104 Size::Exact(item_size) => size.max(item_size),
105 _ => size,
106 })),
107 other => other,
108 };
109 let height = match style.height {
110 Size::Shrink => Size::Exact(self.items.iter().fold(0.0f32, |size, item| match item.size().1 {
111 Size::Exact(item_size) => size.max(item_size),
112 _ => size,
113 })),
114 other => other,
115 };
116
117 style
118 .background
119 .resolve_size((style.width, style.height), (width, height), style.padding)
120 }
121
122 fn hit(&self, state: &State, layout: Rectangle, clip: Rectangle, _: &Stylesheet, x: f32, y: f32) -> bool {
123 self.focused(state) || (layout.point_inside(x, y) && clip.point_inside(x, y))
124 }
125
126 fn focused(&self, state: &State) -> bool {
127 matches!(state.inner, InnerState::Open { .. } | InnerState::Pressed { .. })
128 }
129
130 fn event(
131 &mut self,
132 state: &mut State,
133 layout: Rectangle,
134 clip: Rectangle,
135 _: &Stylesheet,
136 event: Event,
137 context: &mut Context<T>,
138 ) {
139 state.inner = match (event, std::mem::replace(&mut state.inner, InnerState::Idle)) {
140 (Event::Cursor(x, y), InnerState::Idle) => {
141 let hovered = layout.point_inside(x, y) && clip.point_inside(x, y);
142 if hovered != state.hovered {
143 context.redraw();
144 state.hovered = hovered;
145 }
146 InnerState::Idle
147 }
148
149 (Event::Cursor(x, y), InnerState::Open { scroll, hover_item }) => {
150 let hovered = x >= layout.left
151 && x < layout.right
152 && y >= layout.bottom
153 && y < layout.bottom + self.items.len() as f32 * layout.height();
154 if hovered != state.hovered {
155 context.redraw();
156 state.hovered = hovered;
157 }
158
159 let new_hover_item =
160 (((y - layout.bottom) / layout.height()).floor().max(0.0) as usize).min(self.items.len() - 1);
161
162 if new_hover_item != hover_item {
163 context.redraw();
164 InnerState::Open {
165 scroll,
166 hover_item: new_hover_item,
167 }
168 } else {
169 InnerState::Open { scroll, hover_item }
170 }
171 }
172
173 (Event::Cursor(x, y), InnerState::Pressed { scroll, hover_item }) => {
174 let hovered = x >= layout.left
175 && x < layout.right
176 && y >= layout.bottom
177 && y < layout.bottom + self.items.len() as f32 * layout.height();
178 if hovered != state.hovered {
179 context.redraw();
180 state.hovered = hovered;
181 }
182
183 let new_hover_item =
184 (((y - layout.bottom) / layout.height()).floor().max(0.0) as usize).min(self.items.len() - 1);
185
186 if new_hover_item != hover_item || !state.hovered {
187 context.redraw();
188 InnerState::Open {
189 scroll,
190 hover_item: new_hover_item,
191 }
192 } else {
193 InnerState::Pressed { scroll, hover_item }
194 }
195 }
196
197 (Event::Press(Key::LeftMouseButton), InnerState::Idle) => {
198 if state.hovered {
199 context.redraw();
200 InnerState::Open {
201 scroll: 0.0,
202 hover_item: 0,
203 }
204 } else {
205 InnerState::Idle
206 }
207 }
208
209 (Event::Press(Key::LeftMouseButton), InnerState::Open { scroll, hover_item }) => {
210 context.redraw();
211 if state.hovered {
212 InnerState::Pressed { scroll, hover_item }
213 } else {
214 InnerState::Idle
215 }
216 }
217
218 (Event::Release(Key::LeftMouseButton), InnerState::Pressed { hover_item, .. }) => {
219 context.redraw();
220 state.selected_item.replace(hover_item);
221 context.push((self.on_select)(hover_item));
222 InnerState::Idle
223 }
224
225 (_, state) => state,
226 };
227 }
228
229 fn draw(
230 &mut self,
231 state: &mut State,
232 layout: Rectangle,
233 clip: Rectangle,
234 style: &Stylesheet,
235 ) -> Vec<Primitive<'a>> {
236 let content = style.background.content_rect(layout, style.padding);
237 let focused = self.focused(state);
238
239 let mut result = Vec::new();
240 if focused {
241 result.push(Primitive::LayerUp);
242 }
243 match state.inner {
244 InnerState::Idle => {
245 result.extend(style.background.render(layout));
246 if let Some(selected) = state.selected_item {
247 result.extend(self.items[selected].draw(content, clip));
248 }
249 }
250 InnerState::Open { hover_item, .. } | InnerState::Pressed { hover_item, .. } => {
251 let padding = style.background.padding();
252 let expanded = Rectangle {
253 left: layout.left,
254 top: layout.top,
255 right: layout.right,
256 bottom: layout.bottom + self.items.len() as f32 * layout.height() + padding.top + padding.bottom,
257 };
258 result.extend(style.background.render(expanded));
259 for (index, item) in self.items.iter_mut().enumerate() {
260 if index == hover_item {
261 result.push(Primitive::DrawRect(
262 Rectangle {
263 left: layout.left + padding.left,
264 top: layout.top + (1 + index) as f32 * layout.height() + padding.top,
265 right: layout.right - padding.right,
266 bottom: layout.bottom + (1 + index) as f32 * layout.height() + padding.top,
267 },
268 style.color,
269 ));
270 }
271
272 let layout = Rectangle {
273 left: content.left + padding.left,
274 top: content.top + (1 + index) as f32 * layout.height() + padding.top,
275 right: content.right - padding.right,
276 bottom: content.bottom + (1 + index) as f32 * layout.height(),
277 };
278 result.extend(item.draw(layout, clip));
279 }
280 }
281 }
282 if focused {
283 result.push(Primitive::LayerDown);
284 }
285 result
286 }
287}
288
289impl<'a, T: 'a + Send, F: 'a + Send + Fn(usize) -> T> IntoNode<'a, T> for Dropdown<'a, T, F> {
290 fn into_node(self) -> Node<'a, T> {
291 Node::from_widget(self)
292 }
293}
294
295impl Default for State {
296 fn default() -> Self {
297 State {
298 selected_item: None,
299 hovered: false,
300 inner: InnerState::Idle,
301 }
302 }
303}