1240 lines · 46.6 KB · Rust
RawDownload
use std::{ops::Range, rc::Rc}; use gpui::{    AbsoluteLength, AppContext, Context, DefiniteLength, DragMoveEvent, Entity, EntityId,    FocusHandle, Length, ListHorizontalSizingBehavior, ListSizingBehavior, ListState, Point,    Stateful, UniformListScrollHandle, WeakEntity, list, transparent_black, uniform_list,}; use crate::{    ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component,    ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator,    InteractiveElement, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce,    ScrollAxes, ScrollableHandle, Scrollbars, SharedString, StatefulInteractiveElement, Styled,    StyledExt as _, StyledTypography, Window, WithScrollbar, div, example_group_with_title, h_flex,    px, single_example,    table_row::{IntoTableRow as _, TableRow},    v_flex,};use itertools::intersperse_with; pub mod table_row;#[cfg(test)]mod tests; const RESIZE_COLUMN_WIDTH: f32 = 8.0; /// Represents an unchecked table row, which is a vector of elements./// Will be converted into `TableRow<T>` internallypub type UncheckedTableRow<T> = Vec<T>; #[derive(Debug)]struct DraggedColumn(usize); struct UniformListData {    render_list_of_rows_fn:        Box<dyn Fn(Range<usize>, &mut Window, &mut App) -> Vec<UncheckedTableRow<AnyElement>>>,    element_id: ElementId,    row_count: usize,} struct VariableRowHeightListData {    /// Unlike UniformList, this closure renders only single row, allowing each one to have its own height    render_row_fn: Box<dyn Fn(usize, &mut Window, &mut App) -> UncheckedTableRow<AnyElement>>,    list_state: ListState,    row_count: usize,} enum TableContents {    Vec(Vec<TableRow<AnyElement>>),    UniformList(UniformListData),    VariableRowHeightList(VariableRowHeightListData),} impl TableContents {    fn rows_mut(&mut self) -> Option<&mut Vec<TableRow<AnyElement>>> {        match self {            TableContents::Vec(rows) => Some(rows),            TableContents::UniformList(_) => None,            TableContents::VariableRowHeightList(_) => None,        }    }     fn len(&self) -> usize {        match self {            TableContents::Vec(rows) => rows.len(),            TableContents::UniformList(data) => data.row_count,            TableContents::VariableRowHeightList(data) => data.row_count,        }    }     fn is_empty(&self) -> bool {        self.len() == 0    }} pub struct TableInteractionState {    pub focus_handle: FocusHandle,    pub scroll_handle: UniformListScrollHandle,    pub custom_scrollbar: Option<Scrollbars>,} impl TableInteractionState {    pub fn new(cx: &mut App) -> Self {        Self {            focus_handle: cx.focus_handle(),            scroll_handle: UniformListScrollHandle::new(),            custom_scrollbar: None,        }    }     pub fn with_custom_scrollbar(mut self, custom_scrollbar: Scrollbars) -> Self {        self.custom_scrollbar = Some(custom_scrollbar);        self    }     pub fn scroll_offset(&self) -> Point<Pixels> {        self.scroll_handle.offset()    }     pub fn set_scroll_offset(&self, offset: Point<Pixels>) {        self.scroll_handle.set_offset(offset);    }     pub fn listener<E: ?Sized>(        this: &Entity<Self>,        f: impl Fn(&mut Self, &E, &mut Window, &mut Context<Self>) + 'static,    ) -> impl Fn(&E, &mut Window, &mut App) + 'static {        let view = this.downgrade();        move |e: &E, window: &mut Window, cx: &mut App| {            view.update(cx, |view, cx| f(view, e, window, cx)).ok();        }    }     /// Renders invisible resize handles overlaid on top of table content.    ///    /// - Spacer: invisible element that matches the width of table column content    /// - Divider: contains the actual resize handle that users can drag to resize columns    ///    /// Structure: [spacer] [divider] [spacer] [divider] [spacer]    ///    /// Business logic:    /// 1. Creates spacers matching each column width    /// 2. Intersperses (inserts) resize handles between spacers (interactive only for resizable columns)    /// 3. Each handle supports hover highlighting, double-click to reset, and drag to resize    /// 4. Returns an absolute-positioned overlay that sits on top of table content    fn render_resize_handles(        &self,        column_widths: &TableRow<Length>,        resizable_columns: &TableRow<TableResizeBehavior>,        initial_sizes: &TableRow<DefiniteLength>,        columns: Option<Entity<TableColumnWidths>>,        window: &mut Window,        cx: &mut App,    ) -> AnyElement {        let spacers = column_widths            .as_slice()            .iter()            .map(|width| base_cell_style(Some(*width)).into_any_element());         let mut column_ix = 0;        let resizable_columns_shared = Rc::new(resizable_columns.clone());        let initial_sizes_shared = Rc::new(initial_sizes.clone());        let mut resizable_columns_iter = resizable_columns.as_slice().iter();         // Insert dividers between spacers (column content)        let dividers = intersperse_with(spacers, || {            let resizable_columns = Rc::clone(&resizable_columns_shared);            let initial_sizes = Rc::clone(&initial_sizes_shared);            window.with_id(column_ix, |window| {                let mut resize_divider = div()                    // This is required because this is evaluated at a different time than the use_state call above                    .id(column_ix)                    .relative()                    .top_0()                    .w_px()                    .h_full()                    .bg(cx.theme().colors().border.opacity(0.8));                 let mut resize_handle = div()                    .id("column-resize-handle")                    .absolute()                    .left_neg_0p5()                    .w(px(RESIZE_COLUMN_WIDTH))                    .h_full();                 if resizable_columns_iter                    .next()                    .is_some_and(TableResizeBehavior::is_resizable)                {                    let hovered = window.use_state(cx, |_window, _cx| false);                     resize_divider = resize_divider.when(*hovered.read(cx), |div| {                        div.bg(cx.theme().colors().border_focused)                    });                     resize_handle = resize_handle                        .on_hover(move |&was_hovered, _, cx| hovered.write(cx, was_hovered))                        .cursor_col_resize()                        .when_some(columns.clone(), |this, columns| {                            this.on_click(move |event, window, cx| {                                if event.click_count() >= 2 {                                    columns.update(cx, |columns, _| {                                        columns.on_double_click(                                            column_ix,                                            &initial_sizes,                                            &resizable_columns,                                            window,                                        );                                    })                                }                                 cx.stop_propagation();                            })                        })                        .on_drag(DraggedColumn(column_ix), |_, _offset, _window, cx| {                            cx.new(|_cx| gpui::Empty)                        })                }                 column_ix += 1;                resize_divider.child(resize_handle).into_any_element()            })        });         h_flex()            .id("resize-handles")            .absolute()            .inset_0()            .w_full()            .children(dividers)            .into_any_element()    }} #[derive(Debug, Copy, Clone, PartialEq)]pub enum TableResizeBehavior {    None,    Resizable,    MinSize(f32),} impl TableResizeBehavior {    pub fn is_resizable(&self) -> bool {        *self != TableResizeBehavior::None    }     pub fn min_size(&self) -> Option<f32> {        match self {            TableResizeBehavior::None => None,            TableResizeBehavior::Resizable => Some(0.05),            TableResizeBehavior::MinSize(min_size) => Some(*min_size),        }    }} pub struct TableColumnWidths {    widths: TableRow<DefiniteLength>,    visible_widths: TableRow<DefiniteLength>,    cached_bounds_width: Pixels,    initialized: bool,} impl TableColumnWidths {    pub fn new(cols: usize, _: &mut App) -> Self {        Self {            widths: vec![DefiniteLength::default(); cols].into_table_row(cols),            visible_widths: vec![DefiniteLength::default(); cols].into_table_row(cols),            cached_bounds_width: Default::default(),            initialized: false,        }    }     pub fn cols(&self) -> usize {        self.widths.cols()    }     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_double_click(        &mut self,        double_click_position: usize,        initial_sizes: &TableRow<DefiniteLength>,        resize_behavior: &TableRow<TableResizeBehavior>,        window: &mut Window,    ) {        let bounds_width = self.cached_bounds_width;        let rem_size = window.rem_size();        let initial_sizes =            initial_sizes.map_ref(|length| Self::get_fraction(length, bounds_width, rem_size));        let widths = self            .widths            .map_ref(|length| Self::get_fraction(length, bounds_width, rem_size));         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.clone(); // previously was copy    }     fn reset_to_initial_size(        col_idx: usize,        mut widths: TableRow<f32>,        initial_sizes: TableRow<f32>,        resize_behavior: &TableRow<TableResizeBehavior>,    ) -> TableRow<f32> {        // 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        //         // 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 {            let diff_remaining =                Self::propagate_resize_diff(diff, col_idx, &mut widths, resize_behavior, -1);             if diff_remaining != 0.0 {                Self::propagate_resize_diff(                    diff_remaining,                    col_idx,                    &mut widths,                    resize_behavior,                    1,                );            }        }         widths    }     fn on_drag_move(        &mut self,        drag_event: &DragMoveEvent<DraggedColumn>,        resize_behavior: &TableRow<TableResizeBehavior>,        window: &mut Window,        cx: &mut Context<Self>,    ) {        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;         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_ref(|length| Self::get_fraction(length, bounds_width, rem_size));         for length in widths[0..=col_idx].iter() {            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;        }        let cols = resize_behavior.cols();        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 - column_handle_width / 2.0;         Self::drag_column_handle(diff, col_idx, &mut widths, resize_behavior);         self.visible_widths = widths.map(DefiniteLength::Fraction);    }     fn drag_column_handle(        diff: f32,        col_idx: usize,        widths: &mut TableRow<f32>,        resize_behavior: &TableRow<TableResizeBehavior>,    ) {        // 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);        }    }     fn propagate_resize_diff(        diff: f32,        col_idx: usize,        widths: &mut TableRow<f32>,        resize_behavior: &TableRow<TableResizeBehavior>,        direction: i8,    ) -> f32 {        let mut diff_remaining = diff;        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 < widths.cols() {            let Some(min_size) = resize_behavior[curr_column].min_size() else {                if curr_column == 0 {                    break;                }                curr_column -= step_left;                curr_column += step_right;                continue;            };             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 -= step_left;            curr_column += step_right;        }        widths[col_idx] = widths[col_idx] + (diff - diff_remaining);         diff_remaining    }} pub struct TableWidths {    initial: TableRow<DefiniteLength>,    current: Option<Entity<TableColumnWidths>>,    resizable: TableRow<TableResizeBehavior>,}

Lines 1–500 of 1240.