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:
1 parent
8fd3e431db
commit
3c35daa4d8
6 files changed
+356
-48
No files matched your search
@@ -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"]);
|
||||
}
|
||||
Reference in new issue
Block a user