vizia_core/views/
select.rs1use std::ops::Deref;
2
3use crate::context::TreeProps;
4use crate::icons::{ICON_CHECK, ICON_CHEVRON_DOWN};
5use crate::prelude::*;
6
7pub struct Select {
9 on_select: Option<Box<dyn Fn(&mut EventContext, usize)>>,
10 placeholder: Signal<String>,
11 is_open: Signal<bool>,
12 min_selected: Signal<usize>,
13 max_selected: Signal<usize>,
14}
15
16pub(crate) enum SelectEvent {
17 SetOption(usize),
18}
19
20impl Select {
21 pub fn new<L, S, V, T>(
23 cx: &mut Context,
24 list: L,
25 selected: S,
26 show_handle: bool,
27 ) -> Handle<Self>
28 where
29 L: SignalGet<V> + SignalMap<V> + Res<V> + 'static,
30 V: Deref<Target = [T]> + Clone + 'static,
31 T: 'static + ToStringLocalized + Clone + PartialEq,
32 S: Res<Option<usize>> + 'static,
33 {
34 let list = list.to_signal(cx);
35 let is_open = Signal::new(false);
36 let placeholder = Signal::new(String::new());
37 let display_text = Signal::new(String::new());
38 let min_selected = Signal::new(0);
39 let max_selected = Signal::new(usize::MAX);
40 let selected = selected.to_signal(cx);
41 let locale = cx.environment().locale;
42 Self { on_select: None, placeholder, is_open, min_selected, max_selected }
43 .build(cx, |cx| {
44 Button::new(cx, |cx| {
45 HStack::new(cx, move |cx| {
47 Label::new(cx, display_text)
48 .bind(list, move |handle| {
49 let text = if let Some(index) = selected.get() {
50 list.with(|list| list.get(index).cloned())
51 .map(|item| item.to_string_local(&handle))
52 .unwrap_or_else(|| placeholder.get())
53 } else {
54 placeholder.get()
55 };
56
57 display_text.set_if_changed(text);
58 })
59 .bind(selected, move |handle| {
60 let text = if let Some(index) = selected.get() {
61 list.with(|list| list.get(index).cloned())
62 .map(|item| item.to_string_local(&handle))
63 .unwrap_or_else(|| placeholder.get())
64 } else {
65 placeholder.get()
66 };
67
68 display_text.set_if_changed(text);
69 })
70 .bind(placeholder, move |handle| {
71 let text = if let Some(index) = selected.get() {
72 list.with(|list| list.get(index).cloned())
73 .map(|item| item.to_string_local(&handle))
74 .unwrap_or_else(|| placeholder.get())
75 } else {
76 placeholder.get()
77 };
78
79 display_text.set_if_changed(text);
80 })
81 .bind(locale, move |handle| {
82 let text = if let Some(index) = selected.get() {
83 list.with(|list| list.get(index).cloned())
84 .map(|item| item.to_string_local(&handle))
85 .unwrap_or_else(|| placeholder.get())
86 } else {
87 placeholder.get()
88 };
89
90 display_text.set_if_changed(text);
91 })
92 .width(Stretch(2.0))
93 .text_wrap(false)
94 .text_overflow(TextOverflow::Ellipsis)
95 .hoverable(false);
96 if show_handle {
97 Svg::new(cx, ICON_CHEVRON_DOWN)
98 .class("icon")
99 .size(Pixels(16.0))
100 .hoverable(false);
101 }
102 })
103 .width(Stretch(1.0))
104 .gap(Pixels(8.0))
106 })
107 .variant(ButtonVariant::Outline)
108 .width(Stretch(1.0))
109 .on_press(|cx| cx.emit(PopupEvent::Open));
110
111 Binding::new(cx, is_open, move |cx| {
112 let is_open = is_open.get();
113 if is_open {
114 Popover::new(cx, |cx| {
115 let list_signal = list;
116
117 List::new(cx, list_signal, move |cx, _, item| {
118 Svg::new(cx, ICON_CHECK).class("checkmark").size(Pixels(16.0));
119 Label::new(cx, item.map(|v| v.clone())).hoverable(false);
120 })
121 .selectable(Selectable::Single)
122 .min_selected(min_selected)
123 .max_selected(max_selected)
124 .type_ahead_text(move |cx, index| {
125 list_signal.with(|list| {
126 list.get(index).map(|item| item.to_string_local(cx))
127 })
128 })
129 .focus_first_item_on_focus_in(false)
130 .selection(
131 selected.map(|s| {
132 if let Some(index) = s { vec![*index] } else { vec![] }
133 }),
134 )
135 .on_select(|cx, index| {
136 cx.emit(SelectEvent::SetOption(index));
137 cx.emit(PopupEvent::Close);
138 })
139 .focused(true);
140 })
141 .arrow_size(Pixels(4.0))
142 .on_blur(|cx| cx.emit(PopupEvent::Close));
143 }
144 });
145 })
146 .navigable(false)
147 }
148}
149
150impl View for Select {
151 fn element(&self) -> Option<&'static str> {
152 Some("select")
153 }
154
155 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
156 event.map(|select_event, _| match select_event {
157 SelectEvent::SetOption(index) => {
158 if let Some(callback) = &self.on_select {
159 (callback)(cx, *index);
160 }
161 }
162 });
163
164 event.map(|popup_event, meta| match popup_event {
165 PopupEvent::Open => {
166 self.is_open.set_if_changed(true);
167
168 meta.consume();
169 }
170
171 PopupEvent::Close => {
172 self.is_open.set_if_changed(false);
173 let e = cx.first_child();
174 cx.with_current(e, |cx| cx.focus());
175 meta.consume();
176 }
177
178 PopupEvent::Switch => {
179 self.is_open.set(!self.is_open.get());
180 meta.consume();
181 }
182 });
183 }
184}
185
186impl Handle<'_, Select> {
187 pub fn placeholder<P: ToStringLocalized + Clone + 'static>(
189 self,
190 placeholder: impl Res<P> + 'static,
191 ) -> Self {
192 let placeholder = placeholder.to_signal(self.cx);
195 let locale = self.cx.environment().locale;
196
197 self.bind(placeholder, move |handle| {
198 let val = placeholder.get();
199 let txt = val.to_string_local(&handle);
200 handle.modify(|select| select.placeholder.set(txt));
201 })
202 .bind(locale, move |handle| {
203 let val = placeholder.get();
204 let txt = val.to_string_local(&handle);
205 handle.modify(|select| select.placeholder.set(txt));
206 })
207 }
208
209 pub fn on_select<F>(self, callback: F) -> Self
211 where
212 F: 'static + Fn(&mut EventContext, usize),
213 {
214 self.modify(|select: &mut Select| select.on_select = Some(Box::new(callback)))
215 }
216
217 pub fn min_selected(self, min_selected: impl Res<usize> + 'static) -> Self {
219 let min_selected = min_selected.to_signal(self.cx);
220 self.bind(min_selected, move |handle| {
221 let min_selected = min_selected.get();
222 handle.modify(|select: &mut Select| select.min_selected.set(min_selected));
223 })
224 }
225
226 pub fn max_selected(self, max_selected: impl Res<usize> + 'static) -> Self {
228 let max_selected = max_selected.to_signal(self.cx);
229 self.bind(max_selected, move |handle| {
230 let max_selected = max_selected.get();
231 handle.modify(|select: &mut Select| select.max_selected.set(max_selected));
232 })
233 }
234}