@@ -1,22 +1,22 @@
use std::{ops::Range, rc::Rc};
-use gpui::{
- DefiniteLength, Entity, EntityId, FocusHandle, Length, ListHorizontalSizingBehavior,
- ListSizingBehavior, ListState, Point, Stateful, UniformListScrollHandle, WeakEntity, list,
- transparent_black, uniform_list,
-};
-
use crate::{
ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component,
- ComponentScope, Context, Div, ElementId, FixedWidth as _, FluentBuilder as _, HeaderResizeInfo,
- Indicator, InteractiveElement, IntoElement, ParentElement, Pixels, RedistributableColumnsState,
- RegisterComponent, RenderOnce, ScrollAxes, ScrollableHandle, Scrollbars, SharedString,
- StatefulInteractiveElement, Styled, StyledExt as _, StyledTypography, Window, WithScrollbar,
- bind_redistributable_columns, div, example_group_with_title, h_flex, px,
+ ComponentScope, Context, Div, DraggedColumn, ElementId, FixedWidth as _, FluentBuilder as _,
+ HeaderResizeInfo, Indicator, InteractiveElement, IntoElement, ParentElement, Pixels,
+ RESIZE_DIVIDER_WIDTH, RedistributableColumnsState, RegisterComponent, RenderOnce, ScrollAxes,
+ ScrollableHandle, Scrollbars, SharedString, StatefulInteractiveElement, Styled, StyledExt as _,
+ StyledTypography, TableResizeBehavior, Window, WithScrollbar, bind_redistributable_columns,
+ div, example_group_with_title, h_flex, px, render_column_resize_divider,
render_redistributable_columns_resize_handles, single_example,
table_row::{IntoTableRow as _, TableRow},
v_flex,
};
+use gpui::{
+ AbsoluteLength, DefiniteLength, DragMoveEvent, Entity, EntityId, FocusHandle, Length,
+ ListHorizontalSizingBehavior, ListSizingBehavior, ListState, Point, ScrollHandle, Stateful,
+ UniformListScrollHandle, WeakEntity, list, transparent_black, uniform_list,
+};
pub mod table_row;
#[cfg(test)]
@@ -26,6 +26,96 @@ mod tests;
/// Will be converted into `TableRow<T>` internally
pub type UncheckedTableRow<T> = Vec<T>;
+/// State for independently resizable columns (spreadsheet-style).
+///
+/// Each column has its own absolute width; dragging a resize handle changes only
+/// that column's width, growing or shrinking the overall table width.
+pub struct ResizableColumnsState {
+ initial_widths: TableRow<AbsoluteLength>,
+ widths: TableRow<AbsoluteLength>,
+ resize_behavior: TableRow<TableResizeBehavior>,
+}
+
+impl ResizableColumnsState {
+ pub fn new(
+ cols: usize,
+ initial_widths: Vec<impl Into<AbsoluteLength>>,
+ resize_behavior: Vec<TableResizeBehavior>,
+ ) -> Self {
+ let widths: TableRow<AbsoluteLength> = initial_widths
+ .into_iter()
+ .map(Into::into)
+ .collect::<Vec<_>>()
+ .into_table_row(cols);
+ Self {
+ initial_widths: widths.clone(),
+ widths,
+ resize_behavior: resize_behavior.into_table_row(cols),
+ }
+ }
+
+ pub fn cols(&self) -> usize {
+ self.widths.cols()
+ }
+
+ pub fn resize_behavior(&self) -> &TableRow<TableResizeBehavior> {
+ &self.resize_behavior
+ }
+
+ pub(crate) fn on_drag_move(
+ &mut self,
+ drag_event: &DragMoveEvent<DraggedColumn>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ let col_idx = drag_event.drag(cx).col_idx;
+ let rem_size = window.rem_size();
+ let drag_x = drag_event.event.position.x - drag_event.bounds.left();
+
+ let left_edge: Pixels = self.widths.as_slice()[..col_idx]
+ .iter()
+ .map(|width| width.to_pixels(rem_size))
+ .fold(px(0.), |acc, x| acc + x);
+
+ let new_width = drag_x - left_edge;
+ let new_width = self.apply_min_size(new_width, self.resize_behavior[col_idx], rem_size);
+
+ self.widths[col_idx] = AbsoluteLength::Pixels(new_width);
+ cx.notify();
+ }
+
+ pub fn set_column_configuration(
+ &mut self,
+ col_idx: usize,
+ width: impl Into<AbsoluteLength>,
+ resize_behavior: TableResizeBehavior,
+ ) {
+ let width = width.into();
+ self.initial_widths[col_idx] = width;
+ self.widths[col_idx] = width;
+ self.resize_behavior[col_idx] = resize_behavior;
+ }
+
+ pub fn reset_column_to_initial_width(&mut self, col_idx: usize) {
+ self.widths[col_idx] = self.initial_widths[col_idx];
+ }
+
+ fn apply_min_size(
+ &self,
+ width: Pixels,
+ behavior: TableResizeBehavior,
+ rem_size: Pixels,
+ ) -> Pixels {
+ match behavior.min_size() {
+ Some(min_rems) => {
+ let min_px = rem_size * min_rems;
+ width.max(min_px)
+ }
+ None => width,
+ }
+ }
+}
+
struct UniformListData {
render_list_of_rows_fn:
Box<dyn Fn(Range<usize>, &mut Window, &mut App) -> Vec<UncheckedTableRow<AnyElement>>>,
@@ -71,6 +161,7 @@ impl TableContents {
pub struct TableInteractionState {
pub focus_handle: FocusHandle,
pub scroll_handle: UniformListScrollHandle,
+ pub horizontal_scroll_handle: ScrollHandle,
pub custom_scrollbar: Option<Scrollbars>,
}
@@ -79,6 +170,7 @@ impl TableInteractionState {
Self {
focus_handle: cx.focus_handle(),
scroll_handle: UniformListScrollHandle::new(),
+ horizontal_scroll_handle: ScrollHandle::new(),
custom_scrollbar: None,
}
}
@@ -120,6 +212,9 @@ pub enum ColumnWidthConfig {
columns_state: Entity<RedistributableColumnsState>,
table_width: Option<DefiniteLength>,
},
+ /// Independently resizable columns — dragging changes absolute column widths
+ /// and thus the overall table width. Like spreadsheets.
+ Resizable(Entity<ResizableColumnsState>),
}
pub enum StaticColumnWidths {
@@ -184,24 +279,52 @@ impl ColumnWidthConfig {
columns_state: entity,
..
} => Some(entity.read(cx).widths_to_render()),
+ ColumnWidthConfig::Resizable(entity) => {
+ let state = entity.read(cx);
+ Some(
+ state
+ .widths
+ .map_cloned(|abs| Length::Definite(DefiniteLength::Absolute(abs))),
+ )
+ }
}
}
/// Table-level width.
- pub fn table_width(&self) -> Option<Length> {
+ pub fn table_width(&self, window: &Window, cx: &App) -> Option<Length> {
match self {
ColumnWidthConfig::Static { table_width, .. }
| ColumnWidthConfig::Redistributable { table_width, .. } => {
table_width.map(Length::Definite)
}
+ ColumnWidthConfig::Resizable(entity) => {
+ let state = entity.read(cx);
+ let rem_size = window.rem_size();
+ let total: Pixels = state
+ .widths
+ .as_slice()
+ .iter()
+ .map(|abs| abs.to_pixels(rem_size))
+ .fold(px(0.), |acc, x| acc + x);
+ Some(Length::Definite(DefiniteLength::Absolute(
+ AbsoluteLength::Pixels(total),
+ )))
+ }
}
}
/// ListHorizontalSizingBehavior for uniform_list.
- pub fn list_horizontal_sizing(&self) -> ListHorizontalSizingBehavior {
- match self.table_width() {
- Some(_) => ListHorizontalSizingBehavior::Unconstrained,
- None => ListHorizontalSizingBehavior::FitList,
+ pub fn list_horizontal_sizing(
+ &self,
+ window: &Window,
+ cx: &App,
+ ) -> ListHorizontalSizingBehavior {
+ match self {
+ ColumnWidthConfig::Resizable(_) => ListHorizontalSizingBehavior::FitList,
+ _ => match self.table_width(window, cx) {
+ Some(_) => ListHorizontalSizingBehavior::Unconstrained,
+ None => ListHorizontalSizingBehavior::FitList,
+ },
}
}
}
@@ -515,13 +638,7 @@ pub fn render_table_header(
if info.resize_behavior[header_idx].is_resizable() {
this.on_click(move |event, window, cx| {
if event.click_count() > 1 {
- info.columns_state
- .update(cx, |column, _| {
- column.reset_column_to_initial_width(
- header_idx, window,
- );
- })
- .ok();
+ info.reset_column(header_idx, window, cx);
}
})
} else {
@@ -572,6 +689,68 @@ impl TableRenderContext {
}
}
+fn render_resize_handles_resizable(
+ columns_state: &Entity<ResizableColumnsState>,
+ window: &mut Window,
+ cx: &mut App,
+) -> AnyElement {
+ let (widths, resize_behavior) = {
+ let state = columns_state.read(cx);
+ (state.widths.clone(), state.resize_behavior.clone())
+ };
+
+ let rem_size = window.rem_size();
+ let resize_behavior = Rc::new(resize_behavior);
+ let n_cols = widths.cols();
+ let mut dividers: Vec<AnyElement> = Vec::with_capacity(n_cols);
+ let mut accumulated_px = px(0.);
+
+ for col_idx in 0..n_cols {
+ let col_width_px = widths[col_idx].to_pixels(rem_size);
+ accumulated_px = accumulated_px + col_width_px;
+
+ // Add a resize divider after every column, including the last.
+ // For the last column the divider is pulled 1px inward so it isn't clipped
+ // by the overflow_hidden content container.
+ {
+ let divider_left = if col_idx + 1 == n_cols {
+ accumulated_px - px(RESIZE_DIVIDER_WIDTH)
+ } else {
+ accumulated_px
+ };
+ let divider = div().id(col_idx).absolute().top_0().left(divider_left);
+ let entity_id = columns_state.entity_id();
+ let on_reset: Rc<dyn Fn(&mut Window, &mut App)> = {
+ let columns_state = columns_state.clone();
+ Rc::new(move |_window, cx| {
+ columns_state.update(cx, |state, cx| {
+ state.reset_column_to_initial_width(col_idx);
+ cx.notify();
+ });
+ })
+ };
+ dividers.push(render_column_resize_divider(
+ divider,
+ col_idx,
+ resize_behavior[col_idx].is_resizable(),
+ entity_id,
+ on_reset,
+ None,
+ window,
+ cx,
+ ));
+ }
+ }
+
+ div()
+ .id("resize-handles")
+ .absolute()
+ .inset_0()
+ .w_full()
+ .children(dividers)
+ .into_any_element()
+}
+
impl RenderOnce for Table {
fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let table_context = TableRenderContext::new(&self, cx);
@@ -582,36 +761,47 @@ impl RenderOnce for Table {
.as_ref()
.and_then(|_| match &self.column_width_config {
ColumnWidthConfig::Redistributable { columns_state, .. } => {
- Some(HeaderResizeInfo::from_state(columns_state, cx))
+ Some(HeaderResizeInfo::from_redistributable(columns_state, cx))
+ }
+ ColumnWidthConfig::Resizable(entity) => {
+ Some(HeaderResizeInfo::from_resizable(entity, cx))
}
_ => None,
});
- let table_width = self.column_width_config.table_width();
- let horizontal_sizing = self.column_width_config.list_horizontal_sizing();
+ let table_width = self.column_width_config.table_width(window, cx);
+ let horizontal_sizing = self.column_width_config.list_horizontal_sizing(window, cx);
let no_rows_rendered = self.rows.is_empty();
-
- // Extract redistributable entity for drag/drop/prepaint handlers
- let redistributable_entity =
- interaction_state
- .as_ref()
- .and_then(|_| match &self.column_width_config {
- ColumnWidthConfig::Redistributable {
- columns_state: entity,
- ..
- } => Some(entity.clone()),
- _ => None,
- });
-
- let resize_handles =
- interaction_state
- .as_ref()
- .and_then(|_| match &self.column_width_config {
- ColumnWidthConfig::Redistributable { columns_state, .. } => Some(
- render_redistributable_columns_resize_handles(columns_state, window, cx),
+ let variable_list_state = if let TableContents::VariableRowHeightList(data) = &self.rows {
+ Some(data.list_state.clone())
+ } else {
+ None
+ };
+
+ let (redistributable_entity, resizable_entity, resize_handles) =
+ if let Some(_) = interaction_state.as_ref() {
+ match &self.column_width_config {
+ ColumnWidthConfig::Redistributable { columns_state, .. } => (
+ Some(columns_state.clone()),
+ None,
+ Some(render_redistributable_columns_resize_handles(
+ columns_state,
+ window,
+ cx,
+ )),
),
- _ => None,
- });
+ ColumnWidthConfig::Resizable(entity) => (
+ None,
+ Some(entity.clone()),
+ Some(render_resize_handles_resizable(entity, window, cx)),
+ ),
+ _ => (None, None, None),
+ }
+ } else {
+ (None, None, None)
+ };
+
+ let is_resizable = resizable_entity.is_some();
let table = div()
.when_some(table_width, |this, width| this.w(width))
@@ -629,6 +819,14 @@ impl RenderOnce for Table {
.when_some(redistributable_entity, |this, widths| {
bind_redistributable_columns(this, widths)
})
+ .when_some(resizable_entity, |this, entity| {
+ this.on_drag_move::<DraggedColumn>(move |event, window, cx| {
+ if event.drag(cx).state_id != entity.entity_id() {
+ return;
+ }
+ entity.update(cx, |state, cx| state.on_drag_move(event, window, cx));
+ })
+ })
.child({
let content = div()
.flex_grow()
@@ -709,22 +907,7 @@ impl RenderOnce for Table {
})
.when_some(resize_handles, |parent, handles| parent.child(handles));
- if let Some(state) = interaction_state.as_ref() {
- let scrollbars = state
- .read(cx)
- .custom_scrollbar
- .clone()
- .unwrap_or_else(|| Scrollbars::new(ScrollAxes::Both));
- content
- .custom_scrollbars(
- scrollbars.tracked_scroll_handle(&state.read(cx).scroll_handle),
- window,
- cx,
- )
- .into_any_element()
- } else {
- content.into_any_element()
- }
+ content.into_any_element()
})
.when_some(
no_rows_rendered
@@ -742,10 +925,52 @@ impl RenderOnce for Table {
},
);
- if let Some(interaction_state) = interaction_state.as_ref() {
- table
- .track_focus(&interaction_state.read(cx).focus_handle)
- .id(("table", interaction_state.entity_id()))
+ if let Some(state) = interaction_state.as_ref() {
+ // Resizable mode: wrap table in a horizontal scroll container first
+ let content = if is_resizable {
+ let mut h_scroll_container = div()
+ .id("table-h-scroll")
+ .overflow_x_scroll()
+ .flex_grow()
+ .h_full()
+ .track_scroll(&state.read(cx).horizontal_scroll_handle)
+ .child(table);
+ h_scroll_container.style().restrict_scroll_to_axis = Some(true);
+ div().size_full().child(h_scroll_container)
+ } else {
+ table
+ };
+
+ // Attach vertical scrollbars (converts Div → Stateful<Div>)
+ let scrollbars = state
+ .read(cx)
+ .custom_scrollbar
+ .clone()
+ .unwrap_or_else(|| Scrollbars::new(ScrollAxes::Both));
+ let mut content = if let Some(list_state) = variable_list_state {
+ content.custom_scrollbars(scrollbars.tracked_scroll_handle(&list_state), window, cx)
+ } else {
+ content.custom_scrollbars(
+ scrollbars.tracked_scroll_handle(&state.read(cx).scroll_handle),
+ window,
+ cx,
+ )
+ };
+
+ // Add horizontal scrollbar when in resizable mode
+ if is_resizable {
+ content = content.custom_scrollbars(
+ Scrollbars::new(ScrollAxes::Horizontal)
+ .tracked_scroll_handle(&state.read(cx).horizontal_scroll_handle),
+ window,
+ cx,
+ );
+ }
+ content.style().restrict_scroll_to_axis = Some(true);
+
+ content
+ .track_focus(&state.read(cx).focus_handle)
+ .id(("table", state.entity_id()))
.into_any_element()
} else {
table.into_any_element()
@@ -1,23 +1,32 @@
use std::rc::Rc;
use gpui::{
- AbsoluteLength, AppContext as _, Bounds, DefiniteLength, DragMoveEvent, Empty, Entity, Length,
- WeakEntity,
+ AbsoluteLength, AppContext as _, Bounds, DefiniteLength, DragMoveEvent, Empty, Entity,
+ EntityId, Length, Stateful, WeakEntity,
};
use itertools::intersperse_with;
-use super::data_table::table_row::{IntoTableRow as _, TableRow};
+use super::data_table::{
+ ResizableColumnsState,
+ table_row::{IntoTableRow as _, TableRow},
+};
use crate::{
ActiveTheme as _, AnyElement, App, Context, Div, FluentBuilder as _, InteractiveElement,
IntoElement, ParentElement, Pixels, StatefulInteractiveElement, Styled, Window, div, h_flex,
px,
};
-const RESIZE_COLUMN_WIDTH: f32 = 8.0;
-const RESIZE_DIVIDER_WIDTH: f32 = 1.0;
+pub(crate) const RESIZE_COLUMN_WIDTH: f32 = 8.0;
+pub(crate) const RESIZE_DIVIDER_WIDTH: f32 = 1.0;
+/// Drag payload for column resize handles.
+/// Includes the `EntityId` of the owning column state so that
+/// `on_drag_move` handlers on unrelated tables ignore the event.
#[derive(Debug)]
-struct DraggedColumn(usize);
+pub(crate) struct DraggedColumn {
+ pub(crate) col_idx: usize,
+ pub(crate) state_id: EntityId,
+}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum TableResizeBehavior {
@@ -40,20 +49,56 @@ impl TableResizeBehavior {
}
}
+#[derive(Clone)]
+pub(crate) enum ColumnsStateRef {
+ Redistributable(WeakEntity<RedistributableColumnsState>),
+ Resizable(WeakEntity<ResizableColumnsState>),
+}
+
#[derive(Clone)]
pub struct HeaderResizeInfo {
- pub columns_state: WeakEntity<RedistributableColumnsState>,
+ pub(crate) columns_state: ColumnsStateRef,
pub resize_behavior: TableRow<TableResizeBehavior>,
}
impl HeaderResizeInfo {
- pub fn from_state(columns_state: &Entity<RedistributableColumnsState>, cx: &App) -> Self {
+ pub fn from_redistributable(
+ columns_state: &Entity<RedistributableColumnsState>,
+ cx: &App,
+ ) -> Self {
let resize_behavior = columns_state.read(cx).resize_behavior().clone();
Self {
- columns_state: columns_state.downgrade(),
+ columns_state: ColumnsStateRef::Redistributable(columns_state.downgrade()),
resize_behavior,
}
}
+
+ pub fn from_resizable(columns_state: &Entity<ResizableColumnsState>, cx: &App) -> Self {
+ let resize_behavior = columns_state.read(cx).resize_behavior().clone();
+ Self {
+ columns_state: ColumnsStateRef::Resizable(columns_state.downgrade()),
+ resize_behavior,
+ }
+ }
+
+ pub fn reset_column(&self, col_idx: usize, window: &mut Window, cx: &mut App) {
+ match &self.columns_state {
+ ColumnsStateRef::Redistributable(weak) => {
+ weak.update(cx, |state, cx| {
+ state.reset_column_to_initial_width(col_idx, window);
+ cx.notify();
+ })
+ .ok();
+ }
+ ColumnsStateRef::Resizable(weak) => {
+ weak.update(cx, |state, cx| {
+ state.reset_column_to_initial_width(col_idx);
+ cx.notify();
+ })
+ .ok();
+ }
+ }
+ }
}
pub struct RedistributableColumnsState {
@@ -237,7 +282,7 @@ impl RedistributableColumnsState {
let mut col_position = 0.0;
let rem_size = window.rem_size();
- let col_idx = drag_event.drag(cx).0;
+ let col_idx = drag_event.drag(cx).col_idx;
let divider_width = Self::get_fraction(
&DefiniteLength::Absolute(AbsoluteLength::Pixels(px(RESIZE_DIVIDER_WIDTH))),
@@ -348,6 +393,9 @@ pub fn bind_redistributable_columns(
.on_drag_move::<DraggedColumn>({
let columns_state = columns_state.clone();
move |event, window, cx| {
+ if event.drag(cx).state_id != columns_state.entity_id() {
+ return;
+ }
columns_state.update(cx, |columns, cx| {
columns.on_drag_move(event, window, cx);
});
@@ -394,67 +442,34 @@ pub fn render_redistributable_columns_resize_handles(
let columns_state = columns_state.clone();
column_ix += 1;
- window.with_id(current_column_ix, |window| {
- let mut resize_divider = div()
- .id(current_column_ix)
- .relative()
- .top_0()
- .w(px(RESIZE_DIVIDER_WIDTH))
- .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 resize_behavior[current_column_ix].is_resizable() {
- let is_highlighted = window.use_state(cx, |_window, _cx| false);
-
- resize_divider = resize_divider.when(*is_highlighted.read(cx), |div| {
- div.bg(cx.theme().colors().border_focused)
- });
-
- resize_handle = resize_handle
- .on_hover({
- let is_highlighted = is_highlighted.clone();
- move |&was_hovered, _, cx| is_highlighted.write(cx, was_hovered)
- })
- .cursor_col_resize()
- .on_click({
- let columns_state = columns_state.clone();
- move |event, window, cx| {
- if event.click_count() >= 2 {
- columns_state.update(cx, |columns, _| {
- columns.reset_column_to_initial_width(
- current_column_ix,
- window,
- );
- });
- }
-
- cx.stop_propagation();
- }
- })
- .on_drag(DraggedColumn(current_column_ix), {
- let is_highlighted = is_highlighted.clone();
- move |_, _offset, _window, cx| {
- is_highlighted.write(cx, true);
- cx.new(|_cx| Empty)
- }
- })
- .on_drop::<DraggedColumn>(move |_, _, cx| {
- is_highlighted.write(cx, false);
- columns_state.update(cx, |state, _| {
- state.commit_preview();
- });
+ {
+ let divider = div().id(current_column_ix).relative().top_0();
+ let entity_id = columns_state.entity_id();
+ let on_reset: Rc<dyn Fn(&mut Window, &mut App)> = {
+ let columns_state = columns_state.clone();
+ Rc::new(move |window, cx| {
+ columns_state.update(cx, |columns, cx| {
+ columns.reset_column_to_initial_width(current_column_ix, window);
+ cx.notify();
});
- }
-
- resize_divider.child(resize_handle).into_any_element()
- })
+ })
+ };
+ let on_drag_end: Option<Rc<dyn Fn(&mut App)>> = {
+ Some(Rc::new(move |cx| {
+ columns_state.update(cx, |state, _| state.commit_preview());
+ }))
+ };
+ render_column_resize_divider(
+ divider,
+ current_column_ix,
+ resize_behavior[current_column_ix].is_resizable(),
+ entity_id,
+ on_reset,
+ on_drag_end,
+ window,
+ cx,
+ )
+ }
},
);
@@ -467,6 +482,83 @@ pub fn render_redistributable_columns_resize_handles(
.into_any_element()
}
+/// Builds a single column resize divider with an interactive drag handle.
+///
+/// The caller provides:
+/// - `divider`: a pre-positioned divider element (with absolute or relative positioning)
+/// - `col_idx`: which column this divider is for
+/// - `is_resizable`: whether the column supports resizing
+/// - `entity_id`: the `EntityId` of the owning column state (for the drag payload)
+/// - `on_reset`: called on double-click to reset the column to its initial width
+/// - `on_drag_end`: called when the drag ends (e.g. to commit preview widths)
+pub(crate) fn render_column_resize_divider(
+ divider: Stateful<Div>,
+ col_idx: usize,
+ is_resizable: bool,
+ entity_id: EntityId,
+ on_reset: Rc<dyn Fn(&mut Window, &mut App)>,
+ on_drag_end: Option<Rc<dyn Fn(&mut App)>>,
+ window: &mut Window,
+ cx: &mut App,
+) -> AnyElement {
+ window.with_id(col_idx, |window| {
+ let mut resize_divider = divider.w(px(RESIZE_DIVIDER_WIDTH)).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 is_resizable {
+ let is_highlighted = window.use_state(cx, |_window, _cx| false);
+
+ resize_divider = resize_divider.when(*is_highlighted.read(cx), |div| {
+ div.bg(cx.theme().colors().border_focused)
+ });
+
+ resize_handle = resize_handle
+ .on_hover({
+ let is_highlighted = is_highlighted.clone();
+ move |&was_hovered, _, cx| is_highlighted.write(cx, was_hovered)
+ })
+ .cursor_col_resize()
+ .on_click(move |event, window, cx| {
+ if event.click_count() >= 2 {
+ on_reset(window, cx);
+ }
+ cx.stop_propagation();
+ })
+ .on_drag(
+ DraggedColumn {
+ col_idx,
+ state_id: entity_id,
+ },
+ {
+ let is_highlighted = is_highlighted.clone();
+ move |_, _offset, _window, cx| {
+ is_highlighted.write(cx, true);
+ cx.new(|_cx| Empty)
+ }
+ },
+ )
+ .on_drop::<DraggedColumn>(move |_, _, cx| {
+ is_highlighted.write(cx, false);
+ if let Some(on_drag_end) = &on_drag_end {
+ on_drag_end(cx);
+ }
+ });
+ }
+
+ resize_divider.child(resize_handle).into_any_element()
+ })
+}
+
fn resize_spacer(width: Length) -> Div {
div().w(width).h_full()
}