Make style-path helpers generic over fill & stroke

This commit is contained in:
adroitwhiz 2020-04-15 09:51:07 -04:00
parent dc430a0111
commit 1ecab99cfb
6 changed files with 215 additions and 120 deletions

View file

@ -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;

View file

@ -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);
}

View file

@ -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) {

View file

@ -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<paper.Item>} 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,

View file

@ -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.

View file

@ -14,7 +14,7 @@ const reducer = makeColorStyleReducer({
defaultColor: DEFAULT_COLOR,
selectionPrimaryColorKey: 'fillColor',
selectionSecondaryColorKey: 'fillColor2',
selectionGradientTypeKey: 'gradientType'
selectionGradientTypeKey: 'fillGradientType'
});
// Action creators ==================================