use nalgebra_glm::Vec2;
use winit::keyboard::KeyCode;
use crate::ecs::world::World;
use super::InteractionSnapshot;
use super::text_cursor::{byte_index_at_x, measure_text_width, validate_widget_text};
use super::text_edit::EditBuffer;
use crate::ecs::ui::components::{CharStyle, TextSnapshot};
use crate::prelude::*;
pub(super) fn handle_text_input(
world: &mut World,
entity: freecs::Entity,
interaction: &InteractionSnapshot,
data: &crate::ecs::ui::components::UiTextInputData,
ctx: &super::TextEditContext<'_>,
) {
let focused_entity = ctx.focused_entity;
let frame_chars = ctx.frame_chars;
let frame_keys = ctx.frame_keys;
let ctrl_held = ctx.ctrl_held;
let shift_held = ctx.shift_held;
let mouse_position = ctx.mouse_position;
let current_time = ctx.current_time;
let dpi_scale = ctx.dpi_scale;
let is_focused = focused_entity == Some(entity);
let mut changed = false;
let mut cursor_blink_timer = data.cursor_blink_timer;
let mut scroll_offset = data.scroll_offset;
let mut clear_focus = false;
let mut undo_stack = data.undo_stack.clone();
let input_mask = &data.input_mask;
let max_length = data.max_length;
let mut buffer = EditBuffer::new(
data.text.clone(),
data.cursor_position,
data.selection_start,
false,
);
if is_focused {
let mut needs_snapshot = false;
for character in frame_chars {
if *character >= ' ' && input_mask.accepts(*character) {
if let Some(max) = max_length {
let removed = buffer
.selection_range()
.map(|(min, max_sel)| max_sel - min)
.unwrap_or(0);
if buffer.length() - removed + 1 > max {
continue;
}
}
if !needs_snapshot {
std::sync::Arc::make_mut(&mut undo_stack).push_initial(snapshot(&buffer));
needs_snapshot = true;
}
buffer.insert_char(*character, CharStyle::default());
changed = true;
cursor_blink_timer = current_time;
}
}
if needs_snapshot {
std::sync::Arc::make_mut(&mut undo_stack).push(snapshot(&buffer), current_time);
}
for (key, is_pressed) in frame_keys {
if !is_pressed {
continue;
}
match key {
KeyCode::Backspace => {
std::sync::Arc::make_mut(&mut undo_stack).push_initial(snapshot(&buffer));
if buffer.backspace(ctrl_held) {
std::sync::Arc::make_mut(&mut undo_stack)
.push(snapshot(&buffer), current_time);
changed = true;
}
cursor_blink_timer = current_time;
}
KeyCode::Delete => {
std::sync::Arc::make_mut(&mut undo_stack).push_initial(snapshot(&buffer));
if buffer.delete_forward(ctrl_held) {
std::sync::Arc::make_mut(&mut undo_stack)
.push(snapshot(&buffer), current_time);
changed = true;
}
cursor_blink_timer = current_time;
}
KeyCode::ArrowLeft => {
buffer.move_left(ctrl_held, shift_held);
cursor_blink_timer = current_time;
}
KeyCode::ArrowRight => {
buffer.move_right(ctrl_held, shift_held);
cursor_blink_timer = current_time;
}
KeyCode::Home => {
buffer.move_home(shift_held);
cursor_blink_timer = current_time;
}
KeyCode::End => {
buffer.move_end(shift_held);
cursor_blink_timer = current_time;
}
KeyCode::KeyA if ctrl_held => {
buffer.select_all();
cursor_blink_timer = current_time;
}
KeyCode::KeyZ if ctrl_held => {
if let Some(restored) = std::sync::Arc::make_mut(&mut undo_stack).undo() {
buffer.text = restored.text.clone();
buffer.cursor = restored.cursor_position;
buffer.selection = restored.selection_start;
changed = true;
cursor_blink_timer = current_time;
}
}
KeyCode::KeyY if ctrl_held => {
if let Some(restored) = std::sync::Arc::make_mut(&mut undo_stack).redo() {
buffer.text = restored.text.clone();
buffer.cursor = restored.cursor_position;
buffer.selection = restored.selection_start;
changed = true;
cursor_blink_timer = current_time;
}
}
KeyCode::KeyC if ctrl_held => {
if let Some(selected) = buffer.selected_text() {
ui_set_clipboard_text(world, selected);
}
}
KeyCode::KeyX if ctrl_held => {
if let Some(selected) = buffer.selected_text() {
ui_set_clipboard_text(world, selected);
std::sync::Arc::make_mut(&mut undo_stack).push_initial(snapshot(&buffer));
buffer.delete_selection();
changed = true;
std::sync::Arc::make_mut(&mut undo_stack)
.push(snapshot(&buffer), current_time);
}
}
KeyCode::KeyV if ctrl_held => {
let mut paste_text = ui_read_system_clipboard(world);
if !paste_text.is_empty() {
if let Some(max) = max_length {
let removed = buffer
.selection_range()
.map(|(min, max_sel)| max_sel - min)
.unwrap_or(0);
let available = max.saturating_sub(buffer.length() - removed);
paste_text = paste_text.chars().take(available).collect();
}
if !paste_text.is_empty() {
std::sync::Arc::make_mut(&mut undo_stack)
.push_initial(snapshot(&buffer));
buffer.insert_str(&paste_text, CharStyle::default());
changed = true;
std::sync::Arc::make_mut(&mut undo_stack)
.push(snapshot(&buffer), current_time);
}
}
}
KeyCode::Escape => {
buffer.selection = None;
clear_focus = true;
}
KeyCode::Enter => {
clear_focus = true;
}
_ => {}
}
}
if interaction.clicked
&& let Some(rect) = world.ui.get_ui_layout_node(entity).map(|n| n.computed_rect)
{
let font_size = world
.resources
.retained_ui
.theme_state
.active_theme()
.font_size;
let local_x = (mouse_position.x - rect.min.x) / dpi_scale - 8.0 + scroll_offset;
let hit_text =
crate::ecs::ui::widgets::masked_input_display(&buffer.text, data.password);
let new_pos = byte_index_at_x(
&mut world.resources.text.font_engine,
&hit_text,
font_size,
local_x,
);
if shift_held {
if buffer.selection.is_none() {
buffer.selection = Some(buffer.cursor);
}
} else {
buffer.selection = None;
}
buffer.cursor = new_pos;
cursor_blink_timer = current_time;
}
if interaction.double_clicked {
buffer.select_word_at_cursor();
}
} else {
buffer.selection = None;
}
let text = buffer.text;
let cursor_position = buffer.cursor;
let selection_start = buffer.selection;
if clear_focus {
world
.resources
.retained_ui
.interaction_for_active_mut()
.focused_entity = None;
}
if changed {
world.resources.text.cache.set_text(
data.text_slot,
crate::ecs::ui::widgets::masked_input_display(&text, data.password),
);
}
let cursor_visible = is_focused && ((current_time - cursor_blink_timer) % 1.0) < 0.5;
if let Some(cursor_node) = world.ui.get_ui_layout_node_mut(data.cursor_entity) {
cursor_node.visible = cursor_visible;
}
let has_selection =
is_focused && selection_start.is_some() && selection_start != Some(cursor_position);
if let Some(sel_node) = world.ui.get_ui_layout_node_mut(data.selection_entity) {
sel_node.visible = has_selection;
}
let input_rect = world.ui.get_ui_layout_node(entity).map(|n| n.computed_rect);
if let Some(rect) = input_rect {
let font_size = world
.resources
.retained_ui
.theme_state
.active_theme()
.font_size;
{
let displayed_text =
crate::ecs::ui::widgets::masked_input_display(&text, data.password);
let text_before_cursor: String = displayed_text.chars().take(cursor_position).collect();
let cursor_x = measure_text_width(
&mut world.resources.text.font_engine,
&text_before_cursor,
font_size,
);
let visible_width = rect.width() / dpi_scale - 16.0;
if cursor_x - scroll_offset > visible_width {
scroll_offset = cursor_x - visible_width;
} else if cursor_x - scroll_offset < 0.0 {
scroll_offset = cursor_x;
}
let cursor_screen_x = cursor_x - scroll_offset;
let mut sel_start_x = 0.0f32;
let mut sel_end_x = 0.0f32;
if has_selection {
let sel_start = selection_start.unwrap();
let sel_min = sel_start.min(cursor_position);
let sel_max = sel_start.max(cursor_position);
let text_before_sel: String = displayed_text.chars().take(sel_min).collect();
let text_to_sel_end: String = displayed_text.chars().take(sel_max).collect();
sel_start_x = measure_text_width(
&mut world.resources.text.font_engine,
&text_before_sel,
font_size,
) - scroll_offset;
sel_end_x = measure_text_width(
&mut world.resources.text.font_engine,
&text_to_sel_end,
font_size,
) - scroll_offset;
}
if let Some(cursor_node) = world.ui.get_ui_layout_node_mut(data.cursor_entity)
&& let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
cursor_node.base_layout.as_mut()
{
window.position =
crate::ecs::ui::units::Ab(Vec2::new(8.0 + cursor_screen_x, 4.0)).into();
}
if has_selection
&& let Some(sel_node) = world.ui.get_ui_layout_node_mut(data.selection_entity)
&& let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
sel_node.base_layout.as_mut()
{
window.position =
crate::ecs::ui::units::Ab(Vec2::new(8.0 + sel_start_x, 4.0)).into();
window.size = crate::ecs::ui::units::Ab(Vec2::new(
sel_end_x - sel_start_x,
rect.height() / dpi_scale - 8.0,
))
.into();
}
}
}
if changed {
world.resources.retained_ui.events_for_active_mut().push(
crate::ecs::ui::resources::UiEvent::TextInputChanged {
entity,
text: text.clone(),
},
);
validate_widget_text(world, entity, &text);
}
if clear_focus {
world.resources.retained_ui.events_for_active_mut().push(
crate::ecs::ui::resources::UiEvent::TextInputSubmitted {
entity,
text: text.clone(),
},
);
}
let text_is_empty = text.is_empty();
let placeholder_entity = if let Some(widget_data) = world.ui.get_ui_text_input_mut(entity) {
widget_data.text = text;
widget_data.cursor_position = cursor_position;
widget_data.selection_start = selection_start;
widget_data.changed = changed;
widget_data.cursor_blink_timer = cursor_blink_timer;
widget_data.scroll_offset = scroll_offset;
widget_data.undo_stack = undo_stack;
widget_data.placeholder_entity
} else {
None
};
if let Some(ph_entity) = placeholder_entity
&& let Some(node) = world.ui.get_ui_layout_node_mut(ph_entity)
{
node.visible = text_is_empty;
}
}
fn snapshot(buffer: &EditBuffer) -> TextSnapshot {
TextSnapshot {
text: buffer.text.clone(),
cursor_position: buffer.cursor,
selection_start: buffer.selection,
}
}
pub(super) fn handle_selectable_label(
world: &mut World,
entity: freecs::Entity,
interaction: &InteractionSnapshot,
data: &crate::ecs::ui::components::UiSelectableLabelData,
) {
if let Some(widget_data) = world.ui.get_ui_selectable_label_mut(entity) {
widget_data.changed = false;
}
if interaction.clicked {
let new_selected = !data.selected;
if let Some(group_id) = data.group_id
&& new_selected
{
let siblings = world
.resources
.retained_ui
.groups
.selectable_labels
.get(&group_id)
.cloned()
.unwrap_or_default();
for other in siblings {
if other == entity {
continue;
}
if let Some(other_sl) = world.ui.get_ui_selectable_label(other)
&& other_sl.selected
{
ui_set_selected(world, other, false);
if let Some(wd) = world.ui.get_ui_selectable_label_mut(other) {
wd.changed = true;
}
}
}
}
ui_set_selected(world, entity, new_selected);
if let Some(widget_data) = world.ui.get_ui_selectable_label_mut(entity) {
widget_data.selected = new_selected;
widget_data.changed = true;
}
world.resources.retained_ui.events_for_active_mut().push(
crate::ecs::ui::resources::UiEvent::SelectableLabelClicked {
entity,
selected: new_selected,
},
);
}
}