1
2use crate::{
3 widget::*,
4 makepad_derive_widget::*,
5 makepad_draw::*,
6 scroll_bar::{ScrollBar, ScrollBarAction}
7};
8
9live_design!{
10 PortalListBase = {{PortalList}} {}
11}
12
13#[derive(Clone,Copy)]
14struct ScrollSample{
15 abs: f64,
16 time: f64,
17}
18
19enum ScrollState {
20 Stopped,
21 Drag{samples:Vec<ScrollSample>},
22 Flick {delta: f64, next_frame: NextFrame},
23 Pulldown {next_frame: NextFrame},
24}
25
26#[derive(Clone)]
27enum ListDrawState {
28 Begin,
29 Down {index: u64, pos: f64, viewport: Rect},
30 Up {index: u64, pos: f64, hit_bottom: bool, viewport: Rect},
31 DownAgain {index: u64, pos: f64, viewport: Rect},
32 End {viewport: Rect}
33}
34
35
36#[derive(Clone, WidgetAction)]
37pub enum PortalListAction {
38 Scroll,
39 None
40}
41impl ListDrawState {
42 fn is_down_again(&self) -> bool {
43 match self {
44 Self::DownAgain {..} => true,
45 _ => false
46 }
47 }
48}
49#[derive(Live)]
50pub struct PortalList {
51 #[rust] area: Area,
52 #[walk] walk: Walk,
53 #[layout] layout: Layout,
54
55 #[rust] range_start: u64,
56 #[rust(u64::MAX)] range_end: u64,
57 #[rust(0u64)] view_window: u64,
58 #[live(0.2)] flick_scroll_minimum: f64,
59 #[live(80.0)] flick_scroll_maximum: f64,
60 #[live(0.005)] flick_scroll_scaling: f64,
61 #[live(0.98)] flick_scroll_decay: f64,
62 #[live(0.2)] swipe_drag_duration: f64,
63 #[live(100.0)] max_pull_down: f64,
64 #[live(true)] align_top_when_empty: bool,
65 #[live(false)] grab_key_focus: bool,
66 #[live(true)] drag_scrolling: bool,
67 #[live(false)] allow_empty: bool,
68 #[rust] first_id: u64,
69 #[rust] first_scroll: f64,
70 #[rust(Vec2Index::X)] vec_index: Vec2Index,
71 #[live] scroll_bar: ScrollBar,
72 #[live] capture_overload: bool,
73 #[live(false)] keep_invisible: bool,
74 #[rust] draw_state: DrawStateWrap<ListDrawState>,
75 #[rust] draw_align_list: Vec<AlignItem>,
76 #[rust] detect_tail_in_draw: bool,
77 #[live(false)] auto_tail: bool,
78 #[rust(false)] tail_range: bool,
79
80 #[rust] templates: ComponentMap<LiveId, LivePtr>,
81 #[rust] items: ComponentMap<(u64, LiveId), WidgetRef>,
82 #[rust(ScrollState::Stopped)] scroll_state: ScrollState
84}
85
86struct AlignItem {
87 align_range: TurtleAlignRange,
88 size: DVec2,
89 shift: f64,
90 index: u64
91}
92
93impl LiveHook for PortalList {
94 fn before_live_design(cx: &mut Cx) {
95 register_widget!(cx, PortalList)
96 }
97
98 fn before_apply(&mut self, _cx: &mut Cx, from: ApplyFrom, _index: usize, _nodes: &[LiveNode]) {
99 if let ApplyFrom::UpdateFromDoc {..} = from {
100 self.templates.clear();
101 }
102 }
103
104 fn apply_value_instance(&mut self, cx: &mut Cx, from: ApplyFrom, index: usize, nodes: &[LiveNode]) -> usize {
106 let id = nodes[index].id;
107 match from {
108 ApplyFrom::NewFromDoc {file_id} | ApplyFrom::UpdateFromDoc {file_id} => {
109 if nodes[index].origin.has_prop_type(LivePropType::Instance) {
110 let live_ptr = cx.live_registry.borrow().file_id_index_to_live_ptr(file_id, index);
111 self.templates.insert(id, live_ptr);
112 for ((_, templ_id), node) in self.items.iter_mut() {
114 if *templ_id == id {
115 node.apply(cx, from, index, nodes);
116 }
117 }
118 }
119 else {
120 cx.apply_error_no_matching_field(live_error_origin!(), index, nodes);
121 }
122 }
123 _ => ()
124 }
125 nodes.skip_node(index)
126 }
127
128 fn after_apply(&mut self, _cx: &mut Cx, _from: ApplyFrom, _index: usize, _nodes: &[LiveNode]) {
129 if let Flow::Down = self.layout.flow {
130 self.vec_index = Vec2Index::Y
131 }
132 else {
133 self.vec_index = Vec2Index::X
134 }
135 if self.auto_tail{
136 self.tail_range = true;
137 }
138 }
139}
140
141impl PortalList {
142
143 fn begin(&mut self, cx: &mut Cx2d, walk: Walk) {
144 cx.begin_turtle(walk, self.layout);
145 self.draw_align_list.clear();
146 }
147
148 fn end(&mut self, cx: &mut Cx2d) {
149
150 let vi = self.vec_index;
153 let mut at_end = false;
154 let mut visible_items = 0;
155
156 if let Some(ListDrawState::End {viewport}) = self.draw_state.get() {
157 let list = &mut self.draw_align_list;
158 if list.len()>0 {
159 list.sort_by( | a, b | a.index.cmp(&b.index));
160 let first_index = list.iter().position( | v | v.index == self.first_id).unwrap();
161
162 let mut first_pos = self.first_scroll;
165 for i in (0..first_index).rev() {
166 let item = &list[i];
167 first_pos -= item.size.index(vi);
168 }
169
170 let mut last_pos = self.first_scroll;
175 let mut last_item_pos = None;
176 for i in first_index..list.len() {
177 let item = &list[i];
178 last_pos += item.size.index(vi);
179 if item.index < self.range_end {
180 last_item_pos = Some(last_pos);
181 }
182 else {
183 break;
184 }
185 }
186
187 let mut not_filling_viewport = false;
190 if list[0].index == self.range_start {
191 let mut total = 0.0;
192 for item in list.iter() {
193 if item.index >= self.range_end {
194 break;
195 }
196 total += item.size.index(vi);
197 }
198 not_filling_viewport = total < viewport.size.index(vi);
199 }
200
201 if list.first().unwrap().index == self.range_start && first_pos > 0.0 {
203 let min = if let ScrollState::Stopped = self.scroll_state {
204 0.0
205 }
206 else {
207 self.max_pull_down
208 };
209
210 let mut pos = first_pos.min(min); for item in list {
212 let shift = DVec2::from_index_pair(vi, pos, 0.0);
213 cx.shift_align_range(&item.align_range, shift - DVec2::from_index_pair(vi, item.shift, 0.0));
214 pos += item.size.index(vi);
215 visible_items += 1;
216 }
217 self.first_scroll = first_pos.min(min);
218 self.first_id = self.range_start;
219 }
220 else {
221 let shift = if let Some(last_item_pos) = last_item_pos {
224 if self.align_top_when_empty && not_filling_viewport {
225 -first_pos
226 }
227 else {
228 let ret = (viewport.size.index(vi) - last_item_pos).max(0.0);
229 if ret > 0.0 {
230 at_end = true;
231 }
232 ret
233 }
234 }
235 else {
236 0.0
237 };
238 let mut first_id_changed = false;
240 let start_pos = self.first_scroll + shift;
241 let mut pos = start_pos;
242 for i in (0..first_index).rev() {
243 let item = &list[i];
244 let visible = pos > 0.0;
245 pos -= item.size.index(vi);
246 let shift = DVec2::from_index_pair(vi, pos, 0.0);
247 cx.shift_align_range(&item.align_range, shift - DVec2::from_index_pair(vi, item.shift, 0.0));
248 if visible { self.first_scroll = pos;
250 self.first_id = item.index;
251 first_id_changed = true;
252 if item.index < self.range_end {
253 visible_items += 1;
254 }
255 }
256 }
257 let mut pos = start_pos;
259 for i in first_index..list.len() {
260 let item = &list[i];
261 let shift = DVec2::from_index_pair(vi, pos, 0.0);
262 cx.shift_align_range(&item.align_range, shift - DVec2::from_index_pair(vi, item.shift, 0.0));
263 pos += item.size.index(vi);
264 let invisible = pos < 0.0;
265 if invisible { self.first_scroll = pos - item.size.index(vi);
267 self.first_id = item.index;
268 first_id_changed = true;
269 }
270 else if item.index < self.range_end {
271 visible_items += 1;
272 }
273 }
274 if !first_id_changed {
276 self.first_scroll = start_pos;
277 }
278 }
279 if !self.scroll_bar.animator_in_state(cx, id!(hover.pressed)){
280 self.update_scroll_bar(cx);
281 }
282 }
283 }
284 else {
285 log!("Draw state not at end in listview, please review your next_visible_item loop")
286 }
287 let rect = cx.turtle().rect();
288 if at_end || self.view_window == 0 || self.view_window > visible_items{
289 self.view_window = visible_items.max(4) - 3;
290 }
291 if self.detect_tail_in_draw{
292 self.detect_tail_in_draw = false;
293 if self.auto_tail && at_end{
294 self.tail_range = true;
295 }
296 }
297 let total_views = (self.range_end - self.range_start) as f64 / self.view_window as f64;
298 self.scroll_bar.draw_scroll_bar(cx, Axis::Vertical, rect, dvec2(100.0, rect.size.y * total_views));
299 if !self.keep_invisible{
300 self.items.retain_visible();
301 }
302 cx.end_turtle_with_area(&mut self.area);
303 }
304
305 pub fn next_visible_item(&mut self, cx: &mut Cx2d) -> Option<u64> {
306 let vi = self.vec_index;
307 if let Some(draw_state) = self.draw_state.get() {
308 match draw_state {
309 ListDrawState::Begin => {
310 let viewport = cx.turtle().padded_rect();
311 self.draw_state.set(ListDrawState::Down {
312 index: self.first_id,
313 pos: self.first_scroll,
314 viewport,
315 });
316
317 cx.begin_turtle(Walk {
318 abs_pos: Some(dvec2(viewport.pos.x, viewport.pos.y + self.first_scroll)),
319 margin: Default::default(),
320 width: Size::Fill,
321 height: Size::Fit
322 }, Layout::flow_down());
323 return Some(self.first_id)
324 }
325 ListDrawState::Down {index, pos, viewport} | ListDrawState::DownAgain {index, pos, viewport} => {
326 let is_down_again = draw_state.is_down_again();
327 let did_draw = cx.turtle_has_align_items() || self.allow_empty;
328 let align_range = cx.get_turtle_align_range();
329 let rect = cx.end_turtle();
330 self.draw_align_list.push(AlignItem {
331 align_range,
332 shift: pos,
333 size: rect.size,
334 index
335 });
336
337 if !did_draw || pos + rect.size.index(vi) > viewport.size.index(vi) {
338 if self.first_id>0 && !is_down_again {
340 self.draw_state.set(ListDrawState::Up {
341 index: self.first_id - 1,
342 pos: self.first_scroll,
343 hit_bottom: index >= self.range_end,
344 viewport
345 });
346 cx.begin_turtle(Walk {
347 abs_pos: Some(dvec2(viewport.pos.x, viewport.pos.y)),
348 margin: Default::default(),
349 width: Size::Fill,
350 height: Size::Fit
351 }, Layout::flow_down());
352 return Some(self.first_id - 1);
353 }
354 else {
355 self.draw_state.set(ListDrawState::End {viewport});
356 return None
357 }
358 }
359 if is_down_again {
360 self.draw_state.set(ListDrawState::DownAgain {
361 index: index + 1,
362 pos: pos + rect.size.index(vi),
363 viewport
364 });
365 }
366 else {
367 self.draw_state.set(ListDrawState::Down {
368 index: index + 1,
369 pos: pos + rect.size.index(vi),
370 viewport
371 });
372 }
373 cx.begin_turtle(Walk {
374 abs_pos: Some(dvec2(viewport.pos.x, viewport.pos.y + pos + rect.size.index(vi))),
375 margin: Default::default(),
376 width: Size::Fill,
377 height: Size::Fit
378 }, Layout::flow_down());
379 return Some(index + 1)
380 }
381 ListDrawState::Up {index, pos, hit_bottom, viewport} => {
382 let did_draw = cx.turtle_has_align_items() || self.allow_empty;
383 let align_range = cx.get_turtle_align_range();
384 let rect = cx.end_turtle();
385 self.draw_align_list.push(AlignItem {
386 align_range,
387 size: rect.size,
388 shift: 0.0,
389 index
390 });
391 if index == self.range_start {
392 if pos - rect.size.index(vi) > 0.0 {
396 if let Some(last_index) = self.draw_align_list.iter().map( | v | v.index).max() {
398 let total_height: f64 = self.draw_align_list.iter().map( | v | v.size.index(vi)).sum();
400 self.draw_state.set(ListDrawState::DownAgain {
401 index: last_index + 1,
402 pos: total_height,
403 viewport
404 });
405 cx.begin_turtle(Walk {
406 abs_pos: Some(dvec2(viewport.pos.x, viewport.pos.y)),
407 margin: Default::default(),
408 width: Size::Fill,
409 height: Size::Fit
410 }, Layout::flow_down());
411 return Some(last_index + 1);
412 }
413 }
414 self.draw_state.set(ListDrawState::End {viewport});
415 return None
416 }
417
418 if !did_draw || pos < if hit_bottom {-viewport.size.index(vi)} else {0.0} {
419 self.draw_state.set(ListDrawState::End {viewport});
420 return None
421 }
422
423 self.draw_state.set(ListDrawState::Up {
424 index: index - 1,
425 hit_bottom,
426 pos: pos - rect.size.index(vi),
427 viewport
428 });
429
430 cx.begin_turtle(Walk {
431 abs_pos: Some(dvec2(viewport.pos.x, viewport.pos.y)),
432 margin: Default::default(),
433 width: Size::Fill,
434 height: Size::Fit
435 }, Layout::flow_down());
436
437 return Some(index - 1);
438 }
439 _ => ()
440 }
441 }
442 None
443 }
444
445 pub fn item(&mut self, cx: &mut Cx, entry_id: u64, template: LiveId) -> Option<WidgetRef> {
446 if let Some(ptr) = self.templates.get(&template) {
447 let entry = self.items.get_or_insert(cx, (entry_id, template), | cx | {
448 WidgetRef::new_from_ptr(cx, Some(*ptr))
449 });
450 return Some(entry.clone())
451 }
452 None
453 }
454
455 pub fn set_item_range(&mut self, cx: &mut Cx, range_start: u64, range_end: u64) {
456 self.range_start = range_start;
457 if self.range_end != range_end {
458 self.range_end = range_end;
459 if self.tail_range{
460 self.first_id = self.range_end.max(1) - 1;
461 self.first_scroll = 0.0;
462 }
463 self.update_scroll_bar(cx);
464 }
465 }
466
467 pub fn update_scroll_bar(&mut self, cx: &mut Cx) {
468 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();
469 self.scroll_bar.set_scroll_pos_no_action(cx, scroll_pos);
471 }
472
473 fn delta_top_scroll(&mut self, cx: &mut Cx, delta: f64, clip_top: bool) {
474 self.first_scroll += delta;
475 if self.first_id == self.range_start {
476 self.first_scroll = self.first_scroll.min(self.max_pull_down);
477 }
478 if self.first_id == self.range_start && self.first_scroll > 0.0 && clip_top {
479 self.first_scroll = 0.0;
480 }
481 self.update_scroll_bar(cx);
482 }
483}
484
485
486impl Widget for PortalList {
487 fn redraw(&mut self, cx: &mut Cx) {
488 self.area.redraw(cx);
489 }
490
491 fn handle_widget_event_with(&mut self, cx: &mut Cx, event: &Event, dispatch_action: &mut dyn FnMut(&mut Cx, WidgetActionItem)) {
492 let uid = self.widget_uid();
493
494 let mut scroll_to = None;
495 self.scroll_bar.handle_event_with(cx, event, &mut | _cx, action | {
496 if let ScrollBarAction::Scroll {scroll_pos, view_total, view_visible} = action {
498 scroll_to = Some((scroll_pos, scroll_pos+0.5 >= view_total - view_visible))
499 }
500 });
501 if let Some((scroll_to, at_end)) = scroll_to {
502 if at_end && self.auto_tail{
503 self.first_id = self.range_end.max(1) - 1;
504 self.first_scroll = 0.0;
505 self.tail_range = true;
506 }
507 else if self.tail_range {
508 self.tail_range = false;
509 }
510
511 let scroll_to = ((scroll_to / self.scroll_bar.get_scroll_view_visible()) * self.view_window as f64) as u64;
512 self.first_id = scroll_to;
513 self.first_scroll = 0.0;
514 dispatch_action(cx, WidgetActionItem::new(PortalListAction::Scroll.into(), uid));
515 self.area.redraw(cx);
516 }
517
518 for item in self.items.values_mut() {
519 let item_uid = item.widget_uid();
520 item.handle_widget_event_with(cx, event, &mut | cx, action | {
521 dispatch_action(cx, action.with_container(uid).with_item(item_uid))
522 });
523 }
524
525 match &mut self.scroll_state {
526 ScrollState::Flick {delta, next_frame} => {
527 if let Some(_) = next_frame.is_event(event) {
528 *delta = *delta * self.flick_scroll_decay;
529 if delta.abs()>self.flick_scroll_minimum {
530 *next_frame = cx.new_next_frame();
531 let delta = *delta;
532 self.delta_top_scroll(cx, delta, true);
533 dispatch_action(cx, PortalListAction::Scroll.into_action(uid));
534 self.area.redraw(cx);
535 } else {
536 self.scroll_state = ScrollState::Stopped;
537 }
538 }
539 }
540 ScrollState::Pulldown {next_frame} => {
541 if let Some(_) = next_frame.is_event(event) {
542 if self.first_id == self.range_start && self.first_scroll > 0.0 {
544 self.first_scroll *= 0.9;
545 if self.first_scroll < 1.0 {
546 self.first_scroll = 0.0;
547 }
548 else {
549 *next_frame = cx.new_next_frame();
550 dispatch_action(cx, PortalListAction::Scroll.into_action(uid));
551 }
552 self.area.redraw(cx);
553 }
554 else {
555 self.scroll_state = ScrollState::Stopped
556 }
557 }
558 }
559 _=>()
560 }
561 let vi = self.vec_index;
562 let is_scroll = if let Event::Scroll(_) = event {true} else {false};
563 if self.scroll_bar.is_area_captured(cx){
564 self.scroll_state = ScrollState::Stopped;
565 }
566 if !self.scroll_bar.is_area_captured(cx) || is_scroll{
567 match event.hits_with_capture_overload(cx, self.area, self.capture_overload) {
568 Hit::FingerScroll(e) => {
569 if self.tail_range {
570 self.tail_range = false;
571 }
572 self.detect_tail_in_draw = true;
573 self.scroll_state = ScrollState::Stopped;
574 self.delta_top_scroll(cx, -e.scroll.index(vi), true);
575 dispatch_action(cx, PortalListAction::Scroll.into_action(uid));
576 self.area.redraw(cx);
577 },
578
579 Hit::KeyDown(ke) => match ke.key_code {
580 KeyCode::Home => {
581 self.first_id = 0;
582 self.first_scroll = 0.0;
583 self.tail_range = false;
584 self.update_scroll_bar(cx);
585 self.area.redraw(cx);
586 },
587 KeyCode::End => {
588 self.first_id = self.range_end.max(1) - 1;
589 self.first_scroll = 0.0;
590 if self.auto_tail {
591 self.tail_range = true;
592 }
593 self.update_scroll_bar(cx);
594 self.area.redraw(cx);
595 },
596 KeyCode::PageUp => {
597 self.first_id = self.first_id.max(self.view_window) - self.view_window;
598 self.first_scroll = 0.0;
599 self.tail_range = false;
600 self.update_scroll_bar(cx);
601 self.area.redraw(cx);
602 },
603 KeyCode::PageDown => {
604 self.first_id += self.view_window;
605 self.first_scroll = 0.0;
606 if self.first_id >= self.range_end.max(1) {
607 self.first_id = self.range_end.max(1) - 1;
608 }
609 self.detect_tail_in_draw = true;
610 self.update_scroll_bar(cx);
611 self.area.redraw(cx);
612 },
613 KeyCode::ArrowDown => {
614 self.first_id += 1;
615 if self.first_id >= self.range_end.max(1) {
616 self.first_id = self.range_end.max(1) - 1;
617 }
618 self.detect_tail_in_draw = true;
619 self.first_scroll = 0.0;
620 self.update_scroll_bar(cx);
621 self.area.redraw(cx);
622 },
623 KeyCode::ArrowUp => {
624 if self.first_id > 0 {
625 self.first_id -= 1;
626 if self.first_id < self.range_start {
627 self.first_id = self.range_start;
628 }
629 self.first_scroll = 0.0;
630 self.area.redraw(cx);
631 self.tail_range = false;
632 self.update_scroll_bar(cx);
633 }
634 },
635 _ => ()
636 }
637 Hit::FingerDown(e) => {
638 if self.grab_key_focus {
640 cx.set_key_focus(self.area);
641 }
642 if self.tail_range {
644 self.tail_range = false;
645 }
646 if self.drag_scrolling{
647 self.scroll_state = ScrollState::Drag {
648 samples: vec![ScrollSample{abs:e.abs.index(vi),time:e.time}]
649 };
650 }
651 }
652 Hit::FingerMove(e) => {
653 cx.set_cursor(MouseCursor::Default);
655 match &mut self.scroll_state {
656 ScrollState::Drag {samples}=>{
657 let new_abs = e.abs.index(vi);
658 let old_sample = *samples.last().unwrap();
659 samples.push(ScrollSample{abs:new_abs, time:e.time});
660 if samples.len()>4{
661 samples.remove(0);
662 }
663 self.delta_top_scroll(cx, new_abs - old_sample.abs, false);
664 self.area.redraw(cx);
665 }
666 _=>()
667 }
668 }
669 Hit::FingerUp(_e) => {
670 match &mut self.scroll_state {
672 ScrollState::Drag {samples}=>{
673 let mut last = None;
676 let mut scaled_delta = 0.0;
677 let mut total_delta = 0.0;
678 for sample in samples.iter().rev(){
679 if last.is_none(){
680 last = Some(sample);
681 }
682 else{
683 total_delta += last.unwrap().abs - sample.abs;
684 scaled_delta += (last.unwrap().abs - sample.abs)/ (last.unwrap().time - sample.time)
685 }
686 }
687 scaled_delta *= self.flick_scroll_scaling;
688 if self.first_id == self.range_start && self.first_scroll > 0.0 {
689 self.scroll_state = ScrollState::Pulldown {next_frame: cx.new_next_frame()};
690 }
691 else if total_delta.abs() > 10.0 && scaled_delta.abs() > self.flick_scroll_minimum{
692
693 self.scroll_state = ScrollState::Flick {
694 delta: scaled_delta.min(self.flick_scroll_maximum).max(-self.flick_scroll_maximum),
695 next_frame: cx.new_next_frame()
696 };
697 }
698 else{
699 self.scroll_state = ScrollState::Stopped;
700 }
701 }
702 _=>()
703 }
704 }
707 Hit::KeyFocus(_) => {
708 }
709 Hit::KeyFocusLost(_) => {
710 }
711 _ => ()
712 }
713 }
714 }
715
716 fn walk(&mut self, _cx:&mut Cx) -> Walk {self.walk}
717
718 fn draw_walk_widget(&mut self, cx: &mut Cx2d, walk: Walk) -> WidgetDraw {
719 if self.draw_state.begin(cx, ListDrawState::Begin) {
720 self.begin(cx, walk);
721 return WidgetDraw::hook_above()
722 }
723 if let Some(_) = self.draw_state.get() {
725 self.end(cx);
726 self.draw_state.end();
727 }
728 WidgetDraw::done()
729 }
730}
731
732#[derive(Clone, Default, PartialEq, WidgetRef)]
733pub struct PortalListRef(WidgetRef);
734
735impl PortalListRef {
736 pub fn set_first_id_and_scroll(&self, id: u64, s: f64) {
737 if let Some(mut inner) = self.borrow_mut() {
738 inner.first_id = id;
739 inner.first_scroll = s;
740 }
741 }
742
743 pub fn set_first_id(&self, id: u64) {
744 if let Some(mut inner) = self.borrow_mut() {
745 inner.first_id = id;
746 }
747 }
748
749 pub fn first_id(&self) -> u64 {
750 if let Some(inner) = self.borrow() {
751 inner.first_id
752 }
753 else {
754 0
755 }
756 }
757
758 pub fn set_tail_range(&self, tail_range: bool) {
759 if let Some(mut inner) = self.borrow_mut() {
760 inner.tail_range = tail_range
761 }
762 }
763
764 pub fn item(&self, cx: &mut Cx, entry_id: u64, template: LiveId) -> Option<WidgetRef> {
765 if let Some(mut inner) = self.borrow_mut() {
766 inner.item(cx, entry_id, template)
767 }
768 else {
769 None
770 }
771 }
772
773 pub fn items_with_actions(&self, actions: &WidgetActions) -> Vec<(u64, WidgetRef)> {
774 let mut set = Vec::new();
775 self.items_with_actions_vec(actions, &mut set);
776 set
777 }
778
779 fn items_with_actions_vec(&self, actions: &WidgetActions, set: &mut Vec<(u64, WidgetRef)>) {
780 let uid = self.widget_uid();
781 for action in actions {
782 if action.container_uid == uid {
783
784 if let Some(inner) = self.borrow() {
785 for ((item_id, _), item) in inner.items.iter() {
786
787 if item.widget_uid() == action.item_uid {
788 set.push((*item_id, item.clone()))
789 }
790 }
791 }
792 }
793 }
794 }
795}
796
797#[derive(Clone, Default, WidgetSet)]
798pub struct PortalListSet(WidgetSet);
799
800impl PortalListSet {
801 pub fn set_first_id(&self, id: u64) {
802 for list in self.iter() {
803 list.set_first_id(id)
804 }
805 }
806
807
808 pub fn items_with_actions(&self, actions: &WidgetActions) -> Vec<(u64, WidgetRef)> {
809 let mut set = Vec::new();
810 for list in self.iter() {
811 list.items_with_actions_vec(actions, &mut set)
812 }
813 set
814 }
815}