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