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