select.rs

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