[Editor] Improve drawing on a touch screen.
- it's now possible to start a drawing with a pen and use fingers to zoom or scroll without interacting with the current drawing; - it's now possible to draw with a finger and them zoom with two fingers.
This commit is contained in:
parent
4d228818a9
commit
89f61b4262
@ -422,9 +422,9 @@ class AnnotationEditorLayer {
|
|||||||
this.div.addEventListener("pointerdown", this.pointerdown.bind(this), {
|
this.div.addEventListener("pointerdown", this.pointerdown.bind(this), {
|
||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
this.div.addEventListener("pointerup", this.pointerup.bind(this), {
|
const pointerup = this.pointerup.bind(this);
|
||||||
signal,
|
this.div.addEventListener("pointerup", pointerup, { signal });
|
||||||
});
|
this.div.addEventListener("pointercancel", pointerup, { signal });
|
||||||
}
|
}
|
||||||
|
|
||||||
disableClick() {
|
disableClick() {
|
||||||
|
|||||||
@ -69,12 +69,22 @@ class DrawingEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
static _currentDrawId = -1;
|
static _currentDrawId = -1;
|
||||||
|
|
||||||
static _currentDraw = null;
|
|
||||||
|
|
||||||
static _currentDrawingOptions = null;
|
|
||||||
|
|
||||||
static _currentParent = null;
|
static _currentParent = null;
|
||||||
|
|
||||||
|
static #currentDraw = null;
|
||||||
|
|
||||||
|
static #currentDrawingAC = null;
|
||||||
|
|
||||||
|
static #currentDrawingOptions = null;
|
||||||
|
|
||||||
|
static #currentPointerId = NaN;
|
||||||
|
|
||||||
|
static #currentPointerType = null;
|
||||||
|
|
||||||
|
static #currentPointerIds = null;
|
||||||
|
|
||||||
|
static #currentMoveTimestamp = NaN;
|
||||||
|
|
||||||
static _INNER_MARGIN = 3;
|
static _INNER_MARGIN = 3;
|
||||||
|
|
||||||
constructor(params) {
|
constructor(params) {
|
||||||
@ -168,7 +178,7 @@ class DrawingEditor extends AnnotationEditor {
|
|||||||
this._defaultDrawingOptions.updateProperty(propertyName, value);
|
this._defaultDrawingOptions.updateProperty(propertyName, value);
|
||||||
}
|
}
|
||||||
if (this._currentParent) {
|
if (this._currentParent) {
|
||||||
this._currentDraw.updateProperty(propertyName, value);
|
DrawingEditor.#currentDraw.updateProperty(propertyName, value);
|
||||||
this._currentParent.drawLayer.updateProperties(
|
this._currentParent.drawLayer.updateProperties(
|
||||||
this._currentDrawId,
|
this._currentDrawId,
|
||||||
this._defaultDrawingOptions.toSVGProperties()
|
this._defaultDrawingOptions.toSVGProperties()
|
||||||
@ -639,32 +649,81 @@ class DrawingEditor extends AnnotationEditor {
|
|||||||
unreachable("Not implemented");
|
unreachable("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
static startDrawing(
|
static startDrawing(parent, uiManager, _isLTR, event) {
|
||||||
parent,
|
// The _currentPointerType is set when the user starts an empty drawing
|
||||||
uiManager,
|
// session. If, in the same drawing session, the user starts using a
|
||||||
_isLTR,
|
// different type of pointer (e.g. a pen and then a finger), we just return.
|
||||||
{ target, offsetX: x, offsetY: y }
|
//
|
||||||
) {
|
// The _currentPointerId and _currentPointerIds are used to keep track of
|
||||||
|
// the pointers with a same type (e.g. two fingers). If the user starts to
|
||||||
|
// draw with a finger and then uses a second finger, we just stop the
|
||||||
|
// current drawing and let the user zoom the document.
|
||||||
|
|
||||||
|
const { target, offsetX: x, offsetY: y, pointerId, pointerType } = event;
|
||||||
|
if (
|
||||||
|
DrawingEditor.#currentPointerType &&
|
||||||
|
DrawingEditor.#currentPointerType !== pointerType
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
viewport: { rotation },
|
viewport: { rotation },
|
||||||
} = parent;
|
} = parent;
|
||||||
const { width: parentWidth, height: parentHeight } =
|
const { width: parentWidth, height: parentHeight } =
|
||||||
target.getBoundingClientRect();
|
target.getBoundingClientRect();
|
||||||
const ac = new AbortController();
|
|
||||||
|
const ac = (DrawingEditor.#currentDrawingAC = new AbortController());
|
||||||
const signal = parent.combinedSignal(ac);
|
const signal = parent.combinedSignal(ac);
|
||||||
|
|
||||||
|
DrawingEditor.#currentPointerId ||= pointerId;
|
||||||
|
DrawingEditor.#currentPointerType ??= pointerType;
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"pointerup",
|
"pointerup",
|
||||||
e => {
|
e => {
|
||||||
ac.abort();
|
if (DrawingEditor.#currentPointerId === e.pointerId) {
|
||||||
parent.toggleDrawing(true);
|
this._endDraw(e);
|
||||||
this._endDraw(e);
|
} else {
|
||||||
|
DrawingEditor.#currentPointerIds?.delete(e.pointerId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ signal }
|
||||||
|
);
|
||||||
|
window.addEventListener(
|
||||||
|
"pointercancel",
|
||||||
|
e => {
|
||||||
|
if (DrawingEditor.#currentPointerId === e.pointerId) {
|
||||||
|
this._currentParent.endDrawingSession();
|
||||||
|
} else {
|
||||||
|
DrawingEditor.#currentPointerIds?.delete(e.pointerId);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ signal }
|
{ signal }
|
||||||
);
|
);
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"pointerdown",
|
"pointerdown",
|
||||||
stopEvent /* Avoid to have undesired clicks during drawing. */,
|
e => {
|
||||||
|
if (DrawingEditor.#currentPointerType !== e.pointerType) {
|
||||||
|
// For example, we started with a pen and the user
|
||||||
|
// is now using a finger.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For example, the user is using a second finger.
|
||||||
|
(DrawingEditor.#currentPointerIds ||= new Set()).add(e.pointerId);
|
||||||
|
|
||||||
|
// The first finger created a first point and a second finger just
|
||||||
|
// started, so we stop the drawing and remove this only point.
|
||||||
|
if (DrawingEditor.#currentDraw.isCancellable()) {
|
||||||
|
DrawingEditor.#currentDraw.removeLastElement();
|
||||||
|
if (DrawingEditor.#currentDraw.isEmpty()) {
|
||||||
|
this._currentParent.endDrawingSession(/* isAborted = */ true);
|
||||||
|
} else {
|
||||||
|
this._endDraw(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
capture: true,
|
capture: true,
|
||||||
passive: false,
|
passive: false,
|
||||||
@ -675,54 +734,114 @@ class DrawingEditor extends AnnotationEditor {
|
|||||||
target.addEventListener("pointermove", this._drawMove.bind(this), {
|
target.addEventListener("pointermove", this._drawMove.bind(this), {
|
||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
|
target.addEventListener(
|
||||||
|
"touchmove",
|
||||||
|
e => {
|
||||||
|
if (e.timeStamp === DrawingEditor.#currentMoveTimestamp) {
|
||||||
|
// This move event is used to draw so we don't want to scroll.
|
||||||
|
stopEvent(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ signal }
|
||||||
|
);
|
||||||
parent.toggleDrawing();
|
parent.toggleDrawing();
|
||||||
uiManager._editorUndoBar?.hide();
|
uiManager._editorUndoBar?.hide();
|
||||||
|
|
||||||
if (this._currentDraw) {
|
if (DrawingEditor.#currentDraw) {
|
||||||
parent.drawLayer.updateProperties(
|
parent.drawLayer.updateProperties(
|
||||||
this._currentDrawId,
|
this._currentDrawId,
|
||||||
this._currentDraw.startNew(x, y, parentWidth, parentHeight, rotation)
|
DrawingEditor.#currentDraw.startNew(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
parentWidth,
|
||||||
|
parentHeight,
|
||||||
|
rotation
|
||||||
|
)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uiManager.updateUIForDefaultProperties(this);
|
uiManager.updateUIForDefaultProperties(this);
|
||||||
|
|
||||||
this._currentDraw = this.createDrawerInstance(
|
DrawingEditor.#currentDraw = this.createDrawerInstance(
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
parentWidth,
|
parentWidth,
|
||||||
parentHeight,
|
parentHeight,
|
||||||
rotation
|
rotation
|
||||||
);
|
);
|
||||||
this._currentDrawingOptions = this.getDefaultDrawingOptions();
|
DrawingEditor.#currentDrawingOptions = this.getDefaultDrawingOptions();
|
||||||
this._currentParent = parent;
|
this._currentParent = parent;
|
||||||
|
|
||||||
({ id: this._currentDrawId } = parent.drawLayer.draw(
|
({ id: this._currentDrawId } = parent.drawLayer.draw(
|
||||||
this._mergeSVGProperties(
|
this._mergeSVGProperties(
|
||||||
this._currentDrawingOptions.toSVGProperties(),
|
DrawingEditor.#currentDrawingOptions.toSVGProperties(),
|
||||||
this._currentDraw.defaultSVGProperties
|
DrawingEditor.#currentDraw.defaultSVGProperties
|
||||||
),
|
),
|
||||||
/* isPathUpdatable = */ true,
|
/* isPathUpdatable = */ true,
|
||||||
/* hasClip = */ false
|
/* hasClip = */ false
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
static _drawMove({ offsetX, offsetY }) {
|
static _drawMove(event) {
|
||||||
|
DrawingEditor.#currentMoveTimestamp = -1;
|
||||||
|
if (!DrawingEditor.#currentDraw) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { offsetX, offsetY, pointerId } = event;
|
||||||
|
|
||||||
|
if (DrawingEditor.#currentPointerId !== pointerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (DrawingEditor.#currentPointerIds?.size >= 1) {
|
||||||
|
// The user is using multiple fingers and the first one is moving.
|
||||||
|
this._endDraw(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._currentParent.drawLayer.updateProperties(
|
this._currentParent.drawLayer.updateProperties(
|
||||||
this._currentDrawId,
|
this._currentDrawId,
|
||||||
this._currentDraw.add(offsetX, offsetY)
|
DrawingEditor.#currentDraw.add(offsetX, offsetY)
|
||||||
);
|
);
|
||||||
|
// We track the timestamp to know if the touchmove event is used to draw.
|
||||||
|
DrawingEditor.#currentMoveTimestamp = event.timeStamp;
|
||||||
|
stopEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
static _endDraw({ offsetX, offsetY }) {
|
static _cleanup(all) {
|
||||||
|
if (all) {
|
||||||
|
this._currentDrawId = -1;
|
||||||
|
this._currentParent = null;
|
||||||
|
DrawingEditor.#currentDraw = null;
|
||||||
|
DrawingEditor.#currentDrawingOptions = null;
|
||||||
|
DrawingEditor.#currentPointerType = null;
|
||||||
|
DrawingEditor.#currentMoveTimestamp = NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DrawingEditor.#currentDrawingAC) {
|
||||||
|
DrawingEditor.#currentDrawingAC.abort();
|
||||||
|
DrawingEditor.#currentDrawingAC = null;
|
||||||
|
DrawingEditor.#currentPointerId = NaN;
|
||||||
|
DrawingEditor.#currentPointerIds = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static _endDraw(event) {
|
||||||
const parent = this._currentParent;
|
const parent = this._currentParent;
|
||||||
parent.drawLayer.updateProperties(
|
if (!parent) {
|
||||||
this._currentDrawId,
|
return;
|
||||||
this._currentDraw.end(offsetX, offsetY)
|
}
|
||||||
);
|
|
||||||
|
parent.toggleDrawing(true);
|
||||||
|
this._cleanup(false);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
parent.drawLayer.updateProperties(
|
||||||
|
this._currentDrawId,
|
||||||
|
DrawingEditor.#currentDraw.end(event.offsetX, event.offsetY)
|
||||||
|
);
|
||||||
|
}
|
||||||
if (this.supportMultipleDrawings) {
|
if (this.supportMultipleDrawings) {
|
||||||
const draw = this._currentDraw;
|
const draw = DrawingEditor.#currentDraw;
|
||||||
const drawId = this._currentDrawId;
|
const drawId = this._currentDrawId;
|
||||||
const lastElement = draw.getLastElement();
|
const lastElement = draw.getLastElement();
|
||||||
parent.addCommands({
|
parent.addCommands({
|
||||||
@ -753,7 +872,7 @@ class DrawingEditor extends AnnotationEditor {
|
|||||||
parent.toggleDrawing(true);
|
parent.toggleDrawing(true);
|
||||||
parent.cleanUndoStack(AnnotationEditorParamsType.DRAW_STEP);
|
parent.cleanUndoStack(AnnotationEditorParamsType.DRAW_STEP);
|
||||||
|
|
||||||
if (!this._currentDraw.isEmpty()) {
|
if (!DrawingEditor.#currentDraw.isEmpty()) {
|
||||||
const {
|
const {
|
||||||
pageDimensions: [pageWidth, pageHeight],
|
pageDimensions: [pageWidth, pageHeight],
|
||||||
scale,
|
scale,
|
||||||
@ -764,32 +883,25 @@ class DrawingEditor extends AnnotationEditor {
|
|||||||
false,
|
false,
|
||||||
{
|
{
|
||||||
drawId: this._currentDrawId,
|
drawId: this._currentDrawId,
|
||||||
drawOutlines: this._currentDraw.getOutlines(
|
drawOutlines: DrawingEditor.#currentDraw.getOutlines(
|
||||||
pageWidth * scale,
|
pageWidth * scale,
|
||||||
pageHeight * scale,
|
pageHeight * scale,
|
||||||
scale,
|
scale,
|
||||||
this._INNER_MARGIN
|
this._INNER_MARGIN
|
||||||
),
|
),
|
||||||
drawingOptions: this._currentDrawingOptions,
|
drawingOptions: DrawingEditor.#currentDrawingOptions,
|
||||||
mustBeCommitted: !isAborted,
|
mustBeCommitted: !isAborted,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this._cleanup();
|
this._cleanup(true);
|
||||||
return editor;
|
return editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.drawLayer.remove(this._currentDrawId);
|
parent.drawLayer.remove(this._currentDrawId);
|
||||||
this._cleanup();
|
this._cleanup(true);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static _cleanup() {
|
|
||||||
this._currentDrawId = -1;
|
|
||||||
this._currentDraw = null;
|
|
||||||
this._currentDrawingOptions = null;
|
|
||||||
this._currentParent = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the drawing options.
|
* Create the drawing options.
|
||||||
* @param {Object} _data
|
* @param {Object} _data
|
||||||
|
|||||||
@ -74,6 +74,12 @@ class InkDrawOutliner {
|
|||||||
return !this.#lines || this.#lines.length === 0;
|
return !this.#lines || this.#lines.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isCancellable() {
|
||||||
|
// The user a second finger after drawing 5 points: it's small enough
|
||||||
|
// to not be a real drawing.
|
||||||
|
return this.#points.length <= 10;
|
||||||
|
}
|
||||||
|
|
||||||
add(x, y) {
|
add(x, y) {
|
||||||
// The point is in canvas coordinates which means that there is no rotation.
|
// The point is in canvas coordinates which means that there is no rotation.
|
||||||
// It's the same as parent coordinates.
|
// It's the same as parent coordinates.
|
||||||
|
|||||||
@ -156,7 +156,6 @@
|
|||||||
|
|
||||||
.annotationEditorLayer.inkEditing {
|
.annotationEditorLayer.inkEditing {
|
||||||
cursor: var(--editorInk-editing-cursor);
|
cursor: var(--editorInk-editing-cursor);
|
||||||
touch-action: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.annotationEditorLayer .draw {
|
.annotationEditorLayer .draw {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user