use serde::Deserialize; use crate::{ actions, elements::*, impl_actions, AppContext, Entity, MouseButton, MutableAppContext, RenderContext, View, ViewContext, WeakViewHandle, }; pub struct Select { handle: WeakViewHandle, render_item: Box ElementBox>, selected_item_ix: usize, item_count: usize, is_open: bool, list_state: UniformListState, build_style: Option SelectStyle>>, } #[derive(Clone, Default)] pub struct SelectStyle { pub header: ContainerStyle, pub menu: ContainerStyle, } pub enum ItemType { Header, Selected, Unselected, } #[derive(Clone, Deserialize, PartialEq)] pub struct SelectItem(pub usize); actions!(select, [ToggleSelect]); impl_actions!(select, [SelectItem]); pub enum Event {} pub fn init(cx: &mut MutableAppContext) { cx.add_action(Select::toggle); cx.add_action(Select::select_item); } impl Select { pub fn new ElementBox>( item_count: usize, cx: &mut ViewContext, render_item: F, ) -> Self { Self { handle: cx.weak_handle(), render_item: Box::new(render_item), selected_item_ix: 0, item_count, is_open: false, list_state: UniformListState::default(), build_style: Default::default(), } } pub fn with_style( mut self, f: impl 'static + FnMut(&mut MutableAppContext) -> SelectStyle, ) -> Self { self.build_style = Some(Box::new(f)); self } pub fn set_item_count(&mut self, count: usize, cx: &mut ViewContext) { self.item_count = count; cx.notify(); } fn toggle(&mut self, _: &ToggleSelect, cx: &mut ViewContext) { self.is_open = !self.is_open; cx.notify(); } fn select_item(&mut self, action: &SelectItem, cx: &mut ViewContext) { self.selected_item_ix = action.0; self.is_open = false; cx.notify(); } pub fn selected_index(&self) -> usize { self.selected_item_ix } } impl Entity for Select { type Event = Event; } impl View for Select { fn ui_name() -> &'static str { "Select" } fn render(&mut self, cx: &mut RenderContext) -> ElementBox { if self.item_count == 0 { return Empty::new().boxed(); } enum Header {} enum Item {} let style = if let Some(build_style) = self.build_style.as_mut() { (build_style)(cx) } else { Default::default() }; let mut result = Flex::column().with_child( MouseEventHandler::
::new(self.handle.id(), cx, |mouse_state, cx| { Container::new((self.render_item)( self.selected_item_ix, ItemType::Header, mouse_state.hovered, cx, )) .with_style(style.header) .boxed() }) .on_click(MouseButton::Left, move |_, cx| { cx.dispatch_action(ToggleSelect) }) .boxed(), ); if self.is_open { result.add_child( Overlay::new( Container::new( ConstrainedBox::new( UniformList::new( self.list_state.clone(), self.item_count, cx, move |this, mut range, items, cx| { let selected_item_ix = this.selected_item_ix; range.end = range.end.min(this.item_count); items.extend(range.map(|ix| { MouseEventHandler::::new(ix, cx, |mouse_state, cx| { (this.render_item)( ix, if ix == selected_item_ix { ItemType::Selected } else { ItemType::Unselected }, mouse_state.hovered, cx, ) }) .on_click(MouseButton::Left, move |_, cx| { cx.dispatch_action(SelectItem(ix)) }) .boxed() })) }, ) .boxed(), ) .with_max_height(200.) .boxed(), ) .with_style(style.menu) .boxed(), ) .boxed(), ) } result.boxed() } }