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