1use super::{Decorators, v_stack_from_iter};
2use crate::context::StyleCx;
3use crate::event::EventPropagation;
4use crate::id::ViewId;
5use crate::style::Style;
6use crate::style_class;
7use crate::view::IntoView;
8use crate::{
9 event::{Event, EventListener},
10 view::View,
11};
12use floem_reactive::{Effect, RwSignal, SignalGet, SignalUpdate};
13use ui_events::keyboard::{Key, KeyState, KeyboardEvent, NamedKey};
14
15style_class!(pub ListClass);
16style_class!(pub ListItemClass);
17
18enum ListUpdate {
19 SelectionChanged(Option<usize>),
20 Accept,
21}
22
23pub(crate) struct Item {
24 pub(crate) id: ViewId,
25 pub(crate) index: usize,
26 pub(crate) selection: RwSignal<Option<usize>>,
27 pub(crate) child: ViewId,
28}
29
30pub struct List {
32 id: ViewId,
33 selection: RwSignal<Option<usize>>,
34 onaccept: Option<Box<dyn Fn(Option<usize>)>>,
35 child: ViewId,
36}
37
38impl List {
39 pub fn selection(&self) -> RwSignal<Option<usize>> {
41 self.selection
42 }
43
44 pub fn on_select(self, on_select: impl Fn(Option<usize>) + 'static) -> Self {
46 Effect::new(move |_| {
47 let selection = self.selection.get();
48 on_select(selection);
49 });
50 self
51 }
52
53 pub fn on_accept(mut self, on_accept: impl Fn(Option<usize>) + 'static) -> Self {
55 self.onaccept = Some(Box::new(on_accept));
56 self
57 }
58}
59
60pub fn list<V>(iterator: impl IntoIterator<Item = V>) -> List
76where
77 V: IntoView + 'static,
78{
79 let list_id = ViewId::new();
80 let selection = RwSignal::new(Some(0));
81 Effect::new(move |old_idx: Option<Option<usize>>| {
82 let selection = selection.get();
83 list_id.update_state(ListUpdate::SelectionChanged(old_idx.flatten()));
84 selection
85 });
86 let stack = v_stack_from_iter(iterator.into_iter().enumerate().map(move |(index, v)| {
87 let id = ViewId::new();
88 let v = v.into_view().class(ListItemClass);
89 let child = v.id();
90 id.set_children([v]);
91 Item {
92 id,
93 selection,
94 index,
95 child,
96 }
97 .on_click_stop(move |_| {
98 if selection.get_untracked() != Some(index) {
99 selection.set(Some(index));
100 list_id.update_state(ListUpdate::Accept);
101 }
102 })
103 }))
104 .style(|s| s.width_full().height_full());
105 let length = stack.id().children().len();
106 let child = stack.id();
107 list_id.set_children([stack]);
108 List {
109 id: list_id,
110 selection,
111 child,
112 onaccept: None,
113 }
114 .on_event(EventListener::KeyDown, move |e| {
115 if let Event::Key(KeyboardEvent {
116 state: KeyState::Down,
117 key,
118 ..
119 }) = e
120 {
121 match key {
122 Key::Named(NamedKey::Home) => {
123 if length > 0 {
124 selection.set(Some(0));
125 }
126 EventPropagation::Stop
127 }
128 Key::Named(NamedKey::End) => {
129 if length > 0 {
130 selection.set(Some(length - 1));
131 }
132 EventPropagation::Stop
133 }
134 Key::Named(NamedKey::ArrowUp) => {
135 let current = selection.get_untracked();
136 match current {
137 Some(i) => {
138 if i > 0 {
139 selection.set(Some(i - 1));
140 }
141 }
142 None => {
143 if length > 0 {
144 selection.set(Some(length - 1));
145 }
146 }
147 }
148 EventPropagation::Stop
149 }
150 Key::Named(NamedKey::Enter) => {
151 list_id.update_state(ListUpdate::Accept);
152 EventPropagation::Stop
153 }
154 Key::Character(c) if c == " " => {
155 list_id.update_state(ListUpdate::Accept);
156 EventPropagation::Stop
157 }
158 Key::Named(NamedKey::ArrowDown) => {
159 let current = selection.get_untracked();
160 match current {
161 Some(i) => {
162 if i < length - 1 {
163 selection.set(Some(i + 1));
164 }
165 }
166 None => {
167 if length > 0 {
168 selection.set(Some(0));
169 }
170 }
171 }
172 EventPropagation::Stop
173 }
174 _ => EventPropagation::Continue,
175 }
176 } else {
177 EventPropagation::Continue
178 }
179 })
180 .class(ListClass)
181}
182
183impl View for List {
184 fn id(&self) -> ViewId {
185 self.id
186 }
187
188 fn update(&mut self, _cx: &mut crate::context::UpdateCx, state: Box<dyn std::any::Any>) {
189 if let Ok(change) = state.downcast::<ListUpdate>() {
190 match *change {
191 ListUpdate::SelectionChanged(old_idx) => {
192 if let Some(old_idx) = old_idx {
193 let child = self.child.children()[old_idx];
194 child.request_style_recursive();
195 }
196 if let Some(index) = self.selection.get_untracked() {
197 let child = self.child.children()[index];
198 child.request_style_recursive();
199 child.scroll_to(None);
200 }
201 }
202 ListUpdate::Accept => {
203 if let Some(on_accept) = &self.onaccept {
204 on_accept(self.selection.get_untracked());
205 }
206 }
207 }
208 }
209 }
210}
211
212impl View for Item {
213 fn id(&self) -> ViewId {
214 self.id
215 }
216
217 fn view_style(&self) -> Option<crate::style::Style> {
218 Some(Style::new().flex_col())
219 }
220
221 fn debug_name(&self) -> std::borrow::Cow<'static, str> {
222 "List Item".into()
223 }
224
225 fn style_pass(&mut self, cx: &mut StyleCx<'_>) {
226 let selected = self.selection.get_untracked();
227 if Some(self.index) == selected {
228 cx.save();
229 cx.selected();
230 cx.style_view(self.child);
231 cx.restore();
232 } else {
233 cx.style_view(self.child);
234 }
235 }
236}
237
238pub trait ListExt {
241 fn list(self) -> List;
242}
243impl<V: IntoView + 'static, T: IntoIterator<Item = V> + 'static> ListExt for T {
244 fn list(self) -> List {
245 list(self)
246 }
247}