diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index b28a9a487067757b42e09babb31380d1efa14098..5e7e9667064fa00f872dd307eef7a25bace5e8a1 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -1435,11 +1435,12 @@ impl Render for KeymapEditor { DefiniteLength::Fraction(0.45), DefiniteLength::Fraction(0.08), ]) - .header(["", "Action", "Arguments", "Keystrokes", "Context", "Source"]) .resizable_columns( [false, true, true, true, true, true], - self.current_widths.clone(), + &self.current_widths, + cx, ) + .header(["", "Action", "Arguments", "Keystrokes", "Context", "Source"]) .uniform_list( "keymap-editor-table", row_count, diff --git a/crates/settings_ui/src/ui_components/table.rs b/crates/settings_ui/src/ui_components/table.rs index aa8dbe9772e15c10c907683c266d4a7a5b1466cc..1f5ca3be94e4c5fea5c6cf0c8d48b270d0076abe 100644 --- a/crates/settings_ui/src/ui_components/table.rs +++ b/crates/settings_ui/src/ui_components/table.rs @@ -2,9 +2,9 @@ use std::{ops::Range, rc::Rc, time::Duration}; use editor::{EditorSettings, ShowScrollbar, scroll::ScrollbarAutoHide}; use gpui::{ - AppContext, Axis, Context, Entity, FocusHandle, Length, ListHorizontalSizingBehavior, - ListSizingBehavior, MouseButton, Point, Stateful, Task, UniformListScrollHandle, WeakEntity, - transparent_black, uniform_list, + AbsoluteLength, AppContext, Axis, Context, DefiniteLength, DragMoveEvent, Entity, FocusHandle, + Length, ListHorizontalSizingBehavior, ListSizingBehavior, MouseButton, Point, Stateful, Task, + UniformListScrollHandle, WeakEntity, transparent_black, uniform_list, }; use itertools::intersperse_with; @@ -239,10 +239,6 @@ impl TableInteractionState { eprintln!("Start resizing column {:?}", ix); cx.new(|_cx| gpui::Empty) }) - .on_drag_move::(|e, _window, cx| { - eprintln!("Resizing column {:?}", e.drag(cx)); - // Do something here - }) } column_ix += 1; @@ -439,15 +435,135 @@ impl TableInteractionState { } pub struct ColumnWidths { - widths: [Pixels; COLS], + widths: [DefiniteLength; COLS], + initialized: bool, } impl ColumnWidths { pub fn new(_: &mut App) -> Self { Self { - widths: [px(0.0); COLS], + widths: [DefiniteLength::default(); COLS], + initialized: false, + } + } + + fn get_fraction(length: &DefiniteLength, bounds_width: Pixels, rem_size: Pixels) -> f32 { + match length { + DefiniteLength::Absolute(AbsoluteLength::Pixels(pixels)) => *pixels / bounds_width, + DefiniteLength::Absolute(AbsoluteLength::Rems(rems_width)) => { + rems_width.to_pixels(rem_size) / bounds_width + } + DefiniteLength::Fraction(fraction) => *fraction, } } + + fn on_drag_move( + &mut self, + drag_event: &DragMoveEvent, + window: &mut Window, + cx: &mut Context, + ) { + // - [ ] Fix bugs in resize + // - [ ] Create and respect a minimum size + // - [ ] Cascade resize columns to next column if at minimum width + // - [ ] Double click to reset column widths + let drag_position = drag_event.event.position; + let bounds = drag_event.bounds; + + let mut col_position = 0.0; + let rem_size = window.rem_size(); + let bounds_width = bounds.right() - bounds.left(); + let col_idx = drag_event.drag(cx).0; + + for length in self.widths[0..=col_idx].iter() { + col_position += Self::get_fraction(length, bounds_width, rem_size); + } + + let mut total_length_ratio = col_position; + for length in self.widths[col_idx + 1..].iter() { + total_length_ratio += Self::get_fraction(length, bounds_width, rem_size); + } + + let drag_fraction = (drag_position.x - bounds.left()) / bounds_width; + let drag_fraction = drag_fraction * total_length_ratio; + let diff = drag_fraction - col_position; + + let is_dragging_right = diff > 0.0; + + // TODO: broken when dragging left + // split into an if dragging_right { this } else { new loop} + // then combine if same logic + let mut diff_left = diff; + let mut curr_column = col_idx + 1; + let min_size = 0.05; // todo! + + if is_dragging_right { + while diff_left > 0.0 && curr_column < COLS { + let mut curr_width = + Self::get_fraction(&self.widths[curr_column], bounds_width, rem_size) + - diff_left; + + diff_left = 0.0; + if min_size > curr_width { + diff_left += min_size - curr_width; + curr_width = min_size; + } + self.widths[curr_column] = DefiniteLength::Fraction(curr_width); + curr_column += 1; + } + + self.widths[col_idx] = DefiniteLength::Fraction( + Self::get_fraction(&self.widths[col_idx], bounds_width, rem_size) + + (diff - diff_left), + ); + } else { + curr_column = col_idx; + while diff_left < 0.0 && curr_column > 0 { + let mut curr_width = + Self::get_fraction(&self.widths[curr_column], bounds_width, rem_size) + + diff_left; + + diff_left = 0.0; + if curr_width < min_size { + diff_left = curr_width - min_size; + curr_width = min_size + } + + self.widths[curr_column] = DefiniteLength::Fraction(curr_width); + curr_column -= 1; + } + + self.widths[col_idx + 1] = DefiniteLength::Fraction( + Self::get_fraction(&self.widths[col_idx + 1], bounds_width, rem_size) + - (diff - diff_left), + ); + } + } +} + +pub struct TableWidths { + initial: [DefiniteLength; COLS], + current: Option>>, + resizable: [bool; COLS], +} + +impl TableWidths { + pub fn new(widths: [impl Into; COLS]) -> Self { + let widths = widths.map(Into::into); + + TableWidths { + initial: widths.clone(), + current: None, + resizable: [false; COLS], + } + } + + fn lengths(&self, cx: &App) -> [Length; COLS] { + self.current + .as_ref() + .map(|entity| entity.read(cx).widths.map(|w| Length::Definite(w))) + .unwrap_or(self.initial.map(|w| Length::Definite(w))) + } } /// A table component @@ -458,9 +574,7 @@ pub struct Table { headers: Option<[AnyElement; COLS]>, rows: TableContents, interaction_state: Option>, - initial_widths: Option<[Length; COLS]>, - current_widths: Option>>, - resizable_columns: Option<[bool; COLS]>, + col_widths: Option>, map_row: Option), &mut Window, &mut App) -> AnyElement>>, empty_table_callback: Option AnyElement>>, } @@ -474,11 +588,9 @@ impl Table { headers: None, rows: TableContents::Vec(Vec::new()), interaction_state: None, - initial_widths: None, - current_widths: None, map_row: None, empty_table_callback: None, - resizable_columns: None, + col_widths: None, } } @@ -539,18 +651,32 @@ impl Table { self } - pub fn column_widths(mut self, widths: [impl Into; COLS]) -> Self { - self.initial_widths = Some(widths.map(Into::into)); + pub fn column_widths(mut self, widths: [impl Into; COLS]) -> Self { + if self.col_widths.is_none() { + self.col_widths = Some(TableWidths::new(widths)); + } self } pub fn resizable_columns( mut self, resizable: [impl Into; COLS], - current_widths: Entity>, + column_widths: &Entity>, + cx: &mut App, ) -> Self { - self.resizable_columns = Some(resizable.map(Into::into)); - self.current_widths = Some(current_widths); + if let Some(table_widths) = self.col_widths.as_mut() { + table_widths.resizable = resizable.map(Into::into); + let column_widths = table_widths + .current + .get_or_insert_with(|| column_widths.clone()); + + column_widths.update(cx, |widths, _| { + if !widths.initialized { + widths.initialized = true; + widths.widths = table_widths.initial.clone(); + } + }) + } self } @@ -668,11 +794,11 @@ pub struct TableRenderContext { } impl TableRenderContext { - fn new(table: &Table) -> Self { + fn new(table: &Table, cx: &App) -> Self { Self { striped: table.striped, total_row_count: table.rows.len(), - column_widths: table.initial_widths, + column_widths: table.col_widths.as_ref().map(|widths| widths.lengths(cx)), map_row: table.map_row.clone(), } } @@ -680,8 +806,13 @@ impl TableRenderContext { impl RenderOnce for Table { fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement { - let table_context = TableRenderContext::new(&self); + let table_context = TableRenderContext::new(&self, cx); let interaction_state = self.interaction_state.and_then(|state| state.upgrade()); + let current_widths = self + .col_widths + .as_ref() + .and_then(|widths| widths.current.as_ref()) + .map(|curr| curr.downgrade()); let scroll_track_size = px(16.); let h_scroll_offset = if interaction_state @@ -704,6 +835,18 @@ impl RenderOnce for Table { .when_some(self.headers.take(), |this, headers| { this.child(render_header(headers, table_context.clone(), cx)) }) + .when_some(current_widths, |this, widths| { + this.on_drag_move::(move |e, window, cx| { + widths + .update(cx, |widths, cx| { + widths.on_drag_move(e, window, cx); + }) + .ok(); + }) + }) + .on_drop::(|_, _, _| { + // Finish the resize operation + }) .child( div() .flex_grow() @@ -759,15 +902,14 @@ impl RenderOnce for Table { ), }) .when_some( - self.initial_widths - .as_ref() - .zip(interaction_state.as_ref()) - .zip(self.resizable_columns.as_ref()), - |parent, ((column_widths, state), resizable_columns)| { + self.col_widths.as_ref().zip(interaction_state.as_ref()), + |parent, (table_widths, state)| { parent.child(state.update(cx, |state, cx| { + let resizable_columns = table_widths.resizable; + let column_widths = table_widths.lengths(cx); state.render_resize_handles( - column_widths, - resizable_columns, + &column_widths, + &resizable_columns, window, cx, ) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 4565cef34719cdf3d4c506e7ba73dedb8cc6e3de..ff9fe6d4c0ff4f91b7de0df7a787b3575d3f0daf 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -995,9 +995,12 @@ mod element { Axis::Horizontal => px(HORIZONTAL_MIN_SIZE), Axis::Vertical => px(VERTICAL_MIN_SIZE), }; + // Equivalent to ColumnWidths (but in terms of flexes instead of percentages) + // Flexes make this annoying, because we have to output "1.33, 1, 1", instead of "40%, 30%, 30%" let mut flexes = flexes.lock(); debug_assert!(flex_values_in_bounds(flexes.as_slice())); + // Math to convert a flex value to a pixel value let size = move |ix, flexes: &[f32]| { container_size.along(axis) * (flexes[ix] / flexes.len() as f32) }; @@ -1007,9 +1010,13 @@ mod element { return; } + // This is basically a "bucket" of pixel changes that need to be applied in response to this + // mouse event. Probably a small, fractional number like 0.5 or 1.5 pixels let mut proposed_current_pixel_change = (e.position - child_start).along(axis) - size(ix, flexes.as_slice()); + // This takes a pixel change, and computes the flex changes that correspond to this pixel change + // as well as the next one, for some reason let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| { let flex_change = pixel_dx / container_size.along(axis); let current_target_flex = flexes[target_ix] + flex_change; @@ -1017,6 +1024,9 @@ mod element { (current_target_flex, next_target_flex) }; + // Generate the list of flex successors, from the current index. + // If you're dragging column 3 forward, out of 6 columns, then this code will produce [4, 5, 6] + // If you're dragging column 3 backward, out of 6 columns, then this code will produce [2, 1, 0] let mut successors = iter::from_fn({ let forward = proposed_current_pixel_change > px(0.); let mut ix_offset = 0; @@ -1034,6 +1044,7 @@ mod element { } }); + // Now actually loop over these, and empty our bucket of pixel changes while proposed_current_pixel_change.abs() > px(0.) { let Some(current_ix) = successors.next() else { break;