vizia_core/views/
picklist.rs1use std::ops::Deref;
2
3use crate::context::TreeProps;
4use crate::icons::{ICON_CHECK, ICON_CHEVRON_DOWN};
5use crate::prelude::*;
6
7pub struct PickList {
9 on_select: Option<Box<dyn Fn(&mut EventContext, usize)>>,
10 placeholder: Signal<String>,
11 is_open: Signal<bool>,
12}
13
14pub(crate) enum PickListEvent {
15 SetOption(usize),
16}
17
18impl PickList {
19 pub fn new<L, S, V, T>(
21 cx: &mut Context,
22 list: L,
23 selected: S,
24 show_handle: bool,
25 ) -> Handle<Self>
26 where
27 L: SignalGet<V> + SignalMap<V> + Res<V> + 'static,
28 V: Deref<Target = [T]> + Clone + 'static,
29 T: 'static + ToStringLocalized + Clone + PartialEq,
30 S: Res<usize> + 'static,
31 {
32 let is_open = Signal::new(false);
33 let placeholder = Signal::new(String::new());
34 let selected = selected.to_signal(cx);
35 Self { on_select: None, placeholder, is_open }
36 .build(cx, |cx| {
37 Button::new(cx, |cx| {
38 HStack::new(cx, move |cx| {
40 Label::new(cx, placeholder)
41 .bind(list, move |handle| {
42 let list = list.get();
43 handle.bind(selected, move |handle| {
44 let selected_index = selected.get();
45 let list_len = list.len();
46 if selected_index < list_len {
47 let selected_text =
48 if let Some(item) = list.get(selected_index) {
49 item.to_string_local(&handle)
50 } else {
51 placeholder.get()
52 };
53 handle.text(selected_text);
54 } else {
55 handle.text(placeholder.get());
56 }
57 });
58 })
59 .width(Stretch(2.0))
60 .text_wrap(false)
61 .text_overflow(TextOverflow::Ellipsis)
62 .hoverable(false);
63 if show_handle {
64 Svg::new(cx, ICON_CHEVRON_DOWN)
65 .class("icon")
66 .size(Pixels(16.0))
67 .hoverable(false);
68 }
69 })
70 .width(Stretch(1.0))
71 .gap(Pixels(8.0))
73 })
74 .width(Stretch(1.0))
75 .on_press(|cx| cx.emit(PopupEvent::Open));
76
77 Binding::new(cx, is_open, move |cx| {
78 let is_open = is_open.get();
79 if is_open {
80 Popup::new(cx, |cx| {
81 List::new(cx, list, move |cx, _, item| {
82 Element::new(cx).class("focus-indicator");
83 Svg::new(cx, ICON_CHECK).class("checkmark").size(Pixels(16.0));
84 Label::new(cx, item.map(|v| v.clone())).hoverable(false);
85 })
86 .selectable(Selectable::Single)
87 .selected(selected.map(|s| vec![*s]))
88 .on_select(|cx, index| {
89 cx.emit(PickListEvent::SetOption(index));
90 cx.emit(PopupEvent::Close);
91 })
92 .focused(true);
93 })
94 .arrow_size(Pixels(4.0))
95 .on_blur(|cx| cx.emit(PopupEvent::Close));
96 }
97 });
98 })
99 .navigable(false)
100 }
101}
102
103impl View for PickList {
104 fn element(&self) -> Option<&'static str> {
105 Some("picklist")
106 }
107
108 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
109 event.map(|picklist_event, _| match picklist_event {
110 PickListEvent::SetOption(index) => {
111 if let Some(callback) = &self.on_select {
112 (callback)(cx, *index);
113 }
114 }
115 });
116
117 event.map(|popup_event, meta| match popup_event {
118 PopupEvent::Open => {
119 self.is_open.set_if_changed(true);
120
121 meta.consume();
122 }
123
124 PopupEvent::Close => {
125 self.is_open.set_if_changed(false);
126 let e = cx.first_child();
127 cx.with_current(e, |cx| cx.focus());
128 meta.consume();
129 }
130
131 PopupEvent::Switch => {
132 self.is_open.set(!self.is_open.get());
133 meta.consume();
134 }
135 });
136 }
137}
138
139impl Handle<'_, PickList> {
140 pub fn placeholder<P: ToStringLocalized + Clone + 'static>(
142 self,
143 placeholder: impl Res<P> + 'static,
144 ) -> Self {
145 let placeholder = placeholder.to_signal(self.cx);
146 self.bind(placeholder, move |handle| {
147 let val = placeholder.get();
148 let txt = val.to_string_local(&handle);
149 handle.modify(|picklist| picklist.placeholder.set(txt));
150 })
151 }
152
153 pub fn on_select<F>(self, callback: F) -> Self
155 where
156 F: 'static + Fn(&mut EventContext, usize),
157 {
158 self.modify(|picklist: &mut PickList| picklist.on_select = Some(Box::new(callback)))
159 }
160}