Menus · button rows: Menu::buttons lays icon buttons in one row, arrows cross it, a press inside a panel stays the menu's

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-13 20:20:18 -04:00
1 parent 8fd3e431db
commit 3c35daa4d8
6 files changed
+356 -48

No files matched your search

+137
View File
@@ -0,0 +1,137 @@
use gpui::{
App, Context, FocusHandle, IntoElement, Modifiers, MouseButton, ParentElement, Render,
SharedString, Styled, TestAppContext, VisualTestContext, Window, div, point, px,
};
use super::{press, settle};
use crate::{
menus::{ContextMenu, Menu, MenuItem, SearchableMenu},
primitives::{FocusScope, IconName},
theme::Theme,
};
/// A context menu headed by clipboard buttons.
struct Clip {
root: FocusHandle,
ran: Vec<SharedString>,
}
impl Clip {
fn menu(&self, cx: &mut Context<Self>) -> Menu {
let run = |name: &'static str| {
let view = cx.entity();
move |_: &mut Window, cx: &mut App| {
view.update(cx, |clip, _| clip.ran.push(name.into()))
}
};
Menu::new()
.buttons([
MenuItem::new("Cut")
.icon(IconName::Scissors)
.on_click(run("cut")),
MenuItem::new("Copy")
.icon(IconName::Copy)
.disabled(true)
.on_click(run("copy")),
MenuItem::new("Paste")
.icon(IconName::Clipboard)
.keys("secondary-v")
.on_click(run("paste")),
])
.item(MenuItem::new("Select all").on_click(run("select all")))
}
}
impl Render for Clip {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let menu = self.menu(cx);
FocusScope::new(&self.root)
.size_full()
.child(SearchableMenu::new("find", "Find", menu.clone()))
.child(ContextMenu::new("area", menu).child(div().w(px(300.0)).h(px(200.0))))
}
}
fn clip(cx: &mut TestAppContext) -> (gpui::Entity<Clip>, &mut VisualTestContext) {
cx.update(|cx| {
Theme::init(cx);
crate::forms::bind_keys(cx);
});
let (view, cx) = cx.add_window_view(|_, cx| Clip {
root: cx.focus_handle(),
ran: Vec::new(),
});
settle(cx);
(view, cx)
}
fn ran(view: &gpui::Entity<Clip>, cx: &mut VisualTestContext) -> Vec<SharedString> {
view.read_with(cx, |clip, _| clip.ran.clone())
}
fn right_click(cx: &mut VisualTestContext) {
let at = point(px(60.0), px(120.0));
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);
}
fn press_button(selector: &'static str, cx: &mut VisualTestContext) {
let at = cx.debug_bounds(selector).expect("the button").center();
cx.simulate_mouse_move(at, None, Modifiers::none());
cx.simulate_click(at, Modifiers::none());
settle(cx);
}
#[gpui::test]
fn arrows_walk_a_button_row_past_its_disabled_button(cx: &mut TestAppContext) {
let (view, cx) = clip(cx);
right_click(cx);
press("down", cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["cut"], "down marks the row's first button");
right_click(cx);
press("down", cx);
press("right", cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["cut", "paste"], "right skips copy");
right_click(cx);
press("down", cx);
press("left", cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["cut", "paste", "paste"], "left wraps");
right_click(cx);
press("down", cx);
press("down", cx);
press("enter", cx);
assert_eq!(
ran(&view, cx).last().map(|name| name.as_ref()),
Some("select all")
);
}
#[gpui::test]
fn a_pressed_button_runs_and_a_disabled_one_does_not(cx: &mut TestAppContext) {
let (view, cx) = clip(cx);
right_click(cx);
press_button("menu-button-0-1", cx);
assert!(ran(&view, cx).is_empty(), "copy is off");
press_button("menu-button-0-2", cx);
assert_eq!(ran(&view, cx), ["paste"]);
assert!(
cx.debug_bounds("menu-button-0-0").is_none(),
"the menu closed after paste"
);
}
#[gpui::test]
fn the_filter_finds_a_button_by_its_name(cx: &mut TestAppContext) {
let (view, cx) = clip(cx);
cx.update(|window, cx| window.focus_next(cx));
press("enter", cx);
cx.simulate_input("pas");
settle(cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["paste"]);
}