use rdom_core::NodeId;
use super::ScrollAxis;
use super::geometry::nearest_scroll_container;
use super::scroll::set_scroll;
use crate::TuiDom;
use crate::node::TuiNodeExt;
const AUTOSCROLL_EDGE_ZONE: i32 = 2;
const AUTOSCROLL_MAX_STEP: i32 = 3;
fn scroll_container_from_hit(dom: &TuiDom, x: u16, y: u16) -> Option<NodeId> {
use crate::runtime::hit_test::HitTestExt;
let hit = dom.hit_test(x, y)?;
nearest_scroll_container(dom, hit)
}
pub(crate) fn resolve_autoscroll_container(
dom: &TuiDom,
source: NodeId,
pointer: (u16, u16),
) -> Option<NodeId> {
scroll_container_from_hit(dom, pointer.0, pointer.1).or_else(|| {
let cap = dom.node(source).tui_ext()?.layout;
if cap.width == 0 || cap.height == 0 {
return None;
}
let cx = (pointer.0 as i32).clamp(cap.x, cap.x + cap.width as i32 - 1) as u16;
let cy = (pointer.1 as i32).clamp(cap.y, cap.y + cap.height as i32 - 1) as u16;
scroll_container_from_hit(dom, cx, cy)
})
}
pub(crate) fn autoscroll_step_for(
dom: &TuiDom,
container: NodeId,
pointer: (u16, u16),
) -> Option<(ScrollAxis, i32)> {
let ext = dom.node(container).tui_ext()?;
let border = dom
.node(container)
.computed()
.map(|c| c.border)
.unwrap_or_default();
let pb = crate::layout::compute_padding_box(ext.layout, border);
let zone = AUTOSCROLL_EDGE_ZONE.max(1);
let band = |pos: i32, start: i32, len: u16, offset: usize, content: usize| -> Option<i32> {
let last = start + len as i32 - 1;
let into_far = pos - (last - (zone - 1));
if into_far >= 0 && offset + usize::from(len) < content {
return Some((into_far + 1).clamp(1, AUTOSCROLL_MAX_STEP));
}
let into_near = (start + (zone - 1)) - pos;
if into_near >= 0 && offset > 0 {
return Some(-((into_near + 1).clamp(1, AUTOSCROLL_MAX_STEP)));
}
None
};
if let Some(step) = band(
pointer.1 as i32,
pb.y,
pb.height,
ext.scroll_y,
ext.scroll_content_height,
) {
return Some((ScrollAxis::Vertical, step));
}
band(
pointer.0 as i32,
pb.x,
pb.width,
ext.scroll_x,
ext.scroll_content_width,
)
.map(|step| (ScrollAxis::Horizontal, step))
}
pub(crate) fn autoscroll_step(
dom: &mut TuiDom,
container: NodeId,
axis: ScrollAxis,
step: i32,
) -> bool {
let before = match dom.node(container).tui_ext() {
Some(e) => match axis {
ScrollAxis::Vertical => e.scroll_y,
ScrollAxis::Horizontal => e.scroll_x,
},
None => return false,
} as i32;
let after = set_scroll(dom, container, axis, before + step) as i32;
after != before
}