Migrate text layers to nodes (#1155)

* Initial work towards text to node

* Add the text generate node

* Implement live edit

* Fix merge error

* Cleanup text tool

* Implement text

* Fix transforms

* Fix broken image frame

* Double click to edit text

* Fix rendering text on load

* Moving whilst editing

* Better text properties

* Prevent changing vector when there is a Text node

* Push node api

* Use node fn macro

* Stable ids

* Image module as a seperate file

* Explain check for "Input Frame" node

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube 2023-04-27 03:07:43 +01:00 committed by Keavon Chambers
parent 271f9d5158
commit ef93f8442a
44 changed files with 1082 additions and 1143 deletions

View file

@ -7,6 +7,7 @@
type MouseCursorIcon,
type XY,
DisplayEditableTextbox,
DisplayEditableTextboxTransform,
DisplayRemoveEditableTextbox,
TriggerTextCommit,
TriggerViewportResize,
@ -37,6 +38,8 @@
// Interactive text editing
let textInput: undefined | HTMLDivElement = undefined;
let showTextInput: boolean;
let textInputMatrix: number[];
// CSS properties
let canvasSvgWidth: number | undefined = undefined;
@ -121,32 +124,6 @@
export async function updateDocumentArtwork(svg: string) {
artworkSvg = svg;
rasterizedCanvas = undefined;
await tick();
if (textInput) {
const foreignObject = canvasContainer?.getElementsByTagName("foreignObject")[0] as SVGForeignObjectElement | undefined;
if (!foreignObject || foreignObject.children.length > 0) return;
const addedInput = foreignObject.appendChild(textInput);
window.dispatchEvent(new CustomEvent("modifyinputfield", { detail: addedInput }));
await tick();
// Necessary to select contenteditable: https://stackoverflow.com/questions/6139107/programmatically-select-text-in-a-contenteditable-html-element/6150060#6150060
const range = window.document.createRange();
range.selectNodeContents(addedInput);
const selection = window.getSelection();
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
}
addedInput.focus();
addedInput.click();
}
}
export function updateDocumentOverlays(svg: string) {
@ -257,13 +234,20 @@
editor.instance.onChangeText(textCleaned);
}
export function displayEditableTextbox(displayEditableTextbox: DisplayEditableTextbox) {
textInput = window.document.createElement("div") as HTMLDivElement;
export async function displayEditableTextbox(displayEditableTextbox: DisplayEditableTextbox) {
showTextInput = true;
await tick();
if (!textInput) {
return;
}
if (displayEditableTextbox.text === "") textInput.textContent = "";
else textInput.textContent = `${displayEditableTextbox.text}\n`;
textInput.contentEditable = "true";
textInput.style.transformOrigin = "0 0";
textInput.style.width = displayEditableTextbox.lineWidth ? `${displayEditableTextbox.lineWidth}px` : "max-content";
textInput.style.height = "auto";
textInput.style.fontSize = `${displayEditableTextbox.fontSize}px`;
@ -273,17 +257,37 @@
if (!textInput) return;
editor.instance.updateBounds(textInputCleanup(textInput.innerText));
};
textInputMatrix = displayEditableTextbox.transform;
const new_font = new FontFace("text-font", `url(${displayEditableTextbox.url})`);
window.document.fonts.add(new_font);
textInput.style.fontFamily = "text-font";
// Necessary to select contenteditable: https://stackoverflow.com/questions/6139107/programmatically-select-text-in-a-contenteditable-html-element/6150060#6150060
const range = window.document.createRange();
range.selectNodeContents(textInput);
const selection = window.getSelection();
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
}
textInput.focus();
textInput.click();
window.dispatchEvent(new CustomEvent("modifyinputfield", { detail: textInput }));
}
export function displayRemoveEditableTextbox() {
textInput = undefined;
window.dispatchEvent(new CustomEvent("modifyinputfield", { detail: undefined }));
showTextInput = false;
}
// Resize elements to render the new viewport size
export function viewportResize() {
if (!canvasContainer) return;
// Resize the canvas
canvasSvgWidth = Math.ceil(parseFloat(getComputedStyle(canvasContainer).width));
canvasSvgHeight = Math.ceil(parseFloat(getComputedStyle(canvasContainer).height));
@ -364,6 +368,9 @@
displayEditableTextbox(data);
});
editor.subscriptions.subscribeJsMessage(DisplayEditableTextboxTransform, async (data) => {
textInputMatrix = data.transform;
});
editor.subscriptions.subscribeJsMessage(DisplayRemoveEditableTextbox, async () => {
await tick();
@ -432,6 +439,11 @@
<svg class="overlays" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
{@html overlaysSvg}
</svg>
<div class="text-input" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
{#if showTextInput}
<div bind:this={textInput} style:transform="matrix({textInputMatrix})" />
{/if}
</div>
</div>
</LayoutCol>
<LayoutCol class="bar-area right-scrollbar">
@ -575,29 +587,23 @@
}
}
foreignObject {
width: 10000px;
height: 10000px;
.text-input div {
cursor: text;
background: none;
border: none;
margin: 0;
padding: 0;
overflow: visible;
white-space: pre-wrap;
display: inline-block;
// Workaround to force Chrome to display the flashing text entry cursor when text is empty
padding-left: 1px;
margin-left: -1px;
div {
cursor: text;
background: none;
&:focus {
border: none;
margin: 0;
padding: 0;
overflow: visible;
white-space: pre-wrap;
display: inline-block;
// Workaround to force Chrome to display the flashing text entry cursor when text is empty
padding-left: 1px;
margin-left: -1px;
&:focus {
border: none;
outline: none; // Ok for contenteditable element
margin: -1px;
}
outline: none; // Ok for contenteditable element
margin: -1px;
}
}
}

View file

@ -704,6 +704,14 @@ export class DisplayEditableTextbox extends JsMessage {
@Type(() => Color)
readonly color!: Color;
readonly url!: string;
readonly transform!: number[];
}
export class DisplayEditableTextboxTransform extends JsMessage {
readonly transform!: number[];
}
export class UpdateImageData extends JsMessage {
@ -1384,6 +1392,7 @@ export const messageMakers: Record<string, MessageMaker> = {
DisplayDialogDismiss,
DisplayDialogPanic,
DisplayEditableTextbox,
DisplayEditableTextboxTransform,
DisplayRemoveEditableTextbox,
TriggerAboutGraphiteLocalizedCommitDate,
TriggerImaginateCheckServerStatus,