Skip to content

Commit

Permalink
table: Add drag to move col.
Browse files Browse the repository at this point in the history
  • Loading branch information
huacnlee committed Jul 28, 2024
1 parent 79958fc commit 347e8f3
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 65 deletions.
123 changes: 65 additions & 58 deletions crates/story/src/table_story.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,6 @@ struct Customer {
verified: bool,
confirmed: bool,
}
impl Customer {
fn col_names() -> Vec<SharedString> {
vec![
"ID".into(),
"Login".into(),
"First Name".into(),
"Last Name".into(),
"Company".into(),
"City".into(),
"Country".into(),
"Email".into(),
"Phone".into(),
"Gender".into(),
"Age".into(),
"Verified".into(),
"Confirmed".into(),
"Twitter".into(),
]
}
}

fn randome_customers(size: usize) -> Vec<Customer> {
(0..size)
Expand All @@ -67,80 +47,102 @@ fn randome_customers(size: usize) -> Vec<Customer> {
}
struct CustomerTableDelegate {
customers: Vec<Customer>,
col_names: Vec<(SharedString, SharedString)>,
loop_selection: 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()),
],
loop_selection: true,
}
}
}

impl TableDelegate for CustomerTableDelegate {
fn cols_count(&self) -> usize {
Customer::col_names().len()
self.col_names.len()
}

fn rows_count(&self) -> usize {
self.customers.len()
}

fn column_name(&self, col_ix: usize) -> SharedString {
if let Some(name) = Customer::col_names().get(col_ix) {
name.clone()
if let Some(col) = self.col_names.get(col_ix) {
col.1.clone()
} else {
"--".into()
}
}

fn col_width(&self, col_ix: usize) -> Option<Pixels> {
match col_ix {
0 => Some(50.0),
1 => Some(220.0),
2 => Some(150.0),
3 => Some(150.0),
4 => Some(300.0),
5 => Some(200.0),
6 => Some(200.0),
7 => Some(350.0),
8 => Some(240.0),
9 => Some(80.0),
10 => Some(90.0),
11 => Some(90.0),
12 => Some(90.0),
13 => Some(90.0),
_ => None,
if let Some(col) = self.col_names.get(col_ix) {
Some(
match col.0.as_ref() {
"id" => 50.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,
}
.into(),
)
} else {
None
}
.map(Pixels::from)
}

fn can_resize_col(&self, col_ix: usize) -> bool {
return col_ix > 1;
}

fn on_col_widths_changed(&mut self, col_widths: Vec<Option<Pixels>>) {
println!("Col widths changed: {:?}", col_widths);
}

fn render_td(&self, row_ix: usize, col_ix: usize) -> impl gpui::IntoElement {
let customer = self.customers.get(row_ix).unwrap();
let text = match col_ix {
0 => customer.id.to_string(),
1 => customer.login.clone(),
2 => customer.first_name.clone(),
3 => customer.last_name.clone(),
4 => customer.company.clone(),
5 => customer.city.clone(),
6 => customer.country.clone(),
7 => customer.email.clone(),
8 => customer.phone.clone(),
9 => customer.gender.to_string(),
10 => customer.age.to_string(),
11 => customer.verified.to_string(),
12 => customer.confirmed.to_string(),

let col = self.col_names.get(col_ix).unwrap();
let text = match col.0.as_ref() {
"id" => customer.id.to_string(),
"login" => customer.login.clone(),
"first_name" => customer.first_name.clone(),
"last_name" => customer.last_name.clone(),
"company" => customer.company.clone(),
"city" => customer.city.clone(),
"country" => customer.country.clone(),
"email" => customer.email.clone(),
"phone" => customer.phone.clone(),
"gender" => customer.gender.to_string(),
"age" => customer.age.to_string(),
"verified" => customer.verified.to_string(),
"confirmed" => customer.confirmed.to_string(),
"twitter" => "twitter".to_string(),
_ => "--".to_string(),
};

Expand All @@ -150,6 +152,11 @@ impl TableDelegate for CustomerTableDelegate {
fn can_loop_select(&self) -> bool {
self.loop_selection
}

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);
}
}

pub struct TableStory {
Expand Down
51 changes: 44 additions & 7 deletions crates/ui/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ struct ColGroup {
#[derive(Clone, Render)]
pub struct DragCol(pub (EntityId, usize));

#[derive(Clone, Render)]
pub struct ResizeCol(pub (EntityId, usize));

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum SelectionState {
Column,
Expand Down Expand Up @@ -95,9 +98,6 @@ pub trait TableDelegate: Sized + 'static {
/// This is only called when the table initializes.
fn col_width(&self, col_ix: usize) -> Option<Pixels>;

/// When the column has resized, this method is called.
fn on_col_widths_changed(&mut self, col_widths: Vec<Option<Pixels>>) {}

/// 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.column_name(col_ix))
Expand All @@ -114,6 +114,9 @@ pub trait TableDelegate: Sized + 'static {
fn can_loop_select(&self) -> bool {
true
}

/// Move the column at the given `col_ix` to insert before the column at the given `to_ix`.
fn move_col(&mut self, col_ix: usize, to_ix: usize) {}
}

impl<D> Table<D>
Expand Down Expand Up @@ -317,8 +320,8 @@ where
.hover(|this| this.bg(cx.theme().drag_border))
.when(is_resizing, |this| this.bg(cx.theme().drag_border))
.on_drag_move(cx.listener(
move |view, e: &DragMoveEvent<DragCol>, cx| match e.drag(cx) {
DragCol((entity_id, ix)) => {
move |view, e: &DragMoveEvent<ResizeCol>, cx| match e.drag(cx) {
ResizeCol((entity_id, ix)) => {
if cx.entity_id() != *entity_id {
return;
}
Expand All @@ -340,7 +343,7 @@ where
}
},
))
.on_drag(DragCol((cx.entity_id(), ix)), |drag, cx| {
.on_drag(ResizeCol((cx.entity_id(), ix)), |drag, cx| {
cx.stop_propagation();
cx.new_view(|_| drag.clone())
})
Expand Down Expand Up @@ -395,16 +398,38 @@ where
/// Becuase the horizontal scroll handle will use the child_item_bounds to
/// calculate the item position for itself's `scroll_to_item` method.
fn render_th(&self, col_ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
let entity_id = cx.entity_id();
self.col_wrap(col_ix, cx)
.child(
self.render_cell(col_ix, cx)
.id(("col-header", col_ix))
.on_mouse_down(
MouseButton::Left,
cx.listener(move |this, _, cx| {
this.on_col_head_click(col_ix, cx);
}),
)
.child(self.delegate.render_th(col_ix)),
.child(self.delegate.render_th(col_ix))
.on_drag(DragCol((entity_id, col_ix)), |drag, cx| {
cx.stop_propagation();
cx.new_view(|_| drag.clone())
})
.drag_over::<DragCol>(|this, _, cx| {
this.rounded_l_none()
.border_l_2()
.border_r_0()
.border_color(cx.theme().drag_border)
})
.on_drop(cx.listener(move |table, drag: &DragCol, cx| {
let drag = drag.0;

// If the drag col is not the same as the drop col, then swap the cols.
if drag.0 != cx.entity_id() {
return;
}

table.move_col(drag.1, col_ix, cx);
})),
)
// resize handle
.child(self.render_resize_handle(col_ix, cx))
Expand All @@ -419,6 +444,18 @@ where
.size_full()
})
}

fn move_col(&mut self, col_ix: usize, to_ix: usize, cx: &mut ViewContext<Self>) {
if col_ix == to_ix {
return;
}

self.delegate.move_col(col_ix, to_ix);
let col_group = self.col_groups.remove(col_ix);
self.col_groups.insert(to_ix, col_group);

cx.notify();
}
}

impl<D> FocusableView for Table<D>
Expand Down

0 comments on commit 347e8f3

Please sign in to comment.