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