Scroll the rows of the animation timeline up and down (#413)
game-maker / gate (push) Successful in 4m28s
game-maker / suite (push) Successful in 18m13s

An animation with more rows than the panel is tall cut the bottom rows
off. There was no way to reach them, their fields or their keys.

- The view has a new `scrollY`. Rows scroll up under the ruler, which
  stays where it is together with the tab head.
- The wheel over the header column scrolls the rows. Over the cells it
  still zooms, and `Shift`+wheel still scrolls across.
- A scrollbar at the right edge shows up only when the rows do not
  fit. Its thumb can be dragged, a click beside it jumps there.
- The middle button pans both ways. `Shift`+drag stays across only.
- A row picked or added out of view is scrolled just far enough to be
  seen whole. Value fields out of view are not drawn.
- The tab keeps its scroll like its zoom when switching.

Found on the way: `zoomCells` built a new view from `panX` and
`cellWidth` only, so every zoom would have dropped the scroll. The
type check caught it, and a unit test now holds it.

Tests:
- tsc, eslint, Prettier clean
- Unit timeline-view: 20 green, 6 new. Bending `rowAt` to ignore the
  scroll or `revealRow` to do nothing turns exactly those tests red.
- e2e 25 on melektaus-atx: ALL PASSED, 37 checks (25j, 25j-ii new).
  32 rows, the wheel brings the last field into view and typing keys
  77 there. Dragging the thumb up goes back to 0.
- Gate on melektaus-atx: green, 305 files, 4730 tests.
- Not checked in the browser: the middle button, revealing a picked
  row, the wheel right over a value field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-13 07:22:11 +02:00
1 parent 0648853356
commit 480db9c4e4
6 files changed
+341 -56

No files matched your search

+50 -1
View File
@@ -12,7 +12,7 @@ const SIZE = 40;
* removes everything it made.
*/
export async function animation(t) {
const { check, json, evaluate, mutate, shot, click, doubleClick, drag, press, typeText } = t;
const { check, json, evaluate, mutate, shot, click, doubleClick, drag, press, typeText, wheel } = t;
const { undo, settle, sleep, waitUntil, find } = t;
await press('Escape');
@@ -496,6 +496,55 @@ export async function animation(t) {
JSON.stringify({ shownX, keyedX }),
);
// 25j. More rows than room (#413): 16 objects with a row each. The wheel over the header column
// scrolls the rows, the last one comes into view and its field keys a typed value; the
// scrollbar's thumb dragged up takes them back to the top.
await mutate(`
const p = __gm.state.project;
const box = p.assets.find((a) => a.id === 'an-box');
const arm = box.objects[0];
for (let i = 2; i <= 16; i++)
box.objects.push({ ...structuredClone(arm), id: 'an-arm' + i, name: 'Arm ' + i });
p.assets.find((a) => a.id === 'an-walk').tracks = box.objects.map((o, i) => (
{ id: i === 15 ? 'an-last' : 'an-s' + i, target: o.id, property: 'x', keys: [] }
));
`);
await evaluate('__gm.animation.scrub(2)');
await sleep(150);
const before = { view: await json('__gm.animation.view()'), thumb: await json('__gm.animation.thumb()') };
const lastHidden = await json("__gm.animation.valueAt('an-last')");
const header = await json("__gm.animation.headerAt('an-s0')");
await wheel(header.x, header.y, 5000);
const scrolled = await json('__gm.animation.view()');
const lastField = await json("__gm.animation.valueAt('an-last')");
if (lastField) {
await click(lastField.x, lastField.y);
await typeText('77');
await press('Enter');
await sleep(150);
}
const lastKeys = (await walk()).tracks.find((r) => r.id === 'an-last').keys;
check(
'the wheel over the headers scrolls to the last row, and its field keys a typed value',
before.view.scrollY === 0 &&
before.thumb !== null &&
lastHidden === null &&
scrolled.scrollY === before.thumb.max &&
scrolled.cellWidth === before.view.cellWidth &&
lastField !== null &&
JSON.stringify(lastKeys) === JSON.stringify([{ frame: 2, value: 77 }]),
JSON.stringify({ before, lastHidden, scrolled, lastField, lastKeys }),
);
await shot('21-scrolled-rows');
const thumb = await json('__gm.animation.thumb()');
await drag(thumb.x, thumb.y, thumb.x, 5);
const toTop = await json('__gm.animation.view()');
check(
'the scrollbar thumb dragged up takes the rows back to the top',
toTop.scrollY === 0 && (await json("__gm.animation.valueAt('an-last')")) === null,
JSON.stringify({ thumb, toTop }),
);
await mutate(`
const p = __gm.state.project;
p.assets = p.assets.filter((a) => !['an-walk', 'an-box', 'an-sprite'].includes(a.id));