use crossterm::event::MouseEvent;
use rdom_core::Selection;
use crate::TuiDom;
use crate::layout::UserSelect;
use crate::node::is_descendant_or_self;
use crate::render::inline::{InlineFlow, inline_flow_for_text, inline_flow_layout};
use crate::runtime::hit_test::HitTestExt;
use crate::runtime::router::Router;
use crate::runtime::selection::user_select;
pub(crate) fn begin(router: &mut Router, dom: &mut TuiDom, mouse: MouseEvent) -> bool {
let Some(anchor) = dom.position_at(mouse.column, mouse.row) else {
return false;
};
if dom.node(anchor.node).node_type() != rdom_core::NodeType::Text {
return false;
}
let initial = match user_select::ancestor_with(dom, anchor.node, UserSelect::All) {
Some(host) => {
user_select::span_all_text(dom, host).unwrap_or_else(|| Selection::caret(anchor))
}
None => Selection::caret(anchor),
};
dom.set_selection(Some(initial));
let anchor_flow = inline_flow_for_text(dom, anchor.node);
let capture_holder = anchor_flow.map(|f| f.owner()).unwrap_or(anchor.node);
let _ = dom.set_pointer_capture(capture_holder);
router.selection_drag = anchor_flow;
true
}
pub(crate) fn extend(dom: &mut TuiDom, mouse: MouseEvent, anchor_flow: InlineFlow) -> bool {
let Some(sel) = dom.selection().copied() else {
return false;
};
if user_select::ancestor_with(dom, sel.anchor.node, UserSelect::All).is_some() {
return false;
}
let raw_focus = match dom.position_at(mouse.column, mouse.row) {
Some(p) => p,
None => match clamp_to_anchor_flow(dom, anchor_flow, mouse.column, mouse.row) {
Some(p) => p,
None => return false,
},
};
let focus = match user_select::ancestor_with(dom, sel.anchor.node, UserSelect::Contain) {
Some(host) if !is_descendant_or_self(dom, raw_focus.node, host) => {
match user_select::clamp_to_contain_host(dom, host, mouse) {
Some(p) => p,
None => return false,
}
}
_ => raw_focus,
};
if sel.focus == focus {
return false;
}
dom.set_selection(Some(Selection::new(sel.anchor, focus)));
true
}
fn clamp_to_anchor_flow(
dom: &TuiDom,
anchor_flow: InlineFlow,
x: u16,
y: u16,
) -> Option<rdom_core::Position> {
let (layout, content) = inline_flow_layout(dom, anchor_flow)?;
if layout.lines.is_empty() {
return None;
}
let (line_idx, y_overshoot_down, y_overshoot_up) = if (y as i32) < content.y {
(0, false, true)
} else if (y as i32) >= content.y + content.height as i32 {
(layout.lines.len() - 1, true, false)
} else {
let raw = (y as i32 - content.y) as usize;
(raw.min(layout.lines.len() - 1), false, false)
};
let target_line = &layout.lines[line_idx];
if target_line.fragments.is_empty() {
for line in layout.lines.iter().rev() {
if let Some(frag) = line.fragments.last() {
return Some(rdom_core::Position::new(
frag.text_node,
frag.source_byte_offset + frag.text.len(),
));
}
}
return None;
}
let first = target_line.fragments.first().unwrap();
let last = target_line.fragments.last().unwrap();
if y_overshoot_down {
return Some(rdom_core::Position::new(
last.text_node,
last.source_byte_offset + last.text.len(),
));
}
if y_overshoot_up {
return Some(rdom_core::Position::new(
first.text_node,
first.source_byte_offset,
));
}
let line_right = content.x + last.x as i32 + last.width as i32;
let line_left = content.x + first.x as i32;
if (x as i32) >= line_right {
Some(rdom_core::Position::new(
last.text_node,
last.source_byte_offset + last.text.len(),
))
} else if (x as i32) < line_left {
Some(rdom_core::Position::new(
first.text_node,
first.source_byte_offset,
))
} else {
Some(rdom_core::Position::new(
last.text_node,
last.source_byte_offset + last.text.len(),
))
}
}
pub(crate) fn end(router: &mut Router) {
router.selection_drag = None;
}