diff --git a/src/containers/fill-color-indicator.jsx b/src/containers/fill-color-indicator.jsx index 8ff198ef..43291462 100644 --- a/src/containers/fill-color-indicator.jsx +++ b/src/containers/fill-color-indicator.jsx @@ -16,7 +16,7 @@ import {isBitmap} from '../lib/format'; import GradientTypes from '../lib/gradient-types'; import FillColorIndicatorComponent from '../components/fill-color-indicator.jsx'; -import {applyFillColorToSelection, +import {applyColorToSelection, applyGradientTypeToSelection, getRotatedColor, swapColorsInSelection, @@ -45,11 +45,12 @@ class FillColorIndicator extends React.Component { } handleChangeFillColor (newColor) { // Apply color and update redux, but do not update svg until picker closes. - const isDifferent = applyFillColorToSelection( + const isDifferent = applyColorToSelection( newColor, this.props.colorIndex, this.props.gradientType === GradientTypes.SOLID, isBitmap(this.props.format), + false, // applyToStroke this.props.textEditTarget); this._hasChanged = this._hasChanged || isDifferent; this.props.onChangeFillColor(newColor, this.props.colorIndex); @@ -59,6 +60,7 @@ class FillColorIndicator extends React.Component { const isDifferent = applyGradientTypeToSelection( gradientType, isBitmap(this.props.format), + false, // applyToStroke this.props.textEditTarget); this._hasChanged = this._hasChanged || isDifferent; const hasSelectedItems = getSelectedLeafItems().length > 0; @@ -92,6 +94,7 @@ class FillColorIndicator extends React.Component { if (getSelectedLeafItems().length) { const isDifferent = swapColorsInSelection( isBitmap(this.props.format), + false, // applyToStroke this.props.textEditTarget); this.props.setSelectedItems(); this._hasChanged = this._hasChanged || isDifferent; diff --git a/src/containers/stroke-color-indicator.jsx b/src/containers/stroke-color-indicator.jsx index ea78e0cb..57f68e3e 100644 --- a/src/containers/stroke-color-indicator.jsx +++ b/src/containers/stroke-color-indicator.jsx @@ -10,7 +10,7 @@ import Formats from '../lib/format'; import {isBitmap} from '../lib/format'; import StrokeColorIndicatorComponent from '../components/stroke-color-indicator.jsx'; -import {applyStrokeColorToSelection, applyStrokeWidthToSelection} from '../helper/style-path'; +import {applyColorToSelection, applyStrokeWidthToSelection} from '../helper/style-path'; class StrokeColorIndicator extends React.Component { constructor (props) { @@ -40,8 +40,13 @@ class StrokeColorIndicator extends React.Component { this.props.onChangeStrokeWidth(0); } // Apply color and update redux, but do not update svg until picker closes. - this._hasChanged = - applyStrokeColorToSelection(newColor, isBitmap(this.props.format), this.props.textEditTarget) || + this._hasChanged = applyColorToSelection( + newColor, + 0, // colorIndex, + true, // isSolidGradient + isBitmap(this.props.format), + true, // applyToStroke + this.props.textEditTarget) || this._hasChanged; this.props.onChangeStrokeColor(newColor); } diff --git a/src/containers/stroke-width-indicator.jsx b/src/containers/stroke-width-indicator.jsx index d224f44d..16412677 100644 --- a/src/containers/stroke-width-indicator.jsx +++ b/src/containers/stroke-width-indicator.jsx @@ -7,7 +7,7 @@ import {changeStrokeColor} from '../reducers/stroke-color'; import {changeStrokeWidth} from '../reducers/stroke-width'; import StrokeWidthIndicatorComponent from '../components/stroke-width-indicator.jsx'; import {getSelectedLeafItems} from '../helper/selection'; -import {applyStrokeColorToSelection, applyStrokeWidthToSelection, getColorsFromSelection, MIXED} +import {applyColorToSelection, applyStrokeWidthToSelection, getColorsFromSelection, MIXED} from '../helper/style-path'; import Modes from '../lib/modes'; import Formats from '../lib/format'; @@ -25,7 +25,13 @@ class StrokeWidthIndicator extends React.Component { if ((!this.props.strokeWidth || this.props.strokeWidth === 0) && newWidth > 0) { let currentColor = getColorsFromSelection(getSelectedLeafItems(), isBitmap(this.props.format)).strokeColor; if (currentColor === null) { - changed = applyStrokeColorToSelection('#000', isBitmap(this.props.format), this.props.textEditTarget) || + changed = applyColorToSelection( + '#000', + 0, // colorIndex, + true, // isSolidGradient + isBitmap(this.props.format), + true, // applyToStroke + this.props.textEditTarget) || changed; currentColor = '#000'; } else if (currentColor !== MIXED) { diff --git a/src/helper/style-path.js b/src/helper/style-path.js index 1a2a394a..33cadf46 100644 --- a/src/helper/style-path.js +++ b/src/helper/style-path.js @@ -7,13 +7,13 @@ import GradientTypes from '../lib/gradient-types'; import parseColor from 'parse-color'; import {DEFAULT_COLOR} from '../reducers/fill-style'; import {isCompoundPathChild} from '../helper/compound-path'; +import log from '../log/log'; const MIXED = 'scratch-paint/style-path/mixed'; // Check if the item color matches the incoming color. If the item color is a gradient, we assume // that the incoming color never matches, since we don't support gradients yet. const _colorMatch = function (itemColor, incomingColor) { - // @todo colorMatch should not be called with gradients as arguments once stroke gradients are supported if (itemColor && itemColor.type === 'gradient') return false; // Either both are null or both are the same color when converted to CSS. return (!itemColor && !incomingColor) || @@ -62,7 +62,8 @@ const getRotatedColor = function (firstColor) { * @param {?string} color2 CSS string, or null for transparent * @param {GradientType} gradientType gradient type * @param {paper.Rectangle} bounds Bounds of the object - * @param {paper.Point} radialCenter Where the center of a radial gradient should be, if the gradient is radial + * @param {?paper.Point} [radialCenter] Where the center of a radial gradient should be, if the gradient is radial. + * Defaults to center of bounds. * @return {paper.Color} Color object with gradient, may be null or color string if the gradient type is solid */ const createGradientObject = function (color1, color2, gradientType, bounds, radialCenter) { @@ -74,7 +75,7 @@ const createGradientObject = function (color1, color2, gradientType, bounds, rad color2 = getColorStringForTransparent(color1); } const halfLongestDimension = Math.max(bounds.width, bounds.height) / 2; - const start = gradientType === GradientTypes.RADIAL ? radialCenter : + const start = gradientType === GradientTypes.RADIAL ? (radialCenter || bounds.center) : gradientType === GradientTypes.VERTICAL ? bounds.topCenter : gradientType === GradientTypes.HORIZONTAL ? bounds.leftCenter : null; @@ -93,17 +94,25 @@ const createGradientObject = function (color1, color2, gradientType, bounds, rad }; /** - * Called when setting fill color + * Called when setting an item's color * @param {string} colorString color, css format, or null if completely transparent * @param {number} colorIndex index of color being changed * @param {boolean} isSolidGradient True if is solid gradient. Sometimes the item has a gradient but the color * picker is set to a solid gradient. This happens when a mix of colors and gradient types is selected. * When changing the color in this case, the solid gradient should override the existing gradient on the item. - * @param {?boolean} bitmapMode True if the fill color is being set in bitmap mode + * @param {?boolean} bitmapMode True if the color is being set in bitmap mode + * @param {?boolean} applyToStroke True if changing the selection's stroke, false if changing its fill. * @param {?string} textEditTargetId paper.Item.id of text editing target, if any * @return {boolean} Whether the color application actually changed visibly. */ -const applyFillColorToSelection = function (colorString, colorIndex, isSolidGradient, bitmapMode, textEditTargetId) { +const applyColorToSelection = function ( + colorString, + colorIndex, + isSolidGradient, + bitmapMode, + applyToStroke, + textEditTargetId +) { const items = _getColorStateListeners(textEditTargetId); let changed = false; for (let item of items) { @@ -112,40 +121,51 @@ const applyFillColorToSelection = function (colorString, colorIndex, isSolidGrad } // In bitmap mode, fill color applies to the stroke if there is a stroke - if (bitmapMode && item.strokeColor !== null && item.strokeWidth) { + if ( + bitmapMode && + !applyToStroke && + item.strokeColor !== null && + item.strokeWidth + ) { if (!_colorMatch(item.strokeColor, colorString)) { changed = true; item.strokeColor = colorString; } - } else if (isSolidGradient || !item.fillColor || !item.fillColor.gradient || - !item.fillColor.gradient.stops.length === 2) { + continue; + } + + const itemColorProp = applyToStroke ? 'strokeColor' : 'fillColor'; + const itemColor = item[itemColorProp]; + + if (isSolidGradient || !itemColor || !itemColor.gradient || + !itemColor.gradient.stops.length === 2) { // Applying a solid color - if (!_colorMatch(item.fillColor, colorString)) { + if (!_colorMatch(itemColor, colorString)) { changed = true; if (isPointTextItem(item) && !colorString) { // Allows transparent text to be hit - item.fillColor = 'rgba(0,0,0,0)'; + item[itemColorProp] = 'rgba(0,0,0,0)'; } else { - item.fillColor = colorString; + item[itemColorProp] = colorString; } } - } else if (!_colorMatch(item.fillColor.gradient.stops[colorIndex].color, colorString)) { + } else if (!_colorMatch(itemColor.gradient.stops[colorIndex].color, colorString)) { // Changing one color of an existing gradient changed = true; const otherIndex = colorIndex === 0 ? 1 : 0; if (colorString === null) { - colorString = getColorStringForTransparent(item.fillColor.gradient.stops[otherIndex].color.toCSS()); + colorString = getColorStringForTransparent(itemColor.gradient.stops[otherIndex].color.toCSS()); } const colors = [0, 0]; colors[colorIndex] = colorString; // If the other color is transparent, its RGB values need to be adjusted for the gradient to be smooth - if (item.fillColor.gradient.stops[otherIndex].color.alpha === 0) { + if (itemColor.gradient.stops[otherIndex].color.alpha === 0) { colors[otherIndex] = getColorStringForTransparent(colorString); } else { - colors[otherIndex] = item.fillColor.gradient.stops[otherIndex].color.toCSS(); + colors[otherIndex] = itemColor.gradient.stops[otherIndex].color.toCSS(); } // There seems to be a bug where setting colors on stops doesn't always update the view, so set gradient. - item.fillColor.gradient = {stops: colors, radial: item.fillColor.gradient.radial}; + itemColor.gradient = {stops: colors, radial: itemColor.gradient.radial}; } } return changed; @@ -154,10 +174,13 @@ const applyFillColorToSelection = function (colorString, colorIndex, isSolidGrad /** * Called to swap gradient colors * @param {?boolean} bitmapMode True if the fill color is being set in bitmap mode + * @param {?boolean} applyToStroke True if changing the selection's stroke, false if changing its fill. * @param {?string} textEditTargetId paper.Item.id of text editing target, if any * @return {boolean} Whether the color application actually changed visibly. */ -const swapColorsInSelection = function (bitmapMode, textEditTargetId) { +const swapColorsInSelection = function (bitmapMode, applyToStroke, textEditTargetId) { + if (bitmapMode) return; // @todo + const items = _getColorStateListeners(textEditTargetId); let changed = false; for (const item of items) { @@ -166,21 +189,19 @@ const swapColorsInSelection = function (bitmapMode, textEditTargetId) { // that would leave us right where we started. if (isCompoundPathChild(item)) continue; - if (bitmapMode) { - // @todo - return; - } else if (!item.fillColor || !item.fillColor.gradient || !item.fillColor.gradient.stops.length === 2) { + const itemColor = applyToStroke ? item.strokeColor : item.fillColor; + if (!itemColor || !itemColor.gradient || !itemColor.gradient.stops.length === 2) { // Only one color; nothing to swap continue; - } else if (!item.fillColor.gradient.stops[0].color.equals(item.fillColor.gradient.stops[1].color)) { + } else if (!itemColor.gradient.stops[0].color.equals(itemColor.gradient.stops[1].color)) { // Changing one color of an existing gradient changed = true; const colors = [ - item.fillColor.gradient.stops[1].color.toCSS(), - item.fillColor.gradient.stops[0].color.toCSS() + itemColor.gradient.stops[1].color.toCSS(), + itemColor.gradient.stops[0].color.toCSS() ]; // There seems to be a bug where setting colors on stops doesn't always update the view, so set gradient. - item.fillColor.gradient = {stops: colors, radial: item.fillColor.gradient.radial}; + itemColor.gradient = {stops: colors, radial: itemColor.gradient.radial}; } } return changed; @@ -190,10 +211,11 @@ const swapColorsInSelection = function (bitmapMode, textEditTargetId) { * Called when setting gradient type * @param {GradientType} gradientType gradient type * @param {?boolean} bitmapMode True if the fill color is being set in bitmap mode + * @param {boolean} applyToStroke True if changing the selection's stroke, false if changing its fill. * @param {?string} textEditTargetId paper.Item.id of text editing target, if any * @return {boolean} Whether the color application actually changed visibly. */ -const applyGradientTypeToSelection = function (gradientType, bitmapMode, textEditTargetId) { +const applyGradientTypeToSelection = function (gradientType, bitmapMode, applyToStroke, textEditTargetId) { const items = _getColorStateListeners(textEditTargetId); let changed = false; for (let item of items) { @@ -201,40 +223,45 @@ const applyGradientTypeToSelection = function (gradientType, bitmapMode, textEdi item = item.parent; } + const itemColorProp = applyToStroke ? 'strokeColor' : 'fillColor'; + const itemColor = item[itemColorProp]; + + const hasGradient = itemColor && itemColor.gradient; + let itemColor1; - if (item.fillColor === null || item.fillColor.alpha === 0) { + if (itemColor === null || itemColor.alpha === 0) { // Transparent itemColor1 = null; - } else if (!item.fillColor.gradient) { + } else if (!hasGradient) { // Solid color - itemColor1 = item.fillColor.toCSS(); - } else if (!item.fillColor.gradient.stops[0] || item.fillColor.gradient.stops[0].color.alpha === 0) { + itemColor1 = itemColor.toCSS(); + } else if (!itemColor.gradient.stops[0] || itemColor.gradient.stops[0].color.alpha === 0) { // Gradient where first color is transparent itemColor1 = null; } else { // Gradient where first color is not transparent - itemColor1 = item.fillColor.gradient.stops[0].color.toCSS(); + itemColor1 = itemColor.gradient.stops[0].color.toCSS(); } let itemColor2; - if (!item.fillColor || !item.fillColor.gradient || !item.fillColor.gradient.stops[1]) { + if (!hasGradient || !itemColor.gradient.stops[1]) { // If item color is solid or a gradient that has no 2nd color, set the 2nd color based on the first color itemColor2 = getRotatedColor(itemColor1); - } else if (item.fillColor.gradient.stops[1].color.alpha === 0) { + } else if (itemColor.gradient.stops[1].color.alpha === 0) { // Gradient has 2nd color which is transparent itemColor2 = null; } else { // Gradient has 2nd color which is not transparent - itemColor2 = item.fillColor.gradient.stops[1].color.toCSS(); + itemColor2 = itemColor.gradient.stops[1].color.toCSS(); } if (bitmapMode) { // @todo Add when we apply gradients to selections in bitmap mode continue; } else if (gradientType === GradientTypes.SOLID) { - if (item.fillColor && item.fillColor.gradient) { + if (itemColor && itemColor.gradient) { changed = true; - item.fillColor = itemColor1; + item[itemColorProp] = itemColor1; } continue; } @@ -245,12 +272,15 @@ const applyGradientTypeToSelection = function (gradientType, bitmapMode, textEdi if (itemColor2 === null) { itemColor2 = getColorStringForTransparent(itemColor1); } - if (gradientType === GradientTypes.RADIAL) { - const hasRadialGradient = item.fillColor && item.fillColor.gradient && item.fillColor.gradient.radial; + + switch (gradientType) { + case GradientTypes.RADIAL: { + const hasRadialGradient = hasGradient && itemColor.gradient.radial; if (!hasRadialGradient) { changed = true; const halfLongestDimension = Math.max(item.bounds.width, item.bounds.height) / 2; - item.fillColor = { + + item[itemColorProp] = { gradient: { stops: [itemColor1, itemColor2], radial: true @@ -259,13 +289,15 @@ const applyGradientTypeToSelection = function (gradientType, bitmapMode, textEdi destination: item.position.add(new paper.Point(halfLongestDimension, 0)) }; } - } else if (gradientType === GradientTypes.HORIZONTAL) { - const hasHorizontalGradient = item.fillColor && item.fillColor.gradient && - !item.fillColor.gradient.radial && - Math.abs(item.fillColor.origin.y - item.fillColor.destination.y) < 1e-8; + break; + } + case GradientTypes.HORIZONTAL: { + const hasHorizontalGradient = hasGradient && !itemColor.gradient.radial && + Math.abs(itemColor.origin.y - itemColor.destination.y) < 1e-8; if (!hasHorizontalGradient) { changed = true; - item.fillColor = { + + item[itemColorProp] = { gradient: { stops: [itemColor1, itemColor2] }, @@ -273,12 +305,15 @@ const applyGradientTypeToSelection = function (gradientType, bitmapMode, textEdi destination: item.bounds.rightCenter }; } - } else if (gradientType === GradientTypes.VERTICAL) { - const hasVerticalGradient = item.fillColor && item.fillColor.gradient && !item.fillColor.gradient.radial && - Math.abs(item.fillColor.origin.x - item.fillColor.destination.x) < 1e-8; + break; + } + case GradientTypes.VERTICAL: { + const hasVerticalGradient = hasGradient && !itemColor.gradient.radial && + Math.abs(itemColor.origin.x - itemColor.destination.x) < 1e-8; if (!hasVerticalGradient) { changed = true; - item.fillColor = { + + item[itemColorProp] = { gradient: { stops: [itemColor1, itemColor2] }, @@ -286,31 +321,8 @@ const applyGradientTypeToSelection = function (gradientType, bitmapMode, textEdi destination: item.bounds.bottomCenter }; } + break; } - } - return changed; -}; - -/** - * Called when setting stroke color - * @param {string} colorString New color, css format - * @param {?boolean} bitmapMode True if the stroke color is being set in bitmap mode - * @param {?string} textEditTargetId paper.Item.id of text editing target, if any - * @return {boolean} Whether the color application actually changed visibly. - */ -const applyStrokeColorToSelection = function (colorString, bitmapMode, textEditTargetId) { - // Bitmap mode doesn't have stroke color - if (bitmapMode) return false; - - const items = _getColorStateListeners(textEditTargetId); - let changed = false; - for (let item of items) { - if (item.parent instanceof paper.CompoundPath) { - item = item.parent; - } - if (!_colorMatch(item.strokeColor, colorString)) { - changed = true; - item.strokeColor = colorString; } } return changed; @@ -339,6 +351,33 @@ const applyStrokeWidthToSelection = function (value, textEditTargetId) { return changed; }; +const _colorStateFromGradient = gradient => { + const colorState = {}; + // Scratch only recognizes 2 color gradients + if (gradient.stops.length === 2) { + if (gradient.radial) { + colorState.gradientType = GradientTypes.RADIAL; + } else { + // Always use horizontal for linear gradients, since horizontal and vertical gradients + // are the same with rotation. We don't want to show MIXED just because anything is rotated. + colorState.gradientType = GradientTypes.HORIZONTAL; + } + colorState.primary = gradient.stops[0].color.alpha === 0 ? + null : + gradient.stops[0].color.toCSS(); + colorState.secondary = gradient.stops[1].color.alpha === 0 ? + null : + gradient.stops[1].color.toCSS(); + } else { + if (gradient.stops.length < 2) log.warn(`Gradient has ${gradient.stops.length} stop(s)`); + + colorState.primary = MIXED; + colorState.secondary = MIXED; + } + + return colorState; +}; + /** * Get state of colors and stroke width for selection * @param {!Array} selectedItems Selected paper items @@ -349,12 +388,15 @@ const applyStrokeWidthToSelection = function (value, textEditTargetId) { * Thickness is line thickness, used in the bitmap editor */ const getColorsFromSelection = function (selectedItems, bitmapMode) { + // TODO: DRY out this code let selectionFillColorString; let selectionFillColor2String; let selectionStrokeColorString; + let selectionStrokeColor2String; let selectionStrokeWidth; let selectionThickness; - let selectionGradientType; + let selectionFillGradientType; + let selectionStrokeGradientType; let firstChild = true; for (let item of selectedItems) { @@ -365,7 +407,9 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) { let itemFillColorString; let itemFillColor2String; let itemStrokeColorString; - let itemGradientType = GradientTypes.SOLID; + let itemStrokeColor2String; + let itemFillGradientType = GradientTypes.SOLID; + let itemStrokeGradientType = GradientTypes.SOLID; if (!isGroup(item)) { if (item.fillColor) { @@ -373,25 +417,10 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) { if (isPointTextItem(item) && item.fillColor.alpha === 0) { itemFillColorString = null; } else if (item.fillColor.type === 'gradient') { - // Scratch only recognizes 2 color gradients - if (item.fillColor.gradient.stops.length === 2) { - if (item.fillColor.gradient.radial) { - itemGradientType = GradientTypes.RADIAL; - } else { - // Always use horizontal for linear gradients, since horizontal and vertical gradients - // are the same with rotation. We don't want to show MIXED just because anything is rotated. - itemGradientType = GradientTypes.HORIZONTAL; - } - itemFillColorString = item.fillColor.gradient.stops[0].color.alpha === 0 ? - null : - item.fillColor.gradient.stops[0].color.toCSS(); - itemFillColor2String = item.fillColor.gradient.stops[1].color.alpha === 0 ? - null : - item.fillColor.gradient.stops[1].color.toCSS(); - } else { - itemFillColorString = MIXED; - itemFillColor2String = MIXED; - } + const {primary, secondary, gradientType} = _colorStateFromGradient(item.fillColor.gradient); + itemFillColorString = primary; + itemFillColor2String = secondary; + itemFillGradientType = gradientType; } else { itemFillColorString = item.fillColor.alpha === 0 ? null : @@ -399,15 +428,34 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) { } } if (item.strokeColor) { - // Stroke color is fill color in bitmap - if (bitmapMode) { - itemFillColorString = item.strokeColor.toCSS(); - } else if (item.strokeColor.type === 'gradient') { - itemStrokeColorString = MIXED; + + if (item.strokeColor.type === 'gradient') { + const {primary, secondary, gradientType} = _colorStateFromGradient(item.strokeColor.gradient); + const strokeColorString = primary; + const strokeColor2String = secondary; + const strokeGradientType = gradientType; + + // Stroke color is fill color in bitmap + if (bitmapMode) { + itemFillColorString = strokeColorString; + itemFillColor2String = strokeColor2String; + itemFillGradientType = strokeGradientType; + } else { + itemStrokeColorString = strokeColorString; + itemStrokeColor2String = strokeColor2String; + itemStrokeGradientType = strokeGradientType; + } } else { - itemStrokeColorString = item.strokeColor.alpha === 0 || !item.strokeWidth ? + const strokeColorString = item.strokeColor.alpha === 0 || !item.strokeWidth ? null : item.strokeColor.toCSS(); + + // Stroke color is fill color in bitmap + if (bitmapMode) { + itemFillColorString = strokeColorString; + } else { + itemStrokeColorString = strokeColorString; + } } } else { itemStrokeColorString = null; @@ -418,7 +466,9 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) { selectionFillColorString = itemFillColorString; selectionFillColor2String = itemFillColor2String; selectionStrokeColorString = itemStrokeColorString; - selectionGradientType = itemGradientType; + selectionStrokeColor2String = itemStrokeColor2String; + selectionFillGradientType = itemFillGradientType; + selectionStrokeGradientType = itemStrokeGradientType; selectionStrokeWidth = itemStrokeColorString ? item.strokeWidth : 0; if (item.strokeWidth && item.data && item.data.zoomLevel) { selectionThickness = item.strokeWidth / item.data.zoomLevel; @@ -430,14 +480,22 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) { if (itemFillColor2String !== selectionFillColor2String) { selectionFillColor2String = MIXED; } - if (itemGradientType !== selectionGradientType) { - selectionGradientType = GradientTypes.SOLID; + if (itemFillGradientType !== selectionFillGradientType) { + selectionFillGradientType = GradientTypes.SOLID; selectionFillColorString = MIXED; selectionFillColor2String = MIXED; } + if (itemStrokeGradientType !== selectionStrokeGradientType) { + selectionStrokeGradientType = GradientTypes.SOLID; + selectionStrokeColorString = MIXED; + selectionStrokeColor2String = MIXED; + } if (itemStrokeColorString !== selectionStrokeColorString) { selectionStrokeColorString = MIXED; } + if (itemStrokeColor2String !== selectionStrokeColor2String) { + selectionStrokeColor2String = MIXED; + } const itemStrokeWidth = itemStrokeColorString ? item.strokeWidth : 0; if (selectionStrokeWidth !== itemStrokeWidth) { selectionStrokeWidth = null; @@ -445,27 +503,50 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) { } } // Convert selection gradient type from horizontal to vertical if first item is exactly vertical - if (selectedItems && selectedItems.length && selectionGradientType !== GradientTypes.SOLID) { + // This is because up to this point, we assume all non-radial gradients are horizontal + // Otherwise, if there were a mix of horizontal/vertical gradient types in the selection, they would show as MIXED + // whereas we want them to show as horizontal (or vertical if the first item is vertical) + if (selectedItems && selectedItems.length) { let firstItem = selectedItems[0]; if (firstItem.parent instanceof paper.CompoundPath) firstItem = firstItem.parent; - const direction = firstItem.fillColor.destination.subtract(firstItem.fillColor.origin); - if (Math.abs(direction.angle) === 90) { - selectionGradientType = GradientTypes.VERTICAL; + + if (selectionFillGradientType !== GradientTypes.SOLID) { + // Stroke color is fill color in bitmap if fill color is missing + // TODO: this whole "treat horizontal/vertical gradients specially" logic is janky; refactor at some point + const firstItemColor = (bitmapMode && firstItem.strokeColor) ? firstItem.strokeColor : firstItem.fillColor; + const direction = firstItemColor.destination.subtract(firstItemColor.origin); + if (Math.abs(direction.angle) === 90) { + selectionFillGradientType = GradientTypes.VERTICAL; + } + } + + if (selectionStrokeGradientType !== GradientTypes.SOLID) { + const direction = firstItem.strokeColor.destination.subtract(firstItem.strokeColor.origin); + if (Math.abs(direction.angle) === 90) { + selectionStrokeGradientType = GradientTypes.VERTICAL; + } } } if (bitmapMode) { return { fillColor: selectionFillColorString ? selectionFillColorString : null, fillColor2: selectionFillColor2String ? selectionFillColor2String : null, - gradientType: selectionGradientType, + fillGradientType: selectionFillGradientType, thickness: selectionThickness }; } + + // Treat stroke gradients as MIXED + // TODO: remove this once stroke gradients are supported + if (selectionStrokeGradientType !== GradientTypes.SOLID) selectionStrokeColorString = MIXED; + return { fillColor: selectionFillColorString ? selectionFillColorString : null, fillColor2: selectionFillColor2String ? selectionFillColor2String : null, - gradientType: selectionGradientType, + fillGradientType: selectionFillGradientType, strokeColor: selectionStrokeColorString ? selectionStrokeColorString : null, + strokeColor2: selectionStrokeColor2String ? selectionStrokeColor2String : null, + strokeGradientType: selectionStrokeGradientType, strokeWidth: selectionStrokeWidth || (selectionStrokeWidth === null) ? selectionStrokeWidth : 0 }; }; @@ -510,9 +591,8 @@ const styleShape = function (path, options) { }; export { - applyFillColorToSelection, + applyColorToSelection, applyGradientTypeToSelection, - applyStrokeColorToSelection, applyStrokeWidthToSelection, createGradientObject, getColorsFromSelection, diff --git a/src/helper/tools/text-tool.js b/src/helper/tools/text-tool.js index d7188846..bba8bda7 100644 --- a/src/helper/tools/text-tool.js +++ b/src/helper/tools/text-tool.js @@ -241,7 +241,8 @@ class TextTool extends paper.Tool { content: '', font: this.font, fontSize: 40, - // TODO: style using gradient? + // TODO: style using gradient + // https://github.com/LLK/scratch-paint/issues/1164 fillColor: this.colorState.fillColor.primary, // Default leading for both the HTML text area and paper.PointText // is 120%, but for some reason they are slightly off from each other. diff --git a/src/reducers/fill-style.js b/src/reducers/fill-style.js index b98b73f8..98046c49 100644 --- a/src/reducers/fill-style.js +++ b/src/reducers/fill-style.js @@ -14,7 +14,7 @@ const reducer = makeColorStyleReducer({ defaultColor: DEFAULT_COLOR, selectionPrimaryColorKey: 'fillColor', selectionSecondaryColorKey: 'fillColor2', - selectionGradientTypeKey: 'gradientType' + selectionGradientTypeKey: 'fillGradientType' }); // Action creators ==================================