From e658aeee96bf4c004f41e31440ac86ad125ef77b Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Sun, 26 Jan 2025 16:25:34 +0800 Subject: [PATCH] table: Improve sort icon color and fix stripe row display. (#578) - Improve sort icon color: image - Avoid duplicate border when enabled fixed column. image TODO - [x] The extra rows not show when `stripe` is true. --- assets/icons/sort-ascending.svg | 17 ++++- assets/icons/sort-descending.svg | 17 ++++- crates/story/src/table_story.rs | 101 +++++++++++++------------ crates/ui/src/table.rs | 126 +++++++++++++++++++------------ 4 files changed, 160 insertions(+), 101 deletions(-) diff --git a/assets/icons/sort-ascending.svg b/assets/icons/sort-ascending.svg index 9be1b977..bc17bd79 100644 --- a/assets/icons/sort-ascending.svg +++ b/assets/icons/sort-ascending.svg @@ -1,4 +1,15 @@ - - - + + + diff --git a/assets/icons/sort-descending.svg b/assets/icons/sort-descending.svg index ab650568..f41eecbe 100644 --- a/assets/icons/sort-descending.svg +++ b/assets/icons/sort-descending.svg @@ -1,4 +1,15 @@ - - - + + + diff --git a/crates/story/src/table_story.rs b/crates/story/src/table_story.rs index c1561157..8597332f 100644 --- a/crates/story/src/table_story.rs +++ b/crates/story/src/table_story.rs @@ -200,52 +200,44 @@ impl StockTableDelegate { Column::new("price", "Price", Some(ColSort::Default)), Column::new("change", "Chg", Some(ColSort::Default)), Column::new("change_percent", "Chg%", Some(ColSort::Default)), - Column::new("volume", "Volume", Some(ColSort::Default)), - Column::new("turnover", "Turnover", Some(ColSort::Default)), - Column::new("market_cap", "Market Cap", Some(ColSort::Default)), - Column::new("ttm", "TTM", Some(ColSort::Default)), - Column::new("five_mins_ranking", "5m Ranking", Some(ColSort::Default)), - Column::new("th60_days_ranking", "60d Ranking", Some(ColSort::Default)), - Column::new("year_change_percent", "Year Chg%", Some(ColSort::Default)), - Column::new("bid", "Bid", Some(ColSort::Default)), - Column::new("bid_volume", "Bid Vol", Some(ColSort::Default)), - Column::new("ask", "Ask", Some(ColSort::Default)), - Column::new("ask_volume", "Ask Vol", Some(ColSort::Default)), - Column::new("open", "Open", Some(ColSort::Default)), - Column::new("prev_close", "Prev Close", Some(ColSort::Default)), - Column::new("high", "High", Some(ColSort::Default)), - Column::new("low", "Low", Some(ColSort::Default)), - Column::new("turnover_rate", "Turnover Rate", Some(ColSort::Default)), - Column::new("rise_rate", "Rise Rate", Some(ColSort::Default)), - Column::new("amplitude", "Amplitude", Some(ColSort::Default)), - Column::new("pe_status", "P/E", Some(ColSort::Default)), - Column::new("pb_status", "P/B", Some(ColSort::Default)), - Column::new("volume_ratio", "Volume Ratio", Some(ColSort::Default)), - Column::new("bid_ask_ratio", "Bid Ask Ratio", Some(ColSort::Default)), - Column::new( - "latest_pre_close", - "Latest Pre Close", - Some(ColSort::Default), - ), - Column::new( - "latest_post_close", - "Latest Post Close", - Some(ColSort::Default), - ), - Column::new("pre_market_cap", "Pre Mkt Cap", Some(ColSort::Default)), - Column::new("pre_market_percent", "Pre Mkt%", Some(ColSort::Default)), - Column::new("pre_market_change", "Pre Mkt Chg", Some(ColSort::Default)), - Column::new("post_market_cap", "Post Mkt Cap", Some(ColSort::Default)), - Column::new("post_market_percent", "Post Mkt%", Some(ColSort::Default)), - Column::new("post_market_change", "Post Mkt Chg", Some(ColSort::Default)), - Column::new("float_cap", "Float Cap", Some(ColSort::Default)), - Column::new("shares", "Shares", Some(ColSort::Default)), - Column::new("shares_float", "Float Shares", Some(ColSort::Default)), - Column::new("day_5_ranking", "5d Ranking", Some(ColSort::Default)), - Column::new("day_10_ranking", "10d Ranking", Some(ColSort::Default)), - Column::new("day_30_ranking", "30d Ranking", Some(ColSort::Default)), - Column::new("day_120_ranking", "120d Ranking", Some(ColSort::Default)), - Column::new("day_250_ranking", "250d Ranking", Some(ColSort::Default)), + Column::new("volume", "Volume", None), + Column::new("turnover", "Turnover", None), + Column::new("market_cap", "Market Cap", None), + Column::new("ttm", "TTM", None), + Column::new("five_mins_ranking", "5m Ranking", None), + Column::new("th60_days_ranking", "60d Ranking", None), + Column::new("year_change_percent", "Year Chg%", None), + Column::new("bid", "Bid", None), + Column::new("bid_volume", "Bid Vol", None), + Column::new("ask", "Ask", None), + Column::new("ask_volume", "Ask Vol", None), + Column::new("open", "Open", None), + Column::new("prev_close", "Prev Close", None), + Column::new("high", "High", None), + Column::new("low", "Low", None), + Column::new("turnover_rate", "Turnover Rate", None), + Column::new("rise_rate", "Rise Rate", None), + Column::new("amplitude", "Amplitude", None), + Column::new("pe_status", "P/E", None), + Column::new("pb_status", "P/B", None), + Column::new("volume_ratio", "Volume Ratio", None), + Column::new("bid_ask_ratio", "Bid Ask Ratio", None), + Column::new("latest_pre_close", "Latest Pre Close", None), + Column::new("latest_post_close", "Latest Post Close", None), + Column::new("pre_market_cap", "Pre Mkt Cap", None), + Column::new("pre_market_percent", "Pre Mkt%", None), + Column::new("pre_market_change", "Pre Mkt Chg", None), + Column::new("post_market_cap", "Post Mkt Cap", None), + Column::new("post_market_percent", "Post Mkt%", None), + Column::new("post_market_change", "Post Mkt Chg", None), + Column::new("float_cap", "Float Cap", None), + Column::new("shares", "Shares", None), + Column::new("shares_float", "Float Shares", None), + Column::new("day_5_ranking", "5d Ranking", None), + Column::new("day_10_ranking", "10d Ranking", None), + Column::new("day_30_ranking", "30d Ranking", None), + Column::new("day_120_ranking", "120d Ranking", None), + Column::new("day_250_ranking", "250d Ranking", None), ], loop_selection: true, col_resize: true, @@ -263,7 +255,7 @@ impl StockTableDelegate { fn update_stocks(&mut self, size: usize) { self.stocks = random_stocks(size); - self.is_eof = false; + self.is_eof = size <= 50; self.loading = false; self.full_loading = false; } @@ -477,6 +469,17 @@ impl TableDelegate for StockTableDelegate { ColSort::Descending => b.id.cmp(&a.id), _ => a.id.cmp(&b.id), }), + "symbol" => self.stocks.sort_by(|a, b| match sort { + ColSort::Descending => b.symbol.cmp(&a.symbol), + _ => a.id.cmp(&b.id), + }), + "change" | "change_percent" => self.stocks.sort_by(|a, b| match sort { + ColSort::Descending => b + .change + .partial_cmp(&a.change) + .unwrap_or(std::cmp::Ordering::Equal), + _ => a.id.cmp(&b.id), + }), _ => {} } } @@ -652,6 +655,7 @@ impl TableStory { fn toggle_col_resize(&mut self, checked: &bool, cx: &mut ViewContext) { self.table.update(cx, |table, cx| { table.delegate_mut().col_resize = *checked; + table.refresh(cx); cx.notify(); }); } @@ -659,6 +663,7 @@ impl TableStory { fn toggle_col_order(&mut self, checked: &bool, cx: &mut ViewContext) { self.table.update(cx, |table, cx| { table.delegate_mut().col_order = *checked; + table.refresh(cx); cx.notify(); }); } @@ -666,6 +671,7 @@ impl TableStory { fn toggle_col_sort(&mut self, checked: &bool, cx: &mut ViewContext) { self.table.update(cx, |table, cx| { table.delegate_mut().col_sort = *checked; + table.refresh(cx); cx.notify(); }); } @@ -673,6 +679,7 @@ impl TableStory { fn toggle_col_selection(&mut self, checked: &bool, cx: &mut ViewContext) { self.table.update(cx, |table, cx| { table.delegate_mut().col_selection = *checked; + table.refresh(cx); cx.notify(); }); } diff --git a/crates/ui/src/table.rs b/crates/ui/src/table.rs index 88cc432b..e769f6a0 100644 --- a/crates/ui/src/table.rs +++ b/crates/ui/src/table.rs @@ -753,9 +753,14 @@ where } } + #[inline] fn render_cell(&self, col_ix: usize, _cx: &mut ViewContext) -> Div { - let col_width = self.col_groups[col_ix].width; - let col_padding = self.col_groups[col_ix].padding; + let Some(col_group) = self.col_groups.get(col_ix) else { + return div(); + }; + + let col_width = col_group.width; + let col_padding = col_group.padding; div() .w(col_width) @@ -1037,11 +1042,9 @@ where // Render left fixed columns this.child( h_flex() - .id("table-head-fixed-left") + .relative() .h_full() .bg(cx.theme().table_head) - .border_r_1() - .border_color(cx.theme().border) .children( self.col_groups .iter() @@ -1049,6 +1052,18 @@ where .enumerate() .map(|(col_ix, _)| self.render_th(col_ix, cx)), ) + .child( + // Fixed columns border + div() + .absolute() + .top_0() + .right_0() + .bottom_0() + .w_0() + .flex_shrink_0() + .border_r_1() + .border_color(cx.theme().border), + ) .child( canvas( move |bounds, cx| { @@ -1102,6 +1117,7 @@ where row_ix: usize, rows_count: usize, left_cols_count: usize, + col_sizes: Rc>>, cols_count: usize, cx: &mut ViewContext, ) -> impl IntoElement { @@ -1109,13 +1125,6 @@ where let is_stripe_row = self.stripe && row_ix % 2 != 0; let is_selected = self.selected_row == Some(row_ix); let view = cx.view().clone(); - let col_sizes: Rc>> = Rc::new( - self.col_groups - .iter() - .skip(left_cols_count) - .map(|col| col.bounds.size) - .collect(), - ); if row_ix < rows_count { self.delegate @@ -1135,13 +1144,12 @@ where this.bg(cx.theme().table_hover) } }) - .children(if left_cols_count > 0 { + .when(left_cols_count > 0, |this| { // Left fixed columns - Some( + this.child( h_flex() + .relative() .h_full() - .border_r_1() - .border_color(cx.theme().table_row_border) .children({ let mut items = Vec::with_capacity(left_cols_count); @@ -1155,10 +1163,20 @@ where }); items - }), + }) + .child( + // Fixed columns border + div() + .absolute() + .top_0() + .right_0() + .bottom_0() + .w_0() + .flex_shrink_0() + .border_r_1() + .border_color(cx.theme().border), + ), ) - } else { - None }) .child( h_flex() @@ -1265,6 +1283,34 @@ where } } + /// Calculate the extra rows needed to fill the table empty space when `stripe` is true. + fn calculate_extra_rows_needed(&self, rows_count: usize) -> usize { + if !self.stripe { + return 0; + } + + let mut extra_rows_needed = 0; + + let row_height = self.size.table_row_height(); + let total_height = self + .vertical_scroll_handle + .0 + .borrow() + .base_handle + .bounds() + .size + .height; + + let actual_height = row_height * rows_count as f32; + let remaining_height = total_height - actual_height; + + if remaining_height > px(0.) { + extra_rows_needed = (remaining_height / row_height).ceil() as usize; + } + + extra_rows_needed + } + #[inline] fn measure_render_td( &mut self, @@ -1341,35 +1387,7 @@ where let left_cols_count = self.fixed_cols.left; let rows_count = self.delegate.rows_count(cx); let loading = self.delegate.loading(cx); - - let row_height = self - .vertical_scroll_handle - .0 - .borrow() - .last_item_size - .map(|size| size.item.height); - let total_height = self - .vertical_scroll_handle - .0 - .borrow() - .base_handle - .bounds() - .size - .height; - - // Calculate the extra rows needed to fill the table for stripe style. - let mut extra_rows_needed = 0; - if self.stripe { - if let Some(row_height) = row_height { - if row_height > px(0.) { - let actual_height = row_height * rows_count as f32; - let remaining_height = total_height - actual_height; - if remaining_height > px(0.) { - extra_rows_needed = (remaining_height / row_height).ceil() as usize; - } - } - } - } + let extra_rows_needed = self.calculate_extra_rows_needed(rows_count); let inner_table = v_flex() .key_context("Table") @@ -1405,6 +1423,17 @@ where rows_count + extra_rows_needed, { move |table, visible_range, cx| { + // We must calculate the col sizes here, because the col sizes + // need render_th first, then that method will set the bounds of each col. + let col_sizes: Rc>> = Rc::new( + table + .col_groups + .iter() + .skip(left_cols_count) + .map(|col| col.bounds.size) + .collect(), + ); + table.load_more_if_need(rows_count, visible_range.end, cx); table.update_visible_range_if_need( visible_range.clone(), @@ -1430,6 +1459,7 @@ where row_ix, rows_count, left_cols_count, + col_sizes.clone(), cols_count, cx, ));