-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Leveling Out Screen Sprint #52
Comments
I thought it would be good to get a list together of the remaining components needed to make the Leveling Out Screen visually feature complete. If there is an issue already open that ties into that it has been linked.
After going through the design doc I believe this is all that is left for visual feature complete. |
@marlitas and I were just collaborating on this and got to a point where the dotted lines and such are working, but we're not ready to commit it, so here is a patch: Subject: [PATCH] display mode improvements
---
Index: js/leveling-out/model/CandyBar.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/leveling-out/model/CandyBar.ts b/js/leveling-out/model/CandyBar.ts
--- a/js/leveling-out/model/CandyBar.ts (revision f766d86618309b0a86a354975dc9222f89a8c15f)
+++ b/js/leveling-out/model/CandyBar.ts (date 1706038132067)
@@ -9,7 +9,6 @@
*
*/
-import BooleanProperty from '../../../../axon/js/BooleanProperty.js';
import Property from '../../../../axon/js/Property.js';
import Vector2 from '../../../../dot/js/Vector2.js';
import IOType from '../../../../tandem/js/types/IOType.js';
@@ -18,36 +17,73 @@
import NotepadPlate from './NotepadPlate.js';
import { PhetioObjectOptions } from '../../../../tandem/js/PhetioObject.js';
import PickRequired from '../../../../phet-core/js/types/PickRequired.js';
+import Vector2Property from '../../../../dot/js/Vector2Property.js';
+import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js';
+import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
+import EnumerationValue from '../../../../phet-core/js/EnumerationValue.js';
+import Enumeration from '../../../../phet-core/js/Enumeration.js';
+import EnumerationProperty from '../../../../axon/js/EnumerationProperty.js';
+
+type StateType = 'plate' | 'dragging' | 'animating';
+
+// This enumeration is used to track how a CandyBar instance should be displayed in the view, including whether it
+// should be shown at all.
+class DisplayMode extends EnumerationValue {
+ public static readonly SOLID = new DisplayMode();
+ public static readonly DASHED = new DisplayMode();
+ public static readonly NONE = new DisplayMode();
+
+ // Gets a list of keys, values and mapping between them. For use in EnumerationProperty and PhET-iO
+ public static readonly enumeration = new Enumeration( DisplayMode, {
+ phetioDocumentation: 'Describes the way in which a CandyBar instance is to be displayed.'
+ } );
+}
type CandyBarOptions = {
- isActive: boolean;
+ displayMode: DisplayMode;
notepadPlate: NotepadPlate;
position: Vector2;
} & PickRequired<PhetioObjectOptions, 'tandem'>;
-type StateType = 'plate' | 'dragging' | 'animating';
-
// Total number of candy bars allocated, for debugging.
let count = 0;
export default class CandyBar {
-
- public readonly isActiveProperty: Property<boolean>;
public readonly parentPlateProperty: Property<NotepadPlate>;
public readonly positionProperty: Property<Vector2>;
public readonly stateProperty: Property<StateType>;
+ // If the displayModeProperty value is set to 'SOLID' this candy bar is actively participating in the model and
+ // contributing towards calculations including the mean.
+ public readonly displayModeProperty: EnumerationProperty<DisplayMode>;
+
+ // A CandyBar instance is only active if its displayModeProperty in the SOLID mode. It is essentially a "convenience
+ // Property".
+ public readonly isActiveProperty: TReadOnlyProperty<boolean>;
+
+ // This property is true when the displayModeProperty is either SOLID or DASHED, and it indicates that this should be
+ // portrayed in the view in some fashion.
+ public readonly visibleProperty: TReadOnlyProperty<boolean>;
+
// For debugging
public readonly index = count++;
public constructor( providedOptions: CandyBarOptions ) {
- this.isActiveProperty = new BooleanProperty( providedOptions.isActive, {
+ // The display property determines whether the candy bar is solid, dashed, or not visible.
+ this.displayModeProperty = new EnumerationProperty( providedOptions.displayMode, {
// phet-io
- tandem: providedOptions.tandem.createTandem( 'isActiveProperty' ),
- phetioReadOnly: true,
- phetioState: false
+ tandem: providedOptions.tandem.createTandem( 'displayModeProperty' ),
+ phetioReadOnly: true
+ } );
+
+ this.isActiveProperty = DerivedProperty.valueEqualsConstant( this.displayModeProperty, DisplayMode.SOLID );
+
+ // A candy bar may be visible if it is not actively participating in the model if it is displaying a dashed
+ // representation of a candy bar
+ this.visibleProperty = new DerivedProperty( [ this.displayModeProperty ], displayMode => {
+ return displayMode !== DisplayMode.NONE;
} );
this.parentPlateProperty = new Property( providedOptions.notepadPlate, {
@@ -59,7 +95,12 @@
} );
// REVIEW: These may need phetioState: true
- this.positionProperty = new Property( providedOptions.position );
+ this.positionProperty = new Vector2Property( providedOptions.position, {
+
+ // phet-io
+ tandem: providedOptions.tandem.createTandem( 'positionProperty' ),
+ phetioReadOnly: true
+ } );
this.stateProperty = new Property<StateType>( 'plate' );
}
@@ -67,8 +108,9 @@
this.positionProperty.reset();
this.stateProperty.reset();
this.parentPlateProperty.reset();
- this.isActiveProperty.reset();
+ this.displayModeProperty.reset();
}
}
+export { DisplayMode };
meanShareAndBalance.register( 'CandyBar', CandyBar );
\ No newline at end of file
Index: js/leveling-out/model/LevelingOutModel.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/leveling-out/model/LevelingOutModel.ts b/js/leveling-out/model/LevelingOutModel.ts
--- a/js/leveling-out/model/LevelingOutModel.ts (revision f766d86618309b0a86a354975dc9222f89a8c15f)
+++ b/js/leveling-out/model/LevelingOutModel.ts (date 1706038195576)
@@ -22,13 +22,13 @@
import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js';
import NumberIO from '../../../../tandem/js/types/NumberIO.js';
-import CandyBar from './CandyBar.js';
+import CandyBar, { DisplayMode } from './CandyBar.js';
import Property from '../../../../axon/js/Property.js';
type SelfOptions = EmptySelfOptions;
type LevelingOutModelOptions = SelfOptions & PickRequired<MeanShareAndBalanceModelOptions, 'tandem'>;
-const MAX_PEOPLE = 7;
+const MAX_PLATES = 7;
export default class LevelingOutModel extends MeanShareAndBalanceModel {
@@ -37,6 +37,7 @@
public readonly notepadPlates: Array<NotepadPlate>;
public readonly tablePlates: Array<TablePlate>;
+ public readonly platesMap = new Map<NotepadPlate, TablePlate>();
public readonly candyBars: Array<CandyBar>;
public readonly meanProperty: TReadOnlyProperty<number>;
@@ -91,9 +92,9 @@
// In Mean Share and Balance, we decided arrays start counting at 1
let totalCandyBarCount = 1;
- // Statically allocate plates, people, and candyBars. Whether they participate in the model is controlled by the
- // isActiveProperty on each one.
- for ( let tablePlateIndex = 0; tablePlateIndex < MAX_PEOPLE; tablePlateIndex++ ) {
+ // Statically allocate plates and candyBars. Whether they participate in the model is controlled by the
+ // isActiveProperty on each.
+ for ( let tablePlateIndex = 0; tablePlateIndex < MAX_PLATES; tablePlateIndex++ ) {
const x = tablePlateIndex * MeanShareAndBalanceConstants.TABLE_PLATE_WIDTH;
const notepadPlate = new NotepadPlate( {
isActive: tablePlateIndex < this.numberOfPeopleProperty.value,
@@ -121,10 +122,12 @@
const x = notepadPlate.position.x;
const y = notepadPlate.position.y - ( ( MeanShareAndBalanceConstants.CANDY_BAR_HEIGHT + 2 ) * ( candyBarIndex + 1 ) );
- const isActive = notepadPlate.isActiveProperty.value && candyBarIndex < tablePlate.candyBarNumberProperty.value;
+ const displayMode = notepadPlate.isActiveProperty.value && candyBarIndex < tablePlate.candyBarNumberProperty.value ?
+ DisplayMode.SOLID :
+ DisplayMode.NONE;
const candyBar = new CandyBar( {
- isActive: isActive,
+ displayMode: displayMode,
notepadPlate: notepadPlate,
position: new Vector2( x, y ),
@@ -151,7 +154,9 @@
}
}
candyBars.forEach( ( candyBar, i ) => {
- candyBar.isActiveProperty.value = isActive && i < tablePlate.candyBarNumberProperty.value;
+ candyBar.displayModeProperty.value = isActive && i < tablePlate.candyBarNumberProperty.value ?
+ DisplayMode.SOLID :
+ DisplayMode.NONE;
this.reorganizeCandyBars( notepadPlate );
} );
} );
@@ -165,8 +170,12 @@
else if ( candyBarNumber < oldCandyBarNumber ) {
this.tablePlateCandyBarAmountDecrease( notepadPlate, oldCandyBarNumber - candyBarNumber );
}
+ this.setDashedCandyBars( tablePlate, notepadPlate );
}
} );
+
+ // Connect tablePlates and notepadPlates in map.
+ this.platesMap.set( notepadPlate, tablePlate );
totalCandyBarsPropertyDependencies.push( tablePlate.candyBarNumberProperty );
totalCandyBarsPropertyDependencies.push( tablePlate.isActiveProperty );
@@ -175,7 +184,7 @@
// Tracks the total number of candyBars based on the "ground truth" tablePlate numbers.
// Must be deriveAny because .map() does not preserve .length()
this.totalCandyBarsProperty = DerivedProperty.deriveAny( totalCandyBarsPropertyDependencies, () => {
- const candyBarAmounts = this.getActivePeople().map( tablePlate => tablePlate.candyBarNumberProperty.value );
+ const candyBarAmounts = this.getActiveTablePlates().map( tablePlate => tablePlate.candyBarNumberProperty.value );
return _.sum( candyBarAmounts );
}, {
tandem: options.tandem.createTandem( 'totalCandyBarsProperty' ),
@@ -199,11 +208,30 @@
} );
}
- public getActivePeople(): Array<TablePlate> {
+ public setDashedCandyBars( tablePlate: TablePlate, notepadPlate: NotepadPlate ): void {
+ const notepadCandyBars = this.getActiveCandyBarsOnPlate( notepadPlate );
+ const numberOfCandyBarsOnNotepadPlate = notepadCandyBars.length;
+ const numberOfCandyBarsOnTablePlate = tablePlate.candyBarNumberProperty.value;
+ const candyBars = this.getCandyBarsOnPlate( notepadPlate );
+ for ( let i = 0; i < MeanShareAndBalanceConstants.MAX_NUMBER_OF_CANDY_BARS_PER_PERSON; i++ ) {
+ if ( numberOfCandyBarsOnNotepadPlate < numberOfCandyBarsOnTablePlate ) {
+ candyBars[ i ].displayModeProperty.value = i < numberOfCandyBarsOnNotepadPlate ? DisplayMode.SOLID :
+ i < numberOfCandyBarsOnTablePlate ? DisplayMode.DASHED :
+ DisplayMode.NONE;
+ }
+ else {
+ candyBars[ i ].displayModeProperty.value = i < numberOfCandyBarsOnNotepadPlate ?
+ DisplayMode.SOLID :
+ DisplayMode.NONE;
+ }
+ }
+ }
+
+ public getActiveTablePlates(): Array<TablePlate> {
return this.tablePlates.filter( tablePlate => tablePlate.isActiveProperty.value );
}
- public getActivePlates(): Array<NotepadPlate> {
+ public getActiveNotepadPlates(): Array<NotepadPlate> {
return this.notepadPlates.filter( plate => plate.isActiveProperty.value );
}
@@ -212,7 +240,8 @@
}
public getCandyBarsOnPlate( plate: NotepadPlate ): Array<CandyBar> {
- return this.candyBars.filter( candyBar => candyBar.parentPlateProperty.value === plate );
+ return this.candyBars.filter( candyBar => candyBar.parentPlateProperty.value === plate )
+ .sort( ( a, b ) => b.positionProperty.value.y - a.positionProperty.value.y );
}
public getInactiveCandyBarsOnPlate( plate: NotepadPlate ): Array<CandyBar> {
@@ -253,13 +282,15 @@
* When candyBars are added to a notepadPlate they may appear in random positions or be overlapping. Re-stack them.
*/
public reorganizeCandyBars( plate: NotepadPlate ): void {
- const plateStateCandyBars = this.getActivePlateStateCandyBars( plate );
+ const plateStateCandyBars = this.getCandyBarsOnPlate( plate );
plateStateCandyBars.forEach( ( candyBar, i ) => {
const Y_MARGIN = 2; // Distance between adjacent candyBars, and notepadPlate.
const newPosition = new Vector2( plate.position.x, plate.position.y - ( ( MeanShareAndBalanceConstants.CANDY_BAR_HEIGHT + Y_MARGIN ) * ( i + 1 ) ) );
candyBar.positionProperty.set( newPosition );
} );
+
+ this.setDashedCandyBars( this.platesMap.get( plate )!, plate );
}
/**
@@ -269,7 +300,7 @@
private shareExtraCandyBars( numberOfExtraCandyBars: number ): void {
for ( let i = 0; i < numberOfExtraCandyBars; i++ ) {
const minPlate = this.getPlateWithLeastCandyBars();
- this.getBottomInactiveCandyBarOnPlate( minPlate ).isActiveProperty.set( true );
+ this.getBottomInactiveCandyBarOnPlate( minPlate ).displayModeProperty.set( DisplayMode.SOLID );
this.reorganizeCandyBars( minPlate );
}
}
@@ -281,14 +312,14 @@
private borrowMissingCandyBars( numberOfMissingCandyBars: number ): void {
for ( let i = 0; i < numberOfMissingCandyBars; i++ ) {
const maxPlate = this.getPlateWithMostActiveCandyBars();
- this.getTopActiveCandyBarOnPlate( maxPlate ).isActiveProperty.set( false );
+ this.getTopActiveCandyBarOnPlate( maxPlate ).displayModeProperty.set( DisplayMode.NONE );
this.reorganizeCandyBars( maxPlate );
}
}
/**
- * When an active tablePlate adds candy bar to their notepadPlate and the paper notepadPlate has no more space on it,
- * a piece of candy bar will be added onto the paper notepadPlate with the least candy bar.
+ * When an active tablePlate adds a candy bar to their notepadPlate and the notepadPlate has no more space on it,
+ * a candy bar will be added onto the notepadPlate with the least amount of candy bars.
*/
private tablePlateCandyBarAmountIncrease( plate: NotepadPlate, numberOfCandyBarsAdded: number ): void {
for ( let i = 0; i < numberOfCandyBarsAdded; i++ ) {
@@ -299,11 +330,11 @@
minPlate !== plate,
`minPlate ${minPlate.linePlacement} should not be the same as affected plate: ${plate.linePlacement}`
);
- this.getBottomInactiveCandyBarOnPlate( minPlate ).isActiveProperty.set( true );
+ this.getBottomInactiveCandyBarOnPlate( minPlate ).displayModeProperty.set( DisplayMode.SOLID );
this.reorganizeCandyBars( minPlate );
}
else {
- this.getBottomInactiveCandyBarOnPlate( plate ).isActiveProperty.set( true );
+ this.getBottomInactiveCandyBarOnPlate( plate ).displayModeProperty.set( DisplayMode.SOLID );
}
this.reorganizeCandyBars( plate );
}
@@ -318,18 +349,18 @@
const numberOfCandyBarsOnPlate = this.getActivePlateStateCandyBars( plate ).length;
if ( numberOfCandyBarsOnPlate === 0 ) {
const maxPlate = this.getPlateWithMostActiveCandyBars();
- this.getTopActiveCandyBarOnPlate( maxPlate ).isActiveProperty.set( false );
+ this.getTopActiveCandyBarOnPlate( maxPlate ).displayModeProperty.set( DisplayMode.NONE );
this.reorganizeCandyBars( maxPlate );
}
else {
- this.getTopActiveCandyBarOnPlate( plate ).isActiveProperty.set( false );
+ this.getTopActiveCandyBarOnPlate( plate ).displayModeProperty.set( DisplayMode.NONE );
}
this.reorganizeCandyBars( plate );
}
}
public getPlateWithMostActiveCandyBars(): NotepadPlate {
- const maxPlate = _.maxBy( this.getActivePlates(), ( plate => this.getActiveCandyBarsOnPlate( plate ).length ) );
+ const maxPlate = _.maxBy( this.getActiveNotepadPlates(), ( plate => this.getActiveCandyBarsOnPlate( plate ).length ) );
// _.maxBy can return undefined if all the elements in the array are null, undefined, or NAN.
// candyBarsNumberProperty will always be a number.
@@ -337,7 +368,7 @@
}
public getPlateWithLeastCandyBars(): NotepadPlate {
- const minPlate = _.minBy( this.getActivePlates(), ( plate => this.getActiveCandyBarsOnPlate( plate ).length ) );
+ const minPlate = _.minBy( this.getActiveNotepadPlates(), ( plate => this.getActiveCandyBarsOnPlate( plate ).length ) );
// _.minBy can return undefined if all the elements in the array are null, undefined, or NAN.
// candyBarsNumberProperty will always be a number.
@@ -364,7 +395,11 @@
this.tablePlates.forEach( ( tablePlate, index ) => {
this.getCandyBarsOnPlate( this.notepadPlates[ index ] ).forEach( ( candyBar, i ) => {
- candyBar.isActiveProperty.value = i < tablePlate.candyBarNumberProperty.value;
+
+ // There will be no 'dashed' candyBars when syncing data.
+ candyBar.displayModeProperty.value = tablePlate.isActiveProperty.value && i < tablePlate.candyBarNumberProperty.value ?
+ DisplayMode.SOLID :
+ DisplayMode.NONE;
} );
} );
}
Index: js/leveling-out/view/LevelingOutScreenView.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/leveling-out/view/LevelingOutScreenView.ts b/js/leveling-out/view/LevelingOutScreenView.ts
--- a/js/leveling-out/view/LevelingOutScreenView.ts (revision f766d86618309b0a86a354975dc9222f89a8c15f)
+++ b/js/leveling-out/view/LevelingOutScreenView.ts (date 1706036515893)
@@ -77,7 +77,7 @@
// function for what candy bars should do at the end of their drag
const candyBarDropped = ( candyBar: NotepadCandyBarNode ) => {
- const platesWithSpace = model.getPlatesWithSpace( model.getActivePlates() );
+ const platesWithSpace = model.getPlatesWithSpace( model.getActiveNotepadPlates() );
// find the notepadPlate closest to where the candy bar was dropped.
const closestPlate = _.minBy( platesWithSpace, plate => Math.abs( plate.position.x - candyBar.candyBar.positionProperty.value.x ) );
@@ -100,6 +100,17 @@
}
candyBar.candyBar.parentPlateProperty.set( closestPlate! );
+
+ // // Update 'dashed' candy bars for the plate the candy bar was move to.
+ // const tablePlate = model.platesMap.get( closestPlate! );
+ // model.setDashedCandyBars( tablePlate!, closestPlate! );
+ //
+ // // Update 'dashed' candy bars for the plate the candy bar was moved from.
+ // const oldTablePlate = model.platesMap.get( currentParent );
+ // model.setDashedCandyBars( oldTablePlate!, currentParent );
+
+ model.reorganizeCandyBars( closestPlate! );
+ model.reorganizeCandyBars( currentParent );
};
@@ -133,8 +144,7 @@
const candyBarsParentTandem = options.tandem.createTandem( 'notepadCandyBars' );
const notepadCandyBars = model.candyBars.map( ( candyBar, i ) =>
new NotepadCandyBarNode( model, candyBar, notepadBoundsProperty, candyBarDropped, {
- tandem: candyBarsParentTandem.createTandem( `notepadCandyBar${i + 1}` ),
- visibleProperty: candyBar.isActiveProperty
+ tandem: candyBarsParentTandem.createTandem( `notepadCandyBar${i + 1}` )
} ) );
// This contains all the candy bars from the top (paper) representation and the bottom (table) representation.
Index: js/leveling-out/view/NotepadCandyBarNode.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/leveling-out/view/NotepadCandyBarNode.ts b/js/leveling-out/view/NotepadCandyBarNode.ts
--- a/js/leveling-out/view/NotepadCandyBarNode.ts (revision f766d86618309b0a86a354975dc9222f89a8c15f)
+++ b/js/leveling-out/view/NotepadCandyBarNode.ts (date 1706038395708)
@@ -19,10 +19,10 @@
import MeanShareAndBalanceColors from '../../common/MeanShareAndBalanceColors.js';
import StrictOmit from '../../../../phet-core/js/types/StrictOmit.js';
import WithRequired from '../../../../phet-core/js/types/WithRequired.js';
-import CandyBar from '../model/CandyBar.js';
+import CandyBar, { DisplayMode } from '../model/CandyBar.js';
type SelfOptions = EmptySelfOptions;
-type NotepadCandyBarNodeOptions = SelfOptions & StrictOmit<WithRequired<NodeOptions, 'tandem'>, 'children'>;
+type NotepadCandyBarNodeOptions = SelfOptions & StrictOmit<WithRequired<NodeOptions, 'tandem'>, 'children' | 'visibleProperty'>;
export default class NotepadCandyBarNode extends InteractiveHighlighting( Node ) {
@@ -40,6 +40,12 @@
stroke: 'black'
} );
+ candyBar.displayModeProperty.link( displayMode => {
+ candyBarRectangle.fill = displayMode === DisplayMode.SOLID ? MeanShareAndBalanceColors.candyBarColorProperty.value : null;
+ candyBarRectangle.stroke = displayMode === DisplayMode.SOLID ? 'black' : MeanShareAndBalanceColors.candyBarColorProperty.value;
+ candyBarRectangle.lineDash = displayMode === DisplayMode.DASHED ? [ 2, 1.5 ] : [];
+ } );
+
const children: Array<Node> = [ candyBarRectangle ];
// In ?dev mode, show the index of the candy bar to help understand how things are organized and how they
@@ -50,7 +56,9 @@
const options = optionize<NotepadCandyBarNodeOptions, SelfOptions, NodeOptions>()( {
children: children,
- cursor: 'pointer'
+ cursor: 'pointer',
+ visibleProperty: candyBar.visibleProperty,
+ pickableProperty: candyBar.isActiveProperty // We cannot drag a candyBar if it is not active
}, providedOptions );
super( options ); |
Discussed with @amanda-phet the candy bars on the notepad should have a drop shadow. Coming up! |
The drop shadow has been added. |
I just went through the spec and I think we have now implemented all of the visual functionality for the Leveling Out screen. I'd close this issue, but I noticed that as of this moment the first sentence in the "Screen 2: Leveling Out" section of the spec says, "The Leveling Out screen starts with two people in the play area with default values of 3 and 1." @amanda-phet - can you confirm that this sentence is out of date and the current behavior where it starts with a single person with one candy bar is the desired initial state? If so, this issue can be closed. |
From standup @amanda-phet confirmed that the sentence "The Leveling Out screen starts with two people in the play area with default values of 3 and 1." is correct and up to date. The above commit implements that. I believe this issue can be closed now. |
This issue will track the initial sprint for the Leveling Out Screen.
The text was updated successfully, but these errors were encountered: