mirror of
https://github.com/Doodle3D/Doodle3D-Core.git
synced 2024-12-22 11:03:48 +01:00
Text can now be edited directly on the canvas
This commit is contained in:
parent
834ca531fd
commit
04186fd56c
6
package-lock.json
generated
6
package-lock.json
generated
@ -11747,12 +11747,6 @@
|
|||||||
"prop-types": "15.6.0"
|
"prop-types": "15.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-router-redux": {
|
|
||||||
"version": "4.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-router-redux/-/react-router-redux-4.0.8.tgz",
|
|
||||||
"integrity": "sha1-InQDWWtRUeGCN32rg1tdRfD4BU4=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"react-svg-inline": {
|
"react-svg-inline": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-svg-inline/-/react-svg-inline-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-svg-inline/-/react-svg-inline-2.0.1.tgz",
|
||||||
|
@ -82,7 +82,6 @@
|
|||||||
"normalize-jss": "^4.0.0",
|
"normalize-jss": "^4.0.0",
|
||||||
"raw-loader": "^0.5.1",
|
"raw-loader": "^0.5.1",
|
||||||
"react-dom": "^16.1.1",
|
"react-dom": "^16.1.1",
|
||||||
"react-router-redux": "^4.0.8",
|
|
||||||
"react-tap-event-plugin": "^3.0.2",
|
"react-tap-event-plugin": "^3.0.2",
|
||||||
"redux": "^3.7.2",
|
"redux": "^3.7.2",
|
||||||
"redux-action-wrapper": "^1.0.1",
|
"redux-action-wrapper": "^1.0.1",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ActionCreators as undo } from 'redux-undo';
|
import { ActionCreators as undo } from 'redux-undo';
|
||||||
import * as notification from 'react-notification-system-redux';
|
import * as notification from 'react-notification-system-redux';
|
||||||
import { routerActions as router } from 'react-router-redux';
|
|
||||||
import * as selectionUtils from '../utils/selectionUtils.js';
|
import * as selectionUtils from '../utils/selectionUtils.js';
|
||||||
import { calculatePointInImage, decomposeMatrix } from '../utils/matrixUtils.js';
|
import { calculatePointInImage, decomposeMatrix } from '../utils/matrixUtils.js';
|
||||||
import { loadImage, prepareImage } from '../utils/imageUtils.js';
|
import { loadImage, prepareImage } from '../utils/imageUtils.js';
|
||||||
@ -74,7 +73,6 @@ export const ALIGN = 'ALIGN';
|
|||||||
export const ADD_IMAGE = 'ADD_IMAGE';
|
export const ADD_IMAGE = 'ADD_IMAGE';
|
||||||
export const D2_TEXT_INIT = 'D2_TEXT_INIT';
|
export const D2_TEXT_INIT = 'D2_TEXT_INIT';
|
||||||
export const D2_TEXT_INPUT_CHANGE = 'D2_TEXT_INPUT_CHANGE';
|
export const D2_TEXT_INPUT_CHANGE = 'D2_TEXT_INPUT_CHANGE';
|
||||||
export const D2_TEXT_ADD = 'D2_TEXT_ADD';
|
|
||||||
export const UNION = 'UNION';
|
export const UNION = 'UNION';
|
||||||
export const INTERSECT = 'INTERSECT';
|
export const INTERSECT = 'INTERSECT';
|
||||||
export const MOVE_SELECTION = 'MOVE_SELECTION';
|
export const MOVE_SELECTION = 'MOVE_SELECTION';
|
||||||
@ -390,15 +388,11 @@ export function addImage(file) {
|
|||||||
export function d2textInit(position, textId, screenMatrixContainer, screenMatrixZoom) {
|
export function d2textInit(position, textId, screenMatrixContainer, screenMatrixZoom) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch({ type: D2_TEXT_INIT, position, textId, screenMatrixContainer, screenMatrixZoom });
|
dispatch({ type: D2_TEXT_INIT, position, textId, screenMatrixContainer, screenMatrixZoom });
|
||||||
dispatch(router.push('/sketch/inputtext'));
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function d2textInputChange(text, family, weight, style, fill) {
|
export function d2textInputChange(text, family, weight, style, fill) {
|
||||||
return { type: D2_TEXT_INPUT_CHANGE, text, family, weight, style, fill };
|
return { type: D2_TEXT_INPUT_CHANGE, text, family, weight, style, fill };
|
||||||
}
|
}
|
||||||
export function d2textAdd() {
|
|
||||||
return { type: D2_TEXT_ADD };
|
|
||||||
}
|
|
||||||
|
|
||||||
const traceDragThrottle = createThrottle();
|
const traceDragThrottle = createThrottle();
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import ShapesManager from '../d2/ShapesManager.js';
|
|||||||
import EventGroup from '../d2/EventGroup.js';
|
import EventGroup from '../d2/EventGroup.js';
|
||||||
import ReactResizeDetector from 'react-resize-detector';
|
import ReactResizeDetector from 'react-resize-detector';
|
||||||
import { load as loadPattern } from '../d2/Shape.js';
|
import { load as loadPattern } from '../d2/Shape.js';
|
||||||
|
import InputText from './InputText.js';
|
||||||
// import createDebug from 'debug';
|
// import createDebug from 'debug';
|
||||||
// const debug = createDebug('d3d:d2');
|
// const debug = createDebug('d3d:d2');
|
||||||
|
|
||||||
@ -63,6 +64,9 @@ class D2Panel extends React.Component {
|
|||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
classes: PropTypes.objectOf(PropTypes.string)
|
classes: PropTypes.objectOf(PropTypes.string)
|
||||||
};
|
};
|
||||||
|
state = {
|
||||||
|
screenMatrix: new CAL.Matrix()
|
||||||
|
};
|
||||||
activeNeedRender = false;
|
activeNeedRender = false;
|
||||||
inactiveNeedRender = false;
|
inactiveNeedRender = false;
|
||||||
|
|
||||||
@ -117,7 +121,7 @@ class D2Panel extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switchTool(toolName) {
|
switchTool(toolName) {
|
||||||
if (this.state && toolName === this.state.d2.tool) return;
|
if (this.state.state && toolName === this.state.state.d2.tool) return;
|
||||||
// cleanup & remove previous tool
|
// cleanup & remove previous tool
|
||||||
if (this.tool) {
|
if (this.tool) {
|
||||||
this.tool.destroy();
|
this.tool.destroy();
|
||||||
@ -135,18 +139,18 @@ class D2Panel extends React.Component {
|
|||||||
this.objectContainerActive.add(this.tool);
|
this.objectContainerActive.add(this.tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(newState) {
|
update(state) {
|
||||||
if (this.state === newState) return;
|
if (this.state.state === state) return;
|
||||||
|
|
||||||
this.updateTool(newState);
|
this.updateTool(state);
|
||||||
|
|
||||||
const shapesNeedRender = this.shapesManager.update(newState);
|
const shapesNeedRender = this.shapesManager.update(state);
|
||||||
if (shapesNeedRender.active) this.activeNeedRender = true;
|
if (shapesNeedRender.active) this.activeNeedRender = true;
|
||||||
if (shapesNeedRender.inactive) this.inactiveNeedRender = true;
|
if (shapesNeedRender.inactive) this.inactiveNeedRender = true;
|
||||||
|
|
||||||
// Update Objects Container Space with zoom & panning
|
// Update Objects Container Space with zoom & panning
|
||||||
const newCanvasMatrix = newState.d2.canvasMatrix;
|
const newCanvasMatrix = state.d2.canvasMatrix;
|
||||||
if (this.state && newCanvasMatrix !== this.state.d2.canvasMatrix) {
|
if (this.state.state && newCanvasMatrix !== this.state.state.d2.canvasMatrix) {
|
||||||
this.objectContainerActive.copyMatrix(newCanvasMatrix);
|
this.objectContainerActive.copyMatrix(newCanvasMatrix);
|
||||||
this.objectContainerInactive.copyMatrix(newCanvasMatrix);
|
this.objectContainerInactive.copyMatrix(newCanvasMatrix);
|
||||||
|
|
||||||
@ -154,9 +158,9 @@ class D2Panel extends React.Component {
|
|||||||
this.inactiveNeedRender = true;
|
this.inactiveNeedRender = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selection = (this.state) ? this.state.selection : null;
|
const selection = (this.state.state) ? this.state.state.selection : null;
|
||||||
const newSelection = newState.selection;
|
const newSelection = state.selection;
|
||||||
if (!this.state || newSelection !== selection) {
|
if (!this.state.state || newSelection !== selection) {
|
||||||
const newSelectedObjects = newSelection.objects;
|
const newSelectedObjects = newSelection.objects;
|
||||||
if (!selection || selection.objects !== newSelectedObjects) {
|
if (!selection || selection.objects !== newSelectedObjects) {
|
||||||
const selected = newSelectedObjects.map((object) => object.id);
|
const selected = newSelectedObjects.map((object) => object.id);
|
||||||
@ -167,25 +171,29 @@ class D2Panel extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dragSelect = (this.state) ? this.state.d2.transform.dragSelect : null;
|
const dragSelect = (this.state.state) ? this.state.state.d2.transform.dragSelect : null;
|
||||||
const newDragSelect = newState.d2.transform.dragSelect;
|
const newDragSelect = state.d2.transform.dragSelect;
|
||||||
if (!dragSelect || dragSelect !== newDragSelect) {
|
if (!dragSelect || dragSelect !== newDragSelect) {
|
||||||
this.activeNeedRender = true;
|
this.activeNeedRender = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = newState;
|
this.setState({ state });
|
||||||
}
|
}
|
||||||
|
|
||||||
resizeHandler = (width, height) => {
|
resizeHandler = (width, height) => {
|
||||||
this.sceneActive.setSize(width, height, PIXEL_RATIO);
|
this.sceneActive.setSize(width, height, PIXEL_RATIO);
|
||||||
this.sceneInactive.setSize(width, height, PIXEL_RATIO);
|
this.sceneInactive.setSize(width, height, PIXEL_RATIO);
|
||||||
|
|
||||||
this.sceneInactive.x = this.sceneActive.x = Math.round(width / 2 * PIXEL_RATIO);
|
const x = Math.round(width / 2 * PIXEL_RATIO);
|
||||||
this.sceneInactive.y = this.sceneActive.y = Math.round(height / 2 * PIXEL_RATIO);
|
const y = Math.round(height / 2 * PIXEL_RATIO);
|
||||||
|
|
||||||
const scale = Math.min(width * PIXEL_RATIO / CANVAS_WIDTH, height * PIXEL_RATIO / CANVAS_HEIGHT);
|
const scale = Math.min(width * PIXEL_RATIO / CANVAS_WIDTH, height * PIXEL_RATIO / CANVAS_HEIGHT);
|
||||||
|
|
||||||
this.sceneInactive.scale = this.sceneActive.scale = scale;
|
const screenMatrix = new CAL.Matrix({ sx: scale, sy: scale, x, y });
|
||||||
|
|
||||||
|
this.sceneInactive.copyMatrix(screenMatrix);
|
||||||
|
this.sceneActive.copyMatrix(screenMatrix);
|
||||||
|
|
||||||
|
this.setState({ screenMatrix });
|
||||||
|
|
||||||
this.inactiveNeedRender = this.activeNeedRender = true;
|
this.inactiveNeedRender = this.activeNeedRender = true;
|
||||||
this.renderRequest();
|
this.renderRequest();
|
||||||
@ -210,12 +218,14 @@ class D2Panel extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
// debug('this.props.state: ', this.props.state);
|
// debug('this.props.state: ', this.props.state);
|
||||||
const { state, classes } = this.props;
|
const { state, classes } = this.props;
|
||||||
|
const { screenMatrix } = this.state;
|
||||||
this.update(state);
|
this.update(state);
|
||||||
this.renderCanvas();
|
this.renderCanvas();
|
||||||
return (
|
return (
|
||||||
<div className={classes.container}>
|
<div className={classes.container}>
|
||||||
<ReactResizeDetector handleWidth handleHeight onResize={this.resizeHandler} />
|
<ReactResizeDetector handleWidth handleHeight onResize={this.resizeHandler} />
|
||||||
<div className={classes.canvasContainer} ref="canvasContainer" />
|
<div className={classes.canvasContainer} ref="canvasContainer" />
|
||||||
|
<InputText screenMatrix={screenMatrix} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
94
src/components/InputText.js
Normal file
94
src/components/InputText.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import injectSheet from 'react-jss';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import * as actions from '../actions/index.js';
|
||||||
|
import * as CAL from 'cal';
|
||||||
|
import { TEXT_TOOL_FONT_SIZE } from '../constants/d2Constants.js';
|
||||||
|
|
||||||
|
document.createElement('canvas');
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
textInputContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
display: 'flex'
|
||||||
|
},
|
||||||
|
textInput: {
|
||||||
|
position: 'absolute',
|
||||||
|
fontSize: `${TEXT_TOOL_FONT_SIZE}px`,
|
||||||
|
background: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
color: 'black',
|
||||||
|
textFillColor: 'transparent',
|
||||||
|
outline: 'none'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputText extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
state: PropTypes.object.isRequired,
|
||||||
|
classes: PropTypes.objectOf(PropTypes.string),
|
||||||
|
changeText: PropTypes.func.isRequired,
|
||||||
|
screenMatrix: PropTypes.instanceOf(CAL.Matrix).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
onInputChange = () => {
|
||||||
|
const shapeData = this.getShapeData();
|
||||||
|
if (!shapeData) return;
|
||||||
|
|
||||||
|
const { changeText } = this.props;
|
||||||
|
const { family, weight, style } = shapeData.text;
|
||||||
|
const text = this.refs.text.value;
|
||||||
|
|
||||||
|
changeText(text, family, weight, style, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
getShapeData = () => {
|
||||||
|
const { state } = this.props;
|
||||||
|
if (!state.d2.activeShape) return null;
|
||||||
|
const shapeData = state.objectsById[state.d2.activeShape];
|
||||||
|
if (shapeData.type !== 'TEXT') return null;
|
||||||
|
return shapeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUpdate() {
|
||||||
|
if (this.refs.text) this.refs.text.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes, state, screenMatrix } = this.props;
|
||||||
|
const shapeData = this.getShapeData();
|
||||||
|
|
||||||
|
if (shapeData) {
|
||||||
|
const { _matrix: m } = shapeData.transform
|
||||||
|
.multiplyMatrix(state.d2.canvasMatrix)
|
||||||
|
.multiplyMatrix(screenMatrix);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classes.textInputContainer}
|
||||||
|
style={{ transform: `matrix(${m[0]}, ${m[3]}, ${m[1]}, ${m[4]}, ${m[2]}, ${m[5]})` }}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className={classes.textInput}
|
||||||
|
style={{
|
||||||
|
family: shapeData.text.family
|
||||||
|
}}
|
||||||
|
value={shapeData.text.text}
|
||||||
|
ref="text"
|
||||||
|
spellCheck="false"
|
||||||
|
autoFocus
|
||||||
|
onChange={this.onInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectSheet(styles)(connect(state => ({
|
||||||
|
state: state.sketcher.present
|
||||||
|
}), {
|
||||||
|
changeText: actions.d2textInputChange
|
||||||
|
})(InputText));
|
@ -41,3 +41,4 @@ export const BRUSH_SIZES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CLIPPER_PRECISION = 100; // accurate to the hundredth
|
export const CLIPPER_PRECISION = 100; // accurate to the hundredth
|
||||||
|
export const TEXT_TOOL_FONT_SIZE = 40;
|
||||||
|
@ -8,6 +8,7 @@ import { MAX_ANGLE } from '../constants/d3Constants.js';
|
|||||||
import { SHAPE_CACHE_LIMIT } from '../constants/general.js';
|
import { SHAPE_CACHE_LIMIT } from '../constants/general.js';
|
||||||
import { createText } from '../utils/textUtils.js';
|
import { createText } from '../utils/textUtils.js';
|
||||||
import { segmentBezierPath } from '../utils/curveUtils.js';
|
import { segmentBezierPath } from '../utils/curveUtils.js';
|
||||||
|
import { TEXT_TOOL_FONT_SIZE } from '../constants/d2Constants.js';
|
||||||
|
|
||||||
const setDirection = (clockwise) => (path) => {
|
const setDirection = (clockwise) => (path) => {
|
||||||
return (THREE.ShapeUtils.isClockWise(path) === clockwise) ? path : path.reverse();
|
return (THREE.ShapeUtils.isClockWise(path) === clockwise) ? path : path.reverse();
|
||||||
@ -120,7 +121,7 @@ function shapeToPointsRaw(shapeData) {
|
|||||||
}
|
}
|
||||||
case 'TEXT': {
|
case 'TEXT': {
|
||||||
const { text, family, style, weight } = shapeData.text;
|
const { text, family, style, weight } = shapeData.text;
|
||||||
const textShapes = createText(text, 400, family, style, weight)
|
const textShapes = createText(text, TEXT_TOOL_FONT_SIZE, 10, family, style, weight)
|
||||||
.map(([points, ...holes]) => ({ points, holes }));
|
.map(([points, ...holes]) => ({ points, holes }));
|
||||||
|
|
||||||
shapes.push(...textShapes);
|
shapes.push(...textShapes);
|
||||||
|
@ -9,19 +9,17 @@ import memoize from 'memoizee';
|
|||||||
const MARGIN = 200;
|
const MARGIN = 200;
|
||||||
|
|
||||||
export const createText = memoize(createTextRaw, { max: SHAPE_CACHE_LIMIT });
|
export const createText = memoize(createTextRaw, { max: SHAPE_CACHE_LIMIT });
|
||||||
export function createTextRaw(text, size, family, style, weight) {
|
export function createTextRaw(text, size, precision, family, style, weight) {
|
||||||
if (text === '') return [];
|
if (text === '') return [];
|
||||||
|
|
||||||
const { width, height, canvas } = createTextCanvas(text, size, family, style, weight);
|
const canvas = createTextCanvas(text, size * precision, family, style, weight);
|
||||||
|
|
||||||
// TODO merge with potrace in flood fill trace reducer
|
// TODO merge with potrace in flood fill trace reducer
|
||||||
const paths = POTRACE.getPaths(POTRACE.traceCanvas(canvas, POTRACE_OPTIONS));
|
const paths = POTRACE.getPaths(POTRACE.traceCanvas(canvas, POTRACE_OPTIONS));
|
||||||
|
|
||||||
const halfWidth = width / 2;
|
|
||||||
const halfHeight = height / 2;
|
|
||||||
const pathsOffset = paths.map(path => path.map(({ x, y }) => ({
|
const pathsOffset = paths.map(path => path.map(({ x, y }) => ({
|
||||||
x: (x - halfWidth) / 10,
|
x: (x - MARGIN) / precision,
|
||||||
y: (y - halfHeight) / 10
|
y: (y - MARGIN) / precision
|
||||||
})));
|
})));
|
||||||
|
|
||||||
const shapes = new ClipperShape(pathsOffset, true, true, false)
|
const shapes = new ClipperShape(pathsOffset, true, true, false)
|
||||||
@ -33,7 +31,7 @@ export function createTextRaw(text, size, family, style, weight) {
|
|||||||
return shapes;
|
return shapes;
|
||||||
}
|
}
|
||||||
|
|
||||||
const textContext = new Text();
|
const textContext = new Text({ baseline: 'top' });
|
||||||
export function createTextCanvas(text, size, family, style, weight) {
|
export function createTextCanvas(text, size, family, style, weight) {
|
||||||
textContext.size = size;
|
textContext.size = size;
|
||||||
textContext.family = family;
|
textContext.family = family;
|
||||||
@ -52,7 +50,7 @@ export function createTextCanvas(text, size, family, style, weight) {
|
|||||||
context.fillStyle = 'white';
|
context.fillStyle = 'white';
|
||||||
context.fillRect(0, 0, width, height);
|
context.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
textContext.drawText(context, text, MARGIN, height / 2);
|
textContext.drawText(context, text, MARGIN, MARGIN);
|
||||||
|
|
||||||
return { width, height, canvas };
|
return canvas;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user