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    style: 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            style: Default::default(),
 52        }
 53    }
 54
 55    pub fn with_style(mut self, style: &SelectStyle) -> Self {
 56        self.style = style.clone();
 57        self
 58    }
 59
 60    pub fn set_item_count(&mut self, count: usize, cx: &mut ViewContext<Self>) {
 61        self.item_count = count;
 62        cx.notify();
 63    }
 64
 65    fn toggle(&mut self, _: &ToggleSelect, cx: &mut ViewContext<Self>) {
 66        self.is_open = !self.is_open;
 67        cx.notify();
 68    }
 69
 70    fn select_item(&mut self, action: &SelectItem, cx: &mut ViewContext<Self>) {
 71        self.selected_item_ix = action.0;
 72        self.is_open = false;
 73        cx.notify();
 74    }
 75}
 76
 77impl Entity for Select {
 78    type Event = Event;
 79}
 80
 81impl View for Select {
 82    fn ui_name() -> &'static str {
 83        "Select"
 84    }
 85
 86    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
 87        if self.item_count == 0 {
 88            return Empty::new().boxed();
 89        }
 90
 91        enum Header {}
 92        enum Item {}
 93
 94        let mut result = Flex::column().with_child(
 95            MouseEventHandler::new::<Header, _, _, _>(self.handle.id(), cx, |mouse_state, cx| {
 96                Container::new((self.render_item)(
 97                    self.selected_item_ix,
 98                    ItemType::Header,
 99                    mouse_state.hovered,
100                    cx,
101                ))
102                .with_style(&self.style.header)
103                .boxed()
104            })
105            .on_click(move |cx| cx.dispatch_action(ToggleSelect))
106            .boxed(),
107        );
108        if self.is_open {
109            let handle = self.handle.clone();
110            result.add_child(
111                Overlay::new(
112                    Container::new(
113                        ConstrainedBox::new(
114                            UniformList::new(
115                                self.list_state.clone(),
116                                self.item_count,
117                                move |mut range, items, mut cx| {
118                                    let handle = handle.upgrade(cx).unwrap();
119                                    let this = handle.read(cx);
120                                    let selected_item_ix = this.selected_item_ix;
121                                    range.end = range.end.min(this.item_count);
122                                    items.extend(range.map(|ix| {
123                                        MouseEventHandler::new::<Item, _, _, _>(
124                                            (handle.id(), ix),
125                                            &mut cx,
126                                            |mouse_state, cx| {
127                                                (handle.read(*cx).render_item)(
128                                                    ix,
129                                                    if ix == selected_item_ix {
130                                                        ItemType::Selected
131                                                    } else {
132                                                        ItemType::Unselected
133                                                    },
134                                                    mouse_state.hovered,
135                                                    cx,
136                                                )
137                                            },
138                                        )
139                                        .on_click(move |cx| cx.dispatch_action(SelectItem(ix)))
140                                        .boxed()
141                                    }))
142                                },
143                            )
144                            .boxed(),
145                        )
146                        .with_max_height(200.)
147                        .boxed(),
148                    )
149                    .with_style(&self.style.menu)
150                    .boxed(),
151                )
152                .boxed(),
153            )
154        }
155        result.boxed()
156    }
157}