fix(editor): use page row height for pages-region scroll window

The Pages and Layers regions use different row heights (32px vs 28px),
but visible_row_range / row_index_at hardcoded LAYER_ROW_HEIGHT for
both. With a long page list scrolled toward the bottom, the pages
window started at the wrong index — only the last page rendered and
the rows above it were skipped, leaving a blank gap. Thread the
region's row height through both helpers.
This commit is contained in:
Kayshen-X 2026-07-04 18:50:36 +08:00
parent 7536627056
commit ec45fd99c1
3 changed files with 77 additions and 11 deletions

View file

@ -265,6 +265,7 @@ impl LayerPanel {
r.layers_rows_top,
r.layers.offset,
r.layers_view_h,
LAYER_ROW_HEIGHT,
point.y,
) {
let item = &self.items[index];
@ -341,6 +342,7 @@ impl LayerPanel {
r.pages_rows_top,
r.pages.offset,
r.pages_view_h,
PAGE_ROW_HEIGHT,
point.y,
) {
let page = &self.pages[index];
@ -371,6 +373,7 @@ impl LayerPanel {
r.layers_rows_top,
r.layers.offset,
r.layers_view_h,
LAYER_ROW_HEIGHT,
point.y,
) {
let item = &self.items[index];
@ -507,7 +510,12 @@ impl Widget for LayerPanel {
origin: Point2D::new(rect.origin.x, r.pages_rows_top),
size: Point2D::new(rect.size.x, r.pages_view_h),
});
for index in visible_row_range(self.pages.len(), r.pages.offset, r.pages_view_h) {
for index in visible_row_range(
self.pages.len(),
r.pages.offset,
r.pages_view_h,
PAGE_ROW_HEIGHT,
) {
let page = &self.pages[index];
y = r.pages_rows_top - r.pages.offset + index as f32 * PAGE_ROW_HEIGHT;
let row = Rect {
@ -595,7 +603,12 @@ impl Widget for LayerPanel {
origin: Point2D::new(rect.origin.x, r.layers_rows_top),
size: Point2D::new(rect.size.x, r.layers_view_h),
});
for index in visible_row_range(self.items.len(), r.layers.offset, r.layers_view_h) {
for index in visible_row_range(
self.items.len(),
r.layers.offset,
r.layers_view_h,
LAYER_ROW_HEIGHT,
) {
let item = &self.items[index];
y = r.layers_rows_top - r.layers.offset + index as f32 * LAYER_ROW_HEIGHT;
let row = Rect {

View file

@ -585,7 +585,12 @@ fn layer_panel_caches_content_widths_on_build() {
#[test]
fn visible_row_range_jumps_directly_to_scrolled_rows() {
let range = visible_row_range(1_000, LAYER_ROW_HEIGHT * 500.0, LAYER_ROW_HEIGHT * 10.0);
let range = visible_row_range(
1_000,
LAYER_ROW_HEIGHT * 500.0,
LAYER_ROW_HEIGHT * 10.0,
LAYER_ROW_HEIGHT,
);
assert_eq!(range.start, 500);
assert!(
@ -600,9 +605,55 @@ fn row_index_at_maps_scrolled_point_to_item_without_linear_scan() {
let scroll = LAYER_ROW_HEIGHT * 500.0;
let point_y = rows_top + LAYER_ROW_HEIGHT * 2.5;
let (index, row_top) = row_index_at(1_000, rows_top, scroll, LAYER_ROW_HEIGHT * 10.0, point_y)
.expect("point lands on a visible row");
let (index, row_top) = row_index_at(
1_000,
rows_top,
scroll,
LAYER_ROW_HEIGHT * 10.0,
LAYER_ROW_HEIGHT,
point_y,
)
.expect("point lands on a visible row");
assert_eq!(index, 502);
assert!((row_top - (rows_top + LAYER_ROW_HEIGHT * 2.0)).abs() < 0.01);
}
/// Pages rows are 32 px tall; the visible-row window and hit-test must
/// use PAGE_ROW_HEIGHT, not the 28 px layer height. With 45 pages
/// scrolled near the bottom, the window must still start at the row
/// whose top the offset lands on — using the wrong height skips rows
/// and leaves a blank gap above the last page.
#[test]
fn pages_visible_range_uses_page_row_height() {
// Scroll so the top of row 39 is at the viewport top.
let scroll = PAGE_ROW_HEIGHT * 39.0;
let range = visible_row_range(45, scroll, PAGE_ROW_HEIGHT * 6.0, PAGE_ROW_HEIGHT);
assert_eq!(
range.start, 39,
"pages window must start at the 32px-row index, got {range:?}"
);
assert!(
range.end >= 45,
"window must reach the last page, got {range:?}"
);
}
#[test]
fn pages_hit_test_uses_page_row_height() {
let rows_top = 60.0;
let scroll = PAGE_ROW_HEIGHT * 39.0;
// Cursor 2.5 rows down from the viewport top → row 41.
let point_y = rows_top + PAGE_ROW_HEIGHT * 2.5;
let (index, row_top) = row_index_at(
45,
rows_top,
scroll,
PAGE_ROW_HEIGHT * 6.0,
PAGE_ROW_HEIGHT,
point_y,
)
.expect("point lands on a page row");
assert_eq!(index, 41, "hit-test must map to the 32px-row index");
assert!((row_top - (rows_top + PAGE_ROW_HEIGHT * 2.0)).abs() < 0.01);
}

View file

@ -409,12 +409,13 @@ pub(super) fn visible_row_range(
row_count: usize,
scroll: f32,
viewport_h: f32,
row_height: f32,
) -> std::ops::Range<usize> {
if row_count == 0 || viewport_h <= 0.0 {
if row_count == 0 || viewport_h <= 0.0 || row_height <= 0.0 {
return 0..0;
}
let start = (scroll.max(0.0) / LAYER_ROW_HEIGHT).floor() as usize;
let visible = (viewport_h / LAYER_ROW_HEIGHT).ceil().max(0.0) as usize;
let start = (scroll.max(0.0) / row_height).floor() as usize;
let visible = (viewport_h / row_height).ceil().max(0.0) as usize;
let end = start
.saturating_add(visible)
.saturating_add(2)
@ -429,9 +430,10 @@ pub(super) fn row_index_at(
rows_top: f32,
scroll: f32,
viewport_h: f32,
row_height: f32,
point_y: f32,
) -> Option<(usize, f32)> {
if row_count == 0 || viewport_h <= 0.0 {
if row_count == 0 || viewport_h <= 0.0 || row_height <= 0.0 {
return None;
}
if point_y < rows_top || point_y > rows_top + viewport_h {
@ -441,10 +443,10 @@ pub(super) fn row_index_at(
if local_y < 0.0 {
return None;
}
let index = (local_y / LAYER_ROW_HEIGHT).floor() as usize;
let index = (local_y / row_height).floor() as usize;
if index >= row_count {
return None;
}
let row_top = rows_top - scroll.max(0.0) + index as f32 * LAYER_ROW_HEIGHT;
let row_top = rows_top - scroll.max(0.0) + index as f32 * row_height;
Some((index, row_top))
}