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/// A view which allows the user to select an item from a dropdown list.
8#[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    /// Creates a new [PickList] view.
21    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                    // A Label and an optional Icon
37                    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(Stretch(1.0))
63                    .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    /// Sets the placeholder text that appears when the textbox has no value.
131    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    /// Sets the callback triggered when an option is selected.
139    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}