table.rs

  1use std::{ops::Range, rc::Rc, time::Duration};
  2
  3use editor::{EditorSettings, ShowScrollbar, scroll::ScrollbarAutoHide};
  4use gpui::{
  5    AppContext, Axis, Context, Entity, FocusHandle, Length, ListHorizontalSizingBehavior,
  6    ListSizingBehavior, MouseButton, Point, Task, UniformListScrollHandle, WeakEntity,
  7    transparent_black, uniform_list,
  8};
  9use settings::Settings as _;
 10use ui::{
 11    ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component,
 12    ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator,
 13    InteractiveElement as _, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce,
 14    Scrollbar, ScrollbarState, StatefulInteractiveElement as _, Styled, StyledExt as _,
 15    StyledTypography, Window, div, example_group_with_title, h_flex, px, single_example, v_flex,
 16};
 17
 18struct UniformListData<const COLS: usize> {
 19    render_item_fn: Box<dyn Fn(Range<usize>, &mut Window, &mut App) -> Vec<[AnyElement; COLS]>>,
 20    element_id: ElementId,
 21    row_count: usize,
 22}
 23
 24enum TableContents<const COLS: usize> {
 25    Vec(Vec<[AnyElement; COLS]>),
 26    UniformList(UniformListData<COLS>),
 27}
 28
 29impl<const COLS: usize> TableContents<COLS> {
 30    fn rows_mut(&mut self) -> Option<&mut Vec<[AnyElement; COLS]>> {
 31        match self {
 32            TableContents::Vec(rows) => Some(rows),
 33            TableContents::UniformList(_) => None,
 34        }
 35    }
 36
 37    fn len(&self) -> usize {
 38        match self {
 39            TableContents::Vec(rows) => rows.len(),
 40            TableContents::UniformList(data) => data.row_count,
 41        }
 42    }
 43}
 44
 45pub struct TableInteractionState {
 46    pub focus_handle: FocusHandle,
 47    pub scroll_handle: UniformListScrollHandle,
 48    pub horizontal_scrollbar: ScrollbarProperties,
 49    pub vertical_scrollbar: ScrollbarProperties,
 50}
 51
 52impl TableInteractionState {
 53    pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
 54        cx.new(|cx| {
 55            let focus_handle = cx.focus_handle();
 56
 57            cx.on_focus_out(&focus_handle, window, |this: &mut Self, _, window, cx| {
 58                this.hide_scrollbars(window, cx);
 59            })
 60            .detach();
 61
 62            let scroll_handle = UniformListScrollHandle::new();
 63            let vertical_scrollbar = ScrollbarProperties {
 64                axis: Axis::Vertical,
 65                state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),
 66                show_scrollbar: false,
 67                show_track: false,
 68                auto_hide: false,
 69                hide_task: None,
 70            };
 71
 72            let horizontal_scrollbar = ScrollbarProperties {
 73                axis: Axis::Horizontal,
 74                state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),
 75                show_scrollbar: false,
 76                show_track: false,
 77                auto_hide: false,
 78                hide_task: None,
 79            };
 80
 81            let mut this = Self {
 82                focus_handle,
 83                scroll_handle,
 84                horizontal_scrollbar,
 85                vertical_scrollbar,
 86            };
 87
 88            this.update_scrollbar_visibility(cx);
 89            this
 90        })
 91    }
 92
 93    pub fn get_scrollbar_offset(&self, axis: Axis) -> Point<Pixels> {
 94        match axis {
 95            Axis::Vertical => self.vertical_scrollbar.state.scroll_handle().offset(),
 96            Axis::Horizontal => self.horizontal_scrollbar.state.scroll_handle().offset(),
 97        }
 98    }
 99
