Simplify input handling (#3282)
This PR takes a different approach to input handling. Rather than returning the optional input handler, focus handle pair from the element trait, we instead allow you to register an input handler imperatively on the window context with `WindowContext::handle_input`. You pass a focus handle reference and any implementer of `PlatformInputHandler`. There's an `ElementInputHandler<V>` that implements `PlatformWindowHandler` so long as `V` implements `InputHandler`. Release Notes: - N/A
This commit is contained in:
commit
8c44f6a814
8 changed files with 145 additions and 218 deletions
|
@ -9580,7 +9580,7 @@ impl Render for Editor {
|
|||
|
||||
impl InputHandler for Editor {
|
||||
fn text_for_range(
|
||||
&self,
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<String> {
|
||||
|
@ -9593,7 +9593,7 @@ impl InputHandler for Editor {
|
|||
)
|
||||
}
|
||||
|
||||
fn selected_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
|
||||
fn selected_text_range(&mut self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
|
||||
// Prevent the IME menu from appearing when holding down an alphabetic key
|
||||
// while input is disabled.
|
||||
if !self.input_enabled {
|
||||
|
|
|
@ -17,11 +17,10 @@ use collections::{BTreeMap, HashMap};
|
|||
use gpui::{
|
||||
black, hsla, point, px, relative, size, transparent_black, Action, AnyElement,
|
||||
BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase,
|
||||
Edges, Element, ElementId, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler,
|
||||
InputHandlerView, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers,
|
||||
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent,
|
||||
ShapedGlyph, Size, Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext,
|
||||
WrappedLineLayout,
|
||||
Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla,
|
||||
InputHandler, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size,
|
||||
Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::language_settings::ShowWhitespaceSetting;
|
||||
|
@ -2517,16 +2516,10 @@ impl Element<Editor> for EditorElement {
|
|||
self.paint_gutter(gutter_bounds, &layout, editor, cx);
|
||||
}
|
||||
self.paint_text(text_bounds, &layout, editor, cx);
|
||||
let input_handler = ElementInputHandler::new(bounds, cx);
|
||||
cx.handle_input(&editor.focus_handle, input_handler);
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_text_input<'a>(
|
||||
&self,
|
||||
editor: &'a mut Editor,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Option<(Box<dyn InputHandlerView>, &'a FocusHandle)> {
|
||||
Some((Box::new(cx.view()), &editor.focus_handle))
|
||||
}
|
||||
}
|
||||
|
||||
// impl EditorElement {
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
use crate::{
|
||||
BorrowWindow, Bounds, ElementId, FocusHandle, InputHandlerView, LayoutId, Pixels, ViewContext,
|
||||
WindowInputHandler,
|
||||
};
|
||||
use crate::{BorrowWindow, Bounds, ElementId, LayoutId, Pixels, ViewContext};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
pub(crate) use smallvec::SmallVec;
|
||||
use std::{any::Any, mem};
|
||||
|
@ -34,14 +31,6 @@ pub trait Element<V: 'static> {
|
|||
element_state: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<V>,
|
||||
);
|
||||
|
||||
fn handle_text_input<'a>(
|
||||
&self,
|
||||
_view_state: &'a mut V,
|
||||
_cx: &mut ViewContext<V>,
|
||||
) -> Option<(Box<dyn InputHandlerView>, &'a FocusHandle)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
|
@ -165,18 +154,6 @@ where
|
|||
mut frame_state,
|
||||
} => {
|
||||
let bounds = cx.layout_bounds(layout_id);
|
||||
if let Some((input_handler, focus_handle)) =
|
||||
self.element.handle_text_input(view_state, cx)
|
||||
{
|
||||
if focus_handle.is_focused(cx) {
|
||||
cx.window.requested_input_handler = Some(Box::new(WindowInputHandler {
|
||||
cx: cx.app.this.clone(),
|
||||
window: cx.window_handle(),
|
||||
input_handler,
|
||||
element_bounds: bounds,
|
||||
}));
|
||||
}
|
||||
}
|
||||
if let Some(id) = self.element.id() {
|
||||
cx.with_element_state(id, |element_state, cx| {
|
||||
let mut element_state = element_state.unwrap();
|
||||
|
|
|
@ -9,6 +9,7 @@ mod executor;
|
|||
mod focusable;
|
||||
mod geometry;
|
||||
mod image_cache;
|
||||
mod input;
|
||||
mod interactive;
|
||||
mod keymap;
|
||||
mod platform;
|
||||
|
@ -24,7 +25,6 @@ mod text_system;
|
|||
mod util;
|
||||
mod view;
|
||||
mod window;
|
||||
mod window_input_handler;
|
||||
|
||||
mod private {
|
||||
/// A mechanism for restricting implementations of a trait to only those in GPUI.
|
||||
|
@ -45,6 +45,7 @@ pub use focusable::*;
|
|||
pub use geometry::*;
|
||||
pub use gpui2_macros::*;
|
||||
pub use image_cache::*;
|
||||
pub use input::*;
|
||||
pub use interactive::*;
|
||||
pub use keymap::*;
|
||||
pub use platform::*;
|
||||
|
@ -66,7 +67,6 @@ pub use text_system::*;
|
|||
pub use util::arc_cow::ArcCow;
|
||||
pub use view::*;
|
||||
pub use window::*;
|
||||
pub use window_input_handler::*;
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use std::{
|
||||
|
|
114
crates/gpui2/src/input.rs
Normal file
114
crates/gpui2/src/input.rs
Normal file
|
@ -0,0 +1,114 @@
|
|||
use crate::{AsyncWindowContext, Bounds, Pixels, PlatformInputHandler, View, ViewContext};
|
||||
use std::ops::Range;
|
||||
|
||||
/// Implement this trait to allow views to handle textual input when implementing an editor, field, etc.
|
||||
///
|
||||
/// Once your view `V` implements this trait, you can use it to construct an [ElementInputHandler<V>].
|
||||
/// This input handler can then be assigned during paint by calling [WindowContext::handle_input].
|
||||
pub trait InputHandler: 'static + Sized {
|
||||
fn text_for_range(&mut self, range: Range<usize>, cx: &mut ViewContext<Self>)
|
||||
-> Option<String>;
|
||||
fn selected_text_range(&mut self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
|
||||
fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
|
||||
fn unmark_text(&mut self, cx: &mut ViewContext<Self>);
|
||||
fn replace_text_in_range(
|
||||
&mut self,
|
||||
range: Option<Range<usize>>,
|
||||
text: &str,
|
||||
cx: &mut ViewContext<Self>,
|
||||
);
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
);
|
||||
fn bounds_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
element_bounds: Bounds<Pixels>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Bounds<Pixels>>;
|
||||
}
|
||||
|
||||
/// The canonical implementation of `PlatformInputHandler`. Call `WindowContext::handle_input`
|
||||
/// with an instance during your element's paint.
|
||||
pub struct ElementInputHandler<V> {
|
||||
view: View<V>,
|
||||
element_bounds: Bounds<Pixels>,
|
||||
cx: AsyncWindowContext,
|
||||
}
|
||||
|
||||
impl<V: 'static> ElementInputHandler<V> {
|
||||
/// Used in [Element::paint] with the element's bounds and a view context for its
|
||||
/// containing view.
|
||||
pub fn new(element_bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) -> Self {
|
||||
ElementInputHandler {
|
||||
view: cx.view(),
|
||||
element_bounds,
|
||||
cx: cx.to_async(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: InputHandler> PlatformInputHandler for ElementInputHandler<V> {
|
||||
fn selected_text_range(&mut self) -> Option<Range<usize>> {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| view.selected_text_range(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn marked_text_range(&mut self) -> Option<Range<usize>> {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| view.marked_text_range(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| {
|
||||
view.text_for_range(range_utf16, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| {
|
||||
view.replace_text_in_range(replacement_range, text, cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
) {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| {
|
||||
view.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self) {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| view.unmark_text(cx))
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| {
|
||||
view.bounds_for_range(range_utf16, self.element_bounds, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}
|
|
@ -293,10 +293,10 @@ impl From<TileId> for etagere::AllocId {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait PlatformInputHandler {
|
||||
fn selected_text_range(&self) -> Option<Range<usize>>;
|
||||
fn marked_text_range(&self) -> Option<Range<usize>>;
|
||||
fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
|
||||
pub trait PlatformInputHandler: 'static {
|
||||
fn selected_text_range(&mut self) -> Option<Range<usize>>;
|
||||
fn marked_text_range(&mut self) -> Option<Range<usize>>;
|
||||
fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
|
||||
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
|
@ -305,7 +305,7 @@ pub trait PlatformInputHandler {
|
|||
new_selected_range: Option<Range<usize>>,
|
||||
);
|
||||
fn unmark_text(&mut self);
|
||||
fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
|
||||
fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -211,7 +211,6 @@ pub struct Window {
|
|||
default_prevented: bool,
|
||||
mouse_position: Point<Pixels>,
|
||||
requested_cursor_style: Option<CursorStyle>,
|
||||
pub(crate) requested_input_handler: Option<Box<dyn PlatformInputHandler>>,
|
||||
scale_factor: f32,
|
||||
bounds: WindowBounds,
|
||||
bounds_observers: SubscriberSet<(), AnyObserver>,
|
||||
|
@ -311,7 +310,6 @@ impl Window {
|
|||
default_prevented: true,
|
||||
mouse_position,
|
||||
requested_cursor_style: None,
|
||||
requested_input_handler: None,
|
||||
scale_factor,
|
||||
bounds,
|
||||
bounds_observers: SubscriberSet::new(),
|
||||
|
@ -1052,9 +1050,6 @@ impl<'a> WindowContext<'a> {
|
|||
.take()
|
||||
.unwrap_or(CursorStyle::Arrow);
|
||||
self.platform.set_cursor_style(cursor_style);
|
||||
if let Some(handler) = self.window.requested_input_handler.take() {
|
||||
self.window.platform_window.set_input_handler(handler);
|
||||
}
|
||||
|
||||
self.window.dirty = false;
|
||||
}
|
||||
|
@ -2182,6 +2177,21 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
|||
})
|
||||
});
|
||||
}
|
||||
|
||||
/// Set an input handler, such as [ElementInputHandler], which interfaces with the
|
||||
/// platform to receive textual input with proper integration with concerns such
|
||||
/// as IME interactions.
|
||||
pub fn handle_input(
|
||||
&mut self,
|
||||
focus_handle: &FocusHandle,
|
||||
input_handler: impl PlatformInputHandler,
|
||||
) {
|
||||
if focus_handle.is_focused(self) {
|
||||
self.window
|
||||
.platform_window
|
||||
.set_input_handler(Box::new(input_handler));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> ViewContext<'_, V> {
|
||||
|
|
|
@ -1,167 +0,0 @@
|
|||
use crate::{
|
||||
AnyWindowHandle, AppCell, Bounds, Context, Pixels, PlatformInputHandler, View, ViewContext,
|
||||
WindowContext,
|
||||
};
|
||||
use std::{ops::Range, rc::Weak};
|
||||
|
||||
pub struct WindowInputHandler {
|
||||
pub cx: Weak<AppCell>,
|
||||
pub input_handler: Box<dyn InputHandlerView>,
|
||||
pub window: AnyWindowHandle,
|
||||
pub element_bounds: Bounds<Pixels>,
|
||||
}
|
||||
|
||||
pub trait InputHandlerView {
|
||||
fn text_for_range(&self, range: Range<usize>, cx: &mut WindowContext) -> Option<String>;
|
||||
fn selected_text_range(&self, cx: &mut WindowContext) -> Option<Range<usize>>;
|
||||
fn marked_text_range(&self, cx: &mut WindowContext) -> Option<Range<usize>>;
|
||||
fn unmark_text(&self, cx: &mut WindowContext);
|
||||
fn replace_text_in_range(
|
||||
&self,
|
||||
range: Option<Range<usize>>,
|
||||
text: &str,
|
||||
cx: &mut WindowContext,
|
||||
);
|
||||
fn replace_and_mark_text_in_range(
|
||||
&self,
|
||||
range: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
cx: &mut WindowContext,
|
||||
);
|
||||
fn bounds_for_range(
|
||||
&self,
|
||||
range_utf16: std::ops::Range<usize>,
|
||||
element_bounds: crate::Bounds<Pixels>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<crate::Bounds<Pixels>>;
|
||||
}
|
||||
|
||||
pub trait InputHandler: Sized {
|
||||
fn text_for_range(&self, range: Range<usize>, cx: &mut ViewContext<Self>) -> Option<String>;
|
||||
fn selected_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
|
||||
fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
|
||||
fn unmark_text(&mut self, cx: &mut ViewContext<Self>);
|
||||
fn replace_text_in_range(
|
||||
&mut self,
|
||||
range: Option<Range<usize>>,
|
||||
text: &str,
|
||||
cx: &mut ViewContext<Self>,
|
||||
);
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
);
|
||||
fn bounds_for_range(
|
||||
&mut self,
|
||||
range_utf16: std::ops::Range<usize>,
|
||||
element_bounds: crate::Bounds<Pixels>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<crate::Bounds<Pixels>>;
|
||||
}
|
||||
|
||||
impl<V: InputHandler + 'static> InputHandlerView for View<V> {
|
||||
fn text_for_range(&self, range: Range<usize>, cx: &mut WindowContext) -> Option<String> {
|
||||
self.update(cx, |this, cx| this.text_for_range(range, cx))
|
||||
}
|
||||
|
||||
fn selected_text_range(&self, cx: &mut WindowContext) -> Option<Range<usize>> {
|
||||
self.update(cx, |this, cx| this.selected_text_range(cx))
|
||||
}
|
||||
|
||||
fn marked_text_range(&self, cx: &mut WindowContext) -> Option<Range<usize>> {
|
||||
self.update(cx, |this, cx| this.marked_text_range(cx))
|
||||
}
|
||||
|
||||
fn unmark_text(&self, cx: &mut WindowContext) {
|
||||
self.update(cx, |this, cx| this.unmark_text(cx))
|
||||
}
|
||||
|
||||
fn replace_text_in_range(
|
||||
&self,
|
||||
range: Option<Range<usize>>,
|
||||
text: &str,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.update(cx, |this, cx| this.replace_text_in_range(range, text, cx))
|
||||
}
|
||||
|
||||
fn replace_and_mark_text_in_range(
|
||||
&self,
|
||||
range: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.update(cx, |this, cx| {
|
||||
this.replace_and_mark_text_in_range(range, new_text, new_selected_range, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn bounds_for_range(
|
||||
&self,
|
||||
range_utf16: std::ops::Range<usize>,
|
||||
element_bounds: crate::Bounds<Pixels>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<crate::Bounds<Pixels>> {
|
||||
self.update(cx, |this, cx| {
|
||||
this.bounds_for_range(range_utf16, element_bounds, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformInputHandler for WindowInputHandler {
|
||||
fn selected_text_range(&self) -> Option<Range<usize>> {
|
||||
self.update(|handler, cx| handler.selected_text_range(cx))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn marked_text_range(&self) -> Option<Range<usize>> {
|
||||
self.update(|handler, cx| handler.marked_text_range(cx))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String> {
|
||||
self.update(|handler, cx| handler.text_for_range(range_utf16, cx))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
|
||||
self.update(|handler, cx| handler.replace_text_in_range(replacement_range, text, cx));
|
||||
}
|
||||
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
) {
|
||||
self.update(|handler, cx| {
|
||||
handler.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx)
|
||||
});
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self) {
|
||||
self.update(|handler, cx| handler.unmark_text(cx));
|
||||
}
|
||||
|
||||
fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
|
||||
self.update(|handler, cx| handler.bounds_for_range(range_utf16, self.element_bounds, cx))
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowInputHandler {
|
||||
fn update<R>(
|
||||
&self,
|
||||
f: impl FnOnce(&dyn InputHandlerView, &mut WindowContext) -> R,
|
||||
) -> Option<R> {
|
||||
let cx = self.cx.upgrade()?;
|
||||
let mut cx = cx.borrow_mut();
|
||||
cx.update_window(self.window, |_, cx| f(&*self.input_handler, cx))
|
||||
.ok()
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue