From 9a022671a2ea92d773d3d1d7abf3b09c739a6ca9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Nov 2023 21:06:00 -0700 Subject: [PATCH 1/4] Simplify IME support --- crates/editor2/src/editor.rs | 4 +- crates/editor2/src/element.rs | 19 +-- crates/gpui2/src/element.rs | 40 +++--- crates/gpui2/src/gpui2.rs | 4 +- crates/gpui2/src/input.rs | 106 ++++++++++++++ crates/gpui2/src/platform.rs | 10 +- crates/gpui2/src/window.rs | 16 ++- crates/gpui2/src/window_input_handler.rs | 167 ----------------------- 8 files changed, 148 insertions(+), 218 deletions(-) create mode 100644 crates/gpui2/src/input.rs delete mode 100644 crates/gpui2/src/window_input_handler.rs diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 2fe35bb1f6..43af9466b0 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -9565,7 +9565,7 @@ impl Render for Editor { impl InputHandler for Editor { fn text_for_range( - &self, + &mut self, range_utf16: Range, cx: &mut ViewContext, ) -> Option { @@ -9578,7 +9578,7 @@ impl InputHandler for Editor { ) } - fn selected_text_range(&self, cx: &mut ViewContext) -> Option> { + fn selected_text_range(&mut self, cx: &mut ViewContext) -> Option> { // Prevent the IME menu from appearing when holding down an alphabetic key // while input is disabled. if !self.input_enabled { diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 0e53aa449d..3e77a66936 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -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 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, - ) -> Option<(Box, &'a FocusHandle)> { - Some((Box::new(cx.view()), &editor.focus_handle)) - } } // impl EditorElement { diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index a6067eb68d..de9788a9a0 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -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 { element_state: &mut Self::ElementState, cx: &mut ViewContext, ); - - fn handle_text_input<'a>( - &self, - _view_state: &'a mut V, - _cx: &mut ViewContext, - ) -> Option<(Box, &'a FocusHandle)> { - None - } } #[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)] @@ -165,18 +154,21 @@ 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((input_handler, focus_handle)) = + // self.element.handle_text_input(view_state, cx) + // { + // todo!() + // // cx.handle_input(&focus_handle, Box::new()) + + // // 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(); diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 91e4141735..86c0528456 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -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::{ diff --git a/crates/gpui2/src/input.rs b/crates/gpui2/src/input.rs new file mode 100644 index 0000000000..8d9e9b01ad --- /dev/null +++ b/crates/gpui2/src/input.rs @@ -0,0 +1,106 @@ +use crate::{AsyncWindowContext, Bounds, Pixels, PlatformInputHandler, View, ViewContext}; +use std::ops::Range; + +pub trait InputHandler: 'static + Sized { + fn text_for_range(&mut self, range: Range, cx: &mut ViewContext) + -> Option; + fn selected_text_range(&mut self, cx: &mut ViewContext) -> Option>; + fn marked_text_range(&self, cx: &mut ViewContext) -> Option>; + fn unmark_text(&mut self, cx: &mut ViewContext); + fn replace_text_in_range( + &mut self, + range: Option>, + text: &str, + cx: &mut ViewContext, + ); + fn replace_and_mark_text_in_range( + &mut self, + range: Option>, + new_text: &str, + new_selected_range: Option>, + cx: &mut ViewContext, + ); + fn bounds_for_range( + &mut self, + range_utf16: Range, + element_bounds: Bounds, + cx: &mut ViewContext, + ) -> Option>; +} + +pub struct ElementInputHandler { + view: View, + element_bounds: Bounds, + cx: AsyncWindowContext, +} + +impl ElementInputHandler { + pub fn new(element_bounds: Bounds, cx: &mut ViewContext) -> Self { + ElementInputHandler { + view: cx.view(), + element_bounds, + cx: cx.to_async(), + } + } +} + +impl PlatformInputHandler for ElementInputHandler { + fn selected_text_range(&mut self) -> Option> { + self.view + .update(&mut self.cx, |view, cx| view.selected_text_range(cx)) + .ok() + .flatten() + } + + fn marked_text_range(&mut self) -> Option> { + self.view + .update(&mut self.cx, |view, cx| view.marked_text_range(cx)) + .ok() + .flatten() + } + + fn text_for_range(&mut self, range_utf16: Range) -> Option { + 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>, 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>, + new_text: &str, + new_selected_range: Option>, + ) { + 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) -> Option> { + self.view + .update(&mut self.cx, |view, cx| { + view.bounds_for_range(range_utf16, self.element_bounds, cx) + }) + .ok() + .flatten() + } +} diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 5ebb12b64d..8b49addec9 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -293,10 +293,10 @@ impl From for etagere::AllocId { } } -pub trait PlatformInputHandler { - fn selected_text_range(&self) -> Option>; - fn marked_text_range(&self) -> Option>; - fn text_for_range(&self, range_utf16: Range) -> Option; +pub trait PlatformInputHandler: 'static { + fn selected_text_range(&mut self) -> Option>; + fn marked_text_range(&mut self) -> Option>; + fn text_for_range(&mut self, range_utf16: Range) -> Option; fn replace_text_in_range(&mut self, replacement_range: Option>, text: &str); fn replace_and_mark_text_in_range( &mut self, @@ -305,7 +305,7 @@ pub trait PlatformInputHandler { new_selected_range: Option>, ); fn unmark_text(&mut self); - fn bounds_for_range(&self, range_utf16: Range) -> Option>; + fn bounds_for_range(&mut self, range_utf16: Range) -> Option>; } #[derive(Debug)] diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 1daebf184c..354c98813f 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -211,7 +211,6 @@ pub struct Window { default_prevented: bool, mouse_position: Point, requested_cursor_style: Option, - pub(crate) requested_input_handler: Option>, scale_factor: f32, bounds: WindowBounds, bounds_observers: SubscriberSet<(), AnyObserver>, @@ -236,6 +235,7 @@ pub(crate) struct Frame { content_mask_stack: Vec>, element_offset_stack: Vec>, focus_stack: Vec, + input_handler: Option>, } impl Window { @@ -311,7 +311,6 @@ impl Window { default_prevented: true, mouse_position, requested_cursor_style: None, - requested_input_handler: None, scale_factor, bounds, bounds_observers: SubscriberSet::new(), @@ -1048,9 +1047,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; } @@ -2174,6 +2170,16 @@ impl<'a, V: 'static> ViewContext<'a, V> { }) }); } + + pub fn handle_input( + &mut self, + focus_handle: &FocusHandle, + input_handler: impl PlatformInputHandler, + ) { + if focus_handle.is_focused(self) { + self.window.current_frame.input_handler = Some(Box::new(input_handler)); + } + } } impl ViewContext<'_, V> diff --git a/crates/gpui2/src/window_input_handler.rs b/crates/gpui2/src/window_input_handler.rs deleted file mode 100644 index f3ff33f3c0..0000000000 --- a/crates/gpui2/src/window_input_handler.rs +++ /dev/null @@ -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, - pub input_handler: Box, - pub window: AnyWindowHandle, - pub element_bounds: Bounds, -} - -pub trait InputHandlerView { - fn text_for_range(&self, range: Range, cx: &mut WindowContext) -> Option; - fn selected_text_range(&self, cx: &mut WindowContext) -> Option>; - fn marked_text_range(&self, cx: &mut WindowContext) -> Option>; - fn unmark_text(&self, cx: &mut WindowContext); - fn replace_text_in_range( - &self, - range: Option>, - text: &str, - cx: &mut WindowContext, - ); - fn replace_and_mark_text_in_range( - &self, - range: Option>, - new_text: &str, - new_selected_range: Option>, - cx: &mut WindowContext, - ); - fn bounds_for_range( - &self, - range_utf16: std::ops::Range, - element_bounds: crate::Bounds, - cx: &mut WindowContext, - ) -> Option>; -} - -pub trait InputHandler: Sized { - fn text_for_range(&self, range: Range, cx: &mut ViewContext) -> Option; - fn selected_text_range(&self, cx: &mut ViewContext) -> Option>; - fn marked_text_range(&self, cx: &mut ViewContext) -> Option>; - fn unmark_text(&mut self, cx: &mut ViewContext); - fn replace_text_in_range( - &mut self, - range: Option>, - text: &str, - cx: &mut ViewContext, - ); - fn replace_and_mark_text_in_range( - &mut self, - range: Option>, - new_text: &str, - new_selected_range: Option>, - cx: &mut ViewContext, - ); - fn bounds_for_range( - &mut self, - range_utf16: std::ops::Range, - element_bounds: crate::Bounds, - cx: &mut ViewContext, - ) -> Option>; -} - -impl InputHandlerView for View { - fn text_for_range(&self, range: Range, cx: &mut WindowContext) -> Option { - self.update(cx, |this, cx| this.text_for_range(range, cx)) - } - - fn selected_text_range(&self, cx: &mut WindowContext) -> Option> { - self.update(cx, |this, cx| this.selected_text_range(cx)) - } - - fn marked_text_range(&self, cx: &mut WindowContext) -> Option> { - 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>, - 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>, - new_text: &str, - new_selected_range: Option>, - 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, - element_bounds: crate::Bounds, - cx: &mut WindowContext, - ) -> Option> { - self.update(cx, |this, cx| { - this.bounds_for_range(range_utf16, element_bounds, cx) - }) - } -} - -impl PlatformInputHandler for WindowInputHandler { - fn selected_text_range(&self) -> Option> { - self.update(|handler, cx| handler.selected_text_range(cx)) - .flatten() - } - - fn marked_text_range(&self) -> Option> { - self.update(|handler, cx| handler.marked_text_range(cx)) - .flatten() - } - - fn text_for_range(&self, range_utf16: Range) -> Option { - self.update(|handler, cx| handler.text_for_range(range_utf16, cx)) - .flatten() - } - - fn replace_text_in_range(&mut self, replacement_range: Option>, 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>, - new_text: &str, - new_selected_range: Option>, - ) { - 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) -> Option> { - self.update(|handler, cx| handler.bounds_for_range(range_utf16, self.element_bounds, cx)) - .flatten() - } -} - -impl WindowInputHandler { - fn update( - &self, - f: impl FnOnce(&dyn InputHandlerView, &mut WindowContext) -> R, - ) -> Option { - let cx = self.cx.upgrade()?; - let mut cx = cx.borrow_mut(); - cx.update_window(self.window, |_, cx| f(&*self.input_handler, cx)) - .ok() - } -} From 8278a0735437cae7e5c152e0503e7fd1bf83cf81 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Nov 2023 21:43:14 -0700 Subject: [PATCH 2/4] Actually set the input handler --- crates/gpui2/src/window.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 354c98813f..b72793b998 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -235,7 +235,6 @@ pub(crate) struct Frame { content_mask_stack: Vec>, element_offset_stack: Vec>, focus_stack: Vec, - input_handler: Option>, } impl Window { @@ -2177,7 +2176,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { input_handler: impl PlatformInputHandler, ) { if focus_handle.is_focused(self) { - self.window.current_frame.input_handler = Some(Box::new(input_handler)); + self.window + .platform_window + .set_input_handler(Box::new(input_handler)); } } } From 7c922ad6ee1c71200c324db9bf60dc1f23f52bbf Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Nov 2023 21:49:21 -0700 Subject: [PATCH 3/4] Remove comments --- crates/gpui2/src/element.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index de9788a9a0..8fdc17de07 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -154,21 +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) - // { - // todo!() - // // cx.handle_input(&focus_handle, Box::new()) - - // // 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(); From d52c5646b451ae27475b3ebb3db0e3e5d772cc56 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Nov 2023 22:03:26 -0700 Subject: [PATCH 4/4] Add docs --- crates/gpui2/src/input.rs | 8 ++++++++ crates/gpui2/src/window.rs | 3 +++ 2 files changed, 11 insertions(+) diff --git a/crates/gpui2/src/input.rs b/crates/gpui2/src/input.rs index 8d9e9b01ad..d768ce946a 100644 --- a/crates/gpui2/src/input.rs +++ b/crates/gpui2/src/input.rs @@ -1,6 +1,10 @@ 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]. +/// 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, cx: &mut ViewContext) -> Option; @@ -28,6 +32,8 @@ pub trait InputHandler: 'static + Sized { ) -> Option>; } +/// The canonical implementation of `PlatformInputHandler`. Call `WindowContext::handle_input` +/// with an instance during your element's paint. pub struct ElementInputHandler { view: View, element_bounds: Bounds, @@ -35,6 +41,8 @@ pub struct ElementInputHandler { } impl ElementInputHandler { + /// Used in [Element::paint] with the element's bounds and a view context for its + /// containing view. pub fn new(element_bounds: Bounds, cx: &mut ViewContext) -> Self { ElementInputHandler { view: cx.view(), diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index b72793b998..29980a486b 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -2170,6 +2170,9 @@ 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,