diff --git a/js/nodes/Imageable.ts b/js/nodes/Imageable.ts
index a59132aa9..4bd157746 100644
--- a/js/nodes/Imageable.ts
+++ b/js/nodes/Imageable.ts
@@ -62,8 +62,63 @@ export type Mipmap = {
updateCanvas?: () => void;
}[];
+/**
+ * The available ways to specify an image as an input to Imageable. See onImagePropertyChange() for parsing logic.
+ * We support a few different 'image' types that can be passed in:
+ *
+ * HTMLImageElement - A normal HTML . If it hasn't been fully loaded yet, Scenery will take care of adding a
+ * listener that will update Scenery with its width/height (and load its data) when the image is fully loaded.
+ * NOTE that if you just created the , it probably isn't loaded yet, particularly in Safari. If the Image
+ * node is constructed with an that hasn't fully loaded, it will have a width and height of 0, which may
+ * cause issues if you are using bounds for layout. Please see initialWidth/initialHeight notes below.
+ *
+ * URL - Provide a {string}, and Scenery will assume it is a URL. This can be a normal URL, or a data URI, both will
+ * work. Please note that this has the same loading-order issues as using HTMLImageElement, but that it's almost
+ * always guaranteed to not have a width/height when you create the Image node. Note that data URI support for
+ * formats depends on the browser - only JPEG and PNG are supported broadly. Please see initialWidth/initialHeight
+ * notes below.
+ * Additionally, note that if a URL is provided, accessing image.getImage() or image.image will result not in the
+ * original URL (currently), but with the automatically created HTMLImageElement.
+ *
+ * HTMLCanvasElement - It's possible to pass an HTML5 Canvas directly into the Image node. It will immediately be
+ * aware of the width/height (bounds) of the Canvas, but NOTE that the Image node will not listen to Canvas size
+ * changes. It is assumed that after you pass in a Canvas to an Image node that it will not be modified further.
+ * Additionally, the Image node will only be rendered using Canvas or WebGL if a Canvas is used as input.
+ *
+ * Mipmap data structure - Image supports a mipmap data structure that provides rasterized mipmap levels. The 'top'
+ * level (level 0) is the entire full-size image, and every other level is twice as small in every direction
+ * (~1/4 the pixels), rounding dimensions up. This is useful for browsers that display the image badly if the
+ * image is too large. Instead, Scenery will dynamically pick the most appropriate size of the image to use,
+ * which improves the image appearance.
+ * The passed in 'image' should be an Array of mipmap objects of the format:
+ * {
+ * img: {HTMLImageElement}, // preferably preloaded, but it isn't required
+ * url: {string}, // URL (usually a data URL) for the image level
+ * width: {number}, // width of the mipmap level, in pixels
+ * height: {number} // height of the mipmap level, in pixels,
+ * canvas: {HTMLCanvasElement} // Canvas element containing the image data for the img.
+ * [updateCanvas]: {function} // If available, should be called before using the Canvas directly.
+ * }
+ * At least one level is required (level 0), and each mipmap level corresponds to the index in the array, e.g.:
+ * [
+ * level 0 (full size, e.g. 100x64)
+ * level 1 (half size, e.g. 50x32)
+ * level 2 (quarter size, e.g. 25x16)
+ * level 3 (eighth size, e.g. 13x8 - note the rounding up)
+ * ...
+ * level N (single pixel, e.g. 1x1 - this is the smallest level permitted, and there should only be one)
+ * ]
+ * Additionally, note that (currently) image.getImage() will return the HTMLImageElement from the first level,
+ * not the mipmap data.
+ *
+ * Also note that if the underlying image (like Canvas data) has changed, it is recommended to call
+ * invalidateImage() instead of changing the image reference (calling setImage() multiple times)
+ */
export type ImageableImage = string | HTMLImageElement | HTMLCanvasElement | Mipmap;
+// The output image type from parsing the input "ImageableImage", see onImagePropertyChange()
+type ParsedImage = HTMLImageElement | HTMLCanvasElement | null;
+
export type ImageableOptions = {
image?: ImageableImage;
imageProperty?: TReadOnlyProperty;
@@ -80,8 +135,8 @@ export type ImageableOptions = {
const Imageable = ( type: SuperType ) => { // eslint-disable-line @typescript-eslint/explicit-module-boundary-types
return class ImageableMixin extends type {
- // (scenery-internal) Internal stateful value, see setImage()
- public _image: HTMLImageElement | HTMLCanvasElement | null;
+ // (scenery-internal) Internal stateful value, see onImagePropertyChange()
+ public _image: ParsedImage;
// For imageProperty
private readonly _imageProperty: TinyForwardingProperty;
@@ -161,59 +216,8 @@ const Imageable = ( type: SuperType ) => { // esl
}
/**
- * Sets the current image to be displayed by this Image node.
- *
- * We support a few different 'image' types that can be passed in:
- *
- * HTMLImageElement - A normal HTML . If it hasn't been fully loaded yet, Scenery will take care of adding a
- * listener that will update Scenery with its width/height (and load its data) when the image is fully loaded.
- * NOTE that if you just created the , it probably isn't loaded yet, particularly in Safari. If the Image
- * node is constructed with an that hasn't fully loaded, it will have a width and height of 0, which may
- * cause issues if you are using bounds for layout. Please see initialWidth/initialHeight notes below.
- *
- * URL - Provide a {string}, and Scenery will assume it is a URL. This can be a normal URL, or a data URI, both will
- * work. Please note that this has the same loading-order issues as using HTMLImageElement, but that it's almost
- * always guaranteed to not have a width/height when you create the Image node. Note that data URI support for
- * formats depends on the browser - only JPEG and PNG are supported broadly. Please see initialWidth/initialHeight
- * notes below.
- * Additionally, note that if a URL is provided, accessing image.getImage() or image.image will result not in the
- * original URL (currently), but with the automatically created HTMLImageElement.
- * TODO: return the original input https://github.com/phetsims/scenery/issues/1581
- *
- * HTMLCanvasElement - It's possible to pass an HTML5 Canvas directly into the Image node. It will immediately be
- * aware of the width/height (bounds) of the Canvas, but NOTE that the Image node will not listen to Canvas size
- * changes. It is assumed that after you pass in a Canvas to an Image node that it will not be modified further.
- * Additionally, the Image node will only be rendered using Canvas or WebGL if a Canvas is used as input.
- *
- * Mipmap data structure - Image supports a mipmap data structure that provides rasterized mipmap levels. The 'top'
- * level (level 0) is the entire full-size image, and every other level is twice as small in every direction
- * (~1/4 the pixels), rounding dimensions up. This is useful for browsers that display the image badly if the
- * image is too large. Instead, Scenery will dynamically pick the most appropriate size of the image to use,
- * which improves the image appearance.
- * The passed in 'image' should be an Array of mipmap objects of the format:
- * {
- * img: {HTMLImageElement}, // preferably preloaded, but it isn't required
- * url: {string}, // URL (usually a data URL) for the image level
- * width: {number}, // width of the mipmap level, in pixels
- * height: {number} // height of the mipmap level, in pixels,
- * canvas: {HTMLCanvasElement} // Canvas element containing the image data for the img.
- * [updateCanvas]: {function} // If available, should be called before using the Canvas directly.
- * }
- * At least one level is required (level 0), and each mipmap level corresponds to the index in the array, e.g.:
- * [
- * level 0 (full size, e.g. 100x64)
- * level 1 (half size, e.g. 50x32)
- * level 2 (quarter size, e.g. 25x16)
- * level 3 (eighth size, e.g. 13x8 - note the rounding up)
- * ...
- * level N (single pixel, e.g. 1x1 - this is the smallest level permitted, and there should only be one)
- * ]
- * Additionally, note that (currently) image.getImage() will return the HTMLImageElement from the first level,
- * not the mipmap data.
- * TODO: return the original input
+ * Sets the current image to be displayed by this Image node. See ImageableImage for details on provided image value.
*
- * Also note that if the underlying image (like Canvas data) has changed, it is recommended to call
- * invalidateImage() instead of changing the image reference (calling setImage() multiple times)
*/
public setImage( image: ImageableImage ): this {
assert && assert( image, 'image should be available' );
@@ -225,16 +229,16 @@ const Imageable = ( type: SuperType ) => { // esl
public set image( value: ImageableImage ) { this.setImage( value ); }
- public get image(): HTMLImageElement | HTMLCanvasElement { return this.getImage(); }
+ public get image(): ParsedImage { return this.getImage(); }
/**
* Returns the current image's representation as either a Canvas or img element.
*
* NOTE: If a URL or mipmap data was provided, this currently doesn't return the original input to setImage(), but
- * instead provides the mapped result (or first mipmap level's image).
- * TODO: return the original result instead. https://github.com/phetsims/scenery/issues/1581
+ * instead provides the mapped result (or first mipmap level's image). If you need the original, use
+ * imageProperty instead.
*/
- public getImage(): HTMLImageElement | HTMLCanvasElement {
+ public getImage(): ParsedImage {
assert && assert( this._image !== null );
return this._image!;
@@ -345,11 +349,11 @@ const Imageable = ( type: SuperType ) => { // esl
* function. This may trigger bounds changes, even if the previous and next image (and image dimensions)
* are the same.
*
- * @param image - See setImage()'s documentation
+ * @param image - See ImageableImage's type documentation
* @param width - Initial width of the image. See setInitialWidth() for more documentation
* @param height - Initial height of the image. See setInitialHeight() for more documentation
*/
- public setImageWithSize( image: string | HTMLImageElement | HTMLCanvasElement | Mipmap, width: number, height: number ): this {
+ public setImageWithSize( image: ImageableImage, width: number, height: number ): this {
// First, setImage(), as it will reset the initial width and height
this.setImage( image );
@@ -934,7 +938,7 @@ const Imageable = ( type: SuperType ) => { // esl
* @param width - logical width of the image
* @param height - logical height of the image
*/
-Imageable.getHitTestData = ( image: HTMLImageElement | HTMLCanvasElement, width: number, height: number ): ImageData | null => {
+Imageable.getHitTestData = ( image: Exclude, width: number, height: number ): ImageData | null => {
// If the image isn't loaded yet, we don't want to try loading anything
if ( !( ( 'naturalWidth' in image ? image.naturalWidth : 0 ) || image.width ) || !( ( 'naturalHeight' in image ? image.naturalHeight : 0 ) || image.height ) ) {
return null;
diff --git a/js/nodes/Path.ts b/js/nodes/Path.ts
index bcd6f2cb7..84a223212 100644
--- a/js/nodes/Path.ts
+++ b/js/nodes/Path.ts
@@ -10,7 +10,7 @@ import Bounds2 from '../../../dot/js/Bounds2.js';
import { Shape } from '../../../kite/js/imports.js';
import Matrix3 from '../../../dot/js/Matrix3.js';
import Vector2 from '../../../dot/js/Vector2.js';
-import { CanvasContextWrapper, CanvasSelfDrawable, Instance, TPathDrawable, Node, NodeOptions, Paint, Paintable, PAINTABLE_DRAWABLE_MARK_FLAGS, PAINTABLE_OPTION_KEYS, PaintableOptions, PathCanvasDrawable, PathSVGDrawable, Renderer, scenery, SVGSelfDrawable } from '../imports.js';
+import { CanvasContextWrapper, CanvasSelfDrawable, Instance, Node, NodeOptions, Paint, Paintable, PAINTABLE_DRAWABLE_MARK_FLAGS, PAINTABLE_OPTION_KEYS, PaintableOptions, PathCanvasDrawable, PathSVGDrawable, Renderer, scenery, SVGSelfDrawable, TPathDrawable } from '../imports.js';
import optionize, { combineOptions } from '../../../phet-core/js/optionize.js';
import TReadOnlyProperty, { isTReadOnlyProperty } from '../../../axon/js/TReadOnlyProperty.js';
import WithRequired from '../../../phet-core/js/types/WithRequired.js';
@@ -31,32 +31,43 @@ const DEFAULT_OPTIONS = {
export type PathBoundsMethod = 'accurate' | 'unstroked' | 'tightPadding' | 'safePadding' | 'none';
+/**
+ * The valid parameter types are:
+ * - Shape: (from Kite), normally used.
+ * - string: Uses the SVG Path format, see https://www.w3.org/TR/SVG/paths.html (the PATH part of ).
+ * This will immediately be converted to a Shape object when set, and getShape() or equivalents will return
+ * the parsed Shape instance instead of the original string. See "ParsedShape"
+ * - null: Indicates that there is no Shape, and nothing is drawn. Usually used as a placeholder.
+ *
+ * NOTE: Be aware of the potential for memory leaks. If a Shape is not marked as immutable (with makeImmutable()),
+ * Path will add a listener so that it is updated when the Shape itself changes. If there is a listener
+ * added, keeping a reference to the Shape will also keep a reference to the Path object (and thus whatever
+ * Nodes are connected to the Path). For now, set path.shape = null if you need to release the reference
+ * that the Shape would have, or call dispose() on the Path if it is not needed anymore.
+ */
+type InputShape = Shape | string | null;
+
+/**
+ * See InputShape for details, but this type differs in that it only supports a Shape, and any "string" data will
+ * be parsed into a Shape instance.
+ */
+type ParsedShape = Shape | null;
+
+// Provide these as an option.
type SelfOptions = {
+
/**
* This sets the shape of the Path, which determines the shape of its appearance. It should generally not be called
- * on Path subtypes like Line, Rectangle, etc.
- *
- * NOTE: When you create a Path with a shape in the constructor, this function will be called.
- *
- * The valid parameter types are:
- * - Shape: (from Kite), normally used.
- * - string: Uses the SVG Path format, see https://www.w3.org/TR/SVG/paths.html (the PATH part of ).
- * This will immediately be converted to a Shape object, and getShape() or equivalents will return the new
- * Shape object instead of the original string.
- * - null: Indicates that there is no Shape, and nothing is drawn. Usually used as a placeholder.
+ * on Path subtypes like Line, Rectangle, etc. See InputShape for details about what to provide for the shape.
*
- * NOTE: Be aware of the potential for memory leaks. If a Shape is not marked as immutable (with makeImmutable()),
- * Path will add a listener so that it is updated when the Shape itself changes. If there is a listener
- * added, keeping a reference to the Shape will also keep a reference to the Path object (and thus whatever
- * Nodes are connected to the Path). For now, set path.shape = null if you need to release the reference
- * that the Shape would have, or call dispose() on the Path if it is not needed anymore.
+ * NOTE: When you create a Path with a shape in the constructor, this setter will be called (don't overload the option).
*/
- shape?: Shape | string | null;
+ shape?: InputShape;
/**
* Similar to `shape`, but allows setting the shape as a Property.
*/
- shapeProperty?: TReadOnlyProperty;
+ shapeProperty?: TReadOnlyProperty;
/**
* Sets the bounds method for the Path. This determines how our (self) bounds are computed, and can particularly
@@ -86,14 +97,14 @@ export default class Path extends Paintable( Node ) {
// so it is best to not have to compute it on changes.
// NOTE: Please use hasShape() to determine if we are actually drawing things, as it is subtype-safe.
// (scenery-internal)
- public _shape: Shape | null;
+ public _shape: ParsedShape;
// For shapeProperty
- private readonly _shapeProperty: TinyForwardingProperty;
+ private readonly _shapeProperty: TinyForwardingProperty;
// This stores a stroked copy of the Shape which is lazily computed. This can be required for computing bounds
// of a Shape with a stroke.
- private _strokedShape: Shape | null;
+ private _strokedShape: ParsedShape;
// (scenery-internal)
public _boundsMethod: PathBoundsMethod;
@@ -113,11 +124,11 @@ export default class Path extends Paintable( Node ) {
* - shape: The actual Shape (or a string representing an SVG path, or null).
* - boundsMethod: Determines how the bounds of a shape are determined.
*
- * @param shape - The initial Shape to display. See setShape() for more details and documentation.
+ * @param shape - The initial Shape to display. See onShapePropertyChange() for more details and documentation.
* @param [providedOptions] - Path-specific options are documented in PATH_OPTION_KEYS above, and can be provided
* along-side options for Node
*/
- public constructor( shape: Shape | string | null | TReadOnlyProperty, providedOptions?: PathOptions ) {
+ public constructor( shape: InputShape | TReadOnlyProperty, providedOptions?: PathOptions ) {
assert && assert( providedOptions === undefined || Object.getPrototypeOf( providedOptions ) === Object.prototype,
'Extra prototype on Node options object is a code smell' );
@@ -141,7 +152,7 @@ export default class Path extends Paintable( Node ) {
super();
// We'll initialize this by mutating.
- this._shapeProperty = new TinyForwardingProperty( null, false, this.onShapePropertyChange.bind( this ) );
+ this._shapeProperty = new TinyForwardingProperty( null, false, this.onShapePropertyChange.bind( this ) );
this._shape = DEFAULT_OPTIONS.shape;
this._strokedShape = null;
@@ -154,7 +165,7 @@ export default class Path extends Paintable( Node ) {
this.mutate( options );
}
- public setShape( shape: Shape | string | null ): this {
+ public setShape( shape: InputShape ): this {
assert && assert( shape === null || typeof shape === 'string' || shape instanceof Shape,
'A path\'s shape should either be null, a string, or a Shape' );
@@ -163,9 +174,9 @@ export default class Path extends Paintable( Node ) {
return this;
}
- public set shape( value: Shape | string | null ) { this.setShape( value ); }
+ public set shape( value: InputShape ) { this.setShape( value ); }
- public get shape(): Shape | null { return this.getShape(); }
+ public get shape(): ParsedShape { return this.getShape(); }
/**
* Returns the shape that was set for this Path (or for subtypes like Line and Rectangle, will return an immutable
@@ -174,11 +185,12 @@ export default class Path extends Paintable( Node ) {
* It is best to generally assume modifications to the Shape returned is not supported. If there is no shape
* currently, null will be returned.
*/
- public getShape(): Shape | null {
+ public getShape(): ParsedShape {
+ assert && assert( this.shapeProperty.value === this._shape );
return this._shape;
}
- private onShapePropertyChange( shape: Shape | string | null ): void {
+ private onShapePropertyChange( shape: InputShape ): void {
assert && assert( shape === null || typeof shape === 'string' || shape instanceof Shape,
'A path\'s shape should either be null, a string, or a Shape' );
@@ -189,7 +201,7 @@ export default class Path extends Paintable( Node ) {
}
if ( typeof shape === 'string' ) {
- // be content with setShape always invalidating the shape?
+ // be content with onShapePropertyChange always invalidating the shape?
shape = new Shape( shape );
}
this._shape = shape;
@@ -205,19 +217,19 @@ export default class Path extends Paintable( Node ) {
/**
* See documentation for Node.setVisibleProperty, except this is for the shape
*/
- public setShapeProperty( newTarget: TReadOnlyProperty | null ): this {
- return this._shapeProperty.setTargetProperty( this, null, newTarget as TProperty );
+ public setShapeProperty( newTarget: TReadOnlyProperty | null ): this {
+ return this._shapeProperty.setTargetProperty( this, null, newTarget as TProperty );
}
- public set shapeProperty( property: TReadOnlyProperty | null ) { this.setShapeProperty( property ); }
+ public set shapeProperty( property: TReadOnlyProperty | null ) { this.setShapeProperty( property ); }
- public get shapeProperty(): TProperty { return this.getShapeProperty(); }
+ public get shapeProperty(): TProperty { return this.getShapeProperty(); }
/**
* Like Node.getVisibleProperty(), but for the shape. Note this is not the same as the Property provided in
- * setImageProperty. Thus is the nature of TinyForwardingProperty.
+ * setShapeProperty. Thus is the nature of TinyForwardingProperty.
*/
- public getShapeProperty(): TProperty {
+ public getShapeProperty(): TProperty {
return this._shapeProperty;
}