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