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