HelpPanel lists its articles in lists::SelectableList: the list is one stop whose cursor scrolls into view, and Enter or a double press opens an article, focus going to All articles as before. SelectableList now activates on Enter's release, armed by its own press, as the palette and menus pick: an activation that moves focus left the release to press what took it, here the way back. ContactSupport cuts a long address in the middle and puts a Copy beside it. The lists tests move into a folder, the release tests on their own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
3.0 KiB
Rust
95 lines
3.0 KiB
Rust
use gpui::{
|
|
AppContext, Context, Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement,
|
|
Render, TestAppContext, Window, div,
|
|
};
|
|
|
|
use super::{enter, settle, setup};
|
|
use crate::{
|
|
buttons::Button,
|
|
forms::{Enter, Input, TextInput},
|
|
lists::{ListItem, SelectableList},
|
|
};
|
|
|
|
/// A list whose open hands focus to a button beside it, and what each heard.
|
|
struct Handoff {
|
|
button: FocusHandle,
|
|
heard: Vec<&'static str>,
|
|
}
|
|
|
|
impl Render for Handoff {
|
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
let (to, opened, clicked) = (self.button.clone(), cx.entity(), cx.entity());
|
|
div()
|
|
.child(
|
|
SelectableList::new("handoff")
|
|
.row("a", ListItem::new("handoff-a", "A"))
|
|
.on_activate(move |_, window, cx| {
|
|
window.focus(&to);
|
|
opened.update(cx, |view, _| view.heard.push("open"));
|
|
}),
|
|
)
|
|
.child(
|
|
Button::new("after", "After")
|
|
.focus_handle(&self.button)
|
|
.on_click(move |_, _, cx| {
|
|
clicked.update(cx, |view, _| view.heard.push("click"))
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Enter opens on its release, so an open that moves focus to a button leaves the button unpressed.
|
|
#[gpui::test]
|
|
fn an_open_that_moves_focus_leaves_the_release_alone(cx: &mut TestAppContext) {
|
|
setup(cx);
|
|
let (view, cx) = cx.add_window_view(|_, cx| Handoff {
|
|
button: cx.focus_handle().tab_stop(true),
|
|
heard: Vec::new(),
|
|
});
|
|
settle(cx);
|
|
cx.update(|window, _| window.focus_next());
|
|
settle(cx);
|
|
enter(cx);
|
|
settle(cx);
|
|
assert_eq!(view.read_with(cx, |view, _| view.heard.clone()), ["open"]);
|
|
}
|
|
|
|
/// A field whose Enter moves focus on to a list, and what the list opened.
|
|
struct Relay {
|
|
field: Entity<TextInput>,
|
|
heard: Vec<&'static str>,
|
|
}
|
|
|
|
impl Render for Relay {
|
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
let opened = cx.entity();
|
|
div()
|
|
.capture_action(|_: &Enter, window, _| window.focus_next())
|
|
.child(Input::new(&self.field))
|
|
.child(
|
|
SelectableList::new("relay")
|
|
.row("a", ListItem::new("relay-a", "A"))
|
|
.on_activate(move |_, _, cx| {
|
|
opened.update(cx, |view, _| view.heard.push("open"))
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// A release whose press fell elsewhere opens nothing.
|
|
#[gpui::test]
|
|
fn a_release_that_began_elsewhere_opens_nothing(cx: &mut TestAppContext) {
|
|
setup(cx);
|
|
cx.update(crate::forms::bind_keys);
|
|
let (view, cx) = cx.add_window_view(|window, cx| Relay {
|
|
field: cx.new(|cx| TextInput::new(window, cx)),
|
|
heard: Vec::new(),
|
|
});
|
|
settle(cx);
|
|
cx.update(|window, _| window.focus_next());
|
|
settle(cx);
|
|
enter(cx);
|
|
settle(cx);
|
|
assert!(view.read_with(cx, |view, _| view.heard.is_empty()));
|
|
}
|