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