Skip to content
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

Where should ProfileColorProperties be defined? #1257

Closed
samreid opened this issue Jul 27, 2021 · 14 comments
Closed

Where should ProfileColorProperties be defined? #1257

samreid opened this issue Jul 27, 2021 · 14 comments

Comments

@samreid
Copy link
Member

samreid commented Jul 27, 2021

In phetsims/scenery-phet#515 (comment) @jessegreenberg said:

@samreid also proposed a different solution, where colors can live at their usage sites.

I later learned this cannot be done on the instance level, and I commented in phetsims/scenery-phet#515 (comment)

Many colors were moved to their usage sites. Colors used in many different places were moved to GravityAndOrbitsConstants.js

There is a problem with this. For instance, assume there are 2 screens, Screen A and Screen B. If both screens are supposed to always have the same background color, they should use the same ColorProperty. However, if the ColorProperty is defined in the Screen constructor, they will have different ColorProperties. Defining it locally in the file as a class-static would work, but not a good match for phet-io, which does not track static tandems. So many of the ColorProperty instances we have seen should remain factored out, and cannot be moved to individual files. On the other hand, if we want the screens backgrounds to vary independently, then it is OK to define the ColorProperty locally. Maybe as a first pass, we will just leave most or all things in the ColorProfile files.

Defining it locally in the file as a class-static would work, but not a good match for phet-io, which does not track static tandems.

But we could come up with a convention for this, if it is otherwise the best plan.

In phetsims/scenery-phet#515 (comment) @zepumph commented:

Isn't the goal/dream of this issue that modern sims would have have this culminating file of colors, and that instead they would be integrated into the usage sites, or put into CONSTANTS files if needed?

In the review, @pixelzoom commented phetsims/scenery-phet#515 (comment)

Perhaps a PSA to developers about the pitfalls of defining ProfileColorProperty instances where they are used? @samreid was originally advocating for this, and in phetsims/scenery-phet#515 (comment) @zepumph seems to think that's still desirable. Grouping ProfileColorProperty instances in one place has a lot of advantages, and that's the pattern that I'd like to use. It might be easier for designers if all devs used the same pattern.

Once we have an easy-to-use global color tandem, it may make it easy to define static constants in each file where appropriate. Self-assigning to investigate that option.

@pixelzoom
Copy link
Contributor

pixelzoom commented Jul 27, 2021

Once we have an easy-to-use global color tandem, it may make it easy to define static constants in each file where appropriate. Self-assigning to investigate that option.

I'm still a fan of putting them in one .js file. That makes it easy for non-developers to find the relevant code, if needed. One could argue that it makes it a tad easier for developers too. My sims will have a *Colors.js file.

@samreid
Copy link
Member Author

samreid commented Jul 28, 2021

