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