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
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, 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(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 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 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 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 cx.append_sub_draw_list(&dl.draw_list);
205 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 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 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 pub fn next_visible_item(&mut self, cx: &mut Cx2d) -> Option<usize> {
251 let vi = self.vec_index;
252 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{ }
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 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 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 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 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 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 pub fn is_at_end(&self) -> bool {
499 self.at_end
500 }
501
502 pub fn visible_items(&self) -> usize {
505 self.visible_items
506 }
507
508 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 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 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 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 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 if self.grab_key_focus {
702 cx.set_key_focus(self.area);
703 }
704 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 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 match &mut self.scroll_state {
734 ScrollState::Drag {samples}=>{
735 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 }
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 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 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 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 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 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 pub fn is_at_end(&self) -> bool {
835 let Some(inner) = self.borrow() else { return false };
836 inner.is_at_end()
837 }
838
839 pub fn visible_items(&self) -> usize {
841 let Some(inner) = self.borrow() else { return 0 };
842 inner.visible_items()
843 }
844
845 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 pub fn scroll_position(&self) -> f64 {
857 let Some(inner) = self.borrow_mut() else { return 0.0 };
858 inner.first_scroll
859 }
860
861 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 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 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 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 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 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 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 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 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 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 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); inner.first_id = starting_id;
1017 inner.scroll_state = ScrollState::Flick {
1019 delta: -speed,
1020 next_frame: cx.new_next_frame()
1021 };
1022 }
1023
1024 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}