use std::time::Duration;
use crossterm::event::{self, Event, KeyCode, KeyEventKind, MouseButton, MouseEventKind};
use crate::events::HitMap;
use crate::layout::layoutbox::geometry::select_item_indices;
use crate::script::ScriptMsg;
use crate::tdom::node::NodeArena;
use super::InputState;
use super::cmd::RenderCmd;
use super::edit::{
apply_ctrl_shortcut, apply_input_key, apply_input_word_key, apply_shift_navigation,
is_input_field_key, paste_into_field,
};
use super::field::{
KeyGate, apply_textarea_key, focused_form, is_number_input, is_readonly_input,
number_input_allows_char, textarea_content_width,
};
use super::focus::{blur_input, push_input_render};
use super::key_info::key_event_info;
use super::mouse::{canvas_pos, handle_mouse_down, handle_mouse_move, handle_mouse_up};
use super::page::page_level_builtins;
use super::select::{OverlayKind, close_select, refresh_datalist};
use super::{SCROLL_LINES_PER_TICK, reapply_console_modes};
pub fn trigger_refresh(
state: &mut InputState,
rows: u16,
max_scroll: u32,
hit_map: Option<&HitMap>,
arena: Option<&NodeArena>,
msgs: &mut Vec<ScriptMsg>,
timeout: Option<Duration>,
) -> Vec<RenderCmd> {
let mut cmds = Vec::new();
if let Some(t) = timeout {
if !event::poll(t).unwrap_or(false) {
return cmds;
}
}
match event::read() {
Ok(Event::Key(key)) => {
if key.kind == KeyEventKind::Release {
if arena.is_some() {
msgs.push(ScriptMsg::KeyUp {
target: state.focused_input,
key: key_event_info(&key),
});
}
return cmds;
}
if arena.is_some() {
state.next_key_id = state.next_key_id.wrapping_add(1);
msgs.push(ScriptMsg::KeyDown {
key_id: state.next_key_id,
target: state.focused_input,
key: key_event_info(&key),
});
}
if let Some(open) = state.open_select.as_ref() {
let Some(arena) = arena else {
return cmds;
};
let node_id = open.node_id;
let kind = open.kind;
match key.code {
KeyCode::Up | KeyCode::Down => {
let dir: i32 = if key.code == KeyCode::Up { -1 } else { 1 };
if let Some(open) = &mut state.open_select {
let items = select_item_indices(&open.rows);
if !items.is_empty() {
let pos =
items.iter().position(|&i| i == open.highlight).unwrap_or(0);
let next = items[(((pos as i32) + dir)
.rem_euclid(items.len() as i32))
as usize];
open.highlight = next;
cmds.push(RenderCmd::SelectMove {
node_id,
highlight: next,
});
}
}
return cmds;
}
KeyCode::Enter => {
if kind == OverlayKind::Select {
let pick = state.open_select.as_ref().map(|o| o.highlight);
close_select(state, arena, pick, msgs, &mut cmds);
} else {
let pick_value = state.open_select.as_ref().and_then(|o| {
o.options.get(o.highlight).map(|opt| opt.value.clone())
});
if let Some(open) = state.open_select.take() {
cmds.push(RenderCmd::SelectClose {
node_id: open.node_id,
});
}
if let Some(value) = pick_value {
state.input_value = value;
state.input_cursor = state.input_value.chars().count();
state.input_sel_anchor = None;
push_input_render(state, node_id, &mut cmds);
msgs.push(ScriptMsg::InputKeystroke { node_id });
}
}
return cmds;
}
KeyCode::Esc => {
close_select(state, arena, None, msgs, &mut cmds);
return cmds;
}
_ => {
close_select(state, arena, None, msgs, &mut cmds);
if kind == OverlayKind::Select {
return cmds;
}
}
}
}
if state.focused_input.is_some() {
let Some(focused) = state.focused_input else {
return cmds;
};
if key.code == KeyCode::Esc
|| (key.code == KeyCode::Enter && !state.focused_is_textarea)
{
if key.code == KeyCode::Enter
&& let Some(form_id) = focused_form(arena, focused)
{
msgs.push(ScriptMsg::FormSubmit { form_id });
}
blur_input(state, msgs, &mut cmds);
return cmds;
}
let content_width = textarea_content_width(arena, focused);
if (key.modifiers.contains(event::KeyModifiers::CONTROL)
|| key.modifiers.contains(event::KeyModifiers::ALT))
&& matches!(
key.code,
KeyCode::Left
| KeyCode::Right
| KeyCode::Home
| KeyCode::End
| KeyCode::Backspace
| KeyCode::Delete
)
{
let mut code = key.code;
if is_readonly_input(arena, focused)
&& matches!(code, KeyCode::Backspace | KeyCode::Delete)
{
code = KeyCode::Null;
}
if apply_input_word_key(
&mut state.input_value,
&mut state.input_cursor,
&mut state.input_sel_anchor,
code,
key.modifiers,
) {
push_input_render(state, focused, &mut cmds);
if arena.is_some() {
msgs.push(ScriptMsg::InputKeystroke { node_id: focused });
}
refresh_datalist(state, hit_map, arena, &mut cmds);
}
return cmds;
}
if key.modifiers.contains(event::KeyModifiers::CONTROL) {
let mut code = key.code;
if is_readonly_input(arena, focused)
&& matches!(code, KeyCode::Char('x') | KeyCode::Char('v'))
{
code = KeyCode::Null;
}
if apply_ctrl_shortcut(state, code) {
push_input_render(state, focused, &mut cmds);
if arena.is_some() {
msgs.push(ScriptMsg::InputKeystroke { node_id: focused });
}
refresh_datalist(state, hit_map, arena, &mut cmds);
}
return cmds;
}
if key.modifiers.contains(event::KeyModifiers::SHIFT) {
if apply_shift_navigation(state, key.code) {
push_input_render(state, focused, &mut cmds);
}
return cmds;
}
if state.focused_is_textarea
&& matches!(key.code, KeyCode::Enter | KeyCode::Up | KeyCode::Down)
{
if apply_textarea_key(
&mut state.input_value,
&mut state.input_cursor,
content_width,
key.code,
) {
state.input_sel_anchor = None;
push_input_render(state, focused, &mut cmds);
if arena.is_some() {
msgs.push(ScriptMsg::InputKeystroke { node_id: focused });
}
}
return cmds;
}
if is_input_field_key(key.code) {
let mut code = key.code;
if !state.focused_is_textarea
&& is_number_input(arena, focused)
&& let KeyCode::Char(c) = code
&& !number_input_allows_char(c)
{
code = KeyCode::Null;
}
if is_readonly_input(arena, focused)
&& matches!(
code,
KeyCode::Char(_) | KeyCode::Backspace | KeyCode::Delete
)
{
code = KeyCode::Null;
}
let changed = if state.focused_is_textarea {
apply_textarea_key(
&mut state.input_value,
&mut state.input_cursor,
content_width,
code,
)
} else {
apply_input_key(&mut state.input_value, &mut state.input_cursor, code)
};
if changed {
state.input_sel_anchor = None;
push_input_render(state, focused, &mut cmds);
if arena.is_some() {
msgs.push(ScriptMsg::InputKeystroke { node_id: focused });
}
refresh_datalist(state, hit_map, arena, &mut cmds);
}
return cmds;
}
}
let builtin = page_level_builtins(state, key, rows, max_scroll, arena, msgs);
if arena.is_some()
&& !builtin.is_empty()
&& !matches!(key.code, KeyCode::Tab | KeyCode::BackTab)
{
state.key_gate = Some(KeyGate {
id: state.next_key_id,
cmds: builtin,
});
return cmds;
}
cmds.extend(builtin);
cmds
}
Ok(Event::Mouse(mouse)) => {
let ctrl = mouse.modifiers.contains(event::KeyModifiers::CONTROL);
state.last_mouse = Some((mouse.column, mouse.row));
match mouse.kind {
MouseEventKind::ScrollUp => {
if state.open_select.is_some()
&& let Some(arena) = arena
{
close_select(state, arena, None, msgs, &mut cmds);
}
if ctrl {
return cmds;
}
if state.scroll_y == 0 {
return cmds;
}
state.scroll_y = state.scroll_y.saturating_sub(SCROLL_LINES_PER_TICK);
cmds.push(RenderCmd::Rerender {
scroll_y: state.scroll_y,
});
}
MouseEventKind::ScrollDown => {
if state.open_select.is_some()
&& let Some(arena) = arena
{
close_select(state, arena, None, msgs, &mut cmds);
}
if ctrl {
return cmds;
}
if state.scroll_y >= max_scroll {
return cmds;
}
state.scroll_y = state.scroll_y.saturating_add(SCROLL_LINES_PER_TICK);
cmds.push(RenderCmd::Rerender {
scroll_y: state.scroll_y,
});
}
MouseEventKind::Down(button) => {
if let (Some(hit_map), Some(arena)) = (hit_map, arena) {
state.pressed = Some((mouse.column, mouse.row));
let (x, y) = canvas_pos(state, mouse.column, mouse.row);
handle_mouse_down(state, hit_map, arena, x, y, button, msgs, &mut cmds);
}
}
MouseEventKind::Up(MouseButton::Left) => {
if let (Some(hit_map), Some(arena)) = (hit_map, arena) {
handle_mouse_up(
state,
hit_map,
arena,
(mouse.column, mouse.row),
MouseButton::Left,
msgs,
&mut cmds,
);
} else {
state.pressed = None;
state.drag_selecting = false;
}
}
MouseEventKind::Up(btn @ (MouseButton::Right | MouseButton::Middle)) => {
if let (Some(hit_map), Some(arena)) = (hit_map, arena) {
handle_mouse_up(
state,
hit_map,
arena,
(mouse.column, mouse.row),
btn,
msgs,
&mut cmds,
);
} else {
state.pressed = None;
state.drag_selecting = false;
}
}
MouseEventKind::Drag(_) | MouseEventKind::Moved => {
if let (Some(hit_map), Some(_)) = (hit_map, arena) {
if !matches!(mouse.kind, MouseEventKind::Drag(_)) {
state.drop_stale_press();
}
let (x, y) = canvas_pos(state, mouse.column, mouse.row);
handle_mouse_move(state, hit_map, x, y, msgs, &mut cmds);
}
}
_ => {}
}
cmds
}
Ok(Event::Resize(_, _new_rows)) => {
reapply_console_modes();
state.invalidate_viewport(&mut cmds);
state.request_resize_revalidation();
state.clamp_scroll_to(max_scroll);
cmds.push(RenderCmd::Redraw {
scroll_y: state.scroll_y,
zoom: state.zoom,
});
cmds
}
Ok(Event::Paste(pasted)) => {
if let Some(focused) = state.focused_input
&& !is_readonly_input(arena, focused)
&& paste_into_field(state, &pasted, focused, &mut cmds)
&& arena.is_some()
{
msgs.push(ScriptMsg::InputKeystroke { node_id: focused });
}
cmds
}
_ => cmds,
}
}