diff --git a/src/menus/hosts.rs b/src/menus/hosts.rs index ca5940a..12d702a 100644 --- a/src/menus/hosts.rs +++ b/src/menus/hosts.rs @@ -2,8 +2,8 @@ use std::rc::Rc; use gpui::{ AnyElement, App, ClickEvent, Div, ElementId, Entity, InteractiveElement, IntoElement, - MouseButton, ParentElement, RenderOnce, SharedString, Stateful, Styled, Window, div, - prelude::*, + MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString, Stateful, Styled, Window, + div, prelude::*, }; use smallvec::SmallVec; @@ -226,6 +226,7 @@ impl RenderOnce for SplitButton { pub struct ContextMenu { id: ElementId, menu: Menu, + request: Option)>>, children: SmallVec<[AnyElement; 2]>, } @@ -234,9 +235,16 @@ impl ContextMenu { Self { id: id.into(), menu, + request: None, children: SmallVec::new(), } } + + /// Opens on the owner's numbered request, not on a right click: for rows that come after the press. + pub fn manual(mut self, request: Option<(u64, Point)>) -> Self { + self.request = Some(request); + self + } } impl ParentElement for ContextMenu { @@ -248,14 +256,24 @@ impl ParentElement for ContextMenu { impl RenderOnce for ContextMenu { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { let state = state(&self.id, window, cx); + if let Some(Some((number, at))) = self.request { + let asked = window.use_keyed_state((self.id.clone(), "asked"), cx, |_, _| None); + if *asked.read(cx) != Some(number) { + asked.update(cx, |asked, _| *asked = Some(number)); + log::info!("context menu {:?}: asked at {at:?}", self.id); + Open::show(&state, &self.menu, Spot::At(at), false, cx); + } + } let (open, shut, menu) = (state.clone(), state.clone(), self.menu.clone()); div() .id(self.id.clone()) .relative() - .on_mouse_down(MouseButton::Right, move |event, window, cx| { - window.prevent_default(); - log::info!("context menu: at {:?}", event.position); - Open::show(&open, &menu, Spot::At(event.position), false, cx); + .when(self.request.is_none(), |host| { + host.on_mouse_down(MouseButton::Right, move |event, window, cx| { + window.prevent_default(); + log::info!("context menu: at {:?}", event.position); + Open::show(&open, &menu, Spot::At(event.position), false, cx); + }) }) .on_mouse_down(MouseButton::Left, move |_, window, cx| { if shut.read(cx).is_open() { diff --git a/src/menus/tests/mod.rs b/src/menus/tests/mod.rs index 608b61d..3450b9c 100644 --- a/src/menus/tests/mod.rs +++ b/src/menus/tests/mod.rs @@ -18,6 +18,7 @@ struct Desk { view_rows: bool, enters_above: usize, shift: bool, + asked: Option<(u64, gpui::Point)>, } impl Desk { @@ -83,7 +84,15 @@ impl Render for Desk { ContextMenu::new("area", menu.clone()) .child(div().w(px(200.0)).h(px(120.0))), ) - .child(SearchableMenu::new("find", "Find", menu)), + .child(SearchableMenu::new("find", "Find", menu.clone())) + .child( + ContextMenu::new("late", menu).manual(self.asked).child( + div() + .debug_selector(|| "late".into()) + .w(px(200.0)) + .h(px(40.0)), + ), + ), ) } } @@ -117,6 +126,7 @@ fn desk(cx: &mut TestAppContext) -> (gpui::Entity, &mut VisualTestContext) view_rows: true, enters_above: 0, shift: false, + asked: None, }); settle(cx); (view, cx) @@ -209,6 +219,27 @@ fn a_right_click_opens_the_context_menu_where_it_lands(cx: &mut TestAppContext) assert_eq!(ran(&view, cx), ["cut"]); } +#[gpui::test] +fn a_manual_context_menu_opens_only_from_its_owner(cx: &mut TestAppContext) { + let (view, cx) = desk(cx); + let at = cx.debug_bounds("late").expect("the host").center(); + cx.simulate_mouse_move(at, None, Modifiers::none()); + cx.simulate_mouse_down(at, MouseButton::Right, Modifiers::none()); + cx.simulate_mouse_up(at, MouseButton::Right, Modifiers::none()); + settle(cx); + press("down", cx); + press("enter", cx); + assert!(ran(&view, cx).is_empty(), "a right click opens nothing"); + view.update(cx, |desk, cx| { + desk.asked = Some((1, point(px(60.0), px(80.0)))); + cx.notify(); + }); + settle(cx); + press("down", cx); + press("enter", cx); + assert_eq!(ran(&view, cx), ["cut"]); +} + #[gpui::test] fn the_trigger_toggles_and_a_press_outside_closes(cx: &mut TestAppContext) { let (view, cx) = desk(cx);