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
 80impl Entity for Select {
 81    type Event = Event;
 82}
 83
 84impl View for Select {
 85    fn ui_name() -> &'static str {
 86        "Select"
 87    }
 88
 89    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
 90        if self.item_count == 0 {
 91            return Empty::new().boxed();
 92        }
 93
 94        enum Header {}
 95        enum Item {}
 96
 97        let style = if let Some(build_style) = self.build_style.as_mut() {
 98            (build_style)(cx)
 99        } else {
100            Default::default()
101        };
102        let mut result = Flex::column().with_child(
103            MouseEventHandler::new::<Header, _, _, _>(self.handle.id(), cx, |mouse_state, cx| {
104                Container::new((self.render_item)(
105                    self.selected_item_ix,
106                    ItemType::Header,
107                    mouse_state.hovered,
108                    cx,
109                ))
110                .with_style(&style.header)
111                .boxed()
112            })
113            .on_click(move |cx| cx.dispatch_action(ToggleSelect))
114            .boxed(),
115        );
116        if self.is_open {
117            let handle = self.handle.clone();
118            result.add_child(
119                Overlay::new(
120                    Container::new(
121                        ConstrainedBox::new(
122                            UniformList::new(
123                                self.list_state.clone(),
124                                self.item_count,
125                                move |mut range, items, mut cx| {
126                                    let handle = handle.upgrade(cx).unwrap();
127                                    let this = handle.read(cx);
128                                    let selected_item_ix = this.selected_item_ix;
129                                    range.end = range.end.min(this.item_count);
130                                    items.extend(range.map(|ix| {
131                                        MouseEventHandler::new::<Item, _, _, _>(
132                                            (handle.id(), ix),
133                                            &mut cx,
134                                            |mouse_state, cx| {
135                                                (handle.read(*cx).render_item)(
136                                                    ix,
137                                                    if ix == selected_item_ix {
138                                                        ItemType::Selected
139                                                    } else {
140                                                        ItemType::Unselected
141                                                    },
142                                                    mouse_state.hovered,
143                                                    cx,
144                                                )
145                                            },
146                                        )
147                                        .on_click(move |cx| cx.dispatch_action(SelectItem(ix)))
148                                        .boxed()
149                                    }))
150                                },
151                            )
152                            .boxed(),
153                        )
154                        .with_max_height(200.)
155                        .boxed(),
156                    )
157                    .with_style(&style.menu)
158                    .boxed(),
159                )
160                .boxed(),
161            )
162        }
163        result.boxed()
164    }
165}