Record the whole editor from the main menu
The game view could already record itself through nodes. Making a video of the editor itself, to show how something is done, needed an outside screen recorder. The main menu now starts and stops one, with no options to set. - "Start recording" sits in its own group before General settings, and reads "Stop recording" while one runs. The name is "Recording <time>", the size is the whole canvas in device pixels (an odd row or column is cut), 60 fps always. Sound is the master, saving goes the same way as for the game view. - The encoder, the sound and the Matroska writing of a take moved from GameRecorder.ts to RecordingTake.ts. A take now reads its pictures from a FrameSource, so the game view and the editor share it. - The editor's picture is copied from the canvas after each render, in the same frame, so nothing is drawn twice. A resting editor does not render, and the recording repeats its last picture. - The mouse pointer is drawn into the recording in the shape the editor shows (arrow, hand, resize arrows, I-beam, crosshair, move). - A red dot with the running time stands beside the menu button on screen only. It is drawn after the copy, and the toolbar makes room for it. - Drawing that dot made Pixi take it as the root for every hit test, and the menu would not open any more. The stage is set back as the last rendered object after it. Tests: - Gate on melektaus-atx green: 303 files, 4686 tests. - Unit recording: 20 green, 5 new. - e2e 67: 8 checks, 67e new. It records through the menu and decodes the MP4: 1280x800 at 60 fps, the pointer black at the mouse, the dot missing, 238 plain spots alike with the canvas. Without the pointer and with the dot drawn before the copy, 67e is red. - e2e 46: the main menu lists "Start recording". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c0ba2673ba
commit
f177ee90a4
13 files changed
+1190
-478
No files matched your search
+35
-6
@@ -78,7 +78,8 @@ import { TilemapPanel } from './panels/TilemapPanel';
|
||||
import { Toolbar } from './panels/Toolbar';
|
||||
import { type PlayHost, PlaySession, type WorldSnapshot } from './PlaySession';
|
||||
import { type RecordingUpload, RecordingUploads } from './recordingUploads';
|
||||
import type { MadeRecording } from '../viewport/GameRecorder';
|
||||
import { EditorRecorder } from './EditorRecorder';
|
||||
import type { MadeRecording } from '../viewport/RecordingTake';
|
||||
import { ProjectSession } from './ProjectSession';
|
||||
import { ParticlesEditor } from './particles/ParticlesEditor';
|
||||
import { ShaderEditor } from './shader/ShaderEditor';
|
||||
@@ -321,6 +322,8 @@ export class Editor implements PlayHost {
|
||||
fps: number | null = null;
|
||||
private readonly play: PlaySession;
|
||||
private readonly recordings: RecordingUploads;
|
||||
/** Records the whole editor from the main menu. */
|
||||
private readonly editorRecorder: EditorRecorder;
|
||||
private readonly editorRun: EditorRun;
|
||||
private readonly projects: ProjectSession;
|
||||
private readonly leftColumn: VStack;
|
||||
@@ -413,6 +416,15 @@ export class Editor implements PlayHost {
|
||||
this.inspector = new Inspector(state);
|
||||
this.tilemapPanel = new TilemapPanel(state);
|
||||
this.recordings = new RecordingUploads({ state, videos });
|
||||
this.editorRecorder = new EditorRecorder({
|
||||
log: (line) => {
|
||||
state.setNotice(line);
|
||||
console.warn('[recording]', line);
|
||||
},
|
||||
saved: (recording) => this.recordings.add(recording),
|
||||
showBadge: (on) => this.toolbar.showRecording(on),
|
||||
badgeRoom: () => this.toolbar.recordingRoom(),
|
||||
});
|
||||
this.play = new PlaySession(state, this);
|
||||
this.editorRun = new EditorRun(state, {
|
||||
// The game view is the one place where no key means anything to the editor, so that is where
|
||||
@@ -792,13 +804,19 @@ export class Editor implements PlayHost {
|
||||
}
|
||||
|
||||
/**
|
||||
* The main menu's last group (#179): the user management for an administrator, and the plugins
|
||||
* for everybody – both open the one tab, which holds the plugins below the accounts.
|
||||
* The main menu's last groups: recording the editor, then the user management for an
|
||||
* administrator and the plugins for everybody (#179) – both open the one tab, which holds the
|
||||
* plugins below the accounts.
|
||||
*/
|
||||
private managementEntries(): MenuEntry[] {
|
||||
const open = () => this.openDocument('management');
|
||||
const recorder = this.editorRecorder;
|
||||
return [
|
||||
{ separator: true },
|
||||
recorder.isRecording
|
||||
? { label: 'Stop recording', onSelect: () => recorder.stop() }
|
||||
: { label: 'Start recording', onSelect: () => recorder.start() },
|
||||
{ separator: true },
|
||||
{ label: 'General settings…', onSelect: () => this.openGeneralSettings() },
|
||||
...(this.usersPane ? [{ label: 'User management', onSelect: open }] : []),
|
||||
{ label: 'Plugins', onSelect: open },
|
||||
@@ -1323,6 +1341,7 @@ export class Editor implements PlayHost {
|
||||
await this.autosave.flush();
|
||||
this.autosave.stop();
|
||||
if (this.play.isPlaying) this.play.stop();
|
||||
this.editorRecorder.stop();
|
||||
this.editorRun.shutDown();
|
||||
await this.plugins?.stopAll();
|
||||
for (const cleanup of this.cleanups.splice(0).reverse()) cleanup();
|
||||
@@ -1511,9 +1530,19 @@ export class Editor implements PlayHost {
|
||||
};
|
||||
}
|
||||
|
||||
/** The recordings of the game view (#405) and what became of them on the way into the project. */
|
||||
recordingInfo(): ReturnType<PlaySession['recordingInfo']> & { uploads: RecordingUpload[] } {
|
||||
return { ...this.play.recordingInfo(), uploads: this.recordings.info() };
|
||||
/**
|
||||
* The recordings of the game view (#405), those of the editor, and what became of them on the way
|
||||
* into the project.
|
||||
*/
|
||||
recordingInfo(): ReturnType<PlaySession['recordingInfo']> & {
|
||||
editor: ReturnType<EditorRecorder['info']>;
|
||||
uploads: RecordingUpload[];
|
||||
} {
|
||||
return {
|
||||
...this.play.recordingInfo(),
|
||||
editor: this.editorRecorder.info(),
|
||||
uploads: this.recordings.info(),
|
||||
};
|
||||
}
|
||||
|
||||
recordingSaved(recording: MadeRecording): void {
|
||||
|
||||
Reference in new issue
Block a user