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