makepad_widgets/
portal_list2.rs

1use crate::{
2    widget::*,
3    makepad_derive_widget::*,
4    makepad_draw::*,
5    scroll_bar::{ScrollBar, ScrollBarAction},
6    portal_list::PortalListAction
7};
8
9live_design!{
10    link widgets;
11    use link::theme::*;
12    use link::shaders::*;
13    use crate::scroll_bar::ScrollBar;
14    
15    pub PortalList2Base = {{PortalList2}} {}
16    pub PortalList2 = <PortalList2Base> {
17        width: Fill, height: Fill,
18        capture_overload: true
19        scroll_bar: <ScrollBar> {}
20        flow: Down
21    }
22}
23
24/// The maximum number of items that will be shown as part of a smooth scroll animation.
25const SMOOTH_SCROLL_MAXIMUM_WINDOW: usize = 20;
26
27#[derive(Clone,Copy)]
28struct ScrollSample{
29    abs: f64,
30    time: f64,
31}
32
33enum ScrollState {
34    Stopped,
35    Drag{samples:Vec<ScrollSample>},
36    Flick {delta: f64, next_frame: NextFrame},
37    Pulldown {next_frame: NextFrame},
38    ScrollingTo {target_id: usize, delta: f64, next_frame: NextFrame},
39}
40
41#[derive(Clone, Debug)]
42enum DrawDirection {
43    Up,
44    Down
45}
46
47#[allow(unused)]
48#[derive(Clone, Debug)]
49enum ListDrawState {
50    BeginItem {index: usize, pos: f64, viewport: Rect, direction:DrawDirection, min:usize, max:usize},
51    EndItem {index: usize, size:Option<f64>, pos: f64,direction:DrawDirection, viewport: Rect, min:usize, max:usize},
52    Ended {viewport: Rect, min:usize, max:usize}
53}
54
55#[derive(Clone, Debug, DefaultNone)]
56pub enum PortalList2Action {
57    Scroll,
58    SmoothScrollReached,
59    None
60}
61
62impl ListDrawState {
63}
64
65#[derive(Live, Widget)]
66pub struct PortalList2 {
67    #[redraw] #[rust] area: Area,
68    #[walk] walk: Walk,
69    #[layout] layout: Layout,
70    
71    #[rust] range_start: usize,
72    #[rust(usize::MAX)] range_end: usize,
73    
74    #[rust(0usize)] view_window: usize,
75    #[rust(0usize)] visible_items: usize,
76        
77    #[live(0.2)] flick_scroll_minimum: f64,
78    #[live(80.0)] flick_scroll_maximum: f64,
79    #[live(0.005)] flick_scroll_scaling: f64,
80    #[live(0.98)] flick_scroll_decay: f64,
81        
82    #[live(100.0)] max_pull_down: f64,
83    
84    #[live(false)] grab_key_focus: bool,
85    #[live] capture_overload: bool,
86    #[live(true)] drag_scrolling: bool,
87    
88    #[live(false)] auto_tail: bool,
89    #[rust(false)] tail_range: bool,
90    #[rust(false)] at_end: bool,
91    #[rust(true)] not_filling_viewport: bool,
92    #[rust] detect_tail_in_draw: bool,
93
94    #[rust] first_id: usize,
95    #[rust] first_scroll: f64,
96    
97    #[rust(Vec2Index::X)] vec_index: Vec2Index,
98    #[live] scroll_bar: ScrollBar,
99    
100    #[rust] draw_state: DrawStateWrap<ListDrawState>,
101    #[rust] templates: ComponentMap<LiveId, LivePtr>,
102    
103    #[rust] items: ComponentMap<usize, WidgetItem>,
104    #[rust] reusable_items: Vec<WidgetItem>,
105    #[rust] draw_lists: ComponentMap<usize, WidgetDrawList>,
106    
107    //#[rust(DragState::None)] drag_state: DragState,
108    #[rust(ScrollState::Stopped)] scroll_state: ScrollState
109}
110
111struct WidgetItem{
112    widget: WidgetRef,
113    template: LiveId,
114}
115
116struct WidgetDrawList{
117    draw_list: DrawList2d,
118    area: Area
119}
120
121impl LiveHook for PortalList2 {
122    fn before_apply(&mut self, _cx: &mut Cx, apply: &mut Apply, _index: usize, _nodes: &[LiveNode]) {
123        if let ApplyFrom::UpdateFromDoc {..} = apply.from {
124            self.templates.clear();
125        }
126    }
127    
128    // hook the apply flow to collect our templates and apply to instanced childnodes
129    fn apply_value_instance(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) -> usize {
130        if nodes[index].is_instance_prop() {
131            if let Some(live_ptr) = apply.from.to_live_ptr(cx, index){
132                let id = nodes[index].id;
133                self.templates.insert(id, live_ptr);
134                // lets apply this thing over all our childnodes with that template
135                for (_, item) in self.items.iter_mut() {
136                    if item.template == id {
137                        item.widget.apply(cx, apply, index, nodes);
138                    }
139                }
140            }
141        }
142        else {
143            cx.apply_error_no_matching_field(live_error_origin!(), index, nodes);
144        }
145        nodes.skip_node(index)
146    }
147    
148    fn after_apply(&mut self, _cx: &mut Cx, _applyl: &mut Apply, _index: usize, _nodes: &[LiveNode]) {
149        if let Flow::Down = self.layout.flow {
150            self.vec_index = Vec2Index::Y
151        }
152        else {
153            self.vec_index = Vec2Index::X
154        }
155    }
156}
157
158impl PortalList2 {
159    
160    fn begin(&mut self, cx: &mut Cx2d, walk: Walk)->bool{
161        
162        if let Some(state) = self.draw_state.begin_state(cx){
163            cx.begin_turtle(walk, self.layout);
164            let viewport = cx.turtle().padded_rect();
165            *state = Some(ListDrawState::BeginItem{
166                min: self.first_id,
167                max: self.first_id,
168                index: self.first_id,
169                pos: self.first_scroll,
170                viewport,
171                direction: DrawDirection::Down
172            });
173            true
174        }
175        else{
176            false
177        }
178    }
179    
180    fn end(&mut self, cx: &mut Cx2d) {
181        cx.end_turtle_with_area(&mut self.area);
182        if let Some(ListDrawState::Ended{min, max, viewport:_}) = self.draw_state.get(){
183            for _id in min..max{
184            }
185        }
186        else{
187            panic!()
188        }
189    }
190    
191    fn begin_item(&mut self, cx:&mut Cx2d,  id:usize, viewport:Rect)->Option<f64>{
192        let vi = self.vec_index;
193        let layout = if vi == Vec2Index::Y { 
194            Layout::flow_down()
195        } else { 
196            Layout::flow_right()
197        };
198        let size =  cx.turtle().padded_rect().size.index(vi);
199        
200        // alright lets look the items drawlist up
201        let dl = if let Some(dl) = self.draw_lists.get_mut(&id){
202            if !cx.will_redraw_check_axis(&mut dl.draw_list, size, vi){
203                // lets emit the drawlist and not redraw it
204                cx.append_sub_draw_list(&dl.draw_list);
205                // return the height of the previous drawlist.
206                return Some(dl.area.rect(cx).size.index(vi))
207            }
208            dl
209        }
210        else{
211            self.draw_lists.insert(id, WidgetDrawList{
212                draw_list: DrawList2d::new(cx),
213                area: Area::Empty,
214            });
215            self.draw_lists.get_mut(&id).unwrap()
216        };
217        // lets begin drawlist,
218        dl.draw_list.begin_always(cx);
219        match vi {
220            Vec2Index::Y => {
221                cx.begin_turtle(Walk {
222                    abs_pos: Some(dvec2(viewport.pos.x, viewport.pos.y)),
223                    margin: Default::default(),
224                    width: Size::Fill,
225                    height: Size::Fit
226                }, layout);
227            }
228            Vec2Index::X => {
229                cx.begin_turtle(Walk {
230                    abs_pos: Some(dvec2(viewport.pos.x , viewport.pos.y)),
231                    margin: Default::default(),
232                    width: Size::Fit,
233                    height: Size::Fill
234                }, layout);
235            }
236        }
237        None
238    }
239    
240    fn end_item(&mut self, cx:&mut Cx2d, id: usize)->f64{
241        // ok lets end an item
242        let dl = self.draw_lists.get_mut(&id).unwrap();
243        let rect = cx.end_turtle_with_area(&mut dl.area);
244        dl.draw_list.end(cx);
245        let vi = self.vec_index;
246        rect.size.index(vi)
247    }
248    
249    /// Returns the index of the next visible item that will be drawn by this PortalList.
250    pub fn next_visible_item(&mut self, cx: &mut Cx2d) -> Option<usize> {
251        let vi = self.vec_index;
252        //let layout = if vi == Vec2Index::Y { Layout::flow_down() } else { Layout::flow_right() };
253        if let Some(draw_state) = self.draw_state.get() {
254            match draw_state {
255                ListDrawState::BeginItem{index, pos, viewport, direction, min, max} => {
256                    let size = self.begin_item(cx, index, viewport);
257                    self.draw_state.set(ListDrawState::EndItem {
258                        min: min.min(index),
259                        max: max.max(index),
260                        index,
261                        pos,
262                        direction,
263                        viewport,
264                        size
265                    });
266                    if size.is_none(){
267                        return Some(index);
268                    }
269                    else {
270                        return self.next_visible_item(cx);
271                    }
272                }
273                ListDrawState::EndItem {index, pos, viewport, size, direction, min, max}  => {
274                    
275                    let size = if let Some(size) = size{
276                        size
277                    }
278                    else{
279                        self.end_item(cx, index)
280                    };
281                    if size == 0.0{ // terminate, possible infinite loop
282                        //error!("Can't use 0.0 size items in portal list");
283                         //return None
284                    }
285                    match direction{
286                        DrawDirection::Down=>{
287                            let next_pos = pos + size;
288                            if next_pos >= viewport.size.index(vi){
289                                if self.first_id > 0{
290                                    self.draw_state.set(ListDrawState::BeginItem {
291                                        index: self.first_id - 1,
292                                        pos: self.first_scroll,
293                                        direction:DrawDirection::Up,
294                                        viewport,
295                                        min,
296                                        max
297                                    });
298                                    return self.next_visible_item(cx);
299                                }
300                                else{
301                                    self.draw_state.set(ListDrawState::Ended{
302                                        viewport,
303                                        min,
304                                        max
305                                    });
306                                    return None
307                                }
308                            }
309                            else{
310                                self.draw_state.set(ListDrawState::BeginItem {
311                                    index: index + 1,
312                                    pos: next_pos,
313                                    min,
314                                    max,
315                                    direction:DrawDirection::Down,
316                                    viewport,
317                                });
318                                return self.next_visible_item(cx)
319                            }
320                        }
321                        DrawDirection::Up=>{
322                            let next_pos = pos - size;
323                            if next_pos < 0.0 || index == 0{
324                                self.draw_state.set(ListDrawState::Ended{
325                                    viewport,
326                                    min,
327                                    max
328                                });
329                                return None
330                            }
331                            else{
332                                self.draw_state.set(ListDrawState::BeginItem {
333                                    index: index - 1,
334                                    pos: next_pos,
335                                    min,
336                                    max,
337                                    direction:DrawDirection::Up,
338                                    viewport,
339                                });
340                                return self.next_visible_item(cx)
341                            }
342                        }
343                    }
344                }
345                _ => ()
346            }
347        }
348        None
349    }
350    
351    /// Creates a new widget from the given `template` or returns an existing widget,
352    /// if one already exists with the same `entry_id`.
353    ///
354    /// If you care whether the widget already existed or not, use [`PortalList::item_with_existed()`] instead.
355    ///
356    /// ## Return
357    /// * If a widget already existed for the given `entry_id` and `template`,
358    ///   this returns a reference to that widget.
359    /// * If a new widget was created successfully, this returns a reference to that new widget.
360    /// * If the given `template` could not be found, this returns `None`.
361    pub fn item(&mut self, cx: &mut Cx, entry_id: usize, template: LiveId) -> WidgetRef {
362        self.item_with_existed(cx, entry_id, template).0
363    }
364    
365    /// Creates a new widget from the given `template` or returns an existing widget,
366    /// if one already exists with the same `entry_id` and `template`.
367    ///
368    /// * If you only want to check whether the item already existed without creating one,
369    ///   use [`PortalList::get_item()`] instead.
370    /// * If you don't care whether the widget already existed or not, use [`PortalList::item()`] instead.
371    ///
372    /// ## Return
373    /// * If a widget of the same `template` already existed for the given `entry_id`,
374    ///   this returns a tuple of that widget and `true`.
375    /// * If a new widget was created successfully, either because an item with the given `entry_id`
376    ///   did not exist or because the existing item with the given `entry_id` did not use the given `template`,
377    ///   this returns a tuple of that widget and `false`.
378    /// * If the given `template` could not be found, this returns `None`.
379    pub fn item_with_existed(&mut self, cx: &mut Cx, entry_id: usize, template: LiveId) -> (WidgetRef, bool) {
380        use std::collections::hash_map::Entry;
381        if let Some(ptr) = self.templates.get(&template) {
382            match self.items.entry(entry_id) {
383                Entry::Occupied(mut occ) => {
384                    if occ.get().template == template {
385                        (occ.get().widget.clone(), true)
386                    } else {
387                        let widget_ref =  if let Some(pos) = self.reusable_items.iter().position(|v| v.template == template){
388                            self.reusable_items.remove(pos).widget
389                        }
390                        else{
391                            WidgetRef::new_from_ptr(cx, Some(*ptr))
392                        };
393                        occ.insert(WidgetItem{
394                            template, 
395                            widget:widget_ref.clone(),
396                        });
397                        (widget_ref, false)
398                    }
399                }
400                Entry::Vacant(vac) => {
401                    let widget_ref =  if let Some(pos) = self.reusable_items.iter().position(|v| v.template == template){
402                        self.reusable_items.remove(pos).widget
403                    }
404                    else{
405                        WidgetRef::new_from_ptr(cx, Some(*ptr))
406                    };
407                    vac.insert(WidgetItem{
408                        template, 
409                        widget: widget_ref.clone(),
410                    });
411                    (widget_ref, false)
412                }
413            }
414        } else {
415            warning!("Template not found: {template}. Did you add it to the <PortalList> instance in `live_design!{{}}`?");
416            (WidgetRef::empty(), false)
417        }
418    }
419    
420    /// Returns the "start" position of the item with the given `entry_id`
421    /// relative to the "start" position of the PortalList.
422    ///
423    /// * For vertical lists, the start position is the top of the item
424    ///   relative to the top of the PortalList.
425    /// * For horizontal lists, the start position is the left side of the item
426    ///   relative to the left side of the PortalList.
427    ///
428    /// Returns `None` if the item with the given `entry_id` does not exist
429    /// or if the item's area rectangle is zero.
430    ///
431    /// TODO: FIXME: this may not properly handle bottom-up lists
432    ///              or lists that go from right to left.
433    pub fn position_of_item(&self, cx: &Cx, entry_id: usize) -> Option<f64> {
434        const ZEROED: Rect = Rect { pos: DVec2 { x: 0.0, y: 0.0 }, size: DVec2 { x: 0.0, y: 0.0 } };
435        
436        if let Some(item) = self.items.get(&entry_id) {
437            let item_rect = item.widget.area().rect(cx);
438            if item_rect == ZEROED {
439                return None;
440            }
441            let self_rect = self.area.rect(cx);
442            if self_rect == ZEROED {
443                return None;
444            }
445            let vi = self.vec_index;
446            Some(item_rect.pos.index(vi) - self_rect.pos.index(vi))
447        } else {
448            None
449        }
450    }
451        
452    /// Returns a reference to the template and widget for the given `entry_id`.
453    pub fn get_item(&self, entry_id: usize) -> Option<(LiveId,WidgetRef)> {
454        if let Some(item) = self.items.get(&entry_id){
455            Some((item.template.clone(), item.widget.clone()))
456        }
457        else{
458            None
459        }
460    }
461        
462    pub fn set_item_range(&mut self, cx: &mut Cx, range_start: usize, range_end: usize) {
463        self.range_start = range_start;
464        if self.range_end != range_end {
465            self.range_end = range_end;
466            if self.tail_range{
467                self.first_id = self.range_end.max(1) - 1;
468                self.first_scroll = 0.0;
469            }
470            self.update_scroll_bar(cx);
471        }
472    }
473    
474    pub fn update_scroll_bar(&mut self, cx: &mut Cx) {
475        let scroll_pos = ((self.first_id - self.range_start) as f64 / ((self.range_end - self.range_start).max(self.view_window + 1) - self.view_window) as f64) * self.scroll_bar.get_scroll_view_total();
476        // move the scrollbar to the right 'top' position
477        self.scroll_bar.set_scroll_pos_no_action(cx, scroll_pos);
478    }
479    
480    fn delta_top_scroll(&mut self, cx: &mut Cx, delta: f64, clip_top: bool) {
481        if self.range_start == self.range_end{
482            self.first_scroll = 0.0
483        }
484        else{
485            self.first_scroll += delta;
486        }            
487        if self.first_id == self.range_start {
488            self.first_scroll = self.first_scroll.min(self.max_pull_down);
489        }
490        if self.first_id == self.range_start && self.first_scroll > 0.0 && clip_top {
491            self.first_scroll = 0.0;
492        }
493        self.update_scroll_bar(cx);
494    }
495
496    /// Returns `true` if currently at the end of the list, meaning that the lasat item
497    /// is visible in the viewport.
498    pub fn is_at_end(&self) -> bool {
499        self.at_end
500    }
501
502    /// Returns the number of items that are currently visible in the viewport,
503    /// including partially visible items.
504    pub fn visible_items(&self) -> usize {
505        self.visible_items
506    }
507
508    /// Returns `true` if this sanity check fails: the first item ID is within the item range.
509    ///
510    /// Returns `false` if the sanity check passes as expected.
511    pub fn fails_sanity_check_first_id_within_item_range(&self) -> bool {
512        !self.tail_range
513            && (self.first_id > self.range_end)
514    }
515}
516
517
518impl Widget for PortalList2 {
519
520    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
521        let uid = self.widget_uid();
522        
523        let mut scroll_to = None;
524        self.scroll_bar.handle_event_with(cx, event, &mut | _cx, action | {
525            // snap the scrollbar to a top-index with scroll_pos 0
526            if let ScrollBarAction::Scroll {scroll_pos, view_total, view_visible} = action {
527                scroll_to = Some((scroll_pos, scroll_pos+0.5 >= view_total - view_visible))
528            }
529        });
530        if let Some((scroll_to, at_end)) = scroll_to {
531            if at_end && self.auto_tail{
532                self.first_id = self.range_end.max(1) - 1;
533                self.first_scroll = 0.0;
534                self.tail_range = true;
535            }
536            else if self.tail_range {
537                self.tail_range = false;
538            }
539
540            self.first_id = ((scroll_to / self.scroll_bar.get_scroll_view_visible()) * self.view_window as f64) as usize;
541            self.first_scroll = 0.0;
542            cx.widget_action(uid, &scope.path, PortalListAction::Scroll);
543            self.area.redraw(cx);
544        }
545        
546        for item in self.items.values_mut() {
547            let item_uid = item.widget.widget_uid();
548            cx.group_widget_actions(uid, item_uid, |cx|{
549                item.widget.handle_event(cx, event, scope)
550            });
551        }
552        
553        match &mut self.scroll_state {
554            ScrollState::ScrollingTo {target_id, delta, next_frame} => {
555                if let Some(_) = next_frame.is_event(event) {
556                    let target_id = *target_id;
557
558                    let distance_to_target = target_id as isize - self.first_id as isize;
559                    let target_passed = distance_to_target.signum() == delta.signum() as isize;
560                    // check to see if we passed the target and fix it. this may happen if the delta is too high,
561                    // so we can just correct the first id, since the animation isn't being smooth anyways.
562                    if target_passed {
563                        self.first_id = target_id;
564                        self.area.redraw(cx);
565                    }
566
567                    let distance_to_target = target_id as isize - self.first_id as isize;
568
569                    // If the target is under first_id (its bigger than it), and end is reached,
570                    // first_id would never be the target, so we just take it as reached.
571                    let target_visible_at_end = self.at_end && target_id > self.first_id;
572                    let target_reached = distance_to_target == 0 || target_visible_at_end;
573
574                    if !target_reached {
575                        *next_frame = cx.new_next_frame();
576                        let delta = *delta;
577
578                        self.delta_top_scroll(cx, delta, true);
579                        cx.widget_action(uid, &scope.path, PortalListAction::Scroll);
580
581                        self.area.redraw(cx);
582                    } else {
583                        self.scroll_state = ScrollState::Stopped;
584                        cx.widget_action(uid, &scope.path, PortalListAction::SmoothScrollReached);
585                    }
586                }
587            }
588            ScrollState::Flick {delta, next_frame} => {
589                if let Some(_) = next_frame.is_event(event) {
590                    *delta = *delta * self.flick_scroll_decay;
591                    if delta.abs()>self.flick_scroll_minimum {
592                        *next_frame = cx.new_next_frame();
593                        let delta = *delta;
594                        self.delta_top_scroll(cx, delta, true);
595                        cx.widget_action(uid, &scope.path, PortalListAction::Scroll);
596                        self.area.redraw(cx);
597                    } else {
598                        self.scroll_state = ScrollState::Stopped;
599                    }
600                }
601            }
602            ScrollState::Pulldown {next_frame} => {
603                if let Some(_) = next_frame.is_event(event) {
604                    // we have to bounce back
605                    if self.first_id == self.range_start && self.first_scroll > 0.0 {
606                        self.first_scroll *= 0.9;
607                        if self.first_scroll < 1.0 {
608                            self.first_scroll = 0.0;
609                        }
610                        else {
611                            *next_frame = cx.new_next_frame();
612                            cx.widget_action(uid, &scope.path, PortalListAction::Scroll);
613                        }
614                        self.area.redraw(cx);
615                    }
616                    else {
617                        self.scroll_state = ScrollState::Stopped
618                    }
619                }
620            }
621            _=>()
622        }
623        let vi = self.vec_index;
624        let is_scroll = if let Event::Scroll(_) = event {true} else {false};
625        if self.scroll_bar.is_area_captured(cx){
626            self.scroll_state = ScrollState::Stopped;
627        }
628        if !self.scroll_bar.is_area_captured(cx) || is_scroll{ 
629            match event.hits_with_capture_overload(cx, self.area, self.capture_overload) {
630                Hit::FingerScroll(e) => {
631                    if self.tail_range {
632                        self.tail_range = false;
633                    }
634                    self.detect_tail_in_draw = true;
635                    self.scroll_state = ScrollState::Stopped;
636                    self.delta_top_scroll(cx, -e.scroll.index(vi), true);
637                    cx.widget_action(uid, &scope.path, PortalListAction::Scroll);
638                    self.area.redraw(cx);
639                },
640                
641                Hit::KeyDown(ke) => match ke.key_code {
642                    KeyCode::Home => {
643                        self.first_id = 0;
644                        self.first_scroll = 0.0;
645                        self.tail_range = false;
646                        self.update_scroll_bar(cx);
647                        self.area.redraw(cx);
648                    },
649                    KeyCode::End => {
650                        self.first_id = self.range_end.max(1) - 1;
651                        self.first_scroll = 0.0;
652                        if self.auto_tail {
653                            self.tail_range = true;
654                        }
655                        self.update_scroll_bar(cx);
656                        self.area.redraw(cx);
657                    },
658                    KeyCode::PageUp => {
659                        self.first_id = self.first_id.max(self.view_window) - self.view_window;
660                        self.first_scroll = 0.0;
661                        self.tail_range = false;
662                        self.update_scroll_bar(cx);
663                        self.area.redraw(cx);
664                    },
665                    KeyCode::PageDown => {
666                        self.first_id += self.view_window;
667                        self.first_scroll = 0.0;
668                        if self.first_id >= self.range_end.max(1) {
669                            self.first_id = self.range_end.max(1) - 1;
670                        }
671                        self.detect_tail_in_draw = true;
672                        self.update_scroll_bar(cx);
673                        self.area.redraw(cx);
674                    },
675                    KeyCode::ArrowDown => {
676                        self.first_id += 1;
677                        if self.first_id >= self.range_end.max(1) {
678                            self.first_id = self.range_end.max(1) - 1;
679                        }
680                        self.detect_tail_in_draw = true;
681                        self.first_scroll = 0.0;
682                        self.update_scroll_bar(cx);
683                        self.area.redraw(cx);
684                    },
685                    KeyCode::ArrowUp => {
686                        if self.first_id > 0 {
687                            self.first_id -= 1;
688                            if self.first_id < self.range_start {
689                                self.first_id = self.range_start;
690                            }
691                            self.first_scroll = 0.0;
692                            self.area.redraw(cx);
693                            self.tail_range = false;
694                            self.update_scroll_bar(cx);
695                        }
696                    },
697                    _ => ()
698                }
699                Hit::FingerDown(e) => {
700                    //log!("Finger down {} {}", e.time, e.abs);
701                    if self.grab_key_focus {
702                        cx.set_key_focus(self.area);
703                    }
704                    // ok so fingerdown eh.
705                    if self.tail_range {
706                        self.tail_range = false;
707                    }
708                    if self.drag_scrolling{
709                        self.scroll_state = ScrollState::Drag {
710                            samples: vec![ScrollSample{abs:e.abs.index(vi),time:e.time}]
711                        };
712                    }
713                }
714                Hit::FingerMove(e) => {
715                    //log!("Finger move {} {}", e.time, e.abs);
716                    cx.set_cursor(MouseCursor::Default);
717                    match &mut self.scroll_state {
718                        ScrollState::Drag {samples}=>{
719                            let new_abs = e.abs.index(vi);
720                            let old_sample = *samples.last().unwrap();
721                            samples.push(ScrollSample{abs:new_abs, time:e.time});
722                            if samples.len()>4{
723                                samples.remove(0);
724                            }
725                            self.delta_top_scroll(cx, new_abs - old_sample.abs, false);
726                            self.area.redraw(cx);
727                        }
728                        _=>()
729                    }
730                }
731                Hit::FingerUp(_e) => {
732                    //log!("Finger up {} {}", e.time, e.abs);
733                    match &mut self.scroll_state {
734                        ScrollState::Drag {samples}=>{
735                            // alright so we need to see if in the last couple of samples
736                            // we have a certain distance per time
737                            let mut last = None;
738                            let mut scaled_delta = 0.0;
739                            let mut total_delta = 0.0;
740                            for sample in samples.iter().rev(){
741                                if last.is_none(){
742                                    last = Some(sample);
743                                }
744                                else{
745                                    total_delta += last.unwrap().abs - sample.abs;
746                                    scaled_delta += (last.unwrap().abs - sample.abs) / (last.unwrap().time - sample.time)
747                                }
748                            }
749                            scaled_delta *= self.flick_scroll_scaling;
750                            if self.first_id == self.range_start && self.first_scroll > 0.0 {
751                                self.scroll_state = ScrollState::Pulldown {next_frame: cx.new_next_frame()};
752                            }
753                            else if total_delta.abs() > 10.0 && scaled_delta.abs() > self.flick_scroll_minimum{
754                                
755                                self.scroll_state = ScrollState::Flick {
756                                    delta: scaled_delta.min(self.flick_scroll_maximum).max(-self.flick_scroll_maximum),
757                                    next_frame: cx.new_next_frame()
758                                };
759                            }
760                            else{
761                                self.scroll_state = ScrollState::Stopped;
762                            }
763                        }
764                        _=>()
765                    }
766                    // ok so. lets check our gap from 'drag'
767                    // here we kinda have to take our last delta and animate it
768                }
769                Hit::KeyFocus(_) => {
770                }
771                Hit::KeyFocusLost(_) => {
772                }
773                _ => ()
774            }
775        }
776    }
777    
778    fn draw_walk(&mut self, cx: &mut Cx2d, _scope:&mut Scope, walk: Walk) -> DrawStep {
779        if self.begin(cx, walk) {
780            return DrawStep::make_step()
781        }
782        // ok so if we are
783        if let Some(_) = self.draw_state.get() {
784            self.end(cx);
785            self.draw_state.end();
786        }
787        DrawStep::done()
788    }
789}
790
791impl PortalList2Ref {
792    /// Sets the first item to be shown and its scroll offset.
793    ///
794    /// On the next draw pass, this PortalList will draw the item with the given `id`
795    /// as the first item in the list, and will set the *scroll offset*
796    /// (from the top of the viewport to the beginning of the first item)
797    /// to the given value `s`.
798    pub fn set_first_id_and_scroll(&self, id: usize, s: f64) {
799        if let Some(mut inner) = self.borrow_mut() {
800            inner.first_id = id;
801            inner.first_scroll = s;
802        }
803    }
804    
805    /// Sets the first item to be shown by this PortalList to the item with the given `id`.
806    pub fn set_first_id(&self, id: usize) {
807        if let Some(mut inner) = self.borrow_mut() {
808            inner.first_id = id;
809        }
810    }
811    
812    /// Returns the ID of the item currently shown as the first item in this PortalList.
813    pub fn first_id(&self) -> usize {
814        if let Some(inner) = self.borrow() {
815            inner.first_id
816        }
817        else {
818            0
819        }
820    }
821    
822    /// Enables whether the PortalList auto-tracks the last item in the list.
823    ///
824    /// If `true`, the PortalList will continually scroll to the last item in the list
825    /// automatically, as new items are added.
826    /// If `false`, the PortalList will not auto-scroll to the last item.
827    pub fn set_tail_range(&self, tail_range: bool) {
828        if let Some(mut inner) = self.borrow_mut() {
829            inner.tail_range = tail_range
830        }
831    }
832
833    /// See [`PortalList::is_at_end()`].
834    pub fn is_at_end(&self) -> bool {
835        let Some(inner) = self.borrow() else { return false };
836        inner.is_at_end()
837    }
838
839    /// See [`PortalList::visible_items()`].
840    pub fn visible_items(&self) -> usize {
841        let Some(inner) = self.borrow() else { return 0 };
842        inner.visible_items()
843    }
844
845    /// Returns whether the given `actions` contain an action indicating that this PortalList was scrolled.
846    pub fn scrolled(&self, actions: &Actions) -> bool {
847        if let PortalListAction::Scroll = actions.find_widget_action(self.widget_uid()).cast() {
848            return true;
849        }
850        false
851    }
852
853    /// Returns the current scroll offset of this PortalList.
854    ///
855    /// See [`PortalListRef::set_first_id_and_scroll()`] for more information.
856    pub fn scroll_position(&self) -> f64 {
857        let Some(inner) = self.borrow_mut() else { return 0.0 };
858        inner.first_scroll
859    }
860    
861    /// See [`PortalList::item()`].
862    pub fn item(&self, cx: &mut Cx, entry_id: usize, template: LiveId) -> WidgetRef {
863        if let Some(mut inner) = self.borrow_mut(){
864            inner.item(cx, entry_id, template)
865        }
866        else{
867            WidgetRef::empty()
868        }
869    }
870
871    /// See [`PortalList::item_with_existed()`].
872    pub fn item_with_existed(&self, cx: &mut Cx, entry_id: usize, template: LiveId) -> (WidgetRef, bool) {
873        if let Some(mut inner) = self.borrow_mut(){
874            inner.item_with_existed(cx, entry_id, template)
875        }
876        else{
877            (WidgetRef::empty(), false)
878        }
879    }
880
881    /// See [`PortalList::get_item()`].
882    pub fn get_item(&self, entry_id: usize) -> Option<(LiveId, WidgetRef)> {
883        let Some(inner) = self.borrow() else { return None };
884        inner.get_item(entry_id)
885    }
886    
887    pub fn position_of_item(&self, cx:&Cx, entry_id: usize) -> Option<f64>{
888        let Some(inner) = self.borrow() else { return None };
889        inner.position_of_item(cx, entry_id)
890    }
891    
892    pub fn items_with_actions(&self, actions: &Actions) -> ItemsWithActions {
893        let mut set = Vec::new();
894        self.items_with_actions_vec(actions, &mut set);
895        set
896    }
897    
898    fn items_with_actions_vec(&self, actions: &Actions, set: &mut ItemsWithActions) {
899        let uid = self.widget_uid();
900        if let Some(inner) = self.borrow() {
901            for action in actions {
902                if let Some(action) = action.as_widget_action(){
903                    if let Some(group) = &action.group{
904                        if group.group_uid == uid{
905                            for (item_id, item) in inner.items.iter() {
906                                if group.item_uid == item.widget.widget_uid(){
907                                    set.push((*item_id, item.widget.clone()))
908                                }
909                            }
910                        }
911                    }
912                }
913            }
914        }
915    }
916    
917    pub fn any_items_with_actions(&self, actions: &Actions)->bool {
918        let uid = self.widget_uid();
919        for action in actions {
920            if let Some(action) = action.as_widget_action(){
921                if let Some(group) = &action.group{
922                    if group.group_uid == uid{
923                        return true
924                    }
925                }
926            }
927        }
928        false
929    }
930
931    /// Initiates a smooth scrolling animation to the specified target item in the list.
932    ///
933    /// ## Arguments
934    /// * `target_id`: The ID (index) of the item to scroll to.
935    /// * `speed`: A positive floating-point value that controls the speed of the animation.
936    ///    The `speed` will always be treated as an absolute value, with the direction of the scroll
937    ///    (up or down) determined by whether `target_id` is above or below the current item.
938    /// * `max_items_to_show`: The maximum number of items to show during the scrolling animation.
939    ///    If `None`, the default value of 20 is used.
940    ///
941    /// ## Example
942    /// ```rust,ignore
943    /// // Scrolls to item 42 at speed 100.0, including at most 30 items in the scroll animation.
944    /// smooth_scroll_to(&mut cx, 42, 100.0, Some(30));
945    /// ```
946    pub fn smooth_scroll_to(&self, cx: &mut Cx, target_id: usize, speed: f64, max_items_to_show: Option<usize>) {
947        let Some(mut inner) = self.borrow_mut() else { return };
948        if inner.items.is_empty() { return };
949        if target_id < inner.range_start || target_id > inner.range_end { return };
950
951        let max_items_to_show = max_items_to_show.unwrap_or(SMOOTH_SCROLL_MAXIMUM_WINDOW);
952        let scroll_direction: f64;
953        let starting_id: Option<usize>;
954        if target_id > inner.first_id {
955            // Scrolling down to a larger item index
956            scroll_direction = -1.0;
957            starting_id = ((target_id - inner.first_id) > max_items_to_show)
958                .then_some(target_id - max_items_to_show);
959        } else {
960            // Scrolling up to a smaller item index
961            scroll_direction = 1.0;
962            starting_id = ((inner.first_id - target_id) > max_items_to_show)
963                .then_some(target_id + max_items_to_show);
964        };
965
966        // First, if the target_id was too far away, jump directly to a closer starting_id.
967        if let Some(start) = starting_id {
968            log!("smooth_scroll_to(): jumping from first ID {} to start ID {}", inner.first_id, start);
969            inner.first_id = start;
970        }
971        // Then, we kick off the actual smooth scroll process.
972        inner.scroll_state = ScrollState::ScrollingTo {
973            target_id,
974            delta: speed.abs() * scroll_direction as f64,
975            next_frame: cx.new_next_frame()
976        };
977    }
978
979    /// Returns the ID of the item that is currently being smoothly scrolled to, if any.
980    pub fn is_smooth_scrolling(&self) -> Option<usize> {
981        let Some(inner) = self.borrow_mut() else { return None };
982        if let ScrollState::ScrollingTo { target_id, .. } = inner.scroll_state {
983            Some(target_id)
984        } else {
985            None
986        }
987    }
988
989    /// Returns whether the given `actions` contain an action indicating that this PortalList completed
990    /// a smooth scroll, reaching the target.
991    pub fn smooth_scroll_reached(&self, actions: &Actions) -> bool {
992        if let PortalListAction::SmoothScrollReached = actions.find_widget_action(self.widget_uid()).cast() {
993            return true;
994        }
995        false
996    }
997
998    /// Trigger an scrolling animation to the end of the list
999    ///
1000    /// ## Arguments
1001    /// * `speed`: This value controls how fast the scrolling animation is.
1002    ///    Note: This number should be large enough to reach the end, so it is important to
1003    ///    test the passed number. TODO provide a better implementation to ensure that the end
1004    ///    is always reached, no matter the speed value.
1005    /// * `max_items_to_show`: The maximum number of items to show during the scrolling animation.
1006    ///    If `None`, the default value of 20 is used.
1007    pub fn smooth_scroll_to_end(&self, cx: &mut Cx, speed: f64, max_items_to_show: Option<usize>) {
1008        let Some(mut inner) = self.borrow_mut() else { return };
1009        if inner.items.is_empty() { return };
1010
1011        let starting_id = inner.range_end
1012            .saturating_sub(max_items_to_show.unwrap_or(SMOOTH_SCROLL_MAXIMUM_WINDOW))
1013            .max(inner.first_id); // don't start before the current first_id
1014
1015        // First, we jump directly to the starting_id.
1016        inner.first_id = starting_id;
1017        // Then, we kick off the actual scrolling process.
1018        inner.scroll_state = ScrollState::Flick {
1019            delta: -speed,
1020            next_frame: cx.new_next_frame()
1021        };
1022    }
1023
1024    /// It indicates if we have items not displayed towards the end of the list (below)
1025    /// For instance, it is useful to show or hide a "jump to the most recent" button
1026    /// on a chat messages list
1027    pub fn further_items_bellow_exist(&self) -> bool {
1028        let Some(inner) = self.borrow() else { return false };
1029        !(inner.at_end || inner.not_filling_viewport)
1030    }
1031}
1032
1033type ItemsWithActions = Vec<(usize, WidgetRef)>;
1034
1035impl PortalList2Set {
1036    pub fn set_first_id(&self, id: usize) {
1037        for list in self.iter() {
1038            list.set_first_id(id)
1039        }
1040    }
1041    
1042    pub fn items_with_actions(&self, actions: &Actions) -> ItemsWithActions {
1043        let mut set = Vec::new();
1044        for list in self.iter() {
1045            list.items_with_actions_vec(actions, &mut set)
1046        }
1047        set
1048    }
1049}