select.rs

  1use serde::Deserialize;
  2
  3use crate::{
  4    actions, elements::*, impl_actions, AppContext, Entity, MutableAppContext, RenderContext, View,
  5    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)]
 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::new::<Header, _, _>(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(move |_, _, cx| cx.dispatch_action(ToggleSelect))
123            .boxed(),
124        );
125        if self.is_open {
126            result.add_child(
127                Overlay::new(
128                    Container::new(
129                        ConstrainedBox::new(
130                            UniformList::new(
131                                self.list_state.clone(),
132                                self.item_count,
133                                cx,
134                                move |this, mut range, items, 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                                                (this.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| {
155                                            cx.dispatch_action(SelectItem(ix))
156                                        })
157                                        .boxed()
158                                    }))
159                                },
160                            )
161                            .boxed(),
162                        )
163                        .with_max_height(200.)
164                        .boxed(),
165                    )
166                    .with_style(style.menu)
167                    .boxed(),
168                )
169                .boxed(),
170            )
171        }
172        result.boxed()
173    }
174}