select.rs

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