use std::{
ops::Range,
time::{Duration, Instant},
};
use gpui::{Pixels, Point};
use super::Runtime;
use crate::{
android::{
self,
selection::{
DragPhase, Handle, HandleAnchors, HandlesChange, ToolbarChange, ViewRect, drag_range,
handles_change, long_press_hits, toolbar_change, toolbar_for_drag,
},
},
platform::window::WindowInner,
};
const LONG_PRESS_WINDOW: Duration = Duration::from_secs(1);
#[derive(Default)]
pub(super) struct InputSync {
active: bool,
selection: Option<Range<usize>>,
autofill: Option<(String, ViewRect)>,
long_press: Option<(Point<Pixels>, Instant)>,
handles_shown: bool,
dragging: Option<Handle>,
drag_ended: bool,
}
impl InputSync {
pub(super) fn long_press(&mut self, position: Point<Pixels>) {
self.long_press = Some((position, Instant::now()));
}
}
impl Runtime {
pub(in crate::runtime) fn sync_text_input(&self) {
let Some(inner) = self.attached() else { return };
let (focused, dirty, scale) = {
let mut state = inner.state.borrow_mut();
(
state.text_input.focused,
std::mem::take(&mut state.text_input.dirty),
state.scale_factor,
)
};
let mut sync = self.android.input.borrow_mut();
if sync
.long_press
.is_some_and(|(_, at)| at.elapsed() > LONG_PRESS_WINDOW)
{
sync.long_press = None;
}
if !focused {
if sync.active {
*sync = InputSync {
long_press: sync.long_press.take(),
..InputSync::default()
};
android::hide_selection_toolbar();
android::hide_selection_handles();
android::clear_autofill_input();
}
return;
}
let _guard = self.enter();
let mut selection = None;
inner.with_input_handler(|handler| selection = handler.selected_text_range(true));
let Some(selection) = selection else { return };
let range = selection.range;
if !dirty && sync.long_press.is_none() && sync.selection.as_ref() == Some(&range) {
return;
}
let mut sample = None;
inner.with_input_handler(|handler| {
let mut adjusted = None;
let text = handler
.text_for_range(0..usize::MAX, &mut adjusted)
.unwrap_or_default();
let start = handler.bounds_for_range(range.start..range.start);
let end = handler.bounds_for_range(range.end..range.end);
sample = handler
.bounds_for_range(range.clone())
.map(|bounds| (text, bounds, start, end));
});
let Some((text, bounds, start, end)) = sample else {
return;
};
let long_press = sync
.long_press
.take()
.is_some_and(|(position, _)| long_press_hits(position, bounds));
let change = toolbar_change(sync.selection.as_ref(), &range, long_press);
let dragging = sync.dragging;
let change = toolbar_for_drag(
dragging.is_some(),
std::mem::take(&mut sync.drag_ended),
&range,
change,
);
let handles = handles_change(
sync.selection.as_ref(),
&range,
long_press,
dragging,
sync.handles_shown,
);
sync.selection = Some(range);
sync.active = true;
let rect = ViewRect::from_bounds(bounds, scale);
let current = (text, rect);
if sync.autofill.as_ref() != Some(¤t) {
android::set_autofill_input(¤t.0, rect);
sync.autofill = Some(current);
}
match change {
ToolbarChange::Show { has_selection } => {
android::show_selection_toolbar(rect, has_selection)
}
ToolbarChange::Hide => android::hide_selection_toolbar(),
ToolbarChange::Keep => {}
}
match handles {
HandlesChange::Show { collapsed } => {
if let (Some(start), Some(end)) = (start, end) {
android::show_selection_handles(
HandleAnchors::from_bounds(start, end, scale),
collapsed,
);
sync.handles_shown = true;
}
}
HandlesChange::Hide => {
android::hide_selection_handles();
sync.handles_shown = false;
}
HandlesChange::Keep => {}
}
}
pub(in crate::runtime) fn handle_drag(
&self,
inner: &WindowInner,
handle: Handle,
phase: DragPhase,
position: Point<Pixels>,
) {
{
let mut sync = self.android.input.borrow_mut();
match phase {
DragPhase::Started => {
sync.dragging = Some(handle);
sync.drag_ended = false;
}
DragPhase::Moved => {}
DragPhase::Ended => {
sync.dragging = None;
sync.drag_ended = true;
}
}
}
if phase == DragPhase::Moved {
inner.with_input_handler(|input| {
let Some(current) = input.selected_text_range(true) else {
return;
};
let Some(index) = input.character_index_for_point(position) else {
return;
};
let range = drag_range(handle, index, ¤t.range, input.text_length_utf16());
if range != current.range {
input.set_selected_text_range(range);
}
});
}
inner.text_input_changed(None);
}
}