Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/TrackLabConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export const PANEL_CORNER_RADIUS = 8;
// ── Screen layout bounds ───────────────────────────────────────────────────────
// SceneryStack's ScreenView.DEFAULT_LAYOUT_BOUNDS = Bounds2(0, 0, 1024, 618).
const LAYOUT_WIDTH = 1024;
const LAYOUT_HEIGHT = 618;

// ── Video display dimensions ───────────────────────────────────────────────────
// The video element is always rendered at this fixed pixel size.
Expand All @@ -22,10 +21,12 @@ export const VIDEO_WIDTH = 640;
export const VIDEO_HEIGHT = 360;

// ── Video position in screen (layout) coordinates ────────────────────────────
// The video element is centered at layoutBounds.center + (0, VIDEO_PLAYER_Y_OFFSET).
export const VIDEO_PLAYER_Y_OFFSET = -20; // video center offset below layout center
// The video player VBox is top-anchored at y=10. The source-control row
// (~40 px) plus MAIN_CONTENT_SPACING (10 px) places the video top at ~60 px,
// so the video centre sits at approximately y=240.
export const VIDEO_PLAYER_Y_OFFSET = -20; // kept for reference; no longer used for positioning
export const VIDEO_CENTER_X = LAYOUT_WIDTH / 2; // 512
export const VIDEO_CENTER_Y = LAYOUT_HEIGHT / 2 + VIDEO_PLAYER_Y_OFFSET; // 289
export const VIDEO_CENTER_Y = 240; // approximate video centre with top-anchored layout

// ── Initial calibration tool geometry ─────────────────────────────────────────
// Half-length of the default calibration segment (pixels from centre to each endpoint).
Expand Down
8 changes: 3 additions & 5 deletions src/screen-name/view/PlaybackControlsNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,9 @@ export class PlaybackControlsNode extends HBox {
// ── Time and frame info display ────────────────────────────────────────
const formatDuration = (seconds: number): string => {
if (!Number.isFinite(seconds) || seconds <= 0) {
return "0:00";
return "0.00 s";
}
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${String(secs).padStart(2, "0")}`;
return `${seconds.toFixed(2)} s`;
};

const totalTimeTextProperty = new DerivedProperty([model.durationProperty], (duration: number) =>
Expand Down Expand Up @@ -167,7 +165,7 @@ export class PlaybackControlsNode extends HBox {
tandem: Tandem.OPT_OUT,
});

this.children = [infoDisplay, timeControlNode, scrubber, rewindButton];
this.children = [timeControlNode, scrubber, rewindButton, infoDisplay];

this.disposePlaybackControlsNode = () => {
timeSpeedProperty.unlink(onSpeedChange);
Expand Down
13 changes: 11 additions & 2 deletions src/screen-name/view/SimScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
CONTROL_PANEL_LEFT_MARGIN,
DATA_TABLE_TOP_SPACING,
RESET_BUTTON_MARGIN,
VIDEO_PLAYER_Y_OFFSET,
} from "../../TrackLabConstants.js";
import type { SimModel } from "../model/SimModel.js";
import { CalibrationToolNode } from "./CalibrationToolNode.js";
Expand Down Expand Up @@ -66,7 +65,7 @@ export class SimScreenView extends ScreenView {
// SimModel from the tool state properties above).
this.videoPlayerNode = new VideoPlayerNode(model, this);
this.videoPlayerNode.left = controlPanel.right + 20;
this.videoPlayerNode.centerY = this.layoutBounds.centerY + VIDEO_PLAYER_Y_OFFSET;
this.videoPlayerNode.top = this.layoutBounds.top + 10;
this.addChild(this.videoPlayerNode);

// ── Coordinate system overlay (above video, below camera modal) ─────────
Expand Down Expand Up @@ -96,6 +95,16 @@ export class SimScreenView extends ScreenView {
});
this.addChild(resetAllButton);

// ── Playback controls bar (bottom of screen, same height as reset button) ─
const playbackControlsNode = this.videoPlayerNode.playbackControlsNode;
this.addChild(playbackControlsNode);
playbackControlsNode.centerX = this.videoPlayerNode.centerX;
playbackControlsNode.centerY = resetAllButton.centerY;
playbackControlsNode.boundsProperty.lazyLink(() => {
playbackControlsNode.centerX = this.videoPlayerNode.centerX;
playbackControlsNode.centerY = resetAllButton.centerY;
});

// ── Kinematics graph (bottom right, above reset all) ─────────────────
const kinematicsGraph = new KinematicsGraphNode(model, this, trackLabPreferences);
this.addChild(kinematicsGraph);
Expand Down
16 changes: 9 additions & 7 deletions src/screen-name/view/VideoPlayerNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export class VideoPlayerNode extends Node {
private readonly videoElement: HTMLVideoElement;
/** Webcam recording panel; positioned by SimScreenView for correct z-ordering. */
public readonly webcamPanel: WebcamPanel;
/** Playback controls bar; positioned by SimScreenView at the bottom of the screen. */
public readonly playbackControlsNode: PlaybackControlsNode;
private readonly model: SimModel;
private readonly disposeVideoPlayer: () => void;
/** Tracks the current blob URL so it can be revoked when a new one is loaded. */
Expand Down Expand Up @@ -103,15 +105,15 @@ export class VideoPlayerNode extends Node {
};
model.playbackRateProperty.link(playbackRateListener);

// ── Playback controls ─────────────────────────────────────────────────
const playbackControlsNode = new PlaybackControlsNode(
// ── Playback controls (positioned by SimScreenView at screen bottom) ──
this.playbackControlsNode = new PlaybackControlsNode(
model,
this.videoElement,
() => this.seekByFrames(-1),
() => this.seekByFrames(1),
);
// Pin to the video width so internal text changes never shift the row.
playbackControlsNode.preferredWidth = VIDEO_WIDTH;
this.playbackControlsNode.preferredWidth = VIDEO_WIDTH;

// ── Fit video element to its intrinsic aspect ratio ───────────────────
// When a new clip is loaded, scale it to fill as much of VIDEO_WIDTH ×
Expand All @@ -131,14 +133,14 @@ export class VideoPlayerNode extends Node {
this.videoElement.width = displayW;
this.videoElement.height = displayH;
model.videoDimensionsProperty.value = new Dimension2(displayW, displayH);
playbackControlsNode.preferredWidth = displayW;
this.playbackControlsNode.preferredWidth = displayW;
model.tracker.resize(displayW, displayH);
};
this.videoElement.addEventListener("loadedmetadata", onDimensionsLoaded);

// Sync model time from video during playback (event-driven, not polled)
const onTimeUpdate = () => {
if (!playbackControlsNode.scrubbing) {
if (!this.playbackControlsNode.scrubbing) {
model.currentTimeProperty.value = this.videoElement.currentTime;
}
};
Expand Down Expand Up @@ -173,7 +175,7 @@ export class VideoPlayerNode extends Node {

// ── Layout ─────────────────────────────────────────────────────────────
const mainContent = new VBox({
children: [videoSourceControlNode, videoLayer, playbackControlsNode],
children: [videoSourceControlNode, videoLayer],
spacing: MAIN_CONTENT_SPACING,
align: "center",
});
Expand Down Expand Up @@ -208,7 +210,7 @@ export class VideoPlayerNode extends Node {
this.currentBlobUrl = null;
}
autoTrackerNode.dispose();
playbackControlsNode.dispose();
this.playbackControlsNode.dispose();
videoSourceControlNode.dispose();
autoTrackingShownProperty.dispose();
};
Expand Down