1765 lines · 63.0 KB · Rust
RawDownload
use std::{ops::Range, rc::Rc, time::Duration}; use editor::{EditorSettings, ShowScrollbar, scroll::ScrollbarAutoHide};use gpui::{    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;use settings::Settings as _;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, SharedString, StatefulInteractiveElement, Styled, StyledExt as _,    StyledTypography, Window, div, example_group_with_title, h_flex, px, single_example, v_flex,}; 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 horizontal_scrollbar: ScrollbarProperties,    pub vertical_scrollbar: ScrollbarProperties,} impl TableInteractionState {    pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {        cx.new(|cx| {            let focus_handle = cx.focus_handle();             cx.on_focus_out(&focus_handle, window, |this: &mut Self, _, window, cx| {                this.hide_scrollbars(window, cx);            })            .detach();             let scroll_handle = UniformListScrollHandle::new();            let vertical_scrollbar = ScrollbarProperties {                axis: Axis::Vertical,                state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),                show_scrollbar: false,                show_track: false,                auto_hide: false,                hide_task: None,            };             let horizontal_scrollbar = ScrollbarProperties {                axis: Axis::Horizontal,                state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),                show_scrollbar: false,                show_track: false,                auto_hide: false,                hide_task: None,            };             let mut this = Self {                focus_handle,                scroll_handle,                horizontal_scrollbar,                vertical_scrollbar,            };             this.update_scrollbar_visibility(cx);            this        })    }     pub fn get_scrollbar_offset(&self, axis: Axis) -> Point<Pixels> {        match axis {            Axis::Vertical => self.vertical_scrollbar.state.scroll_handle().offset(),            Axis::Horizontal => self.horizontal_scrollbar.state.scroll_handle().offset(),        }    }     pub fn set_scrollbar_offset(&self, axis: Axis, offset: Point<Pixels>) {        match axis {            Axis::Vertical => self                .vertical_scrollbar                .state                .scroll_handle()                .set_offset(offset),            Axis::Horizontal => self                .horizontal_scrollbar                .state                .scroll_handle()                .set_offset(offset),        }    }     fn update_scrollbar_visibility(&mut self, cx: &mut Context<Self>) {        let show_setting = EditorSettings::get_global(cx).scrollbar.show;         let scroll_handle = self.scroll_handle.0.borrow();         let autohide = |show: ShowScrollbar, cx: &mut Context<Self>| match show {            ShowScrollbar::Auto => true,            ShowScrollbar::System => cx                .try_global::<ScrollbarAutoHide>()                .map_or_else(|| cx.should_auto_hide_scrollbars(), |autohide| autohide.0),            ShowScrollbar::Always => false,            ShowScrollbar::Never => false,        };         let longest_item_width = scroll_handle.last_item_size.and_then(|size| {            (size.contents.width > size.item.width).then_some(size.contents.width)        });         // is there an item long enough that we should show a horizontal scrollbar?        let item_wider_than_container = if let Some(longest_item_width) = longest_item_width {            longest_item_width > px(scroll_handle.base_handle.bounds().size.width.0)        } else {            true        };         let show_scrollbar = match show_setting {            ShowScrollbar::Auto | ShowScrollbar::System | ShowScrollbar::Always => true,            ShowScrollbar::Never => false,        };        let show_vertical = show_scrollbar;         let show_horizontal = item_wider_than_container && show_scrollbar;         let show_horizontal_track =            show_horizontal && matches!(show_setting, ShowScrollbar::Always);         // TODO: we probably should hide the scroll track when the list doesn't need to scroll        let show_vertical_track = show_vertical && matches!(show_setting, ShowScrollbar::Always);         self.vertical_scrollbar = ScrollbarProperties {            axis: self.vertical_scrollbar.axis,            state: self.vertical_scrollbar.state.clone(),            show_scrollbar: show_vertical,            show_track: show_vertical_track,            auto_hide: autohide(show_setting, cx),            hide_task: None,        };         self.horizontal_scrollbar = ScrollbarProperties {            axis: self.horizontal_scrollbar.axis,            state: self.horizontal_scrollbar.state.clone(),            show_scrollbar: show_horizontal,            show_track: show_horizontal_track,            auto_hide: autohide(show_setting, cx),            hide_task: None,        };         cx.notify();    }     fn hide_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Self>) {        self.horizontal_scrollbar.hide(window, cx);        self.vertical_scrollbar.hide(window, cx);    }     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: &[ResizeBehavior; COLS],        initial_sizes: [DefiniteLength; COLS],        columns: Option<Entity<ColumnWidths<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.into_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(ResizeBehavior::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.down.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()    }     fn render_vertical_scrollbar_track(        this: &Entity<Self>,        parent: Div,        scroll_track_size: Pixels,        cx: &mut App,    ) -> Div {        if !this.read(cx).vertical_scrollbar.show_track {            return parent;        }        let child = v_flex()            .h_full()            .flex_none()            .w(scroll_track_size)            .bg(cx.theme().colors().background)            .child(                div()                    .size_full()                    .flex_1()                    .border_l_1()                    .border_color(cx.theme().colors().border),            );        parent.child(child)    }     fn render_vertical_scrollbar(this: &Entity<Self>, parent: Div, cx: &mut App) -> Div {        if !this.read(cx).vertical_scrollbar.show_scrollbar {            return parent;        }        let child = div()            .id(("table-vertical-scrollbar", this.entity_id()))            .occlude()            .flex_none()            .h_full()            .cursor_default()            .absolute()            .right_0()            .top_0()            .bottom_0()            .w(px(12.))            .on_mouse_move(Self::listener(this, |_, _, _, cx| {                cx.notify();                cx.stop_propagation()            }))            .on_hover(|_, _, cx| {                cx.stop_propagation();            })            .on_mouse_up(                MouseButton::Left,                Self::listener(this, |this, _, window, cx| {                    if !this.vertical_scrollbar.state.is_dragging()                        && !this.focus_handle.contains_focused(window, cx)                    {                        this.vertical_scrollbar.hide(window, cx);                        cx.notify();                    }                     cx.stop_propagation();                }),            )            .on_any_mouse_down(|_, _, cx| {                cx.stop_propagation();            })            .on_scroll_wheel(Self::listener(&this, |_, _, _, cx| {                cx.notify();            }))            .children(Scrollbar::vertical(                this.read(cx).vertical_scrollbar.state.clone(),            ));        parent.child(child)    }     /// Renders the horizontal scrollbar.    ///    /// The right offset is used to determine how far to the right the    /// scrollbar should extend to, useful for ensuring it doesn't collide    /// with the vertical scrollbar when visible.    fn render_horizontal_scrollbar(        this: &Entity<Self>,        parent: Div,        right_offset: Pixels,        cx: &mut App,    ) -> Div {        if !this.read(cx).horizontal_scrollbar.show_scrollbar {            return parent;        }        let child = div()            .id(("table-horizontal-scrollbar", this.entity_id()))            .occlude()            .flex_none()            .w_full()            .cursor_default()            .absolute()            .bottom_neg_px()            .left_0()            .right_0()            .pr(right_offset)            .on_mouse_move(Self::listener(this, |_, _, _, cx| {                cx.notify();                cx.stop_propagation()            }))            .on_hover(|_, _, cx| {                cx.stop_propagation();            })            .on_any_mouse_down(|_, _, cx| {                cx.stop_propagation();            })            .on_mouse_up(                MouseButton::Left,                Self::listener(this, |this, _, window, cx| {                    if !this.horizontal_scrollbar.state.is_dragging()                        && !this.focus_handle.contains_focused(window, cx)                    {                        this.horizontal_scrollbar.hide(window, cx);                        cx.notify();                    }                     cx.stop_propagation();                }),            )            .on_scroll_wheel(Self::listener(this, |_, _, _, cx| {                cx.notify();            }))            .children(Scrollbar::horizontal(                // percentage as f32..end_offset as f32,                this.read(cx).horizontal_scrollbar.state.clone(),            ));        parent.child(child)    }     fn render_horizontal_scrollbar_track(        this: &Entity<Self>,        parent: Div,        scroll_track_size: Pixels,        cx: &mut App,    ) -> Div {        if !this.read(cx).horizontal_scrollbar.show_track {            return parent;        }        let child = h_flex()            .w_full()            .h(scroll_track_size)            .flex_none()            .relative()            .child(                div()                    .w_full()                    .flex_1()                    // for some reason the horizontal scrollbar is 1px                    // taller than the vertical scrollbar??                    .h(scroll_track_size - px(1.))                    .bg(cx.theme().colors().background)                    .border_t_1()                    .border_color(cx.theme().colors().border),            )            .when(this.read(cx).vertical_scrollbar.show_track, |parent| {                parent                    .child(                        div()                            .flex_none()                            // -1px prevents a missing pixel between the two container borders                            .w(scroll_track_size - px(1.))                            .h_full(),                    )                    .child(                        // HACK: Fill the missing 1px 🥲                        div()                            .absolute()                            .right(scroll_track_size - px(1.))                            .bottom(scroll_track_size - px(1.))                            .size_px()                            .bg(cx.theme().colors().border),                    )            });         parent.child(child)    }} #[derive(Debug, Copy, Clone, PartialEq)]pub enum ResizeBehavior {    None,    Resizable,    MinSize(f32),} impl ResizeBehavior {    pub fn is_resizable(&self) -> bool {        *self != ResizeBehavior::None    }     pub fn min_size(&self) -> Option<f32> {        match self {            ResizeBehavior::None => None,            ResizeBehavior::Resizable => Some(0.05),            ResizeBehavior::MinSize(min_size) => Some(*min_size),        }    }} pub struct ColumnWidths<const COLS: usize> {    widths: [DefiniteLength; COLS],    visible_widths: [DefiniteLength; COLS],    cached_bounds_width: Pixels,    initialized: bool,} 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,        }    }     fn get_fraction(length: &DefiniteLength, bounds_width: Pixels, rem_size: Pixels) -> f32 {

Lines 1–500 of 1765.