use rdom_core::{Dom, NodeId};
use crate::ext::TuiExt;
use crate::layout::LayoutRect;
use crate::render::inline::has_inline_layout;
use crate::runtime::selection::user_select;
pub(crate) fn nearest_inline_target_in_subtree(
dom: &Dom<TuiExt>,
root: NodeId,
y: u16,
) -> Option<InlineTarget> {
let y = y as i32;
let mut best: Option<(i32, InlineTarget)> = None;
let mut consider = |target: InlineTarget, top: i32, bottom: i32| {
if user_select::is_unselectable(dom, target.node()) {
return;
}
let dist = if y < top {
top - y
} else if y >= bottom {
y - bottom + 1
} else {
0
};
if best.is_none_or(|(d, _)| dist < d) {
best = Some((dist, target));
}
};
let mut stack = vec![root];
while let Some(id) = stack.pop() {
if has_inline_layout(dom, id)
&& let Some(content) = crate::render::inline::scrolled_content_rect(dom, id)
{
consider(
InlineTarget::Ifc(id),
content.y,
content.y + content.height as i32,
);
}
if let Some(ext) = dom.node(id).ext() {
for (i, anon) in ext.anonymous_blocks.iter().enumerate() {
consider(
InlineTarget::Anonymous {
container: id,
index: i,
},
anon.rect.y,
anon.rect.y + anon.rect.height as i32,
);
}
}
let kids: Vec<NodeId> = dom.node(id).children().map(|c| c.id()).collect();
stack.extend(kids.into_iter().rev());
}
best.map(|(_, t)| t)
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum InlineTarget {
Ifc(NodeId),
Anonymous { container: NodeId, index: usize },
}
impl InlineTarget {
fn node(self) -> NodeId {
match self {
InlineTarget::Ifc(id) => id,
InlineTarget::Anonymous { container, .. } => container,
}
}
pub(super) fn layout_and_rect(
self,
dom: &Dom<TuiExt>,
) -> Option<(&crate::render::inline::InlineLayout, LayoutRect)> {
match self {
InlineTarget::Ifc(id) => {
let ext = dom.node(id).ext()?;
let layout = ext.inline_layout.as_ref()?;
let content = crate::render::inline::scrolled_content_rect(dom, id)?;
Some((layout, content))
}
InlineTarget::Anonymous { container, index } => {
let ext = dom.node(container).ext()?;
let anon = ext.anonymous_blocks.get(index)?;
Some((&anon.inline_layout, anon.rect))
}
}
}
}
pub(super) fn inline_target_at(dom: &Dom<TuiExt>, id: NodeId, y: u16) -> Option<InlineTarget> {
if has_inline_layout(dom, id) {
return Some(InlineTarget::Ifc(id));
}
let ext = dom.node(id).ext()?;
if ext.anonymous_blocks.is_empty() {
return None;
}
let y_i = y as i32;
for (i, anon) in ext.anonymous_blocks.iter().enumerate() {
let top = anon.rect.y;
let bottom = anon.rect.y + anon.rect.height as i32;
if y_i >= top && y_i < bottom {
return Some(InlineTarget::Anonymous {
container: id,
index: i,
});
}
}
None
}