register text-based actions with <input> element via traits
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use gpui::{App, AppContext, Window};
|
||||
use gpui::{Action, App, AppContext, Context, InteractiveElement, Window};
|
||||
|
||||
/// The key context used for input element keybindings.
|
||||
pub const DEFAULT_INPUT_CONTEXT: &str = "Input";
|
||||
@@ -233,3 +233,134 @@ pub trait EditableTextActionHandler<'app>: Sized {
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EditableInputActionElement: super::StateBackedElement {
|
||||
fn register_action<A: Action>(
|
||||
&mut self,
|
||||
init_props: Self::InitProps,
|
||||
listener: fn(&mut Self::State, &A, &mut Window, &mut Context<Self::State>),
|
||||
) where
|
||||
Self: InteractiveElement,
|
||||
{
|
||||
self.interactivity()
|
||||
.on_action::<A>(move |action, window, cx| {
|
||||
let state = Self::get_or_init_state(&init_props, window, cx);
|
||||
state.update(cx, |state, cx| {
|
||||
listener(state, action, window, cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn register_actions(&mut self)
|
||||
where
|
||||
Self: InteractiveElement,
|
||||
Self::InitProps: Clone,
|
||||
Self::State:
|
||||
for<'app> EditableTextActionHandler<'app, Context = gpui::Context<'app, Self::State>>,
|
||||
{
|
||||
use super::actions::*;
|
||||
let init_props = self.init_props();
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.escape(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.insert_enter(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.insert_tab(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.backspace(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.delete(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.delete_word_left(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.delete_word_right(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.delete_to_line_start(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.delete_to_line_end(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.nav_left(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.nav_right(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.nav_up(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.nav_down(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.nav_line_start(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.nav_line_end(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.nav_start(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.nav_end(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.nav_left_word(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.nav_right_word(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.select_all(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.select_left(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.select_right(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.select_up(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.select_down(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.select_start(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.select_end(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.select_left_word(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.select_right_word(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.cut(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.copy(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.paste(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.undo(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.redo(action, window, cx)
|
||||
});
|
||||
self.register_action(init_props.clone(), |state, action, window, cx| {
|
||||
state.show_character_palette(action, window, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use crate::editable_text::{
|
||||
EditableInputActionElement, StateBackedElement, TextInputState, UnicodeTextStorage,
|
||||
};
|
||||
use gpui::{
|
||||
App, Element, ElementId, Entity, Hitbox, InteractiveElement, Interactivity, IntoElement,
|
||||
Length, SharedString, StyleRefinement, Styled, TextStyle,
|
||||
SharedString, StyleRefinement, Styled, TextStyle, Window,
|
||||
};
|
||||
|
||||
use crate::editable_text::{TextInputState, UnicodeTextStorage};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[track_caller]
|
||||
pub fn input(id: impl Into<ElementId>) -> TextInputElement {
|
||||
@@ -11,9 +13,10 @@ pub fn input(id: impl Into<ElementId>) -> TextInputElement {
|
||||
id: id.into(),
|
||||
placeholder: None,
|
||||
interactivity: Interactivity::new(),
|
||||
init_storage: None,
|
||||
init_storage: InitStorage::default(),
|
||||
};
|
||||
this = this.key_context(super::DEFAULT_INPUT_CONTEXT);
|
||||
this.register_actions();
|
||||
this
|
||||
}
|
||||
|
||||
@@ -22,7 +25,7 @@ pub struct TextInputElement {
|
||||
id: ElementId,
|
||||
placeholder: Option<SharedString>,
|
||||
interactivity: Interactivity,
|
||||
init_storage: Option<Box<dyn Fn(&mut App) -> Box<dyn UnicodeTextStorage>>>,
|
||||
init_storage: InitStorage,
|
||||
}
|
||||
|
||||
impl InteractiveElement for TextInputElement {
|
||||
@@ -44,6 +47,39 @@ impl IntoElement for TextInputElement {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(super) struct InitStorage(Option<Rc<dyn Fn(&mut App) -> Box<dyn UnicodeTextStorage>>>);
|
||||
impl InitStorage {
|
||||
fn exec(&self, cx: &mut App) -> Box<dyn UnicodeTextStorage> {
|
||||
match &self.0 {
|
||||
None => Box::new(String::new()),
|
||||
Some(init) => (*init)(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EditableInputActionElement for TextInputElement {}
|
||||
impl super::StateBackedElement for TextInputElement {
|
||||
type State = TextInputState;
|
||||
type InitProps = (ElementId, InitStorage);
|
||||
|
||||
fn init_props(&self) -> Self::InitProps {
|
||||
(self.id.clone(), self.init_storage.clone())
|
||||
}
|
||||
|
||||
fn get_or_init_state(
|
||||
init_props: &Self::InitProps,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<TextInputState> {
|
||||
// Get the state from the app using the element's id as the key.
|
||||
// If it doesnt exist, initialize a new state with the user's desired storage medium.
|
||||
window.use_keyed_state(init_props.0.clone(), cx, |_window, cx| {
|
||||
TextInputState::new(init_props.1.exec(cx), cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct LayoutState {
|
||||
state: Entity<TextInputState>,
|
||||
@@ -76,15 +112,7 @@ impl Element for TextInputElement {
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
let mut resolved_text_style = None;
|
||||
|
||||
// Get the state from the app using the element's id as the key.
|
||||
// If it doesnt exist, initialize a new state with the user's desired storage medium.
|
||||
let state = window.use_keyed_state(self.id.clone(), cx, |_window, cx| {
|
||||
let storage = match &self.init_storage {
|
||||
None => Box::new(String::new()),
|
||||
Some(init_storage) => (*init_storage)(cx),
|
||||
};
|
||||
TextInputState::new(storage, cx)
|
||||
});
|
||||
let state = self.get_state(window, cx);
|
||||
|
||||
let layout_id = self.interactivity.request_layout(
|
||||
global_id,
|
||||
|
||||
@@ -3,8 +3,8 @@ use crate::editable_text::{
|
||||
notify::{TextChanged, TextHistoryPushed},
|
||||
};
|
||||
use gpui::{
|
||||
App, ClipboardItem, FocusHandle, Focusable, NavigationDirection, Pixels, Point, UTF16Selection,
|
||||
Window,
|
||||
App, ClipboardItem, Entity, FocusHandle, Focusable, NavigationDirection, Pixels, Point,
|
||||
UTF16Selection, Window,
|
||||
};
|
||||
use std::ops::Range;
|
||||
|
||||
@@ -14,6 +14,23 @@ pub trait TextStateNotifier {
|
||||
fn emit_history(&mut self, event: TextHistoryPushed);
|
||||
}
|
||||
|
||||
pub(super) trait StateBackedElement {
|
||||
type State: 'static;
|
||||
type InitProps: 'static;
|
||||
|
||||
fn init_props(&self) -> Self::InitProps;
|
||||
|
||||
fn get_or_init_state(
|
||||
init_props: &Self::InitProps,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self::State>;
|
||||
|
||||
fn get_state(&self, window: &mut Window, cx: &mut App) -> Entity<Self::State> {
|
||||
Self::get_or_init_state(&self.init_props(), window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextInputStateBase {
|
||||
storage: Box<dyn UnicodeTextStorage>,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user