To understand this better, I moved a color that is only used once from GasPropertiesColors.js to its usage site. I also added a Tandem, so we could see how that pattern could work. This is not working code (since it uses Tandem.COLORS which won't exist until #1251 is further along), but it is an exploratory idea to understand the pros and cons of a pattern:

Index: js/common/view/LidNode.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/common/view/LidNode.js b/js/common/view/LidNode.js
--- a/js/common/view/LidNode.js	(revision d6584afc1f68c2ff62e8465bfe6cfc03e753481c)
+++ b/js/common/view/LidNode.js	(date 1627452184280)
@@ -12,6 +12,8 @@
 import HandleNode from '../../../../scenery-phet/js/HandleNode.js';
 import Node from '../../../../scenery/js/nodes/Node.js';
 import Rectangle from '../../../../scenery/js/nodes/Rectangle.js';
+import ProfileColorProperty from '../../../../scenery/js/util/ProfileColorProperty.js';
+import Tandem from '../../../../tandem/js/Tandem.js';
 import gasProperties from '../../gasProperties.js';
 import GasPropertiesColors from '../GasPropertiesColors.js';
 import HoldConstant from '../model/HoldConstant.js';
@@ -20,6 +22,14 @@
 const HANDLE_ATTACHMENT_LINE_WIDTH = 1;
 const HANDLE_RIGHT_INSET = 3;
 
+// base of the lid, the part that the handle attaches to
+const lidBaseFillProperty = new ProfileColorProperty( 'lidBaseFill', {
+  default: 'rgb( 180, 180, 180 )',
+  projector: 'rgb( 128, 128, 128 )'
+}, {
+  tandem: Tandem.COLORS.createTandem( 'lidBaseFillProperty' )
+} );
+
 class LidNode extends Node {
 
   /**
@@ -37,7 +47,7 @@
     }, options );
 
     const baseNode = new Rectangle( 0, 0, options.baseWidth, options.baseHeight, {
-      fill: GasPropertiesColors.lidBaseFillProperty,
+      fill: lidBaseFillProperty,
       left: 0,
       bottom: 0
     } );
Index: js/common/GasPropertiesColors.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/common/GasPropertiesColors.js b/js/common/GasPropertiesColors.js
--- a/js/common/GasPropertiesColors.js	(revision d6584afc1f68c2ff62e8465bfe6cfc03e753481c)
+++ b/js/common/GasPropertiesColors.js	(date 1627452045551)
@@ -75,12 +75,6 @@
     projector: 'rgb( 220, 220, 220 )'
   } ),
 
-  // base of the lid, the part that the handle attaches to
-  lidBaseFillProperty: new ProfileColorProperty( 'lidBaseFill', {
-    default: 'rgb( 180, 180, 180 )',
-    projector: 'rgb( 128, 128, 128 )'
-  } ),
-
   // dimensional arrow that appears below the container
   sizeArrowColorProperty: new ProfileColorProperty( 'sizeArrowColor', {
     default: 'white',

Note that by using Tandem.COLORS (name to be determined in #1251) it will appear the same in the tandem tree whether it is in the usage file or the Colors.js file. Note this solves the problem described in #1257 (comment) by making it static, and seems like a reasonable approach.

I'm still a fan of putting them in one .js file.

I'd like to understand this appeal better. Imagine if I argued to keep all of the pointer areas in SimPointerAreas.js and all of the stroke widths in SimStrokeWidths.js etc. This is non-scalable and I fail to see the appeal. If there is a stroke width used twice, then by all means factor it out, but that doesn't mean we should strive to have one file with all the stroke widths. Why is that appealing for colors?

That makes it easy for non-developers to find the relevant code, if needed.

I don't think ease of discovery by non-developers should be a deciding factor here, since we provide Color Editor tooling for that purpose. We can also instruct non-developers how to find a ProfileColorProperty by searching for its name.

One could argue that it makes it a tad easier for developers too.

If I was going to change the strokeWidth of an electron, I would check in ElectronNode.js. If I was going to change the font of the electron's label, that's probably also in ElectronNode.js. Why would we declare everything about how electrons look in ElectronNode.js except its color (assuming that color is used only once, in ElectronNode)?

My sims will have a *Colors.js file.

It makes sense for every sim to have a Colors.js file for shared, factored out or general colors. The main question for this issue is whether we should strive to put all ProfileColorProperties and ColorDefs there or not. I agree all sims should have a *Colors.js file.

@zepumph
Copy link
Member

zepumph commented Jul 28, 2021

Can we please keep the PhET-iO-related conversation to #1251, I'm getting confused.

I'd like to understand this appeal better. Imagine if I argued to keep all of the pointer areas in SimPointerAreas.js and all of the stroke widths in SimStrokeWidths.js etc. This is non-scalable and I fail to see the appeal. If there is a stroke width used twice, then by all means factor it out, but that doesn't mean we should strive to have one file with all the stroke widths. Why is that appealing for colors?

Yes, I agree with this, but I also understand that there is precedent about how colors have been set up thus far. To mandate that they are integrated for all existing usages may be a cost we aren't interested in doing, for not much gain.

From PhET-iO designers, colors haven't seemed that important for the product, so likely using either pattern, on a per-sim basis, will be just fine.

@zepumph
Copy link
Member

zepumph commented Jul 28, 2021

I don't think ease of discovery by non-developers should be a deciding factor here, since we provide Color Editor tooling for that purpose. We can also instruct non-developers how to find a ProfileColorProperty by searching for its name.

Agreed, and this was also decided on in a meeting of some sort.

@samreid
Copy link
Member Author

samreid commented Jul 28, 2021

Can we please keep the PhET-iO-related conversation to #1251, I'm getting confused.

Being able to pass appropriate Tandems into ProfileColorProperties was used as an argument against putting ProfileColorProperties in files where they are used, but it has been demonstrated how to do that without a problem as long as (a) the colors remain static and (b) the tandems are kept together.

Yes, I agree with this, but I also understand that there is precedent about how colors have been set up thus far. To mandate that they are integrated for all existing usages may be a cost we aren't interested in doing, for not much gain.

Good point, I am not excited about incurring the cost of moving all colors for existing sims. But I wouldn't want to use precedent as a reason to say we use an otherwise preferable pattern going forward. Likewise, I'm not excited about having two ways of doing something, or a precedent that half of our sims use one pattern and the other half use another pattern.

Also, I realized that identifying usages of ProfileColorProperties got a lot easier now that they are declared the conventional way (not the ColorProfile/PropertySet dynamic way). Previously, it required a fuzzy text search of a dynamically constructed Property name. Now it is possible to use Alt-F7 "Find Usages" to identify whether there are multiple vs a single file uses. For instance, skimming through the 61 colors in areaModelCommonColorProfile took about 5 minutes, and revealed 41 colors that are used only in one file, and revealed one color that isn't used at all. Moving one took about 30 seconds. Extrapolating from this, we have 462 occurrences of ProfileColorProperty. Assuming the same rate of single-file-usage as area model, there would be 310 to move. If we can move 2 a minute with an error rate of 1%, and 30 minutes each to resolve errors when they are discovered, it would take around 4 hours to move everything. (I'm inclined to estimate 6 hours just to be safe). So it would still be good to make sure we are in agreement about whether it's worth it.

@zepumph
Copy link
Member

zepumph commented Jul 28, 2021

Being able to pass appropriate Tandems into ProfileColorProperties was used as an argument against putting ProfileColorProperties in files where they are used, but it has been demonstrated how to do that without a problem as long as (a) the colors remain static and (b) the tandems are kept together.

especially "pass appropriate Tandems"

It seems like we have a different definition of this. Where is the paper trail that says we prefer sim.global.colors.xColorProperty to sim.simScreen.view.x.colorProperty. Especially when the ProfileColorProperty is defined in X.js?

I think 6 hours is a low-ball estimate, many colors will be used by multiple things, and it may not be obvious to non-responsible devs where the best spot is.

What if we consider *Colors.js a deprecated pattern, to be used only in old sims, and prefer putting ProfileColorProperties in the spots they are used from here.

@pixelzoom
Copy link
Contributor

pixelzoom commented Jul 28, 2021

Imo, there's close to zero value in moving colors from (e.g) MySimColor.js to their usage sites.

Putting colors where they are used makes a lot of sense in common code. E.g. it would be preferrable to have static colors in BicyclePumpNode.js, rather than in SceneryPhetColors.js.

For the reasons that @samreid has mentioned, colors at usage sites should be static to the file, not instantiated per instance of their client class. I predict that this will be an on-going programming error, and something that will cause on-going PhET-iO problems. Maybe we need a CRC item related to ProfileColorProperty instantiation.

Finally, for sim-specific colors... Where they are defined should be up to developer discretion. If you'd like to put your colors at their usage sites, I support your decision. But I don't agree that's always the best decision, so please don't institute a policy that requires me to do that. It has proven in practice (for dozens of sims) that it's much easier for me (cognitively, procedurally, etc.) to go to one place to tweak and maintain sim colors.

@samreid
Copy link
Member Author

samreid commented Jul 28, 2021

@zepumph and I discussed this:

  • There is already a precedent where not all of the colors are supposed to be in the sim Colors.js file, so it was never meant to be complete anyways (nor should it be). For instance, MOLARITY/BeakerNode has const TICK_COLOR = Color.GRAY; and that doesn't need to be in the Colors.js file.
  • We like the idea that when you want to see when the color of ElectronNode changed, it would be in the file history of ElectronNode.js, not elsewhere
  • When I tried moving the 40 ProfileColorProperties out of areaModelCommonColorProfile, the file got a lot shorter and more palatable. It makes sense to me that sim Colors.js file would strive to have "reused or factored out colors" and not try to keep all colors or all ProfileColorProperties. @zepumph pointed out that if colors are static, then they are somewhat factored out anyways (not instance-specific), so may seem reasonable to move to the central color file. But using "something is static" as a heuristic for whether to move it to another file is not a good argument.
  • @zepumph is happy to proceed with either of the two proposals "all ProfileColorProperties in one file" or "put ProfileColorProperties where they are used, or in Colors.js if used in multiple files". Likewise, he doesn't see a problem with some sims using one strategy and other sims using the other strategy, and we do not need to move everything to the second pattern, even if we use it for new code.

@zepumph asked that @pixelzoom and @samreid take next steps to determine how to proceed.

@pixelzoom pixelzoom removed their assignment Jul 28, 2021
@pixelzoom
Copy link
Contributor

@zepumph is happy to proceed with either of the two proposals "all ProfileColorProperties in one file" or "put ProfileColorProperties where they are used, or in Colors.js if used in multiple files"

Again, I see no reason to require that a color be used in multiple files. If that's a hard requirement, I'm not willing to accept this proposal.

@pixelzoom
Copy link
Contributor

pixelzoom commented Jul 28, 2021

When I tried moving the 40 ProfileColorProperties out of areaModelCommonColorProfile, the file got a lot shorter and more palatable. It makes sense to me that sim Colors.js file would strive to have "reused or factored out colors" and not try to keep all colors or all ProfileColorProperties.

I'm sure that @jonathanolson has good reasons for putting all of those colors in areaModelCommonColorProfile. Your "more palatable" may not be more palatable to him. There are legitimate reasons other than "reused" for putting sim-specific colors in one place.

@samreid
Copy link
Member Author

samreid commented Jul 28, 2021

Putting colors where they are used makes a lot of sense in common code. E.g. it would be preferrable to have static colors in BicyclePumpNode.js, rather than in SceneryPhetColors.js.

Sounds good to me.

For the reasons that @samreid has mentioned, colors at usage sites should be static to the file, not instantiated per instance of their client class. I predict that this will be an on-going programming error, and something that will cause on-going PhET-iO problems. Maybe we need a CRC item related to ProfileColorProperty instantiation.

ProfileColorProperty names must be unique, so declaring one in a constructor would throw a runtime assertion. For instance, I test drove this with this patch:

Index: js/common/view/AreaCalculationRadioButtonGroup.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/common/view/AreaCalculationRadioButtonGroup.js b/js/common/view/AreaCalculationRadioButtonGroup.js
--- a/js/common/view/AreaCalculationRadioButtonGroup.js	(revision ddd14016500acffe7579a604c3dfbb377d5c6624)
+++ b/js/common/view/AreaCalculationRadioButtonGroup.js	(date 1627482687102)
@@ -12,6 +12,8 @@
 import Path from '../../../../scenery/js/nodes/Path.js';
 import Rectangle from '../../../../scenery/js/nodes/Rectangle.js';
 import VBox from '../../../../scenery/js/nodes/VBox.js';
+import Color from '../../../../scenery/js/util/Color.js';
+import ProfileColorProperty from '../../../../scenery/js/util/ProfileColorProperty.js';
 import eyeSlashSolidShape from '../../../../sherpa/js/fontawesome-5/eyeSlashSolidShape.js';
 import areaModelCommon from '../../areaModelCommon.js';
 import AreaCalculationChoice from '../model/AreaCalculationChoice.js';
@@ -26,7 +28,7 @@
    */
   constructor( areaCalculationChoiceProperty, selectionButtonAlignGroup ) {
 
-    const darkColorProperty = areaModelCommonColorProfile.calculationIconDarkProperty;
+    const darkColorProperty = new ProfileColorProperty( 'calculationIconDark', { default: Color.BLACK } );
     const lightColorProperty = areaModelCommonColorProfile.calculationIconLightProperty;
 
     super( areaCalculationChoiceProperty, [ {

And the assertion message was:

Assertion failed: cannot use the same name for two different ProfileColorProperty instances: calculationIconDark

Is that good enough, or do you recommend something further in the CRC?

It has proven in practice (for dozens of sims) that it's much easier for me (cognitively, procedurally, etc.) to go to one place to tweak and maintain sim colors.

Thanks, I don't want to institute a decision that will hamper your workflow, that's why I'm trying to understand why you prefer it as a pattern.

I'm sure that @jonathanolson has good reasons for putting all of those colors in areaModelCommonColorProfile. Your "more palatable" may not be more palatable to him.

Maybe they are there because it was previously the only way to get them to show in the color editor?

Can you comment on this point from my discussion with @zepumph?

We like the idea that when you want to see when the color of ElectronNode changed, it would be in the file history of ElectronNode.js, not elsewhere

Since (a) we are in agreement that sometimes it is best to put a color as a static in the related file, but (b) this proposal is only acceptable if you can also factor out a color used in only one file, then it seems the best way forward is to allow both styles, and leave it up to sim developers discretion where particular colors should be defined. Sound good?

@samreid samreid assigned pixelzoom and unassigned samreid Jul 28, 2021
@pixelzoom
Copy link
Contributor

pixelzoom commented Jul 28, 2021

ProfileColorProperty names must be unique, so declaring one in a constructor would throw a runtime assertion. For instance, I test drove this with this patch:

If there's 1 instance of a class, that assertion has zero chance of detecting this problem. The lucky developer who creates the 2nd instance will have to deal with it.

Thanks, I don't want to institute a decision that will hamper your workflow, that's why I'm trying to understand why you prefer it as a pattern.

Reasons why I prefer putting sim-specific colors in RepoColors.js:

  • I don't have 2 different patterns to deal with. Should I look for the color at its usage site or in RepoColors.js? I have one place to go when dealing with colors.
  • I don't have to move a color from its usage site to RepoColor.js when it becomes reused.
  • I don't have to remember the name of the .js class file (the usage site) where the color is defined. If color is defined at usage site, I need to find the .js file. Your example of ElectronNode.js is pretty obvious. It's not as obvious (and may take more time) for a name you can't recall, a sim you're not familiar with, or something that was poorly named.
  • I don't want to end up with some of a class' colors defined in the class file, and some in RepoColors.js. Using your ElectronNode example, what if positiveColorProperty is only used in ElectronNode, but negativeColorProperty is used in > 1 place. Your proposal puts positiveColorProperty in ElectronNode.js, and negativeColorProperty in RepoColors.js. In such a case, I think it's preferrable to keep positiveColorProperty and negativeColorProperty co-located.
  • When it comes time to instrument ProfileColorProperty instances for PhET-iO, I can handle that faster if they are in one file. I'm less likely to assign the same tandem name to 2 ProfileColorProperty instances. And I can also more quickly see which ProfileColorProperty instance are and are not instrumented.

... it seems the best way forward is to allow both styles, and leave it up to sim developers discretion where particular colors should be defined. Sound good?

Yes.

@samreid
Copy link
Member Author

samreid commented Jul 28, 2021

@pixelzoom and I discussed this further, another aspect not mentioned above is that the Constants.js and Colors.js files are a nice place to "fine tune" the simulation. For instance FMWConstants has:

  // Number of points awarded for each correct answer in the Wave Game
  POINTS_PER_CHALLENGE: 1,

this is only used in one place, but it's still nice to have it in the constants file since it parameterizes the overall behavior of the simulation.

We also agreed that there are numerous colors, strokes, fills that we would never want to move to the Colors.js file. It's just the ones we want to tinker with, or things that need to be used in more than one place. Like a panel fill or stroke that is broadly used. So it is a balance of convenience, design-orientation, ease of use for designers, etc. Having them together makes it easy to share with designers and ask "which of these should be phet-io instrumented"? If they are spread out, it is more difficult to share that list with phet-io designers. So there may be a reduced PhET-iO instrumentation cost to keeping these co-located. For a common code file, like BicyclePumpNode.js it is better to include them in the specific file rather than in the Colors.js file.

@pixelzoom suggested that when a sim is published, would be a good time to review Colors.js and make sure it has the appropriate colors. See if any should be moved closer to where they are used.

@pixelzoom also described that having different styles for this is not a significant maintenance problem.

@pixelzoom suggested we could raise this for discussion at dev meeting before closing the issue.

@zepumph
Copy link
Member

zepumph commented Aug 26, 2021

We went over the topics in the above during dev meeting today, as a PSA. Ready to close.

@zepumph zepumph closed this as completed Aug 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants