增加点编辑模式

This commit is contained in:
mkwiser 2014-09-10 20:30:57 +08:00
parent 016cd73943
commit 8c5e54b826
17 changed files with 593 additions and 159 deletions

View File

@ -10,11 +10,10 @@
define(
function(require) {
var lang = require('common/lang');
var selectShape = require('render/util/selectShape');
var computeBoundingBox = require('graphics/computeBoundingBox');
var pathAdjust = require('render/util/pathAdjust');
var glyf2path = require('ttf/util/glyf2path');
var ShapeGroup = require('./ShapeGroup');
var editorMode = require('./mode/editorMode');
/**
* 初始化
@ -31,89 +30,69 @@ define(
render.camera.center.x = e.x;
render.camera.center.y = e.y;
render.camera.scale *= ratio;
render.painter.refresh();
render.camera.ratio = 1;
});
render.capture.on('down', function(e) {
var result = render.getLayer('cover').getShapeIn(e);
if(result) {
if (me.currentGroup) {
me.currentPoint = lang.clone(result[0]);
me.currentGroup.beginTransform(me.currentPoint);
}
}
else {
if (me.currentGroup) {
me.currentGroup.dispose();
me.currentGroup = null;
}
result = render.getLayer('font').getShapeIn(e);
if(result) {
var shape = result[0];
if (result.length > 1) {
shape = selectShape(result);
}
me.currentGroup = new ShapeGroup(shape, render);
}
}
render.camera.startx = e.x;
render.camera.starty = e.y;
render.camera.x = e.x;
render.camera.y = e.y;
render.camera.event = e;
me.mode.down && me.mode.down.call(me, e);
});
render.capture.on('drag', function(e) {
render.camera.mx = e.x - render.camera.x;
render.camera.my = e.y - render.camera.y;
render.camera.x = e.x;
render.camera.y = e.y;
render.camera.event = e;
if(me.currentGroup) {
var mx = render.camera.x;
var my = render.camera.y;
render.camera.x = e.x;
render.camera.y = e.y;
render.camera.event = e;
if (me.currentPoint) {
me.currentGroup.transform(me.currentPoint, render.camera);
}
else {
me.currentGroup.move(e.x - mx, e.y - my);
}
}
me.mode.drag && me.mode.drag.call(me, e);
});
render.capture.on('dragend', function(e) {
if (me.currentGroup) {
if (me.currentPoint) {
me.currentGroup.finishTransform(me.currentPoint);
me.currentPoint = null;
}
}
render.camera.x = e.x;
render.camera.y = e.y;
render.camera.event = e;
me.mode.dragend && me.mode.dragend.call(me, e);
});
render.capture.on('dblclick', function(e) {
me.mode.end.call(me, e);
if(me.mode === editorMode.bound) {
me.mode = editorMode.point;
}
else if(me.mode === editorMode.point){
me.mode = editorMode.bound;
}
me.mode.begin.call(me, e);
});
}
function initLayer() {
this.render.addLayer('cover', {
level: 30,
stroke: true,
strokeColor: 'green'
fill: false,
strokeColor: 'green',
fillColor: 'white',
});
this.render.addLayer('font', {
level: 20
level: 20,
strokeColor: 'red'
});
this.render.addLayer('axis', {
level: 10,
stroke: true
stroke: true,
fill: false
});
}
@ -160,7 +139,7 @@ define(
return shape;
});
this.shapes = shapes;
this.font = font;
// 渲染形状
@ -168,12 +147,19 @@ define(
var fontLayer = this.render.painter.getLayer('font');
this.shapes.forEach(function(shape) {
shapes.forEach(function(shape) {
fontLayer.addShape('path', shape);
});
this.render.refresh();
if (this.mode) {
this.mode.end.call(this);
}
this.mode = editorMode.bound;
this.mode.begin.call(this);
return this;
};

View File

@ -3,13 +3,13 @@
* @author mengke01
* @date
* @description
* 字体编辑控制器
* 形状编辑组
*/
define(
function(require) {
var adjustShape = require('./util/adjustShape');
var adjustShape = require('../util/adjustShape');
var lang = require('common/lang');
/**

121
src/editor/mode/bound.js Normal file
View File

@ -0,0 +1,121 @@
/**
* @file bound.js
* @author mengke01
* @date
* @description
* 轮廓模式处理事件
*/
define(
function(require) {
var ShapeGroup = require('../group/ShapeGroup');
var lang = require('common/lang');
var selectShape = require('render/util/selectShape');
var POS_CUSOR = require('./cursor');
var boundMode = {
name: 'bound',
/**
* 按下事件
*/
down: function(e) {
var render = this.render;
var result = render.getLayer('cover').getShapeIn(e);
if(result) {
if (this.currentGroup) {
this.currentPoint = lang.clone(result[0]);
this.currentGroup.beginTransform(this.currentPoint);
}
}
else {
if (this.currentGroup) {
this.currentGroup.dispose();
this.currentGroup = null;
}
result = render.getLayer('font').getShapeIn(e);
if(result) {
var shape = result[0];
if (result.length > 1) {
shape = selectShape(result);
}
this.currentGroup = new ShapeGroup(shape, render);
}
}
},
/**
* 拖动事件
*/
drag: function(e) {
var render = this.render;
var camera = render.camera;
if(this.currentGroup) {
if (this.currentPoint) {
this.currentGroup.transform(this.currentPoint, camera);
}
else {
this.currentGroup.move(camera.mx, camera.my);
}
}
},
/**
* 拖动结束事件
*/
dragend: function(e) {
if (this.currentGroup) {
if (this.currentPoint) {
this.currentGroup.finishTransform(this.currentPoint);
this.currentPoint = null;
}
}
},
/**
* 开始模式
*/
begin: function() {
var me = this;
var coverLayer = me.render.getLayer('cover');
// 注册鼠标样式
me.render.capture.on('move', me.__moveEvent = function (e) {
var shapes = coverLayer.getShapeIn(e);
if(shapes) {
me.render.setCursor(POS_CUSOR[shapes[0].pos] || 'default');
}
else {
me.render.setCursor('default');
}
});
},
/**
* 结束模式
*/
end: function() {
if (this.currentGroup) {
if (this.currentPoint) {
this.currentGroup.finishTransform(this.currentPoint);
this.currentPoint = null;
}
this.currentGroup.dispose();
this.currentGroup = null;
}
this.render.capture.un('move', this.__moveEvent);
this.render.setCursor('default');
}
};
return boundMode;
}
);

24
src/editor/mode/cursor.js Normal file
View File

@ -0,0 +1,24 @@
/**
* @file cursor.js
* @author mengke01
* @date
* @description
* 光标集合
*/
define(
function(require) {
// 不同位置的光标集合
return {
1: 'nw-resize',
2: 'ne-resize',
3: 'se-resize',
4: 'sw-resize',
5: 's-resize',
6: 'e-resize',
7: 'n-resize',
8: 'w-resize'
};;
}
);

View File

@ -0,0 +1,17 @@
/**
* @file editorMode.js
* @author mengke01
* @date
* @description
* 编辑器模式集合
*/
define(
function(require) {
return {
'bound': require('./bound'),
'point': require('./point')
};
}
);

150
src/editor/mode/point.js Normal file
View File

@ -0,0 +1,150 @@
/**
* @file point.js
* @author mengke01
* @date
* @description
* 点编辑模式
*/
define(
function(require) {
var pathIterator = require('render/util/pathIterator');
var computeBoundingBox = require('graphics/computeBoundingBox');
var pathAdjust = require('render/util/pathAdjust');
var pointMode = {
name: 'point',
/**
* 按下事件
*/
down: function(e) {
var render = this.render;
var result = render.getLayer('cover').getShapeIn(e);
if(result) {
this.currentPoint = result[0];
}
},
/**
* 拖动事件
*/
drag: function(e) {
var render = this.render;
var camera = render.camera;
if(this.currentPoint) {
this.currentPoint.x += camera.mx;
this.currentPoint.y += camera.my;
this.currentPoint._point.x += camera.mx;
this.currentPoint._point.y += camera.my;
render.getLayer('cover').refresh();
render.getLayer('font').refresh();
this.modifiedShape[this.currentPoint._shape] = true;
}
},
/**
* 拖动结束事件
*/
dragend: function(e) {
this.currentPoint = null;
},
begin: function() {
var controls = [];
var shapes = this.render.getLayer('font').shapes;
shapes.forEach(function(shape) {
pathIterator(shape.points, function(c, i, p0, p1, p2) {
if(c == 'M' || c == 'L') {
controls.push({
type: 'point',
x: shape.x + p0.x,
y: shape.y + p0.y,
_point: p0,
_shape: shape.id
});
}
else if (c == 'Q') {
controls.push({
type: 'cpoint',
x: shape.x + p1.x,
y: shape.y + p1.y,
_point: p1,
_shape: shape.id
});
controls.push({
type: 'point',
x: shape.x + p2.x,
y: shape.y + p2.y,
_point: p2,
_shape: shape.id
});
}
});
});
var coverLayer = this.render.getLayer('cover');
coverLayer.options.fill = true;
controls.forEach(function(shape){
coverLayer.addShape(shape);
});
coverLayer.refresh();
var me = this;
// 注册鼠标样式
me.render.capture.on('move', me.__moveEvent = function (e) {
var shape = coverLayer.getShapeIn(e);
if(shape) {
me.render.setCursor('pointer');
}
else {
me.render.setCursor('default');
}
});
this.modifiedShape = {};
},
end: function() {
// 重新调整shape大小和位置
var shapes = Object.keys(this.modifiedShape);
if(shapes.length) {
var fontLayer = this.render.getLayer('font');
shapes.forEach(function(shapeId) {
var shape = fontLayer.getShape(shapeId);
var pathBox = computeBoundingBox.computePathBox(shape.points);
shape.width = pathBox.width;
shape.height = pathBox.height;
shape.x = shape.x + pathBox.x;
shape.y = shape.y + pathBox.y;
shape.points = pathAdjust(shape.points, 1, -pathBox.x, -pathBox.y);
});
fontLayer.refresh();
}
this.modifiedShape = null;
var coverLayer = this.render.getLayer('cover');
coverLayer.options.fill = false;
coverLayer.clearShapes();
coverLayer.refresh();
this.render.capture.un('move', this.__moveEvent);
this.render.setCursor('default');
}
};
return pointMode;
}
);

View File

@ -10,14 +10,12 @@
define(
function(require) {
var pathIterator = require('render/util/pathIterator');
/**
* 根据相对值调整shape大小
*/
function adjustShape(shape, matrix) {
var i = -1;
var l = shape.points.length;
var point;
var scaleX = matrix[2];
var scaleY = matrix[3];
var offsetX = 0;
@ -31,22 +29,18 @@ define(
offsetY = -shape.height;
}
while (++i < l) {
var point = shape.points[i];
switch (point.c) {
case 'M':
case 'L':
point.p.x = scaleX * (point.p.x + offsetX);
point.p.y = scaleY * (point.p.y + offsetY);
break;
case 'Q':
point.p.x = scaleX * (point.p.x + offsetX);
point.p.y = scaleY * (point.p.y + offsetY);
point.p1.x = scaleX * (point.p1.x + offsetX);
point.p1.y = scaleY * (point.p1.y + offsetY);
break;
pathIterator(shape.points, function(c, i, p0, p1, p2) {
if (c == 'Q') {
p1.x = scaleX * (p1.x + offsetX);
p1.y = scaleY * (p1.y + offsetY);
p2.x = scaleX * (p2.x + offsetX);
p2.y = scaleY * (p2.y + offsetY);
}
}
else {
p0.x = scaleX * (p0.x + offsetX);
p0.y = scaleY * (p0.y + offsetY);
}
});
shape.x = matrix[0];
shape.y = matrix[1];

View File

@ -103,7 +103,6 @@ define(
*/
function computePathBoundingBox(path) {
var l = path.length;
var xMax = xMin = yMax = yMin = 0;
var i = -1;
var points = [];
while (++i < l) {
@ -111,6 +110,7 @@ define(
switch (point.c) {
case 'M':
case 'L':
case 'Z':
points.push(point.p);
break;
case 'Q':
@ -135,10 +135,41 @@ define(
return computeBoundingBox(points);
}
/**
* 计算曲线点边界
*
* @private
* @param {path} path对象
*
* @return {Object} x,y,width,height
*/
function computePathBox(path) {
var l = path.length;
var i = -1;
var points = [];
while (++i < l) {
var point = path[i];
switch (point.c) {
case 'M':
case 'L':
case 'Z':
points.push(point.p);
break;
case 'Q':
points.push(point.p1);
points.push(point.p);
break;
}
}
return computeBoundingBox(points);
}
return {
computeBounding: computeBoundingBox,
quadraticBezier: computeQuadraticBezierBoundingBox,
computePath: computePathBoundingBox
computePath: computePathBoundingBox,
computePathBox: computePathBox,
};
}
);

View File

@ -33,17 +33,21 @@ define(
* 设置canvas的绘制样式
*/
function setContextStyle(context, options) {
context.fillStyle = options.fillColor || 'black';
context.strokeStyle = options.strokeColor || 'black';
context.strokeWidth = options.strokeWidth || 1;
}
if(options.fillColor) {
context.fillStyle = options.fillColor;
/**
* 绘制图形
*/
function draw(context, options) {
if(false !== options.stroke) {
context.stroke();
}
if(options.strokeColor) {
context.strokeStyle = options.strokeColor;
}
if(options.strokeWidth) {
context.strokeWidth = options.strokeWidth || 1;
if(false !== options.fill) {
context.fill();
}
}
@ -68,7 +72,12 @@ define(
*/
function Layer(context, options) {
this.context = context;
this.options = options || {};
this.options = lang.extend({
stroke: true,
fill: true
}, options);
this.painter = this.options.painter;
this.id = this.options.id || guid('layer');
this.level = this.options.level;
@ -86,15 +95,13 @@ define(
*/
refresh: function() {
var support = this.painter.support;
this.context.clearRect(0, 0, this.painter.width, this.painter.height);
setContextStyle(this.context, this.options);
this.context.beginPath();
var context = this.context;
var options = this.options;
var camera = this.painter.camera;
context.clearRect(0, 0, this.painter.width, this.painter.height);
setContextStyle(context, options);
context.beginPath();
this.shapes.forEach(function(shape) {
var drawer = support[shape.type];
@ -103,17 +110,28 @@ define(
if(camera.ratio != 1) {
drawer.adjust(shape, camera);
}
drawer.draw(context, shape);
if(shape.style) {
// 绘制之前shape
draw(context, options);
// 绘制当前shape
context.beginPath();
setContextStyle(context, shape.style);
drawer.draw(context, shape);
draw(context, options);
// 重置
setContextStyle(context, options);
context.beginPath();
}
else {
drawer.draw(context, shape);
}
}
});
if(this.options.stroke) {
this.context.stroke();
}
else {
this.context.fill();
}
draw(context, options);
return this;
},

View File

@ -53,7 +53,7 @@ define(
function Render(main, options) {
this.main = main;
this.options = options || {};
this.options = lang.extend({}, options);
this.id = guid();
if(!this.main) {
@ -63,6 +63,16 @@ define(
init.call(this);
}
/**
* 设置鼠标样式
*
* @param {string} name 名字
* @return {Render} 本对象
*/
Render.prototype.setCursor = function(name) {
this.main.style.cursor = name || 'default';
};
/**
* 刷新render
*

View File

@ -10,17 +10,12 @@
define(
function(require) {
var POINT_SIZE = 6;
var POINT_SIZE = 2;
var proto = {
type: 'cpoint',
// 调整大小
adjust: function(shape, camera) {
return shape;
},
/**
* 获取shape的矩形区域
*
@ -45,7 +40,7 @@ define(
* @param {boolean} 是否
*/
isIn: function(shape, x, y) {
return Math.pow(shape.x - x, 2) + Math.pow(shape.y - y, 2) <= Math.pow(POINT_SIZE, 2);
return Math.pow(shape.x - x, 2) + Math.pow(shape.y - y, 2) <= Math.pow(POINT_SIZE * 2, 2);
},
/**

View File

@ -10,6 +10,8 @@
define(
function(require) {
var dashedLineTo = require('../util/dashedLineTo');
var proto = {
type: 'dashedrect',
@ -50,11 +52,10 @@ define(
draw: function(ctx, shape) {
var w = shape.width;
var h = shape.height;
ctx.moveTo(shape.x, shape.y);
ctx.lineTo(shape.x + w, shape.y);
ctx.lineTo(shape.x + w, shape.y + h);
ctx.lineTo(shape.x, shape.y + h);
ctx.lineTo(shape.x, shape.y);
dashedLineTo(ctx, shape.x, shape.y, shape.x + w, shape.y);
dashedLineTo(ctx, shape.x + w, shape.y, shape.x + w, shape.y + h);
dashedLineTo(ctx, shape.x + w, shape.y + h, shape.x, shape.y + h);
dashedLineTo(ctx, shape.x, shape.y + h, shape.x, shape.y);
}
};

View File

@ -10,16 +10,11 @@
define(
function(require) {
var POINT_SIZE = 8; // 控制点的大小
var POINT_SIZE = 6; // 控制点的大小
var proto = {
type: 'point',
// 调整大小
adjust: function(shape, camera) {
return shape;
},
/**
* 获取shape的矩形区域
@ -45,8 +40,8 @@ define(
* @param {boolean} 是否
*/
isIn: function(shape, x, y) {
var w = POINT_SIZE / 2;
var h = POINT_SIZE / 2;
var w = POINT_SIZE;
var h = POINT_SIZE;
return x <= shape.x + w
&& x >= shape.x - w
&& y <= shape.y + h

View File

@ -14,7 +14,7 @@ define(
cpoint: require('./CirclePoint'),
rect: require('./Rect'),
dashedrect: require('./DashedRect'),
point: require('./point'),
point: require('./Point'),
font: require('./Font'),
path: require('./Path')
};

View File

@ -0,0 +1,55 @@
/**
* @file dashedLineTo.js
* @author mengke01
* @date
* @description
* 绘制虚线段
*
* modify from:
* zrender/src/shape/util
*/
define(
function(require) {
/**
* 绘制虚线段
*
* @param {Context2D} ctx canvascontext
* @param {number} x1 x1坐标
* @param {number} y1 y1坐标
* @param {number} x2 x2坐标
* @param {number} y2 y2坐标
* @param {?number} dashLength 虚线长度
*/
function dashedLineTo(ctx, x1, y1, x2, y2, dashLength) {
dashLength = typeof dashLength != 'number'
? 2
: dashLength;
var dx = x2 - x1;
var dy = y2 - y1;
var numDashes = Math.floor(
Math.sqrt(dx * dx + dy * dy) / dashLength
);
dx = dx / numDashes;
dy = dy / numDashes;
var flag = true;
for (var i = 0; i < numDashes; ++i) {
if (flag) {
ctx.moveTo(x1, y1);
} else {
ctx.lineTo(x1, y1);
}
flag = !flag;
x1 += dx;
y1 += dy;
}
ctx.lineTo(x2, y2);
}
return dashedLineTo;
}
);

View File

@ -10,6 +10,8 @@
define(
function(require) {
var pathIterator = require('./pathIterator');
/**
* 对path坐标进行调整
*
@ -23,43 +25,34 @@ define(
var scale = scale || 1;
var x = offsetX || 0;
var y = offsetY || 0;
var l = path.length;
var i = -1;
if(scale == 1) {
while (++i < l) {
var point = path[i];
switch (point.c) {
case 'M':
case 'L':
point.p.x = (point.p.x + x);
point.p.y = (point.p.y + y);
break;
case 'Q':
point.p.x = (point.p.x + x);
point.p.y = (point.p.y + y);
point.p1.x = (point.p1.x + x);
point.p1.y = (point.p1.y + y);
break;
pathIterator(path, function(c, i, p0, p1, p2) {
if (c == 'Q') {
p1.x = p1.x + x;
p1.y = p1.y + y;
p2.x = p2.x + x;
p2.y = p2.y + y;
}
}
else {
p0.x = p0.x + x;
p0.y = p0.y + y;
}
});
}
else {
while (++i < l) {
var point = path[i];
switch (point.c) {
case 'M':
case 'L':
point.p.x = scale * (point.p.x + x);
point.p.y = scale * (point.p.y + y);
break;
case 'Q':
point.p.x = scale * (point.p.x + x);
point.p.y = scale * (point.p.y + y);
point.p1.x = scale * (point.p1.x + x);
point.p1.y = scale * (point.p1.y + y);
break;
pathIterator(path, function(c, i, p0, p1, p2) {
if (c == 'Q') {
p1.x = scale * (p1.x + x);
p1.y = scale * (p1.y + y);
p2.x = scale * (p2.x + x);
p2.y = scale * (p2.y + y);
}
}
else {
p0.x = scale * (p0.x + x);
p0.y = scale * (p0.y + y);
}
});
}
return path;
}

View File

@ -0,0 +1,44 @@
/**
* @file pathIterator.js
* @author mengke01
* @date
* @description
* path遍历
*/
define(
function(require) {
/**
* 路径迭代器
*
* @param {Array} path 路径点集合
* @param {Function} iterator 迭代器参数command, points, index
* @return {Array} path 路径点集合
*/
function pathIterator(path, iterator) {
var i = -1;
var l = path.length;
var prev, point;
while (++i < l) {
point = path[i];
switch (point.c) {
case 'M':
case 'L':
case 'Z':
iterator && iterator(point.c, i, point.p);
prev = point.p;
break;
case 'Q':
iterator && iterator('Q', i, prev, point.p1, point.p);
prev = point.p;
break;
}
}
return path;
}
return pathIterator;
}
);