diff --git a/src/mixins/itext_behavior.mixin.js b/src/mixins/itext_behavior.mixin.js index 1ced81ce..914078eb 100644 --- a/src/mixins/itext_behavior.mixin.js +++ b/src/mixins/itext_behavior.mixin.js @@ -824,6 +824,58 @@ */ insertNewline: function() { this.insertChars('\n'); + }, + + /** + * Set the selectionStart and selectionEnd according to the ne postion of cursor + * mimic the key - mouse navigation when shift is pressed. + */ + setSelectionStartEndWithShift: function(start, end, newSelection) { + if (newSelection <= start) { + if (end === start) { + this._selectionDirection = 'left'; + } + else if (this._selectionDirection === 'right') { + this._selectionDirection = 'left'; + this.selectionEnd = start; + } + this.selectionStart = newSelection; + } + else if (newSelection > start && newSelection < end) { + if (this._selectionDirection === 'right') { + this.selectionEnd = newSelection; + } + else { + this.selectionStart = newSelection; + } + } + else { + // newSelection is > selection start and end + if (end === start) { + this._selectionDirection = 'right'; + } + else if (this._selectionDirection === 'left') { + this._selectionDirection = 'right'; + this.selectionStart = end; + } + this.selectionEnd = newSelection; + } + }, + + setSelectionInBoundaries: function() { + var length = this.text.length; + if (this.selectionStart > length) { + this.selectionStart = length; + } + else if (this.selectionStart < 0) { + this.selectionStart = 0; + } + if (this.selectionEnd > length) { + this.selectionEnd = length; + } + else if (this.selectionEnd < 0) { + this.selectionEnd = 0; + } } }); })(); diff --git a/src/mixins/itext_click_behavior.mixin.js b/src/mixins/itext_click_behavior.mixin.js index 17a1a36d..c1ce178e 100644 --- a/src/mixins/itext_click_behavior.mixin.js +++ b/src/mixins/itext_click_behavior.mixin.js @@ -148,35 +148,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot var newSelection = this.getSelectionStartFromPointer(e), start = this.selectionStart, end = this.selectionEnd; if (e.shiftKey) { - if (newSelection < start) { - if (end === start) { - this._selectionDirection = 'left'; - } - else if (this._selectionDirection === 'right') { - this._selectionDirection = 'left'; - this.selectionEnd = start; - } - this.selectionStart = newSelection; - } - else if (newSelection > start && newSelection < end) { - if (this._selectionDirection === 'right') { - this.selectionEnd = newSelection; - } - else { - this.selectionStart = newSelection; - } - } - else { - // newSelection is > selection start and end - if (end === start) { - this._selectionDirection = 'right'; - } - else if (this._selectionDirection === 'left') { - this._selectionDirection = 'right'; - this.selectionStart = end; - } - this.selectionEnd = newSelection; - } + this.setSelectionStartEndWithShift(start, end, newSelection); } else { this.selectionStart = newSelection; diff --git a/src/mixins/itext_key_behavior.mixin.js b/src/mixins/itext_key_behavior.mixin.js index bb1ebbbf..27664386 100644 --- a/src/mixins/itext_key_behavior.mixin.js +++ b/src/mixins/itext_key_behavior.mixin.js @@ -255,6 +255,25 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot return (e && e.clipboardData) || fabric.window.clipboardData; }, + /** + * Finds the width in pixels before the cursor on the same line + * @private + * @param {Number} lineIndex + * @param {Number} charIndex + * @return {Number} widthBeforeCursor width before cursor + */ + _getWidthBeforeCursor: function(lineIndex, charIndex) { + var textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex), + widthOfLine = this._getLineWidth(this.ctx, lineIndex), + widthBeforeCursor = this._getLineLeftOffset(widthOfLine), _char; + + for (var i = 0, len = textBeforeCursor.length; i < len; i++) { + _char = textBeforeCursor[i]; + widthBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); + } + return widthBeforeCursor; + }, + /** * Gets start offset of a selection * @param {Event} e Event object @@ -262,64 +281,89 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot * @return {Number} */ getDownCursorOffset: function(e, isRight) { - var selectionProp = isRight ? this.selectionEnd : this.selectionStart, + var selectionProp = this._getSelectionForOffset(e, isRight), cursorLocation = this.get2DCursorLocation(selectionProp), - _char, lineLeftOffset, lineIndex = cursorLocation.lineIndex, - textOnSameLineBeforeCursor = this._textLines[lineIndex].slice(0, cursorLocation.charIndex), - textOnSameLineAfterCursor = this._textLines[lineIndex].slice(cursorLocation.charIndex), - textOnNextLine = this._textLines[lineIndex + 1] || ''; - + lineIndex = cursorLocation.lineIndex; // if on last line, down cursor goes to end of line if (lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) { - // move to the end of a text return this.text.length - selectionProp; } + var charIndex = cursorLocation.charIndex, + widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex), + indexOnOtherLine = this._getIndexOnLine(lineIndex + 1, widthBeforeCursor), + textAfterCursor = this._textLines[lineIndex].slice(charIndex); - var widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, lineIndex); - lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); - - var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset; - - for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { - _char = textOnSameLineBeforeCursor[i]; - widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); - } - - var indexOnNextLine = this._getIndexOnNextLine( - cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor); - - return textOnSameLineAfterCursor.length + 1 + indexOnNextLine; + return textAfterCursor.length + indexOnOtherLine + 2; }, /** + * private + * Helps finding if the offset should be counted from Start or End + * @param {Event} e Event object + * @param {Boolean} isRight + * @return {Number} + */ + _getSelectionForOffset: function(e, isRight) { + if (e.shiftKey && this.selectionStart !== this.selectionEnd && isRight) { + return this.selectionEnd; + } + else { + return this.selectionStart; + } + }, + + /** + * @param {Event} e Event object + * @param {Boolean} isRight + * @return {Number} + */ + getUpCursorOffset: function(e, isRight) { + var selectionProp = this._getSelectionForOffset(e, isRight), + cursorLocation = this.get2DCursorLocation(selectionProp), + lineIndex = cursorLocation.lineIndex; + if (lineIndex === 0 || e.metaKey || e.keyCode === 33) { + // if on first line, up cursor goes to start of line + return -selectionProp; + } + var charIndex = cursorLocation.charIndex, + widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex), + indexOnOtherLine = this._getIndexOnLine(lineIndex - 1, widthBeforeCursor), + textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex); + // return a negative offset + return -this._textLines[lineIndex - 1].length + indexOnOtherLine - textBeforeCursor.length; + }, + + /** + * find for a given width it founds the matching character. * @private */ - _getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor) { - var lineIndex = cursorLocation.lineIndex + 1, - widthOfNextLine = this._getLineWidth(this.ctx, lineIndex), - lineLeftOffset = this._getLineLeftOffset(widthOfNextLine), - widthOfCharsOnNextLine = lineLeftOffset, - indexOnNextLine = 0, + _getIndexOnLine: function(lineIndex, width) { + + var widthOfLine = this._getLineWidth(this.ctx, lineIndex), + textOnLine = this._textLines[lineIndex], + lineLeftOffset = this._getLineLeftOffset(widthOfLine), + widthOfCharsOnLine = lineLeftOffset, + indexOnLine = 0, foundMatch; - for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) { + for (var j = 0, jlen = textOnLine.length; j < jlen; j++) { - var _char = textOnNextLine[j], + var _char = textOnLine[j], widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); - widthOfCharsOnNextLine += widthOfChar; + widthOfCharsOnLine += widthOfChar; - if (widthOfCharsOnNextLine > widthOfCharsOnSameLineBeforeCursor) { + if (widthOfCharsOnLine > width) { foundMatch = true; - var leftEdge = widthOfCharsOnNextLine - widthOfChar, - rightEdge = widthOfCharsOnNextLine, - offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), - offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); + var leftEdge = widthOfCharsOnLine - widthOfChar, + rightEdge = widthOfCharsOnLine, + offsetFromLeftEdge = Math.abs(leftEdge - width), + offsetFromRightEdge = Math.abs(rightEdge - width); - indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j; + indexOnLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); break; } @@ -327,12 +371,13 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot // reached end if (!foundMatch) { - indexOnNextLine = textOnNextLine.length; + indexOnLine = textOnLine.length - 1; } - return indexOnNextLine; + return indexOnLine; }, + /** * Moves cursor down * @param {Event} e Event object @@ -344,124 +389,6 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this._moveCursorUpOrDown('Down', e); }, - /** - * Moves cursor down without keeping selection - * @param {Number} offset - */ - moveCursorDownWithoutShift: function(offset) { - this._selectionDirection = 'right'; - this.selectionEnd = this.selectionEnd + offset; - this.selectionStart = this.selectionEnd; - return offset !== 0; - }, - - /** - * private - */ - swapSelectionPoints: function() { - var swapSel = this.selectionEnd; - this.selectionEnd = this.selectionStart; - this.selectionStart = swapSel; - }, - - /** - * Moves cursor down while keeping selection - * @param {Number} offset - */ - moveCursorDownWithShift: function(offset) { - if (this.selectionEnd === this.selectionStart) { - this._selectionDirection = 'right'; - } - if (this._selectionDirection === 'right') { - this.selectionEnd += offset; - } - else { - this.selectionStart += offset; - } - if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'left') { - this.swapSelectionPoints(); - this._selectionDirection = 'right'; - } - if (this.selectionEnd > this.text.length) { - this.selectionEnd = this.text.length; - } - return offset !== 0; - }, - - /** - * @param {Event} e Event object - * @param {Boolean} isRight - * @return {Number} - */ - getUpCursorOffset: function(e, isRight) { - var selectionProp = isRight ? this.selectionEnd : this.selectionStart, - cursorLocation = this.get2DCursorLocation(selectionProp), - lineIndex = cursorLocation.lineIndex; - // if on first line, up cursor goes to start of line - if (lineIndex === 0 || e.metaKey || e.keyCode === 33) { - return selectionProp; - } - - var textOnSameLineBeforeCursor = this._textLines[lineIndex].slice(0, cursorLocation.charIndex), - textOnPreviousLine = this._textLines[lineIndex - 1] || '', - _char, - widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex), - lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor), - widthOfCharsOnSameLineBeforeCursor = lineLeftOffset; - - for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { - _char = textOnSameLineBeforeCursor[i]; - widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); - } - - var indexOnPrevLine = this._getIndexOnPrevLine( - cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor); - - return textOnPreviousLine.length - indexOnPrevLine + textOnSameLineBeforeCursor.length; - }, - - /** - * @private - */ - _getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor) { - - var lineIndex = cursorLocation.lineIndex - 1, - widthOfPreviousLine = this._getLineWidth(this.ctx, lineIndex), - lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine), - widthOfCharsOnPreviousLine = lineLeftOffset, - indexOnPrevLine = 0, - foundMatch; - - for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) { - - var _char = textOnPreviousLine[j], - widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); - - widthOfCharsOnPreviousLine += widthOfChar; - - if (widthOfCharsOnPreviousLine > widthOfCharsOnSameLineBeforeCursor) { - - foundMatch = true; - - var leftEdge = widthOfCharsOnPreviousLine - widthOfChar, - rightEdge = widthOfCharsOnPreviousLine, - offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), - offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); - - indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); - - break; - } - } - - // reached end - if (!foundMatch) { - indexOnPrevLine = textOnPreviousLine.length - 1; - } - - return indexOnPrevLine; - }, - /** * Moves cursor up * @param {Event} e Event object @@ -479,16 +406,18 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot * @param {Event} e Event object */ _moveCursorUpOrDown: function(direction, e) { + // getUpCursorOffset + // getDownCursorOffset var action = 'get' + direction + 'CursorOffset', - moveAction = 'moveCursor' + direction, offset = this[action](e, this._selectionDirection === 'right'); if (e.shiftKey) { - moveAction += 'WithShift'; + this.moveCursorWithShift(offset); } else { - moveAction += 'WithoutShift'; + this.moveCursorWithoutShift(offset); } - if (this[moveAction](offset)) { + if (offset !== 0) { + this.setSelectionInBoundaries(); this.abortCursorAnimation(); this._currentCursorOpacity = 1; this.initDelayedCursor(); @@ -498,23 +427,14 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot }, /** - * Moves cursor up with shift + * Moves cursor with shift * @param {Number} offset */ - moveCursorUpWithShift: function(offset) { - if (this.selectionEnd === this.selectionStart) { - this._selectionDirection = 'left'; - } - if (this._selectionDirection === 'right') { - this.selectionEnd -= offset; - } - else { - this.selectionStart -= offset; - } - if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'right') { - this.swapSelectionPoints(); - this._selectionDirection = 'left'; - } + moveCursorWithShift: function(offset) { + var newSelection = this._selectionDirection === 'left' + ? this.selectionStart + offset + : this.selectionEnd + offset; + this.setSelectionStartEndWithShift(this.selectionStart, this.selectionEnd, newSelection); return offset !== 0; }, @@ -522,10 +442,15 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot * Moves cursor up without shift * @param {Number} offset */ - moveCursorUpWithoutShift: function(offset) { - this._selectionDirection = 'left'; - this.selectionStart -= offset; - this.selectionEnd = this.selectionStart; + moveCursorWithoutShift: function(offset) { + if (offset < 0) { + this.selectionStart += offset; + this.selectionEnd = this.selectionStart; + } + else { + this.selectionEnd += offset; + this.selectionStart = this.selectionEnd; + } return offset !== 0; }, diff --git a/test/unit/itext_key_behaviour.js b/test/unit/itext_key_behaviour.js index bd1cf144..ae106802 100644 --- a/test/unit/itext_key_behaviour.js +++ b/test/unit/itext_key_behaviour.js @@ -103,7 +103,7 @@ iText.selectionStart = 28; iText.selectionEnd = 31; - iText.moveCursorUp({ shiftKey: false}); + iText.moveCursorUp({ shiftKey: false }); equal(selection, 1, 'should fire'); equal(iText.selectionStart, 9, 'should move to upper line'); equal(iText.selectionEnd, 9, 'should move to upper line'); @@ -111,7 +111,7 @@ iText.selectionStart = 1; iText.selectionEnd = 4; - iText.moveCursorDown({ shiftKey: false}); + iText.moveCursorDown({ shiftKey: false }); equal(selection, 1, 'should fire'); equal(iText.selectionStart, 24, 'should move to down line'); equal(iText.selectionEnd, 24, 'should move to down line'); @@ -119,7 +119,7 @@ iText.selectionStart = 28; iText.selectionEnd = 31; - iText.moveCursorLeft({ shiftKey: false}); + iText.moveCursorLeft({ shiftKey: false }); equal(selection, 1, 'should fire'); equal(iText.selectionStart, 28, 'should move to selection Start'); equal(iText.selectionEnd, 28, 'should move to selection Start');