table: Add sort button to table column. (#77)
https://github.com/user-attachments/assets/d6a3dc9b-758f-4108-8d1d-d8a3d2dc0dc2
This commit is contained in:
parent
7bfdfcb2ec
commit
15e9804f08
9 changed files with 301 additions and 67 deletions
|
|
@ -61,7 +61,7 @@ A UI components for building desktop application by using [GPUI](https://gpui.rs
|
||||||
- [x] Vertical scroll
|
- [x] Vertical scroll
|
||||||
- [x] Column resizing
|
- [x] Column resizing
|
||||||
- [x] Column ordering
|
- [x] Column ordering
|
||||||
- [ ] Sort event emit
|
- [x] Column sorting
|
||||||
- [ ] Drawer
|
- [ ] Drawer
|
||||||
- [ ] Modal
|
- [ ] Modal
|
||||||
|
|
||||||
|
|
|
||||||
1
assets/icons/arrow-down.svg
Normal file
1
assets/icons/arrow-down.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-down"><path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></svg>
|
||||||
|
After Width: | Height: | Size: 267 B |
1
assets/icons/arrow-up.svg
Normal file
1
assets/icons/arrow-up.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-up"><path d="m5 12 7-7 7 7"/><path d="M12 19V5"/></svg>
|
||||||
|
After Width: | Height: | Size: 264 B |
1
assets/icons/chevron-up.svg
Normal file
1
assets/icons/chevron-up.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up"><path d="m18 15-6-6-6 6"/></svg>
|
||||||
|
After Width: | Height: | Size: 247 B |
4
assets/icons/sort-ascending.svg
Normal file
4
assets/icons/sort-ascending.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-up-down">
|
||||||
|
<path opacity="0.5" d="m7 15 5 5 5-5"/>
|
||||||
|
<path d="m7 9 5-5 5 5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 297 B |
4
assets/icons/sort-descending.svg
Normal file
4
assets/icons/sort-descending.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-up-down">
|
||||||
|
<path d="m7 15 5 5 5-5"/>
|
||||||
|
<path opacity="0.5" d="m7 9 5-5 5 5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 297 B |
|
|
@ -6,7 +6,7 @@ use gpui::{
|
||||||
use ui::{
|
use ui::{
|
||||||
checkbox::Checkbox,
|
checkbox::Checkbox,
|
||||||
h_flex,
|
h_flex,
|
||||||
table::{Table, TableDelegate, TableEvent},
|
table::{ColSort, Table, TableDelegate, TableEvent},
|
||||||
v_flex, Selectable, Selection,
|
v_flex, Selectable, Selection,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -45,44 +45,67 @@ fn randome_customers(size: usize) -> Vec<Customer> {
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Column {
|
||||||
|
id: SharedString,
|
||||||
|
name: SharedString,
|
||||||
|
sort: Option<ColSort>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Column {
|
||||||
|
fn new(
|
||||||
|
id: impl Into<SharedString>,
|
||||||
|
name: impl Into<SharedString>,
|
||||||
|
sort: Option<ColSort>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id: id.into(),
|
||||||
|
name: name.into(),
|
||||||
|
sort,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct CustomerTableDelegate {
|
struct CustomerTableDelegate {
|
||||||
customers: Vec<Customer>,
|
customers: Vec<Customer>,
|
||||||
col_names: Vec<(SharedString, SharedString)>,
|
columns: Vec<Column>,
|
||||||
loop_selection: bool,
|
loop_selection: bool,
|
||||||
col_resize: bool,
|
col_resize: bool,
|
||||||
col_order: bool,
|
col_order: bool,
|
||||||
|
col_sort: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CustomerTableDelegate {
|
impl CustomerTableDelegate {
|
||||||
fn new(size: usize) -> Self {
|
fn new(size: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
customers: randome_customers(size),
|
customers: randome_customers(size),
|
||||||
col_names: vec![
|
columns: vec![
|
||||||
("id".into(), "ID".into()),
|
Column::new("id", "ID", Some(ColSort::Ascending)),
|
||||||
("login".into(), "Login".into()),
|
Column::new("login", "Login", Some(ColSort::Default)),
|
||||||
("first_name".into(), "First Name".into()),
|
Column::new("first_name", "First Name", Some(ColSort::Default)),
|
||||||
("last_name".into(), "Last Name".into()),
|
Column::new("last_name", "Last Name", Some(ColSort::Default)),
|
||||||
("company".into(), "Company".into()),
|
Column::new("company", "Company", Some(ColSort::Default)),
|
||||||
("city".into(), "City".into()),
|
Column::new("city", "City", Some(ColSort::Default)),
|
||||||
("country".into(), "Country".into()),
|
Column::new("country", "Country", Some(ColSort::Default)),
|
||||||
("email".into(), "Email".into()),
|
Column::new("email", "Email", Some(ColSort::Default)),
|
||||||
("phone".into(), "Phone".into()),
|
Column::new("phone", "Phone", None),
|
||||||
("gender".into(), "Gender".into()),
|
Column::new("gender", "Gender", None),
|
||||||
("age".into(), "Age".into()),
|
Column::new("age", "Age", Some(ColSort::Default)),
|
||||||
("verified".into(), "Verified".into()),
|
Column::new("verified", "Verified", None),
|
||||||
("confirmed".into(), "Confirmed".into()),
|
Column::new("confirmed", "Confirmed", None),
|
||||||
("twitter".into(), "Twitter".into()),
|
Column::new("twitter", "Twitter", None),
|
||||||
],
|
],
|
||||||
loop_selection: true,
|
loop_selection: true,
|
||||||
col_resize: true,
|
col_resize: true,
|
||||||
col_order: true,
|
col_order: true,
|
||||||
|
col_sort: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableDelegate for CustomerTableDelegate {
|
impl TableDelegate for CustomerTableDelegate {
|
||||||
fn cols_count(&self) -> usize {
|
fn cols_count(&self) -> usize {
|
||||||
self.col_names.len()
|
self.columns.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rows_count(&self) -> usize {
|
fn rows_count(&self) -> usize {
|
||||||
|
|
@ -90,17 +113,17 @@ impl TableDelegate for CustomerTableDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn col_name(&self, col_ix: usize) -> SharedString {
|
fn col_name(&self, col_ix: usize) -> SharedString {
|
||||||
if let Some(col) = self.col_names.get(col_ix) {
|
if let Some(col) = self.columns.get(col_ix) {
|
||||||
col.1.clone()
|
col.name.clone()
|
||||||
} else {
|
} else {
|
||||||
"--".into()
|
"--".into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn col_width(&self, col_ix: usize) -> Option<Pixels> {
|
fn col_width(&self, col_ix: usize) -> Option<Pixels> {
|
||||||
if let Some(col) = self.col_names.get(col_ix) {
|
if let Some(col) = self.columns.get(col_ix) {
|
||||||
Some(
|
Some(
|
||||||
match col.0.as_ref() {
|
match col.id.as_ref() {
|
||||||
"id" => 50.0,
|
"id" => 50.0,
|
||||||
"login" => 220.0,
|
"login" => 220.0,
|
||||||
"first_name" => 150.0,
|
"first_name" => 150.0,
|
||||||
|
|
@ -131,8 +154,8 @@ impl TableDelegate for CustomerTableDelegate {
|
||||||
fn render_td(&self, row_ix: usize, col_ix: usize) -> impl gpui::IntoElement {
|
fn render_td(&self, row_ix: usize, col_ix: usize) -> impl gpui::IntoElement {
|
||||||
let customer = self.customers.get(row_ix).unwrap();
|
let customer = self.customers.get(row_ix).unwrap();
|
||||||
|
|
||||||
let col = self.col_names.get(col_ix).unwrap();
|
let col = self.columns.get(col_ix).unwrap();
|
||||||
let text = match col.0.as_ref() {
|
let text = match col.id.as_ref() {
|
||||||
"id" => customer.id.to_string(),
|
"id" => customer.id.to_string(),
|
||||||
"login" => customer.login.clone(),
|
"login" => customer.login.clone(),
|
||||||
"first_name" => customer.first_name.clone(),
|
"first_name" => customer.first_name.clone(),
|
||||||
|
|
@ -162,8 +185,92 @@ impl TableDelegate for CustomerTableDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_col(&mut self, col_ix: usize, to_ix: usize) {
|
fn move_col(&mut self, col_ix: usize, to_ix: usize) {
|
||||||
let col = self.col_names.remove(col_ix);
|
let col = self.columns.remove(col_ix);
|
||||||
self.col_names.insert(to_ix, col);
|
self.columns.insert(to_ix, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn col_sort(&self, col_ix: usize) -> Option<ColSort> {
|
||||||
|
self.columns.get(col_ix).map(|c| c.sort).flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_sort(&mut self, col_ix: usize, sort: ColSort, _: &mut WindowContext) {
|
||||||
|
if let Some(col) = self.columns.get_mut(col_ix) {
|
||||||
|
col.sort = Some(sort);
|
||||||
|
let asc = matches!(sort, ColSort::Ascending);
|
||||||
|
|
||||||
|
match col.id.as_ref() {
|
||||||
|
"id" => self.customers.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)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for col in self.columns.iter_mut() {
|
||||||
|
if let Some(ColSort::Ascending) = col.sort {
|
||||||
|
col.sort = Some(ColSort::Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -209,6 +316,14 @@ impl TableStory {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_col_sort(&mut self, s: &Selection, cx: &mut ViewContext<Self>) {
|
||||||
|
let table = self.table.clone();
|
||||||
|
table.update(cx, |table, cx| {
|
||||||
|
table.delegate_mut().col_sort = s.is_selected();
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn on_table_event(
|
fn on_table_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: View<Table<CustomerTableDelegate>>,
|
_: View<Table<CustomerTableDelegate>>,
|
||||||
|
|
@ -253,6 +368,12 @@ impl Render for TableStory {
|
||||||
.label("Column Order")
|
.label("Column Order")
|
||||||
.selected(delegate.col_order)
|
.selected(delegate.col_order)
|
||||||
.on_click(cx.listener(Self::toggle_col_order)),
|
.on_click(cx.listener(Self::toggle_col_order)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Checkbox::new("col-sort")
|
||||||
|
.label("Column Sort")
|
||||||
|
.selected(delegate.col_sort)
|
||||||
|
.on_click(cx.listener(Self::toggle_col_sort)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(self.table.clone())
|
.child(self.table.clone())
|
||||||
|
|
|
||||||
|
|
@ -6,71 +6,81 @@ use gpui::{
|
||||||
|
|
||||||
#[derive(IntoElement, Clone)]
|
#[derive(IntoElement, Clone)]
|
||||||
pub enum IconName {
|
pub enum IconName {
|
||||||
Check,
|
ArrowDown,
|
||||||
Minus,
|
|
||||||
Dash,
|
|
||||||
Maximize,
|
|
||||||
Minimize,
|
|
||||||
Close,
|
|
||||||
ChevronDown,
|
|
||||||
ChevronsUpDown,
|
|
||||||
Plus,
|
|
||||||
Info,
|
|
||||||
Ellipsis,
|
|
||||||
EllipsisVertical,
|
|
||||||
Search,
|
|
||||||
Delete,
|
|
||||||
CircleX,
|
|
||||||
Loader,
|
|
||||||
LoaderCircle,
|
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
|
ArrowUp,
|
||||||
|
Check,
|
||||||
|
ChevronDown,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
ChevronUp,
|
||||||
|
ChevronsUpDown,
|
||||||
|
CircleX,
|
||||||
|
Close,
|
||||||
|
Dash,
|
||||||
|
Delete,
|
||||||
|
Ellipsis,
|
||||||
|
EllipsisVertical,
|
||||||
Eye,
|
Eye,
|
||||||
EyeOff,
|
EyeOff,
|
||||||
Inbox,
|
|
||||||
Heart,
|
Heart,
|
||||||
HeartOff,
|
HeartOff,
|
||||||
|
Inbox,
|
||||||
|
Info,
|
||||||
|
Loader,
|
||||||
|
LoaderCircle,
|
||||||
|
Maximize,
|
||||||
|
Minimize,
|
||||||
|
Minus,
|
||||||
|
Plus,
|
||||||
|
Search,
|
||||||
Star,
|
Star,
|
||||||
StarOff,
|
StarOff,
|
||||||
ThumbsUp,
|
SortAscending,
|
||||||
|
SortDescending,
|
||||||
ThumbsDown,
|
ThumbsDown,
|
||||||
|
ThumbsUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IconName {
|
impl IconName {
|
||||||
pub fn path(self) -> SharedString {
|
pub fn path(self) -> SharedString {
|
||||||
match self {
|
match self {
|
||||||
IconName::Check => "icons/check.svg",
|
IconName::ArrowDown => "icons/arrow-down.svg",
|
||||||
IconName::Minus => "icons/minus.svg",
|
|
||||||
IconName::Dash => "icons/dash.svg",
|
|
||||||
IconName::Maximize => "icons/maximize.svg",
|
|
||||||
IconName::Minimize => "icons/minimize.svg",
|
|
||||||
IconName::Close => "icons/close.svg",
|
|
||||||
IconName::ChevronDown => "icons/chevron-down.svg",
|
|
||||||
IconName::ChevronsUpDown => "icons/chevrons-up-down.svg",
|
|
||||||
IconName::Plus => "icons/plus.svg",
|
|
||||||
IconName::Info => "icons/info.svg",
|
|
||||||
IconName::Ellipsis => "icons/ellipsis.svg",
|
|
||||||
IconName::EllipsisVertical => "icons/ellipsis-vertical.svg",
|
|
||||||
IconName::Search => "icons/search.svg",
|
|
||||||
IconName::Delete => "icons/delete.svg",
|
|
||||||
IconName::CircleX => "icons/circle-x.svg",
|
|
||||||
IconName::Loader => "icons/loader.svg",
|
|
||||||
IconName::LoaderCircle => "icons/loader-circle.svg",
|
|
||||||
IconName::ArrowLeft => "icons/arrow-left.svg",
|
IconName::ArrowLeft => "icons/arrow-left.svg",
|
||||||
IconName::ArrowRight => "icons/arrow-right.svg",
|
IconName::ArrowRight => "icons/arrow-right.svg",
|
||||||
|
IconName::ArrowUp => "icons/arrow-up.svg",
|
||||||
|
IconName::Check => "icons/check.svg",
|
||||||
|
IconName::ChevronDown => "icons/chevron-down.svg",
|
||||||
IconName::ChevronLeft => "icons/chevron-left.svg",
|
IconName::ChevronLeft => "icons/chevron-left.svg",
|
||||||
IconName::ChevronRight => "icons/chevron-right.svg",
|
IconName::ChevronRight => "icons/chevron-right.svg",
|
||||||
|
IconName::ChevronUp => "icons/chevron-up.svg",
|
||||||
|
IconName::ChevronsUpDown => "icons/chevrons-up-down.svg",
|
||||||
|
IconName::CircleX => "icons/circle-x.svg",
|
||||||
|
IconName::Close => "icons/close.svg",
|
||||||
|
IconName::Dash => "icons/dash.svg",
|
||||||
|
IconName::Delete => "icons/delete.svg",
|
||||||
|
IconName::Ellipsis => "icons/ellipsis.svg",
|
||||||
|
IconName::EllipsisVertical => "icons/ellipsis-vertical.svg",
|
||||||
IconName::Eye => "icons/eye.svg",
|
IconName::Eye => "icons/eye.svg",
|
||||||
IconName::EyeOff => "icons/eye-off.svg",
|
IconName::EyeOff => "icons/eye-off.svg",
|
||||||
IconName::Inbox => "icons/inbox.svg",
|
|
||||||
IconName::Heart => "icons/heart.svg",
|
IconName::Heart => "icons/heart.svg",
|
||||||
IconName::HeartOff => "icons/heart-off.svg",
|
IconName::HeartOff => "icons/heart-off.svg",
|
||||||
|
IconName::Inbox => "icons/inbox.svg",
|
||||||
|
IconName::Info => "icons/info.svg",
|
||||||
|
IconName::Loader => "icons/loader.svg",
|
||||||
|
IconName::LoaderCircle => "icons/loader-circle.svg",
|
||||||
|
IconName::Maximize => "icons/maximize.svg",
|
||||||
|
IconName::Minimize => "icons/minimize.svg",
|
||||||
|
IconName::Minus => "icons/minus.svg",
|
||||||
|
IconName::Plus => "icons/plus.svg",
|
||||||
|
IconName::Search => "icons/search.svg",
|
||||||
IconName::Star => "icons/star.svg",
|
IconName::Star => "icons/star.svg",
|
||||||
IconName::StarOff => "icons/star-off.svg",
|
IconName::StarOff => "icons/star-off.svg",
|
||||||
IconName::ThumbsUp => "icons/thumbs-up.svg",
|
IconName::SortAscending => "icons/sort-ascending.svg",
|
||||||
|
IconName::SortDescending => "icons/sort-descending.svg",
|
||||||
IconName::ThumbsDown => "icons/thumbs-down.svg",
|
IconName::ThumbsDown => "icons/thumbs-down.svg",
|
||||||
|
IconName::ThumbsUp => "icons/thumbs-up.svg",
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::{
|
||||||
h_flex,
|
h_flex,
|
||||||
scroll::{ScrollableAxis, ScrollableMask, Scrollbar, ScrollbarState},
|
scroll::{ScrollableAxis, ScrollableMask, Scrollbar, ScrollbarState},
|
||||||
theme::ActiveTheme,
|
theme::ActiveTheme,
|
||||||
v_flex,
|
v_flex, Icon, IconName,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, div, prelude::FluentBuilder as _, px, uniform_list, AppContext, Bounds, Div,
|
actions, canvas, div, prelude::FluentBuilder as _, px, uniform_list, AppContext, Bounds, Div,
|
||||||
|
|
@ -40,6 +40,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
struct ColGroup {
|
struct ColGroup {
|
||||||
width: Option<Pixels>,
|
width: Option<Pixels>,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
|
sort: Option<ColSort>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
@ -50,6 +51,16 @@ pub(crate) struct DragCol {
|
||||||
pub(crate) col_ix: usize,
|
pub(crate) col_ix: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ColSort {
|
||||||
|
/// No sorting.
|
||||||
|
Default,
|
||||||
|
/// Sort in ascending order.
|
||||||
|
Ascending,
|
||||||
|
/// Sort in descending order.
|
||||||
|
Descending,
|
||||||
|
}
|
||||||
|
|
||||||
impl Render for DragCol {
|
impl Render for DragCol {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
div()
|
div()
|
||||||
|
|
@ -122,6 +133,16 @@ pub trait TableDelegate: Sized + 'static {
|
||||||
/// This is only called when the table initializes.
|
/// This is only called when the table initializes.
|
||||||
fn col_width(&self, col_ix: usize) -> Option<Pixels>;
|
fn col_width(&self, col_ix: usize) -> Option<Pixels>;
|
||||||
|
|
||||||
|
/// Return the sort state of the column at the given index.
|
||||||
|
///
|
||||||
|
/// This is only called when the table initializes.
|
||||||
|
fn col_sort(&self, col_ix: usize) -> Option<ColSort> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform sort on the column at the given index.
|
||||||
|
fn perform_sort(&mut self, col_ix: usize, sort: ColSort, cx: &mut WindowContext) {}
|
||||||
|
|
||||||
/// Render the header cell at the given column index, default to the column name.
|
/// Render the header cell at the given column index, default to the column name.
|
||||||
fn render_th(&self, col_ix: usize) -> impl IntoElement {
|
fn render_th(&self, col_ix: usize) -> impl IntoElement {
|
||||||
div().size_full().child(self.col_name(col_ix))
|
div().size_full().child(self.col_name(col_ix))
|
||||||
|
|
@ -184,6 +205,7 @@ where
|
||||||
.map(|col_ix| ColGroup {
|
.map(|col_ix| ColGroup {
|
||||||
width: self.delegate.col_width(col_ix),
|
width: self.delegate.col_width(col_ix),
|
||||||
bounds: Bounds::default(),
|
bounds: Bounds::default(),
|
||||||
|
sort: self.delegate.col_sort(col_ix),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
@ -453,6 +475,69 @@ where
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn perform_sort(&mut self, col_ix: usize, cx: &mut ViewContext<Self>) {
|
||||||
|
let sort = self.col_groups.get(col_ix).and_then(|g| g.sort);
|
||||||
|
if sort.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sort = sort.unwrap();
|
||||||
|
let sort = match sort {
|
||||||
|
ColSort::Ascending => ColSort::Descending,
|
||||||
|
ColSort::Descending => ColSort::Ascending,
|
||||||
|
ColSort::Default => ColSort::Ascending,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (ix, col_group) in self.col_groups.iter_mut().enumerate() {
|
||||||
|
if ix == col_ix {
|
||||||
|
col_group.sort = Some(sort);
|
||||||
|
} else {
|
||||||
|
col_group.sort = Some(ColSort::Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.delegate_mut().perform_sort(col_ix, sort, cx);
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_sort_icon(
|
||||||
|
&self,
|
||||||
|
col_ix: usize,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Option<impl IntoElement> {
|
||||||
|
let sort = self.col_groups.get(col_ix).and_then(|g| g.sort);
|
||||||
|
if sort.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sort = sort.unwrap();
|
||||||
|
|
||||||
|
let icon = match sort {
|
||||||
|
ColSort::Ascending => IconName::SortAscending,
|
||||||
|
ColSort::Descending => IconName::SortDescending,
|
||||||
|
ColSort::Default => IconName::ChevronsUpDown,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(
|
||||||
|
div()
|
||||||
|
.id(("icon-sort", col_ix))
|
||||||
|
.cursor_pointer()
|
||||||
|
.ml_2()
|
||||||
|
.p(px(2.))
|
||||||
|
.rounded_sm()
|
||||||
|
.hover(|this| this.bg(cx.theme().secondary))
|
||||||
|
.active(|this| this.bg(cx.theme().secondary_active))
|
||||||
|
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
|
||||||
|
.on_click(cx.listener(move |table, _, cx| table.perform_sort(col_ix, cx)))
|
||||||
|
.child(
|
||||||
|
Icon::new(icon)
|
||||||
|
.size_3()
|
||||||
|
.text_color(cx.theme().secondary_foreground),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Render the column header.
|
/// Render the column header.
|
||||||
/// The children must be one by one items.
|
/// The children must be one by one items.
|
||||||
/// Becuase the horizontal scroll handle will use the child_item_bounds to
|
/// Becuase the horizontal scroll handle will use the child_item_bounds to
|
||||||
|
|
@ -472,7 +557,14 @@ where
|
||||||
this.on_col_head_click(col_ix, cx);
|
this.on_col_head_click(col_ix, cx);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(self.delegate.render_th(col_ix))
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.size_full()
|
||||||
|
.justify_between()
|
||||||
|
.items_center()
|
||||||
|
.child(self.delegate.render_th(col_ix))
|
||||||
|
.children(self.render_sort_icon(col_ix, cx)),
|
||||||
|
)
|
||||||
.when(self.delegate.can_move_col(col_ix), |this| {
|
.when(self.delegate.can_move_col(col_ix), |this| {
|
||||||
this.on_drag(
|
this.on_drag(
|
||||||
DragCol {
|
DragCol {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue