Introduce keyboard navigation in context menus
This commit is contained in:
parent
991eb742b0
commit
5b2d6e41f3
20 changed files with 121 additions and 34 deletions
|
@ -9,6 +9,7 @@ doctest = false
|
|||
|
||||
[dependencies]
|
||||
gpui = { path = "../gpui" }
|
||||
menu = { path = "../menu" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
smallvec = "1.6"
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
use gpui::{
|
||||
elements::*, geometry::vector::Vector2F, impl_internal_actions, platform::CursorStyle, Action,
|
||||
elements::*, geometry::vector::Vector2F, keymap, platform::CursorStyle, Action, AppContext,
|
||||
Axis, Entity, MutableAppContext, RenderContext, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
use menu::*;
|
||||
use settings::Settings;
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(ContextMenu::dismiss);
|
||||
cx.add_action(ContextMenu::select_first);
|
||||
cx.add_action(ContextMenu::select_last);
|
||||
cx.add_action(ContextMenu::select_next);
|
||||
cx.add_action(ContextMenu::select_prev);
|
||||
cx.add_action(ContextMenu::confirm);
|
||||
cx.add_action(ContextMenu::cancel);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Dismiss;
|
||||
|
||||
impl_internal_actions!(context_menu, [Dismiss]);
|
||||
|
||||
pub enum ContextMenuItem {
|
||||
Item {
|
||||
label: String,
|
||||
|
@ -32,6 +33,10 @@ impl ContextMenuItem {
|
|||
pub fn separator() -> Self {
|
||||
Self::Separator
|
||||
}
|
||||
|
||||
fn is_separator(&self) -> bool {
|
||||
matches!(self, Self::Separator)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -52,6 +57,12 @@ impl View for ContextMenu {
|
|||
"ContextMenu"
|
||||
}
|
||||
|
||||
fn keymap_context(&self, _: &AppContext) -> keymap::Context {
|
||||
let mut cx = Self::default_keymap_context();
|
||||
cx.set.insert("menu".into());
|
||||
cx
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
if !self.visible {
|
||||
return Empty::new().boxed();
|
||||
|
@ -77,6 +88,7 @@ impl View for ContextMenu {
|
|||
|
||||
fn on_blur(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.visible = false;
|
||||
self.selected_index.take();
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
@ -86,13 +98,66 @@ impl ContextMenu {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
|
||||
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.selected_index {
|
||||
if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) {
|
||||
let window_id = cx.window_id();
|
||||
let view_id = cx.view_id();
|
||||
cx.dispatch_action_at(window_id, view_id, action.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||
if cx.handle().is_focused(cx) {
|
||||
let window_id = cx.window_id();
|
||||
(**cx).focus(window_id, self.previously_focused_view_id.take());
|
||||
}
|
||||
}
|
||||
|
||||
fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
|
||||
self.selected_index = self.items.iter().position(|item| !item.is_separator());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
|
||||
for (ix, item) in self.items.iter().enumerate().rev() {
|
||||
if !item.is_separator() {
|
||||
self.selected_index = Some(ix);
|
||||
cx.notify();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.selected_index {
|
||||
for (ix, item) in self.items.iter().enumerate().skip(ix + 1) {
|
||||
if !item.is_separator() {
|
||||
self.selected_index = Some(ix);
|
||||
cx.notify();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.select_first(&Default::default(), cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.selected_index {
|
||||
for (ix, item) in self.items.iter().enumerate().take(ix).rev() {
|
||||
if !item.is_separator() {
|
||||
self.selected_index = Some(ix);
|
||||
cx.notify();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.select_last(&Default::default(), cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show(
|
||||
&mut self,
|
||||
position: Vector2F,
|
||||
|
@ -202,7 +267,7 @@ impl ContextMenu {
|
|||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(move |_, _, cx| {
|
||||
cx.dispatch_any_action(action.boxed_clone());
|
||||
cx.dispatch_action(Dismiss);
|
||||
cx.dispatch_action(Cancel);
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue