Add resize handles, and the use_keyed_state API to handle hovers
This commit is contained in:
parent
7c1040bc93
commit
89a05abf78
4 changed files with 119 additions and 35 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -14756,6 +14756,7 @@ dependencies = [
|
||||||
"fs",
|
"fs",
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"itertools 0.14.0",
|
||||||
"language",
|
"language",
|
||||||
"log",
|
"log",
|
||||||
"menu",
|
"menu",
|
||||||
|
|
|
@ -23,6 +23,7 @@ feature_flags.workspace = true
|
||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
fuzzy.workspace = true
|
fuzzy.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
itertools.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
|
|
|
@ -1594,15 +1594,14 @@ impl Render for KeymapEditor {
|
||||||
.collect()
|
.collect()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.map_row(
|
.map_row(cx.processor(
|
||||||
cx.processor(|this, (row_index, row): (usize, Div), _window, cx| {
|
|this, (row_index, row): (usize, Stateful<Div>), _window, cx| {
|
||||||
let is_conflict = this.has_conflict(row_index);
|
let is_conflict = this.has_conflict(row_index);
|
||||||
let is_selected = this.selected_index == Some(row_index);
|
let is_selected = this.selected_index == Some(row_index);
|
||||||
|
|
||||||
let row_id = row_group_id(row_index);
|
let row_id = row_group_id(row_index);
|
||||||
|
|
||||||
let row = row
|
let row = row
|
||||||
.id(row_id.clone())
|
|
||||||
.on_any_mouse_down(cx.listener(
|
.on_any_mouse_down(cx.listener(
|
||||||
move |this,
|
move |this,
|
||||||
mouse_down_event: &gpui::MouseDownEvent,
|
mouse_down_event: &gpui::MouseDownEvent,
|
||||||
|
@ -1636,11 +1635,12 @@ impl Render for KeymapEditor {
|
||||||
})
|
})
|
||||||
.when(is_selected, |row| {
|
.when(is_selected, |row| {
|
||||||
row.border_color(cx.theme().colors().panel_focused_border)
|
row.border_color(cx.theme().colors().panel_focused_border)
|
||||||
|
.border_2()
|
||||||
});
|
});
|
||||||
|
|
||||||
row.into_any_element()
|
row.into_any_element()
|
||||||
}),
|
},
|
||||||
),
|
)),
|
||||||
)
|
)
|
||||||
.on_scroll_wheel(cx.listener(|this, event: &ScrollWheelEvent, _, cx| {
|
.on_scroll_wheel(cx.listener(|this, event: &ScrollWheelEvent, _, cx| {
|
||||||
// This ensures that the menu is not dismissed in cases where scroll events
|
// This ensures that the menu is not dismissed in cases where scroll events
|
||||||
|
|
|
@ -3,14 +3,16 @@ use std::{ops::Range, rc::Rc, time::Duration};
|
||||||
use editor::{EditorSettings, ShowScrollbar, scroll::ScrollbarAutoHide};
|
use editor::{EditorSettings, ShowScrollbar, scroll::ScrollbarAutoHide};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext, Axis, Context, Entity, FocusHandle, Length, ListHorizontalSizingBehavior,
|
AppContext, Axis, Context, Entity, FocusHandle, Length, ListHorizontalSizingBehavior,
|
||||||
ListSizingBehavior, MouseButton, Point, Task, UniformListScrollHandle, WeakEntity,
|
ListSizingBehavior, MouseButton, Point, Stateful, Task, UniformListScrollHandle, WeakEntity,
|
||||||
transparent_black, uniform_list,
|
transparent_black, uniform_list,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use itertools::intersperse_with;
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
use ui::{
|
use ui::{
|
||||||
ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component,
|
ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component,
|
||||||
ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator,
|
ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator,
|
||||||
InteractiveElement as _, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce,
|
InteractiveElement, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce,
|
||||||
Scrollbar, ScrollbarState, StatefulInteractiveElement as _, Styled, StyledExt as _,
|
Scrollbar, ScrollbarState, StatefulInteractiveElement as _, Styled, StyledExt as _,
|
||||||
StyledTypography, Window, div, example_group_with_title, h_flex, px, single_example, v_flex,
|
StyledTypography, Window, div, example_group_with_title, h_flex, px, single_example, v_flex,
|
||||||
};
|
};
|
||||||
|
@ -191,6 +193,67 @@ impl TableInteractionState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_resize_handles<const COLS: usize>(
|
||||||
|
&self,
|
||||||
|
column_widths: &[Length; COLS],
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> AnyElement {
|
||||||
|
let spacers = column_widths
|
||||||
|
.iter()
|
||||||
|
.map(|width| base_cell_style(Some(*width)));
|
||||||
|
|
||||||
|
let mut column_ix = 0;
|
||||||
|
let dividers = intersperse_with(spacers, || {
|
||||||
|
let hovered =
|
||||||
|
window
|
||||||
|
.use_keyed_state(("resize-hover", column_ix as u32), cx, |_window, _cx| false);
|
||||||
|
|
||||||
|
let div = div()
|
||||||
|
.relative()
|
||||||
|
.top_0()
|
||||||
|
.w_0p5()
|
||||||
|
.h_full()
|
||||||
|
.bg(cx.theme().colors().border_variant.opacity(0.5))
|
||||||
|
.when(*hovered.read(cx), |div| {
|
||||||
|
div.bg(cx.theme().colors().border_focused)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.id(("column-resize-handle", column_ix as u32))
|
||||||
|
.absolute()
|
||||||
|
.left_neg_0p5()
|
||||||
|
.w_1p5()
|
||||||
|
.h_full()
|
||||||
|
.on_hover(move |&was_hovered, _, cx| {
|
||||||
|
hovered.update(cx, |hovered, _| {
|
||||||
|
*hovered = was_hovered;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.cursor_col_resize()
|
||||||
|
.on_mouse_down(MouseButton::Left, {
|
||||||
|
let column_idx = column_ix;
|
||||||
|
move |_event, _window, _cx| {
|
||||||
|
// TODO: Emit resize event to parent
|
||||||
|
eprintln!("Start resizing column {}", column_idx);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
column_ix += 1;
|
||||||
|
div
|
||||||
|
});
|
||||||
|
|
||||||
|
div()
|
||||||
|
.id("id")
|
||||||
|
.h_flex()
|
||||||
|
.absolute()
|
||||||
|
.w_full()
|
||||||
|
.inset_0()
|
||||||
|
.children(dividers)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
fn render_vertical_scrollbar_track(
|
fn render_vertical_scrollbar_track(
|
||||||
this: &Entity<Self>,
|
this: &Entity<Self>,
|
||||||
parent: Div,
|
parent: Div,
|
||||||
|
@ -385,7 +448,7 @@ pub struct Table<const COLS: usize = 3> {
|
||||||
impl<const COLS: usize> Table<COLS> {
|
impl<const COLS: usize> Table<COLS> {
|
||||||
/// number of headers provided.
|
/// number of headers provided.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Table {
|
Self {
|
||||||
striped: false,
|
striped: false,
|
||||||
width: None,
|
width: None,
|
||||||
headers: None,
|
headers: None,
|
||||||
|
@ -459,9 +522,14 @@ impl<const COLS: usize> Table<COLS> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resizable_columns(mut self) -> Self {
|
||||||
|
self.resizable_columns = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn map_row(
|
pub fn map_row(
|
||||||
mut self,
|
mut self,
|
||||||
callback: impl Fn((usize, Div), &mut Window, &mut App) -> AnyElement + 'static,
|
callback: impl Fn((usize, Stateful<Div>), &mut Window, &mut App) -> AnyElement + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.map_row = Some(Rc::new(callback));
|
self.map_row = Some(Rc::new(callback));
|
||||||
self
|
self
|
||||||
|
@ -477,18 +545,21 @@ impl<const COLS: usize> Table<COLS> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base_cell_style(width: Option<Length>, cx: &App) -> Div {
|
fn base_cell_style(width: Option<Length>) -> Div {
|
||||||
div()
|
div()
|
||||||
.px_1p5()
|
.px_1p5()
|
||||||
.when_some(width, |this, width| this.w(width))
|
.when_some(width, |this, width| this.w(width))
|
||||||
.when(width.is_none(), |this| this.flex_1())
|
.when(width.is_none(), |this| this.flex_1())
|
||||||
.justify_start()
|
.justify_start()
|
||||||
.text_ui(cx)
|
|
||||||
.whitespace_nowrap()
|
.whitespace_nowrap()
|
||||||
.text_ellipsis()
|
.text_ellipsis()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn base_cell_style_text(width: Option<Length>, cx: &App) -> Div {
|
||||||
|
base_cell_style(width).text_ui(cx)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render_row<const COLS: usize>(
|
pub fn render_row<const COLS: usize>(
|
||||||
row_index: usize,
|
row_index: usize,
|
||||||
items: [impl IntoElement; COLS],
|
items: [impl IntoElement; COLS],
|
||||||
|
@ -507,33 +578,33 @@ pub fn render_row<const COLS: usize>(
|
||||||
.column_widths
|
.column_widths
|
||||||
.map_or([None; COLS], |widths| widths.map(Some));
|
.map_or([None; COLS], |widths| widths.map(Some));
|
||||||
|
|
||||||
let row = div().w_full().child(
|
let mut row = h_flex()
|
||||||
h_flex()
|
.h_full()
|
||||||
.id("table_row")
|
.id(("table_row", row_index))
|
||||||
.w_full()
|
.w_full()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.px_1p5()
|
|
||||||
.py_1()
|
|
||||||
.when_some(bg, |row, bg| row.bg(bg))
|
.when_some(bg, |row, bg| row.bg(bg))
|
||||||
.when(!is_striped, |row| {
|
.when(!is_striped, |row| {
|
||||||
row.border_b_1()
|
row.border_b_1()
|
||||||
.border_color(transparent_black())
|
.border_color(transparent_black())
|
||||||
.when(!is_last, |row| row.border_color(cx.theme().colors().border))
|
.when(!is_last, |row| row.border_color(cx.theme().colors().border))
|
||||||
})
|
});
|
||||||
.children(
|
|
||||||
|
row = row.children(
|
||||||
items
|
items
|
||||||
.map(IntoElement::into_any_element)
|
.map(IntoElement::into_any_element)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(column_widths)
|
.zip(column_widths)
|
||||||
.map(|(cell, width)| base_cell_style(width, cx).child(cell)),
|
.map(|(cell, width)| base_cell_style_text(width, cx).px_1p5().py_1().child(cell)),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(map_row) = table_context.map_row {
|
let row = if let Some(map_row) = table_context.map_row {
|
||||||
map_row((row_index, row), window, cx)
|
map_row((row_index, row), window, cx)
|
||||||
} else {
|
} else {
|
||||||
row.into_any_element()
|
row.into_any_element()
|
||||||
}
|
};
|
||||||
|
|
||||||
|
div().h_full().w_full().child(row).into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_header<const COLS: usize>(
|
pub fn render_header<const COLS: usize>(
|
||||||
|
@ -557,7 +628,7 @@ pub fn render_header<const COLS: usize>(
|
||||||
headers
|
headers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(column_widths)
|
.zip(column_widths)
|
||||||
.map(|(h, width)| base_cell_style(width, cx).child(h)),
|
.map(|(h, width)| base_cell_style_text(width, cx).child(h)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,7 +637,7 @@ pub struct TableRenderContext<const COLS: usize> {
|
||||||
pub striped: bool,
|
pub striped: bool,
|
||||||
pub total_row_count: usize,
|
pub total_row_count: usize,
|
||||||
pub column_widths: Option<[Length; COLS]>,
|
pub column_widths: Option<[Length; COLS]>,
|
||||||
pub map_row: Option<Rc<dyn Fn((usize, Div), &mut Window, &mut App) -> AnyElement>>,
|
pub map_row: Option<Rc<dyn Fn((usize, Stateful<Div>), &mut Window, &mut App) -> AnyElement>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const COLS: usize> TableRenderContext<COLS> {
|
impl<const COLS: usize> TableRenderContext<COLS> {
|
||||||
|
@ -660,6 +731,17 @@ impl<const COLS: usize> RenderOnce for Table<COLS> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
.when_some(
|
||||||
|
self.column_widths
|
||||||
|
.as_ref()
|
||||||
|
.zip(interaction_state.as_ref())
|
||||||
|
.filter(|_| self.resizable_columns),
|
||||||
|
|parent, (column_widths, state)| {
|
||||||
|
parent.child(state.update(cx, |state, cx| {
|
||||||
|
state.render_resize_handles(column_widths, window, cx)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
)
|
||||||
.when_some(interaction_state.as_ref(), |this, interaction_state| {
|
.when_some(interaction_state.as_ref(), |this, interaction_state| {
|
||||||
this.map(|this| {
|
this.map(|this| {
|
||||||
TableInteractionState::render_vertical_scrollbar_track(
|
TableInteractionState::render_vertical_scrollbar_track(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue