2017-10-12 18:35:30 -04:00
|
|
|
import paper from '@scratch/paper';
|
2017-10-11 11:32:51 -04:00
|
|
|
import {getSelectedLeafItems} from './selection';
|
2017-10-02 15:25:04 -04:00
|
|
|
import {isPGTextItem, isPointTextItem} from './item';
|
|
|
|
import {isGroup} from './group';
|
2018-03-16 11:36:06 -04:00
|
|
|
import {getItems} from './selection';
|
2017-10-02 15:25:04 -04:00
|
|
|
|
|
|
|
const MIXED = 'scratch-paint/style-path/mixed';
|
|
|
|
|
2017-12-19 13:15:31 -05:00
|
|
|
// 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 check whether the gradient has changed when we support gradients
|
|
|
|
if (itemColor && itemColor.type === 'gradient') return false;
|
|
|
|
// Either both are null or both are the same color when converted to CSS.
|
|
|
|
return (!itemColor && !incomingColor) ||
|
|
|
|
(itemColor && incomingColor && itemColor.toCSS() === new paper.Color(incomingColor).toCSS());
|
2017-12-19 11:59:54 -05:00
|
|
|
};
|
|
|
|
|
2018-03-16 11:36:06 -04:00
|
|
|
// Selected items and currently active text edit items respond to color changes.
|
|
|
|
const _getColorStateListeners = function (textEditTargetId) {
|
|
|
|
const items = getSelectedLeafItems();
|
|
|
|
if (textEditTargetId) {
|
|
|
|
const matches = getItems({
|
|
|
|
match: item => item.id === textEditTargetId
|
|
|
|
});
|
|
|
|
if (matches.length) {
|
|
|
|
items.push(matches[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return items;
|
|
|
|
};
|
|
|
|
|
2017-10-02 15:25:04 -04:00
|
|
|
/**
|
|
|
|
* Called when setting fill color
|
|
|
|
* @param {string} colorString New color, css format
|
2018-03-16 11:36:06 -04:00
|
|
|
* @param {?string} textEditTargetId paper.Item.id of text editing target, if any
|
2017-10-30 11:13:29 -04:00
|
|
|
* @return {boolean} Whether the color application actually changed visibly.
|
2017-10-02 15:25:04 -04:00
|
|
|
*/
|
2018-03-16 11:36:06 -04:00
|
|
|
const applyFillColorToSelection = function (colorString, textEditTargetId) {
|
|
|
|
const items = _getColorStateListeners(textEditTargetId);
|
2017-10-05 18:12:22 -04:00
|
|
|
let changed = false;
|
2017-12-18 12:02:35 -05:00
|
|
|
for (let item of items) {
|
2018-03-16 11:36:06 -04:00
|
|
|
if (isPointTextItem(item) && !colorString) {
|
|
|
|
colorString = 'rgba(0,0,0,0)';
|
|
|
|
} else if (item.parent instanceof paper.CompoundPath) {
|
2017-12-18 12:02:35 -05:00
|
|
|
item = item.parent;
|
|
|
|
}
|
2018-03-16 11:36:06 -04:00
|
|
|
if (!_colorMatch(item.fillColor, colorString)) {
|
|
|
|
changed = true;
|
|
|
|
item.fillColor = colorString;
|
2017-10-02 15:25:04 -04:00
|
|
|
}
|
|
|
|
}
|
2017-10-29 14:10:43 -04:00
|
|
|
return changed;
|
2017-10-02 15:25:04 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when setting stroke color
|
|
|
|
* @param {string} colorString New color, css format
|
2018-03-16 11:36:06 -04:00
|
|
|
* @param {?string} textEditTargetId paper.Item.id of text editing target, if any
|
2017-10-30 11:13:29 -04:00
|
|
|
* @return {boolean} Whether the color application actually changed visibly.
|
2017-10-02 15:25:04 -04:00
|
|
|
*/
|
2018-03-16 11:36:06 -04:00
|
|
|
const applyStrokeColorToSelection = function (colorString, textEditTargetId) {
|
|
|
|
const items = _getColorStateListeners(textEditTargetId);
|
2017-10-05 18:12:22 -04:00
|
|
|
let changed = false;
|
2017-12-18 12:02:35 -05:00
|
|
|
for (let item of items) {
|
|
|
|
if (item.parent instanceof paper.CompoundPath) {
|
|
|
|
item = item.parent;
|
|
|
|
}
|
2017-10-02 15:25:04 -04:00
|
|
|
if (isPGTextItem(item)) {
|
|
|
|
if (item.children) {
|
|
|
|
for (const child of item.children) {
|
|
|
|
if (child.children) {
|
|
|
|
for (const path of child.children) {
|
|
|
|
if (!path.data.isPGGlyphRect) {
|
2017-12-19 13:15:31 -05:00
|
|
|
if (!_colorMatch(path.strokeColor, colorString)) {
|
2017-10-05 18:12:22 -04:00
|
|
|
changed = true;
|
|
|
|
path.strokeColor = colorString;
|
|
|
|
}
|
2017-10-02 15:25:04 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (!child.data.isPGGlyphRect) {
|
2017-10-05 18:12:22 -04:00
|
|
|
if (child.strokeColor !== colorString) {
|
|
|
|
changed = true;
|
|
|
|
child.strokeColor = colorString;
|
|
|
|
}
|
2017-10-02 15:25:04 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (!item.data.isPGGlyphRect) {
|
2017-12-19 13:15:31 -05:00
|
|
|
if (!_colorMatch(item.strokeColor, colorString)) {
|
2017-10-05 18:12:22 -04:00
|
|
|
changed = true;
|
|
|
|
item.strokeColor = colorString;
|
|
|
|
}
|
2017-10-02 15:25:04 -04:00
|
|
|
}
|
2017-12-19 13:15:31 -05:00
|
|
|
} else if (!_colorMatch(item.strokeColor, colorString)) {
|
2017-10-05 18:12:22 -04:00
|
|
|
changed = true;
|
2017-10-02 15:25:04 -04:00
|
|
|
item.strokeColor = colorString;
|
|
|
|
}
|
|
|
|
}
|
2017-10-29 14:10:43 -04:00
|
|
|
return changed;
|
2017-10-02 15:25:04 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when setting stroke width
|
|
|
|
* @param {number} value New stroke width
|
2018-03-16 11:36:06 -04:00
|
|
|
* @param {?string} textEditTargetId paper.Item.id of text editing target, if any
|
|
|
|
* @return {boolean} Whether the color application actually changed visibly.
|
2017-10-02 15:25:04 -04:00
|
|
|
*/
|
2018-03-16 11:36:06 -04:00
|
|
|
const applyStrokeWidthToSelection = function (value, textEditTargetId) {
|
2017-10-26 11:28:10 -04:00
|
|
|
let changed = false;
|
2018-03-16 11:36:06 -04:00
|
|
|
const items = _getColorStateListeners(textEditTargetId);
|
2017-12-18 12:02:35 -05:00
|
|
|
for (let item of items) {
|
|
|
|
if (item.parent instanceof paper.CompoundPath) {
|
|
|
|
item = item.parent;
|
|
|
|
}
|
2017-10-02 15:25:04 -04:00
|
|
|
if (isGroup(item)) {
|
|
|
|
continue;
|
2017-10-05 18:12:22 -04:00
|
|
|
} else if (item.strokeWidth !== value) {
|
2017-10-02 15:25:04 -04:00
|
|
|
item.strokeWidth = value;
|
2017-10-26 11:28:10 -04:00
|
|
|
changed = true;
|
2017-10-02 15:25:04 -04:00
|
|
|
}
|
|
|
|
}
|
2018-03-16 11:36:06 -04:00
|
|
|
return changed;
|
2017-10-02 15:25:04 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get state of colors and stroke width for selection
|
2017-10-03 15:04:53 -04:00
|
|
|
* @param {!Array<paper.Item>} selectedItems Selected paper items
|
2017-10-02 15:25:04 -04:00
|
|
|
* @return {object} Object of strokeColor, strokeWidth, fillColor of the selection.
|
|
|
|
* Gives MIXED when there are mixed values for a color, and null for transparent.
|
|
|
|
* Gives null when there are mixed values for stroke width.
|
|
|
|
*/
|
2017-10-03 15:04:53 -04:00
|
|
|
const getColorsFromSelection = function (selectedItems) {
|
2017-10-02 15:25:04 -04:00
|
|
|
let selectionFillColorString;
|
|
|
|
let selectionStrokeColorString;
|
|
|
|
let selectionStrokeWidth;
|
|
|
|
let firstChild = true;
|
2017-10-29 14:10:43 -04:00
|
|
|
|
2017-12-18 12:02:35 -05:00
|
|
|
for (let item of selectedItems) {
|
|
|
|
if (item.parent instanceof paper.CompoundPath) {
|
|
|
|
// Compound path children inherit fill and stroke color from their parent.
|
|
|
|
item = item.parent;
|
|
|
|
}
|
2017-10-02 15:25:04 -04:00
|
|
|
let itemFillColorString;
|
|
|
|
let itemStrokeColorString;
|
2017-10-29 14:10:43 -04:00
|
|
|
|
2017-10-02 15:25:04 -04:00
|
|
|
// handle pgTextItems differently by going through their children
|
|
|
|
if (isPGTextItem(item)) {
|
|
|
|
for (const child of item.children) {
|
|
|
|
for (const path of child.children) {
|
|
|
|
if (!path.data.isPGGlyphRect) {
|
|
|
|
if (path.fillColor) {
|
|
|
|
itemFillColorString = path.fillColor.toCSS();
|
|
|
|
}
|
|
|
|
if (path.strokeColor) {
|
|
|
|
itemStrokeColorString = path.strokeColor.toCSS();
|
|
|
|
}
|
|
|
|
// check every style against the first of the items
|
|
|
|
if (firstChild) {
|
|
|
|
firstChild = false;
|
|
|
|
selectionFillColorString = itemFillColorString;
|
|
|
|
selectionStrokeColorString = itemStrokeColorString;
|
|
|
|
selectionStrokeWidth = path.strokeWidth;
|
|
|
|
}
|
|
|
|
if (itemFillColorString !== selectionFillColorString) {
|
|
|
|
selectionFillColorString = MIXED;
|
|
|
|
}
|
|
|
|
if (itemStrokeColorString !== selectionStrokeColorString) {
|
|
|
|
selectionStrokeColorString = MIXED;
|
|
|
|
}
|
|
|
|
if (selectionStrokeWidth !== path.strokeWidth) {
|
|
|
|
selectionStrokeWidth = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (!isGroup(item)) {
|
|
|
|
if (item.fillColor) {
|
|
|
|
// hack bc text items with null fill can't be detected by fill-hitTest anymore
|
|
|
|
if (isPointTextItem(item) && item.fillColor.toCSS() === 'rgba(0,0,0,0)') {
|
|
|
|
itemFillColorString = null;
|
2017-11-06 15:06:05 -05:00
|
|
|
} else if (item.fillColor.type === 'gradient') {
|
|
|
|
itemFillColorString = MIXED;
|
2017-10-02 15:25:04 -04:00
|
|
|
} else {
|
|
|
|
itemFillColorString = item.fillColor.toCSS();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (item.strokeColor) {
|
2017-11-06 15:06:05 -05:00
|
|
|
if (item.strokeColor.type === 'gradient') {
|
|
|
|
itemStrokeColorString = MIXED;
|
|
|
|
} else {
|
|
|
|
itemStrokeColorString = item.strokeColor.toCSS();
|
|
|
|
}
|
2017-10-02 15:25:04 -04:00
|
|
|
}
|
|
|
|
// check every style against the first of the items
|
|
|
|
if (firstChild) {
|
|
|
|
firstChild = false;
|
|
|
|
selectionFillColorString = itemFillColorString;
|
|
|
|
selectionStrokeColorString = itemStrokeColorString;
|
|
|
|
selectionStrokeWidth = item.strokeWidth;
|
|
|
|
}
|
|
|
|
if (itemFillColorString !== selectionFillColorString) {
|
|
|
|
selectionFillColorString = MIXED;
|
|
|
|
}
|
|
|
|
if (itemStrokeColorString !== selectionStrokeColorString) {
|
|
|
|
selectionStrokeColorString = MIXED;
|
|
|
|
}
|
|
|
|
if (selectionStrokeWidth !== item.strokeWidth) {
|
|
|
|
selectionStrokeWidth = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
fillColor: selectionFillColorString ? selectionFillColorString : null,
|
|
|
|
strokeColor: selectionStrokeColorString ? selectionStrokeColorString : null,
|
2017-10-03 13:45:19 -04:00
|
|
|
strokeWidth: selectionStrokeWidth || (selectionStrokeWidth === null) ? selectionStrokeWidth : 0
|
2017-10-02 15:25:04 -04:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2017-10-18 19:21:29 -04:00
|
|
|
const styleBlob = function (path, options) {
|
2017-10-02 15:25:04 -04:00
|
|
|
if (options.isEraser) {
|
|
|
|
path.fillColor = 'white';
|
2017-10-05 18:12:22 -04:00
|
|
|
} else if (options.fillColor) {
|
|
|
|
path.fillColor = options.fillColor;
|
2017-10-02 15:25:04 -04:00
|
|
|
} else {
|
2017-10-05 18:12:22 -04:00
|
|
|
// Make sure something visible is drawn
|
|
|
|
path.fillColor = 'black';
|
2017-10-02 15:25:04 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-10-18 19:21:29 -04:00
|
|
|
const stylePath = function (path, strokeColor, strokeWidth) {
|
|
|
|
// Make sure a visible line is drawn
|
|
|
|
path.setStrokeColor(
|
|
|
|
(strokeColor === MIXED || strokeColor === null) ? 'black' : strokeColor);
|
|
|
|
path.setStrokeWidth(
|
|
|
|
strokeWidth === null || strokeWidth === 0 ? 1 : strokeWidth);
|
|
|
|
};
|
|
|
|
|
2017-10-02 15:25:04 -04:00
|
|
|
const styleCursorPreview = function (path, options) {
|
|
|
|
if (options.isEraser) {
|
|
|
|
path.fillColor = 'white';
|
|
|
|
path.strokeColor = 'cornflowerblue';
|
|
|
|
path.strokeWidth = 1;
|
2017-10-05 18:12:22 -04:00
|
|
|
} else if (options.fillColor) {
|
|
|
|
path.fillColor = options.fillColor;
|
2017-10-02 15:25:04 -04:00
|
|
|
} else {
|
2017-10-05 18:12:22 -04:00
|
|
|
// Make sure something visible is drawn
|
|
|
|
path.fillColor = 'black';
|
2017-10-02 15:25:04 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-10-19 16:52:24 -04:00
|
|
|
const styleShape = function (path, options) {
|
|
|
|
path.fillColor = options.fillColor;
|
|
|
|
path.strokeColor = options.strokeColor;
|
|
|
|
path.strokeWidth = options.strokeWidth;
|
|
|
|
};
|
|
|
|
|
2017-10-02 15:25:04 -04:00
|
|
|
export {
|
|
|
|
applyFillColorToSelection,
|
|
|
|
applyStrokeColorToSelection,
|
|
|
|
applyStrokeWidthToSelection,
|
|
|
|
getColorsFromSelection,
|
|
|
|
MIXED,
|
2017-10-18 19:21:29 -04:00
|
|
|
styleBlob,
|
2017-10-19 16:52:24 -04:00
|
|
|
styleShape,
|
2017-10-02 15:25:04 -04:00
|
|
|
stylePath,
|
|
|
|
styleCursorPreview
|
|
|
|
};
|