use std::sync::Arc;
use slate_reactive::Signal;
use crate::event::{
self, ElementImeCommitHandler, ElementImePreeditHandler, ElementKeyHandler,
ElementTextInputHandler, EventCtx, ImeCommitEvent, ImePreeditEvent, Key, KeyEvent, NamedKey,
TextInputEvent,
};
use crate::ime::Preedit;
use crate::elements::text_edit::grapheme::{
insert_text_at, next_grapheme_boundary, prev_grapheme_boundary,
};
use crate::elements::text_edit::ops::{
MotionDir, apply_motion, apply_visual_motion, delete_selection, record_edit, reset_blink,
};
use crate::elements::text_edit::shortcuts;
use crate::elements::text_edit::undo::EditOp;
use super::nav;
pub(super) fn build_key_down_handler(value: Signal<String>) -> ElementKeyHandler {
Arc::new(move |ev: &KeyEvent, cx: &mut EventCtx| {
let id = match cx.element_id() {
Some(i) => i,
None => return,
};
let state_rc = match cx.ime_state(id) {
Some(s) => s,
None => return,
};
if shortcuts::handle_command_shortcut(ev, cx, &state_rc, &value, true) {
return;
}
{
let state = state_rc.borrow();
if state.preedit.is_some() {
return;
}
}
let shift = ev.modifiers.shift;
let new_text: Option<String> = match &ev.key {
Key::Named(NamedKey::Enter) => {
let t = {
let mut state = state_rc.borrow_mut();
nav::insert_newline(&mut state)
};
cx.stop_propagation();
Some(t)
}
Key::Named(NamedKey::Backspace) => {
let mut state = state_rc.borrow_mut();
debug_assert!(
state.text.is_char_boundary(state.caret),
"TextArea caret not on char boundary"
);
state.desired_x = None;
state.caret_affinity = slate_text::Affinity::Downstream;
if state.selection_anchor.is_some_and(|a| a != state.caret) {
delete_selection(&mut state);
record_edit(&mut state, EditOp::Discrete);
reset_blink(&mut state);
cx.stop_propagation();
Some(state.text.clone())
} else {
state.selection_anchor = None;
let old_caret = state.caret;
let new_caret = prev_grapheme_boundary(&state.text, old_caret);
if new_caret < old_caret {
state.text.replace_range(new_caret..old_caret, "");
state.caret = new_caret;
record_edit(&mut state, EditOp::Backspace);
reset_blink(&mut state);
cx.stop_propagation();
Some(state.text.clone())
} else {
None
}
}
}
Key::Named(NamedKey::ArrowLeft) | Key::Named(NamedKey::ArrowRight) => {
let move_right = matches!(ev.key, Key::Named(NamedKey::ArrowRight));
if event::is_line_edge_modifier(&ev.modifiers) {
let layout = state_rc.borrow().last_layout.clone();
if let Some(layout) = layout {
let mut state = state_rc.borrow_mut();
nav::move_line_edge(&mut state, &layout, move_right, shift);
}
cx.stop_propagation();
return;
}
let layout = state_rc.borrow().last_layout.clone();
{
let mut state = state_rc.borrow_mut();
state.desired_x = None;
let run_bearing = layout.as_ref().is_some_and(|l| {
let idx = l.line_for_byte(state.caret);
l.lines.get(idx).is_some_and(|v| !v.line.runs.is_empty())
});
if run_bearing {
let layout = layout.as_ref().unwrap();
let idx = layout.line_for_byte(state.caret);
let stepped = apply_visual_motion(
&mut state,
&layout.lines[idx].line,
move_right,
shift,
);
if !stepped {
nav::visual_cross_line(&mut state, layout, move_right, shift);
}
} else {
state.caret_affinity = slate_text::Affinity::Downstream;
let dir = if move_right {
MotionDir::Right
} else {
MotionDir::Left
};
apply_motion(&mut state, dir, shift, |s| {
s.caret = if move_right {
next_grapheme_boundary(&s.text, s.caret)
} else {
prev_grapheme_boundary(&s.text, s.caret)
};
});
}
reset_blink(&mut state);
state.undo.mark_motion();
}
cx.stop_propagation();
None
}
Key::Named(NamedKey::ArrowUp) | Key::Named(NamedKey::ArrowDown) => {
let down = matches!(ev.key, Key::Named(NamedKey::ArrowDown));
let layout = state_rc.borrow().last_layout.clone();
if let Some(layout) = layout {
let mut state = state_rc.borrow_mut();
nav::move_vertical(&mut state, &layout, down, shift);
}
cx.stop_propagation();
None
}
Key::Named(NamedKey::Home) | Key::Named(NamedKey::End) => {
let to_end = matches!(ev.key, Key::Named(NamedKey::End));
let layout = state_rc.borrow().last_layout.clone();
if let Some(layout) = layout {
let mut state = state_rc.borrow_mut();
nav::move_line_edge(&mut state, &layout, to_end, shift);
}
cx.stop_propagation();
None
}
_ => None,
};
if let Some(t) = new_text {
value.set(t);
}
})
}
pub(super) fn build_text_input_handler(value: Signal<String>) -> ElementTextInputHandler {
Arc::new(move |ev: &TextInputEvent, cx: &mut EventCtx| {
let id = match cx.element_id() {
Some(i) => i,
None => return,
};
let state_rc = match cx.ime_state(id) {
Some(s) => s,
None => return,
};
if ev.text == "\n" || ev.text == "\r" {
return;
}
{
let state = state_rc.borrow();
if state.preedit.is_some() {
return;
}
}
let new_text = {
let mut state = state_rc.borrow_mut();
debug_assert!(
state.text.is_char_boundary(state.caret),
"TextArea caret not on char boundary before text insert"
);
state.desired_x = None;
state.caret_affinity = slate_text::Affinity::Downstream;
let had_selection = state.selection_anchor.is_some_and(|a| a != state.caret);
delete_selection(&mut state);
let old_caret = state.caret;
state.caret = insert_text_at(&mut state.text, old_caret, &ev.text);
let op = if had_selection {
EditOp::Discrete
} else {
EditOp::Insert
};
record_edit(&mut state, op);
reset_blink(&mut state);
state.text.clone()
};
cx.stop_propagation();
value.set(new_text);
})
}
pub(super) fn build_ime_preedit_handler() -> ElementImePreeditHandler {
Arc::new(move |ev: &ImePreeditEvent, cx: &mut EventCtx| {
let id = match cx.element_id() {
Some(i) => i,
None => return,
};
let state_rc = match cx.ime_state(id) {
Some(s) => s,
None => return,
};
{
let mut state = state_rc.borrow_mut();
if ev.text.is_empty() {
state.preedit = None;
} else {
state.preedit = Some(Preedit {
text: ev.text.clone(),
cursor_byte_offset: ev.cursor_byte_offset,
selection: ev.selection.clone(),
});
}
}
cx.stop_propagation();
})
}
pub(super) fn build_ime_commit_handler(value: Signal<String>) -> ElementImeCommitHandler {
Arc::new(move |ev: &ImeCommitEvent, cx: &mut EventCtx| {
let id = match cx.element_id() {
Some(i) => i,
None => return,
};
let state_rc = match cx.ime_state(id) {
Some(s) => s,
None => return,
};
let new_text: Option<String> = {
let mut state = state_rc.borrow_mut();
if ev.text.is_empty() {
state.preedit = None;
None
} else {
debug_assert!(
state.text.is_char_boundary(state.caret),
"TextArea caret not on char boundary before ime commit"
);
state.desired_x = None;
state.caret_affinity = slate_text::Affinity::Downstream;
delete_selection(&mut state);
let old_caret = state.caret;
state.caret = insert_text_at(&mut state.text, old_caret, &ev.text);
state.preedit = None;
record_edit(&mut state, EditOp::Discrete);
reset_blink(&mut state);
Some(state.text.clone())
}
};
cx.stop_propagation();
if let Some(t) = new_text {
value.set(t);
}
})
}