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 style: 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.handle().downgrade(),
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 style: Default::default(),
52 }
53 }
54
55 pub fn with_style(mut self, style: &SelectStyle) -> Self {
56 self.style = style.clone();
57 self
58 }
59
60 pub fn set_item_count(&mut self, count: usize, cx: &mut ViewContext<Self>) {
61 self.item_count = count;
62 cx.notify();
63 }
64
65 fn toggle(&mut self, _: &ToggleSelect, cx: &mut ViewContext<Self>) {
66 self.is_open = !self.is_open;
67 cx.notify();
68 }
69
70 fn select_item(&mut self, action: &SelectItem, cx: &mut ViewContext<Self>) {
71 self.selected_item_ix = action.0;
72 self.is_open = false;
73 cx.notify();
74 }
75}
76
77impl Entity for Select {
78 type Event = Event;
79}
80
81impl View for Select {
82 fn ui_name() -> &'static str {
83 "Select"
84 }
85
86 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
87 if self.item_count == 0 {
88 return Empty::new().boxed();
89 }
90
91 enum Header {}
92 enum Item {}
93
94 let mut result = Flex::column().with_child(
95 MouseEventHandler::new::<Header, _, _, _>(self.handle.id(), cx, |mouse_state, cx| {
96 Container::new((self.render_item)(
97 self.selected_item_ix,
98 ItemType::Header,
99 mouse_state.hovered,
100 cx,
101 ))
102 .with_style(&self.style.header)
103 .boxed()
104 })
105 .on_click(move |cx| cx.dispatch_action(ToggleSelect))
106 .boxed(),
107 );
108 if self.is_open {
109 let handle = self.handle.clone();
110 result.add_child(
111 Overlay::new(
112 Container::new(
113 ConstrainedBox::new(
114 UniformList::new(
115 self.list_state.clone(),
116 self.item_count,
117 move |mut range, items, mut cx| {
118 let handle = handle.upgrade(cx).unwrap();
119 let this = handle.read(cx);
120 let selected_item_ix = this.selected_item_ix;
121 range.end = range.end.min(this.item_count);
122 items.extend(range.map(|ix| {
123 MouseEventHandler::new::<Item, _, _, _>(
124 (handle.id(), ix),
125 &mut cx,
126 |mouse_state, cx| {
127 (handle.read(*cx).render_item)(
128 ix,
129 if ix == selected_item_ix {
130 ItemType::Selected
131 } else {
132 ItemType::Unselected
133 },
134 mouse_state.hovered,
135 cx,
136 )
137 },
138 )
139 .on_click(move |cx| cx.dispatch_action(SelectItem(ix)))
140 .boxed()
141 }))
142 },
143 )
144 .boxed(),
145 )
146 .with_max_height(200.)
147 .boxed(),
148 )
149 .with_style(&self.style.menu)
150 .boxed(),
151 )
152 .boxed(),
153 )
154 }
155 result.boxed()
156 }
157}