1376 lines · 49.6 KB · Rust
RawDownload
use std::{ops::Range, rc::Rc}; use gpui::{    AbsoluteLength, AppContext, Context, DefiniteLength, DragMoveEvent, Entity, EntityId,    FocusHandle, Length, ListHorizontalSizingBehavior, ListSizingBehavior, Point, Stateful,    UniformListScrollHandle, WeakEntity, 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,    ScrollableHandle, Scrollbars, SharedString, StatefulInteractiveElement, Styled, StyledExt as _,    StyledTypography, Window, WithScrollbar, div, example_group_with_title, h_flex, px,    single_example, v_flex,};use itertools::intersperse_with; const RESIZE_COLUMN_WIDTH: f32 = 8.0; #[derive(Debug)]struct DraggedColumn(usize); struct UniformListData<const COLS: usize> {    render_item_fn: Box<dyn Fn(Range<usize>, &mut Window, &mut App) -> Vec<[AnyElement; COLS]>>,    element_id: ElementId,    row_count: usize,} enum TableContents<const COLS: usize> {    Vec(Vec<[AnyElement; COLS]>),    UniformList(UniformListData<COLS>),} impl<const COLS: usize> TableContents<COLS> {    fn rows_mut(&mut self) -> Option<&mut Vec<[AnyElement; COLS]>> {        match self {            TableContents::Vec(rows) => Some(rows),            TableContents::UniformList(_) => None,        }    }     fn len(&self) -> usize {        match self {            TableContents::Vec(rows) => rows.len(),            TableContents::UniformList(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();        }    }     fn render_resize_handles<const COLS: usize>(        &self,        column_widths: &[Length; COLS],        resizable_columns: &[TableResizeBehavior; COLS],        initial_sizes: [DefiniteLength; COLS],        columns: Option<Entity<TableColumnWidths<COLS>>>,        window: &mut Window,        cx: &mut App,    ) -> AnyElement {        let spacers = column_widths            .iter()            .map(|width| base_cell_style(Some(*width)).into_any_element());         let mut column_ix = 0;        let resizable_columns_slice = *resizable_columns;        let mut resizable_columns = resizable_columns.iter();         let dividers = intersperse_with(spacers, || {            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                    .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_slice,                                            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<const COLS: usize> {    widths: [DefiniteLength; COLS],    visible_widths: [DefiniteLength; COLS],    cached_bounds_width: Pixels,    initialized: bool,} impl<const COLS: usize> TableColumnWidths<COLS> {    pub fn new(_: &mut App) -> Self {        Self {            widths: [DefiniteLength::default(); COLS],            visible_widths: [DefiniteLength::default(); COLS],            cached_bounds_width: Default::default(),            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_double_click(        &mut self,        double_click_position: usize,        initial_sizes: &[DefiniteLength; COLS],        resize_behavior: &[TableResizeBehavior; COLS],        window: &mut Window,    ) {        let bounds_width = self.cached_bounds_width;        let rem_size = window.rem_size();        let initial_sizes =            initial_sizes.map(|length| Self::get_fraction(&length, bounds_width, rem_size));        let widths = self            .widths            .map(|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;    }     fn reset_to_initial_size(        col_idx: usize,        mut widths: [f32; COLS],        initial_sizes: [f32; COLS],        resize_behavior: &[TableResizeBehavior; 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        //         // 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: &[TableResizeBehavior; COLS],        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(|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;        }        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 [f32; COLS],        resize_behavior: &[TableResizeBehavior; COLS],    ) {        // 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 [f32; COLS],        resize_behavior: &[TableResizeBehavior; COLS],        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 < 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<const COLS: usize> {    initial: [DefiniteLength; COLS],    current: Option<Entity<TableColumnWidths<COLS>>>,    resizable: [TableResizeBehavior; COLS],} impl<const COLS: usize> TableWidths<COLS> {    pub fn new(widths: [impl Into<DefiniteLength>; COLS]) -> Self {        let widths = widths.map(Into::into);         TableWidths {            initial: widths,            current: None,            resizable: [TableResizeBehavior::None; COLS],        }    }     fn lengths(&self, cx: &App) -> [Length; COLS] {        self.current            .as_ref()            .map(|entity| entity.read(cx).visible_widths.map(Length::Definite))            .unwrap_or(self.initial.map(Length::Definite))    }} /// A table component#[derive(RegisterComponent, IntoElement)]pub struct Table<const COLS: usize = 3> {    striped: bool,    width: Option<Length>,    headers: Option<[AnyElement; COLS]>,    rows: TableContents<COLS>,    interaction_state: Option<WeakEntity<TableInteractionState>>,    col_widths: Option<TableWidths<COLS>>,    map_row: Option<Rc<dyn Fn((usize, Stateful<Div>), &mut Window, &mut App) -> AnyElement>>,    empty_table_callback: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>>,} impl<const COLS: usize> Table<COLS> {    /// number of headers provided.    pub fn new() -> Self {        Self {            striped: false,            width: None,            headers: None,            rows: TableContents::Vec(Vec::new()),            interaction_state: None,            map_row: None,

Lines 1–500 of 1376.