select.rs

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