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] Column resizing
|
||||
- [x] Column ordering
|
||||
- [ ] Sort event emit
|
||||
- [x] Column sorting
|
||||
- [ ] Drawer
|
||||
- [ ] 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::{
|
||||
checkbox::Checkbox,
|
||||
h_flex,
|
||||
table::{Table, TableDelegate, TableEvent},
|
||||
table::{ColSort, Table, TableDelegate, TableEvent},
|
||||
v_flex, Selectable, Selection,
|
||||
};
|
||||
|
||||
|
|
@ -45,44 +45,67 @@ fn randome_customers(size: usize) -> Vec<Customer> {
|
|||
})
|
||||
.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 {
|
||||
customers: Vec<Customer>,
|
||||
col_names: Vec<(SharedString, SharedString)>,
|
||||
columns: Vec<Column>,
|
||||
loop_selection: bool,
|
||||
col_resize: bool,
|
||||
col_order: bool,
|
||||
col_sort: bool,
|
||||
}
|
||||
|
||||
impl CustomerTableDelegate {
|
||||
fn new(size: usize) -> Self {
|
||||
Self {
|
||||
customers: randome_customers(size),
|
||||
col_names: vec![
|
||||
("id".into(), "ID".into()),
|
||||
("login".into(), "Login".into()),
|
||||
("first_name".into(), "First Name".into()),
|
||||
("last_name".into(), "Last Name".into()),
|
||||
("company".into(), "Company".into()),
|
||||
("city".into(), "City".into()),
|
||||
("country".into(), "Country".into()),
|
||||
("email".into(), "Email".into()),
|
||||
("phone".into(), "Phone".into()),
|
||||
("gender".into(), "Gender".into()),
|
||||
("age".into(), "Age".into()),
|
||||
("verified".into(), "Verified".into()),
|
||||
("confirmed".into(), "Confirmed".into()),
|
||||
("twitter".into(), "Twitter".into()),
|
||||
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),
|
||||
],
|
||||
loop_selection: true,
|
||||
col_resize: true,
|
||||
col_order: true,
|
||||
col_sort: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TableDelegate for CustomerTableDelegate {
|
||||
fn cols_count(&self) -> usize {
|
||||
self.col_names.len()
|
||||
self.columns.len()
|
||||
}
|
||||
|
||||
fn rows_count(&self) -> usize {
|
||||
|
|
@ -90,17 +113,17 @@ impl TableDelegate for CustomerTableDelegate {
|
|||
}
|
||||
|
||||
fn col_name(&self, col_ix: usize) -> SharedString {
|
||||
if let Some(col) = self.col_names.get(col_ix) {
|
||||
col.1.clone()
|
||||
if let Some(col) = self.columns.get(col_ix) {
|
||||
col.name.clone()
|
||||
} else {
|
||||
"--".into()
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
match col.0.as_ref() {
|
||||
match col.id.as_ref() {
|
||||
"id" => 50.0,
|
||||
"login" => 220.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 {
|
||||
let customer = self.customers.get(row_ix).unwrap();
|
||||
|
||||
let col = self.col_names.get(col_ix).unwrap();
|
||||
let text = match col.0.as_ref() {
|
||||
let col = self.columns.get(col_ix).unwrap();
|
||||
let text = match col.id.as_ref() {
|
||||
"id" => customer.id.to_string(),
|
||||
"login" => customer.login.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) {
|
||||
let col = self.col_names.remove(col_ix);
|
||||
self.col_names.insert(to_ix, col);
|
||||
let col = self.columns.remove(col_ix);
|
||||
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(
|
||||
&mut self,
|
||||
_: View<Table<CustomerTableDelegate>>,
|
||||
|
|
@ -253,6 +368,12 @@ impl Render for TableStory {
|
|||
.label("Column Order")
|
||||
.selected(delegate.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())
|
||||
|
|
|
|||
|
|
@ -6,71 +6,81 @@ use gpui::{
|
|||
|
||||
#[derive(IntoElement, Clone)]
|
||||
pub enum IconName {
|
||||
Check,
|
||||
Minus,
|
||||
Dash,
|
||||
Maximize,
|
||||
Minimize,
|
||||
Close,
|
||||
ChevronDown,
|
||||
ChevronsUpDown,
|
||||
Plus,
|
||||
Info,
|
||||
Ellipsis,
|
||||
EllipsisVertical,
|
||||
Search,
|
||||
Delete,
|
||||
CircleX,
|
||||
Loader,
|
||||
LoaderCircle,
|
||||
ArrowDown,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
ArrowUp,
|
||||
Check,
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronUp,
|
||||
ChevronsUpDown,
|
||||
CircleX,
|
||||
Close,
|
||||
Dash,
|
||||
Delete,
|
||||
Ellipsis,
|
||||
EllipsisVertical,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Inbox,
|
||||
Heart,
|
||||
HeartOff,
|
||||
Inbox,
|
||||
Info,
|
||||
Loader,
|
||||
LoaderCircle,
|
||||
Maximize,
|
||||
Minimize,
|
||||
Minus,
|
||||
Plus,
|
||||
Search,
|
||||
Star,
|
||||
StarOff,
|
||||
ThumbsUp,
|
||||
SortAscending,
|
||||
SortDescending,
|
||||
ThumbsDown,
|
||||
ThumbsUp,
|
||||
}
|
||||
|
||||
impl IconName {
|
||||
pub fn path(self) -> SharedString {
|
||||
match self {
|
||||
IconName::Check => "icons/check.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::ArrowDown => "icons/arrow-down.svg",
|
||||
IconName::ArrowLeft => "icons/arrow-left.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::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::EyeOff => "icons/eye-off.svg",
|
||||
IconName::Inbox => "icons/inbox.svg",
|
||||
IconName::Heart => "icons/heart.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::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::ThumbsUp => "icons/thumbs-up.svg",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
h_flex,
|
||||
scroll::{ScrollableAxis, ScrollableMask, Scrollbar, ScrollbarState},
|
||||
theme::ActiveTheme,
|
||||
v_flex,
|
||||
v_flex, Icon, IconName,
|
||||
};
|
||||
use gpui::{
|
||||
actions, canvas, div, prelude::FluentBuilder as _, px, uniform_list, AppContext, Bounds, Div,
|
||||
|
|
@ -40,6 +40,7 @@ pub fn init(cx: &mut AppContext) {
|
|||
struct ColGroup {
|
||||
width: Option<Pixels>,
|
||||
bounds: Bounds<Pixels>,
|
||||
sort: Option<ColSort>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
@ -50,6 +51,16 @@ pub(crate) struct DragCol {
|
|||
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 {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
|
|
@ -122,6 +133,16 @@ pub trait TableDelegate: Sized + 'static {
|
|||
/// This is only called when the table initializes.
|
||||
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.
|
||||
fn render_th(&self, col_ix: usize) -> impl IntoElement {
|
||||
div().size_full().child(self.col_name(col_ix))
|
||||
|
|
@ -184,6 +205,7 @@ where
|
|||
.map(|col_ix| ColGroup {
|
||||
width: self.delegate.col_width(col_ix),
|
||||
bounds: Bounds::default(),
|
||||
sort: self.delegate.col_sort(col_ix),
|
||||
})
|
||||
.collect();
|
||||
cx.notify();
|
||||
|
|
@ -453,6 +475,69 @@ where
|
|||
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.
|
||||
/// The children must be one by one items.
|
||||
/// 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);
|
||||
}),
|
||||
)
|
||||
.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| {
|
||||
this.on_drag(
|
||||
DragCol {
|
||||
|
|
|
|||
Loading…
Reference in a new issue