diff --git a/README.md b/README.md index 5bc1b7cb..cdd508ba 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ npm run build # writes dist/ and regenerates index.html; the folder is wh The browser suite and the three example games need a machine with a GPU: ```bash -node tools/seed-e2e.mjs && node tools/e2e.mjs # the editor, 2048 checks (GM_STEPS=20 for one step) +node tools/seed-e2e.mjs && node tools/e2e.mjs # the editor, 2049 checks (GM_STEPS=20 for one step) # every tool signs in first – see the Accounts page node tools/seed-jumprun.mjs && node tools/smoke-jumprun.mjs node tools/seed-tetris.mjs && node tools/smoke-tetris.mjs diff --git a/src/editor/composition/CompositionCanvas.ts b/src/editor/composition/CompositionCanvas.ts index faa3df8b..b8bf14a0 100644 --- a/src/editor/composition/CompositionCanvas.ts +++ b/src/editor/composition/CompositionCanvas.ts @@ -4,6 +4,7 @@ import { findAssetOf } from '../../core/model/Project'; import { compositionVideosAhead } from '../../viewport/items'; import type { VideoHearing } from '../../viewport/VideoLibrary'; import type { Frame } from '../../viewport/WorldViewport'; +import { isDragFrame } from '../framePreview'; import type { EditorState } from '../state'; import { SceneViewport } from '../viewport/SceneViewport'; @@ -24,7 +25,11 @@ export class CompositionCanvas extends SceneViewport { private readonly playhead: () => number, ) { super(state); - state.on('project', () => this.rebuildPicture()); + // A gesture here writes the copy shown and asks for a preview once a frame. Building the copy + // again from the composition then would drop what it moved until the pointer lets go. + state.on('project', () => { + if (!(isDragFrame() && this.editing())) this.rebuildPicture(); + }); state.on('load', () => this.rebuildPicture()); } diff --git a/src/editor/viewport/SceneViewport.ts b/src/editor/viewport/SceneViewport.ts index 6b827905..ac8e2ebd 100644 --- a/src/editor/viewport/SceneViewport.ts +++ b/src/editor/viewport/SceneViewport.ts @@ -240,6 +240,17 @@ export class SceneViewport extends FreeViewport { if (this.collectFrame().items.length > 0) this.frameScene(); } + /** Whether a gesture of this view is under way: a drag, a handle, a corner, an anchor or a stroke. */ + protected editing(): boolean { + return ( + this.dragging !== null || + this.sizer.active || + this.corners.active || + this.anchors.active || + this.painter.active + ); + } + /** The Container tab keeps the view of each container instead (#171). */ protected framesOnOpen(): boolean { return true; diff --git a/tools/e2e/69-composition-tab.mjs b/tools/e2e/69-composition-tab.mjs index e19b2ce6..92ad758b 100644 --- a/tools/e2e/69-composition-tab.mjs +++ b/tools/e2e/69-composition-tab.mjs @@ -6,7 +6,8 @@ * takes an item with its object, and Play runs the playhead to the end. The video of an item about * to begin is opened ahead, so the cut to it shows its picture at once, and Play waits for a video * whose player is still loading. A copied item is pasted under the pointer or at the playhead. A - * video dragged in wears a sprite the composition keeps to itself. + * video dragged in wears a sprite the composition keeps to itself. An item dragged in the picture + * follows the pointer while the button is down. * * Everything it adds carries a `ct69` id and goes again at the end. */ @@ -348,6 +349,34 @@ export async function compositionTab(t) { JSON.stringify({ videoDrop, one, row, two, undone }), ); + // 69l. An item dragged in the picture follows the pointer while the button is down, not only when + // it is let go: the drag's preview each frame does not build the picture again from the document. + await evaluate('__gm.composition.scrub(15)'); + await sleep(100); + const front = (await json('__gm.composition.shown()')).at(-1)?.id ?? ''; + const startAt = await json(`__gm.composition.objectScreen('${front}')`); + const objectX = () => + json( + `__gm.state.project.assets.find((a) => a.id === '${CUT}').objects.find((o) => o.id === '${front}').position.x`, + ); + const xBefore = await objectX(); + await mouse('mouseMoved', startAt.x, startAt.y); + await mouse('mousePressed', startAt.x, startAt.y); + for (let i = 1; i <= 6; i++) { + await mouse('mouseMoved', startAt.x + i * 10, startAt.y, 'left', { buttons: 1 }); + await sleep(16); + } + await sleep(100); + const during = await json(`__gm.composition.objectScreen('${front}')`); + await mouse('mouseReleased', startAt.x + 60, startAt.y); + await sleep(150); + const xAfter = await objectX(); + check( + 'an item dragged in the picture follows the pointer while dragged and keeps the place let go', + Math.abs(during.x - startAt.x - 60) < 2 && xAfter > xBefore, + JSON.stringify({ front, startAt, during, xBefore, xAfter }), + ); + await showTab('scene'); await mutate(` const p = __gm.state.project;