100    pub fn set_scrollbar_offset(&self, axis: Axis, offset: Point<Pixels>) {
101        match axis {
102            Axis::Vertical => self
103                .vertical_scrollbar
104                .state
105                .scroll_handle()
106                .set_offset(offset),
107            Axis::Horizontal => self
108                .horizontal_scrollbar
109                .state
110                .scroll_handle()
111                .set_offset(offset),
112        }
113    }
114
115    fn update_scrollbar_visibility(&mut self, cx: &mut Context<Self>) {
116        let show_setting = EditorSettings::get_global(cx).scrollbar.show;
117
118        let scroll_handle = self.scroll_handle.0.borrow();
119
120        let autohide = |show: ShowScrollbar, cx: &mut Context<Self>| match show {
121            ShowScrollbar::Auto => true,
122            ShowScrollbar::System => cx
123                .try_global::<ScrollbarAutoHide>()
124                .map_or_else(|| cx.should_auto_hide_scrollbars(), |autohide| autohide.0),
125            ShowScrollbar::Always => false,
126            ShowScrollbar::Never => false,
127        };
128
129        let longest_item_width = scroll_handle.last_item_size.and_then(|size| {
130            (size.contents.width > size.item.width).then_some(size.contents.width)
131        });
132
133        // is there an item long enough that we should show a horizontal scrollbar?
134        let item_wider_than_container = if let Some(longest_item_width) = longest_item_width {
135            longest_item_width > px(scroll_handle.base_handle.bounds().size.width.0)
136        } else {
137            true
138        };
139
140        let show_scrollbar = match show_setting {
141            ShowScrollbar::Auto | ShowScrollbar::System | ShowScrollbar::Always => true,
142            ShowScrollbar::Never => false,
143        };
144        let show_vertical = show_scrollbar;
145
146        let show_horizontal = item_wider_than_container && show_scrollbar;
147
148        let show_horizontal_track =
149            show_horizontal && matches!(show_setting, ShowScrollbar::Always);
150
151        // TODO: we probably should hide the scroll track when the list doesn't need to scroll
152        let show_vertical_track = show_vertical && matches!(show_setting, ShowScrollbar::Always);
153
154        self.vertical_scrollbar = ScrollbarProperties {
155            axis: self.vertical_scrollbar.axis,
156            state: self.vertical_scrollbar.state.clone(),
157            show_scrollbar: show_vertical,
158            show_track: show_vertical_track,
159            auto_hide: autohide(show_setting, cx),
160            hide_task: None,
161        };
162
163        self.horizontal_scrollbar = ScrollbarProperties {
164            axis: self.horizontal_scrollbar.axis,
165            state: self.horizontal_scrollbar.state.clone(),
166            show_scrollbar: show_horizontal,
167            show_track: show_horizontal_track,
168            auto_hide: autohide(show_setting, cx),
169            hide_task: None,
170        };
171
172        cx.notify();
173    }
174
175    fn hide_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Self>) {
176        self.horizontal_scrollbar.hide(window, cx);
177        self.vertical_scrollbar.hide(window, cx);
178    }
179
180    pub fn listener<E: ?Sized>(
181        this: &Entity<Self>,
182        f: impl Fn(&mut Self, &E, &mut Window, &mut Context<Self>) + 'static,
183    ) -> impl Fn(&E, &mut Window, &mut App) + 'static {
184        let view = this.downgrade();
185        move |e: &E, window: &mut Window, cx: &mut App| {
186            view.update(cx, |view, cx| f(view, e, window, cx)).ok();
187        }
188    }
189
190    fn render_vertical_scrollbar_track(
191        this: &Entity<Self>,
192        parent: Div,
193        scroll_track_size: Pixels,
194        cx: &mut App,
195    ) -> Div {
196        if !this.read(cx).vertical_scrollbar.show_track {
197            return parent;
198        }
199        let child = v_flex()
200            .h_full()
201            .flex_none()
202            .w(scroll_track_size)
203            .bg(cx.theme().colors().background)
204            .child(
205                div()
206                    .size_full()
207                    .flex_1()
208                    .border_l_1()
209                    .border_color(cx.theme().colors().border),
210            );
211        parent.child(child)
212    }
213
214    fn render_vertical_scrollbar(this: &Entity<Self>, parent: Div, cx: &mut App) -> Div {
215        if !this.read(cx).vertical_scrollbar.show_scrollbar {
216            return parent;
217        }
218        let child = div()
219            .id(("table-vertical-scrollbar", this.entity_id()))
220            .occlude()
221            .flex_none()
222            .h_full()
223            .cursor_default()
224            .absolute()
225            .right_0()
226            .top_0()
227            .bottom_0()
228            .w(px(12.))
229            .on_mouse_move(Self::listener(this, |_, _, _, cx| {
230                cx.notify();
231                cx.stop_propagation()
232            }))
233            .on_hover(|_, _, cx| {
234                cx.stop_propagation();
235            })
236            .on_mouse_up(
237                MouseButton::Left,
238                Self::listener(this, |this, _, window, cx| {
239                    if !this.vertical_scrollbar.state.is_dragging()
240                        && !this.focus_handle.contains_focused(window, cx)
241                    {
242                        this.vertical_scrollbar.hide(window, cx);
243                        cx.notify();
244                    }
245
246                    cx.stop_propagation();
247                }),
248            )
249            .on_any_mouse_down(|_, _, cx| {
250                cx.stop_propagation();
251            })
252            .on_scroll_wheel(Self::listener(&this, |_, _, _, cx| {
253                cx.notify();
254            }))
255            .children(Scrollbar::vertical(
256                this.read(cx).vertical_scrollbar.state.clone(),
257            ));
258        parent.child(child)
259    }
260
261    /// Renders the horizontal scrollbar.
262    ///
263    /// The right offset is used to determine how far to the right the
264    /// scrollbar should extend to, useful for ensuring it doesn't collide
265    /// with the vertical scrollbar when visible.
266    fn render_horizontal_scrollbar(
267        this: &Entity<Self>,
268        parent: Div,
269        right_offset: Pixels,
270        cx: &mut App,
271    ) -> Div {
272        if !this.read(cx).horizontal_scrollbar.show_scrollbar {
273            return parent;
274        }
275        let child = div()
276            .id(("table-horizontal-scrollbar", this.entity_id()))
277            .occlude()
278            .flex_none()
279            .w_full()
280            .cursor_default()
281            .absolute()
282            .bottom_neg_px()
283            .left_0()
284            .right_0()
285            .pr(right_offset)
286            .on_mouse_move(Self::listener(this, |_, _, _, cx| {
287                cx.notify();
288                cx.stop_propagation()
289            }))
290            .on_hover(|_, _, cx| {
291                cx.stop_propagation();
292            })
293            .on_any_mouse_down(|_, _, cx| {
294                cx.stop_propagation();
295            })
296            .on_mouse_up(
297                MouseButton::Left,
298                Self::listener(this, |this, _, window, cx| {
299                    if !this.horizontal_scrollbar.state.is_dragging()
300                        && !this.focus_handle.contains_focused(window, cx)
301                    {
302                        this.horizontal_scrollbar.hide(window, cx);
303                        cx.notify();
304                    }
305
306                    cx.stop_propagation();
307                }),
308            )
309            .on_scroll_wheel(Self::listener(this, |_, _, _, cx| {
310                cx.notify();
311            }))
312            .children(Scrollbar::horizontal(
313                // percentage as f32..end_offset as f32,
314                this.read(cx).horizontal_scrollbar.state.clone(),
315            ));
316        parent.child(child)
317    }
318
319    fn render_horizontal_scrollbar_track(
320        this: &Entity<Self>,
321        parent: Div,
322        scroll_track_size: Pixels,
323        cx: &mut App,
324    ) -> Div {
325        if !this.read(cx).horizontal_scrollbar.show_track {
326            return parent;
327        }
328        let child = h_flex()
329            .w_full()
330            .h(scroll_track_size)
331            .flex_none()
332            .relative()
333            .child(
334                div()
335                    .w_full()
336                    .flex_1()
337                    // for some reason the horizontal scrollbar is 1px
338                    // taller than the vertical scrollbar??
339                    .h(scroll_track_size - px(1.))
340                    .bg(cx.theme().colors().background)
341                    .border_t_1()
342                    .border_color(cx.theme().colors().border),
343            )
344            .when(this.read(cx).vertical_scrollbar.show_track, |parent| {
345                parent
346                    .child(
347                        div()
348                            .flex_none()
349                            // -1px prevents a missing pixel between the two container borders
350                            .w(scroll_track_size - px(1.))
351                            .h_full(),
352                    )
353                    .child(
354                        // HACK: Fill the missing 1px 🥲
355                        div()
356                            .absolute()
357                            .right(scroll_track_size - px(1.))
358                            .bottom(scroll_track_size - px(1.))
359                            .size_px()
360                            .bg(cx.theme().colors().border),
361                    )
362            });
363
364        parent.child(child)
365    }
366}
367
368/// A table component
369#[derive(RegisterComponent, IntoElement)]
370pub struct Table<const COLS: usize = 3> {
371    striped: bool,
372    width: Option<Length>,
373    headers: Option<[AnyElement; COLS]>,
374    rows: TableContents<COLS>,
375    interaction_state: Option<WeakEntity<TableInteractionState>>,
376    column_widths: Option<[Length; COLS]>,
377    map_row: Option<Rc<dyn Fn((usize, Div), &mut Window, &mut App) -> AnyElement>>,
378}
379
380impl<const COLS: usize> Table<COLS> {
381    /// number of headers provided.
382    pub fn new() -> Self {
383        Table {
384            striped: false,
385            width: None,
386            headers: None,
387            rows: TableContents::Vec(Vec::new()),
388            interaction_state: None,
389            column_widths: None,
390            map_row: None,
391        }
392    }
393
394    /// Enables uniform list rendering.
395    /// The provided function will be passed directly to the `uniform_list` element.
396    /// Therefore, if this method is called, any calls to [`Table::row`] before or after
397    /// this method is called will be ignored.
398    pub fn uniform_list(
399        mut self,
400        id: impl Into<ElementId>,
401        row_count: usize,
402        render_item_fn: impl Fn(Range<usize>, &mut Window, &mut App) -> Vec<[AnyElement; COLS]>
403        + 'static,
404    ) -> Self {
405        self.rows = TableContents::UniformList(UniformListData {
406            element_id: id.into(),
407            row_count: row_count,
408            render_item_fn: Box::new(render_item_fn),
409        });
410        self
411    }
412
413    /// Enables row striping.
414    pub fn striped(mut self) -> Self {
415        self.striped = true;
416        self
417    }
418
419    /// Sets the width of the table.
420    /// Will enable horizontal scrolling if [`Self::interactable`] is also called.
421    pub fn width(mut self, width: impl Into<Length>) -> Self {
422        self.width = Some(width.into());
423        self
424    }
425
426    /// Enables interaction (primarily scrolling) with the table.
427    ///
428    /// Vertical scrolling will be enabled by default if the table is taller than its container.
429    ///
430    /// Horizontal scrolling will only be enabled if [`Self::width`] is also called, otherwise
431    /// the list will always shrink the table columns to fit their contents I.e. If [`Self::uniform_list`]
432    /// is used without a width and with [`Self::interactable`], the [`ListHorizontalSizingBehavior`] will
433    /// be set to [`ListHorizontalSizingBehavior::FitList`].
434    pub fn interactable(mut self, interaction_state: &Entity<TableInteractionState>) -> Self {
435        self.interaction_state = Some(interaction_state.downgrade());
436        self
437    }
438
439    pub fn header(mut self, headers: [impl IntoElement; COLS]) -> Self {
440        self.headers = Some(headers.map(IntoElement::into_any_element));
441        self
442    }
443
444    pub fn row(mut self, items: [impl IntoElement; COLS]) -> Self {
445        if let Some(rows) = self.rows.rows_mut() {
446            rows.push(items.map(IntoElement::into_any_element));
447        }
448        self
449    }
450
451    pub fn column_widths(mut self, widths: [impl Into<Length>; COLS]) -> Self {
452        self.column_widths = Some(widths.map(Into::into));
453        self
454    }
455
456    pub fn map_row(
457        mut self,
458        callback: impl Fn((usize, Div), &mut Window, &mut App) -> AnyElement + 'static,
459    ) -> Self {
460        self.map_row = Some(Rc::new(callback));
461        self
462    }
463}
464
465fn base_cell_style(width: Option<Length>, cx: &App) -> Div {
466    div()
467        .px_1p5()
468        .when_some(width, |this, width| this.w(width))
469        .when(width.is_none(), |this| this.flex_1())
470        .justify_start()
471        .text_ui(cx)
472        .whitespace_nowrap()
473        .text_ellipsis()
474        .overflow_hidden()
475}
476
477pub fn render_row<const COLS: usize>(
478    row_index: usize,
479    items: [impl IntoElement; COLS],
480    table_context: TableRenderContext<COLS>,
481    window: &mut Window,
482    cx: &mut App,
483) -> AnyElement {
484    let is_striped = table_context.striped;
485    let is_last = row_index == table_context.total_row_count - 1;
486    let bg = if row_index % 2 == 1 && is_striped {
487        Some(cx.theme().colors().text.opacity(0.05))
488    } else {
489        None
490    };
491    let column_widths = table_context
492        .column_widths
493        .map_or([None; COLS], |widths| widths.map(Some));
494
495    let row = div().w_full().child(
496        h_flex()
497            .id("table_row")
498            .w_full()
499            .justify_between()
500            .px_1p5()
501            .py_1()
502            .when_some(bg, |row, bg| row.bg(bg))
503            .when(!is_striped, |row| {
504                row.border_b_1()
505                    .border_color(transparent_black())
506                    .when(!is_last, |row| row.border_color(cx.theme().colors().border))
507            })
508            .children(
509                items
510                    .map(IntoElement::into_any_element)
511                    .into_iter()
512                    .zip(column_widths)
513                    .map(|(cell, width)| base_cell_style(width, cx).child(cell)),
514            ),
515    );
516
517    if let Some(map_row) = table_context.map_row {
518        map_row((row_index, row), window, cx)
519    } else {
520        row.into_any_element()
521    }
522}
523
524pub fn render_header<const COLS: usize>(
525    headers: [impl IntoElement; COLS],
526    table_context: TableRenderContext<COLS>,
527    cx: &mut App,
528) -> impl IntoElement {
529    let column_widths = table_context
530        .column_widths
531        .map_or([None; COLS], |widths| widths.map(Some));
532    div()
533        .flex()
534        .flex_row()
535        .items_center()
536        .justify_between()
537        .w_full()
538        .p_2()
539        .border_b_1()
540        .border_color(cx.theme().colors().border)
541        .children(
542            headers
543                .into_iter()
544                .zip(column_widths)
545                .map(|(h, width)| base_cell_style(width, cx).child(h)),
546        )
547}
548
549#[derive(Clone)]
550pub struct TableRenderContext<const COLS: usize> {
551    pub striped: bool,
552    pub total_row_count: usize,
553    pub column_widths: Option<[Length; COLS]>,
554    pub map_row: Option<Rc<dyn Fn((usize, Div), &mut Window, &mut App) -> AnyElement>>,
555}
556
557impl<const COLS: usize> TableRenderContext<COLS> {
558    fn new(table: &Table<COLS>) -> Self {
559        Self {
560            striped: table.striped,
561            total_row_count: table.rows.len(),
562            column_widths: table.column_widths,
563            map_row: table.map_row.clone(),
564        }
565    }
566}
567
568impl<const COLS: usize> RenderOnce for Table<COLS> {
569    fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {
570        let table_context = TableRenderContext::new(&self);
571        let interaction_state = self.interaction_state.and_then(|state| state.upgrade());
572
573        let scroll_track_size = px(16.);
574        let h_scroll_offset = if interaction_state
575            .as_ref()
576            .is_some_and(|state| state.read(cx).vertical_scrollbar.show_scrollbar)
577        {
578            // magic number
579            px(3.)
580        } else {
581            px(0.)
582        };
583
584        let width = self.width;
585
586        let table = div()
587            .when_some(width, |this, width| this.w(width))
588            .h_full()
589            .v_flex()
590            .when_some(self.headers.take(), |this, headers| {
591                this.child(render_header(headers, table_context.clone(), cx))
592            })
593            .child(
594                div()
595                    .flex_grow()
596                    .w_full()
597                    .relative()
598                    .overflow_hidden()
599                    .map(|parent| match self.rows {
600                        TableContents::Vec(items) => {
601                            parent.children(items.into_iter().enumerate().map(|(index, row)| {
602                                render_row(index, row, table_context.clone(), window, cx)
603                            }))
604                        }
605                        TableContents::UniformList(uniform_list_data) => parent.child(
606                            uniform_list(
607                                uniform_list_data.element_id,
608                                uniform_list_data.row_count,
609                                {
610                                    let render_item_fn = uniform_list_data.render_item_fn;
611                                    move |range: Range<usize>, window, cx| {
612                                        let elements = render_item_fn(range.clone(), window, cx);
613                                        elements
614                                            .into_iter()
615                                            .zip(range)
616                                            .map(|(row, row_index)| {
617                                                render_row(
618                                                    row_index,
619                                                    row,
620                                                    table_context.clone(),
621                                                    window,
622                                                    cx,
623                                                )
624                                            })
625                                            .collect()
626                                    }
627                                },
628                            )
629                            .size_full()
630                            .flex_grow()
631                            .with_sizing_behavior(ListSizingBehavior::Auto)
632                            .with_horizontal_sizing_behavior(if width.is_some() {
633                                ListHorizontalSizingBehavior::Unconstrained
634                            } else {
635                                ListHorizontalSizingBehavior::FitList
636                            })
637                            .when_some(
638                                interaction_state.as_ref(),
639                                |this, state| {
640                                    this.track_scroll(
641                                        state.read_with(cx, |s, _| s.scroll_handle.clone()),
642                                    )
643                                },
644                            ),
645                        ),
646                    })
647                    .when_some(interaction_state.as_ref(), |this, interaction_state| {
648                        this.map(|this| {
649                            TableInteractionState::render_vertical_scrollbar_track(
650                                interaction_state,
651                                this,
652                                scroll_track_size,
653                                cx,
654                            )
655                        })
656                        .map(|this| {
657                            TableInteractionState::render_vertical_scrollbar(
658                                interaction_state,
659                                this,
660                                cx,
661                            )
662                        })
663                    }),
664            )
665            .when_some(
666                width.and(interaction_state.as_ref()),
667                |this, interaction_state| {
668                    this.map(|this| {
669                        TableInteractionState::render_horizontal_scrollbar_track(
670                            interaction_state,
671                            this,
672                            scroll_track_size,
673                            cx,
674                        )
675                    })
676                    .map(|this| {
677                        TableInteractionState::render_horizontal_scrollbar(
678                            interaction_state,
679                            this,
680                            h_scroll_offset,
681                            cx,
682                        )
683                    })
684                },
685            );
686
687        if let Some(interaction_state) = interaction_state.as_ref() {
688            table
689                .track_focus(&interaction_state.read(cx).focus_handle)
690                .id(("table", interaction_state.entity_id()))
691                .on_hover({
692                    let interaction_state = interaction_state.downgrade();
693                    move |hovered, window, cx| {
694                        interaction_state
695                            .update(cx, |interaction_state, cx| {
696                                if *hovered {
697                                    interaction_state.horizontal_scrollbar.show(cx);
698                                    interaction_state.vertical_scrollbar.show(cx);
699                                    cx.notify();
700                                } else if !interaction_state
701                                    .focus_handle
702                                    .contains_focused(window, cx)
703                                {
704                                    interaction_state.hide_scrollbars(window, cx);
705                                }
706                            })
707                            .ok();
708                    }
709                })
710                .into_any_element()
711        } else {
712            table.into_any_element()
713        }
714    }
715}
716
717// computed state related to how to render scrollbars
718// one per axis
719// on render we just read this off the keymap editor
720// we update it when
721// - settings change
722// - on focus in, on focus out, on hover, etc.
723#[derive(Debug)]
724pub struct ScrollbarProperties {
725    axis: Axis,
726    show_scrollbar: bool,
727    show_track: bool,
728    auto_hide: bool,
729    hide_task: Option<Task<()>>,
730    state: ScrollbarState,
731}
732
733impl ScrollbarProperties {
734    // Shows the scrollbar and cancels any pending hide task
735    fn show(&mut self, cx: &mut Context<TableInteractionState>) {
736        if !self.auto_hide {
737            return;
738        }
739        self.show_scrollbar = true;
740        self.hide_task.take();
741        cx.notify();
742    }
743
744    fn hide(&mut self, window: &mut Window, cx: &mut Context<TableInteractionState>) {
745        const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
746
747        if !self.auto_hide {
748            return;
749        }
750
751        let axis = self.axis;
752        self.hide_task = Some(cx.spawn_in(window, async move |keymap_editor, cx| {
753            cx.background_executor()
754                .timer(SCROLLBAR_SHOW_INTERVAL)
755                .await;
756
757            if let Some(keymap_editor) = keymap_editor.upgrade() {
758                keymap_editor
759                    .update(cx, |keymap_editor, cx| {
760                        match axis {
761                            Axis::Vertical => {
762                                keymap_editor.vertical_scrollbar.show_scrollbar = false
763                            }
764                            Axis::Horizontal => {
765                                keymap_editor.horizontal_scrollbar.show_scrollbar = false
766                            }
767                        }
768                        cx.notify();
769                    })
770                    .ok();
771            }
772        }));
773    }
774}
775
776impl Component for Table<3> {
777    fn scope() -> ComponentScope {
778        ComponentScope::Layout
779    }
780
781    fn description() -> Option<&'static str> {
782        Some("A table component for displaying data in rows and columns with optional styling.")
783    }
784
785    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
786        Some(
787            v_flex()
788                .gap_6()
789                .children(vec![
790                    example_group_with_title(
791                        "Basic Tables",
792                        vec![
793                            single_example(
794                                "Simple Table",
795                                Table::new()
796                                    .width(px(400.))
797                                    .header(["Name", "Age", "City"])
798                                    .row(["Alice", "28", "New York"])
799                                    .row(["Bob", "32", "San Francisco"])
800                                    .row(["Charlie", "25", "London"])
801                                    .into_any_element(),
802                            ),
803                            single_example(
804                                "Two Column Table",
805                                Table::new()
806                                    .header(["Category", "Value"])
807                                    .width(px(300.))
808                                    .row(["Revenue", "$100,000"])
809                                    .row(["Expenses", "$75,000"])
810                                    .row(["Profit", "$25,000"])
811                                    .into_any_element(),
812                            ),
813                        ],
814                    ),
815                    example_group_with_title(
816                        "Styled Tables",
817                        vec![
818                            single_example(
819                                "Default",
820                                Table::new()
821                                    .width(px(400.))
822                                    .header(["Product", "Price", "Stock"])
823                                    .row(["Laptop", "$999", "In Stock"])
824                                    .row(["Phone", "$599", "Low Stock"])
825                                    .row(["Tablet", "$399", "Out of Stock"])
826                                    .into_any_element(),
827                            ),
828                            single_example(
829                                "Striped",
830                                Table::new()
831                                    .width(px(400.))
832                                    .striped()
833                                    .header(["Product", "Price", "Stock"])
834                                    .row(["Laptop", "$999", "In Stock"])
835                                    .row(["Phone", "$599", "Low Stock"])
836                                    .row(["Tablet", "$399", "Out of Stock"])
837                                    .row(["Headphones", "$199", "In Stock"])
838                                    .into_any_element(),
839                            ),
840                        ],
841                    ),
842                    example_group_with_title(
843                        "Mixed Content Table",
844                        vec![single_example(
845                            "Table with Elements",
846                            Table::new()
847                                .width(px(840.))
848                                .header(["Status", "Name", "Priority", "Deadline", "Action"])
849                                .row([
850                                    Indicator::dot().color(Color::Success).into_any_element(),
851                                    "Project A".into_any_element(),
852                                    "High".into_any_element(),
853                                    "2023-12-31".into_any_element(),
854                                    Button::new("view_a", "View")
855                                        .style(ButtonStyle::Filled)
856                                        .full_width()
857                                        .into_any_element(),
858                                ])
859                                .row([
860                                    Indicator::dot().color(Color::Warning).into_any_element(),
861                                    "Project B".into_any_element(),
862                                    "Medium".into_any_element(),
863                                    "2024-03-15".into_any_element(),
864                                    Button::new("view_b", "View")
865                                        .style(ButtonStyle::Filled)
866                                        .full_width()
867                                        .into_any_element(),
868                                ])
869                                .row([
870                                    Indicator::dot().color(Color::Error).into_any_element(),
871                                    "Project C".into_any_element(),
872                                    "Low".into_any_element(),
873                                    "2024-06-30".into_any_element(),
874                                    Button::new("view_c", "View")
875                                        .style(ButtonStyle::Filled)
876                                        .full_width()
877                                        .into_any_element(),
878                                ])
879                                .into_any_element(),
880                        )],
881                    ),
882                ])
883                .into_any_element(),
884        )
885    }
886}