1use crate::{
2 widget::*,
3 makepad_derive_widget::*,
4 makepad_draw::*,
5 scroll_bar::{ScrollBar, ScrollAxis, ScrollBarAction}
6};
7
8live_design!{
9 link widgets;
10 use link::theme::*;
11 use link::shaders::*;
12 use crate::scroll_bar::ScrollBar;
13
14 pub PortalListBase = {{PortalList}} {}
15
16 pub PortalList = <PortalListBase> {
17 width: Fill, height: Fill,
18 capture_overload: true
19 scroll_bar: <ScrollBar> {}
20 flow: Down
21 }
22}
23
24const 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)]
42enum ListDrawState {
43 Begin,
44 Down {index: usize, pos: f64, viewport: Rect},
45 Up {index: usize, pos: f64, hit_bottom: bool, viewport: Rect},
46 DownAgain {index: usize, pos: f64, viewport: Rect},
47 End {viewport: Rect}
48}
49
50#[derive(Clone, Debug, DefaultNone)]
51pub enum PortalListAction {
52 Scroll,
53 SmoothScrollReached,
54 None
55}
56impl ListDrawState {
57 fn is_down_again(&self) -> bool {
58 match self {
59 Self::DownAgain {..} => true,
60 _ => false
61 }
62 }
63}
64#[derive(Live, Widget)]
65pub struct PortalList {
66 #[redraw] #[rust] area: Area,
67 #[walk] walk: Walk,
68 #[layout] layout: Layout,
69
70 #[rust] range_start: usize,
71 #[rust(usize::MAX)] range_end: usize,
72 #[rust(0usize)] view_window: usize,
73 #[rust(0usize)] visible_items: usize,
74 #[live(0.2)] flick_scroll_minimum: f64,
75 #[live(80.0)] flick_scroll_maximum: f64,
76 #[live(0.005)] flick_scroll_scaling: f64,
77 #[live(0.97)] flick_scroll_decay: f64,
78 #[live(80.0)] max_pull_down: f64,
79 #[live(true)] align_top_when_empty: bool,
80 #[live(false)] grab_key_focus: bool,
81 #[live(true)] drag_scrolling: bool,
82 #[rust] first_id: usize,
83 #[rust] first_scroll: f64,
84 #[rust(Vec2Index::X)] vec_index: Vec2Index,
85 #[live] scroll_bar: ScrollBar,
86 #[live] capture_overload: bool,
87 #[live(false)] keep_invisible: bool,
88 #[rust] draw_state: DrawStateWrap<ListDrawState>,
89 #[rust] draw_align_list: Vec<AlignItem>,
90 #[rust] detect_tail_in_draw: bool,
91 #[live(false)] auto_tail: bool,
92 #[live(false)] draw_caching: bool,
93
94 #[rust(false)] tail_range: bool,
95 #[rust(false)] at_end: bool,
96 #[rust(true)] not_filling_viewport: bool,
97 #[live(false)] reuse_items: bool,
98
99 #[rust] templates: ComponentMap<LiveId, LivePtr>,
100 #[rust] items: ComponentMap<usize, WidgetItem>,
101 #[rust] reusable_items: Vec<WidgetItem>,
102 #[rust] _draw_list_cache: Vec<DrawList>,
103
104 #[rust(ScrollState::Stopped)] scroll_state: ScrollState,
106 #[rust] was_scrolling: bool,
108}
109
110
111#[derive(Default)]
112struct WidgetItem{
113 widget: WidgetRef,
114 template: LiveId,
115}
116
117struct AlignItem {
118 align_range: TurtleAlignRange,
119 size: DVec2,
120 shift: f64,
121 index: usize
122}
123
124impl LiveHook for PortalList {
125 fn before_apply(&mut self, _cx: &mut Cx, apply: &mut Apply, _index: usize, _nodes: &[LiveNode]) {
126 if let ApplyFrom::UpdateFromDoc {..} = apply.from {
127 self.templates.clear();
128 }
129 }
130
131 fn apply_value_instance(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) -> usize {
133 if nodes[index].is_instance_prop() {
134 if let Some(live_ptr) = apply.from.to_live_ptr(cx, index){
135 let id = nodes[index].id;
136 self.templates.insert(id, live_ptr);
137 for (_, item) in self.items.iter_mut() {
139 if item.template == id {
140 item.widget.apply(cx, apply, index, nodes);
141 }
142 }
143 }
144 }
145 else {
146 cx.apply_error_no_matching_field(live_error_origin!(), index, nodes);
147 }
148 nodes.skip_node(index)
149 }
150
151 fn after_apply(&mut self, _cx: &mut Cx, _applyl: &mut Apply, _index: usize, _nodes: &[LiveNode]) {
152 if let Flow::Down = self.layout.flow {
153 self.vec_index = Vec2Index::Y
154 }
155 else {
156 self.vec_index = Vec2Index::X
157 }
158 if self.auto_tail{
159 self.tail_range = true;
160 }
161 }
162}
163
164impl PortalList {
165
166 fn begin(&mut self, cx: &mut Cx2d, walk: Walk) {
167 cx.begin_turtle(walk, self.layout);
168 self.draw_align_list.clear();
169 }
170
171 fn end(&mut self, cx: &mut Cx2d) {
172 self.at_end = false;
175 self.not_filling_viewport = false;
176
177 let vi = self.vec_index;
178 let mut visible_items = 0;
179
180 if let Some(ListDrawState::End {viewport}) = self.draw_state.get() {
181 let list = &mut self.draw_align_list;
182 if list.len() > 0 {
183 list.sort_by( | a, b | a.index.cmp(&b.index));
184 let first_index = list.iter().position( | v | v.index == self.first_id).unwrap();
185
186 let mut first_pos = self.first_scroll;
189 for i in (0..first_index).rev() {
190 let item = &list[i];
191 first_pos -= item.size.index(vi);
192 }
193
194 let mut last_pos = self.first_scroll;
199 let mut last_item_pos = None;
200 for i in first_index..list.len() {
201 let item = &list[i];
202 last_pos += item.size.index(vi);
203 if item.index < self.range_end {
204 last_item_pos = Some(last_pos);
205 }
206 else {
207 break;
208 }
209 }
210
211 if list[0].index == self.range_start {
214 let mut total = 0.0;
215 for item in list.iter() {
216 if item.index >= self.range_end {
217 break;
218 }
219 total += item.size.index(vi);
220 }
221 self.not_filling_viewport = total < viewport.size.index(vi);
222 }
223
224 if list.first().unwrap().index == self.range_start && first_pos > 0.0 {
226 let min = if let ScrollState::Stopped = self.scroll_state {
227 0.0
228 }
229 else {
230 self.max_pull_down
231 };
232
233 let mut pos = first_pos.min(min); for item in list {
235 let shift = DVec2::from_index_pair(vi, pos, 0.0);
236 cx.shift_align_range(&item.align_range, shift - DVec2::from_index_pair(vi, item.shift, 0.0));
237 pos += item.size.index(vi);
238 visible_items += 1;
239 }
240 self.first_scroll = first_pos.min(min);
241 self.first_id = self.range_start;
242 }
243 else {
244 let shift = if let Some(last_item_pos) = last_item_pos {
247 if self.align_top_when_empty && self.not_filling_viewport {
248 -first_pos
249 }
250 else {
251 let ret = viewport.size.index(vi) - last_item_pos;
252 if ret >= 0.0 {
253 self.at_end = true;
254 }
255 ret.max(0.0)
256 }
257 }
258 else {
259 0.0
260 };
261 let mut first_id_changed = false;
263 let start_pos = self.first_scroll + shift;
264 let mut pos = start_pos;
265 for i in (0..first_index).rev() {
266 let item = &list[i];
267 let visible = pos > 0.0;
268 pos -= item.size.index(vi);
269 let shift = DVec2::from_index_pair(vi, pos, 0.0);
270 cx.shift_align_range(&item.align_range, shift - DVec2::from_index_pair(vi, item.shift, 0.0));
271 if visible { self.first_scroll = pos;
273 self.first_id = item.index;
274 first_id_changed = true;
275 if item.index < self.range_end {
276 visible_items += 1;
277 }
278 }
279 }
280 let mut pos = start_pos;
282 for i in first_index..list.len() {
283 let item = &list[i];
284 let shift = DVec2::from_index_pair(vi, pos, 0.0);
285 cx.shift_align_range(&item.align_range, shift - DVec2::from_index_pair(vi, item.shift, 0.0));
286 pos += item.size.index(vi);
287 let invisible = pos < 0.0;
288 if invisible { self.first_scroll = pos - item.size.index(vi);
290 self.first_id = item.index;
291 first_id_changed = true;
292 }
293 else if item.index < self.range_end {
294 visible_items += 1;
295 }
296 }
297 if !first_id_changed {
299 self.first_scroll = start_pos;
300 }
301 }
302 if !self.scroll_bar.animator_in_state(cx, id!(hover.pressed)){
303 self.update_scroll_bar(cx);
304 }
305 }
306 }
307 else {
308 }
310 let rect = cx.turtle().rect();
311 if self.at_end || self.view_window == 0 || self.view_window > visible_items{
312 self.view_window = visible_items.max(4) - 3;
313 }
314 if self.detect_tail_in_draw{
315 self.detect_tail_in_draw = false;
316 if self.auto_tail && self.at_end{
317 self.tail_range = true;
318 }
319 }
320 let total_views = (self.range_end - self.range_start) as f64 / self.view_window as f64;
321 match self.vec_index {
322 Vec2Index::Y => {
323 self.scroll_bar.draw_scroll_bar(cx, ScrollAxis::Vertical, rect, dvec2(100.0, rect.size.y * total_views));
324 }
325 Vec2Index::X => {
326 self.scroll_bar.draw_scroll_bar(cx, ScrollAxis::Horizontal, rect, dvec2(rect.size.x * total_views, 100.0));
327 }
328 }
329
330 if !self.keep_invisible{
331 if self.reuse_items{
332 let reusable_items = &mut self.reusable_items;
333 self.items.retain_visible_with(|v|{
334 reusable_items.push(v);
335 });
336 }
337 else{
338 self.items.retain_visible();
339 }
340 }
341
342 cx.end_turtle_with_area(&mut self.area);
343 self.visible_items = visible_items;
344 }
345
346
347 pub fn next_visible_item(&mut self, cx: &mut Cx2d) -> Option<usize> {
349 let vi = self.vec_index;
350 let layout = if vi == Vec2Index::Y { Layout::flow_down() } else { Layout::flow_right() };
351 if let Some(draw_state) = self.draw_state.get() {
352 match draw_state {
353 ListDrawState::Begin => {
354 #[cfg(debug_assertions)]
358 if self.fails_sanity_check_first_id_within_item_range() {
359 warning!("PortalList: first_id {} is greater than range_end {}.\n\
360 --> Check that you have set the correct item range and first item ID!",
361 self.first_id, self.range_end,
362 );
363 }
364
365 let viewport = cx.turtle().padded_rect();
366 self.draw_state.set(ListDrawState::Down {
367 index: self.first_id,
368 pos: self.first_scroll,
369 viewport,
370 });
371 match vi {
372 Vec2Index::Y => {
373 cx.begin_turtle(Walk {
374 abs_pos: Some(dvec2(viewport.pos.x, viewport.pos.y + self.first_scroll)),
375 margin: Default::default(),
376 width: Size::Fill,
377 height: Size::Fit
378 }, layout);
379 }
380 Vec2Index::X => {
381 cx.begin_turtle(Walk {
382 abs_pos: Some(dvec2(viewport.pos.x + self.first_scroll, viewport.pos.y)),
383 margin: Default::default(),
384 width: Size::Fit,
385 height: Size::Fill
386 }, layout);
387 }
388 }
389 return Some(self.first_id);
390 }
391 ListDrawState::Down {index, pos, viewport} | ListDrawState::DownAgain {index, pos, viewport} => {
392 let is_down_again = draw_state.is_down_again();
393 let did_draw = cx.turtle_has_align_items();
394 let align_range = cx.get_turtle_align_range();
395 let rect = cx.end_turtle();
396 self.draw_align_list.push(AlignItem {
397 align_range,
398 shift: pos,
399 size: rect.size,
400 index
401 });
402
403 if !did_draw || pos + rect.size.index(vi) > viewport.size.index(vi) {
404 if self.first_id>0 && !is_down_again {
406 self.draw_state.set(ListDrawState::Up {
407 index: self.first_id - 1,
408 pos: self.first_scroll,
409 hit_bottom: index >= self.range_end,
410 viewport
411 });
412 match vi {
413 Vec2Index::Y => {
414 cx.begin_turtle(Walk {
415 abs_pos: Some(dvec2(viewport.pos.x, viewport.pos.y)),
416 margin: Default::default(),
417 width: Size::Fill,
418 height: Size::Fit
419 }, layout);
420 }
421 Vec2Index::X => {
422 cx.begin_turtle(Walk {
423 abs_pos: Some(dvec2(viewport.pos.x, viewport.pos.y)),
424 margin: Default::default(),
425 width: Size::Fit,
426 height: Size::Fill
427 }, layout);
428 }
429 }
430 return Some(self.first_id - 1);
431 }
432 else {
433 self.draw_state.set(ListDrawState::End {viewport});
434 return None
435 }
436 }
437 if is_down_again {
438 self.draw_state.set(ListDrawState::DownAgain {
439 index: index + 1,
440 pos: pos + rect.size.index(vi),
441 viewport
442 });
443 }
444 else {
445 self.draw_state.set(ListDrawState::Down {
446 index: index + 1,
447 pos: pos + rect.size.index(vi),
448 viewport
449 });
450 }
451 match vi {
452 Vec2Index::Y => {
453 cx.begin_turtle(Walk {
454 abs_pos: Some(dvec2(viewport.pos.x, viewport.pos.y + pos + rect.size.index(vi))),
455 margin: Default::default(),
456 width: Size::Fill,
457 height: Size::Fit
458 }, layout);
459 }
460 Vec2Index::X => {
461 cx.begin_turtle(Walk {
462 abs_pos: Some(dvec2(viewport.pos.x + pos + rect.size.index(vi), viewport.pos.y)),
463 margin: Default::default(),
464 width: Size::Fit,
465 height: Size::Fill
466 }, layout);
467 }
468 }
469 return Some(index + 1);
470 }
471 ListDrawState::Up {index, pos, hit_bottom, viewport} => {
472 let did_draw = cx.turtle_has_align_items();
473 let align_range = cx.get_turtle_align_range();
474 let rect = cx.end_turtle();
475 self.draw_align_list.push(AlignItem {
476 align_range,
477 size: rect.size,
478 shift: 0.0,
479 index
480 });
481 if index == self.range_start {
482 if pos - rect.size.index(vi) > 0.0 {
486 if let Some(last_index) = self.draw_align_list.iter().map( | v | v.index).max() {
488 let total_height: f64 = self.draw_align_list.iter().map( | v | v.size.index(vi)).sum();
490 self.draw_state.set(ListDrawState::DownAgain {
491 index: last_index + 1,
492 pos: total_height,
493 viewport
494 });
495 cx.begin_turtle(Walk {
496 abs_pos: Some(dvec2(viewport.pos.x, viewport.pos.y + total_height)),
497 margin: Default::default(),
498 width: Size::Fill,
499 height: Size::Fit
500 }, Layout::flow_down());
501 return Some(last_index + 1);
502 }
503 }
504 self.draw_state.set(ListDrawState::End {viewport});
505 return None
506 }
507
508 if !did_draw || pos < if hit_bottom {-viewport.size.index(vi)} else {0.0} {
509 self.draw_state.set(ListDrawState::End {viewport});
510 return None
511 }
512
513 self.draw_state.set(ListDrawState::Up {
514 index: index - 1,
515 hit_bottom,
516 pos: pos - rect.size.index(vi),
517 viewport
518 });
519
520 cx.begin_turtle(Walk {
521 abs_pos: Some(dvec2(viewport.pos.x, viewport.pos.y)),
522 margin: Default::default(),
523 width: Size::Fill,
524 height: Size::Fit
525 }, Layout::flow_down());
526
527 return Some(index - 1);
528 }
529 _ => ()
530 }
531 }
532 None
533 }
534
535 pub fn item(&mut self, cx: &mut Cx, entry_id: usize, template: LiveId) -> WidgetRef {
546 self.item_with_existed(cx, entry_id, template).0
547 }
548
549 pub fn item_with_existed(&mut self, cx: &mut Cx, entry_id: usize, template: LiveId) -> (WidgetRef, bool) {
564 use std::collections::hash_map::Entry;
565 if let Some(ptr) = self.templates.get(&template) {
566 match self.items.entry(entry_id) {
567 Entry::Occupied(mut occ) => {
568 if occ.get().template == template {
569 (occ.get().widget.clone(), true)
570 } else {
571 let widget_ref = if let Some(pos) = self.reusable_items.iter().position(|v| v.template == template){
572 self.reusable_items.remove(pos).widget
573 }
574 else{
575 WidgetRef::new_from_ptr(cx, Some(*ptr))
576 };
577 occ.insert(WidgetItem{
578 template,
579 widget:widget_ref.clone(),
580 ..Default::default()
581 });
582 (widget_ref, false)
583 }
584 }
585 Entry::Vacant(vac) => {
586 let widget_ref = if let Some(pos) = self.reusable_items.iter().position(|v| v.template == template){
587 self.reusable_items.remove(pos).widget
588 }
589 else{
590 WidgetRef::new_from_ptr(cx, Some(*ptr))
591 };
592 vac.insert(WidgetItem{
593 template,
594 widget: widget_ref.clone(),
595 ..Default::default()
596 });
597 (widget_ref, false)
598 }
599 }
600 } else {
601 warning!("Template not found: {template}. Did you add it to the <PortalList> instance in `live_design!{{}}`?");
602 (WidgetRef::empty(), false)
603 }
604 }
605
606 pub fn position_of_item(&self, cx: &Cx, entry_id: usize) -> Option<f64> {
620 const ZEROED: Rect = Rect { pos: DVec2 { x: 0.0, y: 0.0 }, size: DVec2 { x: 0.0, y: 0.0 } };
621
622 if let Some(item) = self.items.get(&entry_id) {
623 let item_rect = item.widget.area().rect(cx);
624 if item_rect == ZEROED {
625 return None;
626 }
627 let self_rect = self.area.rect(cx);
628 if self_rect == ZEROED {
629 return None;
630 }
631 let vi = self.vec_index;
632 Some(item_rect.pos.index(vi) - self_rect.pos.index(vi))
633 } else {
634 None
635 }
636 }
637
638 pub fn get_item(&self, entry_id: usize) -> Option<(LiveId,WidgetRef)> {
640 if let Some(item) = self.items.get(&entry_id){
641 Some((item.template.clone(), item.widget.clone()))
642 }
643 else{
644 None
645 }
646 }
647
648 pub fn set_item_range(&mut self, cx: &mut Cx, range_start: usize, range_end: usize) {
649 self.range_start = range_start;
650 if self.range_end != range_end {
651 self.range_end = range_end;
652 if self.tail_range{
653 self.first_id = self.range_end.max(1) - 1;
654 self.first_scroll = 0.0;
655 }
656 self.update_scroll_bar(cx);
657 }
658 }
659
660 pub fn update_scroll_bar(&mut self, cx: &mut Cx) {
661 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();
662 self.scroll_bar.set_scroll_pos_no_action(cx, scroll_pos);
664 }
665
666 fn delta_top_scroll(
677 &mut self,
678 cx: &mut Cx,
679 delta: f64,
680 clip_top: bool,
681 transition_to_pulldown: bool,
682 ) {
683 if self.range_start == self.range_end {
684 self.first_scroll = 0.0
685 }
686 else {
687 self.first_scroll += delta;
688 }
689
690 if self.first_id == self.range_start {
691 self.first_scroll = self.first_scroll.min(self.max_pull_down);
692 if transition_to_pulldown && self.first_scroll > 0.0 {
693 self.scroll_state = ScrollState::Pulldown {next_frame: cx.new_next_frame()};
694 }
695 }
696 if clip_top && self.first_id == self.range_start && self.first_scroll > 0.0 {
697 self.first_scroll = 0.0;
698 }
699 if self.at_end && delta < 0.0 {
701 self.was_scrolling = false;
702 self.scroll_state = ScrollState::Stopped;
703 }
704 self.update_scroll_bar(cx);
705 }
706
707 pub fn is_at_end(&self) -> bool {
710 self.at_end
711 }
712
713 pub fn visible_items(&self) -> usize {
716 self.visible_items
717 }
718
719 pub fn fails_sanity_check_first_id_within_item_range(&self) -> bool {
723 !self.tail_range
724 && (self.first_id > self.range_end)
725 }
726
727 pub fn smooth_scroll_to(&mut self, cx: &mut Cx, target_id: usize, speed: f64, max_items_to_show: Option<usize>) {
743 if self.items.is_empty() { return };
744 if target_id < self.range_start || target_id > self.range_end { return };
745
746 let max_items_to_show = max_items_to_show.unwrap_or(SMOOTH_SCROLL_MAXIMUM_WINDOW);
747 let scroll_direction: f64;
748 let starting_id: Option<usize>;
749 if target_id > self.first_id {
750 scroll_direction = -1.0;
752 starting_id = ((target_id.saturating_sub(self.first_id)) > max_items_to_show)
753 .then_some(target_id.saturating_sub(max_items_to_show));
754 } else {
755 scroll_direction = 1.0;
757 starting_id = ((self.first_id.saturating_sub(target_id)) > max_items_to_show)
758 .then_some(target_id + max_items_to_show);
759 };
760
761 if let Some(start) = starting_id {
763 self.first_id = start;
764 }
765 self.scroll_state = ScrollState::ScrollingTo {
767 target_id,
768 delta: speed.abs() * scroll_direction as f64,
769 next_frame: cx.new_next_frame()
770 };
771 }
772
773 pub fn smooth_scroll_to_end(&mut self, cx: &mut Cx, speed: f64, max_items_to_show: Option<usize>) {
783 if self.items.is_empty() { return };
784
785 let speed = speed * self.range_end as f64;
786 self.smooth_scroll_to(cx, self.range_end, speed, max_items_to_show);
787 }
788}
789
790
791impl Widget for PortalList {
792
793 fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
794 let uid = self.widget_uid();
795
796 let mut scroll_to = None;
797 self.scroll_bar.handle_event_with(cx, event, &mut | _cx, action | {
798 if let ScrollBarAction::Scroll {scroll_pos, view_total, view_visible} = action {
800 scroll_to = Some((scroll_pos, scroll_pos+0.5 >= view_total - view_visible))
801 }
802 });
803 if let Some((scroll_to, at_end)) = scroll_to {
804 if at_end && self.auto_tail{
805 self.first_id = self.range_end.max(1) - 1;
806 self.first_scroll = 0.0;
807 self.tail_range = true;
808 } else {
809 self.tail_range = false;
810 }
811
812 self.first_id = ((scroll_to / self.scroll_bar.get_scroll_view_visible()) * self.view_window as f64) as usize;
813 self.first_scroll = 0.0;
814 cx.widget_action(uid, &scope.path, PortalListAction::Scroll);
815 self.was_scrolling = false;
816 self.area.redraw(cx);
817 }
818
819 for item in self.items.values_mut() {
820 let item_uid = item.widget.widget_uid();
821 cx.group_widget_actions(uid, item_uid, |cx|{
822 item.widget.handle_event(cx, event, scope)
823 });
824 }
825
826 match &mut self.scroll_state {
827 ScrollState::ScrollingTo {target_id, delta, next_frame} => {
828 if let Some(_) = next_frame.is_event(event) {
829 let target_id = *target_id;
830
831 let distance_to_target = target_id as isize - self.first_id as isize;
832 let target_passed = distance_to_target.signum() == delta.signum() as isize;
833 if target_passed {
836 self.first_id = target_id;
837 self.area.redraw(cx);
838 }
839
840 let distance_to_target = target_id as isize - self.first_id as isize;
841
842 let target_visible_at_end = self.at_end && target_id > self.first_id;
845 let target_reached = distance_to_target == 0 || target_visible_at_end;
846
847 if !target_reached {
848 *next_frame = cx.new_next_frame();
849 let delta = *delta;
850 self.delta_top_scroll(cx, delta, true, false);
852 cx.widget_action(uid, &scope.path, PortalListAction::Scroll);
853 self.area.redraw(cx);
854 } else {
855 self.was_scrolling = false;
856 self.scroll_state = ScrollState::Stopped;
857 cx.widget_action(uid, &scope.path, PortalListAction::SmoothScrollReached);
858 }
859 }
860 }
861 ScrollState::Flick {delta, next_frame} => {
862 if let Some(_) = next_frame.is_event(event) {
863 *delta = *delta * self.flick_scroll_decay;
864 if delta.abs()>self.flick_scroll_minimum {
865 *next_frame = cx.new_next_frame();
866 let delta = *delta;
867 self.delta_top_scroll(cx, delta, false, true);
868 cx.widget_action(uid, &scope.path, PortalListAction::Scroll);
869 self.area.redraw(cx);
870 } else {
871 self.was_scrolling = false;
872 self.scroll_state = ScrollState::Stopped;
873 }
874 }
875 }
876 ScrollState::Pulldown {next_frame} => {
877 if let Some(_) = next_frame.is_event(event) {
878 if self.first_id == self.range_start && self.first_scroll > 0.0 {
880 self.first_scroll *= 0.85;
881 if self.first_scroll < 1.0 {
882 self.first_scroll = 0.0;
883 self.was_scrolling = false;
885 self.scroll_state = ScrollState::Stopped;
886 }
887 else {
888 *next_frame = cx.new_next_frame();
889 cx.widget_action(uid, &scope.path, PortalListAction::Scroll);
890 }
891 self.area.redraw(cx);
892 }
893 else {
894 self.was_scrolling = false;
895 self.scroll_state = ScrollState::Stopped;
896 }
897 }
898 }
899 _=>()
900 }
901 let vi = self.vec_index;
902 let is_scroll = if let Event::Scroll(_) = event {true} else {false};
903 if self.scroll_bar.is_area_captured(cx){
904 self.scroll_state = ScrollState::Stopped;
905 }
906 if !self.scroll_bar.is_area_captured(cx) || is_scroll{
907 match event.hits_with_capture_overload(cx, self.area, self.capture_overload) {
908 Hit::FingerScroll(e) => {
909 self.tail_range = false;
910 self.detect_tail_in_draw = true;
911 self.was_scrolling = false;
912 self.scroll_state = ScrollState::Stopped;
913 self.delta_top_scroll(cx, -e.scroll.index(vi), false, true);
914 cx.widget_action(uid, &scope.path, PortalListAction::Scroll);
915 self.area.redraw(cx);
916 },
917
918 Hit::KeyDown(ke) => match ke.key_code {
919 KeyCode::Home => {
920 self.first_id = 0;
921 self.first_scroll = 0.0;
922 self.tail_range = false;
923 self.update_scroll_bar(cx);
924 self.area.redraw(cx);
925 },
926 KeyCode::End => {
927 self.first_id = self.range_end.max(1) - 1;
928 self.first_scroll = 0.0;
929 if self.auto_tail {
930 self.tail_range = true;
931 }
932 self.update_scroll_bar(cx);
933 self.area.redraw(cx);
934 },
935 KeyCode::PageUp => {
936 self.first_id = self.first_id.max(self.view_window) - self.view_window;
937 self.first_scroll = 0.0;
938 self.tail_range = false;
939 self.update_scroll_bar(cx);
940 self.area.redraw(cx);
941 },
942 KeyCode::PageDown => {
943 self.first_id += self.view_window;
944 self.first_scroll = 0.0;
945 if self.first_id >= self.range_end.max(1) {
946 self.first_id = self.range_end.max(1) - 1;
947 }
948 self.detect_tail_in_draw = true;
949 self.update_scroll_bar(cx);
950 self.area.redraw(cx);
951 },
952 KeyCode::ArrowDown => {
953 self.first_id += 1;
954 if self.first_id >= self.range_end.max(1) {
955 self.first_id = self.range_end.max(1) - 1;
956 }
957 self.detect_tail_in_draw = true;
958 self.first_scroll = 0.0;
959 self.update_scroll_bar(cx);
960 self.area.redraw(cx);
961 },
962 KeyCode::ArrowUp => {
963 if self.first_id > 0 {
964 self.first_id -= 1;
965 if self.first_id < self.range_start {
966 self.first_id = self.range_start;
967 }
968 self.first_scroll = 0.0;
969 self.area.redraw(cx);
970 self.tail_range = false;
971 self.update_scroll_bar(cx);
972 }
973 },
974 _ => ()
975 }
976 Hit::FingerDown(fe) => {
977 if self.grab_key_focus {
982 cx.set_key_focus(self.area);
983 }
984 self.tail_range = false;
985 self.was_scrolling = match &self.scroll_state {
986 ScrollState::Drag { samples } => samples.len() > 1,
987 ScrollState::Stopped => false,
988 _ => true,
989 };
990 if self.drag_scrolling && fe.is_primary_hit() {
991 self.scroll_state = ScrollState::Drag {
992 samples: vec![ScrollSample{abs: fe.abs.index(vi), time: fe.time}]
993 };
994 }
995 }
996 Hit::FingerMove(e) => {
997 cx.set_cursor(MouseCursor::Default);
999 match &mut self.scroll_state {
1000 ScrollState::Drag {samples}=>{
1001 let new_abs = e.abs.index(vi);
1002 let old_sample = *samples.last().unwrap();
1003 samples.push(ScrollSample{abs:new_abs, time:e.time});
1004 if samples.len()>4{
1005 samples.remove(0);
1006 }
1007 self.delta_top_scroll(cx, new_abs - old_sample.abs, false, false);
1008 self.area.redraw(cx);
1009 }
1010 _=>()
1011 }
1012 }
1013 Hit::FingerUp(fe) if fe.is_primary_hit() => {
1014 match &mut self.scroll_state {
1016 ScrollState::Drag {samples}=>{
1017 let mut last = None;
1020 let mut scaled_delta = 0.0;
1021 let mut total_delta = 0.0;
1022 for sample in samples.iter().rev(){
1023 if last.is_none(){
1024 last = Some(sample);
1025 }
1026 else{
1027 total_delta += last.unwrap().abs - sample.abs;
1028 scaled_delta += (last.unwrap().abs - sample.abs)/ (last.unwrap().time - sample.time)
1029 }
1030 }
1031 scaled_delta *= self.flick_scroll_scaling;
1032 if self.first_id == self.range_start && self.first_scroll > 0.0 {
1033 self.scroll_state = ScrollState::Pulldown {next_frame: cx.new_next_frame()};
1034 }
1035 else if total_delta.abs() > 10.0 && scaled_delta.abs() > self.flick_scroll_minimum{
1036
1037 self.scroll_state = ScrollState::Flick {
1038 delta: scaled_delta.min(self.flick_scroll_maximum).max(-self.flick_scroll_maximum),
1039 next_frame: cx.new_next_frame()
1040 };
1041 }
1042 else {
1043 self.was_scrolling = false;
1044 self.scroll_state = ScrollState::Stopped;
1045 }
1046 }
1047 _=>()
1048 }
1049 }
1052 Hit::KeyFocus(_) => {
1053 }
1054 Hit::KeyFocusLost(_) => {
1055 }
1056 _ => ()
1057 }
1058 }
1059 }
1060
1061 fn draw_walk(&mut self, cx: &mut Cx2d, _scope:&mut Scope, walk: Walk) -> DrawStep {
1062 if self.draw_state.begin(cx, ListDrawState::Begin) {
1063 self.begin(cx, walk);
1064 return DrawStep::make_step()
1065 }
1066 if let Some(_) = self.draw_state.get() {
1068 self.end(cx);
1069 self.draw_state.end();
1070 }
1071 DrawStep::done()
1072 }
1073}
1074
1075impl PortalListRef {
1076 pub fn set_first_id_and_scroll(&self, id: usize, s: f64) {
1083 if let Some(mut inner) = self.borrow_mut() {
1084 inner.first_id = id;
1085 inner.first_scroll = s;
1086 }
1087 }
1088
1089 pub fn set_first_id(&self, id: usize) {
1091 if let Some(mut inner) = self.borrow_mut() {
1092 inner.first_id = id;
1093 }
1094 }
1095
1096 pub fn first_id(&self) -> usize {
1098 if let Some(inner) = self.borrow() {
1099 inner.first_id
1100 }
1101 else {
1102 0
1103 }
1104 }
1105
1106 pub fn set_tail_range(&self, tail_range: bool) {
1112 if let Some(mut inner) = self.borrow_mut() {
1113 inner.tail_range = tail_range
1114 }
1115 }
1116
1117 pub fn is_at_end(&self) -> bool {
1119 let Some(inner) = self.borrow() else { return false };
1120 inner.is_at_end()
1121 }
1122
1123 pub fn visible_items(&self) -> usize {
1125 let Some(inner) = self.borrow() else { return 0 };
1126 inner.visible_items()
1127 }
1128
1129 pub fn was_scrolling(&self) -> bool {
1131 self.borrow().is_some_and(|inner| inner.was_scrolling)
1132 }
1133
1134 pub fn scrolled(&self, actions: &Actions) -> bool {
1136 if let PortalListAction::Scroll = actions.find_widget_action(self.widget_uid()).cast() {
1137 return true;
1138 }
1139 false
1140 }
1141
1142 pub fn scroll_position(&self) -> f64 {
1146 let Some(inner) = self.borrow_mut() else { return 0.0 };
1147 inner.first_scroll
1148 }
1149
1150 pub fn item(&self, cx: &mut Cx, entry_id: usize, template: LiveId) -> WidgetRef {
1152 if let Some(mut inner) = self.borrow_mut(){
1153 inner.item(cx, entry_id, template)
1154 }
1155 else{
1156 WidgetRef::empty()
1157 }
1158 }
1159
1160 pub fn item_with_existed(&self, cx: &mut Cx, entry_id: usize, template: LiveId) -> (WidgetRef, bool) {
1162 if let Some(mut inner) = self.borrow_mut(){
1163 inner.item_with_existed(cx, entry_id, template)
1164 }
1165 else{
1166 (WidgetRef::empty(), false)
1167 }
1168 }
1169
1170 pub fn get_item(&self, entry_id: usize) -> Option<(LiveId, WidgetRef)> {
1172 let Some(inner) = self.borrow() else { return None };
1173 inner.get_item(entry_id)
1174 }
1175
1176 pub fn position_of_item(&self, cx:&Cx, entry_id: usize) -> Option<f64>{
1177 let Some(inner) = self.borrow() else { return None };
1178 inner.position_of_item(cx, entry_id)
1179 }
1180
1181 pub fn items_with_actions(&self, actions: &Actions) -> ItemsWithActions {
1182 let mut set = Vec::new();
1183 self.items_with_actions_vec(actions, &mut set);
1184 set
1185 }
1186
1187 fn items_with_actions_vec(&self, actions: &Actions, set: &mut ItemsWithActions) {
1188 let uid = self.widget_uid();
1189 if let Some(inner) = self.borrow() {
1190 for action in actions {
1191 if let Some(action) = action.as_widget_action(){
1192 if let Some(group) = &action.group{
1193 if group.group_uid == uid{
1194 for (item_id, item) in inner.items.iter() {
1195 if group.item_uid == item.widget.widget_uid(){
1196 set.push((*item_id, item.widget.clone()))
1197 }
1198 }
1199 }
1200 }
1201 }
1202 }
1203 }
1204 }
1205
1206 pub fn any_items_with_actions(&self, actions: &Actions)->bool {
1207 let uid = self.widget_uid();
1208 for action in actions {
1209 if let Some(action) = action.as_widget_action(){
1210 if let Some(group) = &action.group{
1211 if group.group_uid == uid{
1212 return true
1213 }
1214 }
1215 }
1216 }
1217 false
1218 }
1219
1220 pub fn smooth_scroll_to(&self, cx: &mut Cx, target_id: usize, speed: f64, max_items_to_show: Option<usize>) {
1236 let Some(mut inner) = self.borrow_mut() else { return };
1237 inner.smooth_scroll_to(cx, target_id, speed, max_items_to_show);
1238 }
1239
1240 pub fn is_smooth_scrolling(&self) -> Option<usize> {
1242 let Some(inner) = self.borrow_mut() else { return None };
1243 if let ScrollState::ScrollingTo { target_id, .. } = inner.scroll_state {
1244 Some(target_id)
1245 } else {
1246 None
1247 }
1248 }
1249
1250 pub fn smooth_scroll_reached(&self, actions: &Actions) -> bool {
1253 if let PortalListAction::SmoothScrollReached = actions.find_widget_action(self.widget_uid()).cast() {
1254 return true;
1255 }
1256 false
1257 }
1258
1259 pub fn smooth_scroll_to_end(&self, cx: &mut Cx, speed: f64, max_items_to_show: Option<usize>) {
1269 let Some(mut inner) = self.borrow_mut() else { return };
1270
1271 inner.smooth_scroll_to_end(cx, speed, max_items_to_show);
1272 }
1273
1274 pub fn further_items_bellow_exist(&self) -> bool {
1278 let Some(inner) = self.borrow() else { return false };
1279 !(inner.at_end || inner.not_filling_viewport)
1280 }
1281}
1282
1283type ItemsWithActions = Vec<(usize, WidgetRef)>;
1284
1285impl PortalListSet {
1286 pub fn set_first_id(&self, id: usize) {
1287 for list in self.iter() {
1288 list.set_first_id(id)
1289 }
1290 }
1291
1292
1293 pub fn items_with_actions(&self, actions: &Actions) -> ItemsWithActions {
1294 let mut set = Vec::new();
1295 for list in self.iter() {
1296 list.items_with_actions_vec(actions, &mut set)
1297 }
1298 set
1299 }
1300}