diff --git a/crates/story/src/table_story.rs b/crates/story/src/table_story.rs
index 7244da03..cb7afa1f 100644
--- a/crates/story/src/table_story.rs
+++ b/crates/story/src/table_story.rs
@@ -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
>,
- ) -> gpui::Stateful {
- 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(
diff --git a/crates/ui/src/context_menu.rs b/crates/ui/src/context_menu.rs
index 2d968760..65c12f07 100644
--- a/crates/ui/src/context_menu.rs
+++ b/crates/ui/src/context_menu.rs
@@ -16,7 +16,7 @@ pub trait ContextMenuExt: ParentElement + Sized {
self,
f: impl Fn(PopupMenu, &mut ViewContext) -> 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;
diff --git a/crates/ui/src/list/list.rs b/crates/ui/src/list/list.rs
index 910a176e..31b4aae4 100644
--- a/crates/ui/src/list/list.rs
+++ b/crates/ui/src/list/list.rs
@@ -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 {
pub(crate) size: Size,
selected_index: Option,
+ right_clicked_index: Option,
_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) -> 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 FocusableView for List
@@ -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::>()
}
})
@@ -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();
+ }))
+ })
}
}
diff --git a/crates/ui/src/popup_menu.rs b/crates/ui/src/popup_menu.rs
index 8dd1eea8..cf378440 100644
--- a/crates/ui/src/popup_menu.rs
+++ b/crates/ui/src/popup_menu.rs
@@ -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- {
self.menu_items
.iter()
diff --git a/crates/ui/src/table.rs b/crates/ui/src/table.rs
index 4859c08f..2eb6b1b4 100644
--- a/crates/ui/src/table.rs
+++ b/crates/ui/src/table.rs
@@ -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 {
selection_state: SelectionState,
selected_row: Option,
+ right_clicked_row: Option,
selected_col: Option,
/// 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.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.set_selected_row(row_ix, cx)
+ fn on_row_click(
+ &mut self,
+ mouse_button: MouseButton,
+ row_ix: usize,
+ cx: &mut ViewContext,
+ ) {
+ 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) {
@@ -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| {
+ 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();
+ }))
+ })
}
}
diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs
index d86cf062..71d8a19d 100644
--- a/crates/ui/src/theme.rs
+++ b/crates/ui/src/theme.rs
@@ -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 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,