makepad_widget/
listlogic.rs

1use makepad_render::*;
2use crate::scrollview::*;
3
4#[derive(Clone, Default)]
5pub struct ListLogic {
6    pub list_items: Vec<ListItem>,
7    pub scroll_item_in_view: Option<usize>,
8    pub set_scroll_pos: Option<Vec2>,
9    //pub tail_list: bool,
10    pub start_item: usize,
11    pub end_item: usize,
12    pub end_fill: usize,
13    pub selection: Vec<usize>,
14    pub last_range: Option<(usize, usize)>,
15}
16
17#[derive(Clone)]
18pub struct ListItem {
19    pub animator: Animator,
20    pub is_selected: bool
21}
22
23pub enum ListLogicEvent {
24    Animate(AnimateEvent),
25    AnimEnded,
26    Select,
27    Deselect,
28    Cleanup,
29    Over,
30    Out
31}
32
33pub enum ListEvent {
34    SelectSingle(usize),
35    SelectMultiple,
36    None
37}
38
39#[derive(PartialEq)]
40pub enum ListSelect {
41    None,
42    Single(usize),
43    Range(usize),
44    Toggle(usize),
45    All
46}
47
48impl ListSelect {
49    pub fn item_index(&self) -> Option<usize> {
50        match self {
51            ListSelect::Single(index) => Some(*index),
52            _ => None
53        }
54    }
55}
56
57impl ListLogic {
58    pub fn set_list_len(&mut self, len: usize)
59    {
60        if self.list_items.len() < len {
61            for _ in self.list_items.len()..len {
62                self.list_items.push(ListItem {
63                    animator: Animator::default(),
64                    is_selected: false
65                })
66            }
67        }
68        else {
69            self.list_items.truncate(len);
70        }
71    }
72    
73    pub fn handle_list_scroll_bars(&mut self, cx: &mut Cx, event: &mut Event, view: &mut ScrollView)
74        -> bool {
75        if view.handle_scroll_bars(cx, event) {
76            view.redraw_view_area(cx);
77            match &event {
78                Event::FingerScroll {..} => {
79                    return true;
80                },
81                Event::FingerMove {..} => {
82                    return true;
83                },
84                _ => ()
85            }
86        }
87        return false
88    }
89    
90    pub fn begin_list(&mut self, cx: &mut Cx, view: &mut ScrollView, tail_list: bool, row_height: f32) -> Result<(), ()>
91    {
92        view.begin_view(cx, Layout {
93            direction: Direction::Down,
94            ..Layout::default()
95        }) ?;
96        
97        self.set_visible_range_and_scroll(cx, view, tail_list, row_height);
98        
99        Ok(())
100    }
101    
102    
103    pub fn walk_turtle_to_end(&mut self, cx: &mut Cx, row_height: f32) {
104        let left = (self.list_items.len() - self.end_item) as f32 * row_height;
105        cx.walk_turtle(Walk::wh(Width::Fill, Height::Fix(left)));
106    }
107    
108    
109    pub fn end_list(&mut self, cx: &mut Cx, view: &mut ScrollView) {
110        view.end_view(cx);
111        if let Some(set_scroll_pos) = self.set_scroll_pos {
112            view.set_scroll_pos(cx, set_scroll_pos);
113        }
114    }
115    
116    pub fn set_visible_range_and_scroll(&mut self, cx: &mut Cx, view: &mut ScrollView, tail_list: bool, row_height: f32) {
117        let view_rect = cx.get_turtle_rect();
118        
119        // the maximum scroll position given the amount of log items
120        let max_scroll_y = ((self.list_items.len() + 1) as f32 * row_height - view_rect.h).max(0.);
121        
122        // tail the log
123        let (scroll_pos, set_scroll_pos) = if tail_list {
124            (Vec2 {x: 0., y: max_scroll_y}, true)
125        }
126        else {
127            let sp = view.get_scroll_pos(cx);
128            
129            // scroll item into view
130            if let Some(scroll_item_in_view) = self.scroll_item_in_view {
131                self.scroll_item_in_view = None;
132                let item_y = scroll_item_in_view as f32 * row_height;
133                let dy = (item_y + row_height) - (sp.y + view_rect.h);
134                if item_y < sp.y {
135                    (Vec2 {x: 0., y: item_y}, true)
136                }
137                else if dy > 0. {
138                    (Vec2 {x: 0., y: sp.y + dy}, true)
139                }
140                else {
141                    (sp, false)
142                }
143            }
144            else {
145                // clamp the scrollbar to our max list size
146                if sp.y > max_scroll_y {
147                    (Vec2 {x: 0., y: max_scroll_y}, false)
148                }
149                else {
150                    (sp, false)
151                }
152            }
153        };
154        
155        let start_item = (scroll_pos.y / row_height).floor() as usize;
156        let end_item = ((scroll_pos.y + view_rect.h + row_height) / row_height).ceil() as usize;
157        
158        self.start_item = start_item.min(self.list_items.len());
159        self.end_fill = end_item;
160        self.end_item = end_item.min(self.list_items.len());
161        
162        let start_scroll = (self.start_item as f32) * row_height;
163        if set_scroll_pos {
164            self.set_scroll_pos = Some(scroll_pos);
165        }
166        else {
167            self.set_scroll_pos = None;
168        }
169        // lets jump the turtle forward by scrollpos.y
170        cx.move_turtle(0., start_scroll);
171    }
172    
173    pub fn get_next_single_selection(&self) -> ListSelect {
174        if let Some(last) = self.selection.last() {
175            let next = last + 1;
176            if next >= self.list_items.len() { // wrap around
177                ListSelect::Single(0)
178            }
179            else {
180                ListSelect::Single(next)
181            }
182        }
183        else {
184            ListSelect::Single(0)
185        }
186    }
187    
188    pub fn get_prev_single_selection(&self) -> ListSelect {
189        if let Some(first) = self.selection.last() {
190            if *first == 0 { // wrap around
191                ListSelect::Single(self.list_items.len().max(1) - 1)
192            }
193            else {
194                ListSelect::Single(first - 1)
195            }
196        }
197        else {
198            ListSelect::Single(0)
199        }
200    }
201    
202    pub fn handle_list_logic<F>(&mut self, cx: &mut Cx, event: &mut Event, select: ListSelect, mut cb: F) -> ListEvent
203    where F: FnMut(&mut Cx, ListLogicEvent, &mut ListItem, usize)
204    {
205        let mut select = select;
206        
207        for counter in self.start_item..self.end_item {
208            if counter >= self.list_items.len() {
209                break;
210            }
211            let item = &mut self.list_items[counter];
212            match event.hits(cx, item.animator.area, HitOpt::default()) {
213                Event::Animate(ae) => {
214                    cb(cx, ListLogicEvent::Animate(ae), item, counter)
215                },
216                Event::AnimEnded(_) => {
217                    cb(cx, ListLogicEvent::AnimEnded, item, counter)
218                },
219                Event::FingerDown(fe) => {
220                    cx.set_down_mouse_cursor(MouseCursor::Hand);
221                    if fe.modifiers.logo || fe.modifiers.control {
222                        select = ListSelect::Toggle(counter)
223                    }
224                    else if fe.modifiers.shift {
225                        select = ListSelect::Range(counter)
226                    }
227                    else {
228                        select = ListSelect::Single(counter)
229                    }
230                },
231                Event::FingerUp(_fe) => {
232                },
233                Event::FingerMove(_fe) => {
234                },
235                Event::FingerHover(fe) => {
236                    cx.set_hover_mouse_cursor(MouseCursor::Hand);
237                    match fe.hover_state {
238                        HoverState::In => {
239                            cb(cx, ListLogicEvent::Over, item, counter);
240                        },
241                        HoverState::Out => {
242                            cb(cx, ListLogicEvent::Out, item, counter);
243                        },
244                        _ => ()
245                    }
246                },
247                _ => ()
248            }
249        };
250        // clean up outside of window
251        if let Some(last_range) = self.last_range {
252            for counter in last_range.0..last_range.1 {
253                if counter >= self.list_items.len() {
254                    break;
255                }
256                if counter < self.start_item || counter >= self.end_item {
257                    let dm = &mut self.list_items[counter];
258                    cb(cx, ListLogicEvent::Deselect, dm, counter);
259                }
260            }
261        }
262        self.last_range = Some((self.start_item, self.end_item));
263        
264        match select {
265            ListSelect::Range(select_index) => {
266                if let Some(first) = self.selection.first() {
267                    if let Some(last) = self.selection.last() {
268                        
269                        let (start, end) = if select_index < *first {
270                            (select_index, *last)
271                        }
272                        else if select_index > *last {
273                            (*first, select_index)
274                        }
275                        else {
276                            (select_index, select_index)
277                        };
278                        
279                        for counter in &self.selection {
280                            if *counter >= self.list_items.len() || *counter >= start && *counter <= end {
281                                continue;
282                            }
283                            let dm = &mut self.list_items[*counter];
284                            if *counter != select_index {
285                                dm.is_selected = false;
286                                cb(cx, ListLogicEvent::Deselect, dm, select_index);
287                            }
288                        }
289                        self.selection.truncate(0);
290                        for i in start..= end {
291                            let dm = &mut self.list_items[i];
292                            dm.is_selected = true;
293                            cb(cx, ListLogicEvent::Select, dm, i);
294                            self.selection.push(i);
295                        }
296                        
297                    }
298                }
299            },
300            ListSelect::Toggle(select_index) => {
301                let dm = &mut self.list_items[select_index];
302                if dm.is_selected {
303                    dm.is_selected = false;
304                    cb(cx, ListLogicEvent::Deselect, dm, select_index);
305                    if let Some(pos) = self.selection.iter().position( | v | *v == select_index) {
306                        self.selection.remove(pos);
307                    }
308                }
309                else {
310                    self.selection.push(select_index);
311                    dm.is_selected = true;
312                    cb(cx, ListLogicEvent::Over, dm, select_index);
313                }
314            },
315            ListSelect::All => {
316                self.selection.truncate(0);
317                for i in 0..self.list_items.len() {
318                    self.selection.push(i);
319                    let dm = &mut self.list_items[i];
320                    dm.is_selected = true;
321                    cb(cx, ListLogicEvent::Over, dm, i);
322                }
323            },
324            ListSelect::Single(select_index) => {
325                for counter in &self.selection {
326                    if *counter >= self.list_items.len() {
327                        continue;
328                    }
329                    let dm = &mut self.list_items[*counter];
330                    if *counter != select_index {
331                        dm.is_selected = false;
332                        cb(cx, ListLogicEvent::Cleanup, dm, *counter);
333                    }
334                }
335                self.selection.truncate(0);
336                if select_index < self.list_items.len() {
337                    self.selection.push(select_index);
338                    let dm = &mut self.list_items[select_index];
339                    dm.is_selected = true;
340                    cb(cx, ListLogicEvent::Over, dm, select_index);
341                    
342                    return ListEvent::SelectSingle(select_index)
343                }
344            },
345            _ => ()
346        }
347        match select {
348            ListSelect::Range(_) | ListSelect::Toggle(_) | ListSelect::All => {
349                ListEvent::SelectMultiple
350            },
351            _ => ListEvent::None
352        }
353        
354    }
355    
356}