mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2025-01-10 14:42:13 -05:00
Handle pointed and curved
This commit is contained in:
parent
0fc9fd151d
commit
07b30aa55b
4 changed files with 113 additions and 12 deletions
|
@ -95,18 +95,18 @@ const ModeToolsComponent = props => {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(props.className, styles.modeTools)}>
|
<div className={classNames(props.className, styles.modeTools)}>
|
||||||
<LabeledIconButton
|
<LabeledIconButton
|
||||||
disabled={!props.hasSelectedPoints}
|
disabled={!props.hasSelectedUncurvedPoints}
|
||||||
imgAlt="Curved Point Icon"
|
imgAlt="Curved Point Icon"
|
||||||
imgSrc={curvedPointIcon}
|
imgSrc={curvedPointIcon}
|
||||||
title="Curved"
|
title="Curved"
|
||||||
onClick={function () {}}
|
onClick={props.onCurvePoints}
|
||||||
/>
|
/>
|
||||||
<LabeledIconButton
|
<LabeledIconButton
|
||||||
disabled={!props.hasSelectedPoints}
|
disabled={!props.hasSelectedUnpointedPoints}
|
||||||
imgAlt="Straight Point Icon"
|
imgAlt="Straight Point Icon"
|
||||||
imgSrc={straightPointIcon}
|
imgSrc={straightPointIcon}
|
||||||
title="Pointed"
|
title="Pointed"
|
||||||
onClick={function () {}}
|
onClick={props.onPointPoints}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -154,13 +154,16 @@ ModeToolsComponent.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
clipboardItems: PropTypes.arrayOf(PropTypes.array),
|
clipboardItems: PropTypes.arrayOf(PropTypes.array),
|
||||||
eraserValue: PropTypes.number,
|
eraserValue: PropTypes.number,
|
||||||
hasSelectedPoints: PropTypes.bool,
|
hasSelectedUncurvedPoints: PropTypes.bool,
|
||||||
|
hasSelectedUnpointedPoints: PropTypes.bool,
|
||||||
intl: intlShape.isRequired,
|
intl: intlShape.isRequired,
|
||||||
mode: PropTypes.string.isRequired,
|
mode: PropTypes.string.isRequired,
|
||||||
onBrushSliderChange: PropTypes.func,
|
onBrushSliderChange: PropTypes.func,
|
||||||
onCopyToClipboard: PropTypes.func.isRequired,
|
onCopyToClipboard: PropTypes.func.isRequired,
|
||||||
|
onCurvePoints: PropTypes.func.isRequired,
|
||||||
onEraserSliderChange: PropTypes.func,
|
onEraserSliderChange: PropTypes.func,
|
||||||
onPasteFromClipboard: PropTypes.func.isRequired,
|
onPasteFromClipboard: PropTypes.func.isRequired,
|
||||||
|
onPointPoints: PropTypes.func.isRequired,
|
||||||
selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item))
|
selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,21 +13,115 @@ class ModeTools extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
bindAll(this, [
|
bindAll(this, [
|
||||||
'hasSelectedPoints',
|
'_getSelectedUncurvedPoints',
|
||||||
|
'_getSelectedUnpointedPoints',
|
||||||
|
'hasSelectedUncurvedPoints',
|
||||||
|
'hasSelectedUnpointedPoints',
|
||||||
'handleCopyToClipboard',
|
'handleCopyToClipboard',
|
||||||
'handlePasteFromClipboard'
|
'handleCurvePoints',
|
||||||
|
'handlePasteFromClipboard',
|
||||||
|
'handlePointPoints'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
hasSelectedPoints () {
|
_getSelectedUncurvedPoints () {
|
||||||
|
const items = [];
|
||||||
const selectedItems = getSelectedLeafItems();
|
const selectedItems = getSelectedLeafItems();
|
||||||
for (const item of selectedItems) {
|
for (const item of selectedItems) {
|
||||||
|
if (!item.segments) continue;
|
||||||
for (const seg of item.segments) {
|
for (const seg of item.segments) {
|
||||||
if (seg.selected) {
|
if (seg.selected) {
|
||||||
return true;
|
const prev = seg.getPrevious();
|
||||||
|
const next = seg.getNext();
|
||||||
|
const isCurved =
|
||||||
|
(!prev || seg.handleIn.length > 0) &&
|
||||||
|
(!next || seg.handleOut.length > 0) &&
|
||||||
|
(prev && next ? seg.handleOut.isColinear(seg.handleIn) : true);
|
||||||
|
if (!isCurved) items.push(seg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return items;
|
||||||
|
}
|
||||||
|
_getSelectedUnpointedPoints () {
|
||||||
|
const points = [];
|
||||||
|
const selectedItems = getSelectedLeafItems();
|
||||||
|
for (const item of selectedItems) {
|
||||||
|
if (!item.segments) continue;
|
||||||
|
for (const seg of item.segments) {
|
||||||
|
if (seg.selected) {
|
||||||
|
if (seg.handleIn.length > 0 || seg.handleOut.length > 0) {
|
||||||
|
points.push(seg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
hasSelectedUncurvedPoints () {
|
||||||
|
const points = this._getSelectedUncurvedPoints();
|
||||||
|
return points.length > 0;
|
||||||
|
}
|
||||||
|
hasSelectedUnpointedPoints () {
|
||||||
|
const points = this._getSelectedUnpointedPoints();
|
||||||
|
return points.length > 0;
|
||||||
|
}
|
||||||
|
handleCurvePoints () {
|
||||||
|
let changed;
|
||||||
|
const points = this._getSelectedUncurvedPoints();
|
||||||
|
for (const point of points) {
|
||||||
|
const prev = point.getPrevious();
|
||||||
|
const next = point.getNext();
|
||||||
|
const noHandles = point.handleIn.length === 0 && point.handleOut.length === 0;
|
||||||
|
if (!prev && !next) {
|
||||||
|
continue;
|
||||||
|
} else if (prev && (!next || noHandles)) {
|
||||||
|
// Point is end point or has no handles
|
||||||
|
// Direction is average of normal at the point and direction to prev point
|
||||||
|
// Lenth is curve length / 2
|
||||||
|
point.handleIn = (prev.getCurve().getNormalAtTime(1)
|
||||||
|
.add(prev.point.subtract(point.point).normalize()))
|
||||||
|
.normalize()
|
||||||
|
.multiply(prev.getCurve().length / 2);
|
||||||
|
} else if (next && !prev) {
|
||||||
|
// Point is start point
|
||||||
|
// Direction is average of normal at the point and direction to next point
|
||||||
|
// Lenth is curve length / 2
|
||||||
|
point.handleOut = (point.getCurve().getNormalAtTime(0)
|
||||||
|
.add(next.point.subtract(point.point).normalize()))
|
||||||
|
.normalize()
|
||||||
|
.multiply(point.getCurve().length / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point guaranteed to have a handle now. Make the second handle match the length and direction of first.
|
||||||
|
// This defines a curved point.
|
||||||
|
if (point.handleIn.length > 0 && next) {
|
||||||
|
point.handleOut = point.handleIn.multiply(-1);
|
||||||
|
} else if (point.handleOut.length > 0 && prev) {
|
||||||
|
point.handleIn = point.handleOut.multiply(-1);
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
this.props.setSelectedItems();
|
||||||
|
this.props.onUpdateSvg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handlePointPoints () {
|
||||||
|
let changed;
|
||||||
|
const points = this._getSelectedUnpointedPoints();
|
||||||
|
for (const point of points) {
|
||||||
|
const noHandles = point.handleIn.length === 0 && point.handleOut.length === 0;
|
||||||
|
if (!noHandles) {
|
||||||
|
point.handleIn = null;
|
||||||
|
point.handleOut = null;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
debugger;
|
||||||
|
this.props.setSelectedItems();
|
||||||
|
this.props.onUpdateSvg();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
handleCopyToClipboard () {
|
handleCopyToClipboard () {
|
||||||
const selectedItems = getSelectedRootItems();
|
const selectedItems = getSelectedRootItems();
|
||||||
|
@ -62,9 +156,12 @@ class ModeTools extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<ModeToolsComponent
|
<ModeToolsComponent
|
||||||
hasSelectedPoints={this.hasSelectedPoints()}
|
hasSelectedUncurvedPoints={this.hasSelectedUncurvedPoints()}
|
||||||
|
hasSelectedUnpointedPoints={this.hasSelectedUnpointedPoints()}
|
||||||
onCopyToClipboard={this.handleCopyToClipboard}
|
onCopyToClipboard={this.handleCopyToClipboard}
|
||||||
|
onCurvePoints={this.handleCurvePoints}
|
||||||
onPasteFromClipboard={this.handlePasteFromClipboard}
|
onPasteFromClipboard={this.handlePasteFromClipboard}
|
||||||
|
onPointPoints={this.handlePointPoints}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,7 @@ class HandleTool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (moved) {
|
if (moved) {
|
||||||
|
this.setSelectedItems();
|
||||||
this.onUpdateSvg();
|
this.onUpdateSvg();
|
||||||
}
|
}
|
||||||
this.selectedItems = [];
|
this.selectedItems = [];
|
||||||
|
|
|
@ -14,7 +14,7 @@ const reducer = function (state, action) {
|
||||||
if (action.selectedItems.length !== state.length) {
|
if (action.selectedItems.length !== state.length) {
|
||||||
return action.selectedItems;
|
return action.selectedItems;
|
||||||
}
|
}
|
||||||
// Shallow equality check (we may need to update this later for more granularity)
|
// Shallow equality check
|
||||||
for (let i = 0; i < action.selectedItems.length; i++) {
|
for (let i = 0; i < action.selectedItems.length; i++) {
|
||||||
if (action.selectedItems[i] !== state[i]) {
|
if (action.selectedItems[i] !== state[i]) {
|
||||||
return action.selectedItems;
|
return action.selectedItems;
|
||||||
|
|
Loading…
Reference in a new issue