886 lines · 32.5 KB · Rust
RawDownload
use std::{ops::Range, rc::Rc, time::Duration}; use editor::{EditorSettings, ShowScrollbar, scroll::ScrollbarAutoHide};use gpui::{    AppContext, Axis, Context, Entity, FocusHandle, Length, ListHorizontalSizingBehavior,    ListSizingBehavior, MouseButton, Point, Task, UniformListScrollHandle, WeakEntity,    transparent_black, uniform_list,};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 as _, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce,    Scrollbar, ScrollbarState, StatefulInteractiveElement as _, Styled, StyledExt as _,    StyledTypography, Window, div, example_group_with_title, h_flex, px, single_example, v_flex,}; 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,        }    }} 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_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)    }} /// 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>>,    column_widths: Option<[Length; COLS]>,    map_row: Option<Rc<dyn Fn((usize, Div), &mut Window, &mut App) -> AnyElement>>,} impl<const COLS: usize> Table<COLS> {    /// number of headers provided.    pub fn new() -> Self {        Table {            striped: false,            width: None,            headers: None,            rows: TableContents::Vec(Vec::new()),            interaction_state: None,            column_widths: None,            map_row: None,        }    }     /// Enables uniform list rendering.    /// The provided function will be passed directly to the `uniform_list` element.    /// Therefore, if this method is called, any calls to [`Table::row`] before or after    /// this method is called will be ignored.    pub fn uniform_list(        mut self,        id: impl Into<ElementId>,        row_count: usize,        render_item_fn: impl Fn(Range<usize>, &mut Window, &mut App) -> Vec<[AnyElement; COLS]>        + 'static,    ) -> Self {        self.rows = TableContents::UniformList(UniformListData {            element_id: id.into(),            row_count: row_count,            render_item_fn: Box::new(render_item_fn),        });        self    }     /// Enables row striping.    pub fn striped(mut self) -> Self {        self.striped = true;        self    }     /// Sets the width of the table.    /// Will enable horizontal scrolling if [`Self::interactable`] is also called.    pub fn width(mut self, width: impl Into<Length>) -> Self {        self.width = Some(width.into());        self    }     /// Enables interaction (primarily scrolling) with the table.    ///    /// Vertical scrolling will be enabled by default if the table is taller than its container.    ///    /// Horizontal scrolling will only be enabled if [`Self::width`] is also called, otherwise    /// the list will always shrink the table columns to fit their contents I.e. If [`Self::uniform_list`]    /// is used without a width and with [`Self::interactable`], the [`ListHorizontalSizingBehavior`] will    /// be set to [`ListHorizontalSizingBehavior::FitList`].    pub fn interactable(mut self, interaction_state: &Entity<TableInteractionState>) -> Self {        self.interaction_state = Some(interaction_state.downgrade());        self    }     pub fn header(mut self, headers: [impl IntoElement; COLS]) -> Self {        self.headers = Some(headers.map(IntoElement::into_any_element));        self    }     pub fn row(mut self, items: [impl IntoElement; COLS]) -> Self {        if let Some(rows) = self.rows.rows_mut() {            rows.push(items.map(IntoElement::into_any_element));        }        self    }     pub fn column_widths(mut self, widths: [impl Into<Length>; COLS]) -> Self {        self.column_widths = Some(widths.map(Into::into));        self    }     pub fn map_row(        mut self,        callback: impl Fn((usize, Div), &mut Window, &mut App) -> AnyElement + 'static,    ) -> Self {        self.map_row = Some(Rc::new(callback));        self    }} fn base_cell_style(width: Option<Length>, cx: &App) -> Div {    div()        .px_1p5()        .when_some(width, |this, width| this.w(width))        .when(width.is_none(), |this| this.flex_1())        .justify_start()        .text_ui(cx)        .whitespace_nowrap()        .text_ellipsis()        .overflow_hidden()} pub fn render_row<const COLS: usize>(    row_index: usize,    items: [impl IntoElement; COLS],    table_context: TableRenderContext<COLS>,    window: &mut Window,    cx: &mut App,) -> AnyElement {    let is_striped = table_context.striped;    let is_last = row_index == table_context.total_row_count - 1;    let bg = if row_index % 2 == 1 && is_striped {        Some(cx.theme().colors().text.opacity(0.05))    } else {        None    };    let column_widths = table_context        .column_widths        .map_or([None; COLS], |widths| widths.map(Some));     let row = div().w_full().child(        h_flex()            .id("table_row")            .w_full()            .justify_between()            .px_1p5()

Lines 1–500 of 886.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t