Implement the Text Tool and text layer MVP (#492)

* Add text tool

* Double click with the select tool to edit text

* Fix (I think?) transitioning to select tool

* Commit and abort text editing

* Transition to a contenteditable div and autosize

* Fix right click blocking

* Cleanup hints

* Ctrl + enter leaves text edit mode

* Render indervidual bounding boxes for text

* Re-format space indents

* Reflect font size in the textarea

* Fix change tool behaviour

* Remove starting text

* Populate the cache (caused doc load bug)

* Remove console log

* Chrome display the flashing text entry cursor

* Update overlay on input

* Cleanup input.ts

* Fix bounding boxes

* Apply review feedback

* Remove manual test

* Remove svg from gitignore

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2022-01-30 15:10:10 +00:00
committed by Keavon Chambers
co-authored by Keavon Chambers
parent 1d2768c26d
commit 121a68ad3c
33 changed files with 1152 additions and 56 deletions
+30 -8
View File
@@ -3,7 +3,7 @@ import { DocumentsState } from "@/state/documents";
import { FullscreenState } from "@/state/fullscreen";
import { EditorState } from "@/state/wasm-loader";
type EventName = keyof HTMLElementEventMap | keyof WindowEventHandlersEventMap;
type EventName = keyof HTMLElementEventMap | keyof WindowEventHandlersEventMap | "modifyinputfield";
interface EventListenerTarget {
addEventListener: typeof window.addEventListener;
removeEventListener: typeof window.removeEventListener;
@@ -22,25 +22,29 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
{ target: window, eventName: "pointermove", action: (e: PointerEvent): void => onPointerMove(e) },
{ target: window, eventName: "pointerdown", action: (e: PointerEvent): void => onPointerDown(e) },
{ target: window, eventName: "pointerup", action: (e: PointerEvent): void => onPointerUp(e) },
{ target: window, eventName: "dblclick", action: (e: PointerEvent): void => onDoubleClick(e) },
{ target: window, eventName: "mousedown", action: (e: MouseEvent): void => onMouseDown(e) },
{ target: window, eventName: "wheel", action: (e: WheelEvent): void => onMouseScroll(e), options: { passive: false } },
{ target: window, eventName: "modifyinputfield", action: (e: CustomEvent): void => onmodifyinputfiled(e) },
];
let viewportPointerInteractionOngoing = false;
let textInput = undefined as undefined | HTMLDivElement;
// Keyboard events
const shouldRedirectKeyboardEventToBackend = (e: KeyboardEvent): boolean => {
// Don't redirect user input from text entry into HTML elements
const { target } = e;
if (target instanceof HTMLElement && (target.nodeName === "INPUT" || target.nodeName === "TEXTAREA" || target.isContentEditable)) return false;
// Don't redirect when a modal is covering the workspace
if (dialog.dialogIsVisible()) return false;
const key = getLatinKey(e);
if (!key) return false;
// Don't redirect user input from text entry into HTML elements
const { target } = e;
if (key !== "escape" && !(key === "enter" && e.ctrlKey) && target instanceof HTMLElement && (target.nodeName === "INPUT" || target.nodeName === "TEXTAREA" || target.isContentEditable))
return false;
// Don't redirect a fullscreen request
if (key === "f11" && e.type === "keydown" && !e.repeat) {
e.preventDefault();
@@ -107,6 +111,7 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
const { target } = e;
const inCanvas = target instanceof Element && target.closest("[data-canvas]");
const inDialog = target instanceof Element && target.closest("[data-dialog-modal] [data-floating-menu-content]");
const inTextInput = target === textInput;
if (dialog.dialogIsVisible() && !inDialog) {
dialog.dismissDialog();
@@ -114,7 +119,9 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
e.stopPropagation();
}
if (inCanvas) viewportPointerInteractionOngoing = true;
if (textInput && !inTextInput) {
editor.instance.on_change_text(textInput.textContent || "");
} else if (inCanvas && !inTextInput) viewportPointerInteractionOngoing = true;
if (viewportPointerInteractionOngoing) {
const modifiers = makeModifiersBitfield(e);
@@ -125,8 +132,19 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
const onPointerUp = (e: PointerEvent): void => {
if (!e.buttons) viewportPointerInteractionOngoing = false;
const modifiers = makeModifiersBitfield(e);
editor.instance.on_mouse_up(e.clientX, e.clientY, e.buttons, modifiers);
if (!textInput) {
const modifiers = makeModifiersBitfield(e);
editor.instance.on_mouse_up(e.clientX, e.clientY, e.buttons, modifiers);
}
};
const onDoubleClick = (e: PointerEvent): void => {
if (!e.buttons) viewportPointerInteractionOngoing = false;
if (!textInput) {
const modifiers = makeModifiersBitfield(e);
editor.instance.on_double_click(e.clientX, e.clientY, e.buttons, modifiers);
}
};
// Mouse events
@@ -154,6 +172,10 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
}
};
const onmodifyinputfiled = (e: CustomEvent): void => {
textInput = e.detail;
};
// Window events
const onWindowResize = (container: HTMLElement): void => {