Skip to content

Commit

Permalink
rework color mapping to a single listener, phetsims/build-a-nucleus#85
Browse files Browse the repository at this point in the history
  • Loading branch information
zepumph committed Aug 2, 2023
1 parent 8885e07 commit d8d8a05
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 45 deletions.
8 changes: 6 additions & 2 deletions js/view/InteractiveSchematicAtom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import ParticleView from './ParticleView.js';
import TReadOnlyProperty from '../../../axon/js/TReadOnlyProperty.js';
import ModelViewTransform2 from '../../../phetcommon/js/view/ModelViewTransform2.js';
import optionize from '../../../phet-core/js/optionize.js';
import BooleanProperty from '../../../axon/js/BooleanProperty.js';

// constants
const NUM_NUCLEON_LAYERS = 5; // This is based on max number of particles, may need adjustment if that changes.
type SelfOptions = {
highContrastProperty?: null | TReadOnlyProperty<boolean>;
highContrastProperty?: TReadOnlyProperty<boolean>;
};

type InteractiveSchematicAtomOptions = SelfOptions & NodeOptions;
Expand All @@ -29,10 +30,12 @@ class InteractiveSchematicAtom extends Node {
private readonly disposeInteractiveSchematicAtom: VoidFunction;

public constructor( model: BuildAnAtomModel, modelViewTransform: ModelViewTransform2, providedOptions?: InteractiveSchematicAtomOptions ) {
const ownsHighContrastProperty = providedOptions && !providedOptions.highContrastProperty;

const options = optionize<InteractiveSchematicAtomOptions, SelfOptions, NodeOptions>()( {

// {Property.<boolean>|null} - property that can be used to turn on high-contrast particles
highContrastProperty: null,
highContrastProperty: new BooleanProperty( false ),

tandem: Tandem.REQUIRED
}, providedOptions );
Expand Down Expand Up @@ -156,6 +159,7 @@ class InteractiveSchematicAtom extends Node {
atomNode.dispose();
model.particleAtom.electrons.lengthProperty.unlink( updateElectronVisibility );
model.electronShellDepictionProperty.unlink( updateElectronVisibility );
ownsHighContrastProperty && options.highContrastProperty.dispose();
};

this.mutate( options );
Expand Down
59 changes: 20 additions & 39 deletions js/view/ParticleNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

import Utils from '../../../dot/js/Utils.js';
import PhetColorScheme from '../../../scenery-phet/js/PhetColorScheme.js';
import { Circle, CircleOptions, Color, RadialGradient } from '../../../scenery/js/imports.js';
import { Circle, CircleOptions, Color, ColorProperty, RadialGradient } from '../../../scenery/js/imports.js';
import shred from '../shred.js';
import TReadOnlyProperty from '../../../axon/js/TReadOnlyProperty.js';
import optionize from '../../../phet-core/js/optionize.js';
import { ParticleTypeString } from '../model/Particle.js';
import BooleanProperty from '../../../axon/js/BooleanProperty.js';
import Multilink from '../../../axon/js/Multilink.js';

// constants
const DEFAULT_LINE_WIDTH = 0.5;
Expand Down Expand Up @@ -39,7 +41,7 @@ const NUCLEON_COLOR_GRADIENT = [
type SelfOptions = {

// {BooleanProperty|null} - if provided, this is used to set the particle node into and out of high contrast mode
highContrastProperty?: TReadOnlyProperty<boolean> | null;
highContrastProperty?: TReadOnlyProperty<boolean>;
typeProperty?: TReadOnlyProperty<ParticleTypeString> | null;
colorGradientIndexNumberProperty?: TReadOnlyProperty<number> | null;
};
Expand All @@ -50,11 +52,12 @@ class ParticleNode extends Circle {

public constructor( particleType: ParticleTypeString, radius: number, providedOptions?: ParticleNodeOptions ) {

const ownsHighContrastProperty = providedOptions && !providedOptions.highContrastProperty;
const options = optionize<ParticleNodeOptions, SelfOptions, CircleOptions>()( {

cursor: 'pointer',

highContrastProperty: null,
highContrastProperty: new BooleanProperty( false ),

typeProperty: null,

Expand All @@ -69,35 +72,26 @@ class ParticleNode extends Circle {
const baseColor = PARTICLE_COLORS[ particleType ];
assert && assert( baseColor, `Unrecognized particle type: ${particleType}` );

// Create the fill that will be used to make the particles look 3D when not in high-contrast mode.
const gradientFill = new RadialGradient( -radius * 0.4, -radius * 0.4, 0, -radius * 0.4, -radius * 0.4, radius * 1.6 )
.addColorStop( 0, 'white' )
.addColorStop( 1, baseColor );

// Set the options for the default look.
const nonHighContrastStroke = baseColor.colorUtilsDarker( 0.33 );
options.fill = gradientFill;
options.stroke = nonHighContrastStroke;
options.lineWidth = DEFAULT_LINE_WIDTH;

const colorProperty = new ColorProperty( baseColor );
super( radius, options );

// function to change the color of a particle
const changeParticleColor = ( newColor: Color ) => {
const colorMultilink = Multilink.multilink( [
colorProperty,
options.highContrastProperty
], ( color, highContrast ) => {

// Create the fill that will be used to make the particles look 3D when not in high-contrast mode.
const gradientFill = new RadialGradient( -radius * 0.4, -radius * 0.4, 0, -radius * 0.4, -radius * 0.4, radius * 1.6 )
.addColorStop( 0, 'white' )
.addColorStop( 1, newColor );
.addColorStop( 1, color );

// Set the options for the default look.
const nonHighContrastStroke = newColor.colorUtilsDarker( 0.33 );
const nonHighContrastStroke = color.colorUtilsDarker( 0.33 );
this.fill = highContrast ? colorProperty.value : gradientFill;
this.stroke = highContrast ? colorProperty.value.colorUtilsDarker( 0.5 ) : nonHighContrastStroke;
this.lineWidth = highContrast ? HIGH_CONTRAST_LINE_WIDTH : DEFAULT_LINE_WIDTH;
} );

this.mutate( {
fill: gradientFill,
stroke: nonHighContrastStroke
} );
};

// change the color of the particle
options.colorGradientIndexNumberProperty && options.colorGradientIndexNumberProperty.link( indexValue => {
Expand All @@ -114,27 +108,14 @@ class ParticleNode extends Circle {

// the value is close to an integer
if ( Math.floor( indexValue * 10 ) / 10 % 1 === 0 ) {
changeParticleColor( nucleonChangeColorChange[ Utils.toFixed( indexValue, 0 ) as unknown as number ] );
colorProperty.value = nucleonChangeColorChange[ Utils.toFixed( indexValue, 0 ) as unknown as number ];
}
}
} );

// If a highContrastProperty is provided, update the particle appearance based on its value.
// Set up the fill and the strokes based on whether the highContrastProperty option is provided.
let highContrastListener: ( ( highContrast: boolean ) => void ) | null = null;
if ( options.highContrastProperty ) {
highContrastListener = highContrast => {
this.fill = highContrast ? baseColor : gradientFill;
this.stroke = highContrast ? baseColor.colorUtilsDarker( 0.5 ) : nonHighContrastStroke;
this.lineWidth = highContrast ? HIGH_CONTRAST_LINE_WIDTH : DEFAULT_LINE_WIDTH;
};
options.highContrastProperty.link( highContrastListener );
}

this.disposeParticleNode = () => {
if ( highContrastListener ) {
options.highContrastProperty!.unlink( highContrastListener );
}
colorMultilink.dispose();
ownsHighContrastProperty && options.highContrastProperty.dispose();
};
}

Expand Down
12 changes: 8 additions & 4 deletions js/view/ParticleView.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2014-2023, University of Colorado Boulder

/**
* Type that represents a sub-atomic particle in the view.
* Type that represents a subatomic particle in the view.
*/

import Property from '../../../axon/js/Property.js';
Expand All @@ -16,10 +16,11 @@ import ModelViewTransform2 from '../../../phetcommon/js/view/ModelViewTransform2
import optionize from '../../../phet-core/js/optionize.js';
import Vector2 from '../../../dot/js/Vector2.js';
import TReadOnlyProperty from '../../../axon/js/TReadOnlyProperty.js';
import BooleanProperty from '../../../axon/js/BooleanProperty.js';

type SelfOptions = {
dragBounds?: Bounds2;
highContrastProperty?: TReadOnlyProperty<boolean> | null;
highContrastProperty?: TReadOnlyProperty<boolean>;
};
type ParticleViewOptions = SelfOptions & NodeOptions;

Expand All @@ -30,12 +31,14 @@ class ParticleView extends Node {

public constructor( particle: Particle, modelViewTransform: ModelViewTransform2, providedOptions?: ParticleViewOptions ) {

const ownsHighContrastProperty = providedOptions && !providedOptions.highContrastProperty;

const options = optionize<ParticleViewOptions, SelfOptions, NodeOptions>()( {
dragBounds: Bounds2.EVERYTHING,
tandem: Tandem.REQUIRED,

// {BooleanProperty|null} - if provided, this is used to set the particle node into and out of high contrast mode
highContrastProperty: null
highContrastProperty: new BooleanProperty( false )
}, providedOptions );

super();
Expand Down Expand Up @@ -98,6 +101,7 @@ class ParticleView extends Node {
this.mutate( options );

this.disposeParticleView = function() {
ownsHighContrastProperty && options.highContrastProperty.dispose();
particle.positionProperty.unlink( updateParticlePosition );
particleNode.dispose();
this.dragListener.dispose();
Expand All @@ -120,7 +124,7 @@ class ParticleView extends Node {
* Creates the proper view for a particle.
*/
function createParticleNode( particle: Particle, modelViewTransform: ModelViewTransform2,
highContrastProperty: TReadOnlyProperty<boolean> | null, tandem: Tandem ): Node {
highContrastProperty: TReadOnlyProperty<boolean>, tandem: Tandem ): Node {
let particleNode;
if ( particle.type === 'Isotope' ) {
particleNode = new IsotopeNode(
Expand Down

0 comments on commit d8d8a05

Please sign in to comment.