diff --git a/img/contextmenu/btnFont.png b/img/contextmenu/btnFont.png new file mode 100644 index 0000000..6cba1ab Binary files /dev/null and b/img/contextmenu/btnFont.png differ diff --git a/package-lock.json b/package-lock.json index 4c1f9a2..9d2f26e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1932,6 +1932,11 @@ "isarray": "1.0.0" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, "buffer-from": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.1.tgz", @@ -4286,6 +4291,14 @@ } } }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "requires": { + "pend": "1.2.0" + } + }, "figures": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", @@ -5666,6 +5679,33 @@ "pinkie-promise": "2.0.1" } }, + "google-fonts-webpack-plugin": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/google-fonts-webpack-plugin/-/google-fonts-webpack-plugin-0.4.4.tgz", + "integrity": "sha512-+e2D9/DVBG9EDydRovzoqMZ658SsTBGbC0c65GyZqkwNvdj8vRSYQKXqbz7/yt7QaXsCPT1MpH45r3ivWOitcw==", + "requires": { + "lodash": "4.17.4", + "node-fetch": "1.7.3", + "webpack-sources": "0.2.3", + "yauzl": "2.9.1" + }, + "dependencies": { + "source-list-map": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-1.1.2.tgz", + "integrity": "sha1-mIkBnRAkzOVc3AaUmDN+9hhqEaE=" + }, + "webpack-sources": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-0.2.3.tgz", + "integrity": "sha1-F8Yr+vE8cH+dAsR54Nzd6DgGl/s=", + "requires": { + "source-list-map": "1.1.2", + "source-map": "0.5.7" + } + } + } + }, "got": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz", @@ -8398,6 +8438,11 @@ "sort-keys": "1.1.2" } }, + "normalize-wheel": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz", + "integrity": "sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU=" + }, "npm": { "version": "2.15.12", "resolved": "https://registry.npmjs.org/npm/-/npm-2.15.12.tgz", @@ -10550,6 +10595,11 @@ "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=", "dev": true }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, "pepjs": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/pepjs/-/pepjs-0.4.3.tgz", @@ -11747,12 +11797,6 @@ "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": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/react-svg-inline/-/react-svg-inline-2.0.1.tgz", @@ -14370,6 +14414,15 @@ } } }, + "yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha1-qBmB6nCleUYTOIPwKcWCGok1mn8=", + "requires": { + "buffer-crc32": "0.2.13", + "fd-slicer": "1.0.1" + } + }, "yml-loader": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/yml-loader/-/yml-loader-2.1.0.tgz", diff --git a/package.json b/package.json index 8604d69..d789a6f 100755 --- a/package.json +++ b/package.json @@ -27,11 +27,13 @@ "blueimp-canvas-to-blob": "^3.14.0", "bowser": "^1.8.1", "fit-curve": "^0.1.6", + "google-fonts-webpack-plugin": "^0.4.4", "imports-loader": "^0.7.1", "jss": "^9.4.0", "keycode": "^2.1.9", "lodash": "^4.17.4", "memoizee": "^0.3.9", + "normalize-wheel": "^1.0.1", "pouchdb": "^6.3.4", "proptypes": "^1.1.0", "raf": "^3.4.0", @@ -82,7 +84,6 @@ "normalize-jss": "^4.0.0", "raw-loader": "^0.5.1", "react-dom": "^16.1.1", - "react-router-redux": "^4.0.8", "react-tap-event-plugin": "^3.0.2", "redux": "^3.7.2", "redux-action-wrapper": "^1.0.1", diff --git a/shaders/anaglyph_frag.glsl b/shaders/anaglyph_frag.glsl new file mode 100644 index 0000000..114e311 --- /dev/null +++ b/shaders/anaglyph_frag.glsl @@ -0,0 +1,36 @@ +uniform sampler2D mapLeft; +uniform sampler2D mapRight; +varying vec2 vUv; + +uniform mat3 colorMatrixLeft; +uniform mat3 colorMatrixRight; + +float lin( float c ) { + return c <= 0.04045 ? c * 0.0773993808 : + pow( c * 0.9478672986 + 0.0521327014, 2.4 ); +} + +vec4 lin( vec4 c ) { + return vec4( lin( c.r ), lin( c.g ), lin( c.b ), c.a ); +} + +float dev( float c ) { + return c <= 0.0031308 ? c * 12.92 : pow( c, 0.41666 ) * 1.055 - 0.055; +} + +void main() { + + vec2 uv = vUv; + + vec4 colorL = lin( texture2D( mapLeft, uv ) ); + vec4 colorR = lin( texture2D( mapRight, uv ) ); + + vec3 color = clamp( + colorMatrixLeft * colorL.rgb + + colorMatrixRight * colorR.rgb, 0., 1. ); + + gl_FragColor = vec4( + dev( color.r ), dev( color.g ), dev( color.b ), + max( colorL.a, colorR.a ) ); + +} diff --git a/shaders/anaglyph_vert.glsl b/shaders/anaglyph_vert.glsl new file mode 100644 index 0000000..610d70b --- /dev/null +++ b/shaders/anaglyph_vert.glsl @@ -0,0 +1,5 @@ +varying vec2 vUv; +void main() { + vUv = vec2( uv.x, uv.y ); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); +} diff --git a/src/actions/index.js b/src/actions/index.js index 9c6469d..64285fe 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -1,6 +1,5 @@ import { ActionCreators as undo } from 'redux-undo'; import * as notification from 'react-notification-system-redux'; -import { routerActions as router } from 'react-router-redux'; import * as selectionUtils from '../utils/selectionUtils.js'; import { calculatePointInImage, decomposeMatrix } from '../utils/matrixUtils.js'; import { loadImage, prepareImage } from '../utils/imageUtils.js'; @@ -74,7 +73,6 @@ export const ALIGN = 'ALIGN'; export const ADD_IMAGE = 'ADD_IMAGE'; export const D2_TEXT_INIT = 'D2_TEXT_INIT'; export const D2_TEXT_INPUT_CHANGE = 'D2_TEXT_INPUT_CHANGE'; -export const D2_TEXT_ADD = 'D2_TEXT_ADD'; export const UNION = 'UNION'; export const INTERSECT = 'INTERSECT'; export const MOVE_SELECTION = 'MOVE_SELECTION'; @@ -390,14 +388,10 @@ export function addImage(file) { export function d2textInit(position, textId, screenMatrixContainer, screenMatrixZoom) { return (dispatch) => { dispatch({ type: D2_TEXT_INIT, position, textId, screenMatrixContainer, screenMatrixZoom }); - dispatch(router.push('/sketch/inputtext')); }; } -export function d2textInputChange(text, family, weight, style, fill) { - return { type: D2_TEXT_INPUT_CHANGE, text, family, weight, style, fill }; -} -export function d2textAdd() { - return { type: D2_TEXT_ADD }; +export function d2textInputChange(text) { + return { type: D2_TEXT_INPUT_CHANGE, text }; } const traceDragThrottle = createThrottle(); diff --git a/src/components/D2Panel.js b/src/components/D2Panel.js index 83ce0a6..cd9438d 100644 --- a/src/components/D2Panel.js +++ b/src/components/D2Panel.js @@ -23,6 +23,7 @@ import ShapesManager from '../d2/ShapesManager.js'; import EventGroup from '../d2/EventGroup.js'; import ReactResizeDetector from 'react-resize-detector'; import { load as loadPattern } from '../d2/Shape.js'; +import InputText from './InputText.js'; // import createDebug from 'debug'; // const debug = createDebug('d3d:d2'); @@ -63,6 +64,9 @@ class D2Panel extends React.Component { dispatch: PropTypes.func.isRequired, classes: PropTypes.objectOf(PropTypes.string) }; + state = { + screenMatrix: new CAL.Matrix() + }; activeNeedRender = false; inactiveNeedRender = false; @@ -117,7 +121,7 @@ class D2Panel extends React.Component { } 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 if (this.tool) { this.tool.destroy(); @@ -135,18 +139,18 @@ class D2Panel extends React.Component { this.objectContainerActive.add(this.tool); } - update(newState) { - if (this.state === newState) return; + update(state) { + 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.inactive) this.inactiveNeedRender = true; // Update Objects Container Space with zoom & panning - const newCanvasMatrix = newState.d2.canvasMatrix; - if (this.state && newCanvasMatrix !== this.state.d2.canvasMatrix) { + const newCanvasMatrix = state.d2.canvasMatrix; + if (this.state.state && newCanvasMatrix !== this.state.state.d2.canvasMatrix) { this.objectContainerActive.copyMatrix(newCanvasMatrix); this.objectContainerInactive.copyMatrix(newCanvasMatrix); @@ -154,9 +158,9 @@ class D2Panel extends React.Component { this.inactiveNeedRender = true; } - const selection = (this.state) ? this.state.selection : null; - const newSelection = newState.selection; - if (!this.state || newSelection !== selection) { + const selection = (this.state.state) ? this.state.state.selection : null; + const newSelection = state.selection; + if (!this.state.state || newSelection !== selection) { const newSelectedObjects = newSelection.objects; if (!selection || selection.objects !== newSelectedObjects) { 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 newDragSelect = newState.d2.transform.dragSelect; + const dragSelect = (this.state.state) ? this.state.state.d2.transform.dragSelect : null; + const newDragSelect = state.d2.transform.dragSelect; if (!dragSelect || dragSelect !== newDragSelect) { this.activeNeedRender = true; } - this.state = newState; + this.setState({ state }); } resizeHandler = (width, height) => { this.sceneActive.setSize(width, height, PIXEL_RATIO); this.sceneInactive.setSize(width, height, PIXEL_RATIO); - this.sceneInactive.x = this.sceneActive.x = Math.round(width / 2 * PIXEL_RATIO); - this.sceneInactive.y = this.sceneActive.y = Math.round(height / 2 * PIXEL_RATIO); - + const x = Math.round(width / 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); - 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.renderRequest(); @@ -210,12 +218,14 @@ class D2Panel extends React.Component { render() { // debug('this.props.state: ', this.props.state); const { state, classes } = this.props; + const { screenMatrix } = this.state; this.update(state); this.renderCanvas(); return (
+
); } diff --git a/src/components/D3Panel.js b/src/components/D3Panel.js index 52f18fc..2a043dd 100644 --- a/src/components/D3Panel.js +++ b/src/components/D3Panel.js @@ -16,7 +16,7 @@ import TwistTransformer from '../d3/transformers/TwistTransformer.js'; import SculptTransformer from '../d3/transformers/SculptTransformer.js'; import StampTransformer from '../d3/transformers/StampTransformer.js'; import SelectionBox from '../d3/SelectionBox.js'; -import RenderChain from '../d3/RenderChain'; +import RenderChain, { TOONSHADER_OUTLINE, TOONSHADER } from '../d3/RenderChain'; import BaseTransformer from '../d3/transformers/BaseTransformer.js'; import Camera from '../d3/Camera.js'; import ReactResizeDetector from 'react-resize-detector'; @@ -62,8 +62,8 @@ class D3Panel extends React.Component { componentWillMount() { this.createScene(); - const toonShader = hasExtensionsFor.toonShaderPreview; - this.renderChain = new RenderChain(this.renderer, this.scene, this.camera, toonShader, { + const shader = hasExtensionsFor.toonShaderPreview ? TOONSHADER_OUTLINE : TOONSHADER; + this.renderChain = new RenderChain(this.renderer, this.scene, this.camera, shader, { UI: this.UIContainer, shapes: this.shapesManager, boundingBox: this.selectionBox, diff --git a/src/components/DoodlePreview.js b/src/components/DoodlePreview.js index 8003515..4d1af00 100644 --- a/src/components/DoodlePreview.js +++ b/src/components/DoodlePreview.js @@ -45,7 +45,7 @@ class DoodlePreview extends React.Component { scene: null }; - async componentDidMount() { + async componentWillMount() { let { docData, sketchData } = this.props; if (docData) sketchData = await JSONToSketchData(this.props.docData); diff --git a/src/components/InputText.js b/src/components/InputText.js new file mode 100644 index 0000000..73e74bd --- /dev/null +++ b/src/components/InputText.js @@ -0,0 +1,101 @@ +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'; + +const CONTEXT = document.createElement('canvas').getContext('2d'); + +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 text = this.refs.text.value; + + changeText(text); + }; + + 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); + + const { family, style, weight, text } = shapeData.text; + + CONTEXT.font = `${style} normal ${weight} ${TEXT_TOOL_FONT_SIZE}px ${family}`; + const width = Math.max(10, CONTEXT.measureText(shapeData.text.text).width); + + return ( +
+ +
+ ); + } + return null; + } +} + +export default injectSheet(styles)(connect(state => ({ + state: state.sketcher.present +}), { + changeText: actions.d2textInputChange +})(InputText)); diff --git a/src/components/Menu.js b/src/components/Menu.js index 0b08362..3e1a6f7 100644 --- a/src/components/Menu.js +++ b/src/components/Menu.js @@ -1,7 +1,5 @@ import React from 'react'; import PropTypes from 'prop-types'; -import initialMenuStructure from '../constants/menu.js'; -import { connect } from 'react-redux'; // import createDebug from 'debug'; // const debug = createDebug('d3d:menu'); @@ -14,7 +12,7 @@ class Menu extends React.Component { value: PropTypes.string, className: PropTypes.string, children: PropTypes.node, - stateMenu: PropTypes.object + id: PropTypes.string }; onSelect = (event) => { const { onSelect, value } = this.props; @@ -23,7 +21,7 @@ class Menu extends React.Component { if (onSelect) onSelect({ ...event, menuValue }); }; render() { - const { className = '', id, selectedValue, onOpen, onClose, value, children, stateMenu } = this.props; + const { className = '', id, selectedValue, onOpen, onClose, children } = this.props; return (