diff --git a/package-lock.json b/package-lock.json index ead3eb9..588d857 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,7 +72,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -3246,7 +3245,6 @@ "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -3284,7 +3282,6 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3509,7 +3506,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6155,7 +6151,6 @@ "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -6221,7 +6216,6 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -6482,7 +6476,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -6879,7 +6872,6 @@ "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, diff --git a/src/screen-name/view/AutoTrackerNode.ts b/src/screen-name/view/AutoTrackerNode.ts index 8d469e0..b38bd64 100644 --- a/src/screen-name/view/AutoTrackerNode.ts +++ b/src/screen-name/view/AutoTrackerNode.ts @@ -53,6 +53,8 @@ export class AutoTrackerNode extends Node { const hitArea = new Rectangle( 0, 0, VIDEO_W, VIDEO_H, { fill: 'transparent', cursor: 'crosshair', + tagName: 'div', + accessibleName: 'Video tracking area — drag to select object to track', } ); this.addChild( hitArea ); diff --git a/src/screen-name/view/CalibrationToolNode.ts b/src/screen-name/view/CalibrationToolNode.ts index 9f4ee0b..73dd5e0 100644 --- a/src/screen-name/view/CalibrationToolNode.ts +++ b/src/screen-name/view/CalibrationToolNode.ts @@ -1,7 +1,7 @@ -import { Circle, DragListener, HBox, Line, Node, Text } from "scenerystack/scenery"; +import { Circle, HBox, Line, Node, RichDragListener, Text } from "scenerystack/scenery"; import { Keypad, PhetFont } from "scenerystack/scenery-phet"; import { KeypadDialog } from "scenerystack/sim"; -import { Range, Vector2 } from "scenerystack/dot"; +import { Range, type Vector2 } from "scenerystack/dot"; import { DerivedProperty, NumberProperty, Property, type TReadOnlyProperty } from "scenerystack/axon"; import { ComboBox, type ComboBoxItem, Panel, TextPushButton } from "scenerystack/sun"; import { Tandem } from "scenerystack/tandem"; @@ -43,14 +43,17 @@ export class CalibrationToolNode extends Node { this.addChild( calibrationLine ); // ── Endpoint circles ────────────────────────────────────────────────── - const makeEndpoint = () => new Circle( ENDPOINT_RADIUS, { + const makeEndpoint = ( accessibleName: string ) => new Circle( ENDPOINT_RADIUS, { fill: TrackLabColors.calibrationFillProperty, stroke: TrackLabColors.textOnDarkProperty, lineWidth: 1.5, cursor: 'crosshair', + tagName: 'div', + focusable: true, + accessibleName: accessibleName, } ); - const endpoint1 = makeEndpoint(); - const endpoint2 = makeEndpoint(); + const endpoint1 = makeEndpoint( 'Calibration Point 1' ); + const endpoint2 = makeEndpoint( 'Calibration Point 2' ); this.addChild( endpoint1 ); this.addChild( endpoint2 ); @@ -135,17 +138,13 @@ export class CalibrationToolNode extends Node { // ── Drag listeners for endpoints ────────────────────────────────────── const makeDragListener = ( pointProperty: Property ) => { - let startPos = pointProperty.value.copy(); - let startPtr = new Vector2( 0, 0 ); - return new DragListener( { - start: ( event ) => { - startPos = pointProperty.value.copy(); - startPtr = this.globalToLocalPoint( event.pointer.point ); - }, - drag: ( event ) => { - const ptr = this.globalToLocalPoint( event.pointer.point ); - pointProperty.value = startPos.plus( ptr.minus( startPtr ) ); + return new RichDragListener( { + positionProperty: pointProperty, + keyboardDragListenerOptions: { + dragSpeed: 200, + shiftDragSpeed: 40, }, + tandem: Tandem.OPT_OUT, } ); }; endpoint1.addInputListener( makeDragListener( this.point1Property ) ); diff --git a/src/screen-name/view/CoordinateSystemNode.ts b/src/screen-name/view/CoordinateSystemNode.ts index 1720ac9..09a8363 100644 --- a/src/screen-name/view/CoordinateSystemNode.ts +++ b/src/screen-name/view/CoordinateSystemNode.ts @@ -1,7 +1,8 @@ -import { Circle, DragListener, Node, Text } from "scenerystack/scenery"; +import { Circle, Node, RichDragListener, Text } from "scenerystack/scenery"; import { ArrowNode, PhetFont } from "scenerystack/scenery-phet"; -import { Vector2 } from "scenerystack/dot"; +import type { Vector2 } from "scenerystack/dot"; import { NumberProperty, Property, type TReadOnlyProperty } from "scenerystack/axon"; +import { Tandem } from "scenerystack/tandem"; import TrackLabColors from "../../TrackLabColors.js"; const ARROW_LENGTH = 120; @@ -61,6 +62,9 @@ export class CoordinateSystemNode extends Node { stroke: TrackLabColors.textOnDarkProperty, lineWidth: 1.5, cursor: 'crosshair', + tagName: 'div', + focusable: true, + accessibleName: 'Rotation Handle', } ); rotatingNode.addChild( handleDisk ); @@ -72,7 +76,13 @@ export class CoordinateSystemNode extends Node { } ); // ── Position wrapper: translates with viewPositionProperty ─────────── - const positionNode = new Node( { children: [ rotatingNode, originMarker ], cursor: 'move' } ); + const positionNode = new Node( { + children: [ rotatingNode, originMarker ], + cursor: 'move', + tagName: 'div', + focusable: true, + accessibleName: 'Coordinate System', + } ); this.addChild( positionNode ); // ── Property → scene-graph linkage ──────────────────────────────────── @@ -80,28 +90,34 @@ export class CoordinateSystemNode extends Node { this.rotationAngleProperty.link( angle => { rotatingNode.rotation = angle; } ); // ── Drag: translate the entire coordinate system ────────────────────── - let startPos = initialPosition.copy(); - let startPtr = new Vector2( 0, 0 ); - - positionNode.addInputListener( new DragListener( { - start: ( event ) => { - startPos = this.viewPositionProperty.value.copy(); - startPtr = this.globalToLocalPoint( event.pointer.point ); - }, - drag: ( event ) => { - const ptr = this.globalToLocalPoint( event.pointer.point ); - this.viewPositionProperty.value = startPos.plus( ptr.minus( startPtr ) ); + positionNode.addInputListener( new RichDragListener( { + positionProperty: this.viewPositionProperty, + keyboardDragListenerOptions: { + dragSpeed: 300, + shiftDragSpeed: 50, }, + tandem: Tandem.OPT_OUT, } ) ); // ── Drag: rotate around origin ──────────────────────────────────────── // Dragging the handle disk updates the rotation angle based on the // pointer's angle relative to the coordinate-system origin. - handleDisk.addInputListener( new DragListener( { - drag: ( event ) => { - const p = positionNode.globalToLocalPoint( event.pointer.point ); - this.rotationAngleProperty.value = Math.atan2( p.y, p.x ); + handleDisk.addInputListener( new RichDragListener( { + dragListenerOptions: { + drag: ( event ) => { + const p = positionNode.globalToLocalPoint( event.pointer.point ); + this.rotationAngleProperty.value = Math.atan2( p.y, p.x ); + }, + }, + keyboardDragListenerOptions: { + keyboardDragDirection: 'leftRight', + dragSpeed: 100, + shiftDragSpeed: 20, + drag: ( _event, listener ) => { + this.rotationAngleProperty.value += listener.modelDelta.x * ( Math.PI / 180 ); + }, }, + tandem: Tandem.OPT_OUT, } ) ); // ── Visibility: only shown once a video with a finite duration is loaded diff --git a/src/screen-name/view/KeyboardShortcutsNode.ts b/src/screen-name/view/KeyboardShortcutsNode.ts index 70b3ebd..2a78bee 100644 --- a/src/screen-name/view/KeyboardShortcutsNode.ts +++ b/src/screen-name/view/KeyboardShortcutsNode.ts @@ -1,10 +1,17 @@ -import { Node } from "scenerystack/scenery"; -import { BasicActionsKeyboardHelpSection } from "scenerystack/scenery-phet"; +import { Node, VBox } from "scenerystack/scenery"; +import { BasicActionsKeyboardHelpSection, MoveDraggableItemsKeyboardHelpSection } from "scenerystack/scenery-phet"; export class KeyboardShortcutsNode extends Node { public constructor() { super(); - this.addChild( new BasicActionsKeyboardHelpSection() ); + this.addChild( new VBox( { + children: [ + new BasicActionsKeyboardHelpSection(), + new MoveDraggableItemsKeyboardHelpSection(), + ], + spacing: 16, + align: 'left', + } ) ); } }