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. Load more
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()