use std::sync::{Arc, Mutex};
use crate::tdom::node::NodeId;
mod cmd;
mod edit;
mod field;
mod focus;
mod key_info;
mod mouse;
mod page;
mod refresh;
mod select;
pub use cmd::{DirtyInfo, DrainedBatch, RenderCmd, SelectionMode};
pub use edit::{
apply_input_key, apply_input_word_key, is_input_field_key, next_word_boundary,
paste_into_field, prev_word_boundary, selection_range,
};
pub use field::{
KEY_GATE_TIMEOUT, KeyGate, apply_textarea_key, collect_input_ids, number_input_allows_char,
};
pub use focus::apply_focus_requests;
pub use key_info::key_event_info;
pub use mouse::{handle_click, handle_mouse_down, handle_mouse_move, handle_mouse_up};
pub use refresh::trigger_refresh;
pub use select::{OverlayKind, SelectOpenState};
use field::clear_text_selection;
const SCROLL_LINES_PER_TICK: u32 = 3;
const ZOOM_STEP: f32 = 0.1;
const ZOOM_MIN: f32 = 0.2;
const ZOOM_MAX: f32 = 5.0;
const TEXT_SEL_DRAG_THRESHOLD: u32 = 2;
#[derive(Debug)]
pub struct InputState {
pub scroll_y: u32,
pub zoom: f32,
pub pressed: Option<(u16, u16)>,
pub focused_input: Option<NodeId>,
pub focused_is_textarea: bool,
pub input_value: String,
pub input_cursor: usize,
pub input_sel_anchor: Option<usize>,
pub input_clipboard: String,
pub drag_selecting: bool,
pub last_mouse: Option<(u16, u16)>,
pub resize_pending: bool,
pub text_sel_anchor: Option<(u32, u32)>,
pub text_sel_end: Option<(u32, u32)>,
pub text_sel_mode: SelectionMode,
text_sel_armed: bool,
pub copy_flow: bool,
pub copy_block: bool,
pub selection_text: Arc<Mutex<String>>,
pub open_select: Option<SelectOpenState>,
pub next_key_id: u32,
pub key_gate: Option<KeyGate>,
}
impl InputState {
pub fn new() -> Self {
Self {
scroll_y: 0,
zoom: 1.0,
pressed: None,
focused_input: None,
focused_is_textarea: false,
input_value: String::new(),
input_cursor: 0,
input_sel_anchor: None,
input_clipboard: String::new(),
drag_selecting: false,
last_mouse: None,
resize_pending: false,
text_sel_anchor: None,
text_sel_end: None,
text_sel_mode: SelectionMode::Flow,
text_sel_armed: false,
copy_flow: true,
copy_block: true,
selection_text: Arc::new(Mutex::new(String::new())),
open_select: None,
next_key_id: 0,
key_gate: None,
}
}
pub fn invalidate_viewport(&mut self, cmds: &mut Vec<RenderCmd>) {
self.pressed = None;
self.drag_selecting = false;
clear_text_selection(self, cmds);
if let Some(open) = self.open_select.take() {
cmds.push(RenderCmd::SelectClose {
node_id: open.node_id,
});
}
}
pub fn drop_stale_press(&mut self) -> bool {
if self.pressed.take().is_some() {
self.drag_selecting = false;
true
} else {
false
}
}
pub fn request_resize_revalidation(&mut self) {
self.resize_pending = true;
}
pub fn take_resize_revalidation(&mut self) -> bool {
std::mem::take(&mut self.resize_pending)
}
pub fn clamp_scroll_to(&mut self, max_scroll: u32) {
if self.scroll_y > max_scroll {
self.scroll_y = max_scroll;
}
}
}
impl Default for InputState {
fn default() -> Self {
Self::new()
}
}
#[cfg(windows)]
pub(crate) fn reapply_console_modes() {
if let Err(e) = crossterm::terminal::enable_raw_mode() {
log::error!("Failed to re-enable raw mode after resize: {}", e);
}
if let Err(e) = crossterm::execute!(
std::io::sink(),
crossterm::event::EnableMouseCapture,
crossterm::event::EnableBracketedPaste
) {
log::error!("Failed to re-enable input modes after resize: {}", e);
}
}
#[cfg(not(windows))]
pub(crate) fn reapply_console_modes() {}