Add up, down, enter, esc keybinding for Dropdown.
This commit is contained in:
parent
720672b0e2
commit
63076725f2
4 changed files with 70 additions and 15 deletions
|
|
@ -1,18 +1,30 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder as _, px, AppContext, Div, ElementId, FocusHandle, FocusableView,
|
actions, div, prelude::FluentBuilder as _, px, AppContext, Div, ElementId, FocusHandle,
|
||||||
InteractiveElement, IntoElement, ParentElement as _, Render, RenderOnce, SharedString,
|
FocusableView, InteractiveElement, IntoElement, KeyBinding, ParentElement as _, Render,
|
||||||
Stateful, StatefulInteractiveElement as _, Styled as _, View, ViewContext, VisualContext as _,
|
RenderOnce, SharedString, Stateful, StatefulInteractiveElement as _, Styled as _, View,
|
||||||
WeakView,
|
ViewContext, VisualContext as _, WeakView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
actions!(dropdown, [Up, Down, Enter, Escape]);
|
||||||
|
|
||||||
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
let context = Some("Dropdown");
|
||||||
|
cx.bind_keys([
|
||||||
|
KeyBinding::new("up", Up, context),
|
||||||
|
KeyBinding::new("down", Down, context),
|
||||||
|
KeyBinding::new("enter", Enter, context),
|
||||||
|
KeyBinding::new("escape", Escape, context),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
h_flex,
|
h_flex,
|
||||||
list::ListItem,
|
list::ListItem,
|
||||||
picker::{Picker, PickerDelegate},
|
picker::{self, Picker, PickerDelegate},
|
||||||
theme::ActiveTheme,
|
theme::ActiveTheme,
|
||||||
v_flex, IconName, StyledExt,
|
v_flex, IconName,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A trait for items that can be displayed in a dropdown.
|
/// A trait for items that can be displayed in a dropdown.
|
||||||
|
|
@ -98,6 +110,7 @@ where
|
||||||
view.value = Some(item.value().to_string().into());
|
view.value = Some(item.value().to_string().into());
|
||||||
}
|
}
|
||||||
view.open = false;
|
view.open = false;
|
||||||
|
view.focus_handle.focus(cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -139,6 +152,35 @@ where
|
||||||
self.value = Some(value.into());
|
self.value = Some(value.into());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
|
||||||
|
if !self.open {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.picker.focus_handle(cx).focus(cx);
|
||||||
|
cx.dispatch_action(Box::new(picker::SelectPrev));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
|
||||||
|
if !self.open {
|
||||||
|
self.open = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.picker.focus_handle(cx).focus(cx);
|
||||||
|
cx.dispatch_action(Box::new(picker::SelectNext));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
|
||||||
|
if !self.open {
|
||||||
|
self.open = true;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) {
|
||||||
|
self.open = false;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> FocusableView for Dropdown<D>
|
impl<D> FocusableView for Dropdown<D>
|
||||||
|
|
@ -155,11 +197,18 @@ where
|
||||||
D: DropdownDelegate + 'static,
|
D: DropdownDelegate + 'static,
|
||||||
{
|
{
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let group_id = format!("dropdown-group:{}", self.id);
|
||||||
let title = self.title.clone().unwrap_or_else(|| "Select...".into());
|
let title = self.title.clone().unwrap_or_else(|| "Select...".into());
|
||||||
let is_focused = self.focus_handle.is_focused(cx);
|
let focused = self.focus_handle.is_focused(cx);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.key_context("Dropdown")
|
.key_context("Dropdown")
|
||||||
|
.group(group_id.clone())
|
||||||
|
.track_focus(&self.focus_handle)
|
||||||
|
.on_action(cx.listener(Self::up))
|
||||||
|
.on_action(cx.listener(Self::down))
|
||||||
|
.on_action(cx.listener(Self::enter))
|
||||||
|
.on_action(cx.listener(Self::escape))
|
||||||
.size_full()
|
.size_full()
|
||||||
.relative()
|
.relative()
|
||||||
.child(
|
.child(
|
||||||
|
|
@ -177,7 +226,7 @@ where
|
||||||
.shadow_sm()
|
.shadow_sm()
|
||||||
.px_3()
|
.px_3()
|
||||||
.py_2()
|
.py_2()
|
||||||
.when(is_focused, |this| this.border_color(cx.theme().ring))
|
.when(focused, |this| this.border_color(cx.theme().ring))
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
this.open = !this.open;
|
this.open = !this.open;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
@ -204,7 +253,11 @@ where
|
||||||
.rounded(px(cx.theme().radius))
|
.rounded(px(cx.theme().radius))
|
||||||
.shadow_md()
|
.shadow_md()
|
||||||
.track_focus(&self.picker.focus_handle(cx))
|
.track_focus(&self.picker.focus_handle(cx))
|
||||||
.child(self.picker.clone()),
|
.child(self.picker.clone())
|
||||||
|
.on_mouse_down_out(cx.listener(|view, _, cx| {
|
||||||
|
view.open = false;
|
||||||
|
cx.notify();
|
||||||
|
})),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -541,7 +541,7 @@ impl Element for TextElement {
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
let focus_handle = self.input.read(cx).focus_handle.clone();
|
let focus_handle = self.input.read(cx).focus_handle.clone();
|
||||||
let is_focused = focus_handle.is_focused(cx);
|
let focused = focus_handle.is_focused(cx);
|
||||||
|
|
||||||
cx.handle_input(
|
cx.handle_input(
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
|
|
@ -553,7 +553,7 @@ impl Element for TextElement {
|
||||||
let line = prepaint.line.take().unwrap();
|
let line = prepaint.line.take().unwrap();
|
||||||
line.paint(bounds.origin, cx.line_height(), cx).unwrap();
|
line.paint(bounds.origin, cx.line_height(), cx).unwrap();
|
||||||
|
|
||||||
if is_focused {
|
if focused {
|
||||||
if let Some(cursor) = prepaint.cursor.take() {
|
if let Some(cursor) = prepaint.cursor.take() {
|
||||||
cx.paint_quad(cursor);
|
cx.paint_quad(cursor);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,4 +35,5 @@ pub use stock::*;
|
||||||
pub fn init(cx: &mut gpui::AppContext) {
|
pub fn init(cx: &mut gpui::AppContext) {
|
||||||
input::init(cx);
|
input::init(cx);
|
||||||
picker::init(cx);
|
picker::init(cx);
|
||||||
|
dropdown::init(cx);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,12 @@ actions!(
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
let context = Some("Picker");
|
||||||
cx.bind_keys([
|
cx.bind_keys([
|
||||||
KeyBinding::new("enter", Confirm, None),
|
KeyBinding::new("enter", Confirm, context),
|
||||||
KeyBinding::new("escape", Cancel, None),
|
KeyBinding::new("escape", Cancel, context),
|
||||||
KeyBinding::new("up", SelectPrev, None),
|
KeyBinding::new("up", SelectPrev, context),
|
||||||
KeyBinding::new("down", SelectNext, None),
|
KeyBinding::new("down", SelectNext, context),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue