Menus · ContextMenu manual: opens on the owner's numbered request at a point, not on a right click, for rows that come after the press

This commit is contained in:
ZacharyZhang-NY committed 2026-10-04 11:04:32 -04:00
1 parent c0f17eeb48
commit 967d1e4705
2 files changed
+56 -7

No files matched your search

+24 -6
View File
@@ -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<Option<(u64, Point<Pixels>)>>,
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<Pixels>)>) -> 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() {
+32 -1
View File
@@ -18,6 +18,7 @@ struct Desk {
view_rows: bool,
enters_above: usize,
shift: bool,
asked: Option<(u64, gpui::Point<gpui::Pixels>)>,
}
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<Desk>, &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);