makepad_widgets/
flat_list.rs

1
2use crate::{
3    widget::*,
4    makepad_derive_widget::*,
5    makepad_draw::*,
6    scroll_bars::{ScrollBars}
7};
8
9live_design!{
10    link widgets;
11    use link::theme::*;
12    use link::shaders::*;
13    use crate::scroll_bars::ScrollBars;
14        
15    pub FlatListBase = {{FlatList}} {}
16    pub FlatList = <FlatListBase> {
17        width: Fill, height: Fill,
18        capture_overload: true
19        scroll_bars: <ScrollBars> {show_scroll_x: false, show_scroll_y: true}
20        flow: Down
21    }
22    
23}
24/*
25#[derive(Clone,Copy)]
26struct ScrollSample{
27    abs: f64,
28    time: f64,
29}*/
30/*
31enum ScrollState {
32    Stopped,
33    Drag{samples:Vec<ScrollSample>},
34    Flick {delta: f64, next_frame: NextFrame},
35    Pulldown {next_frame: NextFrame},
36}
37*/
38#[derive(Clone, DefaultNone)]
39pub enum FlatListAction {
40    Scroll,
41    None
42}
43
44#[derive(Live, Widget)]
45pub struct FlatList {
46    //#[rust] area: Area,
47    #[walk] walk: Walk,
48    #[layout] layout: Layout,
49
50    #[live(0.2)] flick_scroll_minimum: f64,
51    #[live(80.0)] flick_scroll_maximum: f64,
52    #[live(0.005)] flick_scroll_scaling: f64,
53    #[live(0.98)] flick_scroll_decay: f64,
54    #[live(0.2)] swipe_drag_duration: f64,
55    #[live(100.0)] max_pull_down: f64,
56    #[live(true)] align_top_when_empty: bool,
57    #[live(false)] grab_key_focus: bool,
58    #[live(true)] drag_scrolling: bool,
59    
60    #[rust(Vec2Index::X)] vec_index: Vec2Index,
61    #[redraw] #[live] scroll_bars: ScrollBars,
62    #[live] capture_overload: bool,
63    #[rust] draw_state: DrawStateWrap<()>,
64    
65    #[rust] templates: ComponentMap<LiveId, LivePtr>,
66    #[rust] items: ComponentMap<LiveId, (LiveId,WidgetRef)>,
67    //#[rust(DragState::None)] drag_state: DragState,
68    /*#[rust(ScrollState::Stopped)] scroll_state: ScrollState*/
69}
70
71impl LiveHook for FlatList {
72            
73    fn before_apply(&mut self, _cx: &mut Cx, apply: &mut Apply, _index: usize, _nodes: &[LiveNode]) {
74        if let ApplyFrom::UpdateFromDoc {..} = apply.from {
75            self.templates.clear();
76        }
77    }
78    
79    // hook the apply flow to collect our templates and apply to instanced childnodes
80    fn apply_value_instance(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) -> usize {
81        if nodes[index].is_instance_prop() {
82            if let Some(live_ptr) = apply.from.to_live_ptr(cx, index){
83                let id = nodes[index].id;
84                self.templates.insert(id, live_ptr);
85                // lets apply this thing over all our childnodes with that template
86                for (templ_id, node) in self.items.values_mut() {
87                    if *templ_id == id {
88                        node.apply(cx, apply, index, nodes);
89                    }
90                }
91            }
92        }
93        else {
94            cx.apply_error_no_matching_field(live_error_origin!(), index, nodes);
95        }
96        nodes.skip_node(index)
97    }
98    
99    fn after_apply(&mut self, _cx: &mut Cx, _applyl: &mut Apply, _index: usize, _nodes: &[LiveNode]) {
100        if let Flow::Down = self.layout.flow {
101            self.vec_index = Vec2Index::Y
102        }
103        else {
104            self.vec_index = Vec2Index::X
105        }
106    }
107}
108
109impl FlatList {
110    
111    fn begin(&mut self, cx: &mut Cx2d, walk: Walk) {
112        self.scroll_bars.begin(cx, walk, self.layout);
113    }
114    
115    fn end(&mut self, cx: &mut Cx2d) {
116        self.scroll_bars.end(cx);
117    }
118
119    pub fn space_left(&self, cx:&mut Cx2d)->f64{
120        let view_total = cx.turtle().used();
121        let rect_now = cx.turtle().rect();
122        rect_now.size.y - view_total.y
123    }
124
125    pub fn item(&mut self, cx: &mut Cx, id: LiveId, template: LiveId) -> Option<WidgetRef> {
126        if let Some(ptr) = self.templates.get(&template) {
127            let (_, entry) = self.items.get_or_insert(cx, id, | cx | {
128                (template, WidgetRef::new_from_ptr(cx, Some(*ptr)))
129            });
130            Some(entry.clone())
131        }
132        else {
133            warning!("Template not found: {template}. Did you add it to the <FlatList> instance in `live_design!{{}}`?");
134            None
135        }
136    }
137
138    /*
139    fn delta_top_scroll(&mut self, cx: &mut Cx, delta: f64, clip_top: bool) {
140        self.first_scroll += delta;
141        if self.first_scroll > 0.0 && clip_top{
142            self.first_scroll = 0.0;
143        }
144        self.scroll_bar.set_scroll_pos_no_action(cx, self.first_scroll);
145    }*/
146}
147
148
149impl Widget for FlatList {
150    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
151
152        let uid = self.widget_uid();
153        self.scroll_bars.handle_event(cx, event, scope);
154        /*
155        let mut scroll_to = None;
156        self.scroll_bars.handle_event_with(cx, event, &mut | _cx, action | {
157            // snap the scrollbar to a top-index with scroll_pos 0
158            if let ScrollBarAction::Scroll {scroll_pos, view_total, view_visible} = action {
159                scroll_to = Some((scroll_pos, scroll_pos+0.5 >= view_total - view_visible))
160            }
161        });
162        */
163        for (item_id,item) in self.items.values_mut() {
164            let item_uid = item.widget_uid();
165            scope.with_id(*item_id, |scope|{
166                cx.group_widget_actions(uid, item_uid, |cx|{
167                    item.handle_event(cx, event, scope)
168                });
169            })
170        }
171        /*
172        match &mut self.scroll_state {
173            ScrollState::Flick {delta, next_frame} => {
174                if let Some(_) = next_frame.is_event(event) {
175                    *delta = *delta * self.flick_scroll_decay;
176                    if delta.abs()>self.flick_scroll_minimum {
177                        *next_frame = cx.new_next_frame();
178                        let delta = *delta;
179                        self.delta_top_scroll(cx, delta, true);
180                        dispatch_action(cx, FlatListAction::Scroll.into_action(uid));
181                        self.scroll_bars.redraw(cx);
182                    } else {
183                        self.scroll_state = ScrollState::Stopped;
184                    }
185                }
186            }
187            ScrollState::Pulldown {next_frame} => {
188                if let Some(_) = next_frame.is_event(event) {
189                    // we have to bounce back
190                    if self.first_scroll > 0.0 {
191                        self.first_scroll *= 0.9;
192                        if self.first_scroll < 1.0 {
193                            self.first_scroll = 0.0;
194                        }
195                        else {
196                            *next_frame = cx.new_next_frame();
197                            dispatch_action(cx, FlatListAction::Scroll.into_action(uid));
198                        }
199                        self.scroll_bars.redraw(cx);
200                    }
201                    else {
202                        self.scroll_state = ScrollState::Stopped
203                    }
204                }
205            }
206            _=>()
207        }*/
208        /*
209        let vi = self.vec_index;
210        let is_scroll = if let Event::Scroll(_) = event {true} else {false};
211        if self.scroll_bars.is_area_captured(cx){
212            self.scroll_state = ScrollState::Stopped;
213        }*/
214        /*
215        if !self.scroll_bars.is_area_captured(cx) || is_scroll{ 
216            match event.hits_with_capture_overload(cx, self.area, self.capture_overload) {
217                Hit::FingerScroll(e) => {
218                    self.scroll_state = ScrollState::Stopped;
219                    self.delta_top_scroll(cx, -e.scroll.index(vi), true);
220                    dispatch_action(cx, FlatListAction::Scroll.into_action(uid));
221                    self.area.redraw(cx);
222                },
223                
224                Hit::FingerDown(e) => {
225                    if self.grab_key_focus {
226                        cx.set_key_focus(self.area);
227                    }
228                    if self.drag_scrolling{
229                        self.scroll_state = ScrollState::Drag {
230                            samples: vec![ScrollSample{abs:e.abs.index(vi),time:e.time}]
231                        };
232                    }
233                }
234                Hit::FingerMove(e) => {
235                    //log!("Finger move {} {}", e.time, e.abs);
236                    cx.set_cursor(MouseCursor::Default);
237                    match &mut self.scroll_state {
238                        ScrollState::Drag {samples}=>{
239                            let new_abs = e.abs.index(vi);
240                            let old_sample = *samples.last().unwrap();
241                            samples.push(ScrollSample{abs:new_abs, time:e.time});
242                            if samples.len()>4{
243                                samples.remove(0);
244                            }
245                            self.delta_top_scroll(cx, new_abs - old_sample.abs, false);
246                            self.area.redraw(cx);
247                        }
248                        _=>()
249                    }
250                }
251                Hit::FingerUp(_e) => {
252                    //log!("Finger up {} {}", e.time, e.abs);
253                    match &mut self.scroll_state {
254                        ScrollState::Drag {samples}=>{
255                            // alright so we need to see if in the last couple of samples
256                            // we have a certain distance per time
257                            let mut last = None;
258                            let mut scaled_delta = 0.0;
259                            for sample in samples.iter().rev(){
260                                if last.is_none(){
261                                    last = Some(sample);
262                                }
263                                else{
264                                    scaled_delta += (last.unwrap().abs - sample.abs)/ (last.unwrap().time - sample.time)
265                                }
266                            }
267                            scaled_delta *= self.flick_scroll_scaling;
268                            if  self.first_scroll > 0.0 {
269                                self.scroll_state = ScrollState::Pulldown {next_frame: cx.new_next_frame()};
270                            }
271                            else if scaled_delta.abs() > self.flick_scroll_minimum{
272                                
273                                self.scroll_state = ScrollState::Flick {
274                                    delta: scaled_delta.min(self.flick_scroll_maximum).max(-self.flick_scroll_maximum),
275                                    next_frame: cx.new_next_frame()
276                                };
277                            }
278                            else{
279                                self.scroll_state = ScrollState::Stopped;
280                            }
281                        }
282                        _=>()
283                    }
284                    // ok so. lets check our gap from 'drag'
285                    // here we kinda have to take our last delta and animate it
286                }
287                Hit::KeyFocus(_) => {
288                }
289                Hit::KeyFocusLost(_) => {
290                }
291                _ => ()
292            }
293        }*/
294    }
295    
296    fn draw_walk(&mut self, cx: &mut Cx2d, _scope:&mut Scope, walk: Walk) -> DrawStep {
297        if self.draw_state.begin(cx, ()) {
298            self.begin(cx, walk);
299            return DrawStep::make_step()
300        }
301        self.end(cx);
302        self.draw_state.end();
303        DrawStep::done()
304    }
305}
306
307impl FlatListRef {
308   
309    pub fn item(&self, cx: &mut Cx, entry_id: LiveId, template: LiveId) -> Option<WidgetRef> {
310        if let Some(mut inner) = self.borrow_mut() {
311            inner.item(cx, entry_id, template)
312        }
313        else {
314            None
315        }
316    }
317    
318    pub fn items_with_actions(&self, actions: &Actions) -> Vec<(LiveId, WidgetRef)> {
319        let mut set = Vec::new();
320        self.items_with_actions_vec(actions, &mut set);
321        set
322    }
323    
324    fn items_with_actions_vec(&self, actions: &Actions, set: &mut Vec<(LiveId, WidgetRef)>) {
325        let uid = self.widget_uid();
326        for action in actions {
327            if let Some(action) = action.downcast_ref::<WidgetAction>(){
328                if let Some(group) = &action.group{
329                    if group.group_uid == uid{
330                        if let Some(inner) = self.borrow() {
331                            for (item_id, (_templ_id, item)) in inner.items.iter() {
332                                if group.item_uid == item.widget_uid(){
333                                    set.push((*item_id, item.clone()))
334                                }
335                            }
336                        }
337                    }
338                }
339            }
340        }
341    }
342}
343
344impl FlatListSet {
345    pub fn items_with_actions(&self, actions: &Actions) -> Vec<(LiveId, WidgetRef)> {
346        let mut set = Vec::new();
347        for list in self.iter() {
348            list.items_with_actions_vec(actions, &mut set)
349        }
350        set
351    }
352}