keymap ui: Resize columns on double click improvement (#35095)
This PR improves the behavior of resetting a column's size by double-clicking on its column handle. We now shrink/grow to the side that has more leftover/additional space. We also improved the below 1. dragging was a couple of pixels off to the left because we didn't take the column handle’s width into account. 2. Column dragging now has memory and will shift things to their exact position when reversing a drag before letting the drag handle go. 3. Improved our test infrastructure. 4. Double clicking on a column's header resizes the column Release Notes: - N/A --------- Co-authored-by: Ben Kunkle <ben@zed.dev>
This commit is contained in:
parent
cd50958727
commit
4abe14f94a
2 changed files with 546 additions and 94 deletions
|
@ -45,3 +45,6 @@ ui_input.workspace = true
|
|||
util.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
db = {"workspace"= true, "features" = ["test-support"]}
|
||||
|
|
|
@ -2,9 +2,9 @@ use std::{ops::Range, rc::Rc, time::Duration};
|
|||
|
||||
use editor::{EditorSettings, ShowScrollbar, scroll::ScrollbarAutoHide};
|
||||
use gpui::{
|
||||
AbsoluteLength, AppContext, Axis, Context, DefiniteLength, DragMoveEvent, Entity, FocusHandle,
|
||||
Length, ListHorizontalSizingBehavior, ListSizingBehavior, MouseButton, Point, Stateful, Task,
|
||||
UniformListScrollHandle, WeakEntity, transparent_black, uniform_list,
|
||||
AbsoluteLength, AppContext, Axis, Context, DefiniteLength, DragMoveEvent, Entity, EntityId,
|
||||
FocusHandle, Length, ListHorizontalSizingBehavior, ListSizingBehavior, MouseButton, Point,
|
||||
Stateful, Task, UniformListScrollHandle, WeakEntity, transparent_black, uniform_list,
|
||||
};
|
||||
|
||||
use itertools::intersperse_with;
|
||||
|
@ -13,10 +13,12 @@ use ui::{
|
|||
ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component,
|
||||
ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator,
|
||||
InteractiveElement, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce,
|
||||
Scrollbar, ScrollbarState, StatefulInteractiveElement, Styled, StyledExt as _,
|
||||
Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, StyledExt as _,
|
||||
StyledTypography, Window, div, example_group_with_title, h_flex, px, single_example, v_flex,
|
||||
};
|
||||
|
||||
const RESIZE_COLUMN_WIDTH: f32 = 5.0;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DraggedColumn(usize);
|
||||
|
||||
|
@ -227,7 +229,7 @@ impl TableInteractionState {
|
|||
.id("column-resize-handle")
|
||||
.absolute()
|
||||
.left_neg_0p5()
|
||||
.w(px(5.0))
|
||||
.w(px(RESIZE_COLUMN_WIDTH))
|
||||
.h_full();
|
||||
|
||||
if resizable_columns
|
||||
|
@ -478,6 +480,7 @@ impl ResizeBehavior {
|
|||
|
||||
pub struct ColumnWidths<const COLS: usize> {
|
||||
widths: [DefiniteLength; COLS],
|
||||
visible_widths: [DefiniteLength; COLS],
|
||||
cached_bounds_width: Pixels,
|
||||
initialized: bool,
|
||||
}
|
||||
|
@ -486,6 +489,7 @@ impl<const COLS: usize> ColumnWidths<COLS> {
|
|||
pub fn new(_: &mut App) -> Self {
|
||||
Self {
|
||||
widths: [DefiniteLength::default(); COLS],
|
||||
visible_widths: [DefiniteLength::default(); COLS],
|
||||
cached_bounds_width: Default::default(),
|
||||
initialized: false,
|
||||
}
|
||||
|
@ -512,46 +516,105 @@ impl<const COLS: usize> ColumnWidths<COLS> {
|
|||
let rem_size = window.rem_size();
|
||||
let initial_sizes =
|
||||
initial_sizes.map(|length| Self::get_fraction(&length, bounds_width, rem_size));
|
||||
let mut widths = self
|
||||
let widths = self
|
||||
.widths
|
||||
.map(|length| Self::get_fraction(&length, bounds_width, rem_size));
|
||||
|
||||
let diff = initial_sizes[double_click_position] - widths[double_click_position];
|
||||
let updated_widths = Self::reset_to_initial_size(
|
||||
double_click_position,
|
||||
widths,
|
||||
initial_sizes,
|
||||
resize_behavior,
|
||||
);
|
||||
self.widths = updated_widths.map(DefiniteLength::Fraction);
|
||||
self.visible_widths = self.widths;
|
||||
}
|
||||
|
||||
if diff > 0.0 {
|
||||
let diff_remaining = self.propagate_resize_diff_right(
|
||||
diff,
|
||||
double_click_position,
|
||||
&mut widths,
|
||||
resize_behavior,
|
||||
);
|
||||
fn reset_to_initial_size(
|
||||
col_idx: usize,
|
||||
mut widths: [f32; COLS],
|
||||
initial_sizes: [f32; COLS],
|
||||
resize_behavior: &[ResizeBehavior; COLS],
|
||||
) -> [f32; COLS] {
|
||||
// RESET:
|
||||
// Part 1:
|
||||
// Figure out if we should shrink/grow the selected column
|
||||
// Get diff which represents the change in column we want to make initial size delta curr_size = diff
|
||||
//
|
||||
// Part 2: We need to decide which side column we should move and where
|
||||
//
|
||||
// If we want to grow our column we should check the left/right columns diff to see what side
|
||||
// has a greater delta than their initial size. Likewise, if we shrink our column we should check
|
||||
// the left/right column diffs to see what side has the smallest delta.
|
||||
//
|
||||
// Part 3: resize
|
||||
//
|
||||
// col_idx represents the column handle to the right of an active column
|
||||
//
|
||||
// If growing and right has the greater delta {
|
||||
// shift col_idx to the right
|
||||
// } else if growing and left has the greater delta {
|
||||
// shift col_idx - 1 to the left
|
||||
// } else if shrinking and the right has the greater delta {
|
||||
// shift
|
||||
// } {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if we need to shrink, then if the right
|
||||
//
|
||||
|
||||
if diff_remaining > 0.0 && double_click_position > 0 {
|
||||
self.propagate_resize_diff_left(
|
||||
-diff_remaining,
|
||||
double_click_position - 1,
|
||||
// DRAGGING
|
||||
// we get diff which represents the change in the _drag handle_ position
|
||||
// -diff => dragging left ->
|
||||
// grow the column to the right of the handle as much as we can shrink columns to the left of the handle
|
||||
// +diff => dragging right -> growing handles column
|
||||
// grow the column to the left of the handle as much as we can shrink columns to the right of the handle
|
||||
//
|
||||
|
||||
let diff = initial_sizes[col_idx] - widths[col_idx];
|
||||
|
||||
let left_diff =
|
||||
initial_sizes[..col_idx].iter().sum::<f32>() - widths[..col_idx].iter().sum::<f32>();
|
||||
let right_diff = initial_sizes[col_idx + 1..].iter().sum::<f32>()
|
||||
- widths[col_idx + 1..].iter().sum::<f32>();
|
||||
|
||||
let go_left_first = if diff < 0.0 {
|
||||
left_diff > right_diff
|
||||
} else {
|
||||
left_diff < right_diff
|
||||
};
|
||||
|
||||
if !go_left_first {
|
||||
let diff_remaining =
|
||||
Self::propagate_resize_diff(diff, col_idx, &mut widths, resize_behavior, 1);
|
||||
|
||||
if diff_remaining != 0.0 && col_idx > 0 {
|
||||
Self::propagate_resize_diff(
|
||||
diff_remaining,
|
||||
col_idx,
|
||||
&mut widths,
|
||||
resize_behavior,
|
||||
-1,
|
||||
);
|
||||
}
|
||||
} else if double_click_position > 0 {
|
||||
let diff_remaining = self.propagate_resize_diff_left(
|
||||
diff,
|
||||
double_click_position,
|
||||
&mut widths,
|
||||
resize_behavior,
|
||||
);
|
||||
} else {
|
||||
let diff_remaining =
|
||||
Self::propagate_resize_diff(diff, col_idx, &mut widths, resize_behavior, -1);
|
||||
|
||||
if diff_remaining < 0.0 {
|
||||
self.propagate_resize_diff_right(
|
||||
-diff_remaining,
|
||||
double_click_position,
|
||||
if diff_remaining != 0.0 {
|
||||
Self::propagate_resize_diff(
|
||||
diff_remaining,
|
||||
col_idx,
|
||||
&mut widths,
|
||||
resize_behavior,
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
self.widths = widths.map(DefiniteLength::Fraction);
|
||||
|
||||
widths
|
||||
}
|
||||
|
||||
fn on_drag_move(
|
||||
|
@ -569,98 +632,102 @@ impl<const COLS: usize> ColumnWidths<COLS> {
|
|||
let bounds_width = bounds.right() - bounds.left();
|
||||
let col_idx = drag_event.drag(cx).0;
|
||||
|
||||
let column_handle_width = Self::get_fraction(
|
||||
&DefiniteLength::Absolute(AbsoluteLength::Pixels(px(RESIZE_COLUMN_WIDTH))),
|
||||
bounds_width,
|
||||
rem_size,
|
||||
);
|
||||
|
||||
let mut widths = self
|
||||
.widths
|
||||
.map(|length| Self::get_fraction(&length, bounds_width, rem_size));
|
||||
|
||||
for length in widths[0..=col_idx].iter() {
|
||||
col_position += length;
|
||||
col_position += length + column_handle_width;
|
||||
}
|
||||
|
||||
let mut total_length_ratio = col_position;
|
||||
for length in widths[col_idx + 1..].iter() {
|
||||
total_length_ratio += length;
|
||||
}
|
||||
total_length_ratio += (COLS - 1 - col_idx) as f32 * column_handle_width;
|
||||
|
||||
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 diff = drag_fraction - col_position - column_handle_width / 2.0;
|
||||
|
||||
let is_dragging_right = diff > 0.0;
|
||||
Self::drag_column_handle(diff, col_idx, &mut widths, resize_behavior);
|
||||
|
||||
if is_dragging_right {
|
||||
self.propagate_resize_diff_right(diff, col_idx, &mut widths, resize_behavior);
|
||||
} else {
|
||||
// Resize behavior should be improved in the future by also seeking to the right column when there's not enough space
|
||||
self.propagate_resize_diff_left(diff, col_idx, &mut widths, resize_behavior);
|
||||
}
|
||||
self.widths = widths.map(DefiniteLength::Fraction);
|
||||
self.visible_widths = widths.map(DefiniteLength::Fraction);
|
||||
}
|
||||
|
||||
fn propagate_resize_diff_right(
|
||||
&self,
|
||||
fn drag_column_handle(
|
||||
diff: f32,
|
||||
col_idx: usize,
|
||||
widths: &mut [f32; COLS],
|
||||
resize_behavior: &[ResizeBehavior; COLS],
|
||||
) -> f32 {
|
||||
let mut diff_remaining = diff;
|
||||
let mut curr_column = col_idx + 1;
|
||||
|
||||
while diff_remaining > 0.0 && curr_column < COLS {
|
||||
let Some(min_size) = resize_behavior[curr_column - 1].min_size() else {
|
||||
curr_column += 1;
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut curr_width = widths[curr_column] - diff_remaining;
|
||||
|
||||
diff_remaining = 0.0;
|
||||
if min_size > curr_width {
|
||||
diff_remaining += min_size - curr_width;
|
||||
curr_width = min_size;
|
||||
}
|
||||
widths[curr_column] = curr_width;
|
||||
curr_column += 1;
|
||||
) {
|
||||
// if diff > 0.0 then go right
|
||||
if diff > 0.0 {
|
||||
Self::propagate_resize_diff(diff, col_idx, widths, resize_behavior, 1);
|
||||
} else {
|
||||
Self::propagate_resize_diff(-diff, col_idx + 1, widths, resize_behavior, -1);
|
||||
}
|
||||
|
||||
widths[col_idx] = widths[col_idx] + (diff - diff_remaining);
|
||||
return diff_remaining;
|
||||
}
|
||||
|
||||
fn propagate_resize_diff_left(
|
||||
&mut self,
|
||||
fn propagate_resize_diff(
|
||||
diff: f32,
|
||||
mut curr_column: usize,
|
||||
col_idx: usize,
|
||||
widths: &mut [f32; COLS],
|
||||
resize_behavior: &[ResizeBehavior; COLS],
|
||||
direction: i8,
|
||||
) -> f32 {
|
||||
let mut diff_remaining = diff;
|
||||
let col_idx = curr_column;
|
||||
while diff_remaining < 0.0 {
|
||||
if resize_behavior[col_idx].min_size().is_none() {
|
||||
return diff;
|
||||
}
|
||||
|
||||
let step_right;
|
||||
let step_left;
|
||||
if direction < 0 {
|
||||
step_right = 0;
|
||||
step_left = 1;
|
||||
} else {
|
||||
step_right = 1;
|
||||
step_left = 0;
|
||||
}
|
||||
if col_idx == 0 && direction < 0 {
|
||||
return diff;
|
||||
}
|
||||
let mut curr_column = col_idx + step_right - step_left;
|
||||
|
||||
while diff_remaining != 0.0 && curr_column < COLS {
|
||||
let Some(min_size) = resize_behavior[curr_column].min_size() else {
|
||||
if curr_column == 0 {
|
||||
break;
|
||||
}
|
||||
curr_column -= 1;
|
||||
curr_column -= step_left;
|
||||
curr_column += step_right;
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut curr_width = widths[curr_column] + diff_remaining;
|
||||
|
||||
diff_remaining = 0.0;
|
||||
if curr_width < min_size {
|
||||
diff_remaining = curr_width - min_size;
|
||||
curr_width = min_size
|
||||
}
|
||||
|
||||
let curr_width = widths[curr_column] - diff_remaining;
|
||||
widths[curr_column] = curr_width;
|
||||
|
||||
if min_size > curr_width {
|
||||
diff_remaining = min_size - curr_width;
|
||||
widths[curr_column] = min_size;
|
||||
} else {
|
||||
diff_remaining = 0.0;
|
||||
break;
|
||||
}
|
||||
if curr_column == 0 {
|
||||
break;
|
||||
}
|
||||
curr_column -= 1;
|
||||
curr_column -= step_left;
|
||||
curr_column += step_right;
|
||||
}
|
||||
widths[col_idx + 1] = widths[col_idx + 1] - (diff - diff_remaining);
|
||||
widths[col_idx] = widths[col_idx] + (diff - diff_remaining);
|
||||
|
||||
return diff_remaining;
|
||||
}
|
||||
|
@ -686,7 +753,7 @@ impl<const COLS: usize> TableWidths<COLS> {
|
|||
fn lengths(&self, cx: &App) -> [Length; COLS] {
|
||||
self.current
|
||||
.as_ref()
|
||||
.map(|entity| entity.read(cx).widths.map(Length::Definite))
|
||||
.map(|entity| entity.read(cx).visible_widths.map(Length::Definite))
|
||||
.unwrap_or(self.initial.map(Length::Definite))
|
||||
}
|
||||
}
|
||||
|
@ -799,6 +866,7 @@ impl<const COLS: usize> Table<COLS> {
|
|||
if !widths.initialized {
|
||||
widths.initialized = true;
|
||||
widths.widths = table_widths.initial;
|
||||
widths.visible_widths = widths.widths;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -888,11 +956,24 @@ pub fn render_row<const COLS: usize>(
|
|||
pub fn render_header<const COLS: usize>(
|
||||
headers: [impl IntoElement; COLS],
|
||||
table_context: TableRenderContext<COLS>,
|
||||
columns_widths: Option<(
|
||||
WeakEntity<ColumnWidths<COLS>>,
|
||||
[ResizeBehavior; COLS],
|
||||
[DefiniteLength; COLS],
|
||||
)>,
|
||||
entity_id: Option<EntityId>,
|
||||
cx: &mut App,
|
||||
) -> impl IntoElement {
|
||||
let column_widths = table_context
|
||||
.column_widths
|
||||
.map_or([None; COLS], |widths| widths.map(Some));
|
||||
|
||||
let element_id = entity_id
|
||||
.map(|entity| entity.to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let shared_element_id: SharedString = format!("table-{}", element_id).into();
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
|
@ -902,12 +983,39 @@ pub fn render_header<const COLS: usize>(
|
|||
.p_2()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.children(
|
||||
headers
|
||||
.into_iter()
|
||||
.zip(column_widths)
|
||||
.map(|(h, width)| base_cell_style_text(width, cx).child(h)),
|
||||
)
|
||||
.children(headers.into_iter().enumerate().zip(column_widths).map(
|
||||
|((header_idx, h), width)| {
|
||||
base_cell_style_text(width, cx)
|
||||
.child(h)
|
||||
.id(ElementId::NamedInteger(
|
||||
shared_element_id.clone(),
|
||||
header_idx as u64,
|
||||
))
|
||||
.when_some(
|
||||
columns_widths.as_ref().cloned(),
|
||||
|this, (column_widths, resizables, initial_sizes)| {
|
||||
if resizables[header_idx].is_resizable() {
|
||||
this.on_click(move |event, window, cx| {
|
||||
if event.down.click_count > 1 {
|
||||
column_widths
|
||||
.update(cx, |column, _| {
|
||||
column.on_double_click(
|
||||
header_idx,
|
||||
&initial_sizes,
|
||||
&resizables,
|
||||
window,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -939,6 +1047,12 @@ impl<const COLS: usize> RenderOnce for Table<COLS> {
|
|||
.and_then(|widths| Some((widths.current.as_ref()?, widths.resizable)))
|
||||
.map(|(curr, resize_behavior)| (curr.downgrade(), resize_behavior));
|
||||
|
||||
let current_widths_with_initial_sizes = self
|
||||
.col_widths
|
||||
.as_ref()
|
||||
.and_then(|widths| Some((widths.current.as_ref()?, widths.resizable, widths.initial)))
|
||||
.map(|(curr, resize_behavior, initial)| (curr.downgrade(), resize_behavior, initial));
|
||||
|
||||
let scroll_track_size = px(16.);
|
||||
let h_scroll_offset = if interaction_state
|
||||
.as_ref()
|
||||
|
@ -958,7 +1072,13 @@ impl<const COLS: usize> RenderOnce for Table<COLS> {
|
|||
.h_full()
|
||||
.v_flex()
|
||||
.when_some(self.headers.take(), |this, headers| {
|
||||
this.child(render_header(headers, table_context.clone(), cx))
|
||||
this.child(render_header(
|
||||
headers,
|
||||
table_context.clone(),
|
||||
current_widths_with_initial_sizes,
|
||||
interaction_state.as_ref().map(Entity::entity_id),
|
||||
cx,
|
||||
))
|
||||
})
|
||||
.when_some(current_widths, {
|
||||
|this, (widths, resize_behavior)| {
|
||||
|
@ -972,19 +1092,28 @@ impl<const COLS: usize> RenderOnce for Table<COLS> {
|
|||
.ok();
|
||||
}
|
||||
})
|
||||
.on_children_prepainted(move |bounds, _, cx| {
|
||||
.on_children_prepainted({
|
||||
let widths = widths.clone();
|
||||
move |bounds, _, cx| {
|
||||
widths
|
||||
.update(cx, |widths, _| {
|
||||
// This works because all children x axis bounds are the same
|
||||
widths.cached_bounds_width =
|
||||
bounds[0].right() - bounds[0].left();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.on_drop::<DraggedColumn>(move |_, _, cx| {
|
||||
widths
|
||||
.update(cx, |widths, _| {
|
||||
// This works because all children x axis bounds are the same
|
||||
widths.cached_bounds_width = bounds[0].right() - bounds[0].left();
|
||||
widths.widths = widths.visible_widths;
|
||||
})
|
||||
.ok();
|
||||
// Finish the resize operation
|
||||
})
|
||||
}
|
||||
})
|
||||
.on_drop::<DraggedColumn>(|_, _, _| {
|
||||
// Finish the resize operation
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.flex_grow()
|
||||
|
@ -1313,3 +1442,323 @@ impl Component for Table<3> {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
fn is_almost_eq(a: &[f32], b: &[f32]) -> bool {
|
||||
a.len() == b.len() && a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-6)
|
||||
}
|
||||
|
||||
fn cols_to_str<const COLS: usize>(cols: &[f32; COLS], total_size: f32) -> String {
|
||||
cols.map(|f| "*".repeat(f32::round(f * total_size) as usize))
|
||||
.join("|")
|
||||
}
|
||||
|
||||
fn parse_resize_behavior<const COLS: usize>(
|
||||
input: &str,
|
||||
total_size: f32,
|
||||
) -> [ResizeBehavior; COLS] {
|
||||
let mut resize_behavior = [ResizeBehavior::None; COLS];
|
||||
let mut max_index = 0;
|
||||
for (index, col) in input.split('|').enumerate() {
|
||||
if col.starts_with('X') || col.is_empty() {
|
||||
resize_behavior[index] = ResizeBehavior::None;
|
||||
} else if col.starts_with('*') {
|
||||
resize_behavior[index] = ResizeBehavior::MinSize(col.len() as f32 / total_size);
|
||||
} else {
|
||||
panic!("invalid test input: unrecognized resize behavior: {}", col);
|
||||
}
|
||||
max_index = index;
|
||||
}
|
||||
|
||||
if max_index + 1 != COLS {
|
||||
panic!("invalid test input: too many columns");
|
||||
}
|
||||
resize_behavior
|
||||
}
|
||||
|
||||
mod reset_column_size {
|
||||
use super::*;
|
||||
|
||||
fn parse<const COLS: usize>(input: &str) -> ([f32; COLS], f32, Option<usize>) {
|
||||
let mut widths = [f32::NAN; COLS];
|
||||
let mut column_index = None;
|
||||
for (index, col) in input.split('|').enumerate() {
|
||||
widths[index] = col.len() as f32;
|
||||
if col.starts_with('X') {
|
||||
column_index = Some(index);
|
||||
}
|
||||
}
|
||||
|
||||
for w in widths {
|
||||
assert!(w.is_finite(), "incorrect number of columns");
|
||||
}
|
||||
let total = widths.iter().sum::<f32>();
|
||||
for width in &mut widths {
|
||||
*width /= total;
|
||||
}
|
||||
(widths, total, column_index)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_reset_size<const COLS: usize>(
|
||||
initial_sizes: &str,
|
||||
widths: &str,
|
||||
expected: &str,
|
||||
resize_behavior: &str,
|
||||
) {
|
||||
let (initial_sizes, total_1, None) = parse::<COLS>(initial_sizes) else {
|
||||
panic!("invalid test input: initial sizes should not be marked");
|
||||
};
|
||||
let (widths, total_2, Some(column_index)) = parse::<COLS>(widths) else {
|
||||
panic!("invalid test input: widths should be marked");
|
||||
};
|
||||
assert_eq!(
|
||||
total_1, total_2,
|
||||
"invalid test input: total width not the same {total_1}, {total_2}"
|
||||
);
|
||||
let (expected, total_3, None) = parse::<COLS>(expected) else {
|
||||
panic!("invalid test input: expected should not be marked: {expected:?}");
|
||||
};
|
||||
assert_eq!(
|
||||
total_2, total_3,
|
||||
"invalid test input: total width not the same"
|
||||
);
|
||||
let resize_behavior = parse_resize_behavior::<COLS>(resize_behavior, total_1);
|
||||
let result = ColumnWidths::reset_to_initial_size(
|
||||
column_index,
|
||||
widths,
|
||||
initial_sizes,
|
||||
&resize_behavior,
|
||||
);
|
||||
let is_eq = is_almost_eq(&result, &expected);
|
||||
if !is_eq {
|
||||
let result_str = cols_to_str(&result, total_1);
|
||||
let expected_str = cols_to_str(&expected, total_1);
|
||||
panic!(
|
||||
"resize failed\ncomputed: {result_str}\nexpected: {expected_str}\n\ncomputed values: {result:?}\nexpected values: {expected:?}\n:minimum widths: {resize_behavior:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! check_reset_size {
|
||||
(columns: $cols:expr, starting: $initial:expr, snapshot: $current:expr, expected: $expected:expr, resizing: $resizing:expr $(,)?) => {
|
||||
check_reset_size::<$cols>($initial, $current, $expected, $resizing);
|
||||
};
|
||||
($name:ident, columns: $cols:expr, starting: $initial:expr, snapshot: $current:expr, expected: $expected:expr, minimums: $resizing:expr $(,)?) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
check_reset_size::<$cols>($initial, $current, $expected, $resizing);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
check_reset_size!(
|
||||
basic_right,
|
||||
columns: 5,
|
||||
starting: "**|**|**|**|**",
|
||||
snapshot: "**|**|X|***|**",
|
||||
expected: "**|**|**|**|**",
|
||||
minimums: "X|*|*|*|*",
|
||||
);
|
||||
|
||||
check_reset_size!(
|
||||
basic_left,
|
||||
columns: 5,
|
||||
starting: "**|**|**|**|**",
|
||||
snapshot: "**|**|***|X|**",
|
||||
expected: "**|**|**|**|**",
|
||||
minimums: "X|*|*|*|**",
|
||||
);
|
||||
|
||||
check_reset_size!(
|
||||
squashed_left_reset_col2,
|
||||
columns: 6,
|
||||
starting: "*|***|**|**|****|*",
|
||||
snapshot: "*|*|X|*|*|********",
|
||||
expected: "*|*|**|*|*|*******",
|
||||
minimums: "X|*|*|*|*|*",
|
||||
);
|
||||
|
||||
check_reset_size!(
|
||||
grow_cascading_right,
|
||||
columns: 6,
|
||||
starting: "*|***|****|**|***|*",
|
||||
snapshot: "*|***|X|**|**|*****",
|
||||
expected: "*|***|****|*|*|****",
|
||||
minimums: "X|*|*|*|*|*",
|
||||
);
|
||||
|
||||
check_reset_size!(
|
||||
squashed_right_reset_col4,
|
||||
columns: 6,
|
||||
starting: "*|***|**|**|****|*",
|
||||
snapshot: "*|********|*|*|X|*",
|
||||
expected: "*|*****|*|*|****|*",
|
||||
minimums: "X|*|*|*|*|*",
|
||||
);
|
||||
|
||||
check_reset_size!(
|
||||
reset_col6_right,
|
||||
columns: 6,
|
||||
starting: "*|***|**|***|***|**",
|
||||
snapshot: "*|***|**|***|**|XXX",
|
||||
expected: "*|***|**|***|***|**",
|
||||
minimums: "X|*|*|*|*|*",
|
||||
);
|
||||
|
||||
check_reset_size!(
|
||||
reset_col6_left,
|
||||
columns: 6,
|
||||
starting: "*|***|**|***|***|**",
|
||||
snapshot: "*|***|**|***|****|X",
|
||||
expected: "*|***|**|***|***|**",
|
||||
minimums: "X|*|*|*|*|*",
|
||||
);
|
||||
|
||||
check_reset_size!(
|
||||
last_column_grow_cascading,
|
||||
columns: 6,
|
||||
starting: "*|***|**|**|**|***",
|
||||
snapshot: "*|*******|*|**|*|X",
|
||||
expected: "*|******|*|*|*|***",
|
||||
minimums: "X|*|*|*|*|*",
|
||||
);
|
||||
|
||||
check_reset_size!(
|
||||
goes_left_when_left_has_extreme_diff,
|
||||
columns: 6,
|
||||
starting: "*|***|****|**|**|***",
|
||||
snapshot: "*|********|X|*|**|**",
|
||||
expected: "*|*****|****|*|**|**",
|
||||
minimums: "X|*|*|*|*|*",
|
||||
);
|
||||
|
||||
check_reset_size!(
|
||||
basic_shrink_right,
|
||||
columns: 6,
|
||||
starting: "**|**|**|**|**|**",
|
||||
snapshot: "**|**|XXX|*|**|**",
|
||||
expected: "**|**|**|**|**|**",
|
||||
minimums: "X|*|*|*|*|*",
|
||||
);
|
||||
|
||||
check_reset_size!(
|
||||
shrink_should_go_left,
|
||||
columns: 6,
|
||||
starting: "*|***|**|*|*|*",
|
||||
snapshot: "*|*|XXX|**|*|*",
|
||||
expected: "*|**|**|**|*|*",
|
||||
minimums: "X|*|*|*|*|*",
|
||||
);
|
||||
|
||||
check_reset_size!(
|
||||
shrink_should_go_right,
|
||||
columns: 6,
|
||||
starting: "*|***|**|**|**|*",
|
||||
snapshot: "*|****|XXX|*|*|*",
|
||||
expected: "*|****|**|**|*|*",
|
||||
minimums: "X|*|*|*|*|*",
|
||||
);
|
||||
}
|
||||
|
||||
mod drag_handle {
|
||||
use super::*;
|
||||
|
||||
fn parse<const COLS: usize>(input: &str) -> ([f32; COLS], f32, Option<usize>) {
|
||||
let mut widths = [f32::NAN; COLS];
|
||||
let column_index = input.replace("*", "").find("I");
|
||||
for (index, col) in input.replace("I", "|").split('|').enumerate() {
|
||||
widths[index] = col.len() as f32;
|
||||
}
|
||||
|
||||
for w in widths {
|
||||
assert!(w.is_finite(), "incorrect number of columns");
|
||||
}
|
||||
let total = widths.iter().sum::<f32>();
|
||||
for width in &mut widths {
|
||||
*width /= total;
|
||||
}
|
||||
(widths, total, column_index)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check<const COLS: usize>(
|
||||
distance: i32,
|
||||
widths: &str,
|
||||
expected: &str,
|
||||
resize_behavior: &str,
|
||||
) {
|
||||
let (mut widths, total_1, Some(column_index)) = parse::<COLS>(widths) else {
|
||||
panic!("invalid test input: widths should be marked");
|
||||
};
|
||||
let (expected, total_2, None) = parse::<COLS>(expected) else {
|
||||
panic!("invalid test input: expected should not be marked: {expected:?}");
|
||||
};
|
||||
assert_eq!(
|
||||
total_1, total_2,
|
||||
"invalid test input: total width not the same"
|
||||
);
|
||||
let resize_behavior = parse_resize_behavior::<COLS>(resize_behavior, total_1);
|
||||
|
||||
let distance = distance as f32 / total_1;
|
||||
|
||||
let result = ColumnWidths::drag_column_handle(
|
||||
distance,
|
||||
column_index,
|
||||
&mut widths,
|
||||
&resize_behavior,
|
||||
);
|
||||
|
||||
let is_eq = is_almost_eq(&widths, &expected);
|
||||
if !is_eq {
|
||||
let result_str = cols_to_str(&widths, total_1);
|
||||
let expected_str = cols_to_str(&expected, total_1);
|
||||
panic!(
|
||||
"resize failed\ncomputed: {result_str}\nexpected: {expected_str}\n\ncomputed values: {result:?}\nexpected values: {expected:?}\n:minimum widths: {resize_behavior:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! check {
|
||||
(columns: $cols:expr, distance: $dist:expr, snapshot: $current:expr, expected: $expected:expr, resizing: $resizing:expr $(,)?) => {
|
||||
check!($cols, $dist, $snapshot, $expected, $resizing);
|
||||
};
|
||||
($name:ident, columns: $cols:expr, distance: $dist:expr, snapshot: $current:expr, expected: $expected:expr, minimums: $resizing:expr $(,)?) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
check::<$cols>($dist, $current, $expected, $resizing);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
check!(
|
||||
basic_right_drag,
|
||||
columns: 3,
|
||||
distance: 1,
|
||||
snapshot: "**|**I**",
|
||||
expected: "**|***|*",
|
||||
minimums: "X|*|*",
|
||||
);
|
||||
|
||||
check!(
|
||||
drag_left_against_mins,
|
||||
columns: 5,
|
||||
distance: -1,
|
||||
snapshot: "*|*|*|*I*******",
|
||||
expected: "*|*|*|*|*******",
|
||||
minimums: "X|*|*|*|*",
|
||||
);
|
||||
|
||||
check!(
|
||||
drag_left,
|
||||
columns: 5,
|
||||
distance: -2,
|
||||
snapshot: "*|*|*|*****I***",
|
||||
expected: "*|*|*|***|*****",
|
||||
minimums: "X|*|*|*|*",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue