use super::state::{CodeEditorState, DragState};
use crate::common::editor_runtime::ScrollMetrics;
pub(crate) fn tick(state: &mut CodeEditorState, delta: f32) -> bool {
if !state.pending_chars.is_empty() {
let batch = std::mem::take(&mut state.pending_chars);
insert_at_every_caret(state, &batch);
state.pending_text_changed = true;
}
let (had_events, single_pos) = state.drain_events();
let caret_active = state.has_focus && state.window_active;
let policy = state.policy.caret_policy;
let caret_visible = state.caret_visible.clone();
let wake = state.frame_wake_at.clone();
state
.blink
.tick(policy, caret_active, &caret_visible, wake.as_ref());
let viewport_width = state.viewport_width;
let viewport_height = state.viewport_height;
let viewport_ready = viewport_width > 0.0 && viewport_height > 0.0;
if viewport_ready {
state.engine.set_viewport(viewport_width, viewport_height);
}
let layout_stale = viewport_ready && !state.engine.has_full_layout();
if viewport_ready && (state.needs_full_layout || layout_stale) {
let flow = state.document.snapshot_flow();
state.engine.layout_full(&flow);
state.needs_full_layout = false;
state.last_relayout_block_id = None;
state.content_dirty = true;
state.pending_full_render = true;
} else if viewport_ready && let Some(pos) = single_pos {
let mask = teksilo_text::text_document::HighlightMask::all();
match state
.engine
.relayout_block_snapshot(&state.document, pos, &mask)
{
Ok(block_id) => {
state.last_relayout_block_id = Some(block_id);
state.content_dirty = true;
}
Err(_) => {
state.needs_full_layout = true;
}
}
}
if state.pending_recolor {
if !state.needs_full_layout
&& !state.pending_full_render
&& viewport_ready
&& state.engine.has_full_layout()
{
let flow = state.document.snapshot_flow();
state.engine.apply_paint_highlights(&flow);
state.content_dirty = true;
state.pending_full_render = true;
}
state.pending_recolor = false;
}
if state.engine.has_full_layout() {
let caret_on = state.caret_visible.get() && state.has_focus && state.window_active;
let affinity = state.cursor_affinity;
let cursors: Vec<teksilo_text::CursorDisplay> = state
.all_carets()
.map(|c| teksilo_text::CursorDisplay {
position: c.position(),
anchor: c.anchor(),
affinity,
visible: caret_on,
selected_cells: Vec::new(),
})
.collect();
state.engine.set_cursors(&cursors);
}
let metrics = ScrollMetrics::compute(
state.engine.content_height(),
state.engine.max_content_width(),
viewport_width,
viewport_height,
);
let mut drag_active = false;
if let DragState::Selecting {
auto_scroll_v_per_s,
} = state.drag_state
&& auto_scroll_v_per_s.abs() > 0.0
{
drag_active = true;
let new_y = (state.scroll_y.get() + auto_scroll_v_per_s * delta).clamp(0.0, metrics.max_y);
state.scroll_y.set_if_changed(new_y);
}
metrics.publish(
&state.scroll_x,
&state.scroll_y,
&state.max_scroll_x,
&state.max_scroll_y,
&state.viewport_ratio_x,
&state.viewport_ratio_y,
);
if state.debounce.tick(delta) {
state.pending_text_changed = false;
if let Some((cu, cr)) = state.pending_undo_redo.take() {
state.can_undo.set_if_changed(cu);
state.can_redo.set_if_changed(cr);
}
}
let debounce_pending = state.pending_text_changed || state.pending_undo_redo.is_some();
had_events || debounce_pending || drag_active
}
pub(crate) fn insert_at_every_caret(state: &mut CodeEditorState, text: &str) {
state.merge_collided_carets();
if state.extra_carets.is_empty() {
let _ = state.cursor.insert_text(text);
return;
}
state.cursor.begin_edit_block();
let mut ordered: Vec<usize> = (0..=state.extra_carets.len()).collect();
ordered.sort_by_key(|&i| std::cmp::Reverse(caret_position(state, i)));
for i in ordered {
let cursor = caret_mut(state, i);
let _ = cursor.insert_text(text);
}
state.cursor.end_edit_block();
}
fn caret_position(state: &CodeEditorState, i: usize) -> usize {
if i == 0 {
state.cursor.position()
} else {
state.extra_carets[i - 1].position()
}
}
fn caret_mut(
state: &mut CodeEditorState,
i: usize,
) -> &mut teksilo_text::text_document::TextCursor {
if i == 0 {
&mut state.cursor
} else {
&mut state.extra_carets[i - 1]
}
}