mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
Selection sets and gets fill/stroke color/width
This commit is contained in:
parent
6bcd59f388
commit
b87c17523a
30 changed files with 464 additions and 87 deletions
|
@ -6,6 +6,8 @@ import BufferedInputHOC from './forms/buffered-input-hoc.jsx';
|
|||
import Label from './forms/label.jsx';
|
||||
import Input from './forms/input.jsx';
|
||||
|
||||
import {MIXED} from '../helper/style-path';
|
||||
|
||||
import styles from './paint-editor.css';
|
||||
|
||||
const BufferedInput = BufferedInputHOC(Input);
|
||||
|
@ -21,7 +23,7 @@ const FillColorIndicatorComponent = props => (
|
|||
<Label text={props.intl.formatMessage(messages.fill)}>
|
||||
<BufferedInput
|
||||
type="text"
|
||||
value={props.fillColor}
|
||||
value={props.fillColor === MIXED ? 'mixed' : props.fillColor === null ? 'transparent' : props.fillColor} // @todo Don't use text to indicate mixed
|
||||
onSubmit={props.onChangeFillColor}
|
||||
/>
|
||||
</Label>
|
||||
|
@ -29,7 +31,7 @@ const FillColorIndicatorComponent = props => (
|
|||
);
|
||||
|
||||
FillColorIndicatorComponent.propTypes = {
|
||||
fillColor: PropTypes.string.isRequired,
|
||||
fillColor: PropTypes.string,
|
||||
intl: intlShape,
|
||||
onChangeFillColor: PropTypes.func.isRequired
|
||||
};
|
||||
|
|
|
@ -6,6 +6,8 @@ import BufferedInputHOC from './forms/buffered-input-hoc.jsx';
|
|||
import Label from './forms/label.jsx';
|
||||
import Input from './forms/input.jsx';
|
||||
|
||||
import {MIXED} from '../helper/style-path';
|
||||
|
||||
import styles from './paint-editor.css';
|
||||
|
||||
const BufferedInput = BufferedInputHOC(Input);
|
||||
|
@ -21,7 +23,8 @@ const StrokeColorIndicatorComponent = props => (
|
|||
<Label text={props.intl.formatMessage(messages.stroke)}>
|
||||
<BufferedInput
|
||||
type="text"
|
||||
value={props.strokeColor}
|
||||
// @todo Don't use text to indicate mixed
|
||||
value={props.strokeColor === MIXED ? 'mixed' : props.strokeColor === null ? 'transparent' : props.strokeColor}
|
||||
onSubmit={props.onChangeStrokeColor}
|
||||
/>
|
||||
</Label>
|
||||
|
@ -31,7 +34,7 @@ const StrokeColorIndicatorComponent = props => (
|
|||
StrokeColorIndicatorComponent.propTypes = {
|
||||
intl: intlShape,
|
||||
onChangeStrokeColor: PropTypes.func.isRequired,
|
||||
strokeColor: PropTypes.string.isRequired
|
||||
strokeColor: PropTypes.string
|
||||
};
|
||||
|
||||
export default injectIntl(StrokeColorIndicatorComponent);
|
||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||
|
||||
import BufferedInputHOC from './forms/buffered-input-hoc.jsx';
|
||||
import Input from './forms/input.jsx';
|
||||
|
||||
import {MAX_STROKE_WIDTH} from '../reducers/stroke-width';
|
||||
|
||||
import styles from './paint-editor.css';
|
||||
|
@ -15,7 +16,7 @@ const StrokeWidthIndicatorComponent = props => (
|
|||
max={MAX_STROKE_WIDTH}
|
||||
min="0"
|
||||
type="number"
|
||||
value={props.strokeWidth}
|
||||
value={props.strokeWidth ? props.strokeWidth : 0}
|
||||
onSubmit={props.onChangeStrokeWidth}
|
||||
/>
|
||||
</div>
|
||||
|
@ -23,7 +24,7 @@ const StrokeWidthIndicatorComponent = props => (
|
|||
|
||||
StrokeWidthIndicatorComponent.propTypes = {
|
||||
onChangeStrokeWidth: PropTypes.func.isRequired,
|
||||
strokeWidth: PropTypes.number.isRequired
|
||||
strokeWidth: PropTypes.number
|
||||
};
|
||||
|
||||
export default StrokeWidthIndicatorComponent;
|
||||
|
|
|
@ -2,7 +2,7 @@ import paper from 'paper';
|
|||
import log from '../../log/log';
|
||||
import BroadBrushHelper from './broad-brush-helper';
|
||||
import SegmentBrushHelper from './segment-brush-helper';
|
||||
import {styleCursorPreview} from './style-path';
|
||||
import {MIXED, styleCursorPreview} from '../../helper/style-path';
|
||||
import {clearSelection} from '../../helper/selection';
|
||||
|
||||
/**
|
||||
|
@ -32,6 +32,11 @@ class Blobbiness {
|
|||
this.broadBrushHelper = new BroadBrushHelper();
|
||||
this.segmentBrushHelper = new SegmentBrushHelper();
|
||||
this.updateCallback = updateCallback;
|
||||
|
||||
// The following are stored to check whether these have changed and the cursor preview needs to be redrawn.
|
||||
this.strokeColor = null;
|
||||
this.brushSize = null;
|
||||
this.fillColor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,7 +50,15 @@ class Blobbiness {
|
|||
* @param {?number} options.strokeWidth Width of the brush outline.
|
||||
*/
|
||||
setOptions (options) {
|
||||
this.options = options;
|
||||
const oldFillColor = this.options ? this.options.fillColor : null;
|
||||
const oldStrokeColor = this.options ? this.options.strokeColor : null;
|
||||
const oldStrokeWidth = this.options ? this.options.strokeWidth : null;
|
||||
this.options = {
|
||||
...options,
|
||||
fillColor: options.fillColor === MIXED ? oldFillColor : options.fillColor,
|
||||
strokeColor: options.strokeColor === MIXED ? oldStrokeColor : options.strokeColor,
|
||||
strokeWidth: options.strokeWidth === null ? oldStrokeWidth : options.strokeWidth
|
||||
};
|
||||
this.resizeCursorIfNeeded();
|
||||
}
|
||||
|
||||
|
@ -150,8 +163,8 @@ class Blobbiness {
|
|||
|
||||
if (this.cursorPreview &&
|
||||
this.brushSize === this.options.brushSize &&
|
||||
this.fillColor === this.options.fillColor &&
|
||||
this.strokeColor === this.options.strokeColor) {
|
||||
(this.options.fillColor === MIXED || this.fillColor === this.options.fillColor) &&
|
||||
(this.options.strokeColor === MIXED || this.strokeColor === this.options.strokeColor)) {
|
||||
return;
|
||||
}
|
||||
const newPreview = new paper.Path.Circle({
|
||||
|
@ -162,8 +175,12 @@ class Blobbiness {
|
|||
this.cursorPreview.remove();
|
||||
}
|
||||
this.brushSize = this.options.brushSize;
|
||||
if (this.options.fillColor !== MIXED) {
|
||||
this.fillColor = this.options.fillColor;
|
||||
}
|
||||
if (this.options.strokeColor !== MIXED) {
|
||||
this.strokeColor = this.options.strokeColor;
|
||||
}
|
||||
this.cursorPreview = newPreview;
|
||||
styleCursorPreview(this.cursorPreview, this.options);
|
||||
}
|
||||
|
@ -233,7 +250,7 @@ class Blobbiness {
|
|||
// Eraser didn't hit anything selected, so assume they meant to erase from all instead of from subset
|
||||
// and deselect the selection
|
||||
if (items.length === 0) {
|
||||
clearSelection();
|
||||
clearSelection(this.clearSelectedItems);
|
||||
items = paper.project.getItems({
|
||||
match: function (item) {
|
||||
return blob.isMergeable(lastPath, item) && blob.touches(lastPath, item);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Broadbrush based on http://paperjs.org/tutorials/interaction/working-with-mouse-vectors/
|
||||
import paper from 'paper';
|
||||
import {stylePath} from './style-path';
|
||||
import {stylePath} from '../../helper/style-path';
|
||||
|
||||
/**
|
||||
* Broad brush functions to add as listeners on the mouse. Call them when the corresponding mouse event happens
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import paper from 'paper';
|
||||
import {stylePath} from './style-path';
|
||||
import {stylePath} from '../../helper/style-path';
|
||||
|
||||
/**
|
||||
* Segment brush functions to add as listeners on the mouse. Call them when the corresponding mouse event happens
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
const stylePath = function (path, options) {
|
||||
if (options.isEraser) {
|
||||
path.fillColor = 'white';
|
||||
} else {
|
||||
path.fillColor = options.fillColor;
|
||||
}
|
||||
};
|
||||
|
||||
const styleCursorPreview = function (path, options) {
|
||||
if (options.isEraser) {
|
||||
path.fillColor = 'white';
|
||||
path.strokeColor = 'cornflowerblue';
|
||||
path.strokeWidth = 1;
|
||||
} else {
|
||||
path.fillColor = options.fillColor;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
stylePath,
|
||||
styleCursorPreview
|
||||
};
|
|
@ -6,6 +6,7 @@ import Modes from '../modes/modes';
|
|||
import Blobbiness from './blob/blob';
|
||||
import {changeBrushSize} from '../reducers/brush-mode';
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems} from '../reducers/selected-items';
|
||||
import {clearSelection} from '../helper/selection';
|
||||
import BrushModeComponent from '../components/brush-mode.jsx';
|
||||
|
||||
|
@ -43,7 +44,7 @@ class BrushMode extends React.Component {
|
|||
activateTool () {
|
||||
// TODO: Instead of clearing selection, consider a kind of "draw inside"
|
||||
// analogous to how selection works with eraser
|
||||
clearSelection();
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
|
||||
// TODO: This is temporary until a component that provides the brush size is hooked up
|
||||
this.props.canvas.addEventListener('mousewheel', this.onScroll);
|
||||
|
@ -78,10 +79,11 @@ BrushMode.propTypes = {
|
|||
}),
|
||||
canvas: PropTypes.instanceOf(Element).isRequired,
|
||||
changeBrushSize: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
colorState: PropTypes.shape({
|
||||
fillColor: PropTypes.string.isRequired,
|
||||
strokeColor: PropTypes.string.isRequired,
|
||||
strokeWidth: PropTypes.number.isRequired
|
||||
fillColor: PropTypes.string,
|
||||
strokeColor: PropTypes.string,
|
||||
strokeWidth: PropTypes.number
|
||||
}).isRequired,
|
||||
handleMouseDown: PropTypes.func.isRequired,
|
||||
isBrushModeActive: PropTypes.bool.isRequired,
|
||||
|
@ -94,6 +96,9 @@ const mapStateToProps = state => ({
|
|||
isBrushModeActive: state.scratchPaint.mode === Modes.BRUSH
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
changeBrushSize: brushSize => {
|
||||
dispatch(changeBrushSize(brushSize));
|
||||
},
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import {connect} from 'react-redux';
|
||||
import {changeFillColor} from '../reducers/fill-color';
|
||||
import FillColorIndicatorComponent from '../components/fill-color-indicator.jsx';
|
||||
import {applyFillColorToSelection} from '../helper/style-path';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
fillColor: state.scratchPaint.color.fillColor
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onChangeFillColor: fillColor => {
|
||||
applyFillColorToSelection(fillColor);
|
||||
dispatch(changeFillColor(fillColor));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,7 +4,9 @@ import {connect} from 'react-redux';
|
|||
import bindAll from 'lodash.bindall';
|
||||
import Modes from '../modes/modes';
|
||||
import {changeStrokeWidth} from '../reducers/stroke-width';
|
||||
import {clearSelection} from '../helper/selection';
|
||||
import {clearSelection, getSelectedItems} from '../helper/selection';
|
||||
import {MIXED} from '../helper/style-path';
|
||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||
import LineModeComponent from '../components/line-mode.jsx';
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import paper from 'paper';
|
||||
|
@ -43,7 +45,7 @@ class LineMode extends React.Component {
|
|||
return false; // Static component, for now
|
||||
}
|
||||
activateTool () {
|
||||
clearSelection();
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
this.props.canvas.addEventListener('mousewheel', this.onScroll);
|
||||
this.tool = new paper.Tool();
|
||||
|
||||
|
@ -93,9 +95,12 @@ class LineMode extends React.Component {
|
|||
if (!this.path) {
|
||||
this.path = new paper.Path();
|
||||
|
||||
this.path.setStrokeColor(this.props.colorState.strokeColor);
|
||||
this.path.setStrokeColor(
|
||||
this.props.colorState.strokeColor === MIXED ? 'black' : this.props.colorState.strokeColor);
|
||||
// Make sure a visible line is drawn
|
||||
this.path.setStrokeWidth(Math.max(1, this.props.colorState.strokeWidth));
|
||||
this.path.setStrokeWidth(
|
||||
this.props.colorState.strokeWidth === null || this.props.colorState.strokeWidth === 0 ?
|
||||
1 : this.props.colorState.strokeWidth);
|
||||
|
||||
this.path.setSelected(true);
|
||||
this.path.add(event.point);
|
||||
|
@ -202,6 +207,7 @@ class LineMode extends React.Component {
|
|||
this.hitResult = null;
|
||||
}
|
||||
this.props.onUpdateSvg();
|
||||
this.props.setSelectedItems();
|
||||
|
||||
// TODO add back undo
|
||||
// if (this.path) {
|
||||
|
@ -269,14 +275,16 @@ class LineMode extends React.Component {
|
|||
LineMode.propTypes = {
|
||||
canvas: PropTypes.instanceOf(Element).isRequired,
|
||||
changeStrokeWidth: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
colorState: PropTypes.shape({
|
||||
fillColor: PropTypes.string.isRequired,
|
||||
strokeColor: PropTypes.string.isRequired,
|
||||
strokeWidth: PropTypes.number.isRequired
|
||||
fillColor: PropTypes.string,
|
||||
strokeColor: PropTypes.string,
|
||||
strokeWidth: PropTypes.number
|
||||
}).isRequired,
|
||||
handleMouseDown: PropTypes.func.isRequired,
|
||||
isLineModeActive: PropTypes.bool.isRequired,
|
||||
onUpdateSvg: PropTypes.func.isRequired
|
||||
onUpdateSvg: PropTypes.func.isRequired,
|
||||
setSelectedItems: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
@ -287,6 +295,12 @@ const mapDispatchToProps = dispatch => ({
|
|||
changeStrokeWidth: strokeWidth => {
|
||||
dispatch(changeStrokeWidth(strokeWidth));
|
||||
},
|
||||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
setSelectedItems: () => {
|
||||
dispatch(setSelectedItems(getSelectedItems(true /* recursive */)));
|
||||
},
|
||||
handleMouseDown: () => {
|
||||
dispatch(changeMode(Modes.LINE));
|
||||
}
|
||||
|
|
|
@ -5,12 +5,12 @@ import bindAll from 'lodash.bindall';
|
|||
import Modes from '../modes/modes';
|
||||
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {setHoveredItem, clearHoveredItem} from '../reducers/hover';
|
||||
import {clearHoveredItem, setHoveredItem} from '../reducers/hover';
|
||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||
import {getSelectedItems} from '../helper/selection';
|
||||
|
||||
import ReshapeTool from '../helper/selection-tools/reshape-tool';
|
||||
import ReshapeModeComponent from '../components/reshape-mode.jsx';
|
||||
import paper from 'paper';
|
||||
|
||||
|
||||
class ReshapeMode extends React.Component {
|
||||
constructor (props) {
|
||||
|
@ -40,7 +40,12 @@ class ReshapeMode extends React.Component {
|
|||
return false; // Static component, for now
|
||||
}
|
||||
activateTool () {
|
||||
this.tool = new ReshapeTool(this.props.setHoveredItem, this.props.clearHoveredItem, this.props.onUpdateSvg);
|
||||
this.tool = new ReshapeTool(
|
||||
this.props.setHoveredItem,
|
||||
this.props.clearHoveredItem,
|
||||
this.props.setSelectedItems,
|
||||
this.props.clearSelectedItems,
|
||||
this.props.onUpdateSvg);
|
||||
this.tool.setPrevHoveredItemId(this.props.hoveredItemId);
|
||||
this.tool.activate();
|
||||
}
|
||||
|
@ -59,11 +64,13 @@ class ReshapeMode extends React.Component {
|
|||
|
||||
ReshapeMode.propTypes = {
|
||||
clearHoveredItem: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
handleMouseDown: PropTypes.func.isRequired,
|
||||
hoveredItemId: PropTypes.number,
|
||||
isReshapeModeActive: PropTypes.bool.isRequired,
|
||||
onUpdateSvg: PropTypes.func.isRequired,
|
||||
setHoveredItem: PropTypes.func.isRequired
|
||||
setHoveredItem: PropTypes.func.isRequired,
|
||||
setSelectedItems: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
@ -77,6 +84,12 @@ const mapDispatchToProps = dispatch => ({
|
|||
clearHoveredItem: () => {
|
||||
dispatch(clearHoveredItem());
|
||||
},
|
||||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
setSelectedItems: () => {
|
||||
dispatch(setSelectedItems(getSelectedItems(true /* recursive */)));
|
||||
},
|
||||
handleMouseDown: () => {
|
||||
dispatch(changeMode(Modes.RESHAPE));
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@ import bindAll from 'lodash.bindall';
|
|||
import Modes from '../modes/modes';
|
||||
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {setHoveredItem, clearHoveredItem} from '../reducers/hover';
|
||||
import {clearHoveredItem, setHoveredItem} from '../reducers/hover';
|
||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||
|
||||
import {getSelectedItems} from '../helper/selection';
|
||||
import SelectTool from '../helper/selection-tools/select-tool';
|
||||
import SelectModeComponent from '../components/select-mode.jsx';
|
||||
|
||||
|
@ -38,7 +40,12 @@ class SelectMode extends React.Component {
|
|||
return false; // Static component, for now
|
||||
}
|
||||
activateTool () {
|
||||
this.tool = new SelectTool(this.props.setHoveredItem, this.props.clearHoveredItem, this.props.onUpdateSvg);
|
||||
this.tool = new SelectTool(
|
||||
this.props.setHoveredItem,
|
||||
this.props.clearHoveredItem,
|
||||
this.props.setSelectedItems,
|
||||
this.props.clearSelectedItems,
|
||||
this.props.onUpdateSvg);
|
||||
this.tool.activate();
|
||||
}
|
||||
deactivateTool () {
|
||||
|
@ -55,11 +62,13 @@ class SelectMode extends React.Component {
|
|||
|
||||
SelectMode.propTypes = {
|
||||
clearHoveredItem: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
handleMouseDown: PropTypes.func.isRequired,
|
||||
hoveredItemId: PropTypes.number,
|
||||
isSelectModeActive: PropTypes.bool.isRequired,
|
||||
onUpdateSvg: PropTypes.func.isRequired,
|
||||
setHoveredItem: PropTypes.func.isRequired
|
||||
setHoveredItem: PropTypes.func.isRequired,
|
||||
setSelectedItems: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
@ -73,6 +82,12 @@ const mapDispatchToProps = dispatch => ({
|
|||
clearHoveredItem: () => {
|
||||
dispatch(clearHoveredItem());
|
||||
},
|
||||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
setSelectedItems: () => {
|
||||
dispatch(setSelectedItems(getSelectedItems(true /* recursive */)));
|
||||
},
|
||||
handleMouseDown: () => {
|
||||
dispatch(changeMode(Modes.SELECT));
|
||||
}
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
import paper from 'paper';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import bindAll from 'lodash.bindall';
|
||||
import paper from 'paper';
|
||||
|
||||
import {getSelectedItems} from '../helper/selection';
|
||||
import {getColorsFromSelection} from '../helper/style-path';
|
||||
import {changeStrokeColor} from '../reducers/stroke-color';
|
||||
import {changeStrokeWidth} from '../reducers/stroke-width';
|
||||
import {changeFillColor} from '../reducers/fill-color';
|
||||
|
||||
const SelectionHOC = function (WrappedComponent) {
|
||||
class SelectionComponent extends React.Component {
|
||||
|
@ -52,8 +59,21 @@ const SelectionHOC = function (WrappedComponent) {
|
|||
const mapStateToProps = state => ({
|
||||
hoveredItemId: state.scratchPaint.hoveredItemId
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onUpdateColors: (() => {
|
||||
const selectedItems = getSelectedItems(true /* recursive */);
|
||||
if (selectedItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
const colorState = getColorsFromSelection();
|
||||
dispatch(changeFillColor(colorState.fillColor));
|
||||
dispatch(changeStrokeColor(colorState.strokeColor));
|
||||
dispatch(changeStrokeWidth(colorState.strokeWidth));
|
||||
})
|
||||
});
|
||||
return connect(
|
||||
mapStateToProps
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(SelectionComponent);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import {connect} from 'react-redux';
|
||||
import {changeStrokeColor} from '../reducers/stroke-color';
|
||||
import StrokeColorIndicatorComponent from '../components/stroke-color-indicator.jsx';
|
||||
import {applyStrokeColorToSelection} from '../helper/style-path';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
strokeColor: state.scratchPaint.color.strokeColor
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onChangeStrokeColor: strokeColor => {
|
||||
applyStrokeColorToSelection(strokeColor);
|
||||
dispatch(changeStrokeColor(strokeColor));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import {connect} from 'react-redux';
|
||||
import {changeStrokeWidth} from '../reducers/stroke-width';
|
||||
import StrokeWidthIndicatorComponent from '../components/stroke-width-indicator.jsx';
|
||||
import {applyStrokeWidthToSelection} from '../helper/style-path';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
strokeWidth: state.scratchPaint.color.strokeWidth
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onChangeStrokeWidth: strokeWidth => {
|
||||
applyStrokeWidthToSelection(strokeWidth);
|
||||
dispatch(changeStrokeWidth(strokeWidth));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -6,11 +6,11 @@ const isGroup = function (item) {
|
|||
return isGroupItem(item);
|
||||
};
|
||||
|
||||
const groupSelection = function () {
|
||||
const groupSelection = function (clearSelectedItems) {
|
||||
const items = getSelectedItems();
|
||||
if (items.length > 0) {
|
||||
const group = new paper.Group(items);
|
||||
clearSelection();
|
||||
clearSelection(clearSelectedItems);
|
||||
setItemSelection(group, true);
|
||||
for (let i = 0; i < group.children.length; i++) {
|
||||
group.children[i].selected = true;
|
||||
|
@ -47,8 +47,8 @@ const ungroupLoop = function (group, recursive) {
|
|||
};
|
||||
|
||||
// ungroup items (only top hierarchy)
|
||||
const ungroupItems = function (items) {
|
||||
clearSelection();
|
||||
const ungroupItems = function (items, clearSelectedItems) {
|
||||
clearSelection(clearSelectedItems);
|
||||
const emptyGroups = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
|
|
|
@ -31,7 +31,14 @@ const Modes = keyMirror({
|
|||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||
*/
|
||||
class BoundingBoxTool {
|
||||
constructor (onUpdateSvg) {
|
||||
/**
|
||||
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||
*/
|
||||
constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
|
||||
this.setSelectedItems = setSelectedItems;
|
||||
this.clearSelectedItems = clearSelectedItems;
|
||||
this.onUpdateSvg = onUpdateSvg;
|
||||
this.mode = null;
|
||||
this.boundsPath = null;
|
||||
|
@ -40,7 +47,7 @@ class BoundingBoxTool {
|
|||
this._modeMap = {};
|
||||
this._modeMap[Modes.SCALE] = new ScaleTool(onUpdateSvg);
|
||||
this._modeMap[Modes.ROTATE] = new RotateTool(onUpdateSvg);
|
||||
this._modeMap[Modes.MOVE] = new MoveTool(onUpdateSvg);
|
||||
this._modeMap[Modes.MOVE] = new MoveTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,7 +63,6 @@ class BoundingBoxTool {
|
|||
if (!hitResults || hitResults.length === 0) {
|
||||
if (!multiselect) {
|
||||
this.removeBoundsPath();
|
||||
clearSelection();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3,10 +3,14 @@ import {clearSelection, getSelectedItems} from '../selection';
|
|||
/** Sub tool of the Reshape tool for moving handles, which adjust bezier curves. */
|
||||
class HandleTool {
|
||||
/**
|
||||
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||
*/
|
||||
constructor (onUpdateSvg) {
|
||||
constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
|
||||
this.hitType = null;
|
||||
this.setSelectedItems = setSelectedItems;
|
||||
this.clearSelectedItems = clearSelectedItems;
|
||||
this.onUpdateSvg = onUpdateSvg;
|
||||
}
|
||||
/**
|
||||
|
@ -16,7 +20,7 @@ class HandleTool {
|
|||
*/
|
||||
onMouseDown (hitProperties) {
|
||||
if (!hitProperties.multiselect) {
|
||||
clearSelection();
|
||||
clearSelection(this.clearSelectedItems);
|
||||
}
|
||||
|
||||
hitProperties.hitResult.segment.handleIn.selected = true;
|
||||
|
|
|
@ -8,9 +8,13 @@ import {clearSelection, cloneSelection, getSelectedItems, setItemSelection} from
|
|||
*/
|
||||
class MoveTool {
|
||||
/**
|
||||
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||
*/
|
||||
constructor (onUpdateSvg) {
|
||||
constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
|
||||
this.setSelectedItems = setSelectedItems;
|
||||
this.clearSelectedItems = clearSelectedItems;
|
||||
this.selectedItems = null;
|
||||
this.onUpdateSvg = onUpdateSvg;
|
||||
}
|
||||
|
@ -34,7 +38,7 @@ class MoveTool {
|
|||
// Double click causes all points to be selected in subselect mode.
|
||||
if (hitProperties.doubleClicked) {
|
||||
if (!hitProperties.multiselect) {
|
||||
clearSelection();
|
||||
clearSelection(this.clearSelectedItems);
|
||||
}
|
||||
this._select(item, true /* state */, hitProperties.subselect, true /* fullySelect */);
|
||||
} else if (hitProperties.multiselect) {
|
||||
|
@ -43,7 +47,7 @@ class MoveTool {
|
|||
} else {
|
||||
// deselect all by default if multiselect isn't on
|
||||
if (!hitProperties.multiselect) {
|
||||
clearSelection();
|
||||
clearSelection(this.clearSelectedItems);
|
||||
}
|
||||
this._select(item, true, hitProperties.subselect);
|
||||
}
|
||||
|
@ -71,6 +75,7 @@ class MoveTool {
|
|||
} else {
|
||||
setItemSelection(item, state);
|
||||
}
|
||||
this.setSelectedItems();
|
||||
}
|
||||
onMouseDrag (event) {
|
||||
const dragVector = event.point.subtract(event.downPoint);
|
||||
|
|
|
@ -5,9 +5,11 @@ import {clearSelection, getSelectedItems} from '../selection';
|
|||
/** Subtool of ReshapeTool for moving control points. */
|
||||
class PointTool {
|
||||
/**
|
||||
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||
*/
|
||||
constructor (onUpdateSvg) {
|
||||
constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
|
||||
/**
|
||||
* Deselection often does not happen until mouse up. If the mouse is dragged before
|
||||
* mouse up, deselection is cancelled. This variable keeps track of which paper.Item to deselect.
|
||||
|
@ -24,6 +26,8 @@ class PointTool {
|
|||
*/
|
||||
this.invertDeselect = false;
|
||||
this.selectedItems = null;
|
||||
this.setSelectedItems = setSelectedItems;
|
||||
this.clearSelectedItems = clearSelectedItems;
|
||||
this.onUpdateSvg = onUpdateSvg;
|
||||
}
|
||||
|
||||
|
@ -49,7 +53,7 @@ class PointTool {
|
|||
}
|
||||
} else {
|
||||
if (!hitProperties.multiselect) {
|
||||
clearSelection();
|
||||
clearSelection(this.clearSelectedItems);
|
||||
}
|
||||
hitProperties.hitResult.segment.selected = true;
|
||||
}
|
||||
|
@ -86,7 +90,7 @@ class PointTool {
|
|||
hitProperties.hitResult.item.insert(hitProperties.hitResult.location.index + 1, newSegment);
|
||||
hitProperties.hitResult.segment = newSegment;
|
||||
if (!hitProperties.multiselect) {
|
||||
clearSelection();
|
||||
clearSelection(this.clearSelectedItems);
|
||||
}
|
||||
newSegment.selected = true;
|
||||
|
||||
|
@ -175,7 +179,7 @@ class PointTool {
|
|||
// and delete
|
||||
if (this.deselectOnMouseUp) {
|
||||
if (this.invertDeselect) {
|
||||
clearSelection();
|
||||
clearSelection(this.clearSelectedItems);
|
||||
this.deselectOnMouseUp.selected = true;
|
||||
} else {
|
||||
this.deselectOnMouseUp.selected = false;
|
||||
|
@ -188,6 +192,7 @@ class PointTool {
|
|||
this.deleteOnMouseUp = null;
|
||||
}
|
||||
this.selectedItems = null;
|
||||
this.setSelectedItems();
|
||||
// @todo add back undo
|
||||
this.onUpdateSvg();
|
||||
}
|
||||
|
|
|
@ -41,9 +41,11 @@ class ReshapeTool extends paper.Tool {
|
|||
/**
|
||||
* @param {function} setHoveredItem Callback to set the hovered item
|
||||
* @param {function} clearHoveredItem Callback to clear the hovered item
|
||||
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||
*/
|
||||
constructor (setHoveredItem, clearHoveredItem, onUpdateSvg) {
|
||||
constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg) {
|
||||
super();
|
||||
this.setHoveredItem = setHoveredItem;
|
||||
this.clearHoveredItem = clearHoveredItem;
|
||||
|
@ -52,10 +54,10 @@ class ReshapeTool extends paper.Tool {
|
|||
this.lastEvent = null;
|
||||
this.mode = ReshapeModes.SELECTION_BOX;
|
||||
this._modeMap = {};
|
||||
this._modeMap[ReshapeModes.FILL] = new MoveTool(onUpdateSvg);
|
||||
this._modeMap[ReshapeModes.POINT] = new PointTool(onUpdateSvg);
|
||||
this._modeMap[ReshapeModes.HANDLE] = new HandleTool(onUpdateSvg);
|
||||
this._modeMap[ReshapeModes.SELECTION_BOX] = new SelectionBoxTool(Modes.RESHAPE);
|
||||
this._modeMap[ReshapeModes.FILL] = new MoveTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
|
||||
this._modeMap[ReshapeModes.POINT] = new PointTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
|
||||
this._modeMap[ReshapeModes.HANDLE] = new HandleTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
|
||||
this._modeMap[ReshapeModes.SELECTION_BOX] = new SelectionBoxTool(Modes.RESHAPE, setSelectedItems, clearSelectedItems);
|
||||
|
||||
// We have to set these functions instead of just declaring them because
|
||||
// paper.js tools hook up the listeners in the setter functions.
|
||||
|
|
|
@ -21,15 +21,17 @@ class SelectTool extends paper.Tool {
|
|||
/**
|
||||
* @param {function} setHoveredItem Callback to set the hovered item
|
||||
* @param {function} clearHoveredItem Callback to clear the hovered item
|
||||
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||
*/
|
||||
constructor (setHoveredItem, clearHoveredItem, onUpdateSvg) {
|
||||
constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg) {
|
||||
super();
|
||||
this.setHoveredItem = setHoveredItem;
|
||||
this.clearHoveredItem = clearHoveredItem;
|
||||
this.onUpdateSvg = onUpdateSvg;
|
||||
this.boundingBoxTool = new BoundingBoxTool(onUpdateSvg);
|
||||
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT);
|
||||
this.boundingBoxTool = new BoundingBoxTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
|
||||
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems);
|
||||
this.selectionBoxMode = false;
|
||||
this.prevHoveredItemId = null;
|
||||
|
||||
|
@ -42,6 +44,7 @@ class SelectTool extends paper.Tool {
|
|||
this.onKeyUp = this.handleKeyUp;
|
||||
|
||||
selectRootItem();
|
||||
setSelectedItems();
|
||||
this.boundingBoxTool.setSelectionBounds();
|
||||
}
|
||||
/**
|
||||
|
|
|
@ -3,16 +3,24 @@ import {clearSelection, processRectangularSelection} from '../selection';
|
|||
|
||||
/** Tool to handle drag selection. A dotted line box appears and everything enclosed is selected. */
|
||||
class SelectionBoxTool {
|
||||
constructor (mode) {
|
||||
/**
|
||||
* @param {!Modes} mode Current paint editor mode
|
||||
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||
*/
|
||||
constructor (mode, setSelectedItems, clearSelectedItems) {
|
||||
this.selectionRect = null;
|
||||
this.mode = mode;
|
||||
this.setSelectedItems = setSelectedItems;
|
||||
this.clearSelectedItems = clearSelectedItems;
|
||||
}
|
||||
/**
|
||||
* @param {boolean} multiselect Whether to multiselect on mouse down (e.g. shift key held)
|
||||
*/
|
||||
onMouseDown (multiselect) {
|
||||
if (!multiselect) {
|
||||
clearSelection();
|
||||
clearSelection(this.clearSelectedItems);
|
||||
this.clearSelectedItems();
|
||||
}
|
||||
}
|
||||
onMouseDrag (event) {
|
||||
|
@ -25,6 +33,7 @@ class SelectionBoxTool {
|
|||
processRectangularSelection(event, this.selectionRect, this.mode);
|
||||
this.selectionRect.remove();
|
||||
this.selectionRect = null;
|
||||
this.setSelectedItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,9 +112,11 @@ const selectAllSegments = function () {
|
|||
}
|
||||
};
|
||||
|
||||
const clearSelection = function () {
|
||||
/** @param {!function} dispatchClearSelect Function to update the Redux select state */
|
||||
const clearSelection = function (dispatchClearSelect) {
|
||||
paper.project.deselectAll();
|
||||
// @todo: Update toolbar state on change
|
||||
dispatchClearSelect();
|
||||
};
|
||||
|
||||
// This gets all selected non-grouped items and groups
|
||||
|
|
193
src/helper/style-path.js
Normal file
193
src/helper/style-path.js
Normal file
|
@ -0,0 +1,193 @@
|
|||
import {getSelectedItems} from './selection';
|
||||
import {isPGTextItem, isPointTextItem} from './item';
|
||||
import {isGroup} from './group';
|
||||
|
||||
const MIXED = 'scratch-paint/style-path/mixed';
|
||||
|
||||
/**
|
||||
* Called when setting fill color
|
||||
* @param {string} colorString New color, css format
|
||||
*/
|
||||
const applyFillColorToSelection = function (colorString) {
|
||||
const items = getSelectedItems(true /* recursive */);
|
||||
for (const item of items) {
|
||||
if (isPGTextItem(item)) {
|
||||
for (const child of item.children) {
|
||||
if (child.children) {
|
||||
for (const path of child.children) {
|
||||
if (!path.data.isPGGlyphRect) {
|
||||
path.fillColor = colorString;
|
||||
}
|
||||
}
|
||||
} else if (!child.data.isPGGlyphRect) {
|
||||
child.fillColor = colorString;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isPointTextItem(item) && !colorString) {
|
||||
colorString = 'rgba(0,0,0,0)';
|
||||
}
|
||||
item.fillColor = colorString;
|
||||
}
|
||||
}
|
||||
// @todo add back undo
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when setting stroke color
|
||||
* @param {string} colorString New color, css format
|
||||
*/
|
||||
const applyStrokeColorToSelection = function (colorString) {
|
||||
const items = getSelectedItems(true /* recursive */);
|
||||
|
||||
for (const item of items) {
|
||||
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) {
|
||||
path.strokeColor = colorString;
|
||||
}
|
||||
}
|
||||
} else if (!child.data.isPGGlyphRect) {
|
||||
child.strokeColor = colorString;
|
||||
}
|
||||
}
|
||||
} else if (!item.data.isPGGlyphRect) {
|
||||
item.strokeColor = colorString;
|
||||
}
|
||||
} else {
|
||||
item.strokeColor = colorString;
|
||||
}
|
||||
}
|
||||
// @todo add back undo
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when setting stroke width
|
||||
* @param {number} value New stroke width
|
||||
*/
|
||||
const applyStrokeWidthToSelection = function (value) {
|
||||
const items = getSelectedItems(true /* recursive */);
|
||||
for (const item of items) {
|
||||
if (isGroup(item)) {
|
||||
continue;
|
||||
} else {
|
||||
item.strokeWidth = value;
|
||||
}
|
||||
}
|
||||
// @todo add back undo
|
||||
};
|
||||
|
||||
/**
|
||||
* Get state of colors and stroke width for selection
|
||||
* @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.
|
||||
*/
|
||||
const getColorsFromSelection = function () {
|
||||
const selectedItems = getSelectedItems(true /* recursive */);
|
||||
let selectionFillColorString;
|
||||
let selectionStrokeColorString;
|
||||
let selectionStrokeWidth;
|
||||
let firstChild = true;
|
||||
|
||||
for (const item of selectedItems) {
|
||||
let itemFillColorString;
|
||||
let itemStrokeColorString;
|
||||
|
||||
// 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;
|
||||
} else {
|
||||
itemFillColorString = item.fillColor.toCSS();
|
||||
}
|
||||
}
|
||||
if (item.strokeColor) {
|
||||
itemStrokeColorString = item.strokeColor.toCSS();
|
||||
}
|
||||
// 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,
|
||||
strokeWidth: selectionStrokeWidth || (selectionStrokeWidth === null) ? selectionStrokeWidth : 0 //todo why is this 0 for arrow
|
||||
};
|
||||
};
|
||||
|
||||
const stylePath = function (path, options) {
|
||||
if (options.isEraser) {
|
||||
path.fillColor = 'white';
|
||||
} else {
|
||||
path.fillColor = options.fillColor;
|
||||
}
|
||||
};
|
||||
|
||||
const styleCursorPreview = function (path, options) {
|
||||
if (options.isEraser) {
|
||||
path.fillColor = 'white';
|
||||
path.strokeColor = 'cornflowerblue';
|
||||
path.strokeWidth = 1;
|
||||
} else {
|
||||
path.fillColor = options.fillColor;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
applyFillColorToSelection,
|
||||
applyStrokeColorToSelection,
|
||||
applyStrokeWidthToSelection,
|
||||
getColorsFromSelection,
|
||||
MIXED,
|
||||
stylePath,
|
||||
styleCursorPreview
|
||||
};
|
|
@ -1,4 +1,6 @@
|
|||
import log from '../log/log';
|
||||
import {CHANGE_SELECTED_ITEMS} from './selected-items';
|
||||
import {getColorsFromSelection} from '../helper/style-path';
|
||||
|
||||
const CHANGE_FILL_COLOR = 'scratch-paint/fill-color/CHANGE_FILL_COLOR';
|
||||
const initialState = '#000';
|
||||
|
@ -14,6 +16,12 @@ const reducer = function (state, action) {
|
|||
return state;
|
||||
}
|
||||
return action.fillColor;
|
||||
case CHANGE_SELECTED_ITEMS:
|
||||
// Don't change state if no selection
|
||||
if (!action.selectedItems || !action.selectedItems.length) {
|
||||
return state;
|
||||
}
|
||||
return getColorsFromSelection().fillColor;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@ import brushModeReducer from './brush-mode';
|
|||
import eraserModeReducer from './eraser-mode';
|
||||
import colorReducer from './color';
|
||||
import hoverReducer from './hover';
|
||||
import selectedItemReducer from './selected-items';
|
||||
|
||||
export default combineReducers({
|
||||
mode: modeReducer,
|
||||
brushMode: brushModeReducer,
|
||||
eraserMode: eraserModeReducer,
|
||||
color: colorReducer,
|
||||
hoveredItemId: hoverReducer
|
||||
hoveredItemId: hoverReducer,
|
||||
selectedItems: selectedItemReducer
|
||||
});
|
||||
|
|
48
src/reducers/selected-items.js
Normal file
48
src/reducers/selected-items.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
const CHANGE_SELECTED_ITEMS = 'scratch-paint/select/CHANGE_SELECTED_ITEMS';
|
||||
const initialState = [];
|
||||
|
||||
const reducer = function (state, action) {
|
||||
if (typeof state === 'undefined') state = initialState;
|
||||
switch (action.type) {
|
||||
case CHANGE_SELECTED_ITEMS:
|
||||
// If they are not equal, return the new list of items. Else return old list
|
||||
if (action.selectedItems.length !== state.length) {
|
||||
return action.selectedItems;
|
||||
}
|
||||
// Shallow equality check
|
||||
for (let i = 0; i < action.selectedItems.length; i++) {
|
||||
if (action.selectedItems[i] !== state[i]) {
|
||||
return action.selectedItems;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// Action creators ==================================
|
||||
/**
|
||||
* Set the selected item state to the given array of items
|
||||
* @param {Array<paper.Item>} selectedItems from paper.project.selectedItems
|
||||
* @return {object} Redux action to change the selected items.
|
||||
*/
|
||||
const setSelectedItems = function (selectedItems) {
|
||||
return {
|
||||
type: CHANGE_SELECTED_ITEMS,
|
||||
selectedItems: selectedItems
|
||||
};
|
||||
};
|
||||
const clearSelectedItems = function () {
|
||||
return {
|
||||
type: CHANGE_SELECTED_ITEMS,
|
||||
selectedItems: []
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
reducer as default,
|
||||
setSelectedItems,
|
||||
clearSelectedItems,
|
||||
CHANGE_SELECTED_ITEMS
|
||||
};
|
|
@ -1,4 +1,6 @@
|
|||
import log from '../log/log';
|
||||
import {CHANGE_SELECTED_ITEMS} from './selected-items';
|
||||
import {getColorsFromSelection} from '../helper/style-path';
|
||||
|
||||
const CHANGE_STROKE_COLOR = 'scratch-paint/stroke-color/CHANGE_STROKE_COLOR';
|
||||
const initialState = '#000';
|
||||
|
@ -14,6 +16,12 @@ const reducer = function (state, action) {
|
|||
return state;
|
||||
}
|
||||
return action.strokeColor;
|
||||
case CHANGE_SELECTED_ITEMS:
|
||||
// Don't change state if no selection
|
||||
if (!action.selectedItems || !action.selectedItems.length) {
|
||||
return state;
|
||||
}
|
||||
return getColorsFromSelection().strokeColor;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import log from '../log/log';
|
||||
import {CHANGE_SELECTED_ITEMS} from './selected-items';
|
||||
import {getColorsFromSelection} from '../helper/style-path';
|
||||
|
||||
const CHANGE_STROKE_WIDTH = 'scratch-paint/stroke-width/CHANGE_STROKE_WIDTH';
|
||||
const MAX_STROKE_WIDTH = 400;
|
||||
|
@ -13,6 +15,12 @@ const reducer = function (state, action) {
|
|||
return state;
|
||||
}
|
||||
return Math.min(MAX_STROKE_WIDTH, Math.max(0, action.strokeWidth));
|
||||
case CHANGE_SELECTED_ITEMS:
|
||||
// Don't change state if no selection
|
||||
if (!action.selectedItems || !action.selectedItems.length) {
|
||||
return state;
|
||||
}
|
||||
return getColorsFromSelection().strokeWidth;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue