table, list: Add right clicked row style and improve selection style. (#362)
- table: Add `content_menu` to TableDelegate. <img width="644" alt="image" src="https://github.com/user-attachments/assets/4c21299f-00e8-4a61-964d-338ef7daa802">
This commit is contained in:
parent
c02cb802cb
commit
4bef681c9a
6 changed files with 179 additions and 76 deletions
|
|
@ -10,12 +10,11 @@ use serde::Deserialize;
|
|||
use ui::{
|
||||
button::{Button, ButtonStyled},
|
||||
checkbox::Checkbox,
|
||||
context_menu::ContextMenuExt,
|
||||
h_flex,
|
||||
indicator::Indicator,
|
||||
input::{InputEvent, TextInput},
|
||||
label::Label,
|
||||
popup_menu::PopupMenuExt,
|
||||
popup_menu::{PopupMenu, PopupMenuExt},
|
||||
prelude::FluentBuilder as _,
|
||||
table::{ColFixed, ColSort, Table, TableDelegate, TableEvent},
|
||||
v_flex, Selectable, Size, StyleSized as _,
|
||||
|
|
@ -341,17 +340,11 @@ impl TableDelegate for StockTableDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_tr(
|
||||
&self,
|
||||
row_ix: usize,
|
||||
_: &mut ViewContext<Table<Self>>,
|
||||
) -> gpui::Stateful<gpui::Div> {
|
||||
h_flex().id(("table-row", row_ix)).context_menu(|this, _| {
|
||||
this.menu("Size Large", Box::new(ChangeSize(Size::Large)))
|
||||
.menu("Size Medium", Box::new(ChangeSize(Size::Medium)))
|
||||
.menu("Size Small", Box::new(ChangeSize(Size::Small)))
|
||||
.menu("Size XSmall", Box::new(ChangeSize(Size::XSmall)))
|
||||
})
|
||||
fn context_menu(&self, _: usize, menu: PopupMenu, _: &WindowContext) -> PopupMenu {
|
||||
menu.menu("Size Large", Box::new(ChangeSize(Size::Large)))
|
||||
.menu("Size Medium", Box::new(ChangeSize(Size::Medium)))
|
||||
.menu("Size Small", Box::new(ChangeSize(Size::Small)))
|
||||
.menu("Size XSmall", Box::new(ChangeSize(Size::XSmall)))
|
||||
}
|
||||
|
||||
fn render_td(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ pub trait ContextMenuExt: ParentElement + Sized {
|
|||
self,
|
||||
f: impl Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static,
|
||||
) -> Self {
|
||||
self.child(ContextMenu::new("context_menu").menu(f))
|
||||
self.child(ContextMenu::new("context-menu").menu(f))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -118,23 +118,32 @@ impl Element for ContextMenu {
|
|||
let menu_view = state.menu_view.borrow().clone();
|
||||
|
||||
let (menu_element, menu_layout_id) = if *open.borrow() {
|
||||
let mut menu_element = deferred(
|
||||
anchored()
|
||||
.position(*position)
|
||||
.snap_to_window_with_margin(px(8.))
|
||||
.anchor(anchor)
|
||||
.when_some(menu_view, |this, menu| {
|
||||
// Focus the menu, so that can be handle the action.
|
||||
menu.focus_handle(cx).focus(cx);
|
||||
let has_menu_item = menu_view
|
||||
.as_ref()
|
||||
.map(|menu| !menu.read(cx).is_empty())
|
||||
.unwrap_or(false);
|
||||
|
||||
this.child(div().occlude().child(menu.clone()))
|
||||
}),
|
||||
)
|
||||
.with_priority(1)
|
||||
.into_any();
|
||||
if has_menu_item {
|
||||
let mut menu_element = deferred(
|
||||
anchored()
|
||||
.position(*position)
|
||||
.snap_to_window_with_margin(px(8.))
|
||||
.anchor(anchor)
|
||||
.when_some(menu_view, |this, menu| {
|
||||
// Focus the menu, so that can be handle the action.
|
||||
menu.focus_handle(cx).focus(cx);
|
||||
|
||||
let menu_layout_id = menu_element.request_layout(cx);
|
||||
(Some(menu_element), Some(menu_layout_id))
|
||||
this.child(div().occlude().child(menu.clone()))
|
||||
}),
|
||||
)
|
||||
.with_priority(1)
|
||||
.into_any();
|
||||
|
||||
let menu_layout_id = menu_element.request_layout(cx);
|
||||
(Some(menu_element), Some(menu_layout_id))
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
|
@ -199,9 +208,6 @@ impl Element for ContextMenu {
|
|||
&& event.button == MouseButton::Right
|
||||
&& bounds.contains(&event.position)
|
||||
{
|
||||
cx.prevent_default();
|
||||
cx.stop_propagation();
|
||||
|
||||
*position.borrow_mut() = event.position;
|
||||
*open.borrow_mut() = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use gpui::{
|
|||
ListSizingBehavior, MouseButton, ParentElement, Render, SharedString, Styled, Task,
|
||||
UniformListScrollHandle, View, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use gpui::{deferred, px};
|
||||
use smol::Timer;
|
||||
|
||||
actions!(list, [Cancel, Confirm, SelectPrev, SelectNext]);
|
||||
|
|
@ -92,6 +93,7 @@ pub struct List<D: ListDelegate> {
|
|||
|
||||
pub(crate) size: Size,
|
||||
selected_index: Option<usize>,
|
||||
right_clicked_index: Option<usize>,
|
||||
_search_task: Task<()>,
|
||||
}
|
||||
|
||||
|
|
@ -117,6 +119,7 @@ where
|
|||
query_input: Some(query_input),
|
||||
last_query: None,
|
||||
selected_index: None,
|
||||
right_clicked_index: None,
|
||||
vertical_scroll_handle: UniformListScrollHandle::new(),
|
||||
scrollbar_state: Rc::new(Cell::new(ScrollbarState::new())),
|
||||
max_height: None,
|
||||
|
|
@ -303,6 +306,57 @@ where
|
|||
self.scroll_to_selected_item(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_list_item(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.id("list-item")
|
||||
.w_full()
|
||||
.relative()
|
||||
.children(self.delegate.render_item(ix, cx))
|
||||
.when_some(self.selected_index, |this, selected_index| {
|
||||
this.when(ix == selected_index, |this| {
|
||||
this.child(deferred(
|
||||
div()
|
||||
.absolute()
|
||||
.top(px(-1.))
|
||||
.left(px(-1.))
|
||||
.right(px(-1.))
|
||||
.bottom(px(-1.))
|
||||
.bg(cx.theme().list_active)
|
||||
.border_1()
|
||||
.border_color(cx.theme().list_active_border),
|
||||
))
|
||||
})
|
||||
})
|
||||
.when(self.right_clicked_index == Some(ix), |this| {
|
||||
this.child(deferred(
|
||||
div()
|
||||
.absolute()
|
||||
.top(px(-1.))
|
||||
.left(px(-1.))
|
||||
.right(px(-1.))
|
||||
.bottom(px(-1.))
|
||||
.border_1()
|
||||
.border_color(cx.theme().list_active_border),
|
||||
))
|
||||
})
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |this, _, cx| {
|
||||
cx.stop_propagation();
|
||||
this.right_clicked_index = None;
|
||||
this.selected_index = Some(ix);
|
||||
this.on_action_confirm(&Confirm, cx);
|
||||
}),
|
||||
)
|
||||
.on_mouse_down(
|
||||
MouseButton::Right,
|
||||
cx.listener(move |this, _, cx| {
|
||||
this.right_clicked_index = Some(ix);
|
||||
cx.notify();
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> FocusableView for List<D>
|
||||
|
|
@ -332,8 +386,6 @@ where
|
|||
ListSizingBehavior::Auto
|
||||
};
|
||||
|
||||
let selected_bg = cx.theme().list_active;
|
||||
|
||||
let inital_view = if let Some(input) = &self.query_input {
|
||||
if input.read(cx).text().is_empty() {
|
||||
self.delegate().render_initial(cx)
|
||||
|
|
@ -385,31 +437,7 @@ where
|
|||
uniform_list(view, "uniform-list", items_count, {
|
||||
move |list, visible_range, cx| {
|
||||
visible_range
|
||||
.map(|ix| {
|
||||
div()
|
||||
.id("list-item")
|
||||
.w_full()
|
||||
.children(list.delegate.render_item(ix, cx))
|
||||
.when_some(
|
||||
list.selected_index,
|
||||
|this, selected_index| {
|
||||
this.when(
|
||||
ix == selected_index,
|
||||
|this| this.bg(selected_bg),
|
||||
)
|
||||
},
|
||||
)
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |this, _, cx| {
|
||||
cx.stop_propagation();
|
||||
this.selected_index = Some(ix);
|
||||
this.on_action_confirm(
|
||||
&Confirm, cx,
|
||||
);
|
||||
}),
|
||||
)
|
||||
})
|
||||
.map(|ix| list.render_list_item(ix, cx))
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
})
|
||||
|
|
@ -423,5 +451,12 @@ where
|
|||
)
|
||||
}
|
||||
})
|
||||
// Click out to cancel right clicked row
|
||||
.when(self.right_clicked_index.is_some(), |this| {
|
||||
this.on_mouse_down_out(cx.listener(|this, _, cx| {
|
||||
this.right_clicked_index = None;
|
||||
cx.notify();
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -334,6 +334,10 @@ impl PopupMenu {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.menu_items.is_empty()
|
||||
}
|
||||
|
||||
fn clickable_menu_items(&self) -> impl Iterator<Item = (usize, &PopupMenuItem)> {
|
||||
self.menu_items
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
use std::{cell::Cell, ops::Range, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
context_menu::ContextMenuExt,
|
||||
h_flex,
|
||||
popup_menu::PopupMenu,
|
||||
scroll::{ScrollableAxis, ScrollableMask, Scrollbar, ScrollbarState},
|
||||
theme::ActiveTheme,
|
||||
v_flex, Icon, IconName, Sizable, Size, StyleSized as _,
|
||||
};
|
||||
use gpui::{
|
||||
actions, canvas, div, prelude::FluentBuilder, px, uniform_list, AppContext, Bounds, Div,
|
||||
DragMoveEvent, Edges, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
|
||||
actions, canvas, deferred, div, prelude::FluentBuilder, px, uniform_list, AppContext, Bounds,
|
||||
Div, DragMoveEvent, Edges, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
|
||||
InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, Render,
|
||||
ScrollHandle, SharedString, Stateful, StatefulInteractiveElement as _, Styled,
|
||||
UniformListScrollHandle, ViewContext, VisualContext as _, WindowContext,
|
||||
|
|
@ -119,6 +121,7 @@ pub struct Table<D: TableDelegate> {
|
|||
|
||||
selection_state: SelectionState,
|
||||
selected_row: Option<usize>,
|
||||
right_clicked_row: Option<usize>,
|
||||
selected_col: Option<usize>,
|
||||
|
||||
/// The column index that is being resized.
|
||||
|
|
@ -190,6 +193,11 @@ pub trait TableDelegate: Sized + 'static {
|
|||
h_flex().id(("table-row", row_ix))
|
||||
}
|
||||
|
||||
/// Render the context menu for the row at the given row index.
|
||||
fn context_menu(&self, row_ix: usize, menu: PopupMenu, cx: &WindowContext) -> PopupMenu {
|
||||
menu
|
||||
}
|
||||
|
||||
/// Render cell at the given row and column.
|
||||
fn render_td(
|
||||
&self,
|
||||
|
|
@ -265,6 +273,7 @@ where
|
|||
horizontal_scrollbar_state: Rc::new(Cell::new(ScrollbarState::new())),
|
||||
selection_state: SelectionState::Row,
|
||||
selected_row: None,
|
||||
right_clicked_row: None,
|
||||
selected_col: None,
|
||||
resizing_col: None,
|
||||
bounds: Bounds::default(),
|
||||
|
|
@ -335,6 +344,7 @@ where
|
|||
|
||||
fn set_selected_row(&mut self, row_ix: usize, cx: &mut ViewContext<Self>) {
|
||||
self.selection_state = SelectionState::Row;
|
||||
self.right_clicked_row = None;
|
||||
self.selected_row = Some(row_ix);
|
||||
if let Some(row_ix) = self.selected_row {
|
||||
self.vertical_scroll_handle.scroll_to_item(row_ix);
|
||||
|
|
@ -356,8 +366,17 @@ where
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
fn on_row_click(&mut self, row_ix: usize, cx: &mut ViewContext<Self>) {
|
||||
self.set_selected_row(row_ix, cx)
|
||||
fn on_row_click(
|
||||
&mut self,
|
||||
mouse_button: MouseButton,
|
||||
row_ix: usize,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if mouse_button == MouseButton::Right {
|
||||
self.right_clicked_row = Some(row_ix);
|
||||
} else {
|
||||
self.set_selected_row(row_ix, cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_col_head_click(&mut self, col_ix: usize, cx: &mut ViewContext<Self>) {
|
||||
|
|
@ -938,10 +957,14 @@ where
|
|||
let horizontal_scroll_handle = self.horizontal_scroll_handle.clone();
|
||||
let is_stripe_row = self.stripe && row_ix % 2 != 0;
|
||||
let is_selected = self.selected_row == Some(row_ix);
|
||||
let view = cx.view().clone();
|
||||
|
||||
if row_ix < rows_count {
|
||||
self.delegate
|
||||
.render_tr(row_ix, cx)
|
||||
.context_menu(move |this, cx: &mut ViewContext<PopupMenu>| {
|
||||
view.read(cx).delegate.context_menu(row_ix, this, cx)
|
||||
})
|
||||
.w_full()
|
||||
.h(self.size.table_row_height())
|
||||
.when(row_ix > 0, |this| {
|
||||
|
|
@ -949,7 +972,7 @@ where
|
|||
})
|
||||
.when(is_stripe_row, |this| this.bg(cx.theme().table_even))
|
||||
.hover(|this| {
|
||||
if is_selected {
|
||||
if is_selected || self.right_clicked_row == Some(row_ix) {
|
||||
this
|
||||
} else {
|
||||
this.bg(cx.theme().table_hover)
|
||||
|
|
@ -994,13 +1017,44 @@ where
|
|||
.when_some(self.selected_row, |this, _| {
|
||||
this.when(
|
||||
is_selected && self.selection_state == SelectionState::Row,
|
||||
|this| this.bg(cx.theme().table_active),
|
||||
|this| {
|
||||
this.child(deferred(
|
||||
div()
|
||||
.top(px(-1.))
|
||||
.left(px(-1.))
|
||||
.right(px(-1.))
|
||||
.bottom_0()
|
||||
.absolute()
|
||||
.bg(cx.theme().table_active)
|
||||
.border_1()
|
||||
.border_color(cx.theme().table_active_border),
|
||||
))
|
||||
},
|
||||
)
|
||||
})
|
||||
// Row right click row style
|
||||
.when(self.right_clicked_row == Some(row_ix), |this| {
|
||||
this.child(deferred(
|
||||
div()
|
||||
.top(px(-1.))
|
||||
.left(px(-1.))
|
||||
.right(px(-1.))
|
||||
.bottom_0()
|
||||
.absolute()
|
||||
.border_1()
|
||||
.border_color(cx.theme().selection),
|
||||
))
|
||||
})
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |this, _, cx| {
|
||||
this.on_row_click(row_ix, cx);
|
||||
this.on_row_click(MouseButton::Left, row_ix, cx);
|
||||
}),
|
||||
)
|
||||
.on_mouse_down(
|
||||
MouseButton::Right,
|
||||
cx.listener(move |this, _, cx| {
|
||||
this.on_row_click(MouseButton::Right, row_ix, cx);
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
|
|
@ -1170,5 +1224,12 @@ where
|
|||
.when(rows_count > 0, |this| {
|
||||
this.children(self.render_scrollbar(cx))
|
||||
})
|
||||
// Click out to cancel right clicked row
|
||||
.when(self.right_clicked_row.is_some(), |this| {
|
||||
this.on_mouse_down_out(cx.listener(|this, _, cx| {
|
||||
this.right_clicked_row = None;
|
||||
cx.notify();
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,7 +177,6 @@ struct Colors {
|
|||
pub tab_bar: Hsla,
|
||||
pub list: Hsla,
|
||||
pub list_even: Hsla,
|
||||
pub list_active: Hsla,
|
||||
pub list_head: Hsla,
|
||||
pub link: Hsla,
|
||||
pub drop_target: Hsla,
|
||||
|
|
@ -220,7 +219,6 @@ impl Colors {
|
|||
tab_bar: hsl(240.0, 4.8, 95.9),
|
||||
list: hsl(0.0, 0.0, 100.),
|
||||
list_even: hsl(240.0, 5.0, 96.0),
|
||||
list_active: hsl(240.0, 7., 88.0).opacity(0.75),
|
||||
list_head: hsl(0.0, 0.0, 100.),
|
||||
link: hsl(221.0, 83.0, 53.0),
|
||||
drop_target: hsl(235.0, 30., 44.0).opacity(0.25),
|
||||
|
|
@ -263,7 +261,6 @@ impl Colors {
|
|||
tab_bar: hsl(299.0, 0., 5.5),
|
||||
list: hsl(0.0, 0.0, 8.0),
|
||||
list_even: hsl(240.0, 3.7, 10.0),
|
||||
list_active: hsl(240.0, 3.7, 17.0),
|
||||
list_head: hsl(0.0, 0.0, 8.0),
|
||||
link: hsl(221.0, 83.0, 53.0),
|
||||
drop_target: hsl(235.0, 30., 44.0).opacity(0.1),
|
||||
|
|
@ -326,6 +323,7 @@ pub struct Theme {
|
|||
pub list_even: Hsla,
|
||||
pub list_head: Hsla,
|
||||
pub list_active: Hsla,
|
||||
pub list_active_border: Hsla,
|
||||
pub list_hover: Hsla,
|
||||
pub table: Hsla,
|
||||
pub table_even: Hsla,
|
||||
|
|
@ -333,6 +331,7 @@ pub struct Theme {
|
|||
pub table_head_foreground: Hsla,
|
||||
pub table_row_border: Hsla,
|
||||
pub table_active: Hsla,
|
||||
pub table_active_border: Hsla,
|
||||
pub table_hover: Hsla,
|
||||
pub link: Hsla,
|
||||
pub link_hover: Hsla,
|
||||
|
|
@ -378,7 +377,7 @@ impl Theme {
|
|||
self.border = self.border.apply(mask_color);
|
||||
self.input = self.input.apply(mask_color);
|
||||
self.ring = self.ring.apply(mask_color);
|
||||
self.selection = self.selection.apply(mask_color);
|
||||
// self.selection = self.selection.apply(mask_color);
|
||||
self.scrollbar = self.scrollbar.apply(mask_color);
|
||||
self.scrollbar_thumb = self.scrollbar_thumb.apply(mask_color);
|
||||
self.panel = self.panel.apply(mask_color);
|
||||
|
|
@ -395,13 +394,16 @@ impl Theme {
|
|||
self.list = self.list.apply(mask_color);
|
||||
self.list_even = self.list_even.apply(mask_color);
|
||||
self.list_head = self.list_head.apply(mask_color);
|
||||
self.list_active = self.list_active.apply(mask_color);
|
||||
// self.list_active = self.list_active.apply(mask_color);
|
||||
// self.list_active_border = self.list_active_border.apply(mask_color);
|
||||
self.list_hover = self.list_hover.apply(mask_color);
|
||||
self.table = self.table.apply(mask_color);
|
||||
self.table_even = self.table_even.apply(mask_color);
|
||||
self.table_active = self.table_active.apply(mask_color);
|
||||
// self.table_active = self.table_active.apply(mask_color);
|
||||
// self.table_active_border = self.table_active_border.apply(mask_color);
|
||||
self.table_hover = self.table_hover.apply(mask_color);
|
||||
self.table_row_border = self.table_row_border.apply(mask_color);
|
||||
self.table_head = self.table_head.apply(mask_color);
|
||||
self.table_head_foreground = self.table_head_foreground.apply(mask_color);
|
||||
self.link = self.link.apply(mask_color);
|
||||
self.link_hover = self.link_hover.apply(mask_color);
|
||||
|
|
@ -472,13 +474,15 @@ impl From<Colors> for Theme {
|
|||
list: colors.list,
|
||||
list_even: colors.list_even,
|
||||
list_head: colors.list_head,
|
||||
list_active: colors.list_active,
|
||||
list_hover: colors.list_active.opacity(0.6),
|
||||
list_active: colors.selection.opacity(0.2),
|
||||
list_active_border: colors.selection,
|
||||
list_hover: colors.selection.opacity(0.2),
|
||||
table_head: colors.list_head,
|
||||
table: colors.list,
|
||||
table_even: colors.list_even,
|
||||
table_active: colors.list_active,
|
||||
table_hover: colors.list_active.opacity(0.8),
|
||||
table_active: colors.selection.opacity(0.2),
|
||||
table_active_border: colors.selection,
|
||||
table_hover: colors.selection.opacity(0.2),
|
||||
table_row_border: colors.border.opacity(0.5),
|
||||
table_head_foreground: colors.foreground.opacity(0.7),
|
||||
link: colors.link,
|
||||
|
|
|
|||
Loading…
Reference in a new issue