mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
commit
29ca6c66cf
7 changed files with 157 additions and 148 deletions
|
@ -4,11 +4,12 @@ import React from 'react';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import bindAll from 'lodash.bindall';
|
import bindAll from 'lodash.bindall';
|
||||||
import Modes from '../modes/modes';
|
import Modes from '../modes/modes';
|
||||||
import {clearSelection, getSelectedLeafItems} from '../helper/selection';
|
import {clearSelection} from '../helper/selection';
|
||||||
import {MIXED} from '../helper/style-path';
|
import {endPointHit, touching} from '../helper/snapping';
|
||||||
|
import {drawHitPoint, removeHitPoint} from '../helper/guides';
|
||||||
|
import {stylePath} from '../helper/style-path';
|
||||||
import {changeMode} from '../reducers/modes';
|
import {changeMode} from '../reducers/modes';
|
||||||
import {changeStrokeWidth} from '../reducers/stroke-width';
|
import {clearSelectedItems} from '../reducers/selected-items';
|
||||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
|
||||||
|
|
||||||
import LineModeComponent from '../components/line-mode/line-mode.jsx';
|
import LineModeComponent from '../components/line-mode/line-mode.jsx';
|
||||||
|
|
||||||
|
@ -21,13 +22,11 @@ class LineMode extends React.Component {
|
||||||
bindAll(this, [
|
bindAll(this, [
|
||||||
'activateTool',
|
'activateTool',
|
||||||
'deactivateTool',
|
'deactivateTool',
|
||||||
|
'drawHitPoint',
|
||||||
'onMouseDown',
|
'onMouseDown',
|
||||||
'onMouseMove',
|
'onMouseMove',
|
||||||
'onMouseDrag',
|
'onMouseDrag',
|
||||||
'onMouseUp',
|
'onMouseUp'
|
||||||
'toleranceSquared',
|
|
||||||
'findLineEnd',
|
|
||||||
'onScroll'
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
|
@ -47,7 +46,6 @@ class LineMode extends React.Component {
|
||||||
}
|
}
|
||||||
activateTool () {
|
activateTool () {
|
||||||
clearSelection(this.props.clearSelectedItems);
|
clearSelection(this.props.clearSelectedItems);
|
||||||
this.props.canvas.addEventListener('mousewheel', this.onScroll);
|
|
||||||
this.tool = new paper.Tool();
|
this.tool = new paper.Tool();
|
||||||
|
|
||||||
this.path = null;
|
this.path = null;
|
||||||
|
@ -73,197 +71,135 @@ class LineMode extends React.Component {
|
||||||
this.tool.activate();
|
this.tool.activate();
|
||||||
}
|
}
|
||||||
onMouseDown (event) {
|
onMouseDown (event) {
|
||||||
// Deselect old path
|
if (event.event.button > 0) return; // only first mouse button
|
||||||
if (this.path) {
|
|
||||||
this.path.setSelected(false);
|
|
||||||
this.path = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If you click near a point, continue that line instead of making a new line
|
// If you click near a point, continue that line instead of making a new line
|
||||||
this.hitResult = this.findLineEnd(event.point);
|
this.hitResult = endPointHit(event.point, LineMode.SNAP_TOLERANCE);
|
||||||
if (this.hitResult) {
|
if (this.hitResult) {
|
||||||
this.path = this.hitResult.path;
|
this.path = this.hitResult.path;
|
||||||
|
stylePath(this.path, this.props.colorState.strokeColor, this.props.colorState.strokeWidth);
|
||||||
if (this.hitResult.isFirst) {
|
if (this.hitResult.isFirst) {
|
||||||
this.path.reverse();
|
this.path.reverse();
|
||||||
}
|
}
|
||||||
this.path.lastSegment.setSelected(true);
|
|
||||||
this.path.add(this.hitResult.segment); // Add second point, which is what will move when dragged
|
this.path.lastSegment.handleOut = null; // Make sure added line isn't made curvy
|
||||||
this.path.lastSegment.handleOut = null; // Make sure line isn't curvy
|
this.path.add(this.hitResult.segment.point); // Add second point, which is what will move when dragged
|
||||||
this.path.lastSegment.handleIn = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not near other path, start a new path
|
// If not near other path, start a new path
|
||||||
if (!this.path) {
|
if (!this.path) {
|
||||||
this.path = new paper.Path();
|
this.path = new paper.Path();
|
||||||
|
stylePath(this.path, this.props.colorState.strokeColor, this.props.colorState.strokeWidth);
|
||||||
this.path.setStrokeColor(
|
|
||||||
this.props.colorState.strokeColor === MIXED ? 'black' : this.props.colorState.strokeColor);
|
|
||||||
// Make sure a visible line is drawn
|
|
||||||
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);
|
this.path.add(event.point);
|
||||||
this.path.add(event.point); // Add second point, which is what will move when dragged
|
this.path.add(event.point); // Add second point, which is what will move when dragged
|
||||||
paper.view.draw();
|
paper.view.draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
drawHitPoint (hitResult) {
|
||||||
|
// If near another path's endpoint, draw hit point to indicate that paths would merge
|
||||||
|
if (hitResult) {
|
||||||
|
const hitPath = hitResult.path;
|
||||||
|
if (hitResult.isFirst) {
|
||||||
|
drawHitPoint(hitPath.firstSegment.point);
|
||||||
|
} else {
|
||||||
|
drawHitPoint(hitPath.lastSegment.point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
onMouseMove (event) {
|
onMouseMove (event) {
|
||||||
|
if (this.hitResult) {
|
||||||
|
removeHitPoint();
|
||||||
|
}
|
||||||
|
this.hitResult = endPointHit(event.point, LineMode.SNAP_TOLERANCE);
|
||||||
|
this.drawHitPoint(this.hitResult);
|
||||||
|
}
|
||||||
|
onMouseDrag (event) {
|
||||||
|
if (event.event.button > 0) return; // only first mouse button
|
||||||
|
|
||||||
// If near another path's endpoint, or this path's beginpoint, clip to it to suggest
|
// If near another path's endpoint, or this path's beginpoint, clip to it to suggest
|
||||||
// joining/closing the paths.
|
// joining/closing the paths.
|
||||||
if (this.hitResult) {
|
if (this.hitResult) {
|
||||||
this.hitResult.path.setSelected(false);
|
removeHitPoint();
|
||||||
this.hitResult = null;
|
this.hitResult = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.path &&
|
if (this.path &&
|
||||||
!this.path.closed &&
|
!this.path.closed &&
|
||||||
this.path.firstSegment.point.getDistance(event.point, true) < this.toleranceSquared()) {
|
|
||||||
this.hitResult = {
|
|
||||||
path: this.path,
|
|
||||||
segment: this.path.firstSegment,
|
|
||||||
isFirst: true
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this.hitResult = this.findLineEnd(event.point);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hitResult) {
|
|
||||||
const hitPath = this.hitResult.path;
|
|
||||||
hitPath.setSelected(true);
|
|
||||||
if (this.hitResult.isFirst) {
|
|
||||||
hitPath.firstSegment.setSelected(true);
|
|
||||||
} else {
|
|
||||||
hitPath.lastSegment.setSelected(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onMouseDrag (event) {
|
|
||||||
// If near another path's endpoint, or this path's beginpoint, clip to it to suggest
|
|
||||||
// joining/closing the paths.
|
|
||||||
if (this.hitResult && this.hitResult.path !== this.path) this.hitResult.path.setSelected(false);
|
|
||||||
this.hitResult = null;
|
|
||||||
|
|
||||||
if (this.path &&
|
|
||||||
this.path.segments.length > 3 &&
|
this.path.segments.length > 3 &&
|
||||||
this.path.firstSegment.point.getDistance(event.point, true) < this.toleranceSquared()) {
|
touching(this.path.firstSegment.point, event.point, LineMode.SNAP_TOLERANCE)) {
|
||||||
this.hitResult = {
|
this.hitResult = {
|
||||||
path: this.path,
|
path: this.path,
|
||||||
segment: this.path.firstSegment,
|
segment: this.path.firstSegment,
|
||||||
isFirst: true
|
isFirst: true
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
this.hitResult = this.findLineEnd(event.point, this.path);
|
this.hitResult = endPointHit(event.point, LineMode.SNAP_TOLERANCE, this.path);
|
||||||
if (this.hitResult) {
|
|
||||||
const hitPath = this.hitResult.path;
|
|
||||||
hitPath.setSelected(true);
|
|
||||||
if (this.hitResult.isFirst) {
|
|
||||||
hitPath.firstSegment.setSelected(true);
|
|
||||||
} else {
|
|
||||||
hitPath.lastSegment.setSelected(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// snapping
|
// snapping
|
||||||
if (this.path) {
|
if (this.hitResult) {
|
||||||
if (this.hitResult) {
|
this.drawHitPoint(this.hitResult);
|
||||||
this.path.lastSegment.point = this.hitResult.segment.point;
|
this.path.lastSegment.point = this.hitResult.segment.point;
|
||||||
} else {
|
} else {
|
||||||
this.path.lastSegment.point = event.point;
|
this.path.lastSegment.point = event.point;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMouseUp (event) {
|
onMouseUp (event) {
|
||||||
|
if (event.event.button > 0) return; // only first mouse button
|
||||||
|
|
||||||
// If I single clicked, don't do anything
|
// If I single clicked, don't do anything
|
||||||
if (this.path.segments.length < 2 ||
|
if (this.path.segments.length < 2 ||
|
||||||
(this.path.segments.length === 2 &&
|
(this.path.segments.length === 2 &&
|
||||||
this.path.firstSegment.point.getDistance(event.point, true) < this.toleranceSquared())) {
|
touching(this.path.firstSegment.point, event.point, LineMode.SNAP_TOLERANCE) &&
|
||||||
|
!this.hitResult)) { // Let lines be short if you're connecting them
|
||||||
this.path.remove();
|
this.path.remove();
|
||||||
this.path = null;
|
this.path = null;
|
||||||
// TODO don't erase the line if both ends are snapped to different points
|
|
||||||
return;
|
return;
|
||||||
} else if (
|
} else if (!this.hitResult &&
|
||||||
this.path.lastSegment.point.getDistance(this.path.segments[this.path.segments.length - 2].point, true) <
|
touching(this.path.lastSegment.point, this.path.segments[this.path.segments.length - 2].point,
|
||||||
this.toleranceSquared()) {
|
LineMode.SNAP_TOLERANCE)) {
|
||||||
|
// Single click or short drag on an existing path end point
|
||||||
this.path.removeSegment(this.path.segments.length - 1);
|
this.path.removeSegment(this.path.segments.length - 1);
|
||||||
|
this.path = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If I intersect other line end points, join or close
|
// If I intersect other line end points, join or close
|
||||||
if (this.hitResult) {
|
if (this.hitResult) {
|
||||||
this.path.removeSegment(this.path.segments.length - 1);
|
this.path.removeSegment(this.path.segments.length - 1);
|
||||||
if (this.path.firstSegment === this.hitResult.segment) {
|
if (this.path.firstSegment.point.equals(this.hitResult.segment.point)) {
|
||||||
|
this.path.firstSegment.handleIn = null; // Make sure added line isn't made curvy
|
||||||
// close path
|
// close path
|
||||||
this.path.closed = true;
|
this.path.closed = true;
|
||||||
this.path.setSelected(false);
|
|
||||||
} else {
|
} else {
|
||||||
// joining two paths
|
// joining two paths
|
||||||
if (!this.hitResult.isFirst) {
|
if (!this.hitResult.isFirst) {
|
||||||
this.hitResult.path.reverse();
|
this.hitResult.path.reverse();
|
||||||
}
|
}
|
||||||
|
this.hitResult.path.firstSegment.handleIn = null; // Make sure added line isn't made curvy
|
||||||
this.path.join(this.hitResult.path);
|
this.path.join(this.hitResult.path);
|
||||||
}
|
}
|
||||||
|
removeHitPoint();
|
||||||
this.hitResult = null;
|
this.hitResult = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.setSelectedItems();
|
|
||||||
if (this.path) {
|
if (this.path) {
|
||||||
this.props.onUpdateSvg();
|
this.props.onUpdateSvg();
|
||||||
|
this.path = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toleranceSquared () {
|
|
||||||
return Math.pow(LineMode.SNAP_TOLERANCE / paper.view.zoom, 2);
|
|
||||||
}
|
|
||||||
findLineEnd (point, excludePath) {
|
|
||||||
const lines = paper.project.getItems({
|
|
||||||
class: paper.Path
|
|
||||||
});
|
|
||||||
// Prefer more recent lines
|
|
||||||
for (let i = lines.length - 1; i >= 0; i--) {
|
|
||||||
if (lines[i].closed) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (excludePath && lines[i] === excludePath) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (lines[i].firstSegment &&
|
|
||||||
lines[i].firstSegment.point.getDistance(point, true) < this.toleranceSquared()) {
|
|
||||||
return {
|
|
||||||
path: lines[i],
|
|
||||||
segment: lines[i].firstSegment,
|
|
||||||
isFirst: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (lines[i].lastSegment && lines[i].lastSegment.point.getDistance(point, true) < this.toleranceSquared()) {
|
|
||||||
return {
|
|
||||||
path: lines[i],
|
|
||||||
segment: lines[i].lastSegment,
|
|
||||||
isFirst: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
deactivateTool () {
|
deactivateTool () {
|
||||||
this.props.canvas.removeEventListener('mousewheel', this.onScroll);
|
this.props.canvas.removeEventListener('mousewheel', this.onScroll);
|
||||||
this.tool.remove();
|
this.tool.remove();
|
||||||
this.tool = null;
|
this.tool = null;
|
||||||
this.hitResult = null;
|
if (this.hitResult) {
|
||||||
|
removeHitPoint();
|
||||||
|
this.hitResult = null;
|
||||||
|
}
|
||||||
if (this.path) {
|
if (this.path) {
|
||||||
this.path.setSelected(false);
|
|
||||||
this.path = null;
|
this.path = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onScroll (event) {
|
|
||||||
if (event.deltaY < 0) {
|
|
||||||
this.props.changeStrokeWidth(this.props.colorState.strokeWidth + 1);
|
|
||||||
} else if (event.deltaY > 0 && this.props.colorState.strokeWidth > 1) {
|
|
||||||
this.props.changeStrokeWidth(this.props.colorState.strokeWidth - 1);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<LineModeComponent
|
<LineModeComponent
|
||||||
|
@ -276,7 +212,6 @@ class LineMode extends React.Component {
|
||||||
|
|
||||||
LineMode.propTypes = {
|
LineMode.propTypes = {
|
||||||
canvas: PropTypes.instanceOf(Element).isRequired,
|
canvas: PropTypes.instanceOf(Element).isRequired,
|
||||||
changeStrokeWidth: PropTypes.func.isRequired,
|
|
||||||
clearSelectedItems: PropTypes.func.isRequired,
|
clearSelectedItems: PropTypes.func.isRequired,
|
||||||
colorState: PropTypes.shape({
|
colorState: PropTypes.shape({
|
||||||
fillColor: PropTypes.string,
|
fillColor: PropTypes.string,
|
||||||
|
@ -285,8 +220,7 @@ LineMode.propTypes = {
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
handleMouseDown: PropTypes.func.isRequired,
|
handleMouseDown: PropTypes.func.isRequired,
|
||||||
isLineModeActive: PropTypes.bool.isRequired,
|
isLineModeActive: PropTypes.bool.isRequired,
|
||||||
onUpdateSvg: PropTypes.func.isRequired,
|
onUpdateSvg: PropTypes.func.isRequired
|
||||||
setSelectedItems: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
@ -294,15 +228,9 @@ const mapStateToProps = state => ({
|
||||||
isLineModeActive: state.scratchPaint.mode === Modes.LINE
|
isLineModeActive: state.scratchPaint.mode === Modes.LINE
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
changeStrokeWidth: strokeWidth => {
|
|
||||||
dispatch(changeStrokeWidth(strokeWidth));
|
|
||||||
},
|
|
||||||
clearSelectedItems: () => {
|
clearSelectedItems: () => {
|
||||||
dispatch(clearSelectedItems());
|
dispatch(clearSelectedItems());
|
||||||
},
|
},
|
||||||
setSelectedItems: () => {
|
|
||||||
dispatch(setSelectedItems(getSelectedLeafItems()));
|
|
||||||
},
|
|
||||||
handleMouseDown: () => {
|
handleMouseDown: () => {
|
||||||
dispatch(changeMode(Modes.LINE));
|
dispatch(changeMode(Modes.LINE));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Broadbrush based on http://paperjs.org/tutorials/interaction/working-with-mouse-vectors/
|
// Broadbrush based on http://paperjs.org/tutorials/interaction/working-with-mouse-vectors/
|
||||||
import paper from '@scratch/paper';
|
import paper from '@scratch/paper';
|
||||||
import {stylePath} from '../../helper/style-path';
|
import {styleBlob} from '../../helper/style-path';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broad brush functions to add as listeners on the mouse. Call them when the corresponding mouse event happens
|
* Broad brush functions to add as listeners on the mouse. Call them when the corresponding mouse event happens
|
||||||
|
@ -25,7 +25,7 @@ class BroadBrushHelper {
|
||||||
if (event.event.button > 0) return; // only first mouse button
|
if (event.event.button > 0) return; // only first mouse button
|
||||||
|
|
||||||
this.finalPath = new paper.Path();
|
this.finalPath = new paper.Path();
|
||||||
stylePath(this.finalPath, options);
|
styleBlob(this.finalPath, options);
|
||||||
this.finalPath.add(event.point);
|
this.finalPath.add(event.point);
|
||||||
this.lastPoint = this.secondLastPoint = event.point;
|
this.lastPoint = this.secondLastPoint = event.point;
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ class BroadBrushHelper {
|
||||||
center: event.point,
|
center: event.point,
|
||||||
radius: options.brushSize / 2
|
radius: options.brushSize / 2
|
||||||
});
|
});
|
||||||
stylePath(this.finalPath, options);
|
styleBlob(this.finalPath, options);
|
||||||
} else {
|
} else {
|
||||||
const step = (event.point.subtract(this.lastPoint)).normalize(options.brushSize / 2);
|
const step = (event.point.subtract(this.lastPoint)).normalize(options.brushSize / 2);
|
||||||
step.angle += 90;
|
step.angle += 90;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import paper from '@scratch/paper';
|
import paper from '@scratch/paper';
|
||||||
import {stylePath} from '../../helper/style-path';
|
import {styleBlob} from '../../helper/style-path';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Segment brush functions to add as listeners on the mouse. Call them when the corresponding mouse event happens
|
* Segment brush functions to add as listeners on the mouse. Call them when the corresponding mouse event happens
|
||||||
|
@ -32,7 +32,7 @@ class SegmentBrushHelper {
|
||||||
radius: options.brushSize / 2
|
radius: options.brushSize / 2
|
||||||
});
|
});
|
||||||
this.finalPath = this.firstCircle;
|
this.finalPath = this.firstCircle;
|
||||||
stylePath(this.finalPath, options);
|
styleBlob(this.finalPath, options);
|
||||||
this.lastPoint = event.point;
|
this.lastPoint = event.point;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ class SegmentBrushHelper {
|
||||||
|
|
||||||
const path = new paper.Path();
|
const path = new paper.Path();
|
||||||
|
|
||||||
stylePath(path, options);
|
styleBlob(path, options);
|
||||||
|
|
||||||
// Add handles to round the end caps
|
// Add handles to round the end caps
|
||||||
path.add(new paper.Segment(this.lastPoint.subtract(step), handleVec.multiply(-1), handleVec));
|
path.add(new paper.Segment(this.lastPoint.subtract(step), handleVec.multiply(-1), handleVec));
|
||||||
|
|
|
@ -58,12 +58,8 @@ const rectSelect = function (event, color) {
|
||||||
return rect;
|
return rect;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getGuideColor = function (colorName) {
|
const getGuideColor = function () {
|
||||||
if (colorName === 'blue') {
|
return GUIDE_BLUE;
|
||||||
return GUIDE_BLUE;
|
|
||||||
} else if (colorName === 'grey') {
|
|
||||||
return GUIDE_GREY;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const _removePaperItemsByDataTags = function (tags) {
|
const _removePaperItemsByDataTags = function (tags) {
|
||||||
|
@ -96,12 +92,30 @@ const removeAllGuides = function () {
|
||||||
_removePaperItemsByTags(['guide']);
|
_removePaperItemsByTags(['guide']);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeHitPoint = function () {
|
||||||
|
_removePaperItemsByDataTags(['isHitPoint']);
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawHitPoint = function (point) {
|
||||||
|
removeHitPoint();
|
||||||
|
if (point) {
|
||||||
|
const hitPoint = paper.Path.Circle(point, 4 /* radius */);
|
||||||
|
hitPoint.strokeColor = GUIDE_BLUE;
|
||||||
|
hitPoint.fillColor = new paper.Color(1, 1, 1, 0.5);
|
||||||
|
hitPoint.parent = getGuideLayer();
|
||||||
|
hitPoint.data.isHitPoint = true;
|
||||||
|
hitPoint.data.isHelperItem = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
hoverItem,
|
hoverItem,
|
||||||
hoverBounds,
|
hoverBounds,
|
||||||
rectSelect,
|
rectSelect,
|
||||||
removeAllGuides,
|
removeAllGuides,
|
||||||
removeHelperItems,
|
removeHelperItems,
|
||||||
|
drawHitPoint,
|
||||||
|
removeHitPoint,
|
||||||
getGuideColor,
|
getGuideColor,
|
||||||
setDefaultGuideStyle
|
setDefaultGuideStyle
|
||||||
};
|
};
|
||||||
|
|
|
@ -170,7 +170,7 @@ class BoundingBoxTool {
|
||||||
noSelect: true,
|
noSelect: true,
|
||||||
noHover: true
|
noHover: true
|
||||||
};
|
};
|
||||||
rotHandle.fillColor = getGuideColor('blue');
|
rotHandle.fillColor = getGuideColor();
|
||||||
rotHandle.parent = getGuideLayer();
|
rotHandle.parent = getGuideLayer();
|
||||||
this.boundsRotHandles[index] = rotHandle;
|
this.boundsRotHandles[index] = rotHandle;
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ class BoundingBoxTool {
|
||||||
noHover: true
|
noHover: true
|
||||||
},
|
},
|
||||||
size: [size / paper.view.zoom, size / paper.view.zoom],
|
size: [size / paper.view.zoom, size / paper.view.zoom],
|
||||||
fillColor: getGuideColor('blue'),
|
fillColor: getGuideColor(),
|
||||||
parent: getGuideLayer()
|
parent: getGuideLayer()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
58
src/helper/snapping.js
Normal file
58
src/helper/snapping.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import paper from '@scratch/paper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {paper.Point} point1 point 1
|
||||||
|
* @param {paper.Point} point2 point 2
|
||||||
|
* @param {number} tolerance Distance allowed between points that are "touching"
|
||||||
|
* @return {boolean} true if points are within the tolerance distance.
|
||||||
|
*/
|
||||||
|
const touching = function (point1, point2, tolerance) {
|
||||||
|
return point1.getDistance(point2, true) < Math.pow(tolerance / paper.view.zoom, 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!paper.Point} point Point to check line endpoint hits against
|
||||||
|
* @param {!number} tolerance Distance within which it counts as a hit
|
||||||
|
* @param {?paper.Path} excludePath Path to exclude from hit test, if any. For instance, you
|
||||||
|
* are drawing a line and don't want it to snap to its own start point.
|
||||||
|
* @return {object} data about the end point of an unclosed path, if any such point is within the
|
||||||
|
* tolerance distance of the given point, or null if none exists.
|
||||||
|
*/
|
||||||
|
const endPointHit = function (point, tolerance, excludePath) {
|
||||||
|
const lines = paper.project.getItems({
|
||||||
|
class: paper.Path
|
||||||
|
});
|
||||||
|
// Prefer more recent lines
|
||||||
|
for (let i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].closed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(lines[i].parent instanceof paper.Layer)) {
|
||||||
|
// Don't connect to lines inside of groups
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (excludePath && lines[i] === excludePath) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (lines[i].firstSegment && touching(lines[i].firstSegment.point, point, tolerance)) {
|
||||||
|
return {
|
||||||
|
path: lines[i],
|
||||||
|
segment: lines[i].firstSegment,
|
||||||
|
isFirst: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (lines[i].lastSegment && touching(lines[i].lastSegment.point, point, tolerance)) {
|
||||||
|
return {
|
||||||
|
path: lines[i],
|
||||||
|
segment: lines[i].lastSegment,
|
||||||
|
isFirst: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
endPointHit,
|
||||||
|
touching
|
||||||
|
};
|
|
@ -198,7 +198,7 @@ const getColorsFromSelection = function (selectedItems) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const stylePath = function (path, options) {
|
const styleBlob = function (path, options) {
|
||||||
if (options.isEraser) {
|
if (options.isEraser) {
|
||||||
path.fillColor = 'white';
|
path.fillColor = 'white';
|
||||||
} else if (options.fillColor) {
|
} else if (options.fillColor) {
|
||||||
|
@ -209,6 +209,14 @@ const stylePath = function (path, options) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
const styleCursorPreview = function (path, options) {
|
const styleCursorPreview = function (path, options) {
|
||||||
if (options.isEraser) {
|
if (options.isEraser) {
|
||||||
path.fillColor = 'white';
|
path.fillColor = 'white';
|
||||||
|
@ -228,6 +236,7 @@ export {
|
||||||
applyStrokeWidthToSelection,
|
applyStrokeWidthToSelection,
|
||||||
getColorsFromSelection,
|
getColorsFromSelection,
|
||||||
MIXED,
|
MIXED,
|
||||||
|
styleBlob,
|
||||||
stylePath,
|
stylePath,
|
||||||
styleCursorPreview
|
styleCursorPreview
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue