1732 lines · 63.8 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 {    //! A newtype for a table row that enforces a fixed column count at runtime.    //!    //! This type ensures that all rows in a table have the same width, preventing accidental creation or mutation of rows with inconsistent lengths.    //! It is especially useful for CSV or tabular data where rectangular invariants must be maintained, but the number of columns is only known at runtime.    //! By using `TableRow`, we gain stronger guarantees and safer APIs compared to a bare `Vec<T>`, without requiring const generics.     use std::{        any::type_name,        ops::{            Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,        },    };     #[derive(Clone, Debug, PartialEq, Eq)]    pub struct TableRow<T>(Vec<T>);     impl<T> TableRow<T> {        /// Constructs a `TableRow` from a `Vec<T>`, panicking if the length does not match `expected_length`.        ///        /// Use this when you want to ensure at construction time that the row has the correct number of columns.        /// This enforces the rectangular invariant for table data, preventing accidental creation of malformed rows.        ///        /// # Panics        /// Panics if `data.len() != expected_length`.        pub fn from_vec(data: Vec<T>, expected_length: usize) -> Self {            Self::try_from_vec(data, expected_length).unwrap_or_else(|e| {                let name = type_name::<Vec<T>>();                panic!("Expected {name} to be created successfully: {e}");            })        }         /// Attempts to construct a `TableRow` from a `Vec<T>`, returning an error if the length does not match `expected_len`.        ///        /// This is a fallible alternative to `from_vec`, allowing you to handle inconsistent row lengths gracefully.        /// Returns `Ok(TableRow)` if the length matches, or an `Err` with a descriptive message otherwise.        pub fn try_from_vec(data: Vec<T>, expected_len: usize) -> Result<Self, String> {            if data.len() != expected_len {                Err(format!(                    "Row length {} does not match expected {}",                    data.len(),                    expected_len                ))            } else {                Ok(Self(data))            }        }         /// Returns reference to element by column index.        ///        /// # Panics        /// Panics if `col` is out of bounds (i.e., `col >= self.cols()`).        pub fn expect_get(&self, col: usize) -> &T {            self.0.get(col).unwrap_or_else(|| {                panic!(                    "Expected table row of `{}` to have {col:?}",                    type_name::<T>()                )            })        }         pub fn get(&self, col: usize) -> Option<&T> {            self.0.get(col)        }         pub fn as_slice(&self) -> &[T] {            &self.0        }         pub fn into_vec(self) -> Vec<T> {            self.0        }         /// Like [`map`], but borrows the row and clones each element before mapping.        ///        /// This is useful when you want to map over a borrowed row without consuming it,        /// but your mapping function requires ownership of each element.        ///        /// # Difference        /// - `map_cloned` takes `&self`, clones each element, and applies `f(T) -> U`.        /// - [`map`] takes `self` by value and applies `f(T) -> U` directly, consuming the row.        /// - [`map_ref`] takes `&self` and applies `f(&T) -> U` to references of each element.        pub fn map_cloned<F, U>(&self, f: F) -> TableRow<U>        where            F: FnMut(T) -> U,            T: Clone,        {            self.clone().map(f)        }         /// Consumes the row and transforms all elements within it in a length-safe way.        ///        /// # Difference        /// - `map` takes ownership of the row (`self`) and applies `f(T) -> U` to each element.        /// - Use this when you want to transform and consume the row in one step.        /// - See also [`map_cloned`] (for mapping over a borrowed row with cloning) and [`map_ref`] (for mapping over references).        pub fn map<F, U>(self, f: F) -> TableRow<U>        where            F: FnMut(T) -> U,        {            TableRow(self.0.into_iter().map(f).collect())        }         /// Borrows the row and transforms all elements by reference in a length-safe way.        ///        /// # Difference        /// - `map_ref` takes `&self` and applies `f(&T) -> U` to each element by reference.        /// - Use this when you want to map over a borrowed row without cloning or consuming it.        /// - See also [`map`] (for consuming the row) and [`map_cloned`] (for mapping with cloning).        pub fn map_ref<F, U>(&self, f: F) -> TableRow<U>        where            F: FnMut(&T) -> U,        {            TableRow(self.0.iter().map(f).collect())        }         /// Number of columns (alias to `len()` with more semantic meaning)        pub fn cols(&self) -> usize {            self.0.len()        }    }     ///// Convenience traits /////    pub trait IntoTableRow<T> {        fn into_table_row(self, expected_length: usize) -> TableRow<T>;    }    impl<T> IntoTableRow<T> for Vec<T> {        fn into_table_row(self, expected_length: usize) -> TableRow<T> {            TableRow::from_vec(self, expected_length)        }    }     // Index implementations for convenient access    impl<T> Index<usize> for TableRow<T> {        type Output = T;         fn index(&self, index: usize) -> &Self::Output {            &self.0[index]        }    }     impl<T> IndexMut<usize> for TableRow<T> {        fn index_mut(&mut self, index: usize) -> &mut Self::Output {            &mut self.0[index]        }    }     // Range indexing implementations for slice operations    impl<T> Index<Range<usize>> for TableRow<T> {        type Output = [T];         fn index(&self, index: Range<usize>) -> &Self::Output {            <Vec<T> as Index<Range<usize>>>::index(&self.0, index)        }    }     impl<T> Index<RangeFrom<usize>> for TableRow<T> {        type Output = [T];         fn index(&self, index: RangeFrom<usize>) -> &Self::Output {            <Vec<T> as Index<RangeFrom<usize>>>::index(&self.0, index)        }    }     impl<T> Index<RangeTo<usize>> for TableRow<T> {        type Output = [T];         fn index(&self, index: RangeTo<usize>) -> &Self::Output {            <Vec<T> as Index<RangeTo<usize>>>::index(&self.0, index)        }    }     impl<T> Index<RangeToInclusive<usize>> for TableRow<T> {        type Output = [T];         fn index(&self, index: RangeToInclusive<usize>) -> &Self::Output {            <Vec<T> as Index<RangeToInclusive<usize>>>::index(&self.0, index)        }    }     impl<T> Index<RangeFull> for TableRow<T> {        type Output = [T];         fn index(&self, index: RangeFull) -> &Self::Output {            <Vec<T> as Index<RangeFull>>::index(&self.0, index)        }    }     impl<T> Index<RangeInclusive<usize>> for TableRow<T> {        type Output = [T];         fn index(&self, index: RangeInclusive<usize>) -> &Self::Output {            <Vec<T> as Index<RangeInclusive<usize>>>::index(&self.0, index)        }    }     impl<T> IndexMut<RangeInclusive<usize>> for TableRow<T> {        fn index_mut(&mut self, index: RangeInclusive<usize>) -> &mut Self::Output {            <Vec<T> as IndexMut<RangeInclusive<usize>>>::index_mut(&mut self.0, index)        }    }} 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

Lines 1–500 of 1732.