1use iced_core::{
6 Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
7 keyboard::{self, key::Named},
8 layout::{Limits, Node},
9 mouse::{self, Cursor},
10 overlay, renderer, touch,
11 widget::{Operation, Tree},
12};
13
14pub use crate::core::{alignment::Alignment, offset::Offset};
15
16pub struct DropDown<'a, Message, Theme = iced_widget::Theme, Renderer = iced_widget::Renderer>
18where
19 Message: Clone,
20 Renderer: renderer::Renderer,
21{
22 underlay: Element<'a, Message, Theme, Renderer>,
23 overlay: Element<'a, Message, Theme, Renderer>,
24 on_dismiss: Option<Message>,
25 width: Option<Length>,
26 height: Length,
27 alignment: Alignment,
28 offset: Offset,
29 expanded: bool,
30}
31
32impl<'a, Message, Theme, Renderer> DropDown<'a, Message, Theme, Renderer>
33where
34 Message: Clone,
35 Renderer: renderer::Renderer,
36{
37 pub fn new<U, B>(underlay: U, overlay: B, expanded: bool) -> Self
39 where
40 U: Into<Element<'a, Message, Theme, Renderer>>,
41 B: Into<Element<'a, Message, Theme, Renderer>>,
42 {
43 DropDown {
44 underlay: underlay.into(),
45 overlay: overlay.into(),
46 expanded,
47 on_dismiss: None,
48 width: None,
49 height: Length::Shrink,
50 alignment: Alignment::Bottom,
51 offset: Offset::from(5.0),
52 }
53 }
54
55 #[must_use]
57 pub fn width(mut self, width: impl Into<Length>) -> Self {
58 self.width = Some(width.into());
59 self
60 }
61
62 #[must_use]
64 pub fn height(mut self, height: impl Into<Length>) -> Self {
65 self.height = height.into();
66 self
67 }
68
69 #[must_use]
71 pub fn alignment(mut self, alignment: impl Into<Alignment>) -> Self {
72 self.alignment = alignment.into();
73 self
74 }
75
76 #[must_use]
78 pub fn offset(mut self, offset: impl Into<Offset>) -> Self {
79 self.offset = offset.into();
80 self
81 }
82
83 #[must_use]
85 pub fn on_dismiss(mut self, message: Message) -> Self {
86 self.on_dismiss = Some(message);
87 self
88 }
89}
90
91impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
92 for DropDown<'a, Message, Theme, Renderer>
93where
94 Message: 'a + Clone,
95 Renderer: 'a + renderer::Renderer,
96{
97 fn size(&self) -> Size<Length> {
98 self.underlay.as_widget().size()
99 }
100
101 fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
102 self.underlay
103 .as_widget_mut()
104 .layout(&mut tree.children[0], renderer, limits)
105 }
106
107 fn draw(
108 &self,
109 state: &Tree,
110 renderer: &mut Renderer,
111 theme: &Theme,
112 style: &renderer::Style,
113 layout: Layout<'_>,
114 cursor: Cursor,
115 viewport: &Rectangle,
116 ) {
117 self.underlay.as_widget().draw(
118 &state.children[0],
119 renderer,
120 theme,
121 style,
122 layout,
123 cursor,
124 viewport,
125 );
126 }
127
128 fn children(&self) -> Vec<Tree> {
129 vec![Tree::new(&self.underlay), Tree::new(&self.overlay)]
130 }
131
132 fn diff(&self, tree: &mut Tree) {
133 tree.diff_children(&[&self.underlay, &self.overlay]);
134 }
135
136 fn operate<'b>(
137 &'b mut self,
138 state: &'b mut Tree,
139 layout: Layout<'_>,
140 renderer: &Renderer,
141 operation: &mut dyn Operation<()>,
142 ) {
143 self.underlay
144 .as_widget_mut()
145 .operate(&mut state.children[0], layout, renderer, operation);
146 }
147
148 fn update(
149 &mut self,
150 state: &mut Tree,
151 event: &Event,
152 layout: Layout<'_>,
153 cursor: Cursor,
154 renderer: &Renderer,
155 clipboard: &mut dyn Clipboard,
156 shell: &mut Shell<'_, Message>,
157 viewport: &Rectangle,
158 ) {
159 self.underlay.as_widget_mut().update(
160 &mut state.children[0],
161 event,
162 layout,
163 cursor,
164 renderer,
165 clipboard,
166 shell,
167 viewport,
168 );
169 }
170
171 fn mouse_interaction(
172 &self,
173 state: &Tree,
174 layout: Layout<'_>,
175 cursor: Cursor,
176 viewport: &Rectangle,
177 renderer: &Renderer,
178 ) -> mouse::Interaction {
179 self.underlay.as_widget().mouse_interaction(
180 &state.children[0],
181 layout,
182 cursor,
183 viewport,
184 renderer,
185 )
186 }
187
188 fn overlay<'b>(
189 &'b mut self,
190 state: &'b mut Tree,
191 layout: Layout<'b>,
192 renderer: &Renderer,
193 viewport: &Rectangle,
194 translation: Vector,
195 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
196 if !self.expanded {
197 return self.underlay.as_widget_mut().overlay(
198 &mut state.children[0],
199 layout,
200 renderer,
201 viewport,
202 translation,
203 );
204 }
205
206 Some(overlay::Element::new(Box::new(DropDownOverlay::new(
207 &mut state.children[1],
208 &mut self.overlay,
209 self.on_dismiss.as_ref(),
210 self.width.as_ref(),
211 &self.height,
212 &self.alignment,
213 &self.offset,
214 layout.bounds(),
215 layout.position() + translation,
216 *viewport,
217 ))))
218 }
219}
220
221impl<'a, Message, Theme: 'a, Renderer> From<DropDown<'a, Message, Theme, Renderer>>
222 for Element<'a, Message, Theme, Renderer>
223where
224 Message: 'a + Clone,
225 Renderer: 'a + renderer::Renderer,
226{
227 fn from(drop_down: DropDown<'a, Message, Theme, Renderer>) -> Self {
228 Element::new(drop_down)
229 }
230}
231
232struct DropDownOverlay<
233 'a,
234 'b,
235 Message,
236 Theme = iced_widget::Theme,
237 Renderer = iced_widget::Renderer,
238> where
239 Message: Clone,
240{
241 state: &'b mut Tree,
242 element: &'b mut Element<'a, Message, Theme, Renderer>,
243 on_dismiss: Option<&'b Message>,
244 width: Option<&'b Length>,
245 height: &'b Length,
246 alignment: &'b Alignment,
247 offset: &'b Offset,
248 underlay_bounds: Rectangle,
249 position: Point,
250 viewport: Rectangle,
251}
252
253impl<'a, 'b, Message, Theme, Renderer> DropDownOverlay<'a, 'b, Message, Theme, Renderer>
254where
255 Message: Clone,
256 Renderer: renderer::Renderer,
257{
258 #[allow(clippy::too_many_arguments)]
259 fn new(
260 state: &'b mut Tree,
261 element: &'b mut Element<'a, Message, Theme, Renderer>,
262 on_dismiss: Option<&'b Message>,
263 width: Option<&'b Length>,
264 height: &'b Length,
265 alignment: &'b Alignment,
266 offset: &'b Offset,
267 underlay_bounds: Rectangle,
268 position: Point,
269 viewport: Rectangle,
270 ) -> Self {
271 DropDownOverlay {
272 state,
273 element,
274 on_dismiss,
275 width,
276 height,
277 alignment,
278 offset,
279 underlay_bounds,
280 position,
281 viewport,
282 }
283 }
284}
285
286impl<Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer>
287 for DropDownOverlay<'_, '_, Message, Theme, Renderer>
288where
289 Message: Clone,
290 Renderer: renderer::Renderer,
291{
292 fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
293 let limits = Limits::new(Size::ZERO, bounds)
294 .width(
295 *self
296 .width
297 .unwrap_or(&Length::Fixed(self.underlay_bounds.width)),
298 )
299 .height(*self.height);
300
301 let previous_position = self.position;
302 let max = limits.max();
303
304 let height_above = (previous_position.y - self.offset.y).max(0.0);
305 let height_below =
306 (max.height - previous_position.y - self.underlay_bounds.height - self.offset.y)
307 .max(0.0);
308
309 let ref_center_y = previous_position.y + self.underlay_bounds.height / 2.0;
310 let max_height_symmetric = (ref_center_y.min(max.height - ref_center_y) * 2.0).max(0.0);
311
312 let limits = match self.alignment {
313 Alignment::Top => limits.max_height(height_above),
314 Alignment::TopStart | Alignment::TopEnd => {
315 limits.max_height((height_above + self.underlay_bounds.height).max(0.0))
316 }
317 Alignment::Bottom => limits.max_height(height_below),
318 Alignment::BottomEnd | Alignment::BottomStart => {
319 limits.max_height((height_below + self.underlay_bounds.height).max(0.0))
320 }
321 Alignment::Start | Alignment::End => limits.max_height(max_height_symmetric),
322 };
323
324 let mut node = self
325 .element
326 .as_widget_mut()
327 .layout(self.state, renderer, &limits);
328
329 let mut new_position = match self.alignment {
330 Alignment::TopStart => Point::new(
331 previous_position.x - node.bounds().width - self.offset.x,
332 previous_position.y - node.bounds().height + self.underlay_bounds.height
333 - self.offset.y,
334 ),
335 Alignment::Top => Point::new(
336 previous_position.x + self.underlay_bounds.width / 2.0 - node.bounds().width / 2.0,
337 previous_position.y - node.bounds().height - self.offset.y,
338 ),
339 Alignment::TopEnd => Point::new(
340 previous_position.x + self.underlay_bounds.width + self.offset.x,
341 previous_position.y - node.bounds().height + self.underlay_bounds.height
342 - self.offset.y,
343 ),
344 Alignment::End => Point::new(
345 previous_position.x + self.underlay_bounds.width + self.offset.x,
346 previous_position.y + self.underlay_bounds.height / 2.0
347 - node.bounds().height / 2.0,
348 ),
349 Alignment::BottomEnd => Point::new(
350 previous_position.x + self.underlay_bounds.width + self.offset.x,
351 previous_position.y + self.offset.y,
352 ),
353 Alignment::Bottom => Point::new(
354 previous_position.x + self.underlay_bounds.width / 2.0 - node.bounds().width / 2.0,
355 previous_position.y + self.underlay_bounds.height + self.offset.y,
356 ),
357 Alignment::BottomStart => Point::new(
358 previous_position.x - node.bounds().width - self.offset.x,
359 previous_position.y + self.offset.y,
360 ),
361 Alignment::Start => Point::new(
362 previous_position.x - node.bounds().width - self.offset.x,
363 previous_position.y + self.underlay_bounds.height / 2.0
364 - node.bounds().height / 2.0,
365 ),
366 };
367
368 if new_position.x + node.bounds().width > max.width {
369 new_position.x = max.width - node.bounds().width;
370 }
371 if new_position.x < 0.0 {
372 new_position.x = 0.0;
373 }
374
375 if new_position.y + node.bounds().height > max.height {
376 new_position.y = max.height - node.bounds().height;
377 }
378 if new_position.y < 0.0 {
379 new_position.y = 0.0;
380 }
381
382 node.move_to_mut(new_position);
383 node
384 }
385
386 fn draw(
387 &self,
388 renderer: &mut Renderer,
389 theme: &Theme,
390 style: &renderer::Style,
391 layout: Layout<'_>,
392 cursor: Cursor,
393 ) {
394 let bounds = layout.bounds();
395 self.element
396 .as_widget()
397 .draw(self.state, renderer, theme, style, layout, cursor, &bounds);
398 }
399
400 fn update(
401 &mut self,
402 event: &Event,
403 layout: Layout<'_>,
404 cursor: Cursor,
405 renderer: &Renderer,
406 clipboard: &mut dyn Clipboard,
407 shell: &mut Shell<Message>,
408 ) {
409 self.underlay_bounds = Rectangle {
410 x: self.position.x,
411 y: self.position.y,
412 width: self.underlay_bounds.width,
413 height: self.underlay_bounds.height,
414 };
415
416 if let Some(on_dismiss) = self.on_dismiss {
417 match &event {
418 Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
419 if key == &keyboard::Key::Named(Named::Escape) {
420 shell.publish(on_dismiss.clone());
421 }
422 }
423
424 Event::Mouse(mouse::Event::ButtonPressed(
425 mouse::Button::Left | mouse::Button::Right,
426 ))
427 | Event::Touch(touch::Event::FingerPressed { .. }) => {
428 if !cursor.is_over(layout.bounds()) && !cursor.is_over(self.underlay_bounds) {
429 shell.publish(on_dismiss.clone());
430 }
431 }
432
433 _ => {}
434 }
435 }
436
437 self.element.as_widget_mut().update(
438 self.state,
439 event,
440 layout,
441 cursor,
442 renderer,
443 clipboard,
444 shell,
445 &layout.bounds(),
446 );
447 }
448
449 fn mouse_interaction(
450 &self,
451 layout: Layout<'_>,
452 cursor: Cursor,
453 renderer: &Renderer,
454 ) -> mouse::Interaction {
455 self.element.as_widget().mouse_interaction(
456 self.state,
457 layout,
458 cursor,
459 &self.viewport,
460 renderer,
461 )
462 }
463}