use rdom_core::NodeId;
use super::ScrollAxis;
use super::geometry::{is_vertical_scroll_container, scrolls_vertically_by_style};
use super::scroll::{ClampTo, set_scroll_with};
use crate::TuiDom;
use crate::layout::LayoutRect;
use crate::node::TuiNodeExt;
pub(crate) fn reveal_caret(dom: &mut TuiDom) {
reveal_caret_with(dom, ClampTo::NextLayout, true);
}
pub(crate) fn service_caret_reveal(dom: &mut TuiDom) -> bool {
let Some(sel) = dom.selection() else {
return false;
};
let focus = sel.focus;
let Some(crate::render::inline::InlineFlow::Ifc { block }) =
crate::render::inline::inline_flow_for_text(dom, focus.node)
else {
return false;
};
let pending = dom
.node(block)
.tui_ext()
.is_some_and(|e| e.caret_reveal_pending);
if !pending {
return false;
}
if let Some(ext) = dom.node_mut(block).ext_mut() {
ext.caret_reveal_pending = false;
}
let before = dom.node(block).tui_ext().map(|e| e.scroll_y);
reveal_caret_with(dom, ClampTo::CurrentExtent, false);
dom.node(block).tui_ext().map(|e| e.scroll_y) != before
}
fn reveal_caret_with(dom: &mut TuiDom, clamp: ClampTo, mark_pending: bool) {
let Some(sel) = dom.selection() else { return };
if !sel.is_collapsed() {
return;
}
let focus = sel.focus;
let Some(flow) = crate::render::inline::inline_flow_for_text(dom, focus.node) else {
return;
};
let crate::render::inline::InlineFlow::Ifc { block } = flow else {
return;
};
if mark_pending
&& scrolls_vertically_by_style(dom, block)
&& let Some(ext) = dom.node_mut(block).ext_mut()
{
ext.caret_reveal_pending = true;
}
if !is_vertical_scroll_container(dom, block) {
return;
}
let Some((x, y)) = crate::render::inline::cell_of_position(dom, focus) else {
return;
};
ensure_visible_vertical_with(
dom,
block,
LayoutRect {
x: x as i32,
y: y as i32,
width: 1,
height: 1,
},
clamp,
);
}
pub(crate) fn scroll_into_view(dom: &mut TuiDom, node: NodeId, reveal: LayoutRect) {
let mut cur = dom.node(node).parent_node().map(|p| p.id());
while let Some(id) = cur {
if is_vertical_scroll_container(dom, id) {
ensure_visible_vertical(dom, id, reveal);
return;
}
cur = dom.node(id).parent_node().map(|p| p.id());
}
}
fn ensure_visible_vertical(dom: &mut TuiDom, container: NodeId, reveal: LayoutRect) {
ensure_visible_vertical_with(dom, container, reveal, ClampTo::CurrentExtent)
}
fn ensure_visible_vertical_with(
dom: &mut TuiDom,
container: NodeId,
reveal: LayoutRect,
clamp: ClampTo,
) {
let (port_top, port_bottom, cur_scroll) = {
let Some(ext) = dom.node(container).tui_ext() else {
return;
};
let border = dom
.node(container)
.computed()
.map(|c| c.border)
.unwrap_or_default();
let pb = crate::layout::compute_padding_box(ext.layout, border);
(pb.y, pb.y + pb.height as i32, ext.scroll_y as i32)
};
let r_top = reveal.y;
let r_bottom = reveal.y + reveal.height as i32;
let delta = if r_top < port_top {
r_top - port_top
} else if r_bottom > port_bottom {
(r_bottom - port_bottom).min(r_top - port_top).max(0)
} else {
0
};
if delta != 0 {
set_scroll_with(
dom,
container,
ScrollAxis::Vertical,
cur_scroll + delta,
clamp,
);
}
}