diff --git a/js/AquaRadioButton.js b/js/AquaRadioButton.js index 863fa0d3..1b393633 100644 --- a/js/AquaRadioButton.js +++ b/js/AquaRadioButton.js @@ -10,10 +10,11 @@ define( function( require ) { // modules var AquaRadioButtonIO = require( 'SUN/AquaRadioButtonIO' ); + var ButtonListener = require( 'SCENERY/input/ButtonListener' ); var Circle = require( 'SCENERY/nodes/Circle' ); var inherit = require( 'PHET_CORE/inherit' ); var Node = require( 'SCENERY/nodes/Node' ); - var RadioButton = require( 'SUN/RadioButton' ); + var Rectangle = require( 'SCENERY/nodes/Rectangle' ); var sun = require( 'SUN/sun' ); var Tandem = require( 'TANDEM/Tandem' ); @@ -31,6 +32,7 @@ define( function( require ) { options = _.extend( { cursor: 'pointer', + enabled: true, selectedColor: 'rgb( 143, 197, 250 )', // color used to fill the button when it's selected deselectedColor: 'white', // color used to fill the button when it's deselected centerColor: 'black', // color used to fill the center of teh button when it's selected @@ -38,11 +40,23 @@ define( function( require ) { xSpacing: 8, // horizontal space between the button and the node stroke: 'black', // color used to stroke the outer edge of the button tandem: Tandem.required, - a11yNameAttribute: 'aquaRadioButton', - phetioType: AquaRadioButtonIO + phetioType: AquaRadioButtonIO, + + // a11y + tagName: 'input', + inputType: 'radio', + parentContainerTagName: 'li', + labelTagName: 'label', + prependLabels: true, + a11yNameAttribute: 'aquaRadioButton' }, options ); - // selected node + var self = this; + + // @private + this._enabled = options.enabled; + + // selected node creation var selectedNode = new Node(); var innerCircle = new Circle( options.radius / 3, { fill: options.centerColor } ); var outerCircleSelected = new Circle( options.radius, { fill: options.selectedColor, stroke: options.stroke } ); @@ -69,12 +83,73 @@ define( function( require ) { node.left = this.deselectedCircleButton.right + options.xSpacing; node.centerY = this.deselectedCircleButton.centerY; - RadioButton.call( this, property, value, selectedNode, deselectedNode, options ); + Node.call( this ); + + //Add an invisible node to make sure the layout for selected vs deselected is the same + var background = new Rectangle( selectedNode.bounds.union( deselectedNode.bounds ) ); + selectedNode.pickable = deselectedNode.pickable = false; // the background rectangle suffices + + this.addChild( background ); + this.addChild( selectedNode ); + this.addChild( deselectedNode ); + + // sync control with model + var syncWithModel = function( newValue ) { + selectedNode.visible = ( newValue === value ); + deselectedNode.visible = !selectedNode.visible; + }; + property.link( syncWithModel ); + + // set property value on fire + var fire = function() { + options.tandem.isSuppliedAndEnabled() && self.startEvent( 'user', 'fired', { + value: property.phetioType.elementType.toStateObject( value ) + } ); + property.set( value ); + options.tandem.isSuppliedAndEnabled() && self.endEvent(); + }; + var buttonListener = new ButtonListener( { fire: fire } ); + this.addInputListener( buttonListener ); + + // a11y - input listener so that updates the state of the radio button with keyboard interaction + var changeListener = this.addAccessibleInputListener( { + change: function() { + fire(); + } + } ); + + // a11y - Specify the default value for assistive technology. This attribute is needed in addition to + // the 'checked' property to mark this element as the default selection since 'checked' may be set before + // we are finished adding RadioButtons to the containing group, and the browser will remove the boolean + // 'checked' flag when new buttons are added. + if ( property.value === value ) { + this.setAccessibleAttribute( 'checked', 'checked' ); + } + + // a11y - when the property changes, make sure the correct radio button is marked as 'checked' so that this button + // receives focus on 'tab' + var accessibleCheckedListener = function( newValue ) { + self.accessibleChecked = newValue === value; + }; + property.link( accessibleCheckedListener ); + + // @private + this.disposeRadioButton = function() { + self.removeInputListener( buttonListener ); + self.removeAccessibleInputListener( changeListener ); + property.unlink( accessibleCheckedListener ); + property.unlink( syncWithModel ); + }; + + // a11y - allow consistent a11y naming for radio button types + this.setAccessibleAttribute( 'name', options.a11yNameAttribute ); + + this.mutate( options ); } sun.register( 'AquaRadioButton', AquaRadioButton ); - return inherit( RadioButton, AquaRadioButton, { + return inherit( Node, AquaRadioButton, { /** * Sets whether the circular part of the radio button will be displayed. @@ -84,9 +159,38 @@ define( function( require ) { setCircleButtonVisible: function( circleButtonVisible ) { this.deselectedCircleButton.visible = circleButtonVisible; this.selectedCircleButton.visible = circleButtonVisible; - } + }, + + // @public - Provide dispose() on the prototype for ease of subclassing. + dispose: function() { + this.disposeRadioButton(); + Node.prototype.dispose.call( this ); + }, + + /** + * Sets the enabled state + * @param {boolean} enabled + * @public + */ + setEnabled: function( enabled ) { + this._enabled = enabled; + this.opacity = enabled ? 1 : 0.3; + this.pickable = enabled; // NOTE: This is a side-effect. If you set pickable independently, it will be changed when you set enabled. + }, + set enabled( value ) { this.setEnabled( value ); }, + + /** + * Gets the enabled state + * @returns {boolean} + * @public + */ + getEnabled: function() { + return this._enabled; + }, + get enabled() { return this.getEnabled(); } + }, { - DEFAULT_RADIUS: 7 + DEFAULT_RADIUS: DEFAULT_RADIUS } ); } ); \ No newline at end of file diff --git a/js/AquaRadioButtonIO.js b/js/AquaRadioButtonIO.js index 3a7dcb1c..92253366 100644 --- a/js/AquaRadioButtonIO.js +++ b/js/AquaRadioButtonIO.js @@ -10,7 +10,7 @@ define( function( require ) { 'use strict'; // modules - var RadioButtonIO = require( 'SUN/RadioButtonIO' ); + var NodeIO = require( 'SCENERY/nodes/NodeIO' ); var sun = require( 'SUN/sun' ); // phet-io modules @@ -27,10 +27,10 @@ define( function( require ) { */ function AquaRadioButtonIO( aquaRadioButton, phetioID ) { assert && assertInstanceOf( aquaRadioButton, phet.sun.AquaRadioButton ); - RadioButtonIO.call( this, aquaRadioButton, phetioID ); + NodeIO.call( this, aquaRadioButton, phetioID ); } - phetioInherit( RadioButtonIO, 'AquaRadioButtonIO', AquaRadioButtonIO, { + phetioInherit( NodeIO, 'AquaRadioButtonIO', AquaRadioButtonIO, { setCircleButtonVisible: { returnType: VoidIO, parameterTypes: [ BooleanIO ], @@ -40,7 +40,17 @@ define( function( require ) { documentation: 'Sets whether the circular part of the radio button will be displayed.' } }, { - documentation: 'A radio button which looks like the Mac "Aqua" radio buttons' + documentation: 'A radio button which looks like the Mac "Aqua" radio buttons', + events: [ 'fired' ], + toStateObject: function( radioButton ) { + assert && assertInstanceOf( radioButton, phet.sun.AquaRadioButton ); + return NodeIO.toStateObject( radioButton ); + }, + fromStateObject: function( stateObject ) { return NodeIO.fromStateObject( stateObject ); }, + setValue: function( radioButton, fromStateObject ) { + assert && assertInstanceOf( radioButton, phet.sun.AquaRadioButton ); + NodeIO.setValue( radioButton, fromStateObject ); + } } ); sun.register( 'AquaRadioButtonIO', AquaRadioButtonIO ); diff --git a/js/RadioButton.js b/js/RadioButton.js index cd079382..e69de29b 100644 --- a/js/RadioButton.js +++ b/js/RadioButton.js @@ -1,145 +0,0 @@ -// Copyright 2013-2017, University of Colorado Boulder - -/** - * Base class for radio buttons. - * - * @author Chris Malley (PixelZoom, Inc.) - */ -define( function( require ) { - 'use strict'; - - // modules - var ButtonListener = require( 'SCENERY/input/ButtonListener' ); - var inherit = require( 'PHET_CORE/inherit' ); - var Node = require( 'SCENERY/nodes/Node' ); - var RadioButtonIO = require( 'SUN/RadioButtonIO' ); - var Rectangle = require( 'SCENERY/nodes/Rectangle' ); - var sun = require( 'SUN/sun' ); - var Tandem = require( 'TANDEM/Tandem' ); - - /** - * @param {Property} property - * @param {*} value the value that corresponds to this button, same type as property - * @param {Node} selectedNode node that will be displayed when the button is selected - * @param {Node} deselectedNode node that will be displayed when the button is deselected - * @param {Object} [options] - * @constructor - */ - function RadioButton( property, value, selectedNode, deselectedNode, options ) { - - options = _.extend( { - cursor: 'pointer', - tandem: Tandem.required, - phetioType: RadioButtonIO, - enabled: true, - - // a11y - tagName: 'input', - inputType: 'radio', - containerTagName: 'li', - labelTagName: 'label', - prependLabels: true, - a11yNameAttribute: 'radioButton' - }, options ); - - var self = this; - Node.call( this ); - - // @private - this._enabled = options.enabled; - - //Add an invisible node to make sure the layout for selected vs deselected is the same - var background = new Rectangle( selectedNode.bounds.union( deselectedNode.bounds ) ); - selectedNode.pickable = deselectedNode.pickable = false; // the background rectangle suffices - - this.addChild( background ); - this.addChild( selectedNode ); - this.addChild( deselectedNode ); - - // sync control with model - var syncWithModel = function( newValue ) { - selectedNode.visible = ( newValue === value ); - deselectedNode.visible = !selectedNode.visible; - }; - property.link( syncWithModel ); - - // set property value on fire - var fire = function() { - options.tandem.isSuppliedAndEnabled() && self.startEvent( 'user', 'fired', { - value: property.phetioType.elementType.toStateObject( value ) - } ); - property.set( value ); - options.tandem.isSuppliedAndEnabled() && self.endEvent(); - }; - var buttonListener = new ButtonListener( { fire: fire } ); - this.addInputListener( buttonListener ); - - // a11y - input listener so that updates the state of the radio button with keyboard interaction - var changeListener = this.addAccessibleInputListener( { - change: function() { - fire(); - } - } ); - - // a11y - Specify the default value for assistive technology. This attribute is needed in addition to - // the 'checked' property to mark this element as the default selection since 'checked' may be set before - // we are finished adding RadioButtons to the containing group, and the browser will remove the boolean - // 'checked' flag when new buttons are added. - if ( property.value === value ) { - this.setAccessibleAttribute( 'checked', 'checked' ); - } - - // a11y - when the property changes, make sure the correct radio button is marked as 'checked' so that this button - // receives focus on 'tab' - var accessibleCheckedListener = function( newValue ) { - self.accessibleChecked = newValue === value; - }; - property.link( accessibleCheckedListener ); - - // @private - this.disposeRadioButton = function() { - self.removeInputListener( buttonListener ); - self.removeAccessibleInputListener( changeListener ); - property.unlink( accessibleCheckedListener ); - property.unlink( syncWithModel ); - }; - - // a11y - allow consistent a11y naming for radio button types - this.setAccessibleAttribute( 'name', options.a11yNameAttribute ); - - this.mutate( options ); - } - - sun.register( 'RadioButton', RadioButton ); - - return inherit( Node, RadioButton, { - - // @public - Provide dispose() on the prototype for ease of subclassing. - dispose: function() { - this.disposeRadioButton(); - Node.prototype.dispose.call( this ); - }, - - /** - * Sets the enabled state - * @param {boolean} enabled - * @public - */ - setEnabled: function( enabled ) { - this._enabled = enabled; - this.opacity = enabled ? 1 : 0.3; - this.pickable = enabled; // NOTE: This is a side-effect. If you set pickable independently, it will be changed when you set enabled. - }, - set enabled( value ) { this.setEnabled( value ); }, - - /** - * Gets the enabled state - * @returns {boolean} - * @public - */ - getEnabled: function() { - return this._enabled; - }, - get enabled() { return this.getEnabled(); } - } ); -} ); diff --git a/js/RadioButtonIO.js b/js/RadioButtonIO.js deleted file mode 100644 index ce806eb1..00000000 --- a/js/RadioButtonIO.js +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017-2018, University of Colorado Boulder - -/** - * IO type for RadioButton - * - * @author Sam Reid (PhET Interactive Simulations) - * @author Andrew Adare (PhET Interactive Simulations) - */ -define( function( require ) { - 'use strict'; - - // modules - var NodeIO = require( 'SCENERY/nodes/NodeIO' ); - var sun = require( 'SUN/sun' ); - - // phet-io modules - var assertInstanceOf = require( 'ifphetio!PHET_IO/assertInstanceOf' ); - var phetioInherit = require( 'ifphetio!PHET_IO/phetioInherit' ); - - /** - * @param {RadioButton} radioButton - * @param {String} phetioID - * @constructor - */ - function RadioButtonIO( radioButton, phetioID ) { - assert && assertInstanceOf( radioButton, phet.sun.RadioButton ); - NodeIO.call( this, radioButton, phetioID ); - } - - phetioInherit( NodeIO, 'RadioButtonIO', RadioButtonIO, {}, { - documentation: 'A traditional radio button', - events: [ 'fired' ], - toStateObject: function( radioButton ) { - assert && assertInstanceOf( radioButton, phet.sun.RadioButton ); - return NodeIO.toStateObject( radioButton ); - }, - fromStateObject: function( stateObject ) { return NodeIO.fromStateObject( stateObject ); }, - setValue: function( radioButton, fromStateObject ) { - assert && assertInstanceOf( radioButton, phet.sun.RadioButton ); - NodeIO.setValue( radioButton, fromStateObject ); - } - } ); - - sun.register( 'RadioButtonIO', RadioButtonIO ); - - return RadioButtonIO; -} ); - diff --git a/sun_en.html b/sun_en.html index 9419284f..b17382f9 100644 --- a/sun_en.html +++ b/sun_en.html @@ -17,7 +17,10 @@