cleanup mouse handling
This commit is contained in:
parent
6411152d00
commit
7a50a070e9
4 changed files with 265 additions and 176 deletions
|
|
@ -39,13 +39,24 @@
|
|||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
function GutterHandler(editor) {
|
||||
editor.setDefaultHandler("gutterclick", function(e) {
|
||||
function GutterHandler(mouseHandler) {
|
||||
var editor = mouseHandler.editor;
|
||||
|
||||
mouseHandler.editor.setDefaultHandler("guttermousedown", function(e) {
|
||||
if (e.domEvent.target.className.indexOf("ace_gutter-cell") == -1)
|
||||
return;
|
||||
|
||||
if (!editor.isFocused())
|
||||
return;
|
||||
|
||||
var row = e.getDocumentPosition().row;
|
||||
var selection = editor.session.selection;
|
||||
|
||||
|
||||
selection.moveCursorTo(row, 0);
|
||||
selection.selectLine();
|
||||
|
||||
mouseHandler.$clickSelection = selection.getRange();
|
||||
mouseHandler.captureMouse(e, "selectByLines");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
* Contributor(s):
|
||||
* Fabian Jakobs <fabian AT ajax DOT org>
|
||||
* Mike de Boer <mike AT ajax DOT org>
|
||||
* Harutyun Amirjanyan <harutyun AT c9 DOT io>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
|
|
@ -44,38 +45,46 @@ var event = require("../lib/event");
|
|||
var dom = require("../lib/dom");
|
||||
var BrowserFocus = require("../lib/browser_focus").BrowserFocus;
|
||||
|
||||
var STATE_UNKNOWN = 0;
|
||||
var STATE_SELECT = 1;
|
||||
var STATE_DRAG = 2;
|
||||
|
||||
var DRAG_OFFSET = 5; // pixels
|
||||
|
||||
function DefaultHandlers(editor) {
|
||||
this.editor = editor;
|
||||
this.$clickSelection = null;
|
||||
this.browserFocus = new BrowserFocus();
|
||||
|
||||
editor.setDefaultHandler("mousedown", this.onMouseDown.bind(this));
|
||||
editor.setDefaultHandler("dblclick", this.onDoubleClick.bind(this));
|
||||
editor.setDefaultHandler("tripleclick", this.onTripleClick.bind(this));
|
||||
editor.setDefaultHandler("quadclick", this.onQuadClick.bind(this));
|
||||
editor.setDefaultHandler("mousewheel", this.onScroll.bind(this));
|
||||
|
||||
function DefaultHandlers(mouseHandler) {
|
||||
mouseHandler.$clickSelection = null;
|
||||
mouseHandler.browserFocus = new BrowserFocus();
|
||||
|
||||
var editor = mouseHandler.editor;
|
||||
editor.setDefaultHandler("mousedown", this.onMouseDown.bind(mouseHandler));
|
||||
editor.setDefaultHandler("dblclick", this.onDoubleClick.bind(mouseHandler));
|
||||
editor.setDefaultHandler("tripleclick", this.onTripleClick.bind(mouseHandler));
|
||||
editor.setDefaultHandler("quadclick", this.onQuadClick.bind(mouseHandler));
|
||||
editor.setDefaultHandler("mousewheel", this.onScroll.bind(mouseHandler));
|
||||
|
||||
var exports = ["select", "startSelect", "drag", "dragEnd", "dragWait",
|
||||
"dragWaitEnd", "startDrag"];
|
||||
|
||||
exports.forEach(function(x) {
|
||||
mouseHandler[x] = this[x];
|
||||
}, this);
|
||||
|
||||
mouseHandler.selectByLines = this.extendSelectionBy.bind(mouseHandler, "getLineRange");
|
||||
mouseHandler.selectByWords = this.extendSelectionBy.bind(mouseHandler, "getWordRange");
|
||||
}
|
||||
|
||||
(function() {
|
||||
|
||||
|
||||
this.onMouseDown = function(ev) {
|
||||
this.mousedownEvent = ev;
|
||||
var inSelection = ev.inSelection();
|
||||
var pageX = ev.pageX;
|
||||
var pageY = ev.pageY;
|
||||
var pos = ev.getDocumentPosition();
|
||||
var editor = this.editor;
|
||||
var _self = this;
|
||||
|
||||
|
||||
this.ev = ev
|
||||
var selectionRange = editor.getSelectionRange();
|
||||
var selectionEmpty = selectionRange.isEmpty();
|
||||
var state = STATE_UNKNOWN;
|
||||
|
||||
|
||||
var button = ev.getButton();
|
||||
if (button !== 0) {
|
||||
if (selectionEmpty) {
|
||||
|
|
@ -91,187 +100,224 @@ function DefaultHandlers(editor) {
|
|||
|
||||
// if this click caused the editor to be focused should not clear the
|
||||
// selection
|
||||
if (
|
||||
inSelection && (
|
||||
!this.browserFocus.isFocused()
|
||||
|| new Date().getTime() - this.browserFocus.lastFocus < 20
|
||||
|| !editor.isFocused()
|
||||
)
|
||||
) {
|
||||
if (inSelection && !editor.isFocused()) {
|
||||
editor.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!inSelection) {
|
||||
if (!inSelection || this.$clickSelection || ev.getShiftKey()) {
|
||||
// Directly pick STATE_SELECT, since the user is not clicking inside
|
||||
// a selection.
|
||||
onStartSelect(pos);
|
||||
}
|
||||
|
||||
var mousePageX = pageX, mousePageY = pageY;
|
||||
var mousedownTime = (new Date()).getTime();
|
||||
var dragCursor, dragRange, dragSelectionMarker;
|
||||
|
||||
var onMouseSelection = function(e) {
|
||||
mousePageX = event.getDocumentX(e);
|
||||
mousePageY = event.getDocumentY(e);
|
||||
};
|
||||
|
||||
var onMouseSelectionEnd = function(e) {
|
||||
clearInterval(timerId);
|
||||
if (state == STATE_UNKNOWN)
|
||||
onStartSelect(pos);
|
||||
else if (state == STATE_DRAG)
|
||||
onMouseDragSelectionEnd(e);
|
||||
|
||||
_self.$clickSelection = null;
|
||||
state = STATE_UNKNOWN;
|
||||
};
|
||||
|
||||
var onMouseDragSelectionEnd = function(e) {
|
||||
dom.removeCssClass(editor.container, "ace_dragging");
|
||||
editor.session.removeMarker(dragSelectionMarker);
|
||||
|
||||
if (!editor.$mouseHandler.$clickSelection) {
|
||||
if (!dragCursor) {
|
||||
editor.moveCursorToPosition(pos);
|
||||
editor.selection.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
if (!dragCursor)
|
||||
return;
|
||||
|
||||
if (dragRange.contains(dragCursor.row, dragCursor.column)) {
|
||||
dragCursor = null;
|
||||
return;
|
||||
}
|
||||
|
||||
editor.clearSelection();
|
||||
if (e && (e.ctrlKey || e.altKey)) {
|
||||
var session = editor.session;
|
||||
var newRange = session.insert(dragCursor, session.getTextRange(dragRange));
|
||||
this.startSelect(pos);
|
||||
} else if (inSelection) {
|
||||
var e = ev.domEvent;
|
||||
if ((e.ctrlKey || e.altKey)) {
|
||||
this.startDrag();
|
||||
} else {
|
||||
var newRange = editor.moveText(dragRange, dragCursor);
|
||||
this.mousedownEvent.time = (new Date()).getTime();
|
||||
this.setState("dragWait");
|
||||
}
|
||||
if (!newRange) {
|
||||
dragCursor = null;
|
||||
return;
|
||||
}
|
||||
|
||||
editor.selection.setSelectionRange(newRange);
|
||||
};
|
||||
|
||||
var onSelectionInterval = function() {
|
||||
if (state == STATE_UNKNOWN) {
|
||||
var distance = calcDistance(pageX, pageY, mousePageX, mousePageY);
|
||||
var time = (new Date()).getTime();
|
||||
|
||||
if (distance > DRAG_OFFSET) {
|
||||
state = STATE_SELECT;
|
||||
var cursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY);
|
||||
onStartSelect(cursor);
|
||||
}
|
||||
else if ((time - mousedownTime) > editor.getDragDelay()) {
|
||||
state = STATE_DRAG;
|
||||
dragRange = editor.getSelectionRange();
|
||||
var style = editor.getSelectionStyle();
|
||||
dragSelectionMarker = editor.session.addMarker(dragRange, "ace_selection", style);
|
||||
editor.clearSelection();
|
||||
dom.addCssClass(editor.container, "ace_dragging");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (state == STATE_DRAG)
|
||||
onDragSelectionInterval();
|
||||
else if (state == STATE_SELECT)
|
||||
onUpdateSelectionInterval();
|
||||
};
|
||||
|
||||
function onStartSelect(pos) {
|
||||
if (ev.getShiftKey()) {
|
||||
editor.selection.selectToPosition(pos);
|
||||
}
|
||||
else {
|
||||
if (!_self.$clickSelection) {
|
||||
editor.moveCursorToPosition(pos);
|
||||
editor.selection.clearSelection();
|
||||
}
|
||||
}
|
||||
state = STATE_SELECT;
|
||||
}
|
||||
|
||||
var onUpdateSelectionInterval = function() {
|
||||
var anchor;
|
||||
var cursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY);
|
||||
|
||||
if (_self.$clickSelection) {
|
||||
if (_self.$clickSelection.contains(cursor.row, cursor.column)) {
|
||||
editor.selection.setSelectionRange(_self.$clickSelection);
|
||||
}
|
||||
else {
|
||||
if (_self.$clickSelection.compare(cursor.row, cursor.column) == -1) {
|
||||
anchor = _self.$clickSelection.end;
|
||||
}
|
||||
else {
|
||||
anchor = _self.$clickSelection.start;
|
||||
}
|
||||
editor.selection.setSelectionAnchor(anchor.row, anchor.column);
|
||||
editor.selection.selectToPosition(cursor);
|
||||
}
|
||||
}
|
||||
else {
|
||||
editor.selection.selectToPosition(cursor);
|
||||
}
|
||||
|
||||
editor.renderer.scrollCursorIntoView();
|
||||
};
|
||||
|
||||
var onDragSelectionInterval = function() {
|
||||
dragCursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY);
|
||||
editor.moveCursorToPosition(dragCursor);
|
||||
};
|
||||
|
||||
event.capture(editor.container, onMouseSelection, onMouseSelectionEnd);
|
||||
var timerId = setInterval(onSelectionInterval, 20);
|
||||
|
||||
return ev.preventDefault();
|
||||
this.captureMouse(ev)
|
||||
};
|
||||
|
||||
|
||||
this.startSelect = function(pos) {
|
||||
pos = pos || this.editor.renderer.screenToTextCoordinates(this.x, this.y);
|
||||
if (this.mousedownEvent.getShiftKey()) {
|
||||
this.editor.selection.selectToPosition(pos);
|
||||
}
|
||||
else if (!this.$clickSelection) {
|
||||
this.editor.moveCursorToPosition(pos);
|
||||
this.editor.selection.clearSelection();
|
||||
}
|
||||
this.setState("select");
|
||||
}
|
||||
|
||||
this.select = function() {
|
||||
var anchor, editor = this.editor;
|
||||
var cursor = editor.renderer.screenToTextCoordinates(this.x, this.y);
|
||||
|
||||
if (this.$clickSelection) {
|
||||
var cmp = this.$clickSelection.comparePoint(cursor);
|
||||
|
||||
if (cmp == -1) {
|
||||
anchor = this.$clickSelection.end;
|
||||
} else if (cmp == 1) {
|
||||
anchor = this.$clickSelection.start;
|
||||
} else {
|
||||
cursor = this.$clickSelection.end;
|
||||
anchor = this.$clickSelection.start;
|
||||
}
|
||||
editor.selection.setSelectionAnchor(anchor.row, anchor.column);
|
||||
}
|
||||
editor.selection.selectToPosition(cursor);
|
||||
|
||||
editor.renderer.scrollCursorIntoView();
|
||||
};
|
||||
|
||||
this.extendSelectionBy = function(unitName) {
|
||||
var anchor, editor = this.editor;
|
||||
var cursor = editor.renderer.screenToTextCoordinates(this.x, this.y);
|
||||
var range = editor.selection[unitName](cursor.row, cursor.column);
|
||||
|
||||
if (this.$clickSelection) {
|
||||
var cmpStart = this.$clickSelection.comparePoint(range.start);
|
||||
var cmpEnd = this.$clickSelection.comparePoint(range.end);
|
||||
|
||||
if (cmpStart == -1 && cmpEnd <= 0) {
|
||||
anchor = this.$clickSelection.end;
|
||||
cursor = range.start;
|
||||
} else if (cmpEnd == 1 && cmpStart >= 0) {
|
||||
anchor = this.$clickSelection.start;
|
||||
cursor = range.end;
|
||||
} else if (cmpStart == -1 && cmpEnd == 1) {
|
||||
cursor = range.end;
|
||||
anchor = range.start;
|
||||
} else {
|
||||
cursor = this.$clickSelection.end;
|
||||
anchor = this.$clickSelection.start;
|
||||
}
|
||||
editor.selection.setSelectionAnchor(anchor.row, anchor.column);
|
||||
}
|
||||
editor.selection.selectToPosition(cursor);
|
||||
|
||||
editor.renderer.scrollCursorIntoView();
|
||||
};
|
||||
|
||||
this.startDrag = function() {
|
||||
var editor = this.editor;
|
||||
this.setState("drag");
|
||||
this.dragRange = editor.getSelectionRange();
|
||||
var style = editor.getSelectionStyle();
|
||||
this.dragSelectionMarker = editor.session.addMarker(this.dragRange, "ace_selection", style);
|
||||
editor.clearSelection();
|
||||
dom.addCssClass(editor.container, "ace_dragging");
|
||||
if (!this.$dragKeybinding) {
|
||||
this.$dragKeybinding = {
|
||||
handleKeyboard: function(data, hashId, keyString, keyCode) {
|
||||
if (keyString == "esc")
|
||||
return {command: this.command};
|
||||
},
|
||||
command: {
|
||||
exec: function(editor) {
|
||||
var self = editor.$mouseHandler;
|
||||
self.dragCursor = null
|
||||
self.dragEnd();
|
||||
self.startSelect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
editor.keyBinding.addKeyboardHandler(this.$dragKeybinding);
|
||||
};
|
||||
|
||||
this.dragWait = function() {
|
||||
var distance = calcDistance(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y);
|
||||
var time = (new Date()).getTime();
|
||||
var editor = this.editor;
|
||||
|
||||
if (distance > DRAG_OFFSET) {
|
||||
this.startSelect();
|
||||
} else if ((time - this.mousedownEvent.time) > editor.getDragDelay()) {
|
||||
this.startDrag()
|
||||
}
|
||||
};
|
||||
|
||||
this.dragWaitEnd = function(e) {
|
||||
this.mousedownEvent.domEvent = e;
|
||||
this.startSelect();
|
||||
};
|
||||
|
||||
this.drag = function() {
|
||||
var editor = this.editor;
|
||||
this.dragCursor = editor.renderer.screenToTextCoordinates(this.x, this.y);
|
||||
editor.moveCursorToPosition(this.dragCursor);
|
||||
editor.renderer.scrollCursorIntoView();
|
||||
};
|
||||
|
||||
this.dragEnd = function(e) {
|
||||
var editor = this.editor;
|
||||
var dragCursor = this.dragCursor;
|
||||
var dragRange = this.dragRange;
|
||||
dom.removeCssClass(editor.container, "ace_dragging");
|
||||
editor.session.removeMarker(this.dragSelectionMarker);
|
||||
editor.keyBinding.removeKeyboardHandler(this.$dragKeybinding);
|
||||
|
||||
if (!dragCursor)
|
||||
return;
|
||||
|
||||
editor.clearSelection();
|
||||
if (e && (e.ctrlKey || e.altKey)) {
|
||||
var session = editor.session;
|
||||
var newRange = dragRange;
|
||||
newRange.end = session.insert(dragCursor, session.getTextRange(dragRange));
|
||||
newRange.start = dragCursor;
|
||||
} else if (dragRange.contains(dragCursor.row, dragCursor.column)) {
|
||||
return;
|
||||
} else {
|
||||
var newRange = editor.moveText(dragRange, dragCursor);
|
||||
}
|
||||
|
||||
if (!newRange)
|
||||
return;
|
||||
|
||||
editor.selection.setSelectionRange(newRange);
|
||||
};
|
||||
|
||||
this.onDoubleClick = function(ev) {
|
||||
var pos = ev.getDocumentPosition();
|
||||
var editor = this.editor;
|
||||
|
||||
|
||||
this.setState("selectByWords");
|
||||
|
||||
editor.moveCursorToPosition(pos);
|
||||
editor.selection.selectWord();
|
||||
this.$clickSelection = editor.getSelectionRange();
|
||||
};
|
||||
|
||||
|
||||
this.onTripleClick = function(ev) {
|
||||
var pos = ev.getDocumentPosition();
|
||||
var editor = this.editor;
|
||||
|
||||
|
||||
this.setState("selectByLines");
|
||||
|
||||
editor.moveCursorToPosition(pos);
|
||||
editor.selection.selectLine();
|
||||
this.$clickSelection = editor.getSelectionRange();
|
||||
};
|
||||
|
||||
|
||||
this.onQuadClick = function(ev) {
|
||||
var editor = this.editor;
|
||||
|
||||
|
||||
editor.selectAll();
|
||||
this.$clickSelection = editor.getSelectionRange();
|
||||
this.setState("select");
|
||||
};
|
||||
|
||||
|
||||
this.onScroll = function(ev) {
|
||||
var editor = this.editor;
|
||||
|
||||
var isScrolable = editor.renderer.isScrollableBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed);
|
||||
if (isScrolable) {
|
||||
this.$passScrollEvent = false;
|
||||
} else {
|
||||
if (this.$passScrollEvent)
|
||||
return;
|
||||
|
||||
if (!this.$scrollStopTimeout) {
|
||||
var self = this;
|
||||
this.$scrollStopTimeout = setTimeout(function() {
|
||||
self.$passScrollEvent = true;
|
||||
self.$scrollStopTimeout = null;
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
editor.renderer.scrollBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed);
|
||||
if (editor.renderer.isScrollableBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed))
|
||||
return ev.preventDefault();
|
||||
return ev.preventDefault();
|
||||
};
|
||||
|
||||
|
||||
}).call(DefaultHandlers.prototype);
|
||||
|
||||
exports.DefaultHandlers = DefaultHandlers;
|
||||
|
|
|
|||
|
|
@ -47,10 +47,10 @@ var MouseEvent = require("./mouse_event").MouseEvent;
|
|||
|
||||
var MouseHandler = function(editor) {
|
||||
this.editor = editor;
|
||||
|
||||
new DefaultHandlers(editor);
|
||||
new DefaultGutterHandler(editor);
|
||||
|
||||
|
||||
new DefaultHandlers(this);
|
||||
new DefaultGutterHandler(this);
|
||||
|
||||
event.addListener(editor.container, "mousedown", function(e) {
|
||||
editor.focus();
|
||||
return event.preventDefault(e);
|
||||
|
|
@ -67,7 +67,7 @@ var MouseHandler = function(editor) {
|
|||
event.addMultiMouseDownListener(mouseTarget, 0, 3, 600, this.onMouseEvent.bind(this, "tripleclick"));
|
||||
event.addMultiMouseDownListener(mouseTarget, 0, 4, 600, this.onMouseEvent.bind(this, "quadclick"));
|
||||
event.addMouseWheelListener(editor.container, this.onMouseWheel.bind(this, "mousewheel"));
|
||||
|
||||
|
||||
var gutterEl = editor.renderer.$gutter;
|
||||
event.addListener(gutterEl, "mousedown", this.onMouseEvent.bind(this, "guttermousedown"));
|
||||
event.addListener(gutterEl, "click", this.onMouseEvent.bind(this, "gutterclick"));
|
||||
|
|
@ -89,7 +89,7 @@ var MouseHandler = function(editor) {
|
|||
this.onMouseEvent = function(name, e) {
|
||||
this.editor._emit(name, new MouseEvent(e, this.editor));
|
||||
};
|
||||
|
||||
|
||||
this.$dragDelay = 250;
|
||||
this.setDragDelay = function(dragDelay) {
|
||||
this.$dragDelay = dragDelay;
|
||||
|
|
@ -113,10 +113,42 @@ var MouseHandler = function(editor) {
|
|||
mouseEvent.speed = this.$scrollSpeed * 2;
|
||||
mouseEvent.wheelX = e.wheelX;
|
||||
mouseEvent.wheelY = e.wheelY;
|
||||
|
||||
|
||||
this.editor._emit(name, mouseEvent);
|
||||
};
|
||||
|
||||
this.setState = function(state) {
|
||||
this.state = state;
|
||||
};
|
||||
|
||||
this.captureMouse = function(ev, state) {
|
||||
if (state)
|
||||
this.setState(state);
|
||||
|
||||
this.x = ev.x;
|
||||
this.y = ev.y;
|
||||
|
||||
var self = this;
|
||||
var onMouseSelection = function(e) {
|
||||
self.x = e.clientX;
|
||||
self.y = e.clientY;
|
||||
};
|
||||
|
||||
var onMouseSelectionEnd = function(e) {
|
||||
clearInterval(timerId);
|
||||
self[self.state + "End"] && self[self.state + "End"](e);
|
||||
self.$clickSelection = null;
|
||||
};
|
||||
|
||||
var onSelectionInterval = function() {
|
||||
self[self.state] && self[self.state]();
|
||||
}
|
||||
|
||||
event.capture(this.editor.container, onMouseSelection, onMouseSelectionEnd);
|
||||
var timerId = setInterval(onSelectionInterval, 20);
|
||||
|
||||
ev.preventDefault();
|
||||
};
|
||||
}).call(MouseHandler.prototype);
|
||||
|
||||
exports.MouseHandler = MouseHandler;
|
||||
|
|
|
|||
|
|
@ -1110,7 +1110,7 @@ var VirtualRenderer = function(container, theme) {
|
|||
var row = Math.floor((y + this.scrollTop - canvasPos.top) / this.lineHeight);
|
||||
var col = Math.round(offset);
|
||||
|
||||
return {row: row, column: col, side: offset - col};
|
||||
return {row: row, column: col, side: offset - col > 0 ? 1 : -1};
|
||||
};
|
||||
|
||||
this.screenToTextCoordinates = function(x, y) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue