Call methods on the focused view during input events

This commit is contained in:
Max Brunsfeld 2022-07-20 16:44:26 -07:00
parent 1b0e93b153
commit 0b81a4dfae
3 changed files with 281 additions and 38 deletions

View file

@ -7,8 +7,8 @@ use crate::{
platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions}, platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions},
presenter::Presenter, presenter::Presenter,
util::post_inc, util::post_inc,
AssetCache, AssetSource, ClipboardItem, FontCache, MouseRegionId, PathPromptOptions, AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseRegionId,
TextLayoutCache, PathPromptOptions, TextLayoutCache,
}; };
pub use action::*; pub use action::*;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
@ -28,7 +28,7 @@ use std::{
hash::{Hash, Hasher}, hash::{Hash, Hasher},
marker::PhantomData, marker::PhantomData,
mem, mem,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut, Range},
path::{Path, PathBuf}, path::{Path, PathBuf},
pin::Pin, pin::Pin,
rc::{self, Rc}, rc::{self, Rc},
@ -64,6 +64,33 @@ pub trait View: Entity + Sized {
fn debug_json(&self, _: &AppContext) -> serde_json::Value { fn debug_json(&self, _: &AppContext) -> serde_json::Value {
serde_json::Value::Null serde_json::Value::Null
} }
fn text_for_range(&self, _: Range<usize>, _: &AppContext) -> Option<String> {
None
}
fn selected_text_range(&self, _: &AppContext) -> Option<Range<usize>> {
None
}
fn set_selected_text_range(&mut self, _: Range<usize>, _: &mut ViewContext<Self>) {}
fn marked_text_range(&self, _: &AppContext) -> Option<Range<usize>> {
None
}
fn unmark_text(&mut self, _: &mut ViewContext<Self>) {}
fn replace_text_in_range(
&mut self,
_: Option<Range<usize>>,
_: &str,
_: &mut ViewContext<Self>,
) {
}
fn replace_and_mark_text_in_range(
&mut self,
_: Option<Range<usize>>,
_: &str,
_: Option<Range<usize>>,
_: &mut ViewContext<Self>,
) {
}
} }
pub trait ReadModel { pub trait ReadModel {
@ -154,6 +181,11 @@ pub struct TestAppContext {
condition_duration: Option<Duration>, condition_duration: Option<Duration>,
} }
pub struct WindowInputHandler {
app: Rc<RefCell<MutableAppContext>>,
window_id: usize,
}
impl App { impl App {
pub fn new(asset_source: impl AssetSource) -> Result<Self> { pub fn new(asset_source: impl AssetSource) -> Result<Self> {
let platform = platform::current::platform(); let platform = platform::current::platform();
@ -310,6 +342,121 @@ impl App {
} }
} }
impl WindowInputHandler {
fn read_focused_view<T, F>(&self, f: F) -> Option<T>
where
F: FnOnce(&dyn AnyView, &AppContext) -> T,
{
let app = self.app.borrow();
let view_id = app.focused_view_id(self.window_id)?;
let view = app.cx.views.get(&(self.window_id, view_id))?;
let result = f(view.as_ref(), &app);
Some(result)
}
fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
where
F: FnOnce(usize, usize, &mut dyn AnyView, &mut MutableAppContext) -> T,
{
let mut app = self.app.borrow_mut();
app.update(|app| {
let view_id = app.focused_view_id(self.window_id)?;
let mut view = app.cx.views.remove(&(self.window_id, view_id))?;
let result = f(self.window_id, view_id, view.as_mut(), &mut *app);
app.cx.views.insert((self.window_id, view_id), view);
Some(result)
})
}
}
impl InputHandler for WindowInputHandler {
fn text_for_range(&self, range: Range<usize>) -> Option<String> {
let result = self
.read_focused_view(|view, cx| view.text_for_range(range.clone(), cx))
.flatten();
eprintln!("text_for_range({range:?}) -> {result:?}");
result
}
fn selected_text_range(&self) -> Option<Range<usize>> {
let result = self
.read_focused_view(|view, cx| view.selected_text_range(cx))
.flatten();
eprintln!("selected_text_range() -> {result:?}");
result
}
fn set_selected_text_range(&mut self, range: Range<usize>) {
eprintln!("set_selected_text_range({range:?})");
self.update_focused_view(|window_id, view_id, view, cx| {
view.set_selected_text_range(range, cx, window_id, view_id);
});
}
fn replace_text_in_range(&mut self, range: Option<Range<usize>>, text: &str) {
eprintln!("replace_text_in_range({range:?}, {text:?})");
self.update_focused_view(|window_id, view_id, view, cx| {
view.replace_text_in_range(range, text, cx, window_id, view_id);
});
}
fn marked_text_range(&self) -> Option<Range<usize>> {
let result = self
.read_focused_view(|view, cx| view.marked_text_range(cx))
.flatten();
eprintln!("marked_text_range() -> {result:?}");
result
}
fn unmark_text(&mut self) {
eprintln!("unmark_text()");
self.update_focused_view(|window_id, view_id, view, cx| {
view.unmark_text(cx, window_id, view_id);
});
}
fn replace_and_mark_text_in_range(
&mut self,
range: Option<Range<usize>>,
new_text: &str,
new_selected_range: Option<Range<usize>>,
) {
eprintln!(
"replace_and_mark_text_in_range({range:?}, {new_text:?}, {new_selected_range:?})"
);
self.update_focused_view(|window_id, view_id, view, cx| {
view.replace_and_mark_text_in_range(
range,
new_text,
new_selected_range,
cx,
window_id,
view_id,
);
});
}
// TODO - do these need to be handled separately?
fn cancel_composition(&mut self) {
self.unmark_text();
}
fn finish_composition(&mut self) {
self.unmark_text();
}
}
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
impl TestAppContext { impl TestAppContext {
pub fn new( pub fn new(
@ -1888,6 +2035,11 @@ impl MutableAppContext {
})); }));
} }
window.set_input_handler(Box::new(WindowInputHandler {
app: self.upgrade().0,
window_id,
}));
let scene = let scene =
presenter presenter
.borrow_mut() .borrow_mut()
@ -3179,6 +3331,35 @@ pub trait AnyView {
fn on_blur(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize); fn on_blur(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize);
fn keymap_context(&self, cx: &AppContext) -> keymap::Context; fn keymap_context(&self, cx: &AppContext) -> keymap::Context;
fn debug_json(&self, cx: &AppContext) -> serde_json::Value; fn debug_json(&self, cx: &AppContext) -> serde_json::Value;
fn text_for_range(&self, range: Range<usize>, cx: &AppContext) -> Option<String>;
fn selected_text_range(&self, cx: &AppContext) -> Option<Range<usize>>;
fn set_selected_text_range(
&mut self,
range: Range<usize>,
cx: &mut MutableAppContext,
window_id: usize,
view_id: usize,
);
fn marked_text_range(&self, cx: &AppContext) -> Option<Range<usize>>;
fn unmark_text(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize);
fn replace_text_in_range(
&mut self,
range: Option<Range<usize>>,
text: &str,
cx: &mut MutableAppContext,
window_id: usize,
view_id: usize,
);
fn replace_and_mark_text_in_range(
&mut self,
range: Option<Range<usize>>,
new_text: &str,
new_selected_range: Option<Range<usize>>,
cx: &mut MutableAppContext,
window_id: usize,
view_id: usize,
);
} }
impl<T> AnyView for T impl<T> AnyView for T
@ -3229,6 +3410,59 @@ where
fn debug_json(&self, cx: &AppContext) -> serde_json::Value { fn debug_json(&self, cx: &AppContext) -> serde_json::Value {
View::debug_json(self, cx) View::debug_json(self, cx)
} }
fn text_for_range(&self, range: Range<usize>, cx: &AppContext) -> Option<String> {
View::text_for_range(self, range, cx)
}
fn selected_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
View::selected_text_range(self, cx)
}
fn set_selected_text_range(
&mut self,
range: Range<usize>,
cx: &mut MutableAppContext,
window_id: usize,
view_id: usize,
) {
let mut cx = ViewContext::new(cx, window_id, view_id);
View::set_selected_text_range(self, range, &mut cx)
}
fn marked_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
View::marked_text_range(self, cx)
}
fn unmark_text(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize) {
let mut cx = ViewContext::new(cx, window_id, view_id);
View::unmark_text(self, &mut cx)
}
fn replace_text_in_range(
&mut self,
range: Option<Range<usize>>,
text: &str,
cx: &mut MutableAppContext,
window_id: usize,
view_id: usize,
) {
let mut cx = ViewContext::new(cx, window_id, view_id);
View::replace_text_in_range(self, range, text, &mut cx)
}
fn replace_and_mark_text_in_range(
&mut self,
range: Option<Range<usize>>,
new_text: &str,
new_selected_range: Option<Range<usize>>,
cx: &mut MutableAppContext,
window_id: usize,
view_id: usize,
) {
let mut cx = ViewContext::new(cx, window_id, view_id);
View::replace_and_mark_text_in_range(self, range, new_text, new_selected_range, &mut cx)
}
} }
pub struct ModelContext<'a, T: ?Sized> { pub struct ModelContext<'a, T: ?Sized> {

View file

@ -90,20 +90,20 @@ pub trait Dispatcher: Send + Sync {
} }
pub trait InputHandler { pub trait InputHandler {
fn select(&mut self, range: Range<usize>); fn selected_text_range(&self) -> Option<Range<usize>>;
fn selected_range(&self) -> Option<Range<usize>>; fn set_selected_text_range(&mut self, range: Range<usize>);
fn edit(&mut self, replacement_range: Option<Range<usize>>, text: &str); fn text_for_range(&self, range: Range<usize>) -> Option<String>;
fn compose( fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
fn replace_and_mark_text_in_range(
&mut self, &mut self,
marked_text: &str, range: Option<Range<usize>>,
new_text: &str,
new_selected_range: Option<Range<usize>>, new_selected_range: Option<Range<usize>>,
replacement_range: Option<Range<usize>>,
); );
fn marked_text_range(&self) -> Option<Range<usize>>;
fn unmark_text(&mut self);
fn cancel_composition(&mut self); fn cancel_composition(&mut self);
fn finish_composition(&mut self); fn finish_composition(&mut self);
fn unmark(&mut self);
fn marked_range(&self) -> Option<Range<usize>>;
fn text_for_range(&self, range: Range<usize>) -> Option<String>;
} }
pub trait Window: WindowContext { pub trait Window: WindowContext {

View file

@ -254,6 +254,10 @@ unsafe fn build_classes() {
attributed_substring_for_proposed_range attributed_substring_for_proposed_range
as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id, as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id,
); );
decl.add_method(
sel!(doCommandBySelector:),
do_command_by_selector as extern "C" fn(&Object, Sel, Sel),
);
decl.register() decl.register()
}; };
@ -723,7 +727,7 @@ extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) ->
window_state_borrow.pending_key_event = Some(Default::default()); window_state_borrow.pending_key_event = Some(Default::default());
drop(window_state_borrow); drop(window_state_borrow);
let had_marked_text = let had_marked_text =
with_input_handler(this, |input_handler| input_handler.marked_range()) with_input_handler(this, |input_handler| input_handler.marked_text_range())
.flatten() .flatten()
.is_some(); .is_some();
@ -745,15 +749,19 @@ extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) ->
// } // }
let pending_event = window_state.borrow_mut().pending_key_event.take().unwrap(); let pending_event = window_state.borrow_mut().pending_key_event.take().unwrap();
dbg!(&pending_event);
let mut inserted_text = false; let mut inserted_text = false;
let has_marked_text = pending_event.set_marked_text.is_some() let has_marked_text = pending_event.set_marked_text.is_some()
|| with_input_handler(this, |input_handler| input_handler.marked_range()) || with_input_handler(this, |input_handler| input_handler.marked_text_range())
.flatten() .flatten()
.is_some(); .is_some();
if let Some(text) = pending_event.insert_text.as_ref() { if let Some(text) = pending_event.insert_text.as_ref() {
if !text.is_empty() && (had_marked_text || has_marked_text || text.chars().count() > 1) if !text.is_empty() {
{ with_input_handler(this, |input_handler| {
with_input_handler(this, |input_handler| input_handler.edit(None, &text)); input_handler.replace_text_in_range(None, &text)
});
inserted_text = true; inserted_text = true;
} }
} }
@ -762,7 +770,11 @@ extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) ->
if let Some((text, new_selected_range, replacement_range)) = if let Some((text, new_selected_range, replacement_range)) =
pending_event.set_marked_text pending_event.set_marked_text
{ {
input_handler.compose(&text, new_selected_range, replacement_range) input_handler.replace_and_mark_text_in_range(
replacement_range,
&text,
new_selected_range,
)
} else if had_marked_text && !inserted_text { } else if had_marked_text && !inserted_text {
if pending_event.unmark_text { if pending_event.unmark_text {
input_handler.finish_composition(); input_handler.finish_composition();
@ -784,23 +796,12 @@ extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) ->
handled = true; handled = true;
} else { } else {
if let Some(text) = pending_event.insert_text { if let Some(text) = pending_event.insert_text {
// TODO: we may not need this anymore. if text.chars().count() == 1 {
event.input = Some(text); event.keystroke.key = text;
if event.input.as_ref().unwrap().chars().count() == 1 {
event.keystroke.key = event.input.clone().unwrap();
} }
} }
handled = callback(Event::KeyDown(event.clone())); handled = callback(Event::KeyDown(event.clone()));
if !handled {
if let Some(input) = event.input {
with_input_handler(this, |input_handler| {
input_handler.edit(None, &input);
});
handled = true;
}
}
} }
window_state.borrow_mut().event_callback = Some(callback); window_state.borrow_mut().event_callback = Some(callback);
@ -1066,19 +1067,19 @@ extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
} }
extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL { extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
with_input_handler(this, |input_handler| input_handler.marked_range()) with_input_handler(this, |input_handler| input_handler.marked_text_range())
.flatten() .flatten()
.is_some() as BOOL .is_some() as BOOL
} }
extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange { extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
with_input_handler(this, |input_handler| input_handler.marked_range()) with_input_handler(this, |input_handler| input_handler.marked_text_range())
.flatten() .flatten()
.map_or(NSRange::invalid(), |range| range.into()) .map_or(NSRange::invalid(), |range| range.into())
} }
extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange { extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
with_input_handler(this, |input_handler| input_handler.selected_range()) with_input_handler(this, |input_handler| input_handler.selected_text_range())
.flatten() .flatten()
.map_or(NSRange::invalid(), |range| range.into()) .map_or(NSRange::invalid(), |range| range.into())
} }
@ -1108,11 +1109,11 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS
} else { } else {
drop(window_state); drop(window_state);
with_input_handler(this, |input_handler| { with_input_handler(this, |input_handler| {
input_handler.edit(replacement_range.to_range(), text); input_handler.replace_text_in_range(replacement_range.to_range(), text);
}); });
} }
with_input_handler(this, |input_handler| input_handler.unmark()); with_input_handler(this, |input_handler| input_handler.unmark_text());
} }
} }
@ -1145,7 +1146,11 @@ extern "C" fn set_marked_text(
} else { } else {
drop(window_state); drop(window_state);
with_input_handler(this, |input_handler| { with_input_handler(this, |input_handler| {
input_handler.compose(text, selected_range, replacement_range); input_handler.replace_and_mark_text_in_range(
replacement_range,
text,
selected_range,
);
}); });
} }
} }
@ -1181,7 +1186,7 @@ extern "C" fn attributed_substring_for_proposed_range(
return None; return None;
} }
let selected_range = input_handler.selected_range()?; let selected_range = input_handler.selected_text_range()?;
let intersection = cmp::max(requested_range.start, selected_range.start) let intersection = cmp::max(requested_range.start, selected_range.start)
..cmp::min(requested_range.end, selected_range.end); ..cmp::min(requested_range.end, selected_range.end);
if intersection.start >= intersection.end { if intersection.start >= intersection.end {
@ -1199,6 +1204,10 @@ extern "C" fn attributed_substring_for_proposed_range(
.unwrap_or(nil) .unwrap_or(nil)
} }
extern "C" fn do_command_by_selector(_: &Object, _: Sel, _: Sel) {
//
}
async fn synthetic_drag( async fn synthetic_drag(
window_state: Weak<RefCell<WindowState>>, window_state: Weak<RefCell<WindowState>>,
drag_id: usize, drag_id: usize,