import _ from 'lodash';
import '../../../assert/js/assert.js';
import '../../../globals.js';
// Copyright 2021-2024, University of Colorado Boulder
/**
 * Trait for Nodes that support a standard fill and/or stroke (e.g. Text, Path and Path subtypes).
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
import { isTReadOnlyProperty } from '../../../axon/js/TReadOnlyProperty.js';
import { LINE_STYLE_DEFAULT_OPTIONS, LineStyles } from '../../../kite/js/imports.js';
import arrayRemove from '../../../phet-core/js/arrayRemove.js';
import assertHasProperties from '../../../phet-core/js/assertHasProperties.js';
import inheritance from '../../../phet-core/js/inheritance.js';
import memoize from '../../../phet-core/js/memoize.js';
import platform from '../../../phet-core/js/platform.js';
import { Color, Gradient, Node, Paint, PaintDef, Pattern, Renderer, scenery } from '../imports.js';
const isSafari5 = platform.safari5;
const PAINTABLE_OPTION_KEYS = [
    'fill', // {PaintDef} - Sets the fill of this Node, see setFill() for documentation.
    'fillPickable', // {boolean} - Sets whether the filled area of the Node will be treated as 'inside'. See setFillPickable()
    'stroke', // {PaintDef} - Sets the stroke of this Node, see setStroke() for documentation.
    'strokePickable', // {boolean} - Sets whether the stroked area of the Node will be treated as 'inside'. See setStrokePickable()
    'lineWidth', // {number} - Sets the width of the stroked area, see setLineWidth for documentation.
    'lineCap', // {string} - Sets the shape of the stroked area at the start/end of the path, see setLineCap() for documentation.
    'lineJoin', // {string} - Sets the shape of the stroked area at joints, see setLineJoin() for documentation.
    'miterLimit', // {number} - Sets when lineJoin will switch from miter to bevel, see setMiterLimit() for documentation.
    'lineDash', // {Array.<number>} - Sets a line-dash pattern for the stroke, see setLineDash() for documentation
    'lineDashOffset', // {number} - Sets the offset of the line-dash from the start of the stroke, see setLineDashOffset()
    'cachedPaints' // {Array.<PaintDef>} - Sets which paints should be cached, even if not displayed. See setCachedPaints()
];
const DEFAULT_OPTIONS = {
    fill: null,
    fillPickable: true,
    stroke: null,
    strokePickable: false,
    // Not set initially, but they are the LineStyles defaults
    lineWidth: LINE_STYLE_DEFAULT_OPTIONS.lineWidth,
    lineCap: LINE_STYLE_DEFAULT_OPTIONS.lineCap,
    lineJoin: LINE_STYLE_DEFAULT_OPTIONS.lineJoin,
    lineDashOffset: LINE_STYLE_DEFAULT_OPTIONS.lineDashOffset,
    miterLimit: LINE_STYLE_DEFAULT_OPTIONS.miterLimit
};
const PAINTABLE_DRAWABLE_MARK_FLAGS = ['fill', 'stroke', 'lineWidth', 'lineOptions', 'cachedPaints'];
const Paintable = memoize((Type) => {
    assert && assert(_.includes(inheritance(Type), Node), 'Only Node subtypes should mix Paintable');
    return class PaintableMixin extends Type {
        constructor(...args) {
            super(...args);
            assertHasProperties(this, ['_drawables']);
            this._fill = DEFAULT_OPTIONS.fill;
            this._fillPickable = DEFAULT_OPTIONS.fillPickable;
            this._stroke = DEFAULT_OPTIONS.stroke;
            this._strokePickable = DEFAULT_OPTIONS.strokePickable;
            this._cachedPaints = [];
            this._lineDrawingStyles = new LineStyles();
        }
        /**
         * Sets the fill color for the Node.
         *
         * The fill determines the appearance of the interior part of a Path or Text.
         *
         * Please use null for indicating "no fill" (that is the default). Strings and Scenery Color objects can be
         * provided for a single-color flat appearance, and can be wrapped with an Axon Property. Gradients and patterns
         * can also be provided.
         */
        setFill(fill) {
            assert && assert(PaintDef.isPaintDef(fill), 'Invalid fill type');
            if (assert && typeof fill === 'string') {
                Color.checkPaintString(fill);
            }
            // Instance equality used here since it would be more expensive to parse all CSS
            // colors and compare every time the fill changes. Right now, usually we don't have
            // to parse CSS colors. See https://github.com/phetsims/scenery/issues/255
            if (this._fill !== fill) {
                this._fill = fill;
                this.invalidateFill();
            }
            return this;
        }
        set fill(value) { this.setFill(value); }
        get fill() { return this.getFill(); }
        /**
         * Returns the fill (if any) for this Node.
         */
        getFill() {
            return this._fill;
        }
        /**
         * Returns whether there is a fill applied to this Node.
         */
        hasFill() {
            return this.getFillValue() !== null;
        }
        /**
         * Returns a property-unwrapped fill if applicable.
         */
        getFillValue() {
            const fill = this.getFill();
            return isTReadOnlyProperty(fill) ? fill.get() : fill;
        }
        get fillValue() { return this.getFillValue(); }
        /**
         * Sets the stroke color for the Node.
         *
         * The stroke determines the appearance of the region along the boundary of the Path or Text. The shape of the
         * stroked area depends on the base shape (that of the Path or Text) and multiple parameters:
         * lineWidth/lineCap/lineJoin/miterLimit/lineDash/lineDashOffset. It will be drawn on top of any fill on the
         * same Node.
         *
         * Please use null for indicating "no stroke" (that is the default). Strings and Scenery Color objects can be
         * provided for a single-color flat appearance, and can be wrapped with an Axon Property. Gradients and patterns
         * can also be provided.
         */
        setStroke(stroke) {
            assert && assert(PaintDef.isPaintDef(stroke), 'Invalid stroke type');
            if (assert && typeof stroke === 'string') {
                Color.checkPaintString(stroke);
            }
            // Instance equality used here since it would be more expensive to parse all CSS
            // colors and compare every time the fill changes. Right now, usually we don't have
            // to parse CSS colors. See https://github.com/phetsims/scenery/issues/255
            if (this._stroke !== stroke) {
                this._stroke = stroke;
                if (assert && stroke instanceof Paint && stroke.transformMatrix) {
                    const scaleVector = stroke.transformMatrix.getScaleVector();
                    assert(Math.abs(scaleVector.x - scaleVector.y) < 1e-7, 'You cannot specify a pattern or gradient to a stroke that does not have a symmetric scale.');
                }
                this.invalidateStroke();
            }
            return this;
        }
        set stroke(value) { this.setStroke(value); }
        get stroke() { return this.getStroke(); }
        /**
         * Returns the stroke (if any) for this Node.
         */
        getStroke() {
            return this._stroke;
        }
        /**
         * Returns whether there is a stroke applied to this Node.
         */
        hasStroke() {
            return this.getStrokeValue() !== null;
        }
        /**
         * Returns whether there will appear to be a stroke for this Node. Properly handles the lineWidth:0 case.
         */
        hasPaintableStroke() {
            // Should not be stroked if the lineWidth is 0, see https://github.com/phetsims/scenery/issues/658
            // and https://github.com/phetsims/scenery/issues/523
            return this.hasStroke() && this.getLineWidth() > 0;
        }
        /**
         * Returns a property-unwrapped stroke if applicable.
         */
        getStrokeValue() {
            const stroke = this.getStroke();
            return isTReadOnlyProperty(stroke) ? stroke.get() : stroke;
        }
        get strokeValue() { return this.getStrokeValue(); }
        /**
         * Sets whether the fill is marked as pickable.
         */
        setFillPickable(pickable) {
            if (this._fillPickable !== pickable) {
                this._fillPickable = pickable;
                // TODO: better way of indicating that only the Node under pointers could have changed, but no paint change is needed? https://github.com/phetsims/scenery/issues/1581
                this.invalidateFill();
            }
            return this;
        }
        set fillPickable(value) { this.setFillPickable(value); }
        get fillPickable() { return this.isFillPickable(); }
        /**
         * Returns whether the fill is marked as pickable.
         */
        isFillPickable() {
            return this._fillPickable;
        }
        /**
         * Sets whether the stroke is marked as pickable.
         */
        setStrokePickable(pickable) {
            if (this._strokePickable !== pickable) {
                this._strokePickable = pickable;
                // TODO: better way of indicating that only the Node under pointers could have changed, but no paint change is needed? https://github.com/phetsims/scenery/issues/1581
                this.invalidateStroke();
            }
            return this;
        }
        set strokePickable(value) { this.setStrokePickable(value); }
        get strokePickable() { return this.isStrokePickable(); }
        /**
         * Returns whether the stroke is marked as pickable.
         */
        isStrokePickable() {
            return this._strokePickable;
        }
        /**
         * Sets the line width that will be applied to strokes on this Node.
         */
        setLineWidth(lineWidth) {
            assert && assert(lineWidth >= 0, `lineWidth should be non-negative instead of ${lineWidth}`);
            if (this.getLineWidth() !== lineWidth) {
                this._lineDrawingStyles.lineWidth = lineWidth;
                this.invalidateStroke();
                const stateLen = this._drawables.length;
                for (let i = 0; i < stateLen; i++) {
                    this._drawables[i].markDirtyLineWidth();
                }
            }
            return this;
        }
        set lineWidth(value) { this.setLineWidth(value); }
        get lineWidth() { return this.getLineWidth(); }
        /**
         * Returns the line width that would be applied to strokes.
         */
        getLineWidth() {
            return this._lineDrawingStyles.lineWidth;
        }
        /**
         * Sets the line cap style. There are three options:
         * - 'butt' (the default) stops the line at the end point
         * - 'round' draws a semicircular arc around the end point
         * - 'square' draws a square outline around the end point (like butt, but extended by 1/2 line width out)
         */
        setLineCap(lineCap) {
            assert && assert(lineCap === 'butt' || lineCap === 'round' || lineCap === 'square', `lineCap should be one of "butt", "round" or "square", not ${lineCap}`);
            if (this._lineDrawingStyles.lineCap !== lineCap) {
                this._lineDrawingStyles.lineCap = lineCap;
                this.invalidateStroke();
                const stateLen = this._drawables.length;
                for (let i = 0; i < stateLen; i++) {
                    this._drawables[i].markDirtyLineOptions();
                }
            }
            return this;
        }
        set lineCap(value) { this.setLineCap(value); }
        get lineCap() { return this.getLineCap(); }
        /**
         * Returns the line cap style (controls appearance at the start/end of paths)
         */
        getLineCap() {
            return this._lineDrawingStyles.lineCap;
        }
        /**
         * Sets the line join style. There are three options:
         * - 'miter' (default) joins by extending the segments out in a line until they meet. For very sharp
         *           corners, they will be chopped off and will act like 'bevel', depending on what the miterLimit is.
         * - 'round' draws a circular arc to connect the two stroked areas.
         * - 'bevel' connects with a single line segment.
         */
        setLineJoin(lineJoin) {
            assert && assert(lineJoin === 'miter' || lineJoin === 'round' || lineJoin === 'bevel', `lineJoin should be one of "miter", "round" or "bevel", not ${lineJoin}`);
            if (this._lineDrawingStyles.lineJoin !== lineJoin) {
                this._lineDrawingStyles.lineJoin = lineJoin;
                this.invalidateStroke();
                const stateLen = this._drawables.length;
                for (let i = 0; i < stateLen; i++) {
                    this._drawables[i].markDirtyLineOptions();
                }
            }
            return this;
        }
        set lineJoin(value) { this.setLineJoin(value); }
        get lineJoin() { return this.getLineJoin(); }
        /**
         * Returns the current line join style (controls join appearance between drawn segments).
         */
        getLineJoin() {
            return this._lineDrawingStyles.lineJoin;
        }
        /**
         * Sets the miterLimit value. This determines how sharp a corner with lineJoin: 'miter' will need to be before
         * it gets cut off to the 'bevel' behavior.
         */
        setMiterLimit(miterLimit) {
            assert && assert(isFinite(miterLimit), 'miterLimit should be a finite number');
            if (this._lineDrawingStyles.miterLimit !== miterLimit) {
                this._lineDrawingStyles.miterLimit = miterLimit;
                this.invalidateStroke();
                const stateLen = this._drawables.length;
                for (let i = 0; i < stateLen; i++) {
                    this._drawables[i].markDirtyLineOptions();
                }
            }
            return this;
        }
        set miterLimit(value) { this.setMiterLimit(value); }
        get miterLimit() { return this.getMiterLimit(); }
        /**
         * Returns the miterLimit value.
         */
        getMiterLimit() {
            return this._lineDrawingStyles.miterLimit;
        }
        /**
         * Sets the line dash pattern. Should be an array of numbers "on" and "off" alternating. An empty array
         * indicates no dashing.
         */
        setLineDash(lineDash) {
            assert && assert(Array.isArray(lineDash) && lineDash.every(n => typeof n === 'number' && isFinite(n) && n >= 0), 'lineDash should be an array of finite non-negative numbers');
            if (this._lineDrawingStyles.lineDash !== lineDash) {
                this._lineDrawingStyles.lineDash = lineDash || [];
                this.invalidateStroke();
                const stateLen = this._drawables.length;
                for (let i = 0; i < stateLen; i++) {
                    this._drawables[i].markDirtyLineOptions();
                }
            }
            return this;
        }
        set lineDash(value) { this.setLineDash(value); }
        get lineDash() { return this.getLineDash(); }
        /**
         * Gets the line dash pattern. An empty array is the default, indicating no dashing.
         */
        getLineDash() {
            return this._lineDrawingStyles.lineDash;
        }
        /**
         * Returns whether the stroke will be dashed.
         */
        hasLineDash() {
            return !!this._lineDrawingStyles.lineDash.length;
        }
        /**
         * Sets the offset of the line dash pattern from the start of the stroke. Defaults to 0.
         */
        setLineDashOffset(lineDashOffset) {
            assert && assert(isFinite(lineDashOffset), `lineDashOffset should be a number, not ${lineDashOffset}`);
            if (this._lineDrawingStyles.lineDashOffset !== lineDashOffset) {
                this._lineDrawingStyles.lineDashOffset = lineDashOffset;
                this.invalidateStroke();
                const stateLen = this._drawables.length;
                for (let i = 0; i < stateLen; i++) {
                    this._drawables[i].markDirtyLineOptions();
                }
            }
            return this;
        }
        set lineDashOffset(value) { this.setLineDashOffset(value); }
        get lineDashOffset() { return this.getLineDashOffset(); }
        /**
         * Returns the offset of the line dash pattern from the start of the stroke.
         */
        getLineDashOffset() {
            return this._lineDrawingStyles.lineDashOffset;
        }
        /**
         * Sets the LineStyles object (it determines stroke appearance). The passed-in object will be mutated as needed.
         */
        setLineStyles(lineStyles) {
            this._lineDrawingStyles = lineStyles;
            this.invalidateStroke();
            return this;
        }
        set lineStyles(value) { this.setLineStyles(value); }
        get lineStyles() { return this.getLineStyles(); }
        /**
         * Returns the composite {LineStyles} object, that determines stroke appearance.
         */
        getLineStyles() {
            return this._lineDrawingStyles;
        }
        /**
         * Sets the cached paints to the input array (a defensive copy). Note that it also filters out fills that are
         * not considered paints (e.g. strings, Colors, etc.).
         *
         * When this Node is displayed in SVG, it will force the presence of the cached paint to be stored in the SVG's
         * <defs> element, so that we can switch quickly to use the given paint (instead of having to create it on the
         * SVG-side whenever the switch is made).
         *
         * Also note that duplicate paints are acceptable, and don't need to be filtered out before-hand.
         */
        setCachedPaints(paints) {
            this._cachedPaints = paints.filter((paint) => paint instanceof Paint);
            const stateLen = this._drawables.length;
            for (let i = 0; i < stateLen; i++) {
                this._drawables[i].markDirtyCachedPaints();
            }
            return this;
        }
        set cachedPaints(value) { this.setCachedPaints(value); }
        get cachedPaints() { return this.getCachedPaints(); }
        /**
         * Returns the cached paints.
         */
        getCachedPaints() {
            return this._cachedPaints;
        }
        /**
         * Adds a cached paint. Does nothing if paint is just a normal fill (string, Color), but for gradients and
         * patterns, it will be made faster to switch to.
         *
         * When this Node is displayed in SVG, it will force the presence of the cached paint to be stored in the SVG's
         * <defs> element, so that we can switch quickly to use the given paint (instead of having to create it on the
         * SVG-side whenever the switch is made).
         *
         * Also note that duplicate paints are acceptable, and don't need to be filtered out before-hand.
         */
        addCachedPaint(paint) {
            if (paint instanceof Paint) {
                this._cachedPaints.push(paint);
                const stateLen = this._drawables.length;
                for (let i = 0; i < stateLen; i++) {
                    this._drawables[i].markDirtyCachedPaints();
                }
            }
        }
        /**
         * Removes a cached paint. Does nothing if paint is just a normal fill (string, Color), but for gradients and
         * patterns it will remove any existing cached paint. If it was added more than once, it will need to be removed
         * more than once.
         *
         * When this Node is displayed in SVG, it will force the presence of the cached paint to be stored in the SVG's
         * <defs> element, so that we can switch quickly to use the given paint (instead of having to create it on the
         * SVG-side whenever the switch is made).
         */
        removeCachedPaint(paint) {
            if (paint instanceof Paint) {
                assert && assert(_.includes(this._cachedPaints, paint));
                arrayRemove(this._cachedPaints, paint);
                const stateLen = this._drawables.length;
                for (let i = 0; i < stateLen; i++) {
                    this._drawables[i].markDirtyCachedPaints();
                }
            }
        }
        /**
         * Applies the fill to a Canvas context wrapper, before filling. (scenery-internal)
         */
        beforeCanvasFill(wrapper) {
            assert && assert(this.getFillValue() !== null);
            const fillValue = this.getFillValue();
            wrapper.setFillStyle(fillValue);
            // @ts-expect-error - For performance, we could check this by ruling out string and 'transformMatrix' in fillValue
            if (fillValue.transformMatrix) {
                wrapper.context.save();
                // @ts-expect-error
                fillValue.transformMatrix.canvasAppendTransform(wrapper.context);
            }
        }
        /**
         * Un-applies the fill to a Canvas context wrapper, after filling. (scenery-internal)
         */
        afterCanvasFill(wrapper) {
            const fillValue = this.getFillValue();
            // @ts-expect-error
            if (fillValue.transformMatrix) {
                wrapper.context.restore();
            }
        }
        /**
         * Applies the stroke to a Canvas context wrapper, before stroking. (scenery-internal)
         */
        beforeCanvasStroke(wrapper) {
            const strokeValue = this.getStrokeValue();
            // TODO: is there a better way of not calling so many things on each stroke? https://github.com/phetsims/scenery/issues/1581
            wrapper.setStrokeStyle(this._stroke);
            wrapper.setLineCap(this.getLineCap());
            wrapper.setLineJoin(this.getLineJoin());
            // @ts-expect-error - for performance
            if (strokeValue.transformMatrix) {
                // @ts-expect-error
                const scaleVector = strokeValue.transformMatrix.getScaleVector();
                assert && assert(Math.abs(scaleVector.x - scaleVector.y) < 1e-7, 'You cannot specify a pattern or gradient to a stroke that does not have a symmetric scale.');
                const matrixMultiplier = 1 / scaleVector.x;
                wrapper.context.save();
                // @ts-expect-error
                strokeValue.transformMatrix.canvasAppendTransform(wrapper.context);
                wrapper.setLineWidth(this.getLineWidth() * matrixMultiplier);
                wrapper.setMiterLimit(this.getMiterLimit() * matrixMultiplier);
                wrapper.setLineDash(this.getLineDash().map(dash => dash * matrixMultiplier));
                wrapper.setLineDashOffset(this.getLineDashOffset() * matrixMultiplier);
            }
            else {
                wrapper.setLineWidth(this.getLineWidth());
                wrapper.setMiterLimit(this.getMiterLimit());
                wrapper.setLineDash(this.getLineDash());
                wrapper.setLineDashOffset(this.getLineDashOffset());
            }
        }
        /**
         * Un-applies the stroke to a Canvas context wrapper, after stroking. (scenery-internal)
         */
        afterCanvasStroke(wrapper) {
            const strokeValue = this.getStrokeValue();
            // @ts-expect-error - for performance
            if (strokeValue.transformMatrix) {
                wrapper.context.restore();
            }
        }
        /**
         * If applicable, returns the CSS color for the fill.
         */
        getCSSFill() {
            const fillValue = this.getFillValue();
            // if it's a Color object, get the corresponding CSS
            // 'transparent' will make us invisible if the fill is null
            // @ts-expect-error - toCSS checks for color, left for performance
            return fillValue ? (fillValue.toCSS ? fillValue.toCSS() : fillValue) : 'transparent';
        }
        /**
         * If applicable, returns the CSS color for the stroke.
         */
        getSimpleCSSStroke() {
            const strokeValue = this.getStrokeValue();
            // if it's a Color object, get the corresponding CSS
            // 'transparent' will make us invisible if the fill is null
            // @ts-expect-error - toCSS checks for color, left for performance
            return strokeValue ? (strokeValue.toCSS ? strokeValue.toCSS() : strokeValue) : 'transparent';
        }
        /**
         * Returns the fill-specific property string for use with toString(). (scenery-internal)
         *
         * @param spaces - Whitespace to add
         * @param result
         */
        appendFillablePropString(spaces, result) {
            if (this._fill) {
                if (result) {
                    result += ',\n';
                }
                if (typeof this.getFillValue() === 'string') {
                    result += `${spaces}fill: '${this.getFillValue()}'`;
                }
                else {
                    result += `${spaces}fill: ${this.getFillValue()}`;
                }
            }
            return result;
        }
        /**
         * Returns the stroke-specific property string for use with toString(). (scenery-internal)
         *
         * @param spaces - Whitespace to add
         * @param result
         */
        appendStrokablePropString(spaces, result) {
            function addProp(key, value, nowrap) {
                if (result) {
                    result += ',\n';
                }
                if (!nowrap && typeof value === 'string') {
                    result += `${spaces + key}: '${value}'`;
                }
                else {
                    result += `${spaces + key}: ${value}`;
                }
            }
            if (this._stroke) {
                const defaultStyles = new LineStyles();
                const strokeValue = this.getStrokeValue();
                if (typeof strokeValue === 'string') {
                    addProp('stroke', strokeValue);
                }
                else {
                    addProp('stroke', strokeValue ? strokeValue.toString() : 'null', true);
                }
                _.each(['lineWidth', 'lineCap', 'miterLimit', 'lineJoin', 'lineDashOffset'], prop => {
                    // @ts-expect-error
                    if (this[prop] !== defaultStyles[prop]) {
                        // @ts-expect-error
                        addProp(prop, this[prop]);
                    }
                });
                if (this.lineDash.length) {
                    addProp('lineDash', JSON.stringify(this.lineDash), true);
                }
            }
            return result;
        }
        /**
         * Determines the default allowed renderers (returned via the Renderer bitmask) that are allowed, given the
         * current fill options. (scenery-internal)
         *
         * This will be used for all types that directly mix in Paintable (i.e. Path and Text), but may be overridden
         * by subtypes.
         *
         * @returns - Renderer bitmask, see Renderer for details
         */
        getFillRendererBitmask() {
            let bitmask = 0;
            // Safari 5 has buggy issues with SVG gradients
            if (!(isSafari5 && this._fill instanceof Gradient)) {
                bitmask |= Renderer.bitmaskSVG;
            }
            // we always have Canvas support?
            bitmask |= Renderer.bitmaskCanvas;
            if (!this.hasFill()) {
                // if there is no fill, it is supported by DOM and WebGL
                bitmask |= Renderer.bitmaskDOM;
                bitmask |= Renderer.bitmaskWebGL;
            }
            else if (this._fill instanceof Pattern) {
                // no pattern support for DOM or WebGL (for now!)
            }
            else if (this._fill instanceof Gradient) {
                // no gradient support for DOM or WebGL (for now!)
            }
            else {
                // solid fills always supported for DOM and WebGL
                bitmask |= Renderer.bitmaskDOM;
                bitmask |= Renderer.bitmaskWebGL;
            }
            return bitmask;
        }
        /**
         * Determines the default allowed renderers (returned via the Renderer bitmask) that are allowed, given the
         * current stroke options. (scenery-internal)
         *
         * This will be used for all types that directly mix in Paintable (i.e. Path and Text), but may be overridden
         * by subtypes.
         *
         * @returns - Renderer bitmask, see Renderer for details
         */
        getStrokeRendererBitmask() {
            let bitmask = 0;
            bitmask |= Renderer.bitmaskCanvas;
            // always have SVG support (for now?)
            bitmask |= Renderer.bitmaskSVG;
            if (!this.hasStroke()) {
                // allow DOM support if there is no stroke (since the fill will determine what is available)
                bitmask |= Renderer.bitmaskDOM;
                bitmask |= Renderer.bitmaskWebGL;
            }
            return bitmask;
        }
        /**
         * Invalidates our current fill, triggering recomputation of anything that depended on the old fill's value
         */
        invalidateFill() {
            this.invalidateSupportedRenderers();
            const stateLen = this._drawables.length;
            for (let i = 0; i < stateLen; i++) {
                this._drawables[i].markDirtyFill();
            }
        }
        /**
         * Invalidates our current stroke, triggering recomputation of anything that depended on the old stroke's value
         */
        invalidateStroke() {
            this.invalidateSupportedRenderers();
            const stateLen = this._drawables.length;
            for (let i = 0; i < stateLen; i++) {
                this._drawables[i].markDirtyStroke();
            }
        }
    };
});
scenery.register('Paintable', Paintable);
// @ts-expect-error
Paintable.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
export { Paintable as default, PAINTABLE_DRAWABLE_MARK_FLAGS, PAINTABLE_OPTION_KEYS, DEFAULT_OPTIONS, DEFAULT_OPTIONS as PAINTABLE_DEFAULT_OPTIONS };
//# sourceMappingURL=Paintable.js.map