Merge pull request #15050 from calixteman/make_ink_better

[Editor] - Add the ability to directly draw after selecting ink tool
This commit is contained in:
calixteman 2022-06-16 20:34:56 +02:00 committed by GitHub
commit b8688128e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 162 additions and 39 deletions

View File

@ -43,6 +43,8 @@ import { PixelsPerInch } from "../display_utils.js";
class AnnotationEditorLayer {
#boundClick;
#boundMouseover;
#editors = new Map();
#uiManager;
@ -83,6 +85,7 @@ class AnnotationEditorLayer {
this.pageIndex = options.pageIndex;
this.div = options.div;
this.#boundClick = this.click.bind(this);
this.#boundMouseover = this.mouseover.bind(this);
for (const editor of this.#uiManager.getEditors(options.pageIndex)) {
this.add(editor);
@ -91,13 +94,45 @@ class AnnotationEditorLayer {
this.#uiManager.addLayer(this);
}
/**
* The mode has changed: it must be updated.
* @param {number} mode
*/
updateMode(mode) {
if (mode === AnnotationEditorType.INK) {
// We want to have the ink editor covering all of the page without having
// to click to create it: it must be here when we start to draw.
this.div.addEventListener("mouseover", this.#boundMouseover);
this.div.removeEventListener("click", this.#boundClick);
} else {
this.div.removeEventListener("mouseover", this.#boundMouseover);
}
}
/**
* Mouseover callback.
* @param {MouseEvent} event
*/
mouseover(event) {
if (event.target === this.div && event.buttons === 0) {
// The div is the target so there is no ink editor, hence we can
// create a new one.
// event.buttons === 0 is here to avoid adding a new ink editor
// when we drop an editor.
const editor = this.#createAndAddNewEditor(event);
editor.setInBackground();
}
}
/**
* Add some commands into the CommandManager (undo/redo stuff).
* @param {function} cmd
* @param {function} undo
* @param {boolean} mustExec - If true the command is executed after having
* been added.
*/
addCommands(cmd, undo) {
this.#uiManager.addCommands(cmd, undo);
addCommands(cmd, undo, mustExec) {
this.#uiManager.addCommands(cmd, undo, mustExec);
}
/**
@ -178,6 +213,17 @@ class AnnotationEditorLayer {
* @param {AnnotationEditor} editor
*/
setActiveEditor(editor) {
const currentActive = this.#uiManager.getActive();
if (currentActive === editor) {
return;
}
this.#uiManager.setActiveEditor(editor);
if (currentActive && currentActive !== editor) {
currentActive.commitOrRemove();
}
if (editor) {
this.unselectAll();
this.div.removeEventListener("click", this.#boundClick);
@ -185,7 +231,6 @@ class AnnotationEditorLayer {
this.#uiManager.allowClick = false;
this.div.addEventListener("click", this.#boundClick);
}
this.#uiManager.setActiveEditor(editor);
}
attach(editor) {
@ -212,7 +257,6 @@ class AnnotationEditorLayer {
if (this.#uiManager.isActive(editor) || this.#editors.size === 0) {
this.setActiveEditor(null);
this.#uiManager.allowClick = true;
this.div.focus();
}
}
@ -279,7 +323,22 @@ class AnnotationEditorLayer {
editor.remove();
};
this.addCommands(cmd, undo);
this.addCommands(cmd, undo, true);
}
/**
* Add a new editor and make this addition undoable.
* @param {AnnotationEditor} editor
*/
addUndoableEditor(editor) {
const cmd = () => {
this.addOrRebuild(editor);
};
const undo = () => {
editor.remove();
};
this.addCommands(cmd, undo, false);
}
/**
@ -305,6 +364,26 @@ class AnnotationEditorLayer {
return null;
}
/**
* Create and add a new editor.
* @param {MouseEvent} event
* @returns {AnnotationEditor}
*/
#createAndAddNewEditor(event) {
const id = this.getNextId();
const editor = this.#createNewEditor({
parent: this,
id,
x: event.offsetX,
y: event.offsetY,
});
if (editor) {
this.add(editor);
}
return editor;
}
/**
* Mouseclick callback.
* @param {MouseEvent} event
@ -316,16 +395,7 @@ class AnnotationEditorLayer {
return;
}
const id = this.getNextId();
const editor = this.#createNewEditor({
parent: this,
id,
x: event.offsetX,
y: event.offsetY,
});
if (editor) {
this.addANewEditor(editor);
}
this.#createAndAddNewEditor(event);
}
/**

View File

@ -53,6 +53,20 @@ class AnnotationEditor {
this.isAttachedToDOM = false;
}
/**
* This editor will be behind the others.
*/
setInBackground() {
this.div.classList.add("background");
}
/**
* This editor will be in the foreground.
*/
setInForeground() {
this.div.classList.remove("background");
}
/**
* onfocus callback.
*/
@ -81,12 +95,16 @@ class AnnotationEditor {
event.preventDefault();
this.commitOrRemove();
this.parent.setActiveEditor(null);
}
commitOrRemove() {
if (this.isEmpty()) {
this.remove();
} else {
this.commit();
}
this.parent.setActiveEditor(null);
}
/**
@ -156,7 +174,6 @@ class AnnotationEditor {
this.div = document.createElement("div");
this.div.className = this.name;
this.div.setAttribute("id", this.id);
this.div.draggable = true;
this.div.tabIndex = 100;
const [tx, ty] = this.getInitialTranslation();

View File

@ -33,6 +33,8 @@ class FreeTextEditor extends AnnotationEditor {
#contentHTML = "";
#hasAlreadyBeenCommitted = false;
#fontSize;
static _freeTextDefaultContent = "";
@ -168,6 +170,13 @@ class FreeTextEditor extends AnnotationEditor {
* @returns {undefined}
*/
commit() {
if (!this.#hasAlreadyBeenCommitted) {
// This editor has something and it's the first time
// it's commited so we can it in the undo/redo stack.
this.#hasAlreadyBeenCommitted = true;
this.parent.addUndoableEditor(this);
}
this.disableEditMode();
this.#contentHTML = this.editorDiv.innerHTML;
this.#content = this.#extractText().trimEnd();

View File

@ -21,11 +21,11 @@ import { fitCurve } from "./fit_curve/fit_curve.js";
* Basic draw editor in order to generate an Ink annotation.
*/
class InkEditor extends AnnotationEditor {
#aspectRatio;
#aspectRatio = 0;
#baseHeight;
#baseHeight = 0;
#baseWidth;
#baseWidth = 0;
#boundCanvasMousemove;
@ -35,9 +35,9 @@ class InkEditor extends AnnotationEditor {
#boundCanvasMousedown;
#disableEditing;
#disableEditing = false;
#observer;
#observer = null;
constructor(params) {
super({ ...params, name: "inkEditor" });
@ -48,10 +48,6 @@ class InkEditor extends AnnotationEditor {
this.currentPath = [];
this.scaleFactor = 1;
this.translationX = this.translationY = 0;
this.#baseWidth = this.#baseHeight = 0;
this.#aspectRatio = 0;
this.#disableEditing = false;
this.#observer = null;
this.x = 0;
this.y = 0;
@ -113,8 +109,6 @@ class InkEditor extends AnnotationEditor {
return;
}
super.remove();
// Destroy the canvas.
this.canvas.width = this.canvas.heigth = 0;
this.canvas.remove();
@ -122,11 +116,13 @@ class InkEditor extends AnnotationEditor {
this.#observer.disconnect();
this.#observer = null;
super.remove();
}
/** @inheritdoc */
enableEditMode() {
if (this.#disableEditing) {
if (this.#disableEditing || this.canvas === null) {
return;
}
@ -145,7 +141,7 @@ class InkEditor extends AnnotationEditor {
super.disableEditMode();
this.canvas.style.cursor = "auto";
this.div.draggable = true;
this.div.draggable = !this.isEmpty();
this.div.classList.remove("editing");
this.canvas.removeEventListener("mousedown", this.#boundCanvasMousedown);
@ -154,6 +150,7 @@ class InkEditor extends AnnotationEditor {
/** @inheritdoc */
onceAdded() {
this.div.draggable = !this.isEmpty();
this.div.focus();
}
@ -238,11 +235,15 @@ class InkEditor extends AnnotationEditor {
if (this.paths.length === 0) {
this.remove();
} else {
if (!this.canvas) {
this.#createCanvas();
this.#createObserver();
}
this.#fitToContent();
}
};
this.parent.addCommands(cmd, undo);
this.parent.addCommands(cmd, undo, true);
}
/**
@ -273,8 +274,12 @@ class InkEditor extends AnnotationEditor {
if (this.#disableEditing) {
return;
}
this.disableEditMode();
// This editor must be on top of the main ink editor.
this.setInForeground();
this.#disableEditing = true;
this.div.classList.add("disabled");
@ -297,6 +302,10 @@ class InkEditor extends AnnotationEditor {
return;
}
// We want to draw on top of any other editors.
// Since it's the last child, there's no need to give it a higher z-index.
this.setInForeground();
event.stopPropagation();
this.canvas.addEventListener("mouseleave", this.#boundCanvasMouseleave);
@ -324,6 +333,10 @@ class InkEditor extends AnnotationEditor {
if (this.isInEditMode() && this.currentPath.length !== 0) {
event.stopPropagation();
this.#endDrawing(event);
// Since the ink editor covers all of the page and we want to be able
// to select another editor, we just put this one in the background.
this.setInBackground();
}
}
@ -334,6 +347,7 @@ class InkEditor extends AnnotationEditor {
*/
canvasMouseleave(event) {
this.#endDrawing(event);
this.setInBackground();
}
/**

View File

@ -63,8 +63,9 @@ class CommandManager {
* Add a new couple of commands to be used in case of redo/undo.
* @param {function} cmd
* @param {function} undo
* @param {boolean} mustExec
*/
add(cmd, undo) {
add(cmd, undo, mustExec) {
const save = [cmd, undo];
const next = (this.#position + 1) % this.#maxSize;
if (next !== this.#start) {
@ -79,7 +80,10 @@ class CommandManager {
this.#position = this.#commands.length - 1;
}
this.#setCommands(save);
cmd();
if (mustExec) {
cmd();
}
}
/**
@ -316,6 +320,9 @@ class AnnotationEditorUIManager {
this.#disableAll();
} else {
this.#enableAll();
for (const layer of this.#allLayers) {
layer.updateMode(mode);
}
}
}
@ -409,9 +416,10 @@ class AnnotationEditorUIManager {
* Add a command to execute (cmd) and another one to undo it.
* @param {function} cmd
* @param {function} undo
* @param {boolean} mustExec
*/
addCommands(cmd, undo) {
this.#commandManager.add(cmd, undo);
addCommands(cmd, undo, mustExec) {
this.#commandManager.add(cmd, undo, mustExec);
}
/**
@ -460,7 +468,7 @@ class AnnotationEditorUIManager {
}
};
this.addCommands(cmd, undo);
this.addCommands(cmd, undo, true);
} else {
if (!this.#activeEditor) {
return;
@ -474,7 +482,7 @@ class AnnotationEditorUIManager {
};
}
this.addCommands(cmd, undo);
this.addCommands(cmd, undo, true);
}
/**
@ -501,7 +509,7 @@ class AnnotationEditorUIManager {
layer.addOrRebuild(editor);
};
this.addCommands(cmd, undo);
this.addCommands(cmd, undo, true);
}
}
@ -522,7 +530,7 @@ class AnnotationEditorUIManager {
editor.remove();
};
this.addCommands(cmd, undo);
this.addCommands(cmd, undo, true);
}
/**

View File

@ -98,6 +98,11 @@
overflow: auto;
width: 100%;
height: 100%;
z-index: 1;
}
.annotationEditorLayer .background {
z-index: 0;
}
.annotationEditorLayer .inkEditor:focus {