table: Add drag to move col. (#76)
https://github.com/user-attachments/assets/9964f3da-0376-4b53-b64e-c9652c324aa3
This commit is contained in:
parent
79958fc95c
commit
7bfdfcb2ec
3 changed files with 245 additions and 84 deletions
|
|
@ -59,8 +59,8 @@ A UI components for building desktop application by using [GPUI](https://gpui.rs
|
||||||
- [x] Left, Right / Up, Down to selection column or row.
|
- [x] Left, Right / Up, Down to selection column or row.
|
||||||
- [x] Horizontal scroll
|
- [x] Horizontal scroll
|
||||||
- [x] Vertical scroll
|
- [x] Vertical scroll
|
||||||
- [ ] Column resizing
|
- [x] Column resizing
|
||||||
- [ ] Column ordering
|
- [x] Column ordering
|
||||||
- [ ] Sort event emit
|
- [ ] Sort event emit
|
||||||
- [ ] Drawer
|
- [ ] Drawer
|
||||||
- [ ] Modal
|
- [ ] Modal
|
||||||
|
|
|
||||||
|
|
@ -25,26 +25,6 @@ struct Customer {
|
||||||
verified: bool,
|
verified: bool,
|
||||||
confirmed: 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> {
|
fn randome_customers(size: usize) -> Vec<Customer> {
|
||||||
(0..size)
|
(0..size)
|
||||||
|
|
@ -67,80 +47,106 @@ fn randome_customers(size: usize) -> Vec<Customer> {
|
||||||
}
|
}
|
||||||
struct CustomerTableDelegate {
|
struct CustomerTableDelegate {
|
||||||
customers: Vec<Customer>,
|
customers: Vec<Customer>,
|
||||||
|
col_names: Vec<(SharedString, SharedString)>,
|
||||||
loop_selection: bool,
|
loop_selection: bool,
|
||||||
|
col_resize: bool,
|
||||||
|
col_order: 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![
|
||||||
|
("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,
|
loop_selection: true,
|
||||||
|
col_resize: true,
|
||||||
|
col_order: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableDelegate for CustomerTableDelegate {
|
impl TableDelegate for CustomerTableDelegate {
|
||||||
fn cols_count(&self) -> usize {
|
fn cols_count(&self) -> usize {
|
||||||
Customer::col_names().len()
|
self.col_names.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rows_count(&self) -> usize {
|
fn rows_count(&self) -> usize {
|
||||||
self.customers.len()
|
self.customers.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn column_name(&self, col_ix: usize) -> SharedString {
|
fn col_name(&self, col_ix: usize) -> SharedString {
|
||||||
if let Some(name) = Customer::col_names().get(col_ix) {
|
if let Some(col) = self.col_names.get(col_ix) {
|
||||||
name.clone()
|
col.1.clone()
|
||||||
} else {
|
} else {
|
||||||
"--".into()
|
"--".into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn col_width(&self, col_ix: usize) -> Option<Pixels> {
|
fn col_width(&self, col_ix: usize) -> Option<Pixels> {
|
||||||
match col_ix {
|
if let Some(col) = self.col_names.get(col_ix) {
|
||||||
0 => Some(50.0),
|
Some(
|
||||||
1 => Some(220.0),
|
match col.0.as_ref() {
|
||||||
2 => Some(150.0),
|
"id" => 50.0,
|
||||||
3 => Some(150.0),
|
"login" => 220.0,
|
||||||
4 => Some(300.0),
|
"first_name" => 150.0,
|
||||||
5 => Some(200.0),
|
"last_name" => 150.0,
|
||||||
6 => Some(200.0),
|
"company" => 300.0,
|
||||||
7 => Some(350.0),
|
"city" => 200.0,
|
||||||
8 => Some(240.0),
|
"country" => 200.0,
|
||||||
9 => Some(80.0),
|
"email" => 350.0,
|
||||||
10 => Some(90.0),
|
"phone" => 240.0,
|
||||||
11 => Some(90.0),
|
"gender" => 80.0,
|
||||||
12 => Some(90.0),
|
"age" => 90.0,
|
||||||
13 => Some(90.0),
|
"verified" => 90.0,
|
||||||
_ => None,
|
"confirmed" => 90.0,
|
||||||
|
"twitter" => 90.0,
|
||||||
|
_ => 200.0,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
.map(Pixels::from)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_resize_col(&self, col_ix: usize) -> bool {
|
fn can_resize_col(&self, col_ix: usize) -> bool {
|
||||||
return col_ix > 1;
|
return self.col_resize && 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 {
|
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 text = match col_ix {
|
|
||||||
0 => customer.id.to_string(),
|
let col = self.col_names.get(col_ix).unwrap();
|
||||||
1 => customer.login.clone(),
|
let text = match col.0.as_ref() {
|
||||||
2 => customer.first_name.clone(),
|
"id" => customer.id.to_string(),
|
||||||
3 => customer.last_name.clone(),
|
"login" => customer.login.clone(),
|
||||||
4 => customer.company.clone(),
|
"first_name" => customer.first_name.clone(),
|
||||||
5 => customer.city.clone(),
|
"last_name" => customer.last_name.clone(),
|
||||||
6 => customer.country.clone(),
|
"company" => customer.company.clone(),
|
||||||
7 => customer.email.clone(),
|
"city" => customer.city.clone(),
|
||||||
8 => customer.phone.clone(),
|
"country" => customer.country.clone(),
|
||||||
9 => customer.gender.to_string(),
|
"email" => customer.email.clone(),
|
||||||
10 => customer.age.to_string(),
|
"phone" => customer.phone.clone(),
|
||||||
11 => customer.verified.to_string(),
|
"gender" => customer.gender.to_string(),
|
||||||
12 => customer.confirmed.to_string(),
|
"age" => customer.age.to_string(),
|
||||||
|
"verified" => customer.verified.to_string(),
|
||||||
|
"confirmed" => customer.confirmed.to_string(),
|
||||||
|
"twitter" => "twitter".to_string(),
|
||||||
_ => "--".to_string(),
|
_ => "--".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -150,6 +156,15 @@ impl TableDelegate for CustomerTableDelegate {
|
||||||
fn can_loop_select(&self) -> bool {
|
fn can_loop_select(&self) -> bool {
|
||||||
self.loop_selection
|
self.loop_selection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_move_col(&self, _: usize) -> bool {
|
||||||
|
self.col_order
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
pub struct TableStory {
|
||||||
|
|
@ -178,6 +193,22 @@ impl TableStory {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_col_resize(&mut self, s: &Selection, cx: &mut ViewContext<Self>) {
|
||||||
|
let table = self.table.clone();
|
||||||
|
table.update(cx, |table, cx| {
|
||||||
|
table.delegate_mut().col_resize = s.is_selected();
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_col_order(&mut self, s: &Selection, cx: &mut ViewContext<Self>) {
|
||||||
|
let table = self.table.clone();
|
||||||
|
table.update(cx, |table, cx| {
|
||||||
|
table.delegate_mut().col_order = s.is_selected();
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn on_table_event(
|
fn on_table_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: View<Table<CustomerTableDelegate>>,
|
_: View<Table<CustomerTableDelegate>>,
|
||||||
|
|
@ -202,12 +233,27 @@ impl Render for TableStory {
|
||||||
.size_full()
|
.size_full()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(
|
.child(
|
||||||
h_flex().items_center().child(
|
h_flex()
|
||||||
Checkbox::new("loop-selection")
|
.items_center()
|
||||||
.label("Loop Selection")
|
.gap_2()
|
||||||
.selected(delegate.loop_selection)
|
.child(
|
||||||
.on_click(cx.listener(Self::toggle_loop_selection)),
|
Checkbox::new("loop-selection")
|
||||||
),
|
.label("Loop Selection")
|
||||||
|
.selected(delegate.loop_selection)
|
||||||
|
.on_click(cx.listener(Self::toggle_loop_selection)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Checkbox::new("col-resize")
|
||||||
|
.label("Column Resize")
|
||||||
|
.selected(delegate.col_resize)
|
||||||
|
.on_click(cx.listener(Self::toggle_col_resize)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Checkbox::new("col-order")
|
||||||
|
.label("Column Order")
|
||||||
|
.selected(delegate.col_order)
|
||||||
|
.on_click(cx.listener(Self::toggle_col_order)),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.child(self.table.clone())
|
.child(self.table.clone())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::{
|
||||||
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,
|
||||||
DragMoveEvent, EntityId, EventEmitter, FocusHandle, FocusableView, InteractiveElement as _,
|
DragMoveEvent, EntityId, EventEmitter, FocusHandle, FocusableView, InteractiveElement as _,
|
||||||
IntoElement, KeyBinding, MouseButton, ParentElement as _, Pixels, Render, ScrollHandle,
|
IntoElement, KeyBinding, MouseButton, ParentElement as _, Pixels, Point, Render, ScrollHandle,
|
||||||
SharedString, StatefulInteractiveElement as _, Styled, UniformListScrollHandle, ViewContext,
|
SharedString, StatefulInteractiveElement as _, Styled, UniformListScrollHandle, ViewContext,
|
||||||
VisualContext as _, WindowContext,
|
VisualContext as _, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
@ -36,13 +36,38 @@ pub fn init(cx: &mut AppContext) {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
struct ColGroup {
|
struct ColGroup {
|
||||||
width: Option<Pixels>,
|
width: Option<Pixels>,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct DragCol {
|
||||||
|
pub(crate) entity_id: EntityId,
|
||||||
|
pub(crate) name: SharedString,
|
||||||
|
pub(crate) width: Option<Pixels>,
|
||||||
|
pub(crate) col_ix: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for DragCol {
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
div()
|
||||||
|
.px_4()
|
||||||
|
.py_1()
|
||||||
|
.bg(cx.theme().table_head)
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().border)
|
||||||
|
.shadow_md()
|
||||||
|
.when_some(self.width.clone(), |this, width| this.w(width))
|
||||||
|
.min_w(px(100.))
|
||||||
|
.max_w(px(450.))
|
||||||
|
.child(self.name.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Render)]
|
#[derive(Clone, Render)]
|
||||||
pub struct DragCol(pub (EntityId, usize));
|
pub struct ResizeCol(pub (EntityId, usize));
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
enum SelectionState {
|
enum SelectionState {
|
||||||
|
|
@ -60,6 +85,8 @@ pub enum TableEvent {
|
||||||
pub struct Table<D: TableDelegate> {
|
pub struct Table<D: TableDelegate> {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
delegate: D,
|
delegate: D,
|
||||||
|
/// The bounds of the table.
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
horizontal_scroll_handle: ScrollHandle,
|
horizontal_scroll_handle: ScrollHandle,
|
||||||
vertical_scroll_handle: UniformListScrollHandle,
|
vertical_scroll_handle: UniformListScrollHandle,
|
||||||
col_groups: Vec<ColGroup>,
|
col_groups: Vec<ColGroup>,
|
||||||
|
|
@ -82,7 +109,7 @@ pub trait TableDelegate: Sized + 'static {
|
||||||
fn rows_count(&self) -> usize;
|
fn rows_count(&self) -> usize;
|
||||||
|
|
||||||
/// Returns the name of the column at the given index.
|
/// Returns the name of the column at the given index.
|
||||||
fn column_name(&self, col_ix: usize) -> SharedString;
|
fn col_name(&self, col_ix: usize) -> SharedString;
|
||||||
|
|
||||||
/// Returns whether the column at the given index can be resized. Default: true
|
/// Returns whether the column at the given index can be resized. Default: true
|
||||||
fn can_resize_col(&self, col_ix: usize) -> bool {
|
fn can_resize_col(&self, col_ix: usize) -> bool {
|
||||||
|
|
@ -95,12 +122,9 @@ 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>;
|
||||||
|
|
||||||
/// 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.
|
/// 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.column_name(col_ix))
|
div().size_full().child(self.col_name(col_ix))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render cell at the given row and column.
|
/// Render cell at the given row and column.
|
||||||
|
|
@ -114,6 +138,14 @@ pub trait TableDelegate: Sized + 'static {
|
||||||
fn can_loop_select(&self) -> bool {
|
fn can_loop_select(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return true to enable column order change.
|
||||||
|
fn can_move_col(&self, col_ix: usize) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>
|
impl<D> Table<D>
|
||||||
|
|
@ -132,6 +164,7 @@ where
|
||||||
selected_row: None,
|
selected_row: None,
|
||||||
selected_col: None,
|
selected_col: None,
|
||||||
resizing_col: None,
|
resizing_col: None,
|
||||||
|
bounds: Bounds::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.prepare_col_groups(cx);
|
this.prepare_col_groups(cx);
|
||||||
|
|
@ -316,9 +349,9 @@ where
|
||||||
)
|
)
|
||||||
.hover(|this| this.bg(cx.theme().drag_border))
|
.hover(|this| this.bg(cx.theme().drag_border))
|
||||||
.when(is_resizing, |this| this.bg(cx.theme().drag_border))
|
.when(is_resizing, |this| this.bg(cx.theme().drag_border))
|
||||||
.on_drag_move(cx.listener(
|
.on_drag_move(cx.listener(move |view, e: &DragMoveEvent<ResizeCol>, cx| {
|
||||||
move |view, e: &DragMoveEvent<DragCol>, cx| match e.drag(cx) {
|
match e.drag(cx) {
|
||||||
DragCol((entity_id, ix)) => {
|
ResizeCol((entity_id, ix)) => {
|
||||||
if cx.entity_id() != *entity_id {
|
if cx.entity_id() != *entity_id {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -331,16 +364,24 @@ where
|
||||||
let ix = *ix;
|
let ix = *ix;
|
||||||
view.resizing_col = Some(ix);
|
view.resizing_col = Some(ix);
|
||||||
|
|
||||||
let col_group = view.col_groups.get(ix).expect("BUG: invalid col index");
|
let col_group = view
|
||||||
|
.col_groups
|
||||||
|
.get(ix)
|
||||||
|
.expect("BUG: invalid col index")
|
||||||
|
.clone();
|
||||||
|
|
||||||
view.resize_cols(
|
view.resize_cols(
|
||||||
ix,
|
ix,
|
||||||
e.event.position.x - HANDLE_SIZE - col_group.bounds.left(),
|
e.event.position.x - HANDLE_SIZE - col_group.bounds.left(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// scroll the table if the drag is near the edge
|
||||||
|
view.scroll_table_by_col_resizing(e.event.position, col_group, cx);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
))
|
}))
|
||||||
.on_drag(DragCol((cx.entity_id(), ix)), |drag, cx| {
|
.on_drag(ResizeCol((cx.entity_id(), ix)), |drag, cx| {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
cx.new_view(|_| drag.clone())
|
cx.new_view(|_| drag.clone())
|
||||||
})
|
})
|
||||||
|
|
@ -361,10 +402,32 @@ where
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scroll table when mouse position is near the edge of the table bounds.
|
||||||
|
fn scroll_table_by_col_resizing(
|
||||||
|
&mut self,
|
||||||
|
pos: Point<Pixels>,
|
||||||
|
col_group: ColGroup,
|
||||||
|
_: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
let mut offset = self.horizontal_scroll_handle.offset();
|
||||||
|
let col_bounds = col_group.bounds;
|
||||||
|
|
||||||
|
if pos.x < self.bounds.left() && col_bounds.right() < self.bounds.left() + px(20.) {
|
||||||
|
offset.x += px(1.);
|
||||||
|
} else if pos.x > self.bounds.right() && col_bounds.right() > self.bounds.right() - px(20.)
|
||||||
|
{
|
||||||
|
offset.x -= px(1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.horizontal_scroll_handle.set_offset(offset);
|
||||||
|
}
|
||||||
|
|
||||||
/// The `ix`` is the index of the col to resize,
|
/// The `ix`` is the index of the col to resize,
|
||||||
/// and the `size` is the new size for the col.
|
/// and the `size` is the new size for the col.
|
||||||
fn resize_cols(&mut self, ix: usize, size: Pixels, cx: &mut ViewContext<Self>) {
|
fn resize_cols(&mut self, ix: usize, size: Pixels, cx: &mut ViewContext<Self>) {
|
||||||
const MIN_WIDTH: Pixels = px(10.0);
|
const MIN_WIDTH: Pixels = px(10.0);
|
||||||
|
const MAX_WIDTH: Pixels = px(1200.0);
|
||||||
|
|
||||||
if !self.delegate.can_resize_col(ix) {
|
if !self.delegate.can_resize_col(ix) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -380,7 +443,7 @@ where
|
||||||
if changed_width > px(-1.0) && changed_width < px(1.0) {
|
if changed_width > px(-1.0) && changed_width < px(1.0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.col_groups[ix].width = Some(new_width);
|
self.col_groups[ix].width = Some(new_width.min(MAX_WIDTH));
|
||||||
|
|
||||||
// Resize next col, table not need to resize the right cols.
|
// Resize next col, table not need to resize the right cols.
|
||||||
// let next_width = self.col_groups[ix + 1].width.unwrap_or_default();
|
// let next_width = self.col_groups[ix + 1].width.unwrap_or_default();
|
||||||
|
|
@ -395,16 +458,51 @@ where
|
||||||
/// Becuase the horizontal scroll handle will use the child_item_bounds to
|
/// Becuase the horizontal scroll handle will use the child_item_bounds to
|
||||||
/// calculate the item position for itself's `scroll_to_item` method.
|
/// calculate the item position for itself's `scroll_to_item` method.
|
||||||
fn render_th(&self, col_ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render_th(&self, col_ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let entity_id = cx.entity_id();
|
||||||
|
let col_group = self.col_groups.get(col_ix).expect("BUG: invalid col index");
|
||||||
|
|
||||||
|
let name = self.delegate.col_name(col_ix);
|
||||||
self.col_wrap(col_ix, cx)
|
self.col_wrap(col_ix, cx)
|
||||||
.child(
|
.child(
|
||||||
self.render_cell(col_ix, cx)
|
self.render_cell(col_ix, cx)
|
||||||
|
.id(("col-header", col_ix))
|
||||||
.on_mouse_down(
|
.on_mouse_down(
|
||||||
MouseButton::Left,
|
MouseButton::Left,
|
||||||
cx.listener(move |this, _, cx| {
|
cx.listener(move |this, _, cx| {
|
||||||
this.on_col_head_click(col_ix, cx);
|
this.on_col_head_click(col_ix, cx);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(self.delegate.render_th(col_ix)),
|
.child(self.delegate.render_th(col_ix))
|
||||||
|
.when(self.delegate.can_move_col(col_ix), |this| {
|
||||||
|
this.on_drag(
|
||||||
|
DragCol {
|
||||||
|
entity_id,
|
||||||
|
col_ix,
|
||||||
|
name,
|
||||||
|
width: col_group.width,
|
||||||
|
},
|
||||||
|
|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| {
|
||||||
|
// If the drag col is not the same as the drop col, then swap the cols.
|
||||||
|
if drag.entity_id != cx.entity_id() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.move_col(drag.col_ix, col_ix, cx);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
// resize handle
|
// resize handle
|
||||||
.child(self.render_resize_handle(col_ix, cx))
|
.child(self.render_resize_handle(col_ix, cx))
|
||||||
|
|
@ -419,6 +517,18 @@ where
|
||||||
.size_full()
|
.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>
|
impl<D> FocusableView for Table<D>
|
||||||
|
|
@ -558,6 +668,7 @@ where
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let view = cx.view().clone();
|
||||||
div()
|
div()
|
||||||
.size_full()
|
.size_full()
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
|
|
@ -571,5 +682,9 @@ where
|
||||||
ScrollableAxis::Horizontal,
|
ScrollableAxis::Horizontal,
|
||||||
&horizontal_scroll_handle,
|
&horizontal_scroll_handle,
|
||||||
))
|
))
|
||||||
|
.child(canvas(
|
||||||
|
move |bounds, cx| view.update(cx, |r, _| r.bounds = bounds),
|
||||||
|
|_, _, _| {},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue