use crate::ecs::ui::state::UiStateTrait as _;
use nalgebra_glm::Vec2;
use winit::keyboard::KeyCode;
use crate::ecs::ui::components::UiWidgetState;
use crate::ecs::world::World;
use super::InteractionSnapshot;
use super::text_cursor::{
byte_index_at_x, measure_text_width, next_word_boundary, prev_word_boundary,
};
pub(super) fn handle_drag_value(
world: &mut World,
entity: freecs::Entity,
interaction: &InteractionSnapshot,
data: &crate::ecs::ui::components::UiDragValueData,
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 value = data.value;
let mut changed = false;
let mut editing = data.editing;
let mut edit_text = data.edit_text.clone();
let mut cursor_position = data.cursor_position;
let mut selection_start = data.selection_start;
let mut cursor_blink_timer = data.cursor_blink_timer;
let mut scroll_offset = data.scroll_offset;
let mut drag_start_value = data.drag_start_value;
let mut undo_stack = data.undo_stack.clone();
let mut clear_focus = false;
if editing && is_focused {
let mut needs_snapshot = false;
for character in frame_chars {
if *character >= ' '
&& (character.is_ascii_digit() || *character == '.' || *character == '-')
{
if !needs_snapshot {
undo_stack.push_initial(crate::ecs::ui::components::TextSnapshot {
text: edit_text.clone(),
cursor_position,
selection_start,
});
needs_snapshot = true;
}
if let Some(sel_start) = selection_start {
let min = sel_start.min(cursor_position);
let max = sel_start.max(cursor_position);
let chars: Vec<char> = edit_text.chars().collect();
let mut new_text: String = chars[..min].iter().collect();
new_text.push(*character);
new_text.extend(chars[max..].iter());
edit_text = new_text;
cursor_position = min + 1;
selection_start = None;
} else {
let chars: Vec<char> = edit_text.chars().collect();
let mut new_text: String = chars[..cursor_position].iter().collect();
new_text.push(*character);
new_text.extend(chars[cursor_position..].iter());
edit_text = new_text;
cursor_position += 1;
}
cursor_blink_timer = current_time;
}
}
if needs_snapshot {
undo_stack.push(
crate::ecs::ui::components::TextSnapshot {
text: edit_text.clone(),
cursor_position,
selection_start,
},
current_time,
);
}
for (key, is_pressed) in frame_keys {
if !is_pressed {
continue;
}
let chars: Vec<char> = edit_text.chars().collect();
let len = chars.len();
match key {
KeyCode::Backspace => {
undo_stack.push_initial(crate::ecs::ui::components::TextSnapshot {
text: edit_text.clone(),
cursor_position,
selection_start,
});
if let Some(sel_start) = selection_start {
let min = sel_start.min(cursor_position);
let max = sel_start.max(cursor_position);
let mut new_text: String = chars[..min].iter().collect();
new_text.extend(chars[max..].iter());
edit_text = new_text;
cursor_position = min;
selection_start = None;
} else if cursor_position > 0 {
if ctrl_held {
let new_pos = prev_word_boundary(&edit_text, cursor_position);
let mut new_text: String = chars[..new_pos].iter().collect();
new_text.extend(chars[cursor_position..].iter());
edit_text = new_text;
cursor_position = new_pos;
} else {
let mut new_text: String =
chars[..cursor_position - 1].iter().collect();
new_text.extend(chars[cursor_position..].iter());
edit_text = new_text;
cursor_position -= 1;
}
}
undo_stack.push(
crate::ecs::ui::components::TextSnapshot {
text: edit_text.clone(),
cursor_position,
selection_start,
},
current_time,
);
cursor_blink_timer = current_time;
}
KeyCode::Delete => {
undo_stack.push_initial(crate::ecs::ui::components::TextSnapshot {
text: edit_text.clone(),
cursor_position,
selection_start,
});
if let Some(sel_start) = selection_start {
let min = sel_start.min(cursor_position);
let max = sel_start.max(cursor_position);
let mut new_text: String = chars[..min].iter().collect();
new_text.extend(chars[max..].iter());
edit_text = new_text;
cursor_position = min;
selection_start = None;
} else if cursor_position < len {
if ctrl_held {
let end_pos = next_word_boundary(&edit_text, cursor_position);
let mut new_text: String = chars[..cursor_position].iter().collect();
new_text.extend(chars[end_pos..].iter());
edit_text = new_text;
} else {
let mut new_text: String = chars[..cursor_position].iter().collect();
new_text.extend(chars[cursor_position + 1..].iter());
edit_text = new_text;
}
}
undo_stack.push(
crate::ecs::ui::components::TextSnapshot {
text: edit_text.clone(),
cursor_position,
selection_start,
},
current_time,
);
cursor_blink_timer = current_time;
}
KeyCode::ArrowLeft => {
if shift_held {
if selection_start.is_none() {
selection_start = Some(cursor_position);
}
} else if selection_start.is_some() {
let min = selection_start.unwrap().min(cursor_position);
cursor_position = min;
selection_start = None;
cursor_blink_timer = current_time;
continue;
}
if ctrl_held {
cursor_position = prev_word_boundary(&edit_text, cursor_position);
} else {
cursor_position = cursor_position.saturating_sub(1);
}
if !shift_held {
selection_start = None;
}
cursor_blink_timer = current_time;
}
KeyCode::ArrowRight => {
if shift_held {
if selection_start.is_none() {
selection_start = Some(cursor_position);
}
} else if selection_start.is_some() {
let max = selection_start.unwrap().max(cursor_position);
cursor_position = max;
selection_start = None;
cursor_blink_timer = current_time;
continue;
}
if ctrl_held {
cursor_position = next_word_boundary(&edit_text, cursor_position);
} else if cursor_position < len {
cursor_position += 1;
}
if !shift_held {
selection_start = None;
}
cursor_blink_timer = current_time;
}
KeyCode::Home => {
if shift_held && selection_start.is_none() {
selection_start = Some(cursor_position);
}
cursor_position = 0;
if !shift_held {
selection_start = None;
}
cursor_blink_timer = current_time;
}
KeyCode::End => {
if shift_held && selection_start.is_none() {
selection_start = Some(cursor_position);
}
cursor_position = len;
if !shift_held {
selection_start = None;
}
cursor_blink_timer = current_time;
}
KeyCode::KeyA if ctrl_held => {
selection_start = Some(0);
cursor_position = len;
cursor_blink_timer = current_time;
}
KeyCode::KeyZ if ctrl_held => {
if let Some(snapshot) = undo_stack.undo() {
edit_text = snapshot.text.clone();
cursor_position = snapshot.cursor_position;
selection_start = snapshot.selection_start;
cursor_blink_timer = current_time;
}
}
KeyCode::KeyY if ctrl_held => {
if let Some(snapshot) = undo_stack.redo() {
edit_text = snapshot.text.clone();
cursor_position = snapshot.cursor_position;
selection_start = snapshot.selection_start;
cursor_blink_timer = current_time;
}
}
KeyCode::KeyC if ctrl_held => {
if let Some(sel_start) = selection_start {
let min = sel_start.min(cursor_position);
let max = sel_start.max(cursor_position);
let selected: String =
edit_text.chars().skip(min).take(max - min).collect();
world.resources.retained_ui.clipboard_text = selected;
}
}
KeyCode::KeyX if ctrl_held => {
if let Some(sel_start) = selection_start {
let min = sel_start.min(cursor_position);
let max = sel_start.max(cursor_position);
let selected: String =
edit_text.chars().skip(min).take(max - min).collect();
world.resources.retained_ui.clipboard_text = selected;
undo_stack.push_initial(crate::ecs::ui::components::TextSnapshot {
text: edit_text.clone(),
cursor_position,
selection_start,
});
let chars: Vec<char> = edit_text.chars().collect();
let mut new_text: String = chars[..min].iter().collect();
new_text.extend(chars[max..].iter());
edit_text = new_text;
cursor_position = min;
selection_start = None;
undo_stack.push(
crate::ecs::ui::components::TextSnapshot {
text: edit_text.clone(),
cursor_position,
selection_start,
},
current_time,
);
}
}
KeyCode::KeyV if ctrl_held => {
let paste_text = world.resources.retained_ui.clipboard_text.clone();
if !paste_text.is_empty() {
undo_stack.push_initial(crate::ecs::ui::components::TextSnapshot {
text: edit_text.clone(),
cursor_position,
selection_start,
});
if let Some(sel_start) = selection_start {
let min = sel_start.min(cursor_position);
let max = sel_start.max(cursor_position);
let chars: Vec<char> = edit_text.chars().collect();
let mut new_text: String = chars[..min].iter().collect();
new_text.push_str(&paste_text);
new_text.extend(chars[max..].iter());
edit_text = new_text;
cursor_position = min + paste_text.chars().count();
selection_start = None;
} else {
let chars: Vec<char> = edit_text.chars().collect();
let mut new_text: String = chars[..cursor_position].iter().collect();
new_text.push_str(&paste_text);
new_text.extend(chars[cursor_position..].iter());
edit_text = new_text;
cursor_position += paste_text.chars().count();
}
undo_stack.push(
crate::ecs::ui::components::TextSnapshot {
text: edit_text.clone(),
cursor_position,
selection_start,
},
current_time,
);
}
}
KeyCode::Escape => {
editing = false;
selection_start = None;
clear_focus = true;
}
KeyCode::Enter => {
if let Ok(parsed) = edit_text.parse::<f32>() {
value = parsed.clamp(data.min, data.max);
changed = true;
}
editing = false;
selection_start = None;
clear_focus = true;
}
_ => {}
}
}
if interaction.clicked {
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 best_idx = world
.resources
.text_cache
.font_manager
.best_bitmap_font_for_size(font_size);
let font_arc = world
.resources
.text_cache
.font_manager
.get_bitmap_font_arc(best_idx);
if let Some(atlas) = font_arc {
let local_x = (mouse_position.x - rect.min.x) / dpi_scale - 8.0 + scroll_offset;
let new_pos = byte_index_at_x(&atlas, &edit_text, font_size, local_x);
if shift_held {
if selection_start.is_none() {
selection_start = Some(cursor_position);
}
} else {
selection_start = None;
}
cursor_position = new_pos;
cursor_blink_timer = current_time;
}
}
}
if interaction.double_clicked {
let len = edit_text.chars().count();
let pos = cursor_position.min(len);
let word_start = prev_word_boundary(&edit_text, pos);
let word_end = next_word_boundary(&edit_text, pos).min(len);
selection_start = Some(word_start);
cursor_position = word_end;
}
world
.resources
.text_cache
.set_text(data.text_slot, &edit_text);
} else if editing && !is_focused {
if let Ok(parsed) = edit_text.parse::<f32>() {
value = parsed.clamp(data.min, data.max);
changed = true;
}
editing = false;
selection_start = None;
} else {
if interaction.dragging
&& let Some(drag_start) = interaction.drag_start
{
let delta_x = mouse_position.x - drag_start.x;
let pixel_threshold = 3.0;
if delta_x.abs() > pixel_threshold {
let new_value = (drag_start_value + (delta_x / dpi_scale) * data.speed)
.clamp(data.min, data.max);
if (new_value - value).abs() > f32::EPSILON {
value = new_value;
changed = true;
}
}
}
if interaction.clicked && !interaction.dragging {
editing = true;
edit_text = format!("{:.prec$}", data.value, prec = data.precision);
cursor_position = edit_text.chars().count();
selection_start = Some(0);
cursor_blink_timer = current_time;
world.resources.retained_ui.focused_entity = Some(entity);
}
if interaction.pressed && !interaction.dragging && interaction.drag_start.is_some() {
drag_start_value = value;
}
}
if clear_focus {
world.resources.retained_ui.focused_entity = None;
}
if !editing {
let display = format!(
"{}{:.prec$}{}",
data.prefix,
value,
data.suffix,
prec = data.precision
);
world
.resources
.text_cache
.set_text(data.text_slot, &display);
scroll_offset = 0.0;
}
let cursor_visible = editing && 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 = editing
&& 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;
}
if editing {
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 best_idx = world
.resources
.text_cache
.font_manager
.best_bitmap_font_for_size(font_size);
let font_arc = world
.resources
.text_cache
.font_manager
.get_bitmap_font_arc(best_idx);
if let Some(atlas) = font_arc {
let text_before_cursor: String = edit_text.chars().take(cursor_position).collect();
let cursor_x = measure_text_width(&atlas, &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 = edit_text.chars().take(sel_min).collect();
let text_to_sel_end: String = edit_text.chars().take(sel_max).collect();
sel_start_x =
measure_text_width(&atlas, &text_before_sel, font_size) - scroll_offset;
sel_end_x =
measure_text_width(&atlas, &text_to_sel_end, font_size) - scroll_offset;
}
drop(atlas);
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.layouts[crate::ecs::ui::state::UiBase::INDEX].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.layouts[crate::ecs::ui::state::UiBase::INDEX].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 let Some(up) = data.up_entity {
let up_clicked = world
.ui
.get_ui_node_interaction(up)
.map(|i| i.clicked)
.unwrap_or(false);
if up_clicked {
value = (value + data.step).min(data.max);
changed = true;
}
}
if let Some(down) = data.down_entity {
let down_clicked = world
.ui
.get_ui_node_interaction(down)
.map(|i| i.clicked)
.unwrap_or(false);
if down_clicked {
value = (value - data.step).max(data.min);
changed = true;
}
}
if let Some(interaction_comp) = world.ui.get_ui_node_interaction_mut(entity) {
if editing {
interaction_comp.cursor_icon = Some(winit::window::CursorIcon::Text);
} else {
interaction_comp.cursor_icon = Some(winit::window::CursorIcon::EwResize);
}
}
if let Some(UiWidgetState::DragValue(widget_data)) = world.ui.get_ui_widget_state_mut(entity) {
widget_data.value = value;
widget_data.changed = changed;
widget_data.editing = editing;
widget_data.edit_text = edit_text;
widget_data.cursor_position = cursor_position;
widget_data.selection_start = selection_start;
widget_data.cursor_blink_timer = cursor_blink_timer;
widget_data.scroll_offset = scroll_offset;
widget_data.drag_start_value = drag_start_value;
widget_data.undo_stack = undo_stack;
}
if changed {
world
.resources
.retained_ui
.frame_events
.push(crate::ecs::ui::resources::UiEvent::DragValueChanged { entity, value });
}
}