use teksilo_canvas::SizeProposal;
use teksilo_core::widget_tree::WidgetTree;
use teksilo_text::text_document::TextDocument;
use super::state::SharedState;
use super::{RichTextEditor, ScrollPolicy};
use crate::ScrollArea;
const W: f32 = 900.0;
const H: f32 = 700.0;
fn giant_doc() -> TextDocument {
let text = vec!["lorem"; 6000].join(" ");
let doc = TextDocument::new();
doc.set_plain_text(&text).unwrap();
doc
}
fn make_editor(window: bool) -> RichTextEditor {
RichTextEditor::editor(giant_doc())
.v_scroll_policy(ScrollPolicy::AlwaysOff)
.h_scroll_policy(ScrollPolicy::AlwaysOff)
.min_lines(1)
.window_to_clip(window)
}
fn settle(tree: &mut WidgetTree, w: f32, h: f32) {
for _ in 0..4 {
tree.request_frame();
tree.tick_animations(std::time::Duration::from_millis(16));
tree.layout(SizeProposal::exact(w, h));
}
tree.render();
}
fn glyph_count(state: &SharedState) -> usize {
state
.borrow_mut()
.engine
.with_render_cursor_only(|f| f.glyphs.len())
}
fn render_window(state: &SharedState) -> Option<(f32, f32)> {
state.borrow().engine.render_window()
}
#[test]
fn window_to_clip_culls_to_the_visible_band() {
let ed = make_editor(true);
let state = ed.state_handle();
let mut tree = WidgetTree::new();
let id = tree.add(ed);
tree.add(ScrollArea::from_id(id));
settle(&mut tree, W, H);
let win = render_window(&state).expect("a windowed editor must set a render window");
assert!(
win.1 < H * 3.0,
"the window height ({}) must be viewport-scale (≈{H}px + margin), not the whole \
document",
win.1
);
let windowed = glyph_count(&state);
assert!(windowed > 0, "the visible band must render something");
let ed2 = make_editor(false);
let state2 = ed2.state_handle();
let mut tree2 = WidgetTree::new();
let id2 = tree2.add(ed2);
tree2.add(ScrollArea::from_id(id2));
settle(&mut tree2, W, H);
assert_eq!(
render_window(&state2),
None,
"a non-windowed editor must not set a render window"
);
let full = glyph_count(&state2);
assert!(
windowed * 3 < full,
"windowing must cull the bulk of the document: windowed={windowed} vs full={full}"
);
}
#[test]
fn scrolling_the_outer_area_moves_the_render_window() {
let ed = make_editor(true);
let state = ed.state_handle();
let mut tree = WidgetTree::new();
let id = tree.add(ed);
let area = ScrollArea::from_id(id);
let scroll = area.scroll_y_signal().clone();
tree.add(area);
settle(&mut tree, W, H);
let top_before = render_window(&state).unwrap().0;
scroll.set(1500.0);
settle(&mut tree, W, H);
let top_after = render_window(&state).unwrap().0;
assert!(
top_after > top_before + 500.0,
"scrolling down 1500px must advance the render-window top ({top_before} → {top_after})"
);
}
#[test]
fn the_render_window_tracks_the_visible_clip_band() {
let ed = RichTextEditor::editor(giant_doc())
.v_scroll_policy(ScrollPolicy::AlwaysOff)
.h_scroll_policy(ScrollPolicy::AlwaysOff)
.min_lines(1)
.font_size_scale(1.5)
.window_to_clip(true);
let state = ed.state_handle();
let mut tree = WidgetTree::new();
let id = tree.add(ed);
tree.add(ScrollArea::from_id(id));
settle(&mut tree, W, H);
let h = render_window(&state).unwrap().1;
assert!(
h > 0.0,
"window_to_clip must publish a positive cull height, got {h}"
);
}
#[test]
fn nested_scroll_areas_bound_the_window_to_their_intersection() {
use crate::primitives::FixedSize;
let ed = make_editor(true);
let state = ed.state_handle();
let mut tree = WidgetTree::new();
let id = tree.add(ed);
let inner = tree.add(ScrollArea::from_id(id));
let boxed = tree.add(FixedSize::new().width(W).height(250.0).child_id(inner));
tree.add(ScrollArea::from_id(boxed));
settle(&mut tree, W, H);
let win = render_window(&state).expect("nested-clipped editor still sets a window");
assert!(
win.1 < 250.0 * 3.0,
"the window height ({}) must follow the 250px inner clip, not the 700px outer \
viewport",
win.1
);
}