From 7a8eaefd30240e1d059524b2611d6213ce7c02ae Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 26 Sep 2024 19:40:07 +0800 Subject: [PATCH] story: Add very complex table data update example to test performance. (#274) The example that used: 6000 rows x 60 cols, and refresh data every 100ms. The CPU usage: 40% (macOS M4) https://github.com/user-attachments/assets/40d6c64f-c658-4c7a-9251-fb76e17ec543 --- Cargo.lock | 99 ++++--- crates/story/Cargo.toml | 2 +- crates/story/src/table_story.rs | 487 ++++++++++++++++++++------------ 3 files changed, 363 insertions(+), 225 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b0c7d9e..27be1ff3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1253,6 +1253,40 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.71", +] + [[package]] name = "data-url" version = "0.3.1" @@ -1376,6 +1410,18 @@ dependencies = [ "dtoa", ] +[[package]] +name = "dummy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac124e13ae9aa56acc4241f8c8207501d93afdd8d8e62f0c1f2e12f6508c65" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "dunce" version = "1.0.4" @@ -1557,11 +1603,12 @@ dependencies = [ [[package]] name = "fake" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c25829bde82205da46e1823b2259db6273379f626fc211f126f65654a2669be" +checksum = "2d391ba4af7f1d93f01fcf7b2f29e2bc9348e109dfdbf4dcbdc51dfa38dab0b6" dependencies = [ "deunicode", + "dummy", "rand 0.8.5", ] @@ -2255,7 +2302,7 @@ dependencies = [ "raw-window-handle 0.6.2", "refineable", "resvg", - "schemars 0.8.21", + "schemars", "seahash", "semantic_version", "serde", @@ -2535,6 +2582,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -4391,17 +4444,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "schemars" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be77ed66abed6954aabf6a3e31a84706bedbf93750d267e92ef4a6d90bbd6a61" -dependencies = [ - "schemars_derive 0.7.6", - "serde", - "serde_json", -] - [[package]] name = "schemars" version = "0.8.21" @@ -4409,23 +4451,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", - "schemars_derive 0.8.21", + "schemars_derive", "serde", "serde_json", ] -[[package]] -name = "schemars_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11af7a475c9ee266cfaa9e303a47c830ebe072bf3101ab907a7b7b9d816fa01d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals 0.25.0", - "syn 1.0.109", -] - [[package]] name = "schemars_derive" version = "0.8.21" @@ -4434,7 +4464,7 @@ checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals 0.29.1", + "serde_derive_internals", "syn 2.0.71", ] @@ -4517,17 +4547,6 @@ dependencies = [ "syn 2.0.71", ] -[[package]] -name = "serde_derive_internals" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "serde_derive_internals" version = "0.29.1" @@ -6356,7 +6375,7 @@ dependencies = [ "gpui", "log", "parking_lot", - "schemars 0.7.6", + "schemars", "serde", "smallvec", "ui", diff --git a/crates/story/Cargo.toml b/crates/story/Cargo.toml index d5f3ca50..df147f39 100644 --- a/crates/story/Cargo.toml +++ b/crates/story/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] ui.workspace = true gpui.workspace = true -fake = "2.9.2" +fake = { version = "2.10.0", features = ["dummy"] } charts-rs = "0.3" regex = "1" chrono = "0.4" diff --git a/crates/story/src/table_story.rs b/crates/story/src/table_story.rs index c4d7241a..1aee2046 100644 --- a/crates/story/src/table_story.rs +++ b/crates/story/src/table_story.rs @@ -1,8 +1,8 @@ -use std::time::Duration; +use std::time::{self, Duration}; -use fake::Fake; +use fake::{Fake, Faker}; use gpui::{ - div, img, IntoElement, ParentElement, Pixels, Render, SharedString, Styled, Timer, View, + div, AnyElement, IntoElement, ParentElement, Pixels, Render, SharedString, Styled, Timer, View, ViewContext, VisualContext as _, WindowContext, }; use ui::{ @@ -13,50 +13,125 @@ use ui::{ label::Label, prelude::FluentBuilder as _, table::{ColSort, Table, TableDelegate, TableEvent}, - theme::ActiveTheme as _, - v_flex, Icon, IconName, Selectable, + v_flex, Selectable, }; -struct Customer { +#[derive(Clone, Debug, Default)] +struct Stock { id: usize, - login: String, - first_name: String, - last_name: String, - company: String, - city: String, - country: String, - email: String, - phone: String, - gender: usize, - age: usize, - verified: bool, - confirmed: bool, + symbol: String, + name: String, + price: f64, + change: f64, + change_percent: f64, + volume: f64, + turnover: f64, + market_cap: f64, + ttm: f64, + five_mins_ranking: f64, + th60_days_ranking: f64, + year_change_percent: f64, + bid: f64, + bid_volume: f64, + ask: f64, + ask_volume: f64, + open: f64, + prev_close: f64, + high: f64, + low: f64, + turnover_rate: f64, + rise_rate: f64, + amplitude: f64, + pe_status: f64, + pb_status: f64, + volume_ratio: f64, + bid_ask_ratio: f64, + latest_pre_close: f64, + latest_post_close: f64, + pre_market_cap: f64, + pre_market_percent: f64, + pre_market_change: f64, + post_market_cap: f64, + post_market_percent: f64, + post_market_change: f64, + float_cap: f64, + shares: i64, + shares_float: i64, + day_5_ranking: f64, + day_10_ranking: f64, + day_30_ranking: f64, + day_120_ranking: f64, + day_250_ranking: f64, } -impl Customer { - fn render_avatar(&self, _: &mut WindowContext) -> impl IntoElement { - let image_id = self.id % 70 + 1; - let avatar_url = format!("https://i.pravatar.cc/40?image={}", image_id); - img(avatar_url).size_5().rounded_full() +impl Stock { + fn random_update(&mut self) { + self.price = (-300.0..999.999).fake::(); + self.change = (-0.1..5.0).fake::(); + self.change_percent = (-0.1..0.1).fake::(); + self.volume = (-300.0..999.999).fake::(); + self.turnover = (-300.0..999.999).fake::(); + self.market_cap = (-1000.0..9999.999).fake::(); + self.ttm = (-1000.0..9999.999).fake::(); + self.five_mins_ranking = self.five_mins_ranking * (1.0 + (-0.2..0.2).fake::()); + self.bid = self.price * (1.0 + (-0.2..0.2).fake::()); + self.bid_volume = (100.0..1000.0).fake::(); + self.ask = self.price * (1.0 + (-0.2..0.2).fake::()); + self.ask_volume = (100.0..1000.0).fake::(); + self.bid_ask_ratio = self.bid / self.ask; + self.volume_ratio = self.volume / self.turnover; + self.high = self.price * (1.0 + (0.0..1.5).fake::()); + self.low = self.price * (1.0 + (-1.5..0.0).fake::()); } } -fn randome_customers(size: usize) -> Vec { +fn random_stocks(size: usize) -> Vec { (0..size) - .map(|id| Customer { + .map(|id| Stock { id, - login: fake::faker::internet::en::Username().fake::(), - first_name: fake::faker::name::en::FirstName().fake::(), - last_name: fake::faker::name::en::LastName().fake::(), - company: fake::faker::company::en::CompanyName().fake::(), - city: fake::faker::address::en::CityName().fake::(), - country: fake::faker::address::en::CountryName().fake::(), - email: fake::faker::internet::en::FreeEmail().fake::(), - phone: fake::faker::phone_number::en::PhoneNumber().fake::(), - gender: (0..=1).fake(), - age: (18..80).fake(), - verified: (0..=1).fake::() == 1, - confirmed: (0..=1).fake::() == 1, + symbol: Faker.fake::(), + name: Faker.fake::(), + change: (-100.0..100.0).fake(), + change_percent: (-1.0..1.0).fake(), + volume: (0.0..1000.0).fake(), + turnover: (0.0..1000.0).fake(), + market_cap: (0.0..1000.0).fake(), + ttm: (0.0..1000.0).fake(), + five_mins_ranking: (0.0..1000.0).fake(), + th60_days_ranking: (0.0..1000.0).fake(), + year_change_percent: (-1.0..1.0).fake(), + bid: (0.0..1000.0).fake(), + bid_volume: (0.0..1000.0).fake(), + ask: (0.0..1000.0).fake(), + ask_volume: (0.0..1000.0).fake(), + open: (0.0..1000.0).fake(), + prev_close: (0.0..1000.0).fake(), + high: (0.0..1000.0).fake(), + low: (0.0..1000.0).fake(), + turnover_rate: (0.0..1.0).fake(), + rise_rate: (0.0..1.0).fake(), + amplitude: (0.0..1000.0).fake(), + pe_status: (0.0..1000.0).fake(), + pb_status: (0.0..1000.0).fake(), + volume_ratio: (0.0..1.0).fake(), + bid_ask_ratio: (0.0..1.0).fake(), + latest_pre_close: (0.0..1000.0).fake(), + latest_post_close: (0.0..1000.0).fake(), + pre_market_cap: (0.0..1000.0).fake(), + pre_market_percent: (-1.0..1.0).fake(), + pre_market_change: (-100.0..100.0).fake(), + post_market_cap: (0.0..1000.0).fake(), + post_market_percent: (-1.0..1.0).fake(), + post_market_change: (-100.0..100.0).fake(), + float_cap: (0.0..1000.0).fake(), + shares: (100000..9999999).fake(), + shares_float: (100000..9999999).fake(), + day_5_ranking: (0.0..1000.0).fake(), + day_10_ranking: (0.0..1000.0).fake(), + day_30_ranking: (0.0..1000.0).fake(), + day_120_ranking: (0.0..1000.0).fake(), + day_250_ranking: (0.0..1000.0).fake(), + ..Default::default() }) .collect() } @@ -81,8 +156,8 @@ impl Column { } } -struct CustomerTableDelegate { - customers: Vec, +struct StockTableDelegate { + stocks: Vec, columns: Vec, loop_selection: bool, col_resize: bool, @@ -93,25 +168,67 @@ struct CustomerTableDelegate { is_eof: bool, } -impl CustomerTableDelegate { +impl StockTableDelegate { fn new(size: usize) -> Self { Self { - customers: randome_customers(size), + stocks: random_stocks(size), columns: vec![ - Column::new("id", "ID", Some(ColSort::Ascending)), - Column::new("login", "Login", Some(ColSort::Default)), - Column::new("first_name", "First Name", Some(ColSort::Default)), - Column::new("last_name", "Last Name", Some(ColSort::Default)), - Column::new("company", "Company", Some(ColSort::Default)), - Column::new("city", "City", Some(ColSort::Default)), - Column::new("country", "Country", Some(ColSort::Default)), - Column::new("email", "Email", Some(ColSort::Default)), - Column::new("phone", "Phone", None), - Column::new("gender", "Gender", None), - Column::new("age", "Age", Some(ColSort::Default)), - Column::new("verified", "Verified", None), - Column::new("confirmed", "Confirmed", None), - Column::new("twitter", "Twitter", None), + Column::new("id", "ID", None), + Column::new("symbol", "Symbol", Some(ColSort::Ascending)), + Column::new("name", "Name", None), + Column::new("price", "Price", Some(ColSort::Ascending)), + Column::new("change", "Chg", Some(ColSort::Ascending)), + Column::new("change_percent", "Chg%", Some(ColSort::Ascending)), + Column::new("volume", "Volume", Some(ColSort::Ascending)), + Column::new("turnover", "Turnover", Some(ColSort::Ascending)), + Column::new("market_cap", "Market Cap", Some(ColSort::Ascending)), + Column::new("ttm", "TTM", Some(ColSort::Ascending)), + Column::new("five_mins_ranking", "5m Ranking", Some(ColSort::Ascending)), + Column::new("th60_days_ranking", "60d Ranking", Some(ColSort::Ascending)), + Column::new("year_change_percent", "Year Chg%", Some(ColSort::Ascending)), + Column::new("bid", "Bid", Some(ColSort::Ascending)), + Column::new("bid_volume", "Bid Vol", Some(ColSort::Ascending)), + Column::new("ask", "Ask", Some(ColSort::Ascending)), + Column::new("ask_volume", "Ask Vol", Some(ColSort::Ascending)), + Column::new("open", "Open", Some(ColSort::Ascending)), + Column::new("prev_close", "Prev Close", Some(ColSort::Ascending)), + Column::new("high", "High", Some(ColSort::Ascending)), + Column::new("low", "Low", Some(ColSort::Ascending)), + Column::new("turnover_rate", "Turnover Rate", Some(ColSort::Ascending)), + Column::new("rise_rate", "Rise Rate", Some(ColSort::Ascending)), + Column::new("amplitude", "Amplitude", Some(ColSort::Ascending)), + Column::new("pe_status", "P/E", Some(ColSort::Ascending)), + Column::new("pb_status", "P/B", Some(ColSort::Ascending)), + Column::new("volume_ratio", "Volume Ratio", Some(ColSort::Ascending)), + Column::new("bid_ask_ratio", "Bid Ask Ratio", Some(ColSort::Ascending)), + Column::new( + "latest_pre_close", + "Latest Pre Close", + Some(ColSort::Ascending), + ), + Column::new( + "latest_post_close", + "Latest Post Close", + Some(ColSort::Ascending), + ), + Column::new("pre_market_cap", "Pre Mkt Cap", Some(ColSort::Ascending)), + Column::new("pre_market_percent", "Pre Mkt%", Some(ColSort::Ascending)), + Column::new("pre_market_change", "Pre Mkt Chg", Some(ColSort::Ascending)), + Column::new("post_market_cap", "Post Mkt Cap", Some(ColSort::Ascending)), + Column::new("post_market_percent", "Post Mkt%", Some(ColSort::Ascending)), + Column::new( + "post_market_change", + "Post Mkt Chg", + Some(ColSort::Ascending), + ), + Column::new("float_cap", "Float Cap", Some(ColSort::Ascending)), + Column::new("shares", "Shares", Some(ColSort::Ascending)), + Column::new("shares_float", "Float Shares", Some(ColSort::Ascending)), + Column::new("day_5_ranking", "5d Ranking", Some(ColSort::Ascending)), + Column::new("day_10_ranking", "10d Ranking", Some(ColSort::Ascending)), + Column::new("day_30_ranking", "30d Ranking", Some(ColSort::Ascending)), + Column::new("day_120_ranking", "120d Ranking", Some(ColSort::Ascending)), + Column::new("day_250_ranking", "250d Ranking", Some(ColSort::Ascending)), ], loop_selection: true, col_resize: true, @@ -123,20 +240,37 @@ impl CustomerTableDelegate { } } - fn update_customers(&mut self, size: usize) { - self.customers = randome_customers(size); + fn update_stocks(&mut self, size: usize) { + self.stocks = random_stocks(size); self.is_eof = false; self.loading = false; } + + fn render_value_cell(&self, val: f64) -> AnyElement { + let this = div().child(format!("{:.3}", val)); + // Val is a 0.0 .. n.0 + // 30% to red, 30% to green, others to default + let right_num = ((val - val.floor()) * 1000.).floor() as i32; + + let this = if right_num % 3 == 0 { + this.text_color(ui::red_600()).bg(ui::red_50()) + } else if right_num % 3 == 1 { + this.text_color(ui::green_600()).bg(ui::green_50()) + } else { + this + }; + + this.into_any_element() + } } -impl TableDelegate for CustomerTableDelegate { +impl TableDelegate for StockTableDelegate { fn cols_count(&self) -> usize { self.columns.len() } fn rows_count(&self) -> usize { - self.customers.len() + self.stocks.len() } fn col_name(&self, col_ix: usize) -> SharedString { @@ -151,21 +285,7 @@ impl TableDelegate for CustomerTableDelegate { if let Some(col) = self.columns.get(col_ix) { Some( match col.id.as_ref() { - "id" => 100.0, - "login" => 220.0, - "first_name" => 150.0, - "last_name" => 150.0, - "company" => 300.0, - "city" => 200.0, - "country" => 200.0, - "email" => 350.0, - "phone" => 240.0, - "gender" => 80.0, - "age" => 90.0, - "verified" => 90.0, - "confirmed" => 90.0, - "twitter" => 90.0, - _ => 200.0, + _ => 120.0, } .into(), ) @@ -186,50 +306,63 @@ impl TableDelegate for CustomerTableDelegate { &self, row_ix: usize, col_ix: usize, - cx: &mut ViewContext>, + _cx: &mut ViewContext>, ) -> impl IntoElement { - let customer = self.customers.get(row_ix).unwrap(); - + let stock = self.stocks.get(row_ix).unwrap(); let col = self.columns.get(col_ix).unwrap(); + match col.id.as_ref() { - "id" => customer.id.to_string().into_any_element(), - "login" => h_flex() - .items_center() - .gap_2() - .child(customer.render_avatar(cx)) - .child(customer.login.clone()) + "id" => stock.id.to_string().into_any_element(), + "name" => stock.name.clone().into_any_element(), + "symbol" => stock.symbol.clone().into_any_element(), + "price" => self.render_value_cell(stock.price), + "change" => self.render_value_cell(stock.change), + "change_percent" => self.render_value_cell(stock.change_percent), + "volume" => self.render_value_cell(stock.volume), + "turnover" => self.render_value_cell(stock.turnover), + "market_cap" => self.render_value_cell(stock.market_cap), + "ttm" => self.render_value_cell(stock.ttm), + "five_mins_ranking" => self.render_value_cell(stock.five_mins_ranking), + "th60_days_ranking" => stock.th60_days_ranking.to_string().into_any_element(), + "year_change_percent" => (stock.year_change_percent * 100.0) + .to_string() .into_any_element(), - "first_name" => customer.first_name.clone().into_any_element(), - "last_name" => customer.last_name.clone().into_any_element(), - "company" => h_flex() - .items_center() - .justify_between() - .gap_1() - .child(customer.company.clone()) - .child(IconName::Info) + "bid" => self.render_value_cell(stock.bid), + "bid_volume" => self.render_value_cell(stock.bid_volume), + "ask" => self.render_value_cell(stock.ask), + "ask_volume" => self.render_value_cell(stock.ask_volume), + "open" => stock.open.to_string().into_any_element(), + "prev_close" => stock.prev_close.to_string().into_any_element(), + "high" => self.render_value_cell(stock.high), + "low" => self.render_value_cell(stock.low), + "turnover_rate" => (stock.turnover_rate * 100.0).to_string().into_any_element(), + "rise_rate" => (stock.rise_rate * 100.0).to_string().into_any_element(), + "amplitude" => (stock.amplitude * 100.0).to_string().into_any_element(), + "pe_status" => stock.pe_status.to_string().into_any_element(), + "pb_status" => stock.pb_status.to_string().into_any_element(), + "volume_ratio" => self.render_value_cell(stock.volume_ratio), + "bid_ask_ratio" => self.render_value_cell(stock.bid_ask_ratio), + "latest_pre_close" => stock.latest_pre_close.to_string().into_any_element(), + "latest_post_close" => stock.latest_post_close.to_string().into_any_element(), + "pre_market_cap" => stock.pre_market_cap.to_string().into_any_element(), + "pre_market_percent" => (stock.pre_market_percent * 100.0) + .to_string() .into_any_element(), - "city" => customer.city.clone().into_any_element(), - "country" => customer.country.clone().into_any_element(), - "email" => customer.email.clone().into_any_element(), - "phone" => customer.phone.clone().into_any_element(), - "gender" => match customer.gender { - 0 => "Male", - 1 => "Famale", - _ => "", - } - .into_any_element(), - "age" => customer.age.to_string().into_any_element(), - "verified" => match customer.verified { - true => "Yes".to_string().into_any_element(), - false => "No".to_string().into_any_element(), - }, - "confirmed" => match customer.confirmed { - true => Icon::new(IconName::Check).size_4().into_any_element(), - false => div().into_any_element(), - }, - _ => Label::new("--") - .text_color(cx.theme().muted_foreground) + "pre_market_change" => stock.pre_market_change.to_string().into_any_element(), + "post_market_cap" => stock.post_market_cap.to_string().into_any_element(), + "post_market_percent" => (stock.post_market_percent * 100.0) + .to_string() .into_any_element(), + "post_market_change" => stock.post_market_change.to_string().into_any_element(), + "float_cap" => stock.float_cap.to_string().into_any_element(), + "shares" => stock.shares.to_string().into_any_element(), + "shares_float" => stock.shares_float.to_string().into_any_element(), + "day_5_ranking" => stock.day_5_ranking.to_string().into_any_element(), + "day_10_ranking" => stock.day_10_ranking.to_string().into_any_element(), + "day_30_ranking" => stock.day_30_ranking.to_string().into_any_element(), + "day_120_ranking" => stock.day_120_ranking.to_string().into_any_element(), + "day_250_ranking" => stock.day_250_ranking.to_string().into_any_element(), + _ => "--".to_string().into_any_element(), } } @@ -264,69 +397,13 @@ impl TableDelegate for CustomerTableDelegate { let asc = matches!(sort, ColSort::Ascending); match col.id.as_ref() { - "id" => self.customers.sort_by(|a, b| { + "id" => self.stocks.sort_by(|a, b| { if asc { a.id.cmp(&b.id) } else { b.id.cmp(&a.id) } }), - "login" => self.customers.sort_by(|a, b| { - if asc { - a.login.cmp(&b.login) - } else { - b.login.cmp(&a.login) - } - }), - "first_name" => self.customers.sort_by(|a, b| { - if asc { - a.first_name.cmp(&b.first_name) - } else { - b.first_name.cmp(&a.first_name) - } - }), - "last_name" => self.customers.sort_by(|a, b| { - if asc { - a.last_name.cmp(&b.last_name) - } else { - b.last_name.cmp(&a.last_name) - } - }), - "company" => self.customers.sort_by(|a, b| { - if asc { - a.company.cmp(&b.company) - } else { - b.company.cmp(&a.company) - } - }), - "city" => self.customers.sort_by(|a, b| { - if asc { - a.city.cmp(&b.city) - } else { - b.city.cmp(&a.city) - } - }), - "country" => self.customers.sort_by(|a, b| { - if asc { - a.country.cmp(&b.country) - } else { - b.country.cmp(&a.country) - } - }), - "email" => self.customers.sort_by(|a, b| { - if asc { - a.email.cmp(&b.email) - } else { - b.email.cmp(&a.email) - } - }), - "age" => self.customers.sort_by(|a, b| { - if asc { - a.age.cmp(&b.age) - } else { - b.age.cmp(&a.age) - } - }), _ => {} } @@ -355,9 +432,9 @@ impl TableDelegate for CustomerTableDelegate { cx.update(|cx| { let _ = view.update(cx, |view, _| { - view.delegate_mut().customers.extend(randome_customers(200)); + view.delegate_mut().stocks.extend(random_stocks(200)); view.delegate_mut().loading = false; - view.delegate_mut().is_eof = view.delegate().customers.len() >= 6000; + view.delegate_mut().is_eof = view.delegate().stocks.len() >= 6000; }); }) }) @@ -366,9 +443,10 @@ impl TableDelegate for CustomerTableDelegate { } pub struct TableStory { - table: View>, - num_customers_input: View, + table: View>, + num_stocks_input: View, stripe: bool, + refresh_data: bool, } impl super::Story for TableStory { @@ -402,30 +480,60 @@ impl TableStory { fn new(cx: &mut ViewContext) -> Self { // Create the number input field with validation for positive integers - let num_customers_input = cx.new_view(|cx| { + let num_stocks_input = cx.new_view(|cx| { let mut input = TextInput::new(cx) - .placeholder("Enter number of customers") + .placeholder("Enter number of Stocks to display") .validate(|s| s.parse::().is_ok()); input.set_text("5000", cx); input }); - let delegate = CustomerTableDelegate::new(5000); + let delegate = StockTableDelegate::new(5000); let table = cx.new_view(|cx| Table::new(delegate, cx)); cx.subscribe(&table, Self::on_table_event).detach(); - cx.subscribe(&num_customers_input, Self::on_num_customers_input_change) + cx.subscribe(&num_stocks_input, Self::on_num_stocks_input_change) .detach(); + // Spawn a background to random refresh the list + cx.spawn(move |this, mut cx| async move { + loop { + let delay = (80..150).fake::(); + Timer::after(time::Duration::from_millis(delay)).await; + + this.update(&mut cx, |this, cx| { + if !this.refresh_data { + return; + } + + this.table.update(cx, |table, _| { + table.delegate_mut().stocks.iter_mut().enumerate().for_each( + |(i, stock)| { + let n = (3..10).fake::(); + // update 30% of the stocks + if i % n == 0 { + stock.random_update(); + } + }, + ); + }); + cx.notify(); + }) + .ok(); + } + }) + .detach(); + Self { table, - num_customers_input, + num_stocks_input, stripe: false, + refresh_data: false, } } // Event handler for changes in the number input field - fn on_num_customers_input_change( + fn on_num_stocks_input_change( &mut self, _: View, event: &InputEvent, @@ -434,10 +542,10 @@ impl TableStory { match event { // Update when the user presses Enter or the input loses focus InputEvent::PressEnter | InputEvent::Blur => { - let text = self.num_customers_input.read(cx).text().to_string(); + let text = self.num_stocks_input.read(cx).text().to_string(); if let Ok(num) = text.parse::() { self.table.update(cx, |table, _| { - table.delegate_mut().update_customers(num); + table.delegate_mut().update_stocks(num); }); cx.notify(); } @@ -496,9 +604,14 @@ impl TableStory { }); } + fn toggle_refresh_data(&mut self, checked: &bool, cx: &mut ViewContext) { + self.refresh_data = *checked; + cx.notify(); + } + fn on_table_event( &mut self, - _: View>, + _: View>, event: &TableEvent, _cx: &mut ViewContext, ) { @@ -558,6 +671,12 @@ impl Render for TableStory { .label("Stripe") .selected(self.stripe) .on_click(cx.listener(Self::toggle_stripe)), + ) + .child( + Checkbox::new("refresh-data") + .label("Refresh Data") + .selected(self.refresh_data) + .on_click(cx.listener(Self::toggle_refresh_data)), ), ) .child( @@ -565,11 +684,11 @@ impl Render for TableStory { h_flex() .items_center() .gap_1() - .child(Label::new("Number of Customers:")) + .child(Label::new("Number of Stocks:")) .child( h_flex() .min_w_32() - .child(self.num_customers_input.clone()) + .child(self.num_stocks_input.clone()) .into_any_element(), ) .when(delegate.loading, |this| {