-
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.
-
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(). -
menu— headless body-only shell that renders one row perDropdownItem. Lives incrates/yororen-ui-core/src/headless/menu.rs. The renderer's click handler firesstate.on_select. -
Right-click on a
div— wire.on_mouse_down(MouseButton::Right, …)on the row. There is nocontext_menu_triggerprimitive in v0.3 (it was removed because it added no behaviour over a plaindiv().on_mouse_down(...)); do it directly on the row.
The clean shape is a popover whose trigger is a row that
captures right-click, and whose content is a menu driven
by an Entity<MenuState>. The right-click handler opens the
popover; the menu's set_on_select callback dispatches the
action and closes the popover.
use gpui::{Context, IntoElement, MouseButton, ParentElement, Render, SharedString, Window, div};
use yororen_ui::headless::button::button;
use yororen_ui::headless::dropdown_menu::{
DropdownItem, DropdownMenuItem,
};
use yororen_ui::headless::list_item::list_item;
use yororen_ui::headless::menu::{menu, MenuState};
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<MenuState>,
}
impl RowWithMenu {
pub fn new(cx: &mut gpui::App) -> Self {
Self {
row_label: "Row".into(),
popover_state: PopoverState::new(cx),
menu_state: MenuState::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; `MenuState` 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 row that captures right-click. There is
// no `context_menu_trigger` factory — wire the
// mouse-down handler directly on the row.
let popover_state_for_open = popover_state.clone();
let row = list_item("row-content", self.row_label.clone(), cx)
.selected(false)
.render(cx)
.on_mouse_down(MouseButton::Right, move |_ev, _window, cx| {
popover_state_for_open.update(cx, |s, _cx| s.toggle());
});
// The body: a menu driven by menu_state.
let content = menu("row-menu", menu_state.clone()).render(cx);
// The popover composes trigger + content. The renderer
// floats `content` next to `row` when popover_state
// is open.
popover("row-menu", popover_for_open)
.trigger(row.into_any_element())
.content(content)
.render(cx)
}
}- Right-click is captured with
.on_mouse_down(MouseButton::Right, …)on the row. The closure receives&MouseDownEvent, which carries the screenposition: Point<Pixels>if you want to anchor the popover at the cursor rather than at the trigger bounds; callPopoverState::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>).
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 + right-click when the trigger is a list row;
use dropdown_menu when the trigger is a button.
- Guide-Composing-UI §5 — the popover + menu pattern at the top level
-
Component-Popover — full API reference
for
popoverandPopoverState -
Component-Menu — full API reference
for
menuandMenuState -
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.