From 05189d716ec6324af9dd32b0bfce720593d8376e Mon Sep 17 00:00:00 2001 From: zepumph Date: Mon, 18 May 2020 13:08:21 -0800 Subject: [PATCH] convert from keyboardDragListener to accessibleSlider, support "snap-to" keyboard nav, https://github.com/phetsims/ratio-and-proportion/issues/47 --- js/free-objects/view/RatioHalf.js | 124 ++++++++++++++++++------------ 1 file changed, 76 insertions(+), 48 deletions(-) diff --git a/js/free-objects/view/RatioHalf.js b/js/free-objects/view/RatioHalf.js index 22808148..68202166 100644 --- a/js/free-objects/view/RatioHalf.js +++ b/js/free-objects/view/RatioHalf.js @@ -12,6 +12,7 @@ import BooleanProperty from '../../../../axon/js/BooleanProperty.js'; import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; +import Property from '../../../../axon/js/Property.js'; import Dimension2 from '../../../../dot/js/Dimension2.js'; import Vector2 from '../../../../dot/js/Vector2.js'; import merge from '../../../../phet-core/js/merge.js'; @@ -19,10 +20,10 @@ import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransfo import ArrowNode from '../../../../scenery-phet/js/ArrowNode.js'; import PlusNode from '../../../../scenery-phet/js/PlusNode.js'; import DragListener from '../../../../scenery/js/listeners/DragListener.js'; -import KeyboardDragListener from '../../../../scenery/js/listeners/KeyboardDragListener.js'; import Circle from '../../../../scenery/js/nodes/Circle.js'; import Image from '../../../../scenery/js/nodes/Image.js'; import Rectangle from '../../../../scenery/js/nodes/Rectangle.js'; +import AccessibleSlider from '../../../../sun/js/accessibility/AccessibleSlider.js'; import SoundClip from '../../../../tambo/js/sound-generators/SoundClip.js'; import soundManager from '../../../../tambo/js/soundManager.js'; import Tandem from '../../../../tandem/js/Tandem.js'; @@ -91,34 +92,11 @@ class RatioHalf extends Rectangle { this.alertManager = new FreeObjectAlertManager( valueProperty, gridViewProperty, ratioDescriber, gridDescriber, options.isRight ); // The draggable element inside the Node framed with thick rectangles on the top and bottom. - const pointer = new Node( { - - // pdom - tagName: 'div', - role: 'application', - focusable: true + const pointer = new Pointer( valueProperty, valueRange, { + startDrag: () => { firstInteractionProperty.value = false; } } ); this.addChild( pointer ); - const handNode = new Image( filledInHandImage ); - - // Flip the hand if it isn't a right hand. - handNode.setScaleMagnitude( ( options.isRight ? 1 : -1 ) * .4, .4 ); - handNode.right = handNode.width / 2; - handNode.bottom = handNode.height / 2; - - const circle = new Circle( 20, { - fill: 'black' - } ); - circle.center = Vector2.ZERO; - - const cross = new PlusNode( { - size: new Dimension2( 40, 8 ) - } ); - const crossBackground = Rectangle.bounds( cross.bounds ); - const crossNode = new Node( { children: [ cross, crossBackground ] } ); - crossNode.center = Vector2.ZERO; - // Sound for the wave slider clicks const addSoundOptions = { categoryName: 'user-interface' }; const soundClipOptions = { @@ -183,14 +161,6 @@ class RatioHalf extends Rectangle { } } ); pointer.addInputListener( dragListener ); - - // transform and dragBounds set in layout code below - const keyboardDragListener = new KeyboardDragListener( { - positionProperty: positionProperty, - start: () => { firstInteractionProperty.value = false; }, - end: () => this.alertManager.dragEndListener( valueProperty.get() ) - } ); - pointer.addInputListener( keyboardDragListener ); pointer.addInputListener( { focus: () => { commonGrabSoundClip.play(); @@ -238,22 +208,10 @@ class RatioHalf extends Rectangle { const dragBounds = positionProperty.validBounds.erodedX( modelHalfPointerPointer.x ); dragListener.dragBounds = dragBounds; - keyboardDragListener.dragBounds = dragBounds; }; designingProperties.markerDisplayProperty.link( displayType => { - if ( displayType === CursorDisplay.CIRCLE ) { - pointer.children = [ circle ]; - } - else if ( displayType === CursorDisplay.CROSS ) { - pointer.children = [ crossNode ]; - } - else if ( displayType === CursorDisplay.HAND ) { - pointer.children = [ handNode ]; - } - else { - assert && assert( false, `unsupported displayType: ${displayType}` ); - } + pointer.updatePointerView( displayType ); recomputeDragBounds(); } ); @@ -288,7 +246,6 @@ class RatioHalf extends Rectangle { recomputeDragBounds(); dragListener.transform = modelViewTransform; - keyboardDragListener.transform = modelViewTransform; }; } @@ -311,5 +268,76 @@ class RatioHalf extends Rectangle { // @public - the height of the top and bottom rectangles RatioHalf.FRAMING_RECTANGLE_HEIGHT = FRAMING_RECTANGLE_HEIGHT; +class Pointer extends Node { + + /** + * + * @param {Property.} valueProperty + * @param {Range} valueRange + * @param {Object} [options] + */ + constructor( valueProperty, valueRange, options ) { + + options = merge( {}, options ); + super(); + + // Always the same range, always enabled + this.initializeAccessibleSlider( valueProperty, new Property( valueRange ), new BooleanProperty( true ), options ); + + // @private + this.handNode = new Image( filledInHandImage ); + + // Flip the hand if it isn't a right hand. + this.handNode.setScaleMagnitude( ( options.isRight ? 1 : -1 ) * .4, .4 ); + this.handNode.right = this.handNode.width / 2; + this.handNode.bottom = this.handNode.height / 2; + + // @private + this.circleNode = new Circle( 20, { + fill: 'black' + } ); + this.circleNode.center = Vector2.ZERO; + + const cross = new PlusNode( { + size: new Dimension2( 40, 8 ) + } ); + const crossBackground = Rectangle.bounds( cross.bounds ); + + // @private + this.crossNode = new Node( { children: [ cross, crossBackground ] } ); + this.crossNode.center = Vector2.ZERO; + + designingProperties.gridBaseUnitProperty.link( baseUnit => { + const downDelta = 1 / baseUnit; + this.setKeyboardStep( downDelta ); + this.setShiftKeyboardStep( downDelta / 4 ); + this.setPageKeyboardStep( 1 / 5 ); + } ); + + this.mutate( options ); + } + + /** + * @public + * @param {CursorDisplay} cursorDisplay + */ + updatePointerView( cursorDisplay ) { + if ( cursorDisplay === CursorDisplay.CIRCLE ) { + this.children = [ this.circleNode ]; + } + else if ( cursorDisplay === CursorDisplay.CROSS ) { + this.children = [ this.crossNode ]; + } + else if ( cursorDisplay === CursorDisplay.HAND ) { + this.children = [ this.handNode ]; + } + else { + assert && assert( false, `unsupported cursorDisplay: ${cursorDisplay}` ); + } + } +} + +AccessibleSlider.mixInto( Pointer ); + ratioAndProportion.register( 'RatioHalf', RatioHalf ); export default RatioHalf; \ No newline at end of file