Download frontend/src/components/widgets/inputs/CurveInput.svelte from openfree/graphite2: direct link, hf CLI and curl.
- Browser
- Download file 7.69 kB
-
https://huggingface.co/spaces/openfree/graphite2/resolve/main/frontend/src/components/widgets/inputs/CurveInput.svelte
- Command line
-
hf download hf://spaces/openfree/graphite2/frontend/src/components/widgets/inputs/CurveInput.svelte
-
curl -L -o CurveInput.svelte https://huggingface.co/spaces/openfree/graphite2/resolve/main/frontend/src/components/widgets/inputs/CurveInput.svelte
7.69 kB
| <script lang="ts"> | |
| import { createEventDispatcher } from "svelte"; | |
| import type { Curve, CurveManipulatorGroup } from "@graphite/messages"; | |
| import { clamp } from "@graphite/utility-functions/math"; | |
| import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; | |
| const dispatch = createEventDispatcher<{ | |
| value: Curve; | |
| }>(); | |
| export let classes: Record<string, boolean> = {}; | |
| let styleName = ""; | |
| export { styleName as style }; | |
| export let styles: Record<string, string | number | undefined> = {}; | |
| export let value: Curve; | |
| export let disabled = false; | |
| export let tooltip: string | undefined = undefined; | |
| const GRID_SIZE = 4; | |
| let groups: CurveManipulatorGroup[] = [ | |
| { | |
| anchor: [0, 0], | |
| handles: [ | |
| [-1, -1], | |
| [0.25, 0.25], | |
| ], | |
| }, | |
| { | |
| anchor: [0.5, 0.5], | |
| handles: [ | |
| [0.25, 0.25], | |
| [0.75, 0.75], | |
| ], | |
| }, | |
| { | |
| anchor: [1, 1], | |
| handles: [ | |
| [0.75, 0.75], | |
| [2, 2], | |
| ], | |
| }, | |
| ]; | |
| let selectedNodeIndex: number | undefined = undefined; | |
| let draggedNodeIndex: number | undefined = undefined; | |
| let dAttribute = recalculateSvgPath(); | |
| $: { | |
| groups = [groups[0]].concat(value.manipulatorGroups).concat([groups[groups.length - 1]]); | |
| groups[0].handles[1] = value.firstHandle; | |
| groups[groups.length - 1].handles[0] = value.lastHandle; | |
| dAttribute = recalculateSvgPath(); | |
| } | |
| function updateCurve() { | |
| dispatch("value", { | |
| manipulatorGroups: groups.slice(1, groups.length - 1), | |
| firstHandle: groups[0].handles[1], | |
| lastHandle: groups[groups.length - 1].handles[0], | |
| }); | |
| } | |
| function recalculateSvgPath() { | |
| let dAttribute = ""; | |
| let anchor = groups[0].anchor; | |
| let handle = groups[0].handles[1]; | |
| groups.slice(1).forEach((group) => { | |
| dAttribute += `M${anchor[0]} ${1 - anchor[1]} C${handle[0]} ${1 - handle[1]}, ${group.handles[0][0]} ${1 - group.handles[0][1]}, ${group.anchor[0]} ${1 - group.anchor[1]} `; | |
| anchor = group.anchor; | |
| handle = group.handles[1]; | |
| }); | |
| return dAttribute; | |
| } | |
| function handleManipulatorPointerDown(e: PointerEvent, i: number) { | |
| // Delete an anchor with RMB or MMB | |
| if (e.button > 0 && i > 0 && i < groups.length - 1) { | |
| draggedNodeIndex = undefined; | |
| selectedNodeIndex = undefined; | |
| groups.splice(i, 1); | |
| groups = groups; | |
| dAttribute = recalculateSvgPath(); | |
| updateCurve(); | |
| return; | |
| } | |
| draggedNodeIndex = i; | |
| if (i >= 0) selectedNodeIndex = i; | |
| } | |
| function getSvgPositionFromPointerEvent(e: PointerEvent): [number, number] | undefined { | |
| if (!(e.target instanceof SVGElement)) return undefined; | |
| const target = e.target?.closest("svg") || undefined; | |
| if (!target) return undefined; | |
| const rect = target.getBoundingClientRect(); | |
| const x = (e.x - rect.x) / rect.width; | |
| const y = 1 - (e.y - rect.y) / rect.height; | |
| return [clamp(x), clamp(y)]; | |
| } | |
| function clampHandles() { | |
| for (let i = 0; i < groups.length - 1; i++) { | |
| const [min, max] = [groups[i].anchor[0], groups[i + 1].anchor[0]]; | |
| for (let j = 0; j < 2; j++) { | |
| groups[i + j].handles[1 - j][0] = clamp(groups[i + j].handles[1 - j][0], min, max); | |
| groups[i + j].handles[1 - j][1] = clamp(groups[i + j].handles[1 - j][1]); | |
| } | |
| } | |
| } | |
| function handlePointerUp(e: PointerEvent) { | |
| if (draggedNodeIndex !== undefined) { | |
| draggedNodeIndex = undefined; | |
| return; | |
| } | |
| if (e.button !== 0) return; | |
| const anchor = getSvgPositionFromPointerEvent(e); | |
| if (!anchor) return; | |
| let nodeIndex = groups.findIndex((group) => group.anchor[0] > anchor[0]); | |
| if (nodeIndex === -1) nodeIndex = groups.length; | |
| groups.splice(nodeIndex, 0, { | |
| anchor: anchor, | |
| handles: [ | |
| [anchor[0] - 0.05, anchor[1]], | |
| [anchor[0] + 0.05, anchor[1]], | |
| ], | |
| }); | |
| selectedNodeIndex = nodeIndex; | |
| clampHandles(); | |
| dAttribute = recalculateSvgPath(); | |
| updateCurve(); | |
| } | |
| function setHandlePosition(anchorIndex: number, handleIndex: number, position: [number, number]) { | |
| const { anchor, handles } = groups[anchorIndex]; | |
| const otherHandle = handles[1 - handleIndex]; | |
| const handleVector = [anchor[0] - position[0], anchor[1] - position[1]]; | |
| const handleVectorLength = Math.hypot(...handleVector); | |
| const handleVectorNormalized = [handleVector[0] / handleVectorLength, handleVector[1] / handleVectorLength]; | |
| const otherHandleVectorLength = Math.hypot(anchor[0] - otherHandle[0], anchor[1] - otherHandle[1]); | |
| handles[handleIndex] = position; | |
| handles[1 - handleIndex] = [anchor[0] + handleVectorNormalized[0] * otherHandleVectorLength, anchor[1] + handleVectorNormalized[1] * otherHandleVectorLength]; | |
| } | |
| function handlePointerMove(e: PointerEvent) { | |
| if (draggedNodeIndex === undefined || draggedNodeIndex === 0 || draggedNodeIndex === groups.length - 1) return; | |
| const position = getSvgPositionFromPointerEvent(e); | |
| if (!position) return; | |
| if (draggedNodeIndex > 0) { | |
| position[0] = clamp(position[0], groups[draggedNodeIndex - 1].anchor[0], groups[draggedNodeIndex + 1].anchor[0]); | |
| const group = groups[draggedNodeIndex]; | |
| group.handles = [ | |
| [group.handles[0][0] + position[0] - group.anchor[0], group.handles[0][1] + position[1] - group.anchor[1]], | |
| [group.handles[1][0] + position[0] - group.anchor[0], group.handles[1][1] + position[1] - group.anchor[1]], | |
| ]; | |
| group.anchor = position; | |
| } else { | |
| if (selectedNodeIndex === undefined) return; | |
| setHandlePosition(selectedNodeIndex, -draggedNodeIndex - 1, position); | |
| const group = groups[selectedNodeIndex]; | |
| if (group.handles[0][0] > group.anchor[0]) { | |
| group.handles = [group.handles[1], group.handles[0]]; | |
| draggedNodeIndex = -3 - draggedNodeIndex; | |
| } | |
| } | |
| clampHandles(); | |
| dAttribute = recalculateSvgPath(); | |
| updateCurve(); | |
| } | |
| </script> | |
| <LayoutRow class={"curve-input"} classes={{ disabled, ...classes }} style={styleName} {styles} {tooltip}> | |
| <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}`} /> | |
| <path class="grid" d={`M ${(i + 1) / GRID_SIZE} 0 L ${(i + 1) / GRID_SIZE} 1`} /> | |
| {/each} | |
| <path class="curve" d={dAttribute} /> | |
| {#if selectedNodeIndex !== undefined} | |
| {@const group = groups[selectedNodeIndex]} | |
| {#each [0, 1] as i} | |
| <path d={`M ${group.anchor[0]} ${1 - group.anchor[1]} L ${group.handles[i][0]} ${1 - group.handles[i][1]}`} class="handle-line" /> | |
| <circle cx={group.handles[i][0]} cy={1 - group.handles[i][1]} class="manipulator handle" r="0.02" on:pointerdown={(e) => handleManipulatorPointerDown(e, -i - 1)} /> | |
| {/each} | |
| {/if} | |
| {#each groups as group, i} | |
| <circle cx={group.anchor[0]} cy={1 - group.anchor[1]} class="manipulator" r="0.02" on:pointerdown={(e) => handleManipulatorPointerDown(e, i)} /> | |
| {/each} | |
| </svg> | |
| <slot /> | |
| </LayoutRow> | |
| <style lang="scss" global> | |
| .curve-input { | |
| background: var(--color-1-nearblack); | |
| display: flex; | |
| position: relative; | |
| min-width: calc(2 * var(--widget-height)); | |
| max-width: calc(8 * var(--widget-height)); | |
| .grid { | |
| stroke: var(--color-5-dullgray); | |
| stroke-width: 0.005; | |
| pointer-events: none; | |
| } | |
| .curve { | |
| fill: none; | |
| stroke: var(--color-e-nearwhite); | |
| stroke-width: 0.01; | |
| } | |
| .manipulator { | |
| fill: var(--color-1-nearblack); | |
| stroke: var(--color-e-nearwhite); | |
| stroke-width: 0.01; | |
| &:hover { | |
| fill: var(--color-f-white); | |
| stroke: var(--color-f-white); | |
| } | |
| &.handle { | |
| fill: var(--color-1-nearblack); | |
| stroke: var(--color-c-brightgray); | |
| &:hover { | |
| fill: var(--color-a-softgray); | |
| stroke: var(--color-a-softgray); | |
| } | |
| } | |
| } | |
| .handle-line { | |
| stroke: var(--color-5-dullgray); | |
| stroke-width: 0.005; | |
| pointer-events: none; | |
| } | |
| } | |
| </style> | |