mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2025-01-09 06:02:05 -05:00
commit
af29e606d8
9 changed files with 332 additions and 55 deletions
|
@ -1,27 +1,25 @@
|
|||
import React from 'react';
|
||||
|
||||
import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx';
|
||||
import PropTypes from 'prop-types';
|
||||
import ToolSelectComponent from '../tool-select-base/tool-select-base.jsx';
|
||||
|
||||
import lineIcon from './line.svg';
|
||||
|
||||
const BitLineComponent = () => (
|
||||
<ComingSoonTooltip
|
||||
place="right"
|
||||
tooltipId="bit-line-mode"
|
||||
>
|
||||
<ToolSelectComponent
|
||||
disabled
|
||||
imgDescriptor={{
|
||||
defaultMessage: 'Line',
|
||||
description: 'Label for the line tool, which draws straight line segments',
|
||||
id: 'paint.lineMode.line'
|
||||
}}
|
||||
imgSrc={lineIcon}
|
||||
isSelected={false}
|
||||
onMouseDown={function () {}} // eslint-disable-line react/jsx-no-bind
|
||||
/>
|
||||
</ComingSoonTooltip>
|
||||
const BitLineComponent = props => (
|
||||
<ToolSelectComponent
|
||||
imgDescriptor={{
|
||||
defaultMessage: 'Line',
|
||||
description: 'Label for the line tool, which draws straight line segments',
|
||||
id: 'paint.lineMode.line'
|
||||
}}
|
||||
imgSrc={lineIcon}
|
||||
isSelected={props.isSelected}
|
||||
onMouseDown={props.onMouseDown}
|
||||
/>
|
||||
);
|
||||
|
||||
BitLineComponent.propTypes = {
|
||||
isSelected: PropTypes.bool.isRequired,
|
||||
onMouseDown: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default BitLineComponent;
|
||||
|
|
|
@ -15,14 +15,15 @@ import InputGroup from '../input-group/input-group.jsx';
|
|||
import LabeledIconButton from '../labeled-icon-button/labeled-icon-button.jsx';
|
||||
import Modes from '../../lib/modes';
|
||||
import Formats from '../../lib/format';
|
||||
import {isBitmap} from '../../lib/format';
|
||||
import {isBitmap, isVector} from '../../lib/format';
|
||||
import styles from './mode-tools.css';
|
||||
|
||||
import copyIcon from './icons/copy.svg';
|
||||
import pasteIcon from './icons/paste.svg';
|
||||
|
||||
import brushIcon from '../brush-mode/brush.svg';
|
||||
import bitBrushIcon from '../bit-brush-mode/brush.svg';
|
||||
import bitLineIcon from '../bit-line-mode/line.svg';
|
||||
import brushIcon from '../brush-mode/brush.svg';
|
||||
import curvedPointIcon from './icons/curved-point.svg';
|
||||
import eraserIcon from '../eraser-mode/eraser.svg';
|
||||
import flipHorizontalIcon from './icons/flip-horizontal.svg';
|
||||
|
@ -39,6 +40,11 @@ const ModeToolsComponent = props => {
|
|||
description: 'Label for the brush size input',
|
||||
id: 'paint.modeTools.brushSize'
|
||||
},
|
||||
lineSize: {
|
||||
defaultMessage: 'Line size',
|
||||
description: 'Label for the line size input',
|
||||
id: 'paint.modeTools.lineSize'
|
||||
},
|
||||
eraserSize: {
|
||||
defaultMessage: 'Eraser size',
|
||||
description: 'Label for the eraser size input',
|
||||
|
@ -80,18 +86,22 @@ const ModeToolsComponent = props => {
|
|||
case Modes.BRUSH:
|
||||
/* falls through */
|
||||
case Modes.BIT_BRUSH:
|
||||
/* falls through */
|
||||
case Modes.BIT_LINE:
|
||||
{
|
||||
const currentBrushIcon = isBitmap(props.format) ? bitBrushIcon : brushIcon;
|
||||
const currentIcon = isVector(props.format) ? brushIcon :
|
||||
props.mode === Modes.BIT_LINE ? bitLineIcon : bitBrushIcon;
|
||||
const currentBrushValue = isBitmap(props.format) ? props.bitBrushSize : props.brushValue;
|
||||
const changeFunction = isBitmap(props.format) ? props.onBitBrushSliderChange : props.onBrushSliderChange;
|
||||
const currentMessage = props.mode === Modes.BIT_LINE ? messages.lineSize : messages.brushSize;
|
||||
return (
|
||||
<div className={classNames(props.className, styles.modeTools)}>
|
||||
<div>
|
||||
<img
|
||||
alt={props.intl.formatMessage(messages.brushSize)}
|
||||
alt={props.intl.formatMessage(currentMessage)}
|
||||
className={styles.modeToolsIcon}
|
||||
draggable={false}
|
||||
src={currentBrushIcon}
|
||||
src={currentIcon}
|
||||
/>
|
||||
</div>
|
||||
<LiveInput
|
||||
|
|
|
@ -11,7 +11,7 @@ import {shouldShowGroup, shouldShowUngroup} from '../../helper/group';
|
|||
import {shouldShowBringForward, shouldShowSendBackward} from '../../helper/order';
|
||||
|
||||
import BitBrushMode from '../../containers/bit-brush-mode.jsx';
|
||||
import BitLineMode from '../../components/bit-line-mode/bit-line-mode.jsx';
|
||||
import BitLineMode from '../../containers/bit-line-mode.jsx';
|
||||
import BitOvalMode from '../../components/bit-oval-mode/bit-oval-mode.jsx';
|
||||
import BitRectMode from '../../components/bit-rect-mode/bit-rect-mode.jsx';
|
||||
import BitTextMode from '../../components/bit-text-mode/bit-text-mode.jsx';
|
||||
|
@ -410,7 +410,9 @@ const PaintEditorComponent = props => {
|
|||
<BitBrushMode
|
||||
onUpdateSvg={props.onUpdateSvg}
|
||||
/>
|
||||
<BitLineMode />
|
||||
<BitLineMode
|
||||
onUpdateSvg={props.onUpdateSvg}
|
||||
/>
|
||||
<BitOvalMode />
|
||||
<BitRectMode />
|
||||
<BitTextMode />
|
||||
|
|
107
src/containers/bit-line-mode.jsx
Normal file
107
src/containers/bit-line-mode.jsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import bindAll from 'lodash.bindall';
|
||||
import Modes from '../lib/modes';
|
||||
import {MIXED} from '../helper/style-path';
|
||||
|
||||
import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-color';
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems} from '../reducers/selected-items';
|
||||
import {clearSelection} from '../helper/selection';
|
||||
|
||||
import BitLineModeComponent from '../components/bit-line-mode/bit-line-mode.jsx';
|
||||
import BitLineTool from '../helper/bit-tools/line-tool';
|
||||
|
||||
class BitLineMode extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'activateTool',
|
||||
'deactivateTool'
|
||||
]);
|
||||
}
|
||||
componentDidMount () {
|
||||
if (this.props.isBitLineModeActive) {
|
||||
this.activateTool(this.props);
|
||||
}
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.tool && nextProps.color !== this.props.color) {
|
||||
this.tool.setColor(nextProps.color);
|
||||
}
|
||||
if (this.tool && nextProps.bitBrushSize !== this.props.bitBrushSize) {
|
||||
this.tool.setLineSize(nextProps.bitBrushSize);
|
||||
}
|
||||
|
||||
if (nextProps.isBitLineModeActive && !this.props.isBitLineModeActive) {
|
||||
this.activateTool();
|
||||
} else if (!nextProps.isBitLineModeActive && this.props.isBitLineModeActive) {
|
||||
this.deactivateTool();
|
||||
}
|
||||
}
|
||||
shouldComponentUpdate (nextProps) {
|
||||
return nextProps.isBitLineModeActive !== this.props.isBitLineModeActive;
|
||||
}
|
||||
activateTool () {
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
// Force the default line color if fill is MIXED or transparent
|
||||
let color = this.props.color;
|
||||
if (!color || color === MIXED) {
|
||||
this.props.onChangeFillColor(DEFAULT_COLOR);
|
||||
color = DEFAULT_COLOR;
|
||||
}
|
||||
this.tool = new BitLineTool(
|
||||
this.props.onUpdateSvg
|
||||
);
|
||||
this.tool.setColor(color);
|
||||
this.tool.setLineSize(this.props.bitBrushSize);
|
||||
|
||||
this.tool.activate();
|
||||
}
|
||||
deactivateTool () {
|
||||
this.tool.deactivateTool();
|
||||
this.tool.remove();
|
||||
this.tool = null;
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<BitLineModeComponent
|
||||
isSelected={this.props.isBitLineModeActive}
|
||||
onMouseDown={this.props.handleMouseDown}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BitLineMode.propTypes = {
|
||||
bitBrushSize: PropTypes.number.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
color: PropTypes.string,
|
||||
handleMouseDown: PropTypes.func.isRequired,
|
||||
isBitLineModeActive: PropTypes.bool.isRequired,
|
||||
onChangeFillColor: PropTypes.func.isRequired,
|
||||
onUpdateSvg: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
bitBrushSize: state.scratchPaint.bitBrushSize,
|
||||
color: state.scratchPaint.color.fillColor,
|
||||
isBitLineModeActive: state.scratchPaint.mode === Modes.BIT_LINE
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
handleMouseDown: () => {
|
||||
dispatch(changeMode(Modes.BIT_LINE));
|
||||
},
|
||||
onChangeFillColor: fillColor => {
|
||||
dispatch(changeFillColor(fillColor));
|
||||
}
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(BitLineMode);
|
|
@ -49,6 +49,7 @@ class PaintEditor extends React.Component {
|
|||
'handleZoomReset',
|
||||
'canRedo',
|
||||
'canUndo',
|
||||
'switchMode',
|
||||
'onMouseDown',
|
||||
'setCanvas',
|
||||
'setTextArea',
|
||||
|
@ -81,11 +82,9 @@ class PaintEditor extends React.Component {
|
|||
this.stopEyeDroppingLoop();
|
||||
}
|
||||
|
||||
// @todo move to correct corresponding tool
|
||||
if (isVector(this.props.format) && isBitmap(prevProps.format)) {
|
||||
this.props.changeMode(Modes.BRUSH);
|
||||
} else if (isVector(prevProps.format) && isBitmap(this.props.format)) {
|
||||
this.props.changeMode(Modes.BIT_BRUSH);
|
||||
if ((isVector(this.props.format) && isBitmap(prevProps.format)) ||
|
||||
(isVector(prevProps.format) && isBitmap(this.props.format))) {
|
||||
this.switchMode(this.props.format);
|
||||
}
|
||||
}
|
||||
componentWillUnmount () {
|
||||
|
@ -94,6 +93,31 @@ class PaintEditor extends React.Component {
|
|||
document.removeEventListener('mousedown', this.onMouseDown);
|
||||
document.removeEventListener('touchstart', this.onMouseDown);
|
||||
}
|
||||
switchMode (newFormat) {
|
||||
if (isVector(newFormat)) {
|
||||
switch (this.props.mode) {
|
||||
case Modes.BIT_BRUSH:
|
||||
this.props.changeMode(Modes.BRUSH);
|
||||
break;
|
||||
case Modes.BIT_LINE:
|
||||
this.props.changeMode(Modes.LINE);
|
||||
break;
|
||||
default:
|
||||
this.props.changeMode(Modes.BRUSH);
|
||||
}
|
||||
} else if (isBitmap(newFormat)) {
|
||||
switch (this.props.mode) {
|
||||
case Modes.BRUSH:
|
||||
this.props.changeMode(Modes.BIT_BRUSH);
|
||||
break;
|
||||
case Modes.LINE:
|
||||
this.props.changeMode(Modes.BIT_LINE);
|
||||
break;
|
||||
default:
|
||||
this.props.changeMode(Modes.BIT_BRUSH);
|
||||
}
|
||||
}
|
||||
}
|
||||
handleUpdateSvg (skipSnapshot) {
|
||||
// Store the zoom/pan and restore it after snapshotting
|
||||
// TODO Only doing this because snapshotting at zoom/pan makes export wrong
|
||||
|
@ -300,6 +324,7 @@ PaintEditor.propTypes = {
|
|||
handleSwitchToBitmap: PropTypes.func.isRequired,
|
||||
handleSwitchToVector: PropTypes.func.isRequired,
|
||||
isEyeDropping: PropTypes.bool,
|
||||
mode: PropTypes.oneOf(Object.keys(Modes)).isRequired,
|
||||
name: PropTypes.string,
|
||||
onDeactivateEyeDropper: PropTypes.func.isRequired,
|
||||
onKeyPress: PropTypes.func.isRequired,
|
||||
|
@ -331,6 +356,7 @@ const mapStateToProps = state => ({
|
|||
clipboardItems: state.scratchPaint.clipboard.items,
|
||||
format: state.scratchPaint.format,
|
||||
isEyeDropping: state.scratchPaint.color.eyeDropper.active,
|
||||
mode: state.scratchPaint.mode,
|
||||
pasteOffset: state.scratchPaint.clipboard.pasteOffset,
|
||||
previousTool: state.scratchPaint.color.eyeDropper.previousTool,
|
||||
selectedItems: state.scratchPaint.selectedItems,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import paper from '@scratch/paper';
|
||||
import {getRaster} from '../layer';
|
||||
import {forEachLinePoint, fillEllipse} from '../bitmap';
|
||||
import {forEachLinePoint, getBrushMark} from '../bitmap';
|
||||
import {getGuideLayer} from '../layer';
|
||||
|
||||
/**
|
||||
|
@ -42,6 +42,7 @@ class BrushTool extends paper.Tool {
|
|||
if (!this.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The cursor preview was unattached from the view by an outside process,
|
||||
// such as changing costumes or undo.
|
||||
if (this.cursorPreview && !this.cursorPreview.parent) {
|
||||
|
@ -53,30 +54,13 @@ class BrushTool extends paper.Tool {
|
|||
this.cursorPreview.remove();
|
||||
}
|
||||
|
||||
this.tmpCanvas = document.createElement('canvas');
|
||||
const roundedUpRadius = Math.ceil(this.size / 2);
|
||||
this.tmpCanvas.width = roundedUpRadius * 2;
|
||||
this.tmpCanvas.height = roundedUpRadius * 2;
|
||||
const context = this.tmpCanvas.getContext('2d');
|
||||
context.imageSmoothingEnabled = false;
|
||||
context.fillStyle = this.color;
|
||||
// Small squares for pixel artists
|
||||
if (this.size <= 5) {
|
||||
if (this.size % 2) {
|
||||
context.fillRect(1, 1, this.size, this.size);
|
||||
} else {
|
||||
context.fillRect(0, 0, this.size, this.size);
|
||||
}
|
||||
} else {
|
||||
const roundedDownRadius = ~~(this.size / 2);
|
||||
fillEllipse(roundedDownRadius, roundedDownRadius, roundedDownRadius, roundedDownRadius, context);
|
||||
}
|
||||
|
||||
this.tmpCanvas = getBrushMark(this.size, this.color);
|
||||
this.cursorPreview = new paper.Raster(this.tmpCanvas);
|
||||
this.cursorPreview.guide = true;
|
||||
this.cursorPreview.parent = getGuideLayer();
|
||||
this.cursorPreview.data.isHelperItem = true;
|
||||
}
|
||||
|
||||
this.lastSize = this.size;
|
||||
this.lastColor = this.color;
|
||||
}
|
||||
|
@ -96,10 +80,6 @@ class BrushTool extends paper.Tool {
|
|||
handleMouseDrag (event) {
|
||||
if (event.event.button > 0 || !this.active) return; // only first mouse button
|
||||
|
||||
if (this.isBoundingBoxMode) {
|
||||
this.boundingBoxTool.onMouseDrag(event);
|
||||
return;
|
||||
}
|
||||
forEachLinePoint(this.lastPoint, event.point, this.draw.bind(this));
|
||||
this.lastPoint = event.point;
|
||||
}
|
||||
|
|
124
src/helper/bit-tools/line-tool.js
Normal file
124
src/helper/bit-tools/line-tool.js
Normal file
|
@ -0,0 +1,124 @@
|
|||
import paper from '@scratch/paper';
|
||||
import {getRaster} from '../layer';
|
||||
import {forEachLinePoint, getBrushMark} from '../bitmap';
|
||||
import {getGuideLayer} from '../layer';
|
||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT} from '../view';
|
||||
|
||||
/**
|
||||
* Tool for drawing lines with the bitmap brush.
|
||||
*/
|
||||
class LineTool extends paper.Tool {
|
||||
/**
|
||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||
*/
|
||||
constructor (onUpdateSvg) {
|
||||
super();
|
||||
this.onUpdateSvg = onUpdateSvg;
|
||||
|
||||
// We have to set these functions instead of just declaring them because
|
||||
// paper.js tools hook up the listeners in the setter functions.
|
||||
this.onMouseMove = this.handleMouseMove;
|
||||
this.onMouseDown = this.handleMouseDown;
|
||||
this.onMouseDrag = this.handleMouseDrag;
|
||||
this.onMouseUp = this.handleMouseUp;
|
||||
|
||||
this.colorState = null;
|
||||
this.active = false;
|
||||
this.startPoint = null;
|
||||
this.cursorPreview = null;
|
||||
// Raster to which to draw
|
||||
this.drawTarget = null;
|
||||
}
|
||||
setColor (color) {
|
||||
this.color = color;
|
||||
}
|
||||
setLineSize (size) {
|
||||
// For performance, make sure this is an integer
|
||||
this.size = Math.max(1, ~~size);
|
||||
}
|
||||
// Draw a brush mark at the given point
|
||||
draw (x, y) {
|
||||
const roundedUpRadius = Math.ceil(this.size / 2);
|
||||
this.drawTarget.drawImage(this.tmpCanvas, new paper.Point(~~x - roundedUpRadius, ~~y - roundedUpRadius));
|
||||
}
|
||||
updateCursorIfNeeded () {
|
||||
if (!this.size) {
|
||||
return;
|
||||
}
|
||||
// The cursor preview was unattached from the view by an outside process,
|
||||
// such as changing costumes or undo.
|
||||
if (this.cursorPreview && !this.cursorPreview.parent) {
|
||||
this.cursorPreview = null;
|
||||
}
|
||||
|
||||
if (!this.cursorPreview || !(this.lastSize === this.size && this.lastColor === this.color)) {
|
||||
if (this.cursorPreview) {
|
||||
this.cursorPreview.remove();
|
||||
}
|
||||
|
||||
this.tmpCanvas = getBrushMark(this.size, this.color);
|
||||
this.cursorPreview = new paper.Raster(this.tmpCanvas);
|
||||
this.cursorPreview.guide = true;
|
||||
this.cursorPreview.parent = getGuideLayer();
|
||||
this.cursorPreview.data.isHelperItem = true;
|
||||
}
|
||||
this.lastSize = this.size;
|
||||
this.lastColor = this.color;
|
||||
}
|
||||
handleMouseMove (event) {
|
||||
this.updateCursorIfNeeded();
|
||||
this.cursorPreview.position = new paper.Point(~~event.point.x, ~~event.point.y);
|
||||
}
|
||||
handleMouseDown (event) {
|
||||
if (event.event.button > 0) return; // only first mouse button
|
||||
this.active = true;
|
||||
|
||||
this.cursorPreview.remove();
|
||||
|
||||
const tmpCanvas = document.createElement('canvas');
|
||||
tmpCanvas.width = ART_BOARD_WIDTH;
|
||||
tmpCanvas.height = ART_BOARD_HEIGHT;
|
||||
this.drawTarget = new paper.Raster(tmpCanvas);
|
||||
this.drawTarget.parent = getGuideLayer();
|
||||
this.drawTarget.guide = true;
|
||||
this.drawTarget.locked = true;
|
||||
this.drawTarget.position = getRaster().position;
|
||||
|
||||
this.draw(event.point.x, event.point.y);
|
||||
this.startPoint = event.point;
|
||||
}
|
||||
handleMouseDrag (event) {
|
||||
if (event.event.button > 0 || !this.active) return; // only first mouse button
|
||||
|
||||
// Clear
|
||||
const context = this.drawTarget.canvas.getContext('2d');
|
||||
context.clearRect(0, 0, ART_BOARD_WIDTH, ART_BOARD_HEIGHT);
|
||||
|
||||
forEachLinePoint(this.startPoint, event.point, this.draw.bind(this));
|
||||
}
|
||||
handleMouseUp (event) {
|
||||
if (event.event.button > 0 || !this.active) return; // only first mouse button
|
||||
|
||||
this.drawTarget.remove();
|
||||
this.drawTarget = getRaster();
|
||||
forEachLinePoint(this.startPoint, event.point, this.draw.bind(this));
|
||||
this.drawTarget = null;
|
||||
this.onUpdateSvg();
|
||||
|
||||
this.lastPoint = null;
|
||||
this.active = false;
|
||||
|
||||
this.updateCursorIfNeeded();
|
||||
this.cursorPreview.position = new paper.Point(~~event.point.x, ~~event.point.y);
|
||||
}
|
||||
deactivateTool () {
|
||||
this.active = false;
|
||||
this.tmpCanvas = null;
|
||||
if (this.cursorPreview) {
|
||||
this.cursorPreview.remove();
|
||||
this.cursorPreview = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default LineTool;
|
|
@ -81,6 +81,34 @@ const fillEllipse = function (centerX, centerY, radiusX, radiusY, context) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!number} size The diameter of the brush
|
||||
* @param {!string} color The css color of the brush
|
||||
* @return {HTMLCanvasElement} a canvas with the brush mark printed on it
|
||||
*/
|
||||
const getBrushMark = function (size, color) {
|
||||
size = ~~size;
|
||||
const canvas = document.createElement('canvas');
|
||||
const roundedUpRadius = Math.ceil(size / 2);
|
||||
canvas.width = roundedUpRadius * 2;
|
||||
canvas.height = roundedUpRadius * 2;
|
||||
const context = canvas.getContext('2d');
|
||||
context.imageSmoothingEnabled = false;
|
||||
context.fillStyle = color;
|
||||
// Small squares for pixel artists
|
||||
if (size <= 5) {
|
||||
if (size % 2) {
|
||||
context.fillRect(1, 1, size, size);
|
||||
} else {
|
||||
context.fillRect(0, 0, size, size);
|
||||
}
|
||||
} else {
|
||||
const roundedDownRadius = ~~(size / 2);
|
||||
fillEllipse(roundedDownRadius, roundedDownRadius, roundedDownRadius, roundedDownRadius, context);
|
||||
}
|
||||
return canvas;
|
||||
};
|
||||
|
||||
const rowBlank_ = function (imageData, width, y) {
|
||||
for (let x = 0; x < width; ++x) {
|
||||
if (imageData.data[(y * width << 2) + (x << 2) + 3] !== 0) return false;
|
||||
|
@ -114,6 +142,7 @@ const trim = function (raster) {
|
|||
};
|
||||
|
||||
export {
|
||||
getBrushMark,
|
||||
fillEllipse,
|
||||
forEachLinePoint,
|
||||
trim
|
||||
|
|
|
@ -2,6 +2,7 @@ import keyMirror from 'keymirror';
|
|||
|
||||
const Modes = keyMirror({
|
||||
BIT_BRUSH: null,
|
||||
BIT_LINE: null,
|
||||
BRUSH: null,
|
||||
ERASER: null,
|
||||
LINE: null,
|
||||
|
|
Loading…
Reference in a new issue