diff --git a/frontend-svelte/src/components/floating-menus/ColorPicker.svelte b/frontend-svelte/src/components/floating-menus/ColorPicker.svelte index 9eafeb5b..b45b8326 100644 --- a/frontend-svelte/src/components/floating-menus/ColorPicker.svelte +++ b/frontend-svelte/src/components/floating-menus/ColorPicker.svelte @@ -306,7 +306,7 @@ value={newColor.toHexOptionalAlpha() || "-"} on:commitText={({ detail }) => setColorCode(detail)} centered={true} - tooltip="Color code in hexadecimal format. 6 digits if opaque, 8 with alpha.\nAccepts input of CSS color values including named colors." + tooltip={"Color code in hexadecimal format. 6 digits if opaque, 8 with alpha.\nAccepts input of CSS color values including named colors."} /> @@ -347,8 +347,8 @@ unit={channel === "h" ? "°" : "%"} minWidth={56} tooltip={{ - h: "Hue component, the "color" along the rainbow", - s: "Saturation component, the "colorfulness" from gray to vivid", + h: `Hue component, the "color" along the rainbow`, + s: `Saturation component, the "colorfulness" from gray to vivid`, v: "Value (or Brightness), the distance away from being darkened to black", }[channel]} /> diff --git a/frontend-svelte/src/components/floating-menus/MenuList.svelte b/frontend-svelte/src/components/floating-menus/MenuList.svelte index 17ccabec..68588a25 100644 --- a/frontend-svelte/src/components/floating-menus/MenuList.svelte +++ b/frontend-svelte/src/components/floating-menus/MenuList.svelte @@ -1,3 +1,5 @@ + + (isOpen = detail)} + {open} + on:open={({ detail }) => (open = detail)} on:naturalWidth type="Dropdown" windowEdgeMargin={0} @@ -248,7 +236,7 @@ {/if} {#if entry.children} - + {/if} {/each} diff --git a/frontend-svelte/src/components/layout/FloatingMenu.svelte b/frontend-svelte/src/components/layout/FloatingMenu.svelte index f4ad4c1e..40b23fd1 100644 --- a/frontend-svelte/src/components/layout/FloatingMenu.svelte +++ b/frontend-svelte/src/components/layout/FloatingMenu.svelte @@ -104,10 +104,8 @@ // Gets the client bounds of the elements and apply relevant styles to them // TODO: Use the Vue :style attribute more whilst not causing recursive updates afterUpdate(() => { - // Turning measuring on and off both cause the component to change, which causes the `updated()` Vue event to fire extraneous times (hurting performance and sometimes causing an infinite loop) - if (measuringOngoingGuard) return; - - positionAndStyleFloatingMenu(); + // Turning measuring on and off both causes the component to change, which causes the `updated()` Vue event to fire extraneous times (hurting performance and sometimes causing an infinite loop) + if (!measuringOngoingGuard) positionAndStyleFloatingMenu(); }); function resizeObserverCallback(entries: ResizeObserverEntry[]) { @@ -204,13 +202,13 @@ // To be called by the parent component. Measures the actual width of the floating menu content element and returns it in a promise. export async function measureAndEmitNaturalWidth(): Promise { + if (!measuringOngoingGuard) return; + // Wait for the changed content which fired the `updated()` Vue event to be put into the DOM await tick(); // Wait until all fonts have been loaded and rendered so measurements of content involving text are accurate - // API is experimental but supported in all browsers - https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready - // eslint-disable-next-line @typescript-eslint/no-explicit-any - await (document as any).fonts.ready; + await document.fonts.ready; // Make the component show itself with 0 min-width so it can be measured, and wait until the values have been updated to the DOM measuringOngoing = true; diff --git a/frontend-svelte/src/components/panels/LayerTree.svelte b/frontend-svelte/src/components/panels/LayerTree.svelte index 475d6936..228a801e 100644 --- a/frontend-svelte/src/components/panels/LayerTree.svelte +++ b/frontend-svelte/src/components/panels/LayerTree.svelte @@ -60,25 +60,20 @@ let layerTreeOptionsLayout = defaultWidgetLayout(); onMount(() => { - editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerTreeStructureJs, (updateDocumentLayerTreeStructure) => { - rebuildLayerTree(updateDocumentLayerTreeStructure); - }); - editor.subscriptions.subscribeJsMessage(UpdateLayerTreeOptionsLayout, (updateLayerTreeOptionsLayout) => { patchWidgetLayout(layerTreeOptionsLayout, updateLayerTreeOptionsLayout); layerTreeOptionsLayout = layerTreeOptionsLayout; }); - editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerDetails, (updateDocumentLayerDetails) => { - const targetPath = updateDocumentLayerDetails.data.path; - const targetLayer = updateDocumentLayerDetails.data; + editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerTreeStructureJs, (updateDocumentLayerTreeStructure) => { + rebuildLayerTree(updateDocumentLayerTreeStructure); + }); - const layer = layerCache.get(targetPath.toString()); - if (layer) { - Object.assign(layer, targetLayer); - } else { - layerCache.set(targetPath.toString(), targetLayer); - } + editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerDetails, (updateDocumentLayerDetails) => { + const targetLayer = updateDocumentLayerDetails.data; + const targetPath = targetLayer.path; + + updateLayerInTree(targetPath, targetLayer); }); }); @@ -134,34 +129,23 @@ window.getSelection()?.removeAllRanges(); } - // TODO: Svelte: test this works function selectLayerWithModifiers(e: MouseEvent, listing: LayerListingInfo) { - const ctrl = e.ctrlKey; - const meta = e.metaKey; - const shift = e.shiftKey; - const alt = e.altKey; + // Get the pressed state of the modifier keys + const [ctrl, meta, shift, alt] = [e.ctrlKey, e.metaKey, e.shiftKey, e.altKey]; + // Get the state of the platform's accel key and its opposite platform's accel key + const [accel, oppositeAccel] = platformIsMac() ? [meta, ctrl] : [ctrl, meta]; - if (!ctrl && !meta && !shift && !alt) selectLayer(false, false, false, listing, e); - else if (!ctrl && !meta && shift && !alt) selectLayer(false, false, true, listing, e); - else if (ctrl && !meta && !shift && !alt) selectLayer(true, false, false, listing, e); - else if (ctrl && !meta && shift && !alt) selectLayer(true, false, true, listing, e); - else if (!ctrl && meta && !shift && !alt) selectLayer(false, true, false, listing, e); - else if (!ctrl && meta && shift && !alt) selectLayer(false, true, true, listing, e); - else if ((ctrl && meta) || alt) e.stopPropagation(); + // Select the layer only if the accel and/or shift keys are pressed + if (!oppositeAccel && !alt) selectLayer(accel, shift, listing); + + e.stopPropagation(); } - async function selectLayer(ctrl: boolean, cmd: boolean, shift: boolean, listing: LayerListingInfo, event: Event) { + function selectLayer(accel: boolean, shift: boolean, listing: LayerListingInfo) { + // Don't select while we are entering text to rename the layer if (listing.editingName) return; - const ctrlOrCmd = platformIsMac() ? cmd : ctrl; - // Pressing the Ctrl key on a Mac, or the Cmd key on another platform, is a violation of the `.exact` qualifier so we filter it out here - const opposite = platformIsMac() ? ctrl : cmd; - - if (!opposite) editor.instance.selectLayer(listing.entry.path, ctrlOrCmd, shift); - - // We always want to stop propagation so the click event doesn't pass through the layer and cause a deselection by clicking the layer panel background - // This is also why we cover the remaining cases not considered by the `.exact` qualifier, in the last two bindings on the layer element, with a `stopPropagation()` call - event.stopPropagation(); + editor.instance.selectLayer(listing.entry.path, accel, shift); } async function deselectAllLayers() { @@ -243,7 +227,7 @@ fakeHighlight = [layer.path]; } const select = (): void => { - if (!layer.layerMetadata.selected) selectLayer(false, false, false, listing, event); + if (!layer.layerMetadata.selected) selectLayer(false, false, listing); }; const target = (event.target || undefined) as HTMLElement | undefined; @@ -283,16 +267,19 @@ const layerWithNameBeingEdited = layers.find((layer: LayerListingInfo) => layer.editingName); const layerPathWithNameBeingEdited = layerWithNameBeingEdited?.entry.path; const layerIdWithNameBeingEdited = layerPathWithNameBeingEdited?.slice(-1)[0]; - const path = [] as bigint[]; - layers = [] as LayerListingInfo[]; + const path: bigint[] = []; - const recurse = (folder: UpdateDocumentLayerTreeStructureJs, layers: LayerListingInfo[], cache: Map): void => { + // Clear the layer tree before rebuilding it + layers = []; + + // Build the new layer tree + const recurse = (folder: UpdateDocumentLayerTreeStructureJs): void => { folder.children.forEach((item, index) => { // TODO: fix toString const layerId = BigInt(item.layerId.toString()); path.push(layerId); - const mapping = cache.get(path.toString()); + const mapping = layerCache.get(path.toString()); if (mapping) { layers.push({ folderIndex: index, @@ -303,13 +290,24 @@ } // Call self recursively if there are any children - if (item.children.length >= 1) recurse(item, layers, cache); + if (item.children.length >= 1) recurse(item); path.pop(); }); }; + recurse(updateDocumentLayerTreeStructure); + layers = layers; + } - recurse(updateDocumentLayerTreeStructure, layers, layerCache); + function updateLayerInTree(targetPath: BigUint64Array, targetLayer: LayerPanelEntry) { + const path = targetPath.toString(); + layerCache.set(path, targetLayer); + + const layer = layers.find((layer: LayerListingInfo) => layer.entry.path.toString() === path); + if (layer) { + layer.entry = targetLayer; + layers = layers; + } } function getLayerTypeData(layerType: LayerType): LayerTypeData { diff --git a/frontend-svelte/src/components/panels/Properties.svelte b/frontend-svelte/src/components/panels/Properties.svelte index 3088b073..d9c44b9a 100644 --- a/frontend-svelte/src/components/panels/Properties.svelte +++ b/frontend-svelte/src/components/panels/Properties.svelte @@ -52,5 +52,9 @@ .sections { flex: 1 1 100%; } + + .text-button { + flex-basis: 0; + } } diff --git a/frontend-svelte/src/components/widgets/buttons/TextButton.svelte b/frontend-svelte/src/components/widgets/buttons/TextButton.svelte index 9843351d..b0964ed5 100644 --- a/frontend-svelte/src/components/widgets/buttons/TextButton.svelte +++ b/frontend-svelte/src/components/widgets/buttons/TextButton.svelte @@ -41,7 +41,7 @@ display: flex; justify-content: center; align-items: center; - flex: 0 0 0; + flex: 0 0 auto; height: 24px; margin: 0; padding: 0 8px; diff --git a/frontend-svelte/src/components/widgets/inputs/MenuBarInput.svelte b/frontend-svelte/src/components/widgets/inputs/MenuBarInput.svelte index da5cf7ca..49777da7 100644 --- a/frontend-svelte/src/components/widgets/inputs/MenuBarInput.svelte +++ b/frontend-svelte/src/components/widgets/inputs/MenuBarInput.svelte @@ -21,7 +21,6 @@ const editor = getContext("editor"); - let self: HTMLDivElement; let entries: MenuListEntry[] = []; function clickEntry(menuListEntry: MenuListEntry, e: MouseEvent) { @@ -36,7 +35,7 @@ (e.target as HTMLElement | undefined)?.focus(); if (menuListEntry.ref) { - menuListEntry.ref.isOpen = true; + menuListEntry.ref.open = true; entries = entries; } else { throw new Error("The menu bar floating menu has no associated ref"); @@ -74,7 +73,7 @@ }); -