1#![warn(missing_docs)]
4
5use egui::{
6 text::{CCursor, CCursorRange},
7 Id, Response, ScrollArea, TextEdit, Ui, Widget, WidgetText,
8};
9use std::hash::Hash;
10
11pub struct DropDownBox<
13 'a,
14 F: FnMut(&mut Ui, &str) -> Response,
15 V: AsRef<str>,
16 I: Iterator<Item = V>,
17> {
18 buf: &'a mut String,
19 popup_id: Id,
20 display: F,
21 it: I,
22 hint_text: WidgetText,
23 filter_by_input: bool,
24 select_on_focus: bool,
25 desired_width: Option<f32>,
26 max_height: Option<f32>,
27}
28
29impl<'a, F: FnMut(&mut Ui, &str) -> Response, V: AsRef<str>, I: Iterator<Item = V>>
30 DropDownBox<'a, F, V, I>
31{
32 pub fn from_iter(
34 it: impl IntoIterator<IntoIter = I>,
35 id_source: impl Hash,
36 buf: &'a mut String,
37 display: F,
38 ) -> Self {
39 Self {
40 popup_id: Id::new(id_source),
41 it: it.into_iter(),
42 display,
43 buf,
44 hint_text: WidgetText::default(),
45 filter_by_input: true,
46 select_on_focus: false,
47 desired_width: None,
48 max_height: None,
49 }
50 }
51
52 pub fn hint_text(mut self, hint_text: impl Into<WidgetText>) -> Self {
54 self.hint_text = hint_text.into();
55 self
56 }
57
58 pub fn filter_by_input(mut self, filter_by_input: bool) -> Self {
60 self.filter_by_input = filter_by_input;
61 self
62 }
63
64 pub fn select_on_focus(mut self, select_on_focus: bool) -> Self {
66 self.select_on_focus = select_on_focus;
67 self
68 }
69
70 pub fn desired_width(mut self, desired_width: f32) -> Self {
72 self.desired_width = desired_width.into();
73 self
74 }
75
76 pub fn max_height(mut self, height: f32) -> Self {
78 self.max_height = height.into();
79 self
80 }
81}
82
83impl<'a, F: FnMut(&mut Ui, &str) -> Response, V: AsRef<str>, I: Iterator<Item = V>> Widget
84 for DropDownBox<'a, F, V, I>
85{
86 fn ui(self, ui: &mut Ui) -> Response {
87 let Self {
88 popup_id,
89 buf,
90 it,
91 mut display,
92 hint_text,
93 filter_by_input,
94 select_on_focus,
95 desired_width,
96 max_height,
97 } = self;
98
99 let mut edit = TextEdit::singleline(buf).hint_text(hint_text);
100 if let Some(dw) = desired_width {
101 edit = edit.desired_width(dw);
102 }
103 let mut edit_output = edit.show(ui);
104 let mut r = edit_output.response;
105 if r.gained_focus() {
106 if select_on_focus {
107 edit_output
108 .state
109 .cursor
110 .set_char_range(Some(CCursorRange::two(
111 CCursor::new(0),
112 CCursor::new(buf.len()),
113 )));
114 edit_output.state.store(ui.ctx(), r.id);
115 }
116 ui.memory_mut(|m| m.open_popup(popup_id));
117 }
118
119 let mut changed = false;
120 egui::popup_below_widget(
121 ui,
122 popup_id,
123 &r,
124 egui::PopupCloseBehavior::CloseOnClick,
125 |ui| {
126 if let Some(max) = max_height {
127 ui.set_max_height(max);
128 }
129
130 ScrollArea::vertical()
131 .max_height(f32::INFINITY)
132 .show(ui, |ui| {
133 for var in it {
134 let text = var.as_ref();
135 if filter_by_input
136 && !buf.is_empty()
137 && !text.to_lowercase().contains(&buf.to_lowercase())
138 {
139 continue;
140 }
141
142 if display(ui, text).clicked() {
143 *buf = text.to_owned();
144 changed = true;
145
146 ui.memory_mut(|m| m.close_popup());
147 }
148 }
149 });
150 },
151 );
152
153 if changed {
154 r.mark_changed();
155 }
156
157 r
158 }
159}