1use eframe::egui::{Align, Button, Id, Layout, Response, Ui, Vec2, Widget};
8use std::hash::Hash;
9
10pub struct MultiSelect<'a, F: FnMut(&mut Ui, &str) -> Response> {
13 popup_id: Id,
14 items: &'a mut Vec<String>,
15 answers: &'a mut Vec<String>,
16 options: &'a Vec<String>,
17 display: F,
18 max_opt: &'a u8,
19 toasted: &'a mut bool,
20}
21
22impl<'a, F: FnMut(&mut Ui, &str) -> Response> MultiSelect<'a, F> {
23 pub fn new(
25 id_source: impl Hash,
26 items: &'a mut Vec<String>,
27 answers: &'a mut Vec<String>,
28 options: &'a Vec<String>,
29 display: F,
30 max_opt: &'a u8,
31 toasted: &'a mut bool,
32 ) -> Self {
33 Self {
34 popup_id: Id::new(id_source),
35 items,
36 answers,
37 options,
38 display,
39 max_opt,
40 toasted,
41 }
42 }
43}
44
45impl<'a, F: FnMut(&mut Ui, &str) -> Response> Widget for MultiSelect<'a, F> {
46 fn ui(self, ui: &mut Ui) -> Response {
47 let Self {
48 popup_id,
49 items,
50 answers,
51 options,
52 mut display,
53 max_opt,
54 toasted,
55 } = self;
56
57 if items.is_empty() && answers.is_empty() {
58 for item in options.clone() {
59 items.push(item)
60 }
61 }
62 let mut r = if answers.is_empty() {
63 ui.add(
64 Button::new(format!("Choose max {} options", max_opt))
65 .min_size(Vec2 { x: 200.0, y: 22.0 }),
66 )
67 } else {
68 ui.horizontal(|ui| {
69 ui.set_width(320.0);
70 ui.horizontal_wrapped(|ui| {
71 ui.set_max_width(220.0);
72 for (i, item) in answers.clone().iter().enumerate() {
73 if ui.selectable_label(true, format!("{item} x")).clicked() {
74 answers.remove(i);
75 items.push(item.clone());
76 ui.memory_mut(|m| m.open_popup(popup_id))
77 };
78 }
79 });
80 ui.with_layout(Layout::right_to_left(Align::TOP), |ui| {
81 let icon_trash = egui_phosphor::regular::TRASH.to_owned();
82 if ui.button(icon_trash).clicked() {
83 answers.clear();
84 items.clear();
85 for item in options.clone() {
86 items.push(item)
87 }
88 ui.memory_mut(|m| m.open_popup(popup_id))
89 };
90 let icon_open = egui_phosphor::regular::FOLDER_OPEN.to_owned();
91 if ui.button(icon_open).clicked() && !items.is_empty() {
92 ui.memory_mut(|m| m.open_popup(popup_id))
93 }
94 });
95 })
96 .response
97 };
98 if r.clicked() {
99 ui.memory_mut(|m| m.open_popup(popup_id));
100 }
101 let mut changed = false;
102 eframe::egui::popup_below_widget(
103 ui,
104 popup_id,
105 &r,
106 eframe::egui::PopupCloseBehavior::CloseOnClickOutside,
107 |ui| {
108 eframe::egui::ScrollArea::vertical().show(ui, |ui| {
109 for (i, var) in items.clone().iter().enumerate() {
110 let text = var.clone();
111
112 if display(ui, &text).clicked() {
113 if answers.clone().len() != *max_opt as usize {
114 answers.push(text.clone());
115 items.remove(i);
116 changed = true;
117 } else {
118 *toasted = true;
119 }
120 }
121 }
122 });
123 },
124 );
125 if changed {
126 r.mark_changed();
127 if !items.is_empty() {
128 ui.memory_mut(|m| m.open_popup(popup_id))
129 }
130 if answers.len() == *max_opt as usize {
131 ui.memory_mut(|m| m.close_popup())
132 }
133 }
134 r
135 }
136}