diff --git a/lib/pen.js b/lib/pen.js index 89b9a78..0cc725d 100644 --- a/lib/pen.js +++ b/lib/pen.js @@ -9,17 +9,23 @@ export default class Painter { this.globalHeight = {}; } - paint(callback) { + isMoving = false + movingCache = {} + paint(callback, isMoving, movingCache) { this.style = { width: this.data.width.toPx(), height: this.data.height.toPx(), }; + if (isMoving) { + this.isMoving = true + this.movingCache = movingCache + } this._background(); for (const view of this.data.views) { this._drawAbsolute(view); } this.ctx.draw(false, () => { - callback && callback(); + callback && callback(this.callbackInfo); }); } @@ -420,6 +426,7 @@ export default class Painter { this._doBorder(view, width, height); } + callbackInfo = {} _fillAbsText(view) { if (!view.text) { return; @@ -434,77 +441,16 @@ export default class Painter { height, extra, } = this._preProcess(view, view.css.background && view.css.borderRadius); - - this.ctx.fillStyle = (view.css.color || 'black'); - const { - lines, - lineHeight, - textArray, - linesArray, - } = extra; - // 如果设置了id,则保留 text 的长度 - if (view.id) { - let textWidth = 0; - for (let i = 0; i < textArray.length; ++i) { - textWidth = this.ctx.measureText(textArray[i]).width > textWidth ? this.ctx.measureText(textArray[i]).width : textWidth; - } - this.globalWidth[view.id] = width ? (textWidth < width ? textWidth : width) : textWidth; - } - let lineIndex = 0; - for (let j = 0; j < textArray.length; ++j) { - const preLineLength = Math.round(textArray[j].length / linesArray[j]); - let start = 0; - let alreadyCount = 0; - for (let i = 0; i < linesArray[j]; ++i) { - // 绘制行数大于最大行数,则直接跳出循环 - if (lineIndex >= lines) { - break; - } - alreadyCount = preLineLength; - let text = textArray[j].substr(start, alreadyCount); - let measuredWith = this.ctx.measureText(text).width; - // 如果测量大小小于width一个字符的大小,则进行补齐,如果测量大小超出 width,则进行减除 - // 如果已经到文本末尾,也不要进行该循环 - while ((start + alreadyCount <= textArray[j].length) && (width - measuredWith > view.css.fontSize.toPx() || measuredWith > width)) { - if (measuredWith < width) { - text = textArray[j].substr(start, ++alreadyCount); - } else { - if (text.length <= 1) { - // 如果只有一个字符时,直接跳出循环 - break; - } - text = textArray[j].substr(start, --alreadyCount); - } - measuredWith = this.ctx.measureText(text).width; - } - start += text.length; - // 如果是最后一行了,发现还有未绘制完的内容,则加... - if (lineIndex === lines - 1 && (j < textArray.length - 1 || start < textArray[j].length)) { - while (this.ctx.measureText(`${text}...`).width > width) { - if (text.length <= 1) { - // 如果只有一个字符时,直接跳出循环 - break; - } - text = text.substring(0, text.length - 1); - } - text += '...'; - measuredWith = this.ctx.measureText(text).width; - } - this.ctx.setTextAlign(view.css.textAlign ? view.css.textAlign : 'left'); - let x; - switch (view.css.textAlign) { - case 'center': - x = 0; - break; - case 'right': - x = (width / 2); - break; - default: - x = -(width / 2); - break; - } - const y = -(height / 2) + (lineIndex === 0 ? view.css.fontSize.toPx() : (view.css.fontSize.toPx() + lineIndex * lineHeight)); - lineIndex++; + if (this.isMoving && JSON.stringify(this.movingCache) !== JSON.stringify({})) { + this.globalWidth[view.id] = this.movingCache.globalWidth + this.ctx.setTextAlign(view.css.textAlign ? view.css.textAlign : 'left'); + for (const i of this.movingCache.lineArray) { + const { + measuredWith, + text, + x, + y, + } = i if (view.css.textStyle === 'stroke') { this.ctx.strokeText(text, x, y, measuredWith); } else { @@ -513,23 +459,130 @@ export default class Painter { const fontSize = view.css.fontSize.toPx(); if (view.css.textDecoration) { this.ctx.beginPath(); - if (/\bunderline\b/.test(view.css.textDecoration)) { - this.ctx.moveTo(x, y); - this.ctx.lineTo(x + measuredWith, y); - } - if (/\boverline\b/.test(view.css.textDecoration)) { - this.ctx.moveTo(x, y - fontSize); - this.ctx.lineTo(x + measuredWith, y - fontSize); - } - if (/\bline-through\b/.test(view.css.textDecoration)) { - this.ctx.moveTo(x, y - fontSize / 3); - this.ctx.lineTo(x + measuredWith, y - fontSize / 3); - } + this.ctx.moveTo(...this.callbackInfo.textDecoration.moveTo); + this.ctx.lineTo(...this.callbackInfo.textDecoration.lineTo); this.ctx.closePath(); this.ctx.strokeStyle = view.css.color; this.ctx.stroke(); } } + } else { + this.ctx.fillStyle = (view.css.color || 'black'); + const { + lines, + lineHeight, + textArray, + linesArray, + } = extra; + // 如果设置了id,则保留 text 的长度 + if (view.id) { + let textWidth = 0; + for (let i = 0; i < textArray.length; ++i) { + textWidth = this.ctx.measureText(textArray[i]).width > textWidth ? this.ctx.measureText(textArray[i]).width : textWidth; + } + this.globalWidth[view.id] = width ? (textWidth < width ? textWidth : width) : textWidth; + if (!this.isMoving) { + Object.assign(this.callbackInfo, { + globalWidth: this.globalWidth[view.id] + }) + } + } + let lineIndex = 0; + for (let j = 0; j < textArray.length; ++j) { + const preLineLength = Math.round(textArray[j].length / linesArray[j]); + let start = 0; + let alreadyCount = 0; + for (let i = 0; i < linesArray[j]; ++i) { + // 绘制行数大于最大行数,则直接跳出循环 + if (lineIndex >= lines) { + break; + } + alreadyCount = preLineLength; + let text = textArray[j].substr(start, alreadyCount); + let measuredWith = this.ctx.measureText(text).width; + // 如果测量大小小于width一个字符的大小,则进行补齐,如果测量大小超出 width,则进行减除 + // 如果已经到文本末尾,也不要进行该循环 + while ((start + alreadyCount <= textArray[j].length) && (width - measuredWith > view.css.fontSize.toPx() || measuredWith > width)) { + if (measuredWith < width) { + text = textArray[j].substr(start, ++alreadyCount); + } else { + if (text.length <= 1) { + // 如果只有一个字符时,直接跳出循环 + break; + } + text = textArray[j].substr(start, --alreadyCount); + } + measuredWith = this.ctx.measureText(text).width; + } + start += text.length + // 如果是最后一行了,发现还有未绘制完的内容,则加... + if (lineIndex === lines - 1 && (j < textArray.length - 1 || start < textArray[j].length)) { + while (this.ctx.measureText(`${text}...`).width > width) { + if (text.length <= 1) { + // 如果只有一个字符时,直接跳出循环 + break; + } + text = text.substring(0, text.length - 1); + } + text += '...'; + measuredWith = this.ctx.measureText(text).width; + } + this.ctx.setTextAlign(view.css.textAlign ? view.css.textAlign : 'left'); + let x; + switch (view.css.textAlign) { + case 'center': + x = 0; + break; + case 'right': + x = (width / 2); + break; + default: + x = -(width / 2); + break; + } + const y = -(height / 2) + (lineIndex === 0 ? view.css.fontSize.toPx() : (view.css.fontSize.toPx() + lineIndex * lineHeight)); + lineIndex++; + if (view.css.textStyle === 'stroke') { + this.ctx.strokeText(text, x, y, measuredWith); + } else { + this.ctx.fillText(text, x, y, measuredWith); + } + const fontSize = view.css.fontSize.toPx(); + if (view.css.textDecoration) { + this.ctx.beginPath(); + if (/\bunderline\b/.test(view.css.textDecoration)) { + this.ctx.moveTo(x, y); + this.ctx.lineTo(x + measuredWith, y); + !this.isMoving && (this.callbackInfo.textDecoration = { + moveTo: [x, y], + lineTo: [x + measuredWith, y] + }) + } + if (/\boverline\b/.test(view.css.textDecoration)) { + this.ctx.moveTo(x, y - fontSize); + this.ctx.lineTo(x + measuredWith, y - fontSize); + !this.isMoving && (this.callbackInfo.textDecoration = { + moveTo: [x, y - fontSize], + lineTo: [x + measuredWith, y - fontSize] + }) + } + if (/\bline-through\b/.test(view.css.textDecoration)) { + this.ctx.moveTo(x, y - fontSize / 3); + this.ctx.lineTo(x + measuredWith, y - fontSize / 3); + !this.isMoving && (this.callbackInfo.textDecoration = { + moveTo: [x, y - fontSize / 3], + lineTo: [x + measuredWith, y - fontSize / 3] + }) + } + this.ctx.closePath(); + this.ctx.strokeStyle = view.css.color; + this.ctx.stroke(); + } + if (!this.isMoving) { + this.callbackInfo.lineArray ? this.callbackInfo.lineArray.push({ text, x, y, measuredWith }) : this.callbackInfo.lineArray = [{ text, x, y, measuredWith }] + } + } + } } this.ctx.restore(); this._doBorder(view, width, height); diff --git a/painter.js b/painter.js index 90ccf50..59f5a1b 100644 --- a/painter.js +++ b/painter.js @@ -13,6 +13,7 @@ Component({ canvasHeightInPx: 0, paintCount: 0, currentPalette: {}, + movingCache: {}, /** * 组件的属性列表 */ @@ -22,7 +23,7 @@ Component({ }, palette: { type: Object, - observer: function (newVal, oldVal) { + observer: function(newVal, oldVal) { if (this.isNeedRefresh(newVal, oldVal)) { this.paintCount = 0; this.startPaint(); @@ -31,7 +32,7 @@ Component({ }, dancePalette: { type: Object, - observer: function (newVal, oldVal) { + observer: function(newVal, oldVal) { if (!this.isEmpty(newVal)) { this.initDancePalette(newVal); } @@ -48,7 +49,7 @@ Component({ }, action: { type: Object, - observer: function (newVal, oldVal) { + observer: function(newVal, oldVal) { if (newVal) { this.doAction(newVal) } @@ -82,7 +83,7 @@ Component({ return true; }, - doAction(newVal) { + doAction(newVal, callback, isMoving) { if (newVal && newVal.id && this.touchedView.id !== newVal.id) { // 带 id 的动作给撤回时使用,不带 id,表示对当前选中对象进行操作 const { @@ -118,17 +119,21 @@ Component({ const draw = { width: this.currentPalette.width, height: this.currentPalette.height, - views: [doView] + views: this.isEmpty(doView) ? [] : [doView] } const pen = new Pen(this.globalContext, draw); - pen.paint(); + if (isMoving && this.currentPalette.views[0].type === 'text') { + pen.paint(callback, true, this.movingCache); + } else { + pen.paint(callback) + } const { rect } = doView const block = { width: this.currentPalette.width, height: this.currentPalette.height, - views: [{ + views: this.isEmpty(this.touchedView) ? [] : [{ type: 'rect', css: { height: `${rect.bottom - rect.top}px`, @@ -151,6 +156,19 @@ Component({ } }] } + if (this.touchedView.type === 'text') { + block.views.push({ + type: 'rect', + css: { + height: `${2 * ACTION_POINT_RADIUS}px`, + width: `${2 * ACTION_POINT_RADIUS}px`, + borderRadius: `${ACTION_POINT_RADIUS}px`, + color: '#0000ff', + left: `${rect.left - ACTION_POINT_RADIUS}px`, + top: `${rect.top - ACTION_POINT_RADIUS}px` + } + }) + } const topBlock = new Pen(this.frontContext, block) topBlock.paint(); }, @@ -173,34 +191,48 @@ Component({ ) }, + isDelete(x, y, rect) { + return (x > rect.left - ACTION_POINT_RADIUS && + y > rect.top - ACTION_POINT_RADIUS && + x < rect.left + ACTION_POINT_RADIUS && + y < rect.top + ACTION_POINT_RADIUS) + }, + touchedView: {}, findedIndex: -1, - onClick(event) { + onClick() { const x = this.startX const y = this.startY const totalLayerCount = this.currentPalette.views.length - const hasTouchedView = this.findedIndex !== -1 - this.touchedView = {} let canBeTouched = [] + let isDelete = false for (let i = totalLayerCount - 1; i >= 0; i--) { const view = this.currentPalette.views[i] const { rect } = view - if (this.inArea(x, y, rect, hasTouchedView)) { + if (this.touchedView && this.touchedView.id && + this.touchedView.id === view.id && + this.isDelete(x, y, rect)) { + canBeTouched.length = 0 + this.currentPalette.views.splice(i, 1) + isDelete = true + break + } + if (this.inArea(x, y, rect, !this.isEmpty(this.touchedView))) { canBeTouched.push({ view, index: i }) } } + this.touchedView = {} if (canBeTouched.length === 0) { this.findedIndex = -1 } else { let i = 0 const touchAble = canBeTouched.filter(item => Boolean(item.view.id)) if (touchAble.length === 0) { - this.touchedView = {} this.findedIndex = canBeTouched[0].index } else { for (i = 0; i < touchAble.length; i++) { @@ -226,7 +258,6 @@ Component({ } if (this.findedIndex < 0 || (this.touchedView && !this.touchedView.id)) { // 证明点击了背景 或无法移动的view - this.touchedView = {} const block = { width: this.currentPalette.width, height: this.currentPalette.height, @@ -234,7 +265,9 @@ Component({ } const topBlock = new Pen(this.frontContext, block) topBlock.paint(); - if (this.findedIndex < 0) { + if (isDelete) { + this.doAction() + } else if (this.findedIndex < 0) { this.triggerEvent('touchStart', {}) } this.findedIndex = -1 @@ -260,11 +293,15 @@ Component({ } if (this.prevFindedIndex < this.findedIndex) { new Pen(this.bottomContext, bottomDraw).paint(); - this.doAction() + this.doAction(null, (callbackInfo) => { + this.movingCache = callbackInfo + }) new Pen(this.topContext, topDraw).paint(); } else { new Pen(this.topContext, topDraw).paint(); - this.doAction() + this.doAction(null, (callbackInfo) => { + this.movingCache = callbackInfo + }) new Pen(this.bottomContext, bottomDraw).paint(); } this.prevFindedIndex = this.findedIndex @@ -290,6 +327,7 @@ Component({ } = this.touchedView if (rect.right - ACTION_POINT_RADIUS < x && x < rect.right + ACTION_POINT_RADIUS && rect.bottom - ACTION_POINT_RADIUS < y && y < rect.bottom + ACTION_POINT_RADIUS) { this.isScale = true + this.movingCache = {} this.startH = rect.bottom - rect.top this.startW = rect.right - rect.left } else { @@ -357,7 +395,11 @@ Component({ } this.doAction({ css - }) + }, (callbackInfo) => { + if (this.isScale) { + this.movingCache = callbackInfo + } + }, !this.isScale) }, initScreenK() { @@ -378,12 +420,6 @@ Component({ initDancePalette() { this.initScreenK(); - this.hasIdViews = [] - this.properties.dancePalette && this.properties.dancePalette.views.map(view => { - if (view.id) { - this.hasIdViews.push(view) - } - }) this.downloadImages(this.properties.dancePalette).then((palette) => { this.currentPalette = palette @@ -507,10 +543,10 @@ Component({ setTimeout(() => { wx.canvasToTempFilePath({ canvasId: 'photo', - success: function (res) { + success: function(res) { that.getImageInfo(res.tempFilePath); }, - fail: function (error) { + fail: function(error) { console.error(`canvasToTempFilePath failed, ${JSON.stringify(error)}`); that.triggerEvent('imgErr', { error: error @@ -584,4 +620,4 @@ function setStringPrototype(screenK, scale) { } return res; }; -} \ No newline at end of file +}