mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 03:38:11 +08:00
Replace text-only tooltips with custom richly styled tooltips (#3436)
* Replace the title attribute with custom FloatingMenu tooltips * Separate tooltip labels and descriptions into two styled blocks * Move keyboard shortcut tooltips to a separate section at the bottom * Update shortcut key styling in tooltips and hints bar * Fix .to_string()
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
import { type Editor } from "@graphite/editor";
|
||||
import { createClipboardManager } from "@graphite/io-managers/clipboard";
|
||||
import { createHyperlinkManager } from "@graphite/io-managers/hyperlinks";
|
||||
import { createHyperlinkManager } from "@graphite/io-managers/hyperlink";
|
||||
import { createInputManager } from "@graphite/io-managers/input";
|
||||
import { createLocalizationManager } from "@graphite/io-managers/localization";
|
||||
import { createPanicManager } from "@graphite/io-managers/panic";
|
||||
@@ -15,6 +15,7 @@
|
||||
import { createFullscreenState } from "@graphite/state-providers/fullscreen";
|
||||
import { createNodeGraphState } from "@graphite/state-providers/node-graph";
|
||||
import { createPortfolioState } from "@graphite/state-providers/portfolio";
|
||||
import { createTooltipState } from "@graphite/state-providers/tooltip";
|
||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
||||
|
||||
import MainWindow from "@graphite/components/window/MainWindow.svelte";
|
||||
@@ -26,6 +27,8 @@
|
||||
// State provider systems
|
||||
let dialog = createDialogState(editor);
|
||||
setContext("dialog", dialog);
|
||||
let tooltip = createTooltipState();
|
||||
setContext("tooltip", tooltip);
|
||||
let document = createDocumentState(editor);
|
||||
setContext("document", document);
|
||||
let fonts = createFontsState(editor);
|
||||
|
||||
@@ -31,6 +31,14 @@
|
||||
blue: [0, 0, 1],
|
||||
magenta: [1, 0, 1],
|
||||
};
|
||||
const PURE_COLORS_GRAYABLE = [
|
||||
["Red", "#ff0000", "#4c4c4c"],
|
||||
["Yellow", "#ffff00", "#e3e3e3"],
|
||||
["Green", "#00ff00", "#969696"],
|
||||
["Cyan", "#00ffff", "#b2b2b2"],
|
||||
["Blue", "#0000ff", "#1c1c1c"],
|
||||
["Magenta", "#ff00ff", "#696969"],
|
||||
];
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
@@ -420,7 +428,13 @@
|
||||
>
|
||||
<LayoutCol class="pickers-and-gradient">
|
||||
<LayoutRow class="pickers">
|
||||
<LayoutCol class="saturation-value-picker" title={disabled ? "Saturation and value (disabled)" : "Saturation and value"} on:pointerdown={onPointerDown} data-saturation-value-picker>
|
||||
<LayoutCol
|
||||
class="saturation-value-picker"
|
||||
data-tooltip-label="Saturation and Value"
|
||||
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
||||
on:pointerdown={onPointerDown}
|
||||
data-saturation-value-picker
|
||||
>
|
||||
{#if !isNone}
|
||||
<div class="selection-circle" style:top={`${(1 - value) * 100}%`} style:left={`${saturation * 100}%`} />
|
||||
{/if}
|
||||
@@ -434,12 +448,24 @@
|
||||
/>
|
||||
{/if}
|
||||
</LayoutCol>
|
||||
<LayoutCol class="hue-picker" title={disabled ? "Hue (disabled)" : "Hue"} on:pointerdown={onPointerDown} data-hue-picker>
|
||||
<LayoutCol
|
||||
class="hue-picker"
|
||||
data-tooltip-label="Hue"
|
||||
data-tooltip-description={"The shade along the spectrum of the rainbow." + (disabled ? "\n\nDisabled (read-only)." : "")}
|
||||
on:pointerdown={onPointerDown}
|
||||
data-hue-picker
|
||||
>
|
||||
{#if !isNone}
|
||||
<div class="selection-needle" style:top={`${(1 - hue) * 100}%`} />
|
||||
{/if}
|
||||
</LayoutCol>
|
||||
<LayoutCol class="alpha-picker" title={disabled ? "Alpha (disabled)" : "Alpha"} on:pointerdown={onPointerDown} data-alpha-picker>
|
||||
<LayoutCol
|
||||
class="alpha-picker"
|
||||
data-tooltip-label="Alpha"
|
||||
data-tooltip-description={"The level of translucency." + (disabled ? "\n\nDisabled (read-only)." : "")}
|
||||
on:pointerdown={onPointerDown}
|
||||
data-alpha-picker
|
||||
>
|
||||
{#if !isNone}
|
||||
<div class="selection-needle" style:top={`${(1 - alpha) * 100}%`} />
|
||||
{/if}
|
||||
@@ -479,11 +505,11 @@
|
||||
class="choice-preview"
|
||||
classes={{ outlined, transparency }}
|
||||
styles={{ "--outline-amount": outlineFactor }}
|
||||
tooltip={!newColor.equals(oldColor) ? "Comparison between the present color choice (left) and the color before any change was made (right)" : "The present color choice"}
|
||||
tooltipDescription={!newColor.equals(oldColor) ? "Comparison between the present color choice (left) and the color before it was changed (right)." : "The present color choice."}
|
||||
>
|
||||
{#if !newColor.equals(oldColor) && !disabled}
|
||||
<div class="swap-button-background"></div>
|
||||
<IconButton class="swap-button" icon="SwapHorizontal" size={16} action={swapNewWithOld} tooltip="Swap" />
|
||||
<IconButton class="swap-button" icon="SwapHorizontal" size={16} action={swapNewWithOld} tooltipLabel="Swap" />
|
||||
{/if}
|
||||
<LayoutCol class="new-color" classes={{ none: isNone }}>
|
||||
{#if !newColor.equals(oldColor)}
|
||||
@@ -496,9 +522,12 @@
|
||||
</LayoutCol>
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
<!-- <DropdownInput entries={[[{ label: "sRGB" }]]} selectedIndex={0} disabled={true} tooltip="Color model, color space, and HDR (coming soon)" /> -->
|
||||
<!-- <DropdownInput entries={[[{ label: "sRGB" }]]} selectedIndex={0} disabled={true} tooltipDescription="Color model, color space, and HDR (coming soon)." /> -->
|
||||
<LayoutRow>
|
||||
<TextLabel tooltip={"Color code in hexadecimal format. 6 digits if opaque, 8 with alpha.\nAccepts input of CSS color values including named colors."}>Hex</TextLabel>
|
||||
<TextLabel
|
||||
tooltipLabel="Hex Color Code"
|
||||
tooltipDescription={"Color code in hexadecimal format. 6 digits if opaque, 8 with alpha.\nAccepts input of CSS color values including named colors."}>Hex</TextLabel
|
||||
>
|
||||
<Separator type="Related" />
|
||||
<LayoutRow>
|
||||
<TextInput
|
||||
@@ -509,13 +538,14 @@
|
||||
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."}
|
||||
tooltipLabel="Hex Color Code"
|
||||
tooltipDescription={"Color code in hexadecimal format. 6 digits if opaque, 8 with alpha.\nAccepts input of CSS color values including named colors."}
|
||||
bind:this={hexCodeInputWidget}
|
||||
/>
|
||||
</LayoutRow>
|
||||
</LayoutRow>
|
||||
<LayoutRow>
|
||||
<TextLabel tooltip="Red/Green/Blue channels of the color, integers 0–255">RGB</TextLabel>
|
||||
<TextLabel tooltipLabel="Red/Green/Blue" tooltipDescription="Integers 0–255.">RGB</TextLabel>
|
||||
<Separator type="Related" />
|
||||
<LayoutRow>
|
||||
{#each rgbChannels as [channel, strength], index}
|
||||
@@ -535,15 +565,17 @@
|
||||
min={0}
|
||||
max={255}
|
||||
minWidth={1}
|
||||
tooltip={`${{ r: "Red", g: "Green", b: "Blue" }[channel]} channel, integers 0–255`}
|
||||
tooltipLabel={{ r: "Red Channel", g: "Green Channel", b: "Blue Channel" }[channel]}
|
||||
tooltipDescription="Integers 0–255."
|
||||
/>
|
||||
{/each}
|
||||
</LayoutRow>
|
||||
</LayoutRow>
|
||||
<LayoutRow>
|
||||
<TextLabel tooltip={"Hue/Saturation/Value, also known as Hue/Saturation/Brightness (HSB).\nNot to be confused with Hue/Saturation/Lightness (HSL), a different color model."}>
|
||||
HSV
|
||||
</TextLabel>
|
||||
<TextLabel
|
||||
tooltipLabel="Hue/Saturation/Value"
|
||||
tooltipDescription="Also known as Hue/Saturation/Brightness (HSB). Not to be confused with Hue/Saturation/Lightness (HSL), a different color model.">HSV</TextLabel
|
||||
>
|
||||
<Separator type="Related" />
|
||||
<LayoutRow>
|
||||
{#each hsvChannels as [channel, strength], index}
|
||||
@@ -565,17 +597,22 @@
|
||||
unit={channel === "h" ? "°" : "%"}
|
||||
minWidth={1}
|
||||
displayDecimalPlaces={1}
|
||||
tooltip={{
|
||||
h: `Hue component, the shade along the spectrum of the rainbow`,
|
||||
s: `Saturation component, the vividness from grayscale to full color`,
|
||||
v: "Value component, the brightness from black to full color",
|
||||
tooltipLabel={{
|
||||
h: "Hue Component",
|
||||
s: "Saturation Component",
|
||||
v: "Value Component",
|
||||
}[channel]}
|
||||
tooltipDescription={{
|
||||
h: "The shade along the spectrum of the rainbow.",
|
||||
s: "The vividness from grayscale to full color.",
|
||||
v: "The brightness from black to full color.",
|
||||
}[channel]}
|
||||
/>
|
||||
{/each}
|
||||
</LayoutRow>
|
||||
</LayoutRow>
|
||||
<LayoutRow>
|
||||
<TextLabel tooltip="Scale of translucency, from transparent (0%) to opaque (100%), for the color's alpha channel">Alpha</TextLabel>
|
||||
<TextLabel tooltipLabel="Alpha" tooltipDescription="The level of translucency, from transparent (0%) to opaque (100%).">Alpha</TextLabel>
|
||||
<Separator type="Related" />
|
||||
<NumberInput
|
||||
value={!isNone ? alpha * 100 : undefined}
|
||||
@@ -594,29 +631,54 @@
|
||||
unit="%"
|
||||
mode="Range"
|
||||
displayDecimalPlaces={1}
|
||||
tooltip="Scale of translucency, from transparent (0%) to opaque (100%), for the color's alpha channel"
|
||||
tooltipLabel="Alpha"
|
||||
tooltipDescription="The level of translucency, from transparent (0%) to opaque (100%)."
|
||||
/>
|
||||
</LayoutRow>
|
||||
<LayoutRow class="leftover-space" />
|
||||
<LayoutRow>
|
||||
{#if allowNone && !gradient}
|
||||
<button class="preset-color none" {disabled} on:click={() => setColorPreset("none")} title="Set to no color" tabindex="0"></button>
|
||||
<button
|
||||
class="preset-color none"
|
||||
{disabled}
|
||||
on:click={() => setColorPreset("none")}
|
||||
data-tooltip-label="Set to No Color"
|
||||
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
||||
tabindex="0"
|
||||
></button>
|
||||
<Separator type="Related" />
|
||||
{/if}
|
||||
<button class="preset-color black" {disabled} on:click={() => setColorPreset("black")} title="Set to black" tabindex="0"></button>
|
||||
<button
|
||||
class="preset-color black"
|
||||
{disabled}
|
||||
on:click={() => setColorPreset("black")}
|
||||
data-tooltip-label="Set to Black"
|
||||
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
||||
tabindex="0"
|
||||
></button>
|
||||
<Separator type="Related" />
|
||||
<button class="preset-color white" {disabled} on:click={() => setColorPreset("white")} title="Set to white" tabindex="0"></button>
|
||||
<button
|
||||
class="preset-color white"
|
||||
{disabled}
|
||||
on:click={() => setColorPreset("white")}
|
||||
data-tooltip-label="Set to White"
|
||||
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
||||
tabindex="0"
|
||||
></button>
|
||||
<Separator type="Related" />
|
||||
<button class="preset-color pure" {disabled} on:click={setColorPresetSubtile} tabindex="-1">
|
||||
<div data-pure-tile="red" style="--pure-color: #ff0000; --pure-color-gray: #4c4c4c" title="Set to red" />
|
||||
<div data-pure-tile="yellow" style="--pure-color: #ffff00; --pure-color-gray: #e3e3e3" title="Set to yellow" />
|
||||
<div data-pure-tile="green" style="--pure-color: #00ff00; --pure-color-gray: #969696" title="Set to green" />
|
||||
<div data-pure-tile="cyan" style="--pure-color: #00ffff; --pure-color-gray: #b2b2b2" title="Set to cyan" />
|
||||
<div data-pure-tile="blue" style="--pure-color: #0000ff; --pure-color-gray: #1c1c1c" title="Set to blue" />
|
||||
<div data-pure-tile="magenta" style="--pure-color: #ff00ff; --pure-color-gray: #696969" title="Set to magenta" />
|
||||
{#each PURE_COLORS_GRAYABLE as [name, color, gray]}
|
||||
<div
|
||||
data-pure-tile={name.toLowerCase()}
|
||||
style:--pure-color={color}
|
||||
style:--pure-color-gray={gray}
|
||||
data-tooltip-label="Set to Red"
|
||||
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
||||
/>
|
||||
{/each}
|
||||
</button>
|
||||
<Separator type="Related" />
|
||||
<IconButton icon="Eyedropper" size={24} {disabled} action={activateEyedropperSample} tooltip="Sample a pixel color from the document" />
|
||||
<IconButton icon="Eyedropper" size={24} {disabled} action={activateEyedropperSample} tooltipLabel="Eyedropper" tooltipDescription="Sample a pixel color from the document." />
|
||||
</LayoutRow>
|
||||
</LayoutCol>
|
||||
</LayoutRow>
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
export let interactive = false;
|
||||
export let scrollableY = false;
|
||||
export let virtualScrollingEntryHeight = 0;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
|
||||
// Keep the child references outside of the entries array so as to avoid infinite recursion.
|
||||
let childReferences: MenuList[][] = [];
|
||||
@@ -423,7 +425,9 @@
|
||||
class="row"
|
||||
classes={{ open: isEntryOpen(entry), active: entry.label === highlighted?.label, disabled: Boolean(entry.disabled) }}
|
||||
styles={{ height: virtualScrollingEntryHeight || "20px" }}
|
||||
{tooltip}
|
||||
{tooltipLabel}
|
||||
{tooltipDescription}
|
||||
{tooltipShortcut}
|
||||
on:click={() => !entry.disabled && onEntryClick(entry)}
|
||||
on:pointerenter={() => !entry.disabled && onEntryPointerEnter(entry)}
|
||||
on:pointerleave={() => !entry.disabled && onEntryPointerLeave(entry)}
|
||||
|
||||
@@ -118,7 +118,13 @@
|
||||
<TextLabel>{nodeCategory[0]}</TextLabel>
|
||||
</summary>
|
||||
{#each nodeCategory[1].nodes as nodeType}
|
||||
<TextButton {disabled} label={nodeType.name} tooltip={$nodeGraph.nodeDescriptions.get(nodeType.name)} action={() => dispatch("selectNodeType", nodeType.name)} />
|
||||
<TextButton
|
||||
{disabled}
|
||||
label={nodeType.name}
|
||||
tooltipLabel={nodeType.name}
|
||||
tooltipDescription={$nodeGraph.nodeDescriptions.get(nodeType.name)}
|
||||
action={() => dispatch("selectNodeType", nodeType.name)}
|
||||
/>
|
||||
{/each}
|
||||
</details>
|
||||
{:else}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import type { TooltipState } from "@graphite/state-providers/tooltip";
|
||||
|
||||
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
|
||||
const tooltip = getContext<TooltipState>("tooltip");
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
let self: FloatingMenu | undefined;
|
||||
|
||||
$: label = filterTodo($tooltip.element?.getAttribute("data-tooltip-label")?.trim());
|
||||
$: description = filterTodo($tooltip.element?.getAttribute("data-tooltip-description")?.trim());
|
||||
$: shortcut = filterTodo($tooltip.element?.getAttribute("data-tooltip-shortcut")?.trim());
|
||||
|
||||
// TODO: Once all TODOs are replaced with real text, remove this function
|
||||
function filterTodo(text: string | undefined): string | undefined {
|
||||
if (text?.trim().toUpperCase() === "TODO" && !editor.handle.inDevelopmentMode()) return "";
|
||||
return text;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="tooltip" style:top={`${$tooltip.position.y}px`} style:left={`${$tooltip.position.x}px`}>
|
||||
{#if label || description}
|
||||
<FloatingMenu open={true} type="Tooltip" direction="Bottom" bind:this={self}>
|
||||
{#if label || shortcut}
|
||||
<LayoutRow class="tooltip-header">
|
||||
{#if label}
|
||||
<TextLabel class="tooltip-label">{label}</TextLabel>
|
||||
{/if}
|
||||
{#if shortcut}
|
||||
<TextLabel class="tooltip-shortcut">{shortcut}</TextLabel>
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
{/if}
|
||||
{#if description}
|
||||
<TextLabel class="tooltip-description">{description}</TextLabel>
|
||||
{/if}
|
||||
</FloatingMenu>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss" global>
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
.floating-menu-content {
|
||||
max-width: Min(400px, 50vw);
|
||||
|
||||
.tooltip-header + .tooltip-description {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.text-label {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.text-label + .tooltip-shortcut {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.tooltip-shortcut {
|
||||
color: var(--color-b-lightgray);
|
||||
background: var(--color-3-darkgray);
|
||||
padding: 0 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tooltip-description {
|
||||
color: var(--color-b-lightgray);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" context="module">
|
||||
export type MenuType = "Popover" | "Dropdown" | "Dialog" | "Cursor";
|
||||
export type MenuType = "Popover" | "Tooltip" | "Dropdown" | "Dialog" | "Cursor";
|
||||
|
||||
/// Prevents the escape key from closing the parent floating menu of the given element.
|
||||
/// This works by momentarily setting the `data-escape-does-not-close` attribute on the parent floating menu element.
|
||||
@@ -64,7 +64,6 @@
|
||||
let measuringOngoingGuard = false;
|
||||
let minWidthParentWidth = 0;
|
||||
let pointerStillDown = false;
|
||||
let workspaceBounds = new DOMRect();
|
||||
let floatingMenuBounds = new DOMRect();
|
||||
let floatingMenuContentBounds = new DOMRect();
|
||||
|
||||
@@ -174,34 +173,50 @@
|
||||
function positionAndStyleFloatingMenu() {
|
||||
if (type === "Cursor") return;
|
||||
|
||||
const workspace = document.querySelector("[data-workspace]");
|
||||
|
||||
const floatingMenuContentDiv = floatingMenuContent?.div?.();
|
||||
if (!workspace || !self || !floatingMenuContainer || !floatingMenuContent || !floatingMenuContentDiv) return;
|
||||
if (!self || !floatingMenuContainer || !floatingMenuContent || !floatingMenuContentDiv) return;
|
||||
|
||||
const viewportBounds = document.documentElement.getBoundingClientRect();
|
||||
workspaceBounds = workspace.getBoundingClientRect();
|
||||
const windowBounds = document.documentElement.getBoundingClientRect();
|
||||
floatingMenuBounds = self.getBoundingClientRect();
|
||||
const floatingMenuContainerBounds = floatingMenuContainer.getBoundingClientRect();
|
||||
floatingMenuContentBounds = floatingMenuContentDiv.getBoundingClientRect();
|
||||
|
||||
const inParentFloatingMenu = Boolean(floatingMenuContainer.closest("[data-floating-menu-content]"));
|
||||
const overflowingLeft = floatingMenuContentBounds.left - windowEdgeMargin <= windowBounds.left;
|
||||
const overflowingRight = floatingMenuContentBounds.right + windowEdgeMargin >= windowBounds.right;
|
||||
const overflowingTop = floatingMenuContentBounds.top - windowEdgeMargin <= windowBounds.top;
|
||||
const overflowingBottom = floatingMenuContentBounds.bottom + windowEdgeMargin >= windowBounds.bottom;
|
||||
|
||||
// TODO: Make this work for all types. This is currently limited to tooltips because they're inherently small and transient.
|
||||
// TODO: But on popovers and dropdowns, it's a bit harder to do this right. First we check if it's overflowing and flip the direction to avoid the overflow.
|
||||
// TODO: But once it's flipped, if the position moves and the menu would no longer be overflowing, we're still flipped and thus unable to automatically notice the need to flip back.
|
||||
// TODO: So as a result, once flipped, it stays flipped forever even if the menu spawner element is moved back away from the edge of the window.
|
||||
if (type === "Tooltip") {
|
||||
// Flip direction if overflowing the edge of the window
|
||||
if (direction === "Top" && overflowingTop) direction = "Bottom";
|
||||
else if (direction === "Bottom" && overflowingBottom) direction = "Top";
|
||||
else if (direction === "Left" && overflowingLeft) direction = "Right";
|
||||
else if (direction === "Right" && overflowingRight) direction = "Left";
|
||||
}
|
||||
|
||||
const inParentFloatingMenu = Boolean(floatingMenuContainer.closest("[data-floating-menu-content]"));
|
||||
if (!inParentFloatingMenu) {
|
||||
// Required to correctly position content when scrolled (it has a `position: fixed` to prevent clipping)
|
||||
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
||||
const tailOffset = type === "Popover" ? 10 : 0;
|
||||
let tailOffset = 0;
|
||||
if (type === "Popover") tailOffset = 10;
|
||||
if (type === "Tooltip") tailOffset = direction === "Bottom" ? 20 : 10;
|
||||
|
||||
if (direction === "Bottom") floatingMenuContentDiv.style.top = `${tailOffset + floatingMenuBounds.y}px`;
|
||||
if (direction === "Top") floatingMenuContentDiv.style.bottom = `${tailOffset + (viewportBounds.height - floatingMenuBounds.y)}px`;
|
||||
if (direction === "Top") floatingMenuContentDiv.style.bottom = `${tailOffset + (windowBounds.height - floatingMenuBounds.y)}px`;
|
||||
if (direction === "Right") floatingMenuContentDiv.style.left = `${tailOffset + floatingMenuBounds.x}px`;
|
||||
if (direction === "Left") floatingMenuContentDiv.style.right = `${tailOffset + (viewportBounds.width - floatingMenuBounds.x)}px`;
|
||||
if (direction === "Left") floatingMenuContentDiv.style.right = `${tailOffset + (windowBounds.width - floatingMenuBounds.x)}px`;
|
||||
|
||||
// Required to correctly position tail when scrolled (it has a `position: fixed` to prevent clipping)
|
||||
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
||||
if (tail && direction === "Bottom") tail.style.top = `${floatingMenuBounds.y}px`;
|
||||
if (tail && direction === "Top") tail.style.bottom = `${viewportBounds.height - floatingMenuBounds.y}px`;
|
||||
if (tail && direction === "Top") tail.style.bottom = `${windowBounds.height - floatingMenuBounds.y}px`;
|
||||
if (tail && direction === "Right") tail.style.left = `${floatingMenuBounds.x}px`;
|
||||
if (tail && direction === "Left") tail.style.right = `${viewportBounds.width - floatingMenuBounds.x}px`;
|
||||
if (tail && direction === "Left") tail.style.right = `${windowBounds.width - floatingMenuBounds.x}px`;
|
||||
}
|
||||
|
||||
type Edge = "Top" | "Bottom" | "Left" | "Right";
|
||||
@@ -212,31 +227,31 @@
|
||||
zeroedBorderVertical = direction === "Top" ? "Bottom" : "Top";
|
||||
|
||||
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
||||
if (floatingMenuContentBounds.left - windowEdgeMargin <= workspaceBounds.left) {
|
||||
if (overflowingLeft) {
|
||||
floatingMenuContentDiv.style.left = `${windowEdgeMargin}px`;
|
||||
if (workspaceBounds.left + floatingMenuContainerBounds.left === 12) zeroedBorderHorizontal = "Left";
|
||||
if (windowBounds.left + floatingMenuContainerBounds.left === 12) zeroedBorderHorizontal = "Left";
|
||||
}
|
||||
if (floatingMenuContentBounds.right + windowEdgeMargin >= workspaceBounds.right) {
|
||||
if (overflowingRight) {
|
||||
floatingMenuContentDiv.style.right = `${windowEdgeMargin}px`;
|
||||
if (workspaceBounds.right - floatingMenuContainerBounds.right === 12) zeroedBorderHorizontal = "Right";
|
||||
if (windowBounds.right - floatingMenuContainerBounds.right === 12) zeroedBorderHorizontal = "Right";
|
||||
}
|
||||
}
|
||||
if (direction === "Left" || direction === "Right") {
|
||||
zeroedBorderHorizontal = direction === "Left" ? "Right" : "Left";
|
||||
|
||||
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
||||
if (floatingMenuContentBounds.top - windowEdgeMargin <= workspaceBounds.top) {
|
||||
if (overflowingTop) {
|
||||
floatingMenuContentDiv.style.top = `${windowEdgeMargin}px`;
|
||||
if (workspaceBounds.top + floatingMenuContainerBounds.top === 12) zeroedBorderVertical = "Top";
|
||||
if (windowBounds.top + floatingMenuContainerBounds.top === 12) zeroedBorderVertical = "Top";
|
||||
}
|
||||
if (floatingMenuContentBounds.bottom + windowEdgeMargin >= workspaceBounds.bottom) {
|
||||
if (overflowingBottom) {
|
||||
floatingMenuContentDiv.style.bottom = `${windowEdgeMargin}px`;
|
||||
if (workspaceBounds.bottom - floatingMenuContainerBounds.bottom === 12) zeroedBorderVertical = "Bottom";
|
||||
if (windowBounds.bottom - floatingMenuContainerBounds.bottom === 12) zeroedBorderVertical = "Bottom";
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the rounded corner from the content where the tail perfectly meets the corner
|
||||
if (type === "Popover" && windowEdgeMargin === 6 && zeroedBorderVertical && zeroedBorderHorizontal) {
|
||||
if (displayTail && windowEdgeMargin === 6 && zeroedBorderVertical && zeroedBorderHorizontal) {
|
||||
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
||||
switch (`${zeroedBorderVertical}${zeroedBorderHorizontal}`) {
|
||||
case "TopLeft":
|
||||
@@ -585,14 +600,18 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.top .tail {
|
||||
&.top .tail,
|
||||
&.topleft .tail,
|
||||
&.topright .tail {
|
||||
border-width: 8px 6px 0 6px;
|
||||
border-color: var(--color-2-mildblack) transparent transparent transparent;
|
||||
margin-left: -6px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
&.bottom .tail {
|
||||
&.bottom .tail,
|
||||
&.bottomleft .tail,
|
||||
&.bottomright .tail {
|
||||
border-width: 0 6px 8px 6px;
|
||||
border-color: transparent transparent var(--color-2-mildblack) transparent;
|
||||
margin-left: -6px;
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
let styleName = "";
|
||||
export { styleName as style };
|
||||
export let styles: Record<string, string | number | undefined> = {};
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
// TODO: Add middle-click drag scrolling
|
||||
export let scrollableX = false;
|
||||
export let scrollableY = false;
|
||||
@@ -26,13 +28,15 @@
|
||||
|
||||
<!-- Excluded events because these require `|passive` or `|nonpassive` modifiers. Use a <div> for these instead: `on:wheel`, `on:touchmove`, `on:touchstart` -->
|
||||
<div
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
data-scrollable-x={scrollableX ? "" : undefined}
|
||||
data-scrollable-y={scrollableY ? "" : undefined}
|
||||
class={`layout-col ${className} ${extraClasses}`.trim()}
|
||||
class:scrollable-x={scrollableX}
|
||||
class:scrollable-y={scrollableY}
|
||||
style={`${styleName} ${extraStyles}`.trim() || undefined}
|
||||
title={tooltip}
|
||||
bind:this={self}
|
||||
on:auxclick
|
||||
on:blur
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
let styleName = "";
|
||||
export { styleName as style };
|
||||
export let styles: Record<string, string | number | undefined> = {};
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
// TODO: Add middle-click drag scrolling
|
||||
export let scrollableX = false;
|
||||
export let scrollableY = false;
|
||||
@@ -26,13 +28,15 @@
|
||||
|
||||
<!-- Excluded events because these require `|passive` or `|nonpassive` modifiers. Use a <div> for these instead: `on:wheel`, `on:touchmove`, `on:touchstart` -->
|
||||
<div
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
data-scrollable-x={scrollableX ? "" : undefined}
|
||||
data-scrollable-y={scrollableY ? "" : undefined}
|
||||
class={`layout-row ${className} ${extraClasses}`.trim()}
|
||||
class:scrollable-x={scrollableX}
|
||||
class:scrollable-y={scrollableY}
|
||||
style={`${styleName} ${extraStyles}`.trim() || undefined}
|
||||
title={tooltip}
|
||||
bind:this={self}
|
||||
on:auxclick
|
||||
on:blur
|
||||
|
||||
@@ -698,7 +698,7 @@
|
||||
.icon-button {
|
||||
margin: 0;
|
||||
|
||||
&[title^="Coming Soon"] {
|
||||
&[data-tooltip-description^="Coming soon."] {
|
||||
opacity: 0.25;
|
||||
transition: opacity 0.1s;
|
||||
|
||||
|
||||
@@ -609,7 +609,6 @@
|
||||
styles={{ "--layer-indent-levels": `${listing.entry.depth - 1}` }}
|
||||
data-layer
|
||||
data-index={index}
|
||||
tooltip={listing.entry.tooltip}
|
||||
on:pointerdown={(e) => layerPointerDown(e, listing)}
|
||||
on:click={(e) => selectLayerWithModifiers(e, listing)}
|
||||
>
|
||||
@@ -618,9 +617,12 @@
|
||||
class="expand-arrow"
|
||||
class:expanded={listing.entry.expanded}
|
||||
disabled={!listing.entry.childrenPresent}
|
||||
title={listing.entry.expanded
|
||||
? "Collapse (Click) / Collapse All (Alt Click)"
|
||||
: `Expand (Click) / Expand All (Alt Click)${listing.entry.ancestorOfSelected ? "\n(A selected layer is contained within)" : ""}`}
|
||||
data-tooltip-label={listing.entry.expanded ? "Collapse (All)" : "Expand (All)"}
|
||||
data-tooltip-description={(listing.entry.expanded
|
||||
? "Hide the layers nested within. (To affect all open descendants, perform the shortcut shown.)"
|
||||
: "Show the layers nested within. (To affect all closed descendants, perform the shortcut shown.)") +
|
||||
(listing.entry.ancestorOfSelected && !listing.entry.expanded ? "\n\nNote: a selected layer is currently contained within.\n" : "")}
|
||||
data-tooltip-shortcut="Alt Click"
|
||||
on:click={(e) => handleExpandArrowClickWithModifiers(e, listing.entry.id)}
|
||||
tabindex="0"
|
||||
></button>
|
||||
@@ -628,7 +630,12 @@
|
||||
<div class="expand-arrow-none"></div>
|
||||
{/if}
|
||||
{#if listing.entry.clipped}
|
||||
<IconLabel icon="Clipped" class="clipped-arrow" tooltip="Clipping mask is active (Alt-click border to release)" />
|
||||
<IconLabel
|
||||
icon="Clipped"
|
||||
class="clipped-arrow"
|
||||
tooltipDescription="Clipping mask is active. To release it, perform the shortcut on the layer border."
|
||||
tooltipShortcut="Alt Click"
|
||||
/>
|
||||
{/if}
|
||||
<div class="thumbnail">
|
||||
{#if $nodeGraph.thumbnails.has(listing.entry.id)}
|
||||
@@ -659,7 +666,8 @@
|
||||
size={24}
|
||||
icon={listing.entry.unlocked ? "PadlockUnlocked" : "PadlockLocked"}
|
||||
hoverIcon={listing.entry.unlocked ? "PadlockLocked" : "PadlockUnlocked"}
|
||||
tooltip={(listing.entry.unlocked ? "Lock" : "Unlock") + (!listing.entry.parentsUnlocked ? "\n(A parent of this layer is locked and that status is being inherited)" : "")}
|
||||
tooltipLabel={listing.entry.unlocked ? "Lock" : "Unlock"}
|
||||
tooltipDescription={!listing.entry.parentsUnlocked ? "A parent of this layer is locked and that status is being inherited." : ""}
|
||||
/>
|
||||
{/if}
|
||||
<IconButton
|
||||
@@ -669,7 +677,8 @@
|
||||
size={24}
|
||||
icon={listing.entry.visible ? "EyeVisible" : "EyeHidden"}
|
||||
hoverIcon={listing.entry.visible ? "EyeHide" : "EyeShow"}
|
||||
tooltip={(listing.entry.visible ? "Hide" : "Show") + (!listing.entry.parentsVisible ? "\n(A parent of this layer is hidden and that status is being inherited)" : "")}
|
||||
tooltipLabel={listing.entry.visible ? "Hide" : "Show"}
|
||||
tooltipDescription={!listing.entry.parentsVisible ? "A parent of this layer is hidden and that status is being inherited." : ""}
|
||||
/>
|
||||
</LayoutRow>
|
||||
{/each}
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
return `M-2,-2 L${nodeWidth + 2},-2 L${nodeWidth + 2},${nodeHeight + 2} L-2,${nodeHeight + 2}z ${rectangles.join(" ")}`;
|
||||
}
|
||||
|
||||
function dataTypeTooltip(value: FrontendGraphInput | FrontendGraphOutput): string {
|
||||
function dataTypeTooltipLabel(value: FrontendGraphInput | FrontendGraphOutput): string {
|
||||
return `Data Type: ${value.resolvedType}`;
|
||||
}
|
||||
|
||||
@@ -174,13 +174,11 @@
|
||||
}
|
||||
|
||||
function outputConnectedToText(output: FrontendGraphOutput): string {
|
||||
if (output.connectedTo.length === 0) return "Connected to nothing";
|
||||
|
||||
return `Connected to:\n${output.connectedTo.join("\n")}`;
|
||||
return editor.handle.inDevelopmentMode() ? output.connectedTo.join("\n") : "";
|
||||
}
|
||||
|
||||
function inputConnectedToText(input: FrontendGraphInput): string {
|
||||
return `Connected to:\n${input.connectedTo}`;
|
||||
return editor.handle.inDevelopmentMode() ? input.connectedTo : "";
|
||||
}
|
||||
|
||||
function zipWithUndefined(arr1: FrontendGraphInput[], arr2: FrontendGraphOutput[]) {
|
||||
@@ -310,13 +308,14 @@
|
||||
viewBox="0 0 8 8"
|
||||
class="connector"
|
||||
data-connector="output"
|
||||
data-tooltip-label={dataTypeTooltipLabel(frontendOutput)}
|
||||
data-tooltip-description={outputConnectedToText(frontendOutput)}
|
||||
data-datatype={frontendOutput.dataType}
|
||||
style:--data-color={`var(--color-data-${frontendOutput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${frontendOutput.dataType.toLowerCase()}-dim)`}
|
||||
style:--offset-left={($nodeGraph.updateImportsExports.importPosition.x - 8) / 24}
|
||||
style:--offset-top={($nodeGraph.updateImportsExports.importPosition.y - 8) / 24 + index}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(frontendOutput)}\n\n${outputConnectedToText(frontendOutput)}`}</title>
|
||||
{#if frontendOutput.connectedTo.length > 0}
|
||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
|
||||
{:else}
|
||||
@@ -360,7 +359,7 @@
|
||||
}}
|
||||
/>
|
||||
{#if index > 0}
|
||||
<div class="reorder-drag-grip" title="Reorder this export" />
|
||||
<div class="reorder-drag-grip" data-tooltip-description="Reorder this export" />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -382,14 +381,15 @@
|
||||
viewBox="0 0 8 8"
|
||||
class="connector"
|
||||
data-connector="input"
|
||||
data-tooltip-label={dataTypeTooltipLabel(frontendInput)}
|
||||
data-tooltip-description={inputConnectedToText(frontendInput)}
|
||||
data-datatype={frontendInput.dataType}
|
||||
style:--data-color={`var(--color-data-${frontendInput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${frontendInput.dataType.toLowerCase()}-dim)`}
|
||||
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition.x - 8) / 24}
|
||||
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition.y - 8) / 24 + index}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(frontendInput)}\n\n${inputConnectedToText(frontendInput)}`}</title>
|
||||
{#if frontendInput.connectedTo !== "nothing"}
|
||||
{#if frontendInput.connectedTo !== "Connected to nothing."}
|
||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
|
||||
{:else}
|
||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
|
||||
@@ -406,7 +406,7 @@
|
||||
>
|
||||
{#if (hoveringExportIndex === index || editingNameExportIndex === index) && $nodeGraph.updateImportsExports.addImportExport}
|
||||
{#if index > 0}
|
||||
<div class="reorder-drag-grip" title="Reorder this export" />
|
||||
<div class="reorder-drag-grip" data-tooltip-description="Reorder this export" />
|
||||
{/if}
|
||||
<IconButton
|
||||
size={16}
|
||||
@@ -505,7 +505,10 @@
|
||||
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
||||
style:--layer-area-width={layerAreaWidth}
|
||||
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
|
||||
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}, Position: (${node.position.x}, ${node.position.y})` : "")}
|
||||
data-tooltip-label={node.displayName === node.reference ? node.displayName : `${node.displayName} (${node.reference})`}
|
||||
data-tooltip-description={`
|
||||
${(description || "").trim()}${editor.handle.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position.x}, ${node.position.y}).` : ""}
|
||||
`.trim()}
|
||||
data-node={node.id}
|
||||
>
|
||||
<div class="thumbnail">
|
||||
@@ -519,11 +522,12 @@
|
||||
viewBox="0 0 8 12"
|
||||
class="connector top"
|
||||
data-connector="output"
|
||||
data-tooltip-label={dataTypeTooltipLabel(node.primaryOutput)}
|
||||
data-tooltip-description={outputConnectedToText(node.primaryOutput)}
|
||||
data-datatype={node.primaryOutput.dataType}
|
||||
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
|
||||
{#if node.primaryOutput.connectedTo.length > 0}
|
||||
<path d="M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z" fill="var(--data-color)" />
|
||||
{#if node.primaryOutputConnectedToLayer}
|
||||
@@ -540,14 +544,13 @@
|
||||
viewBox="0 0 8 12"
|
||||
class="connector bottom"
|
||||
data-connector="input"
|
||||
data-tooltip-label={node.primaryInput ? dataTypeTooltipLabel(node.primaryInput) : ""}
|
||||
data-tooltip-description={node.primaryInput ? `${validTypesText(node.primaryInput).trim()}\n\n${inputConnectedToText(node.primaryInput)}` : ""}
|
||||
data-datatype={node.primaryInput?.dataType}
|
||||
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
|
||||
>
|
||||
{#if node.primaryInput}
|
||||
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
|
||||
{/if}
|
||||
{#if node.primaryInput?.connectedTo !== "nothing"}
|
||||
{#if node.primaryInput?.connectedTo !== "Connected to nothing."}
|
||||
<path d="M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z" fill="var(--data-color)" />
|
||||
{#if node.primaryInputConnectedToLayer}
|
||||
<path d="M0,10.95l2.52,-1.69c0.89,-0.6,2.06,-0.6,2.96,0l2.52,1.69v5.05h-8v-5.05z" fill="var(--data-color-dim)" />
|
||||
@@ -564,12 +567,13 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 8 8"
|
||||
class="connector"
|
||||
data-tooltip-label={dataTypeTooltipLabel(stackDataInput)}
|
||||
data-tooltip-description={`${validTypesText(stackDataInput).trim()}\n\n${inputConnectedToText(stackDataInput)}`}
|
||||
data-connector="input"
|
||||
data-datatype={stackDataInput.dataType}
|
||||
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(stackDataInput)}\n\n${validTypesText(stackDataInput)}\n\n${inputConnectedToText(stackDataInput)}`}</title>
|
||||
{#if stackDataInput.connectedTo !== undefined}
|
||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
|
||||
{:else}
|
||||
@@ -582,7 +586,7 @@
|
||||
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
|
||||
<TextLabel>{node.displayName}</TextLabel>
|
||||
</div>
|
||||
<div class="solo-drag-grip" title="Drag only this layer without pushing others outside the stack"></div>
|
||||
<div class="solo-drag-grip" data-tooltip-description="Drag only this layer without pushing others outside the stack"></div>
|
||||
<IconButton
|
||||
class="visibility"
|
||||
data-visibility-button
|
||||
@@ -591,7 +595,7 @@
|
||||
action={() => {
|
||||
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
||||
}}
|
||||
tooltip={node.visible ? "Visible" : "Hidden"}
|
||||
tooltipLabel={node.visible ? "Visible" : "Hidden"}
|
||||
/>
|
||||
|
||||
<svg class="border-mask" width="0" height="0">
|
||||
@@ -650,7 +654,10 @@
|
||||
style:--clip-path-id={`url(#${clipPathId})`}
|
||||
style:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
||||
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}, Position: (${node.position.x}, ${node.position.y})` : "")}
|
||||
data-tooltip-label={node.displayName === node.reference ? node.displayName : `${node.displayName} (${node.reference})`}
|
||||
data-tooltip-description={`
|
||||
${(description || "").trim()}${editor.handle.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position.x}, ${node.position.y}).` : ""}
|
||||
`.trim()}
|
||||
data-node={node.id}
|
||||
>
|
||||
<!-- Primary row -->
|
||||
@@ -664,7 +671,7 @@
|
||||
<div class="secondary" class:in-selected-network={$nodeGraph.inSelectedNetwork}>
|
||||
{#each exposedInputsOutputs as [input, output]}
|
||||
<div class={`secondary-row expanded ${input !== undefined ? "input" : "output"}`}>
|
||||
<TextLabel tooltip={(input !== undefined ? `${input.name}\n\n${input.description}` : `${output.name}\n\n${output.description}`).trim()}>
|
||||
<TextLabel tooltipLabel={input !== undefined ? input.name : output.name} tooltipDescription={input !== undefined ? input.description : output.description}>
|
||||
{input !== undefined ? input.name : output.name}
|
||||
</TextLabel>
|
||||
</div>
|
||||
@@ -679,11 +686,12 @@
|
||||
viewBox="0 0 8 8"
|
||||
class="connector primary-connector"
|
||||
data-connector="input"
|
||||
data-tooltip-label={dataTypeTooltipLabel(node.primaryInput)}
|
||||
data-tooltip-description={`${validTypesText(node.primaryInput).trim()}\n\n${inputConnectedToText(node.primaryInput)}`}
|
||||
data-datatype={node.primaryInput?.dataType}
|
||||
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
|
||||
{#if node.primaryInput.connectedTo !== undefined}
|
||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
|
||||
{:else}
|
||||
@@ -698,11 +706,12 @@
|
||||
viewBox="0 0 8 8"
|
||||
class="connector"
|
||||
data-connector="input"
|
||||
data-tooltip-label={dataTypeTooltipLabel(secondary)}
|
||||
data-tooltip-description={`${validTypesText(secondary).trim()}\n\n${inputConnectedToText(secondary)}`}
|
||||
data-datatype={secondary.dataType}
|
||||
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(secondary)}\n\n${validTypesText(secondary)}\n\n${inputConnectedToText(secondary)}`}</title>
|
||||
{#if secondary.connectedTo !== undefined}
|
||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
|
||||
{:else}
|
||||
@@ -720,11 +729,12 @@
|
||||
viewBox="0 0 8 8"
|
||||
class="connector primary-connector"
|
||||
data-connector="output"
|
||||
data-tooltip-label={dataTypeTooltipLabel(node.primaryOutput)}
|
||||
data-tooltip-description={`${outputConnectedToText(node.primaryOutput)}`}
|
||||
data-datatype={node.primaryOutput.dataType}
|
||||
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
|
||||
{#if node.primaryOutput.connectedTo !== undefined}
|
||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
|
||||
{:else}
|
||||
@@ -738,11 +748,12 @@
|
||||
viewBox="0 0 8 8"
|
||||
class="connector"
|
||||
data-connector="output"
|
||||
data-tooltip-label={dataTypeTooltipLabel(secondary)}
|
||||
data-tooltip-description={`${outputConnectedToText(secondary)}`}
|
||||
data-datatype={secondary.dataType}
|
||||
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(secondary)}\n\n${outputConnectedToText(secondary)}`}</title>
|
||||
{#if secondary.connectedTo !== undefined}
|
||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
|
||||
{:else}
|
||||
@@ -957,6 +968,7 @@
|
||||
.imports-and-exports {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
// Keeps the connectors above the wires
|
||||
z-index: 1;
|
||||
@@ -1054,8 +1066,14 @@
|
||||
|
||||
.layers-and-nodes {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
// Zero specificity with `:where()` to allow other rules to override `pointer-events`
|
||||
:where(& > *) {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.layer,
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
<LayoutCol class={`widget-section ${className}`.trim()} {classes}>
|
||||
<button class="header" class:expanded on:click|stopPropagation={() => (expanded = !expanded)} tabindex="0">
|
||||
<div class="expand-arrow" />
|
||||
<TextLabel tooltip={widgetData.description} bold={true}>{widgetData.name}</TextLabel>
|
||||
<TextLabel tooltipLabel={widgetData.name} tooltipDescription={widgetData.description} bold={true}>{widgetData.name}</TextLabel>
|
||||
<IconButton
|
||||
icon={widgetData.pinned ? "PinActive" : "PinInactive"}
|
||||
tooltip={widgetData.pinned ? "Unpin this node so it's no longer shown here when nothing is selected" : "Pin this node so it's shown here when nothing is selected"}
|
||||
tooltipDescription={widgetData.pinned ? "Unpin this node so it's no longer shown here when nothing is selected." : "Pin this node so it's shown here when nothing is selected."}
|
||||
size={24}
|
||||
action={(e) => {
|
||||
editor.handle.setNodePinned(widgetData.id, !widgetData.pinned);
|
||||
@@ -39,7 +39,7 @@
|
||||
/>
|
||||
<IconButton
|
||||
icon="Trash"
|
||||
tooltip="Delete this node from the layer chain"
|
||||
tooltipDescription="Delete this node from the layer chain."
|
||||
size={24}
|
||||
action={(e) => {
|
||||
editor.handle.deleteNode(widgetData.id);
|
||||
@@ -50,7 +50,7 @@
|
||||
<IconButton
|
||||
icon={widgetData.visible ? "EyeVisible" : "EyeHidden"}
|
||||
hoverIcon={widgetData.visible ? "EyeHide" : "EyeShow"}
|
||||
tooltip={widgetData.visible ? "Hide this node" : "Show this node"}
|
||||
tooltipDescription={widgetData.visible ? "Hide this node." : "Show this node."}
|
||||
size={24}
|
||||
action={(e) => {
|
||||
editor.handle.toggleNodeVisibilityLayerPanel(widgetData.id);
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
|
||||
export let labels: string[];
|
||||
export let disabled = false;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
// Callbacks
|
||||
export let action: (index: number) => void;
|
||||
</script>
|
||||
|
||||
<LayoutRow class="breadcrumb-trail-buttons" {tooltip}>
|
||||
<LayoutRow class="breadcrumb-trail-buttons" {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
|
||||
{#each labels as label, index}
|
||||
<TextButton {label} emphasized={index === labels.length - 1} {disabled} action={() => !disabled && index !== labels.length - 1 && action(index)} />
|
||||
{/each}
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
export let size: IconSize;
|
||||
export let disabled = false;
|
||||
export let active = false;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
// Callbacks
|
||||
export let action: (e?: MouseEvent) => void;
|
||||
|
||||
@@ -28,7 +30,9 @@
|
||||
class:active
|
||||
on:click={action}
|
||||
{disabled}
|
||||
title={tooltip}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
tabindex={active ? -1 : 0}
|
||||
{...$$restProps}
|
||||
>
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
export let image: string;
|
||||
export let width: string | undefined;
|
||||
export let height: string | undefined;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
// Callbacks
|
||||
export let action: (e?: MouseEvent) => void;
|
||||
|
||||
@@ -17,7 +19,17 @@
|
||||
.join(" ");
|
||||
</script>
|
||||
|
||||
<img src={IMAGE_BASE64_STRINGS[image]} style:width style:height class={`image-button ${className} ${extraClasses}`.trim()} title={tooltip} alt="" on:click={action} />
|
||||
<img
|
||||
src={IMAGE_BASE64_STRINGS[image]}
|
||||
style:width
|
||||
style:height
|
||||
class={`image-button ${className} ${extraClasses}`.trim()}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
alt=""
|
||||
on:click={action}
|
||||
/>
|
||||
|
||||
<style lang="scss" global>
|
||||
.image-button {
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
|
||||
export let exposed: boolean;
|
||||
export let dataType: FrontendGraphDataType;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
// Callbacks
|
||||
export let action: (e?: MouseEvent) => void;
|
||||
</script>
|
||||
@@ -16,7 +18,9 @@
|
||||
style:--data-type-color={`var(--color-data-${dataType.toLowerCase()})`}
|
||||
style:--data-type-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
|
||||
on:click={action}
|
||||
title={tooltip}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
tabindex="-1"
|
||||
>
|
||||
{#if !exposed}
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
export let style: PopoverButtonStyle = "DropdownArrow";
|
||||
export let menuDirection: MenuDirection = "Bottom";
|
||||
export let icon: IconName | undefined = undefined;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let disabled = false;
|
||||
export let popoverMinWidth = 1;
|
||||
|
||||
@@ -27,9 +29,20 @@
|
||||
</script>
|
||||
|
||||
<LayoutRow class="popover-button" classes={{ "has-icon": icon !== undefined, "direction-top": menuDirection === "Top" }}>
|
||||
<IconButton class="dropdown-icon" classes={{ open }} {disabled} action={() => onClick()} icon={style || "DropdownArrow"} size={16} {tooltip} data-floating-menu-spawner />
|
||||
<IconButton
|
||||
class="dropdown-icon"
|
||||
classes={{ open }}
|
||||
{disabled}
|
||||
action={() => onClick()}
|
||||
icon={style || "DropdownArrow"}
|
||||
size={16}
|
||||
{tooltipLabel}
|
||||
{tooltipDescription}
|
||||
{tooltipShortcut}
|
||||
data-floating-menu-spawner
|
||||
/>
|
||||
{#if icon !== undefined}
|
||||
<IconLabel class="descriptive-icon" classes={{ open }} {disabled} {icon} {tooltip} />
|
||||
<IconLabel class="descriptive-icon" classes={{ open }} {disabled} {icon} {tooltipLabel} {tooltipDescription} {tooltipShortcut} />
|
||||
{/if}
|
||||
|
||||
<FloatingMenu {open} on:open={({ detail }) => (open = detail)} minWidth={popoverMinWidth} type="Popover" direction={menuDirection || "Bottom"}>
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
export let minWidth = 0;
|
||||
export let disabled = false;
|
||||
export let narrow = false;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let menuListChildren: MenuListEntry[][] | undefined = undefined;
|
||||
|
||||
// Callbacks
|
||||
@@ -59,7 +61,9 @@
|
||||
class:narrow
|
||||
class:flush
|
||||
style:min-width={minWidth > 0 ? `${minWidth}px` : undefined}
|
||||
title={tooltip}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
data-emphasized={emphasized || undefined}
|
||||
data-disabled={disabled || undefined}
|
||||
data-text-button
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
export let checked = false;
|
||||
export let disabled = false;
|
||||
export let icon: IconName = "Checkmark";
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let forLabel: bigint | undefined = undefined;
|
||||
|
||||
let inputElement: HTMLInputElement | undefined;
|
||||
@@ -46,7 +48,15 @@
|
||||
tabindex={disabled ? -1 : 0}
|
||||
bind:this={inputElement}
|
||||
/>
|
||||
<label class:disabled class:checked for={`checkbox-input-${id}`} on:keydown={(e) => e.key === "Enter" && toggleCheckboxFromLabel(e)} title={tooltip}>
|
||||
<label
|
||||
class:disabled
|
||||
class:checked
|
||||
for={`checkbox-input-${id}`}
|
||||
on:keydown={(e) => e.key === "Enter" && toggleCheckboxFromLabel(e)}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
>
|
||||
<LayoutRow class="checkbox-box">
|
||||
<IconLabel icon={displayIcon} />
|
||||
</LayoutRow>
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
export let allowNone = false;
|
||||
export let menuDirection: MenuDirection = "Bottom";
|
||||
// export let allowTransparency = false; // TODO: Implement
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
|
||||
$: outlineFactor = contrastingOutlineFactor(value, ["--color-1-nearblack", "--color-3-darkgray"], 0.01);
|
||||
$: outlined = outlineFactor > 0.0001;
|
||||
@@ -26,7 +28,7 @@
|
||||
$: transparency = value instanceof Gradient ? value.stops.some((stop) => stop.color.alpha < 1) : value.alpha < 1;
|
||||
</script>
|
||||
|
||||
<LayoutCol class="color-button" classes={{ open, disabled, narrow, none, transparency, outlined, "direction-top": menuDirection === "Top" }} {tooltip}>
|
||||
<LayoutCol class="color-button" classes={{ open, disabled, narrow, none, transparency, outlined, "direction-top": menuDirection === "Top" }} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
|
||||
<button style:--chosen-gradient={chosenGradient} style:--outline-amount={outlineFactor} on:click={() => (open = true)} tabindex="0" data-floating-menu-spawner>
|
||||
<!-- {#if disabled && value instanceof Color && !value.none}
|
||||
<TextLabel>sRGB</TextLabel>
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
export let styles: Record<string, string | number | undefined> = {};
|
||||
export let value: Curve;
|
||||
export let disabled = false;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
|
||||
const GRID_SIZE = 4;
|
||||
|
||||
@@ -77,7 +79,7 @@
|
||||
}
|
||||
|
||||
function handleManipulatorPointerDown(e: PointerEvent, i: number) {
|
||||
// Delete an anchor with RMB or MMB
|
||||
// Delete an anchor with right click or middle click
|
||||
if (e.button > 0 && i > 0 && i < manipulatorsList.length - 1) {
|
||||
draggedNodeIndex = undefined;
|
||||
selectedNodeIndex = undefined;
|
||||
@@ -188,7 +190,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<LayoutRow class="curve-input" classes={{ disabled, ...classes }} style={styleName} {styles} {tooltip}>
|
||||
<LayoutRow class="curve-input" classes={{ disabled, ...classes }} style={styleName} {styles} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
|
||||
<svg viewBox="0 0 1 1" on:pointermove={handlePointerMove} on:pointerup={handlePointerUp}>
|
||||
{#each { length: GRID_SIZE - 1 } as _, i}
|
||||
<path class="grid" d={`M 0 ${(i + 1) / GRID_SIZE} L 1 ${(i + 1) / GRID_SIZE}`} />
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
export let interactive = true;
|
||||
export let disabled = false;
|
||||
export let narrow = false;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let minWidth = 0;
|
||||
export let maxWidth = 0;
|
||||
|
||||
@@ -98,7 +100,9 @@
|
||||
<LayoutRow
|
||||
class="dropdown-box"
|
||||
classes={{ disabled, open }}
|
||||
{tooltip}
|
||||
{tooltipLabel}
|
||||
{tooltipDescription}
|
||||
{tooltipShortcut}
|
||||
on:click={() => !disabled && (open = true)}
|
||||
on:blur={unFocusDropdownBox}
|
||||
tabindex={disabled ? -1 : 0}
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
export let disabled = false;
|
||||
export let narrow = false;
|
||||
export let textarea = false;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let placeholder: string | undefined = undefined;
|
||||
export let hideContextMenu = false;
|
||||
|
||||
@@ -74,7 +76,7 @@
|
||||
</script>
|
||||
|
||||
<!-- This is a base component, extended by others like NumberInput and TextInput. It should not be used directly. -->
|
||||
<LayoutRow class={`field-input ${className}`} classes={{ disabled, narrow, ...classes }} style={styleName} {styles} {tooltip}>
|
||||
<LayoutRow class={`field-input ${className}`} classes={{ disabled, narrow, ...classes }} style={styleName} {styles} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
|
||||
{#if !textarea}
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
export let fontStyle: string;
|
||||
export let isStyle = false;
|
||||
export let disabled = false;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
|
||||
let open = false;
|
||||
let entries: MenuListEntry[] = [];
|
||||
@@ -108,7 +110,9 @@
|
||||
class="dropdown-box"
|
||||
classes={{ disabled }}
|
||||
styles={{ ...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}) }}
|
||||
{tooltip}
|
||||
{tooltipLabel}
|
||||
{tooltipDescription}
|
||||
{tooltipShortcut}
|
||||
tabindex={disabled ? -1 : 0}
|
||||
on:click={toggleOpen}
|
||||
data-floating-menu-spawner
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
|
||||
// Label
|
||||
export let label: string | undefined = undefined;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
|
||||
// Disabled
|
||||
export let disabled = false;
|
||||
@@ -688,7 +690,9 @@
|
||||
{label}
|
||||
{disabled}
|
||||
{narrow}
|
||||
{tooltip}
|
||||
{tooltipLabel}
|
||||
{tooltipDescription}
|
||||
{tooltipShortcut}
|
||||
{styles}
|
||||
hideContextMenu={true}
|
||||
spellcheck={false}
|
||||
|
||||
@@ -27,7 +27,15 @@
|
||||
|
||||
<LayoutRow class="radio-input" classes={{ disabled, narrow, mixed }} styles={{ ...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}) }}>
|
||||
{#each entries as entry, index}
|
||||
<button class:active={!mixed ? index === selectedIndex : undefined} on:click={() => handleEntryClick(entry)} title={entry.tooltip} tabindex={index === selectedIndex ? -1 : 0} {disabled}>
|
||||
<button
|
||||
class:active={!mixed ? index === selectedIndex : undefined}
|
||||
on:click={() => handleEntryClick(entry)}
|
||||
data-tooltip-label={entry.tooltipLabel}
|
||||
data-tooltip-description={entry.tooltipDescription}
|
||||
data-tooltip-shortcut={entry.tooltipShortcut}
|
||||
tabindex={index === selectedIndex ? -1 : 0}
|
||||
{disabled}
|
||||
>
|
||||
{#if entry.icon}
|
||||
<IconLabel icon={entry.icon} />
|
||||
{/if}
|
||||
|
||||
@@ -7,14 +7,16 @@
|
||||
|
||||
export let value: string;
|
||||
export let disabled = false;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
|
||||
function setValue(newValue: ReferencePoint) {
|
||||
dispatch("value", newValue);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="reference-point-input" class:disabled title={tooltip}>
|
||||
<div class="reference-point-input" class:disabled data-tooltip-label={tooltipLabel} data-tooltip-description={tooltipDescription} data-tooltip-shortcut={tooltipShortcut}>
|
||||
<button on:click={() => setValue("TopLeft")} class="row-1 col-1" class:active={value === "TopLeft"} tabindex="-1" {disabled}><div /></button>
|
||||
<button on:click={() => setValue("TopCenter")} class="row-1 col-2" class:active={value === "TopCenter"} tabindex="-1" {disabled}><div /></button>
|
||||
<button on:click={() => setValue("TopRight")} class="row-1 col-3" class:active={value === "TopRight"} tabindex="-1" {disabled}><div /></button>
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
export let disabled = false;
|
||||
export let activeMarkerIndex = 0 as number | undefined;
|
||||
// export let disabled = false;
|
||||
// export let tooltip: string | undefined = undefined;
|
||||
// export let tooltipLabel: string | undefined = undefined;
|
||||
// export let tooltipDescription: string | undefined = undefined;
|
||||
// export let tooltipShortcut: string | undefined = undefined;
|
||||
|
||||
let markerTrack: LayoutRow | undefined = undefined;
|
||||
let positionRestore: number | undefined = undefined;
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
export let value: string;
|
||||
export let label: string | undefined = undefined;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let disabled = false;
|
||||
|
||||
let self: FieldInput | undefined;
|
||||
@@ -55,7 +57,9 @@
|
||||
spellcheck={true}
|
||||
{label}
|
||||
{disabled}
|
||||
{tooltip}
|
||||
{tooltipLabel}
|
||||
{tooltipDescription}
|
||||
{tooltipShortcut}
|
||||
bind:this={self}
|
||||
/>
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
// Label
|
||||
export let label: string | undefined = undefined;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let placeholder: string | undefined = undefined;
|
||||
// Disabled
|
||||
export let disabled = false;
|
||||
@@ -79,7 +81,9 @@
|
||||
{label}
|
||||
{disabled}
|
||||
{narrow}
|
||||
{tooltip}
|
||||
{tooltipLabel}
|
||||
{tooltipDescription}
|
||||
{tooltipShortcut}
|
||||
{placeholder}
|
||||
bind:this={self}
|
||||
/>
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
export let icon: IconName;
|
||||
export let iconSizeOverride: number | undefined = undefined;
|
||||
export let disabled = false;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
|
||||
$: iconSizeClass = ((icon: IconName) => {
|
||||
const iconData = ICONS[icon];
|
||||
@@ -26,7 +28,7 @@
|
||||
.join(" ");
|
||||
</script>
|
||||
|
||||
<LayoutRow class={`icon-label ${iconSizeClass} ${className} ${extraClasses}`.trim()} classes={{ disabled }} {tooltip}>
|
||||
<LayoutRow class={`icon-label ${iconSizeClass} ${className} ${extraClasses}`.trim()} classes={{ disabled }} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
|
||||
{@html ICON_SVG_STRINGS[icon] || "�"}
|
||||
</LayoutRow>
|
||||
|
||||
|
||||
@@ -6,14 +6,25 @@
|
||||
export let url: string;
|
||||
export let width: string | undefined;
|
||||
export let height: string | undefined;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
|
||||
$: extraClasses = Object.entries(classes)
|
||||
.flatMap(([className, stateName]) => (stateName ? [className] : []))
|
||||
.join(" ");
|
||||
</script>
|
||||
|
||||
<img src={url} style:width style:height class={`image-label ${className} ${extraClasses}`.trim()} title={tooltip} alt="" />
|
||||
<img
|
||||
src={url}
|
||||
style:width
|
||||
style:height
|
||||
class={`image-label ${className} ${extraClasses}`.trim()}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<style lang="scss" global>
|
||||
.image-label {
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
export let tableAlign = false;
|
||||
export let minWidth = "";
|
||||
export let multiline = false;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let forCheckbox: bigint | undefined = undefined;
|
||||
|
||||
$: extraClasses = Object.entries(classes)
|
||||
@@ -37,7 +39,9 @@
|
||||
class:table-align={tableAlign}
|
||||
style:min-width={minWidth || undefined}
|
||||
style={`${styleName} ${extraStyles}`.trim() || undefined}
|
||||
title={tooltip}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
for={forCheckbox !== undefined ? `checkbox-input-${forCheckbox}` : undefined}
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
$: displayKeyboardLockNotice = requiresLock && !$fullscreen.keyboardLocked;
|
||||
|
||||
function watchKeyboardLockInfoMessage(keyboardLockApiSupported: boolean): string {
|
||||
const RESERVED = "This hotkey is reserved by the browser. ";
|
||||
const RESERVED = "This keyboard shortcut is reserved by the browser.";
|
||||
const USE_FULLSCREEN = "It is made available in fullscreen mode.";
|
||||
const USE_SECURE_CTX = "It is made available in fullscreen mode when this website is served from a secure context (https or localhost).";
|
||||
const SWITCH_BROWSER = "Use a Chromium-based browser (like Chrome or Edge) in fullscreen mode to directly use the shortcut.";
|
||||
@@ -118,7 +118,7 @@
|
||||
</script>
|
||||
|
||||
{#if displayKeyboardLockNotice}
|
||||
<IconLabel class="user-input-label keyboard-lock-notice" icon="Info" tooltip={keyboardLockInfoMessage} />
|
||||
<IconLabel class="user-input-label keyboard-lock-notice" icon="Info" tooltipDescription={keyboardLockInfoMessage} />
|
||||
{:else}
|
||||
<LayoutRow class="user-input-label" classes={{ "text-only": textOnly }}>
|
||||
{#each keysWithLabelsGroups as keysWithLabels, groupIndex}
|
||||
@@ -189,11 +189,13 @@
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
height: 16px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid;
|
||||
border-radius: 4px;
|
||||
border-color: var(--color-5-dullgray);
|
||||
color: var(--color-e-nearwhite);
|
||||
background: var(--color-3-darkgray);
|
||||
color: var(--color-b-lightgray);
|
||||
|
||||
.icon-label {
|
||||
fill: var(--color-b-lightgray);
|
||||
}
|
||||
|
||||
.text-label {
|
||||
// Firefox renders the text 1px lower than Chrome (tested on Windows) with 16px line-height,
|
||||
|
||||
@@ -2,21 +2,31 @@
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { AppWindowState } from "@graphite/state-providers/app-window";
|
||||
import type { DialogState } from "@graphite/state-providers/dialog";
|
||||
import type { TooltipState } from "@graphite/state-providers/tooltip";
|
||||
|
||||
import Dialog from "@graphite/components/floating-menus/Dialog.svelte";
|
||||
import Tooltip from "@graphite/components/floating-menus/Tooltip.svelte";
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import StatusBar from "@graphite/components/window/status-bar/StatusBar.svelte";
|
||||
import TitleBar from "@graphite/components/window/title-bar/TitleBar.svelte";
|
||||
import Workspace from "@graphite/components/window/workspace/Workspace.svelte";
|
||||
|
||||
const dialog = getContext<DialogState>("dialog");
|
||||
const tooltip = getContext<TooltipState>("tooltip");
|
||||
const appWindow = getContext<AppWindowState>("appWindow");
|
||||
</script>
|
||||
|
||||
<LayoutCol class="main-window" classes={{ "viewport-hole-punch": $appWindow.viewportHolePunch }}>
|
||||
<TitleBar />
|
||||
|
||||
<Workspace />
|
||||
|
||||
<StatusBar />
|
||||
{#if $dialog.visible}
|
||||
<Dialog />
|
||||
{/if}
|
||||
{#if $tooltip.visible}
|
||||
<Tooltip />
|
||||
{/if}
|
||||
</LayoutCol>
|
||||
|
||||
<style lang="scss" global>
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
const editor = getContext<Editor>("editor");
|
||||
</script>
|
||||
|
||||
<LayoutRow class="window-button linux" tooltip="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
|
||||
<LayoutRow class="window-button linux" tooltipLabel="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
|
||||
<IconLabel icon="WindowButtonWinMinimize" />
|
||||
</LayoutRow>
|
||||
<LayoutRow class="window-button linux" tooltip={$appWindow.maximized ? "Unmaximize" : "Maximize"} on:click={() => editor.handle.appWindowMaximize()}>
|
||||
<LayoutRow class="window-button linux" tooltipLabel={$appWindow.maximized ? "Unmaximize" : "Maximize"} on:click={() => editor.handle.appWindowMaximize()}>
|
||||
<IconLabel icon={$appWindow.maximized ? "WindowButtonWinRestoreDown" : "WindowButtonWinMaximize"} />
|
||||
</LayoutRow>
|
||||
<LayoutRow class="window-button linux" tooltip="Close" on:click={() => editor.handle.appWindowClose()}>
|
||||
<LayoutRow class="window-button linux" tooltipLabel="Close" on:click={() => editor.handle.appWindowClose()}>
|
||||
<IconLabel icon="WindowButtonWinClose" />
|
||||
</LayoutRow>
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
const fullscreen = getContext<FullscreenState>("fullscreen");
|
||||
|
||||
$: requestFullscreenHotkeys = $fullscreen.keyboardLockApiSupported && !$fullscreen.keyboardLocked;
|
||||
|
||||
async function handleClick() {
|
||||
if ($fullscreen.windowFullscreen) fullscreen.exitFullscreen();
|
||||
else fullscreen.enterFullscreen();
|
||||
@@ -19,8 +17,9 @@
|
||||
<LayoutRow
|
||||
class="window-buttons-web"
|
||||
on:click={handleClick}
|
||||
tooltip={($fullscreen.windowFullscreen ? "Exit Fullscreen (F11)" : "Enter Fullscreen (F11)") +
|
||||
(requestFullscreenHotkeys ? "\n\nThis provides access to hotkeys normally reserved by the browser" : "")}
|
||||
tooltipLabel={$fullscreen.windowFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
|
||||
tooltipDescription={$fullscreen.keyboardLockApiSupported ? "While fullscreen, keyboard shortcuts normally reserved by the browser become available." : ""}
|
||||
tooltipShortcut="F11"
|
||||
>
|
||||
<IconLabel icon={$fullscreen.windowFullscreen ? "FullscreenExit" : "FullscreenEnter"} />
|
||||
</LayoutRow>
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
const editor = getContext<Editor>("editor");
|
||||
</script>
|
||||
|
||||
<LayoutRow class="window-button windows" tooltip="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
|
||||
<LayoutRow class="window-button windows" tooltipLabel="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
|
||||
<IconLabel icon="WindowButtonWinMinimize" />
|
||||
</LayoutRow>
|
||||
<LayoutRow class="window-button windows" tooltip={$appWindow.maximized ? "Restore Down" : "Maximize"} on:click={() => editor.handle.appWindowMaximize()}>
|
||||
<LayoutRow class="window-button windows" tooltipLabel={$appWindow.maximized ? "Restore Down" : "Maximize"} on:click={() => editor.handle.appWindowMaximize()}>
|
||||
<IconLabel icon={$appWindow.maximized ? "WindowButtonWinRestoreDown" : "WindowButtonWinMaximize"} />
|
||||
</LayoutRow>
|
||||
<LayoutRow class="window-button windows" tooltip="Close" on:click={() => editor.handle.appWindowClose()}>
|
||||
<LayoutRow class="window-button windows" tooltipLabel="Close" on:click={() => editor.handle.appWindowClose()}>
|
||||
<IconLabel icon="WindowButtonWinClose" />
|
||||
</LayoutRow>
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
export let tabMinWidths = false;
|
||||
export let tabCloseButtons = false;
|
||||
export let tabLabels: { name: string; unsaved?: boolean; tooltip?: string }[];
|
||||
export let tabLabels: { name: string; unsaved?: boolean; tooltipDescription?: string; tooltipShortcut?: string }[];
|
||||
export let tabActiveIndex: number;
|
||||
export let panelType: PanelType | undefined = undefined;
|
||||
export let clickAction: ((index: number) => void) | undefined = undefined;
|
||||
@@ -118,7 +118,8 @@
|
||||
<LayoutRow
|
||||
class="tab"
|
||||
classes={{ active: tabIndex === tabActiveIndex }}
|
||||
tooltip={tabLabel.tooltip || undefined}
|
||||
tooltipLabel={tabLabel.name}
|
||||
tooltipDescription={tabLabel.tooltipDescription}
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
clickAction?.(tabIndex);
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import type { OpenDocument } from "@graphite/messages";
|
||||
import type { DialogState } from "@graphite/state-providers/dialog";
|
||||
import type { PortfolioState } from "@graphite/state-providers/portfolio";
|
||||
|
||||
import Dialog from "@graphite/components/floating-menus/Dialog.svelte";
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import Panel from "@graphite/components/window/workspace/Panel.svelte";
|
||||
@@ -34,13 +32,12 @@
|
||||
const unsaved = !doc.details.isSaved;
|
||||
if (!editor.handle.inDevelopmentMode()) return { name, unsaved };
|
||||
|
||||
const tooltip = `Document ID: ${doc.id}`;
|
||||
return { name, unsaved, tooltip };
|
||||
const tooltipDescription = `Document ID: ${doc.id}`;
|
||||
return { name, unsaved, tooltipDescription };
|
||||
});
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
const portfolio = getContext<PortfolioState>("portfolio");
|
||||
const dialog = getContext<DialogState>("dialog");
|
||||
|
||||
function resizePanel(e: PointerEvent) {
|
||||
const gutter = (e.target || undefined) as HTMLDivElement | undefined;
|
||||
@@ -175,9 +172,6 @@
|
||||
</LayoutCol>
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
{#if $dialog.visible}
|
||||
<Dialog />
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
|
||||
<style lang="scss" global>
|
||||
|
||||
Reference in New Issue
Block a user