select.rs

  1use serde::Deserialize;
  2
  3use crate::{
  4    actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, View,
  5    ViewContext, WeakViewHandle,
  6};
  7
  8pub struct Select {
  9    handle: WeakViewHandle<Self>,
 10    render_item: Box<dyn Fn(usize, ItemType, bool, &AppContext) -> AnyElement<Self>>,
 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) -> AnyElement<Self>>(
 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 ViewContext<Self>) -> AnyElement<Self> {
 96        if self.item_count == 0 {
 97            return Empty::new().into_any();
 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::new::<Header, _>(self.handle.id(), cx, |mouse_state, cx| {
110                (self.render_item)(
111                    self.selected_item_ix,
112                    ItemType::Header,
113                    mouse_state.hovered(),
114                    cx,
115                )
116                .contained()
117                .with_style(style.header)
118            })
119            .on_click(MouseButton::Left, move |_, this, cx| {
120                this.toggle(&Default::default(), cx);
121            }),
122        );
123        if self.is_open {
124            result.add_child(Overlay::new(
125                UniformList::new(
126                    self.list_state.clone(),
127                    self.item_count,
128                    cx,
129                    move |this, mut range, items, cx| {
130                        let selected_item_ix = this.selected_item_ix;
131                        range.end = range.end.min(this.item_count);
132                        items.extend(range.map(|ix| {
133                            MouseEventHandler::new::<Item, _>(ix, cx, |mouse_state, cx| {
134                                (this.render_item)(
135                                    ix,
136                                    if ix == selected_item_ix {
137                                        ItemType::Selected
138                                    } else {
139                                        ItemType::Unselected
140                                    },
141                                    mouse_state.hovered(),
142                                    cx,
143                                )
144                            })
145                            .on_click(MouseButton::Left, move |_, this, cx| {
146                                this.select_item(&SelectItem(ix), cx);
147                            })
148                            .into_any()
149                        }))
150                    },
151                )
152                .constrained()
153                .with_max_height(200.)
154                .contained()
155                .with_style(style.menu),
156            ));
157        }
158        result.into_any()
159    }
160}