use std::{ops::Range, rc::Rc, time::Duration}; use editor::{EditorSettings, ShowScrollbar, scroll::ScrollbarAutoHide};use gpui::{ AbsoluteLength, AppContext, Axis, Context, DefiniteLength, DragMoveEvent, Entity, EntityId, FocusHandle, Length, ListHorizontalSizingBehavior, ListSizingBehavior, MouseButton, Point, Stateful, Task, UniformListScrollHandle, WeakEntity, transparent_black, uniform_list,}; use itertools::intersperse_with;use settings::Settings as _;use ui::{ ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component, ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator, InteractiveElement, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce, Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, StyledExt as _, StyledTypography, Window, div, example_group_with_title, h_flex, px, single_example, v_flex,}; const RESIZE_COLUMN_WIDTH: f32 = 8.0; #[derive(Debug)]struct DraggedColumn(usize); struct UniformListData<const COLS: usize> { render_item_fn: Box<dyn Fn(Range<usize>, &mut Window, &mut App) -> Vec<[AnyElement; COLS]>>, element_id: ElementId, row_count: usize,} enum TableContents<const COLS: usize> { Vec(Vec<[AnyElement; COLS]>), UniformList(UniformListData<COLS>),} impl<const COLS: usize> TableContents<COLS> { fn rows_mut(&mut self) -> Option<&mut Vec<[AnyElement; COLS]>> { match self { TableContents::Vec(rows) => Some(rows), TableContents::UniformList(_) => None, } } fn len(&self) -> usize { match self { TableContents::Vec(rows) => rows.len(), TableContents::UniformList(data) => data.row_count, } } fn is_empty(&self) -> bool { self.len() == 0 }} pub struct TableInteractionState { pub focus_handle: FocusHandle, pub scroll_handle: UniformListScrollHandle, pub horizontal_scrollbar: ScrollbarProperties, pub vertical_scrollbar: ScrollbarProperties,} impl TableInteractionState { pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> { cx.new(|cx| { let focus_handle = cx.focus_handle(); cx.on_focus_out(&focus_handle, window, |this: &mut Self, _, window, cx| { this.hide_scrollbars(window, cx); }) .detach(); let scroll_handle = UniformListScrollHandle::new(); let vertical_scrollbar = ScrollbarProperties { axis: Axis::Vertical, state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()), show_scrollbar: false, show_track: false, auto_hide: false, hide_task: None, }; let horizontal_scrollbar = ScrollbarProperties { axis: Axis::Horizontal, state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()), show_scrollbar: false, show_track: false, auto_hide: false, hide_task: None, }; let mut this = Self { focus_handle, scroll_handle, horizontal_scrollbar, vertical_scrollbar, }; this.update_scrollbar_visibility(cx); this }) } pub fn get_scrollbar_offset(&self, axis: Axis) -> Point<Pixels> { match axis { Axis::Vertical => self.vertical_scrollbar.state.scroll_handle().offset(), Axis::Horizontal => self.horizontal_scrollbar.state.scroll_handle().offset(), } } pub fn set_scrollbar_offset(&self, axis: Axis, offset: Point<Pixels>) { match axis { Axis::Vertical => self .vertical_scrollbar .state .scroll_handle() .set_offset(offset), Axis::Horizontal => self .horizontal_scrollbar .state .scroll_handle() .set_offset(offset), } } fn update_scrollbar_visibility(&mut self, cx: &mut Context<Self>) { let show_setting = EditorSettings::get_global(cx).scrollbar.show; let scroll_handle = self.scroll_handle.0.borrow(); let autohide = |show: ShowScrollbar, cx: &mut Context<Self>| match show { ShowScrollbar::Auto => true, ShowScrollbar::System => cx .try_global::<ScrollbarAutoHide>() .map_or_else(|| cx.should_auto_hide_scrollbars(), |autohide| autohide.0), ShowScrollbar::Always => false, ShowScrollbar::Never => false, }; let longest_item_width = scroll_handle.last_item_size.and_then(|size| { (size.contents.width > size.item.width).then_some(size.contents.width) }); // is there an item long enough that we should show a horizontal scrollbar? let item_wider_than_container = if let Some(longest_item_width) = longest_item_width { longest_item_width > px(scroll_handle.base_handle.bounds().size.width.0) } else { true }; let show_scrollbar = match show_setting { ShowScrollbar::Auto | ShowScrollbar::System | ShowScrollbar::Always => true, ShowScrollbar::Never => false, }; let show_vertical = show_scrollbar; let show_horizontal = item_wider_than_container && show_scrollbar; let show_horizontal_track = show_horizontal && matches!(show_setting, ShowScrollbar::Always); // TODO: we probably should hide the scroll track when the list doesn't need to scroll let show_vertical_track = show_vertical && matches!(show_setting, ShowScrollbar::Always); self.vertical_scrollbar = ScrollbarProperties { axis: self.vertical_scrollbar.axis, state: self.vertical_scrollbar.state.clone(), show_scrollbar: show_vertical, show_track: show_vertical_track, auto_hide: autohide(show_setting, cx), hide_task: None, }; self.horizontal_scrollbar = ScrollbarProperties { axis: self.horizontal_scrollbar.axis, state: self.horizontal_scrollbar.state.clone(), show_scrollbar: show_horizontal, show_track: show_horizontal_track, auto_hide: autohide(show_setting, cx), hide_task: None, }; cx.notify(); } fn hide_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.horizontal_scrollbar.hide(window, cx); self.vertical_scrollbar.hide(window, cx); } pub fn listener<E: ?Sized>( this: &Entity<Self>, f: impl Fn(&mut Self, &E, &mut Window, &mut Context<Self>) + 'static, ) -> impl Fn(&E, &mut Window, &mut App) + 'static { let view = this.downgrade(); move |e: &E, window: &mut Window, cx: &mut App| { view.update(cx, |view, cx| f(view, e, window, cx)).ok(); } } fn render_resize_handles<const COLS: usize>( &self, column_widths: &[Length; COLS], resizable_columns: &[ResizeBehavior; COLS], initial_sizes: [DefiniteLength; COLS], columns: Option<Entity<ColumnWidths<COLS>>>, window: &mut Window, cx: &mut App, ) -> AnyElement { let spacers = column_widths .iter() .map(|width| base_cell_style(Some(*width)).into_any_element()); let mut column_ix = 0; let resizable_columns_slice = *resizable_columns; let mut resizable_columns = resizable_columns.into_iter(); let dividers = intersperse_with(spacers, || { window.with_id(column_ix, |window| { let mut resize_divider = div() // This is required because this is evaluated at a different time than the use_state call above .id(column_ix) .relative() .top_0() .w_px() .h_full() .bg(cx.theme().colors().border.opacity(0.8)); let mut resize_handle = div() .id("column-resize-handle") .absolute() .left_neg_0p5() .w(px(RESIZE_COLUMN_WIDTH)) .h_full(); if resizable_columns .next() .is_some_and(ResizeBehavior::is_resizable) { let hovered = window.use_state(cx, |_window, _cx| false); resize_divider = resize_divider.when(*hovered.read(cx), |div| { div.bg(cx.theme().colors().border_focused) }); resize_handle = resize_handle .on_hover(move |&was_hovered, _, cx| hovered.write(cx, was_hovered)) .cursor_col_resize() .when_some(columns.clone(), |this, columns| { this.on_click(move |event, window, cx| { if event.down.click_count >= 2 { columns.update(cx, |columns, _| { columns.on_double_click( column_ix, &initial_sizes, &resizable_columns_slice, window, ); }) } cx.stop_propagation(); }) }) .on_drag(DraggedColumn(column_ix), |_, _offset, _window, cx| { cx.new(|_cx| gpui::Empty) }) } column_ix += 1; resize_divider.child(resize_handle).into_any_element() }) }); h_flex() .id("resize-handles") .absolute() .inset_0() .w_full() .children(dividers) .into_any_element() } fn render_vertical_scrollbar_track( this: &Entity<Self>, parent: Div, scroll_track_size: Pixels, cx: &mut App, ) -> Div { if !this.read(cx).vertical_scrollbar.show_track { return parent; } let child = v_flex() .h_full() .flex_none() .w(scroll_track_size) .bg(cx.theme().colors().background) .child( div() .size_full() .flex_1() .border_l_1() .border_color(cx.theme().colors().border), ); parent.child(child) } fn render_vertical_scrollbar(this: &Entity<Self>, parent: Div, cx: &mut App) -> Div { if !this.read(cx).vertical_scrollbar.show_scrollbar { return parent; } let child = div() .id(("table-vertical-scrollbar", this.entity_id())) .occlude() .flex_none() .h_full() .cursor_default() .absolute() .right_0() .top_0() .bottom_0() .w(px(12.)) .on_mouse_move(Self::listener(this, |_, _, _, cx| { cx.notify(); cx.stop_propagation() })) .on_hover(|_, _, cx| { cx.stop_propagation(); }) .on_mouse_up( MouseButton::Left, Self::listener(this, |this, _, window, cx| { if !this.vertical_scrollbar.state.is_dragging() && !this.focus_handle.contains_focused(window, cx) { this.vertical_scrollbar.hide(window, cx); cx.notify(); } cx.stop_propagation(); }), ) .on_any_mouse_down(|_, _, cx| { cx.stop_propagation(); }) .on_scroll_wheel(Self::listener(&this, |_, _, _, cx| { cx.notify(); })) .children(Scrollbar::vertical( this.read(cx).vertical_scrollbar.state.clone(), )); parent.child(child) } /// Renders the horizontal scrollbar. /// /// The right offset is used to determine how far to the right the /// scrollbar should extend to, useful for ensuring it doesn't collide /// with the vertical scrollbar when visible. fn render_horizontal_scrollbar( this: &Entity<Self>, parent: Div, right_offset: Pixels, cx: &mut App, ) -> Div { if !this.read(cx).horizontal_scrollbar.show_scrollbar { return parent; } let child = div() .id(("table-horizontal-scrollbar", this.entity_id())) .occlude() .flex_none() .w_full() .cursor_default() .absolute() .bottom_neg_px() .left_0() .right_0() .pr(right_offset) .on_mouse_move(Self::listener(this, |_, _, _, cx| { cx.notify(); cx.stop_propagation() })) .on_hover(|_, _, cx| { cx.stop_propagation(); }) .on_any_mouse_down(|_, _, cx| { cx.stop_propagation(); }) .on_mouse_up( MouseButton::Left, Self::listener(this, |this, _, window, cx| { if !this.horizontal_scrollbar.state.is_dragging() && !this.focus_handle.contains_focused(window, cx) { this.horizontal_scrollbar.hide(window, cx); cx.notify(); } cx.stop_propagation(); }), ) .on_scroll_wheel(Self::listener(this, |_, _, _, cx| { cx.notify(); })) .children(Scrollbar::horizontal( // percentage as f32..end_offset as f32, this.read(cx).horizontal_scrollbar.state.clone(), )); parent.child(child) } fn render_horizontal_scrollbar_track( this: &Entity<Self>, parent: Div, scroll_track_size: Pixels, cx: &mut App, ) -> Div { if !this.read(cx).horizontal_scrollbar.show_track { return parent; } let child = h_flex() .w_full() .h(scroll_track_size) .flex_none() .relative() .child( div() .w_full() .flex_1() // for some reason the horizontal scrollbar is 1px // taller than the vertical scrollbar?? .h(scroll_track_size - px(1.)) .bg(cx.theme().colors().background) .border_t_1() .border_color(cx.theme().colors().border), ) .when(this.read(cx).vertical_scrollbar.show_track, |parent| { parent .child( div() .flex_none() // -1px prevents a missing pixel between the two container borders .w(scroll_track_size - px(1.)) .h_full(), ) .child( // HACK: Fill the missing 1px 🥲 div() .absolute() .right(scroll_track_size - px(1.)) .bottom(scroll_track_size - px(1.)) .size_px() .bg(cx.theme().colors().border), ) }); parent.child(child) }} #[derive(Debug, Copy, Clone, PartialEq)]pub enum ResizeBehavior { None, Resizable, MinSize(f32),} impl ResizeBehavior { pub fn is_resizable(&self) -> bool { *self != ResizeBehavior::None } pub fn min_size(&self) -> Option<f32> { match self { ResizeBehavior::None => None, ResizeBehavior::Resizable => Some(0.05), ResizeBehavior::MinSize(min_size) => Some(*min_size), } }} pub struct ColumnWidths<const COLS: usize> { widths: [DefiniteLength; COLS], visible_widths: [DefiniteLength; COLS], cached_bounds_width: Pixels, initialized: bool,} impl<const COLS: usize> ColumnWidths<COLS> { pub fn new(_: &mut App) -> Self { Self { widths: [DefiniteLength::default(); COLS], visible_widths: [DefiniteLength::default(); COLS], cached_bounds_width: Default::default(), initialized: false, } } fn get_fraction(length: &DefiniteLength, bounds_width: Pixels, rem_size: Pixels) -> f32 {Lines 1–500 of 1765. Load more
use std::{ops::Range, rc::Rc, time::Duration}; use editor::{EditorSettings, ShowScrollbar, scroll::ScrollbarAutoHide}; use gpui::{ AbsoluteLength, AppContext, Axis, Context, DefiniteLength, DragMoveEvent, Entity, EntityId, FocusHandle, Length, ListHorizontalSizingBehavior, ListSizingBehavior, MouseButton, Point, Stateful, Task, UniformListScrollHandle, WeakEntity, transparent_black, uniform_list, }; use itertools::intersperse_with; use settings::Settings as _; use ui::{ ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component, ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator, InteractiveElement, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce, Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, StyledExt as _, StyledTypography, Window, div, example_group_with_title, h_flex, px, single_example, v_flex, }; const RESIZE_COLUMN_WIDTH: f32 = 8.0; #[derive(Debug)] struct DraggedColumn(usize); struct UniformListData<const COLS: usize> { render_item_fn: Box<dyn Fn(Range<usize>, &mut Window, &mut App) -> Vec<[AnyElement; COLS]>>, element_id: ElementId, row_count: usize, } enum TableContents<const COLS: usize> { Vec(Vec<[AnyElement; COLS]>), UniformList(UniformListData<COLS>), } impl<const COLS: usize> TableContents<COLS> { fn rows_mut(&mut self) -> Option<&mut Vec<[AnyElement; COLS]>> { match self { TableContents::Vec(rows) => Some(rows), TableContents::UniformList(_) => None, } } fn len(&self) -> usize { match self { TableContents::Vec(rows) => rows.len(), TableContents::UniformList(data) => data.row_count, } } fn is_empty(&self) -> bool { self.len() == 0 } } pub struct TableInteractionState { pub focus_handle: FocusHandle, pub scroll_handle: UniformListScrollHandle, pub horizontal_scrollbar: ScrollbarProperties, pub vertical_scrollbar: ScrollbarProperties, } impl TableInteractionState { pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> { cx.new(|cx| { let focus_handle = cx.focus_handle(); cx.on_focus_out(&focus_handle, window, |this: &mut Self, _, window, cx| { this.hide_scrollbars(window, cx); }) .detach(); let scroll_handle = UniformListScrollHandle::new(); let vertical_scrollbar = ScrollbarProperties { axis: Axis::Vertical, state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()), show_scrollbar: false, show_track: false, auto_hide: false, hide_task: None, }; let horizontal_scrollbar = ScrollbarProperties { axis: Axis::Horizontal, state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()), show_scrollbar: false, show_track: false, auto_hide: false, hide_task: None, }; let mut this = Self { focus_handle, scroll_handle, horizontal_scrollbar, vertical_scrollbar, }; this.update_scrollbar_visibility(cx); this }) } pub fn get_scrollbar_offset(&self, axis: Axis) -> Point<Pixels> { match axis { Axis::Vertical => self.vertical_scrollbar.state.scroll_handle().offset(), Axis::Horizontal => self.horizontal_scrollbar.state.scroll_handle().offset(), } } pub fn set_scrollbar_offset(&self, axis: Axis, offset: Point<Pixels>) { match axis { Axis::Vertical => self .vertical_scrollbar .state .scroll_handle() .set_offset(offset), Axis::Horizontal => self .horizontal_scrollbar .state .scroll_handle() .set_offset(offset), } } fn update_scrollbar_visibility(&mut self, cx: &mut Context<Self>) { let show_setting = EditorSettings::get_global(cx).scrollbar.show; let scroll_handle = self.scroll_handle.0.borrow(); let autohide = |show: ShowScrollbar, cx: &mut Context<Self>| match show { ShowScrollbar::Auto => true, ShowScrollbar::System => cx .try_global::<ScrollbarAutoHide>() .map_or_else(|| cx.should_auto_hide_scrollbars(), |autohide| autohide.0), ShowScrollbar::Always => false, ShowScrollbar::Never => false, }; let longest_item_width = scroll_handle.last_item_size.and_then(|size| { (size.contents.width > size.item.width).then_some(size.contents.width) }); // is there an item long enough that we should show a horizontal scrollbar? let item_wider_than_container = if let Some(longest_item_width) = longest_item_width { longest_item_width > px(scroll_handle.base_handle.bounds().size.width.0) } else { true }; let show_scrollbar = match show_setting { ShowScrollbar::Auto | ShowScrollbar::System | ShowScrollbar::Always => true, ShowScrollbar::Never => false, }; let show_vertical = show_scrollbar; let show_horizontal = item_wider_than_container && show_scrollbar; let show_horizontal_track = show_horizontal && matches!(show_setting, ShowScrollbar::Always); // TODO: we probably should hide the scroll track when the list doesn't need to scroll let show_vertical_track = show_vertical && matches!(show_setting, ShowScrollbar::Always); self.vertical_scrollbar = ScrollbarProperties { axis: self.vertical_scrollbar.axis, state: self.vertical_scrollbar.state.clone(), show_scrollbar: show_vertical, show_track: show_vertical_track, auto_hide: autohide(show_setting, cx), hide_task: None, }; self.horizontal_scrollbar = ScrollbarProperties { axis: self.horizontal_scrollbar.axis, state: self.horizontal_scrollbar.state.clone(), show_scrollbar: show_horizontal, show_track: show_horizontal_track, auto_hide: autohide(show_setting, cx), hide_task: None, }; cx.notify(); } fn hide_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.horizontal_scrollbar.hide(window, cx); self.vertical_scrollbar.hide(window, cx); } pub fn listener<E: ?Sized>( this: &Entity<Self>, f: impl Fn(&mut Self, &E, &mut Window, &mut Context<Self>) + 'static, ) -> impl Fn(&E, &mut Window, &mut App) + 'static { let view = this.downgrade(); move |e: &E, window: &mut Window, cx: &mut App| { view.update(cx, |view, cx| f(view, e, window, cx)).ok(); } } fn render_resize_handles<const COLS: usize>( &self, column_widths: &[Length; COLS], resizable_columns: &[ResizeBehavior; COLS], initial_sizes: [DefiniteLength; COLS], columns: Option<Entity<ColumnWidths<COLS>>>, window: &mut Window, cx: &mut App, ) -> AnyElement { let spacers = column_widths .iter() .map(|width| base_cell_style(Some(*width)).into_any_element()); let mut column_ix = 0; let resizable_columns_slice = *resizable_columns; let mut resizable_columns = resizable_columns.into_iter(); let dividers = intersperse_with(spacers, || { window.with_id(column_ix, |window| { let mut resize_divider = div() // This is required because this is evaluated at a different time than the use_state call above .id(column_ix) .relative() .top_0() .w_px() .h_full() .bg(cx.theme().colors().border.opacity(0.8)); let mut resize_handle = div() .id("column-resize-handle") .absolute() .left_neg_0p5() .w(px(RESIZE_COLUMN_WIDTH)) .h_full(); if resizable_columns .next() .is_some_and(ResizeBehavior::is_resizable) { let hovered = window.use_state(cx, |_window, _cx| false); resize_divider = resize_divider.when(*hovered.read(cx), |div| { div.bg(cx.theme().colors().border_focused) }); resize_handle = resize_handle .on_hover(move |&was_hovered, _, cx| hovered.write(cx, was_hovered)) .cursor_col_resize() .when_some(columns.clone(), |this, columns| { this.on_click(move |event, window, cx| { if event.down.click_count >= 2 { columns.update(cx, |columns, _| { columns.on_double_click( column_ix, &initial_sizes, &resizable_columns_slice, window, ); }) } cx.stop_propagation(); }) }) .on_drag(DraggedColumn(column_ix), |_, _offset, _window, cx| { cx.new(|_cx| gpui::Empty) }) } column_ix += 1; resize_divider.child(resize_handle).into_any_element() }) }); h_flex() .id("resize-handles") .absolute() .inset_0() .w_full() .children(dividers) .into_any_element() } fn render_vertical_scrollbar_track( this: &Entity<Self>, parent: Div, scroll_track_size: Pixels, cx: &mut App, ) -> Div { if !this.read(cx).vertical_scrollbar.show_track { return parent; } let child = v_flex() .h_full() .flex_none() .w(scroll_track_size) .bg(cx.theme().colors().background) .child( div() .size_full() .flex_1() .border_l_1() .border_color(cx.theme().colors().border), ); parent.child(child) } fn render_vertical_scrollbar(this: &Entity<Self>, parent: Div, cx: &mut App) -> Div { if !this.read(cx).vertical_scrollbar.show_scrollbar { return parent; } let child = div() .id(("table-vertical-scrollbar", this.entity_id())) .occlude() .flex_none() .h_full() .cursor_default() .absolute() .right_0() .top_0() .bottom_0() .w(px(12.)) .on_mouse_move(Self::listener(this, |_, _, _, cx| { cx.notify(); cx.stop_propagation() })) .on_hover(|_, _, cx| { cx.stop_propagation(); }) .on_mouse_up( MouseButton::Left, Self::listener(this, |this, _, window, cx| { if !this.vertical_scrollbar.state.is_dragging() && !this.focus_handle.contains_focused(window, cx) { this.vertical_scrollbar.hide(window, cx); cx.notify(); } cx.stop_propagation(); }), ) .on_any_mouse_down(|_, _, cx| { cx.stop_propagation(); }) .on_scroll_wheel(Self::listener(&this, |_, _, _, cx| { cx.notify(); })) .children(Scrollbar::vertical( this.read(cx).vertical_scrollbar.state.clone(), )); parent.child(child) } /// Renders the horizontal scrollbar. /// /// The right offset is used to determine how far to the right the /// scrollbar should extend to, useful for ensuring it doesn't collide /// with the vertical scrollbar when visible. fn render_horizontal_scrollbar( this: &Entity<Self>, parent: Div, right_offset: Pixels, cx: &mut App, ) -> Div { if !this.read(cx).horizontal_scrollbar.show_scrollbar { return parent; } let child = div() .id(("table-horizontal-scrollbar", this.entity_id())) .occlude() .flex_none() .w_full() .cursor_default() .absolute() .bottom_neg_px() .left_0() .right_0() .pr(right_offset) .on_mouse_move(Self::listener(this, |_, _, _, cx| { cx.notify(); cx.stop_propagation() })) .on_hover(|_, _, cx| { cx.stop_propagation(); }) .on_any_mouse_down(|_, _, cx| { cx.stop_propagation(); }) .on_mouse_up( MouseButton::Left, Self::listener(this, |this, _, window, cx| { if !this.horizontal_scrollbar.state.is_dragging() && !this.focus_handle.contains_focused(window, cx) { this.horizontal_scrollbar.hide(window, cx); cx.notify(); } cx.stop_propagation(); }), ) .on_scroll_wheel(Self::listener(this, |_, _, _, cx| { cx.notify(); })) .children(Scrollbar::horizontal( // percentage as f32..end_offset as f32, this.read(cx).horizontal_scrollbar.state.clone(), )); parent.child(child) } fn render_horizontal_scrollbar_track( this: &Entity<Self>, parent: Div, scroll_track_size: Pixels, cx: &mut App, ) -> Div { if !this.read(cx).horizontal_scrollbar.show_track { return parent; } let child = h_flex() .w_full() .h(scroll_track_size) .flex_none() .relative() .child( div() .w_full() .flex_1() // for some reason the horizontal scrollbar is 1px // taller than the vertical scrollbar?? .h(scroll_track_size - px(1.)) .bg(cx.theme().colors().background) .border_t_1() .border_color(cx.theme().colors().border), ) .when(this.read(cx).vertical_scrollbar.show_track, |parent| { parent .child( div() .flex_none() // -1px prevents a missing pixel between the two container borders .w(scroll_track_size - px(1.)) .h_full(), ) .child( // HACK: Fill the missing 1px 🥲 div() .absolute() .right(scroll_track_size - px(1.)) .bottom(scroll_track_size - px(1.)) .size_px() .bg(cx.theme().colors().border), ) }); parent.child(child) } } #[derive(Debug, Copy, Clone, PartialEq)] pub enum ResizeBehavior { None, Resizable, MinSize(f32), } impl ResizeBehavior { pub fn is_resizable(&self) -> bool { *self != ResizeBehavior::None } pub fn min_size(&self) -> Option<f32> { match self { ResizeBehavior::None => None, ResizeBehavior::Resizable => Some(0.05), ResizeBehavior::MinSize(min_size) => Some(*min_size), } } } pub struct ColumnWidths<const COLS: usize> { widths: [DefiniteLength; COLS], visible_widths: [DefiniteLength; COLS], cached_bounds_width: Pixels, initialized: bool, } impl<const COLS: usize> ColumnWidths<COLS> { pub fn new(_: &mut App) -> Self { Self { widths: [DefiniteLength::default(); COLS], visible_widths: [DefiniteLength::default(); COLS], cached_bounds_width: Default::default(), initialized: false, } } fn get_fraction(length: &DefiniteLength, bounds_width: Pixels, rem_size: Pixels) -> f32 {