diff --git a/crates/story/src/list_story.rs b/crates/story/src/list_story.rs index fb2560eb..8da40510 100644 --- a/crates/story/src/list_story.rs +++ b/crates/story/src/list_story.rs @@ -1,4 +1,5 @@ use core::time; +use std::time::Duration; use fake::Fake; use gpui::{ @@ -135,6 +136,9 @@ struct CompanyListDelegate { matched_companies: Vec, selected_index: usize, confirmed_index: Option, + query: String, + loading: bool, + is_eof: bool, } impl ListDelegate for CompanyListDelegate { @@ -149,6 +153,7 @@ impl ListDelegate for CompanyListDelegate { } fn perform_search(&mut self, query: &str, _: &mut ViewContext>) -> Task<()> { + self.query = query.to_string(); self.matched_companies = self .companies .iter() @@ -214,6 +219,38 @@ impl ListDelegate for CompanyListDelegate { None } + + fn can_load_more(&self, _: &AppContext) -> bool { + return !self.loading && !self.is_eof; + } + + fn load_more_threshold(&self) -> usize { + 150 + } + + fn load_more(&mut self, cx: &mut ViewContext>) { + self.loading = true; + + cx.spawn(|view, mut cx| async move { + dbg!("Load more data"); + // Simulate network request, delay 1s to load data. + Timer::after(Duration::from_secs(1)).await; + + cx.update(|cx| { + _ = view.update(cx, move |view, cx| { + let query = view.delegate().query.clone(); + view.delegate_mut() + .companies + .extend((0..200).map(|_| random_company())); + view.delegate_mut().perform_search(&query, cx); + view.delegate_mut().loading = false; + dbg!("Loaded more data len: ", view.delegate().companies.len()); + view.delegate_mut().is_eof = view.delegate().companies.len() >= 6000; + }); + }) + }) + .detach(); + } } impl CompanyListDelegate { @@ -259,6 +296,9 @@ impl ListStory { companies, selected_index: 0, confirmed_index: None, + query: "".to_string(), + loading: false, + is_eof: false, }, cx, ) diff --git a/crates/ui/src/list/list.rs b/crates/ui/src/list/list.rs index 688f12df..0d0e2527 100644 --- a/crates/ui/src/list/list.rs +++ b/crates/ui/src/list/list.rs @@ -1,3 +1,4 @@ +use std::ops::Range; use std::time::Duration; use std::{cell::Cell, rc::Rc}; @@ -77,6 +78,29 @@ pub trait ListDelegate: Sized + 'static { /// Cancel the selection, e.g.: Pressed ESC. fn cancel(&mut self, cx: &mut ViewContext>) {} + + /// Return true to enable load more data when scrolling to the bottom. + /// + /// Default: true + fn can_load_more(&self, cx: &AppContext) -> bool { + true + } + + /// Returns a threshold value (n rows), of course, when scrolling to the bottom, + /// the remaining number of rows triggers `load_more`. + /// + /// Default: 50 rows + fn load_more_threshold(&self) -> usize { + 50 + } + + /// Load more data when the table is scrolled to the bottom. + /// + /// This will performed in a background task. + /// + /// This is always called when the table is near the bottom, + /// so you must check if there is more data to load or lock the loading state. + fn load_more(&mut self, cx: &mut ViewContext>) {} } pub struct List { @@ -259,6 +283,31 @@ where cx.notify(); } + /// Dispatch delegate's `load_more` method when the visible range is near the end. + fn load_more_if_need(&mut self, visible_range: Range, cx: &mut ViewContext) { + if !self.delegate.can_load_more(cx) { + return; + } + + let items_count = self.delegate.items_count(cx); + let threshold = self.delegate.load_more_threshold(); + if items_count < threshold { + return; + } + + // Securely handle subtract logic to prevent attempt to subtract with overflow + if visible_range.end >= items_count - threshold { + cx.spawn(|view, mut cx| async move { + cx.update(|cx| { + view.update(cx, |view, cx| { + view.delegate.load_more(cx); + }) + }) + }) + .detach() + } + } + fn on_action_cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { self.set_selected_index(None, cx); self.delegate.cancel(cx); @@ -430,6 +479,8 @@ where this.child( uniform_list(view, "uniform-list", items_count, { move |list, visible_range, cx| { + list.load_more_if_need(visible_range.clone(), cx); + visible_range .map(|ix| list.render_list_item(ix, cx)) .collect::>() diff --git a/crates/ui/src/table.rs b/crates/ui/src/table.rs index c2b56bdb..3fdb7f26 100644 --- a/crates/ui/src/table.rs +++ b/crates/ui/src/table.rs @@ -583,26 +583,27 @@ where } /// Dispatch delegate's `load_more` method when the visible range is near the end. - fn load_more(&mut self, visible_range: Range, cx: &mut ViewContext) { + fn load_more_if_need(&mut self, visible_range: Range, cx: &mut ViewContext) { if !self.delegate.can_load_more(cx) { return; } let row_count = self.delegate.rows_count(cx); - let load_more_count = self.delegate.load_more_threshold(); + let threshold = self.delegate.load_more_threshold(); + if row_count < threshold { + return; + } // Securely handle subtract logic to prevent attempt to subtract with overflow - if row_count >= load_more_count { - if visible_range.end >= row_count - load_more_count { - cx.spawn(|view, mut cx| async move { - cx.update(|cx| { - view.update(cx, |view, cx| { - view.delegate.load_more(cx); - }) + if visible_range.end >= row_count - threshold { + cx.spawn(|view, mut cx| async move { + cx.update(|cx| { + view.update(cx, |view, cx| { + view.delegate.load_more(cx); }) }) - .detach() - } + }) + .detach() } } @@ -1202,7 +1203,7 @@ where rows_count + extra_rows_needed, { move |table, visible_range, cx| { - table.load_more(visible_range.clone(), cx); + table.load_more_if_need(visible_range.clone(), cx); if visible_range.end > rows_count { table.scroll_to_row(