mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-25 04:58:12 +08:00
Bundle Graphite using Tauri (#873)
* Setup tauri component for graphite editor Integrate graphite into tauri app Split interpreted-executor out of graph-craft * Add gpu execution node * General Cleanup
This commit is contained in:
committed by
Keavon Chambers
parent
a2043bcb7c
commit
f1b7abede7
@@ -280,7 +280,7 @@ import {
|
||||
type LayerPanelEntry,
|
||||
defaultWidgetLayout,
|
||||
UpdateDocumentLayerDetails,
|
||||
UpdateDocumentLayerTreeStructure,
|
||||
UpdateDocumentLayerTreeStructureJs,
|
||||
UpdateLayerTreeOptionsLayout,
|
||||
layerTypeData,
|
||||
} from "@/wasm-communication/messages";
|
||||
@@ -483,14 +483,14 @@ export default defineComponent({
|
||||
this.fakeHighlight = undefined;
|
||||
this.dragInPanel = false;
|
||||
},
|
||||
rebuildLayerTree(updateDocumentLayerTreeStructure: UpdateDocumentLayerTreeStructure) {
|
||||
rebuildLayerTree(updateDocumentLayerTreeStructure: UpdateDocumentLayerTreeStructureJs) {
|
||||
const layerWithNameBeingEdited = this.layers.find((layer: LayerListingInfo) => layer.editingName);
|
||||
const layerPathWithNameBeingEdited = layerWithNameBeingEdited?.entry.path;
|
||||
const layerIdWithNameBeingEdited = layerPathWithNameBeingEdited?.slice(-1)[0];
|
||||
const path = [] as bigint[];
|
||||
this.layers = [] as LayerListingInfo[];
|
||||
|
||||
const recurse = (folder: UpdateDocumentLayerTreeStructure, layers: LayerListingInfo[], cache: Map<string, LayerPanelEntry>): void => {
|
||||
const recurse = (folder: UpdateDocumentLayerTreeStructureJs, layers: LayerListingInfo[], cache: Map<string, LayerPanelEntry>): void => {
|
||||
folder.children.forEach((item, index) => {
|
||||
// TODO: fix toString
|
||||
const layerId = BigInt(item.layerId.toString());
|
||||
@@ -520,7 +520,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerTreeStructure, (updateDocumentLayerTreeStructure) => {
|
||||
this.editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerTreeStructureJs, (updateDocumentLayerTreeStructure) => {
|
||||
this.rebuildLayerTree(updateDocumentLayerTreeStructure);
|
||||
});
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { platformIsMac } from "@/utility-functions/platform";
|
||||
import { type KeyRaw, type KeysGroup, type MenuBarEntry, type MenuListEntry, UpdateMenuBarLayout } from "@/wasm-communication/messages";
|
||||
import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, UpdateMenuBarLayout } from "@/wasm-communication/messages";
|
||||
|
||||
import MenuList from "@/components/floating-menus/MenuList.vue";
|
||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||
@@ -92,7 +92,7 @@ export default defineComponent({
|
||||
mounted() {
|
||||
this.editor.subscriptions.subscribeJsMessage(UpdateMenuBarLayout, (updateMenuBarLayout) => {
|
||||
const arraysEqual = (a: KeyRaw[], b: KeyRaw[]): boolean => a.length === b.length && a.every((aValue, i) => aValue === b[i]);
|
||||
const shortcutRequiresLock = (shortcut: KeysGroup): boolean => {
|
||||
const shortcutRequiresLock = (shortcut: LayoutKeysGroup): boolean => {
|
||||
const shortcutKeys = shortcut.map((keyWithLabel) => keyWithLabel.key);
|
||||
|
||||
// If this shortcut matches any of the browser-reserved shortcuts
|
||||
|
||||
@@ -127,7 +127,7 @@ import { defineComponent, type PropType } from "vue";
|
||||
|
||||
import { type IconName } from "@/utility-functions/icons";
|
||||
import { platformIsMac } from "@/utility-functions/platform";
|
||||
import { type KeyRaw, type KeysGroup, type Key, type MouseMotion } from "@/wasm-communication/messages";
|
||||
import { type KeyRaw, type LayoutKeysGroup, type Key, type MouseMotion } from "@/wasm-communication/messages";
|
||||
|
||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||
@@ -158,7 +158,7 @@ const ICON_WIDTHS = {
|
||||
export default defineComponent({
|
||||
inject: ["fullscreen"],
|
||||
props: {
|
||||
keysWithLabelsGroups: { type: Array as PropType<KeysGroup[]>, default: () => [] },
|
||||
keysWithLabelsGroups: { type: Array as PropType<LayoutKeysGroup[]>, default: () => [] },
|
||||
mouseMotion: { type: String as PropType<MouseMotion | undefined>, required: false },
|
||||
requiresLock: { type: Boolean as PropType<boolean>, default: false },
|
||||
},
|
||||
@@ -182,7 +182,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
keyTextOrIconList(keyGroup: KeysGroup): LabelData[] {
|
||||
keyTextOrIconList(keyGroup: LayoutKeysGroup): LabelData[] {
|
||||
return keyGroup.map((key) => this.keyTextOrIcon(key));
|
||||
},
|
||||
keyTextOrIcon(keyWithLabel: Key): LabelData {
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { platformIsMac } from "@/utility-functions/platform";
|
||||
import { type HintData, type HintInfo, type KeysGroup, UpdateInputHints } from "@/wasm-communication/messages";
|
||||
import { type HintData, type HintInfo, type LayoutKeysGroup, UpdateInputHints } from "@/wasm-communication/messages";
|
||||
|
||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
import Separator from "@/components/widgets/labels/Separator.vue";
|
||||
@@ -63,7 +63,7 @@ export default defineComponent({
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
inputKeysForPlatform(hint: HintInfo): KeysGroup[] {
|
||||
inputKeysForPlatform(hint: HintInfo): LayoutKeysGroup[] {
|
||||
if (platformIsMac() && hint.keyGroupsMac) return hint.keyGroupsMac;
|
||||
return hint.keyGroups;
|
||||
},
|
||||
|
||||
@@ -214,7 +214,7 @@ import { defineComponent, nextTick, type PropType } from "vue";
|
||||
|
||||
import { platformIsMac } from "@/utility-functions/platform";
|
||||
|
||||
import { type KeysGroup, type Key } from "@/wasm-communication/messages";
|
||||
import { type LayoutKeysGroup, type Key } from "@/wasm-communication/messages";
|
||||
|
||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
@@ -258,7 +258,7 @@ export default defineComponent({
|
||||
openDocument() {
|
||||
this.editor.instance.documentOpen();
|
||||
},
|
||||
platformModifiers(reservedKey: boolean): KeysGroup {
|
||||
platformModifiers(reservedKey: boolean): LayoutKeysGroup {
|
||||
// TODO: Remove this by properly feeding these keys from a layout provided by the backend
|
||||
|
||||
const ALT: Key = { key: "Alt", label: "Alt" };
|
||||
|
||||
@@ -30,7 +30,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const listeners: { target: EventListenerTarget; eventName: EventName; action: (event: any) => void; options?: boolean | AddEventListenerOptions }[] = [
|
||||
{ target: window, eventName: "resize", action: (): void => onWindowResize(container) },
|
||||
{ target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent): void => onBeforeUnload(e) },
|
||||
{ target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent): Promise<void> => onBeforeUnload(e) },
|
||||
{ target: window.document, eventName: "contextmenu", action: (e: MouseEvent): void => e.preventDefault() },
|
||||
{ target: window.document, eventName: "fullscreenchange", action: (): void => fullscreen.fullscreenModeChanged() },
|
||||
{ target: window, eventName: "keyup", action: (e: KeyboardEvent): Promise<void> => onKeyUp(e) },
|
||||
@@ -235,15 +235,15 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
|
||||
if (boundsOfViewports.length > 0) editor.instance.boundsOfViewports(data);
|
||||
}
|
||||
|
||||
function onBeforeUnload(e: BeforeUnloadEvent): void {
|
||||
async function onBeforeUnload(e: BeforeUnloadEvent): Promise<void> {
|
||||
const activeDocument = document.state.documents[document.state.activeDocumentIndex];
|
||||
if (activeDocument && !activeDocument.isAutoSaved) editor.instance.triggerAutoSave(activeDocument.id);
|
||||
|
||||
// Skip the message if the editor crashed, since work is already lost
|
||||
if (editor.instance.hasCrashed()) return;
|
||||
if (await editor.instance.hasCrashed()) return;
|
||||
|
||||
// Skip the message during development, since it's annoying when testing
|
||||
if (editor.instance.inDevelopmentMode()) return;
|
||||
if (await editor.instance.inDevelopmentMode()) return;
|
||||
|
||||
const allDocumentsSaved = document.state.documents.reduce((acc, doc) => acc && doc.isSaved, true);
|
||||
if (!allDocumentsSaved) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
|
||||
import type WasmBindgenPackage from "@/../wasm/pkg";
|
||||
import { panicProxy } from "@/utility-functions/panic-proxy";
|
||||
import { type JsMessageType } from "@/wasm-communication/messages";
|
||||
@@ -24,6 +26,30 @@ export async function updateImage(path: BigUint64Array, mime: string, imageData:
|
||||
editorInstance?.setImageBlobURL(documentId, path, blobURL, image.naturalWidth, image.naturalHeight);
|
||||
}
|
||||
|
||||
export async function fetchImage(path: BigUint64Array, mime: string, documentId: bigint, url: string): Promise<void> {
|
||||
const data = await fetch(url);
|
||||
const blob = await data.blob();
|
||||
|
||||
const blobURL = URL.createObjectURL(blob);
|
||||
|
||||
// Pre-decode the image so it is ready to be drawn instantly once it's placed into the viewport SVG
|
||||
const image = new Image();
|
||||
image.src = blobURL;
|
||||
await image.decode();
|
||||
|
||||
editorInstance?.setImageBlobURL(documentId, path, blobURL, image.naturalWidth, image.naturalHeight);
|
||||
}
|
||||
|
||||
// export async function dispatchTauri(message: string): Promise<string> {
|
||||
export async function dispatchTauri(message: any): Promise<void> {
|
||||
try {
|
||||
const response = await invoke("handle_message", { message });
|
||||
editorInstance?.tauriResponse(response);
|
||||
} catch {
|
||||
console.error("Failed to dispatch Tauri message");
|
||||
}
|
||||
}
|
||||
|
||||
// Should be called asynchronously before `createEditor()`
|
||||
export async function initWasm(): Promise<void> {
|
||||
// Skip if the WASM module is already initialized
|
||||
@@ -34,8 +60,14 @@ export async function initWasm(): Promise<void> {
|
||||
wasmImport = await import("@/../wasm/pkg").then(panicProxy);
|
||||
|
||||
// Provide a random starter seed which must occur after initializing the WASM module, since WASM can't generate its own random numbers
|
||||
const randomSeed = BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
|
||||
const randomSeedFloat = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||
const randomSeed = BigInt(randomSeedFloat);
|
||||
wasmImport?.setRandomSeed(randomSeed);
|
||||
try {
|
||||
await invoke("set_random_seed", { seed: randomSeedFloat });
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
// Should be called after running `initWasm()` and its promise resolving
|
||||
|
||||
@@ -136,9 +136,9 @@ export type HintData = HintGroup[];
|
||||
export type HintGroup = HintInfo[];
|
||||
|
||||
export class HintInfo {
|
||||
readonly keyGroups!: KeysGroup[];
|
||||
readonly keyGroups!: LayoutKeysGroup[];
|
||||
|
||||
readonly keyGroupsMac!: KeysGroup[] | undefined;
|
||||
readonly keyGroupsMac!: LayoutKeysGroup[] | undefined;
|
||||
|
||||
readonly mouse!: MouseMotion | undefined;
|
||||
|
||||
@@ -151,8 +151,8 @@ export class HintInfo {
|
||||
export type KeyRaw = string;
|
||||
// Serde converts a Rust `Key` enum variant into this format (via a custom serializer) with both the `Key` variant name (called `RawKey` in TS) and the localized `label` for the key
|
||||
export type Key = { key: KeyRaw; label: string };
|
||||
export type KeysGroup = Key[];
|
||||
export type ActionKeys = { keys: KeysGroup };
|
||||
export type LayoutKeysGroup = Key[];
|
||||
export type ActionKeys = { keys: LayoutKeysGroup };
|
||||
|
||||
export type MouseMotion = string;
|
||||
|
||||
@@ -596,8 +596,8 @@ export class TriggerSavePreferences extends JsMessage {
|
||||
|
||||
export class DocumentChanged extends JsMessage {}
|
||||
|
||||
export class UpdateDocumentLayerTreeStructure extends JsMessage {
|
||||
constructor(readonly layerId: bigint, readonly children: UpdateDocumentLayerTreeStructure[]) {
|
||||
export class UpdateDocumentLayerTreeStructureJs extends JsMessage {
|
||||
constructor(readonly layerId: bigint, readonly children: UpdateDocumentLayerTreeStructureJs[]) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -607,7 +607,7 @@ type DataBuffer = {
|
||||
length: bigint;
|
||||
};
|
||||
|
||||
export function newUpdateDocumentLayerTreeStructure(input: { dataBuffer: DataBuffer }, wasm: WasmRawInstance): UpdateDocumentLayerTreeStructure {
|
||||
export function newUpdateDocumentLayerTreeStructure(input: { dataBuffer: DataBuffer }, wasm: WasmRawInstance): UpdateDocumentLayerTreeStructureJs {
|
||||
const pointerNum = Number(input.dataBuffer.pointer);
|
||||
const lengthNum = Number(input.dataBuffer.length);
|
||||
|
||||
@@ -624,7 +624,7 @@ export function newUpdateDocumentLayerTreeStructure(input: { dataBuffer: DataBuf
|
||||
const layerIdsSection = new DataView(wasmMemoryBuffer, pointerNum + 8 + structureSectionLength * 8);
|
||||
|
||||
let layersEncountered = 0;
|
||||
let currentFolder = new UpdateDocumentLayerTreeStructure(BigInt(-1), []);
|
||||
let currentFolder = new UpdateDocumentLayerTreeStructureJs(BigInt(-1), []);
|
||||
const currentFolderStack = [currentFolder];
|
||||
|
||||
for (let i = 0; i < structureSectionLength; i += 1) {
|
||||
@@ -639,7 +639,7 @@ export function newUpdateDocumentLayerTreeStructure(input: { dataBuffer: DataBuf
|
||||
const layerId = layerIdsSection.getBigUint64(layersEncountered * 8, true);
|
||||
layersEncountered += 1;
|
||||
|
||||
const childLayer = new UpdateDocumentLayerTreeStructure(layerId, []);
|
||||
const childLayer = new UpdateDocumentLayerTreeStructureJs(layerId, []);
|
||||
currentFolder.children.push(childLayer);
|
||||
}
|
||||
|
||||
@@ -1361,7 +1361,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||
UpdateDocumentArtwork,
|
||||
UpdateDocumentBarLayout,
|
||||
UpdateDocumentLayerDetails,
|
||||
UpdateDocumentLayerTreeStructure: newUpdateDocumentLayerTreeStructure,
|
||||
UpdateDocumentLayerTreeStructureJs: newUpdateDocumentLayerTreeStructure,
|
||||
UpdateDocumentModeLayout,
|
||||
UpdateDocumentOverlays,
|
||||
UpdateDocumentRulers,
|
||||
|
||||
Reference in New Issue
Block a user