2017-11-14 16:01:04 +01:00
|
|
|
import update from 'react-addons-update';
|
|
|
|
import fillPath from 'fill-path';
|
|
|
|
import ClipperShape from 'clipper-js';
|
2017-11-14 15:27:48 +01:00
|
|
|
import * as actions from '../../../actions/index.js';
|
|
|
|
import { SHAPE_TYPE_PROPERTIES } from '../../../constants/shapeTypeProperties.js';
|
|
|
|
import { LINE_WIDTH, CLIPPER_PRECISION } from '../../../constants/d2Constants.js';
|
2017-11-14 16:01:04 +01:00
|
|
|
import { shapeToPoints, applyMatrixOnShape, pathToVectorPath } from '../../../shape/shapeDataUtils.js';
|
2017-11-14 16:09:12 +01:00
|
|
|
import { addObject } from '../../../reducer/objectReducers.js';
|
2017-11-14 15:27:48 +01:00
|
|
|
import subtractShapeFromState from '../../../utils/subtractShapeFromState.js';
|
|
|
|
import { getColor, getFirst, filterType, getObjectsFromIds } from '../../../utils/objectSelectors.js';
|
2017-11-14 16:01:04 +01:00
|
|
|
import createDebug from 'debug';
|
2017-11-14 15:27:48 +01:00
|
|
|
const debug = createDebug('d3d:reducer:bucket');
|
|
|
|
|
|
|
|
const MITER_LIMIT = 30.0;
|
|
|
|
|
|
|
|
export default function bucketReducer(state, action) {
|
|
|
|
if (action.log !== false) debug(action.type);
|
|
|
|
|
|
|
|
switch (action.type) {
|
|
|
|
case actions.D2_TAP:
|
2017-11-14 16:01:04 +01:00
|
|
|
const color = state.context.color;
|
|
|
|
// if (experimentalColorPicker) {
|
|
|
|
// const imageColor = getColor(
|
|
|
|
// getFirst(
|
|
|
|
// filterType(
|
|
|
|
// getObjectsFromIds(state, action.objects),
|
|
|
|
// 'IMAGE_GUIDE'
|
|
|
|
// )
|
|
|
|
// ),
|
|
|
|
// action.position,
|
|
|
|
// action.screenMatrixZoom
|
|
|
|
// );
|
|
|
|
// if (imageColor !== null) color = imageColor;
|
|
|
|
// }
|
2017-11-14 15:27:48 +01:00
|
|
|
|
|
|
|
// if clicked on a filled shape change shape color
|
|
|
|
const filledPathIndex = action.objects.findIndex(id => (
|
|
|
|
state.objectsById[id].fill &&
|
|
|
|
SHAPE_TYPE_PROPERTIES[state.objectsById[id].type].tools[state.d2.tool]
|
|
|
|
));
|
|
|
|
if (filledPathIndex !== -1) {
|
|
|
|
const id = action.objects[filledPathIndex];
|
|
|
|
return update(state, {
|
|
|
|
objectsById: { [id]: { color: { $set: color } } }
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise fill paths
|
|
|
|
const {
|
|
|
|
screenMatrixContainer,
|
|
|
|
screenMatrixZoom
|
|
|
|
} = action;
|
|
|
|
|
|
|
|
// convert mouse position to container space
|
|
|
|
// discard screen matrix zoom and apply screen matrix container
|
|
|
|
const matrix = screenMatrixZoom.inverseMatrix().multiplyMatrix(screenMatrixContainer);
|
|
|
|
const position = action.position.applyMatrix(matrix);
|
|
|
|
|
|
|
|
const paths = getPaths(state, screenMatrixContainer);
|
|
|
|
|
|
|
|
const result = fillPath(paths, position, {
|
|
|
|
lineWidth: LINE_WIDTH,
|
|
|
|
miterLimit: MITER_LIMIT,
|
|
|
|
fillOffset: 'outside'
|
|
|
|
});
|
|
|
|
// TODO
|
|
|
|
// reduce number of points of result, sometimes result has 900+ points
|
|
|
|
|
|
|
|
if (result.length === 0) return state;
|
|
|
|
|
|
|
|
const fillPaths = result
|
|
|
|
.map(pathToVectorPath)
|
|
|
|
.map(path => {
|
|
|
|
path.push(path[0].clone());
|
|
|
|
return path;
|
|
|
|
});
|
|
|
|
|
|
|
|
const fillShape = new ClipperShape(fillPaths, true, true, true)
|
|
|
|
.offset(LINE_WIDTH / 2.0, {
|
|
|
|
joinType: 'jtMiter',
|
|
|
|
endType: 'etClosedPolygon',
|
|
|
|
miterLimit: MITER_LIMIT
|
|
|
|
});
|
|
|
|
|
|
|
|
state = subtractShapeFromState(state, fillShape, state.d2.tool, {
|
|
|
|
matrix: screenMatrixContainer,
|
|
|
|
skipCompoundPath: true,
|
|
|
|
scale: CLIPPER_PRECISION
|
|
|
|
});
|
|
|
|
|
|
|
|
return addCompoundPathToState(state, fillPaths, screenMatrixContainer.inverseMatrix(), color);
|
|
|
|
default:
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getPaths(state, screenMatrixContainer) {
|
|
|
|
const paths = [];
|
|
|
|
|
|
|
|
for (const id of state.spaces[state.activeSpace].objectIds) {
|
|
|
|
const shapeData = state.objectsById[id];
|
|
|
|
|
|
|
|
if (!SHAPE_TYPE_PROPERTIES[shapeData.type].tools[state.d2.tool]) continue;
|
|
|
|
|
|
|
|
const shapes = shapeToPoints(shapeData);
|
|
|
|
for (let i = 0; i < shapes.length; i ++) {
|
|
|
|
const { points, holes } = shapes[i];
|
|
|
|
const matrix = shapeData.transform.multiplyMatrix(screenMatrixContainer);
|
|
|
|
const shape = applyMatrixOnShape([points, ...holes], matrix);
|
|
|
|
|
|
|
|
paths.push(...shape);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return paths;
|
|
|
|
}
|
|
|
|
|
|
|
|
function addCompoundPathToState(state, paths, transform, color) {
|
|
|
|
const [points, ...holes] = paths;
|
|
|
|
return addObject(state, {
|
|
|
|
type: 'COMPOUND_PATH',
|
|
|
|
transform,
|
|
|
|
points,
|
|
|
|
holes,
|
|
|
|
color
|
|
|
|
});
|
|
|
|
}
|