select.rs

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