select.rs

  1use crate::{
  2    action, elements::*, AppContext, Entity, MutableAppContext, RenderContext, View, ViewContext,
  3    WeakViewHandle,
  4};
  5
  6pub struct Select {
  7    handle: WeakViewHandle<Self>,
  8    render_item: Box<dyn Fn(usize, ItemType, bool, &AppContext) -> ElementBox>,
  9    selected_item_ix: usize,
 10    item_count: usize,
 11    is_open: bool,
 12    list_state: UniformListState,
 13    build_style: Option<Box<dyn FnMut(&mut MutableAppContext) -> SelectStyle>>,
 14}
 15
 16#[derive(Clone, Default)]
 17pub struct SelectStyle {
 18    pub header: ContainerStyle,
 19    pub menu: ContainerStyle,
 20}
 21
 22pub enum ItemType {
 23    Header,
 24    Selected,
 25    Unselected,
 26}
 27
 28action!(ToggleSelect);
 29action!(SelectItem, usize);
 30
 31pub enum Event {}
 32
 33pub fn init(cx: &mut MutableAppContext) {
 34    cx.add_action(Select::toggle);
 35    cx.add_action(Select::select_item);
 36}
 37
 38impl Select {
 39    pub fn new<F: 'static + Fn(usize, ItemType, bool, &AppContext) -> ElementBox>(
 40        item_count: usize,
 41        cx: &mut ViewContext<Self>,
 42        render_item: F,
 43    ) -> Self {
 44        Self {
 45            handle: cx.handle().downgrade(),
 46            render_item: Box::new(render_item),
 47            selected_item_ix: 0,
 48            item_count,
 49            is_open: false,
 50            list_state: UniformListState::default(),
 51            build_style: Default::default(),
 52        }
 53    }
 54
 55    pub fn with_style(
 56        mut self,
 57        f: impl 'static + FnMut(&mut MutableAppContext) -> SelectStyle,
 58    ) -> Self {
 59        self.build_style = Some(Box::new(f));
 60        self
 61    }
 62
 63    pub fn set_item_count(&mut self, count: usize, cx: &mut ViewContext<Self>) {
 64        self.item_count = count;
 65        cx.notify();
 66    }
 67
 68    fn toggle(&mut self, _: &ToggleSelect, cx: &mut ViewContext<Self>) {
 69        self.is_open = !self.is_open;
 70        cx.notify();
 71    }
 72
 73    fn select_item(&mut self, action: &SelectItem, cx: &mut ViewContext<Self>) {
 74        self.selected_item_ix = action.0;
 75        self.is_open = false;
 76        cx.notify();
 77    }
 78
 79    pub fn selected_index(&self) -> usize {
 80        self.selected_item_ix
 81    }
 82}
 83
 84impl Entity for Select {
 85    type Event = Event;
 86}
 87
 88impl View for Select {
 89    fn ui_name() -> &'static str {
 90        "Select"
 91    }
 92
 93    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
 94        if self.item_count == 0 {
 95            return Empty::new().boxed();
 96        }
 97
 98        enum Header {}
 99        enum Item {}
100
101        let style = if let Some(build_style) = self.build_style.as_mut() {
102            (build_style)(cx)
103        } else {
104            Default::default()
105        };
106        let mut result = Flex::column().with_child(
107            MouseEventHandler::new::<Header, _, _, _>(self.handle.id(), cx, |mouse_state, cx| {
108                Container::new((self.render_item)(
109                    self.selected_item_ix,
110                    ItemType::Header,
111                    mouse_state.hovered,
112                    cx,
113                ))
114                .with_style(style.header)
115                .boxed()
116            })
117            .on_click(move |cx| cx.dispatch_action(ToggleSelect))
118            .boxed(),
119        );
120        if self.is_open {
121            let handle = self.handle.clone();
122            result.add_child(
123                Overlay::new(
124                    Container::new(
125                        ConstrainedBox::new(
126                            UniformList::new(
127                                self.list_state.clone(),
128                                self.item_count,
129                                move |mut range, items, cx| {
130                                    let handle = handle.upgrade(cx).unwrap();
131                                    let this = handle.read(cx);
132                                    let selected_item_ix = this.selected_item_ix;
133                                    range.end = range.end.min(this.item_count);
134                                    items.extend(range.map(|ix| {
135                                        MouseEventHandler::new::<Item, _, _, _>(
136                                            (handle.id(), ix),
137                                            cx,
138                                            |mouse_state, cx| {
139                                                (handle.read(cx).render_item)(
140                                                    ix,
141                                                    if ix == selected_item_ix {
142                                                        ItemType::Selected
143                                                    } else {
144                                                        ItemType::Unselected
145                                                    },
146                                                    mouse_state.hovered,
147                                                    cx,
148                                                )
149                                            },
150                                        )
151                                        .on_click(move |cx| cx.dispatch_action(SelectItem(ix)))
152                                        .boxed()
153                                    }))
154                                },
155                            )
156                            .boxed(),
157                        )
158                        .with_max_height(200.)
159                        .boxed(),
160                    )
161                    .with_style(style.menu)
162                    .boxed(),
163                )
164                .boxed(),
165            )
166        }
167        result.boxed()
168    }
169}