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 is_open = Signal::new(false);
35 let placeholder = Signal::new(String::new());
36 let min_selected = Signal::new(0);
37 let max_selected = Signal::new(usize::MAX);
38 let selected = selected.to_signal(cx);
39 Self { on_select: None, placeholder, is_open, min_selected, max_selected }
40 .build(cx, |cx| {
41 Button::new(cx, |cx| {
42 HStack::new(cx, move |cx| {
44 Label::new(cx, placeholder)
45 .bind(list, move |handle| {
46 handle.bind(selected, move |handle| {
48 let selected_index = selected.get();
49 let list_len = list.with(|list| list.len());
50 if let Some(index) = selected_index {
51 if index < list_len {
52 let item = Memo::new(move |_| {
53 list.with(move |list| list.get(index).cloned())
54 });
55
56 if item.get().is_some() {
57 handle
58 .text(item.map(move |it| it.clone().unwrap()));
59 } else {
60 handle.text(placeholder.get());
61 };
62 } else {
63 handle.text(placeholder.get());
64 }
65 } else {
66 handle.text(placeholder.get());
67 }
68 });
69 })
70 .width(Stretch(2.0))
71 .text_wrap(false)
72 .text_overflow(TextOverflow::Ellipsis)
73 .hoverable(false);
74 if show_handle {
75 Svg::new(cx, ICON_CHEVRON_DOWN)
76 .class("icon")
77 .size(Pixels(16.0))
78 .hoverable(false);
79 }
80 })
81 .width(Stretch(1.0))
82 .gap(Pixels(8.0))
84 })
85 .variant(ButtonVariant::Outline)
86 .width(Stretch(1.0))
87 .on_press(|cx| cx.emit(PopupEvent::Open));
88
89 Binding::new(cx, is_open, move |cx| {
90 let is_open = is_open.get();
91 if is_open {
92 Popover::new(cx, |cx| {
93 List::new(cx, list, move |cx, _, item| {
94 Svg::new(cx, ICON_CHECK).class("checkmark").size(Pixels(16.0));
95 Label::new(cx, item.map(|v| v.clone())).hoverable(false);
96 })
97 .selectable(Selectable::Single)
98 .min_selected(min_selected)
99 .max_selected(max_selected)
100 .selection(
101 selected.map(|s| {
102 if let Some(index) = s { vec![*index] } else { vec![] }
103 }),
104 )
105 .on_select(|cx, index| {
106 cx.emit(SelectEvent::SetOption(index));
107 cx.emit(PopupEvent::Close);
108 })
109 .focused(true);
110 })
111 .arrow_size(Pixels(4.0))
112 .on_blur(|cx| cx.emit(PopupEvent::Close));
113 }
114 });
115 })
116 .navigable(false)
117 }
118}
119
120impl View for Select {
121 fn element(&self) -> Option<&'static str> {
122 Some("select")
123 }
124
125 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
126 event.map(|select_event, _| match select_event {
127 SelectEvent::SetOption(index) => {
128 if let Some(callback) = &self.on_select {
129 (callback)(cx, *index);
130 }
131 }
132 });
133
134 event.map(|popup_event, meta| match popup_event {
135 PopupEvent::Open => {
136 self.is_open.set_if_changed(true);
137
138 meta.consume();
139 }
140
141 PopupEvent::Close => {
142 self.is_open.set_if_changed(false);
143 let e = cx.first_child();
144 cx.with_current(e, |cx| cx.focus());
145 meta.consume();
146 }
147
148 PopupEvent::Switch => {
149 self.is_open.set(!self.is_open.get());
150 meta.consume();
151 }
152 });
153 }
154}
155
156impl Handle<'_, Select> {
157 pub fn placeholder<P: ToStringLocalized + Clone + 'static>(
159 self,
160 placeholder: impl Res<P> + 'static,
161 ) -> Self {
162 let placeholder = placeholder.to_signal(self.cx);
163 self.bind(placeholder, move |handle| {
164 let val = placeholder.get();
165 let txt = val.to_string_local(&handle);
166 handle.modify(|select| select.placeholder.set(txt));
167 })
168 }
169
170 pub fn on_select<F>(self, callback: F) -> Self
172 where
173 F: 'static + Fn(&mut EventContext, usize),
174 {
175 self.modify(|select: &mut Select| select.on_select = Some(Box::new(callback)))
176 }
177
178 pub fn min_selected(self, min_selected: impl Res<usize> + 'static) -> Self {
180 let min_selected = min_selected.to_signal(self.cx);
181 self.bind(min_selected, move |handle| {
182 let min_selected = min_selected.get();
183 handle.modify(|select: &mut Select| select.min_selected.set(min_selected));
184 })
185 }
186
187 pub fn max_selected(self, max_selected: impl Res<usize> + 'static) -> Self {
189 let max_selected = max_selected.to_signal(self.cx);
190 self.bind(max_selected, move |handle| {
191 let max_selected = max_selected.get();
192 handle.modify(|select: &mut Select| select.max_selected.set(max_selected));
193 })
194 }
195}