-
Notifications
You must be signed in to change notification settings - Fork 9
Guide Recipes ContextMenu
This recipe shows how to attach a right-click context menu to a row using the v0.3 headless API.
Goal:
- Right-click opens a menu.
- Clicking outside closes it.
- Menu items dispatch actions.
-
context_menu_trigger— headless factory that wires a right-click handler. Lives incrates/yororen-ui-core/src/headless/context_menu_trigger.rs. Exposes.on_show(point, window, cx)— your callback opens the popover.context_menu_trigger(id, cx)takes 2 args. -
popover— headless composite that anchors content next to its trigger. Lives incrates/yororen-ui-core/src/headless/popover.rs. Owns anEntity<PopoverState>withopen()/close()/toggle(). The renderer floats the content beside the trigger whenstate.is_open(). -
dropdown_menu/menu— body variants.menu(headless body-only shell) plusDropdownItem::Item(...)/DropdownItem::Separatorare the menu primitives; thedropdown_menufactory wraps a trigger + content. See Component-DropdownMenu.
The clean shape is a popover whose trigger is a
context_menu_trigger-wrapped row, and whose content is a
dropdown_menu driven by an Entity<DropdownMenuState>. The
right-click handler opens the popover; the dropdown's
set_on_select callback dispatches the action.
use gpui::{Context, IntoElement, ParentElement, Render, SharedString, Window, div};
use yororen_ui::headless::context_menu_trigger::context_menu_trigger;
use yororen_ui::headless::dropdown_menu::{
DropdownItem, DropdownMenuItem, DropdownMenuState, dropdown_menu,
};
use yororen_ui::headless::list_item::list_item;
use yororen_ui::headless::popover::{PopoverState, popover};
pub struct RowWithMenu {
pub row_label: String,
pub popover_state: gpui::Entity<PopoverState>,
pub menu_state: gpui::Entity<DropdownMenuState>,
}
impl RowWithMenu {
pub fn new(cx: &mut gpui::App) -> Self {
Self {
row_label: "Row".into(),
popover_state: PopoverState::new(cx),
menu_state: DropdownMenuState::new(cx),
}
}
}
impl Render for RowWithMenu {
fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let popover_state = self.popover_state.clone();
let menu_state = self.menu_state.clone();
let popover_for_open = popover_state.clone();
let popover_for_close = popover_state.clone();
// Wire the menu items + on_select once per render (cheap;
// `DropdownMenuState` stores the callbacks for the renderer's
// click handlers to fire).
menu_state.update(cx, |s, _cx| {
s.set_items(vec![
DropdownItem::Item(DropdownMenuItem::new("open", "Open")),
DropdownItem::Item(DropdownMenuItem::new("rename", "Rename")),
DropdownItem::Separator,
DropdownItem::Item(DropdownMenuItem::new("delete", "Delete")),
]);
s.set_on_select(move |id: SharedString, _w, cx| {
match id.as_str() {
"open" => { /* … */ }
"rename" => { /* … */ }
"delete" => { /* … */ }
_ => {}
}
// Close the popover after pick.
popover_for_close.update(cx, |s, _cx| s.close());
});
});
// The trigger: a list_item wrapped in context_menu_trigger.
// Right-click fires on_show → we open the popover.
let row = list_item("row-content", self.row_label.clone(), cx)
.selected(false)
.render(cx);
let trigger = context_menu_trigger("row-trigger", &mut *cx)
.on_show({
let popover_state = popover_state.clone();
move |_point, _w, cx| {
popover_state.update(cx, |s, _cx| s.toggle());
}
})
.apply(row);
// The body: a dropdown_menu driven by menu_state.
let content = dropdown_menu("row-menu", menu_state.clone()).render(cx);
// The popover composes trigger + content. The renderer
// floats `content` next to `trigger` when popover_state
// is open.
popover("row-menu", popover_for_open)
.trigger(trigger.into_any_element())
.content(content)
.render(cx)
}
}-
context_menu_trigger(id, cx)returns aContextMenuTriggerPropsthat supports.on_show(point, window, cx)and.apply(div)— theapplypath wires the right-click mouse handler. There's no.render(cx)because the visual is whatever div you pass toapply(here, alist_item). - Right-click position is delivered to
on_showvia&gpui::Point<gpui::Pixels>— useful if you want to anchor the popover at the cursor rather than at the trigger bounds. The default popover placement isBottomStartof the trigger; for cursor-anchored menus, setPopoverState::set_placementto your preferred value. - The popover's
dismiss_on_outside_clickanddismiss_on_escapeare bothtrueby default — clicking outside or pressing Escape closes the menu without extra wiring. -
set_on_selectisFn(SharedString, &mut Window, &mut App)— the id comes in as aSharedString, not&str. Useid.as_str()to compare against string literals. -
DropdownItem::Item(DropdownMenuItem::new(id, label))is the item variant.DropdownItem::Separatordraws a divider. There is no separatemenu_itemfactory — items are constructed directly and passed viaset_items(Vec<DropdownItem>). -
popover.trigger(t: AnyElement)requires anAnyElement.context_menu_trigger.apply(div)returnsStateful<Div>, so call.into_any_element()to convert.
If you don't need right-click and want a click-triggered menu
(think: "⋯" button on a row), reach for dropdown_menu
directly with a button trigger:
use gpui::{Context, IntoElement, ParentElement, Render, SharedString, Window, div};
use yororen_ui::headless::button::button;
use yororen_ui::headless::dropdown_menu::{
DropdownItem, DropdownMenuItem, DropdownMenuState, dropdown_menu,
};
pub struct RowWithDropdown {
pub row_label: String,
pub menu_state: gpui::Entity<DropdownMenuState>,
}
impl RowWithDropdown {
pub fn new(cx: &mut gpui::App) -> Self {
Self {
row_label: "Row".into(),
menu_state: DropdownMenuState::new(cx),
}
}
}
impl Render for RowWithDropdown {
fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let menu_state = self.menu_state.clone();
menu_state.update(cx, |s, _cx| {
s.set_items(vec![
DropdownItem::Item(DropdownMenuItem::new("open", "Open")),
DropdownItem::Item(DropdownMenuItem::new("rename", "Rename")),
DropdownItem::Item(DropdownMenuItem::new("delete", "Delete")),
]);
s.set_on_select(move |id: SharedString, _w, _cx| {
// … handle the pick …
});
});
let trigger = button("row-trigger", cx)
.render(cx)
.child("⋯");
dropdown_menu("row-dropdown", menu_state.clone())
.trigger(trigger.into_any_element())
.render(cx)
}
}Use popover + context_menu_trigger when the trigger is
right-click; use dropdown_menu when the trigger is a button.
- Guide-Composing-UI §5 — the popover + menu pattern at the top level
- Component-ContextMenuTrigger — full API reference for the headless factory
-
Component-Popover — full API reference
for
popoverandPopoverState -
Component-DropdownMenu — full API
reference for
dropdown_menu
Yororen UI v0.3.0 · repository · Apache-2.0 · This wiki documents Yororen UI v0.3.0.
This wiki documents Yororen UI v0.3.0 — the headless-core, swappable-renderer build.