use rdom_core::NodeId;
use super::scroll::max_offsets;
use crate::TuiDom;
use crate::layout::{LayoutRect, Overflow};
use crate::node::TuiNodeExt;
use crate::runtime::smooth_scroll::{ScrollIntoViewOptions, ScrollLogicalPosition, perform_scroll};
pub(crate) fn scroll_element_into_view(
dom: &mut TuiDom,
element: NodeId,
options: ScrollIntoViewOptions,
) {
let rendered = dom.node(element).computed().is_some() && crate::node::is_rendered(dom, element);
let Some(mut rect) = dom.node(element).tui_ext().map(|e| e.layout) else {
return;
};
if !rendered {
return;
}
let mut cur = dom.node(element).parent_node().map(|p| p.id());
while let Some(container) = cur {
if establishes_scrolling_box(dom, container) {
rect = scroll_container(dom, container, rect, options);
}
cur = dom.node(container).parent_node().map(|p| p.id());
}
}
fn establishes_scrolling_box(dom: &TuiDom, id: NodeId) -> bool {
dom.node(id).computed().is_some_and(|c| {
!matches!(c.overflow_x, Overflow::Visible) || !matches!(c.overflow_y, Overflow::Visible)
})
}
fn scroll_container(
dom: &mut TuiDom,
container: NodeId,
rect: LayoutRect,
options: ScrollIntoViewOptions,
) -> LayoutRect {
let Some(ext) = dom.node(container).tui_ext() else {
return rect;
};
let border = dom
.node(container)
.computed()
.map(|c| c.border)
.unwrap_or_default();
let port = crate::layout::compute_padding_box(ext.layout, border);
let (cur_x, cur_y) = (ext.scroll_x as i32, ext.scroll_y as i32);
let laid = super::state::laid_out(ext);
let (laid_x, laid_y) = (laid.0 as i32, laid.1 as i32);
let rel_x = rect.x - port.x - (cur_x - laid_x);
let rel_y = rect.y - port.y - (cur_y - laid_y);
let (max_x, max_y) = max_offsets(dom, container).unwrap_or((0, 0));
let to_x = (cur_x + align(rel_x, rect.width, port.width, options.inline)).clamp(0, max_x);
let to_y = (cur_y + align(rel_y, rect.height, port.height, options.block)).clamp(0, max_y);
perform_scroll(dom, container, to_x, to_y, options.behavior);
LayoutRect {
x: port.x + rel_x - (to_x - cur_x),
y: port.y + rel_y - (to_y - cur_y),
..rect
}
}
fn align(start: i32, size: u16, port: u16, position: ScrollLogicalPosition) -> i32 {
let (size, port) = (i32::from(size), i32::from(port));
let end = start + size;
match position {
ScrollLogicalPosition::Start => start,
ScrollLogicalPosition::End => end - port,
ScrollLogicalPosition::Center => start + (size - port).div_euclid(2),
ScrollLogicalPosition::Nearest => {
let before = start < 0;
let after = end > port;
let fits = size <= port;
if (before && after) || (!before && !after) {
0
} else if (before && fits) || (after && !fits) {
start
} else {
end - port
}
}
}
}