1use serde::Deserialize;
2
3use crate::{
4 actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, View,
5 ViewContext, WeakViewHandle,
6};
7
8pub struct Select {
9 handle: WeakViewHandle<Self>,
10 render_item: Box<dyn Fn(usize, ItemType, bool, &AppContext) -> AnyElement<Self>>,
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 AppContext) -> 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, PartialEq)]
31pub struct SelectItem(pub usize);
32
33actions!(select, [ToggleSelect]);
34impl_actions!(select, [SelectItem]);
35
36pub enum Event {}
37
38pub fn init(cx: &mut AppContext) {
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) -> AnyElement<Self>>(
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(mut self, f: impl 'static + FnMut(&mut AppContext) -> SelectStyle) -> Self {
61 self.build_style = Some(Box::new(f));
62 self
63 }
64
65 pub fn set_item_count(&mut self, count: usize, cx: &mut ViewContext<Self>) {
66 self.item_count = count;
67 cx.notify();
68 }
69
70 fn toggle(&mut self, _: &ToggleSelect, cx: &mut ViewContext<Self>) {
71 self.is_open = !self.is_open;
72 cx.notify();
73 }
74
75 fn select_item(&mut self, action: &SelectItem, cx: &mut ViewContext<Self>) {
76 self.selected_item_ix = action.0;
77 self.is_open = false;
78 cx.notify();
79 }
80
81 pub fn selected_index(&self) -> usize {
82 self.selected_item_ix
83 }
84}
85
86impl Entity for Select {
87 type Event = Event;
88}
89
90impl View for Select {
91 fn ui_name() -> &'static str {
92 "Select"
93 }
94
95 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
96 if self.item_count == 0 {
97 return Empty::new().into_any();
98 }
99
100 enum Header {}
101 enum Item {}
102
103 let style = if let Some(build_style) = self.build_style.as_mut() {
104 (build_style)(cx)
105 } else {
106 Default::default()
107 };
108 let mut result = Flex::column().with_child(
109 MouseEventHandler::new::<Header, _>(self.handle.id(), cx, |mouse_state, cx| {
110 (self.render_item)(
111 self.selected_item_ix,
112 ItemType::Header,
113 mouse_state.hovered(),
114 cx,
115 )
116 .contained()
117 .with_style(style.header)
118 })
119 .on_click(MouseButton::Left, move |_, this, cx| {
120 this.toggle(&Default::default(), cx);
121 }),
122 );
123 if self.is_open {
124 result.add_child(Overlay::new(
125 UniformList::new(
126 self.list_state.clone(),
127 self.item_count,
128 cx,
129 move |this, mut range, items, cx| {
130 let selected_item_ix = this.selected_item_ix;
131 range.end = range.end.min(this.item_count);
132 items.extend(range.map(|ix| {
133 MouseEventHandler::new::<Item, _>(ix, cx, |mouse_state, cx| {
134 (this.render_item)(
135 ix,
136 if ix == selected_item_ix {
137 ItemType::Selected
138 } else {
139 ItemType::Unselected
140 },
141 mouse_state.hovered(),
142 cx,
143 )
144 })
145 .on_click(MouseButton::Left, move |_, this, cx| {
146 this.select_item(&SelectItem(ix), cx);
147 })
148 .into_any()
149 }))
150 },
151 )
152 .constrained()
153 .with_max_height(200.)
154 .contained()
155 .with_style(style.menu),
156 ));
157 }
158 result.into_any()
159 }
160}