use teksilo_canvas::Point;
use teksilo_core::event::{EventResponse, PointerButton, WidgetEvent};
use teksilo_core::widget::EventContext;
use teksilo_text::text_document::{MoveMode, SelectionType};
use super::state::{DragState, SharedState, sync_cursor_signals};
pub(crate) fn handle_pointer_event(
state: &SharedState,
event: &WidgetEvent,
ctx: &mut EventContext,
) -> EventResponse {
match event {
WidgetEvent::PointerDown {
position,
button,
modifiers,
} => {
if *button != PointerButton::Primary {
return EventResponse::Ignored;
}
let shift = modifiers.shift();
let hit = hit_test(state, position);
let Some(hit_pos) = hit else {
return EventResponse::Ignored;
};
{
let mut st = state.borrow_mut();
let mode = if shift {
MoveMode::KeepAnchor
} else {
MoveMode::MoveAnchor
};
st.cursor.set_position(hit_pos, mode);
st.drag_state = DragState::Selecting;
}
sync_cursor_signals(state);
ctx.request_frame();
EventResponse::Ignored
}
WidgetEvent::PointerMove { position } => {
let is_dragging = matches!(state.borrow().drag_state, DragState::Selecting);
if !is_dragging {
return EventResponse::Ignored;
}
let hit = hit_test(state, position);
if let Some(hit_pos) = hit {
let st = state.borrow();
st.cursor.set_position(hit_pos, MoveMode::KeepAnchor);
drop(st);
sync_cursor_signals(state);
}
ctx.request_frame();
EventResponse::Handled
}
WidgetEvent::PointerUp { .. } => {
state.borrow_mut().drag_state = DragState::Idle;
EventResponse::Ignored
}
_ => EventResponse::Ignored,
}
}
pub(crate) fn handle_double_tap(state: &SharedState, pos: Point, ctx: &mut EventContext) {
tap_select(state, &pos, SelectionType::WordUnderCursor);
ctx.request_frame();
}
pub(crate) fn handle_triple_tap(state: &SharedState, pos: Point, ctx: &mut EventContext) {
tap_select(state, &pos, SelectionType::Document);
ctx.request_frame();
}
fn tap_select(state: &SharedState, pos: &Point, kind: SelectionType) {
let hit = hit_test(state, pos);
if let Some(hit_pos) = hit {
let st = state.borrow();
st.cursor.set_position(hit_pos, MoveMode::MoveAnchor);
st.cursor.select(kind);
drop(st);
sync_cursor_signals(state);
}
}
fn hit_test(state: &SharedState, local: &Point) -> Option<usize> {
let st = state.borrow();
let text_viewport = (st.viewport_width - st.suffix_width).max(0.0);
let doc_end = st
.document
.to_plain_text()
.unwrap_or_default()
.chars()
.count();
if st.suffix_width > 0.0 && local.x >= text_viewport {
return Some(doc_end);
}
let adjusted_x = local.x + st.scroll_x;
if let Some(result) = st.engine.hit_test(adjusted_x, local.y) {
return Some(result.position);
}
if local.x >= 0.0 && local.x < text_viewport {
return Some(doc_end);
}
None
}
pub(crate) fn reposition_caret_for_context_menu(state: &SharedState, position: Point) {
let local = {
let st = state.borrow();
Point::new(
position.x - st.viewport_origin.x,
position.y - st.viewport_origin.y,
)
};
let Some(hit_pos) = hit_test(state, &local) else {
return;
};
let st = state.borrow();
let anchor = st.cursor.anchor();
let caret = st.cursor.position();
let (lo, hi) = (anchor.min(caret), anchor.max(caret));
let in_selection = lo != hi && hit_pos >= lo && hit_pos <= hi;
drop(st);
if !in_selection {
let st = state.borrow();
st.cursor.set_position(hit_pos, MoveMode::MoveAnchor);
drop(st);
sync_cursor_signals(state);
}
}