Add grid snapping to graph imports/exports; improve layer panel drag into/between insertion; better preserve graph space on reordering (#1911)

* Fix disconnecting root node when previewing

* Final previewing fixes

* Improve positioning when moving layer to stack

* Improve layer panel

* Import/Export edge grid snapping

* Fix layer ordering and positioning

* Small bug fixes and improvements

* Fix copy and paste position

* Nit

* Align imports/edges when using scrolling

* Fix misaligned exports in demo artwork

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
adamgerhant 2024-08-10 17:30:14 -07:00 committed by GitHub
parent 60707c0369
commit 193f757c45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 494 additions and 190 deletions

View file

@ -514,6 +514,7 @@
handlePosition={scrollbarPos.x}
on:handlePosition={({ detail }) => panCanvasX(detail)}
on:pressTrack={({ detail }) => pageX(detail)}
on:pointerup={() => editor.handle.setGridAlignedEdges()}
/>
</LayoutRow>
</LayoutCol>

View file

@ -21,9 +21,6 @@
entry: LayerPanelEntry;
};
const RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT = 20;
const INSERT_MARK_OFFSET = 2;
type DraggingData = {
select?: () => void;
insertParentId: bigint | undefined;
@ -207,9 +204,6 @@
const treeChildren = tree.div()?.children;
const treeOffset = tree.div()?.getBoundingClientRect().top;
// Closest distance to the middle of the row along the Y axis
let closest = Infinity;
// Folder to insert into
let insertParentId: bigint | undefined = undefined;
let insertDepth = 0;
@ -221,30 +215,26 @@
let highlightFolder = false;
let markerHeight = 0;
let previousHeight: number | undefined = undefined;
if (treeChildren !== undefined && treeOffset !== undefined) {
Array.from(treeChildren).forEach((treeChild, index) => {
const layerPanel = document.querySelector("[data-layer-panel]"); // Selects the element with the data-layer-panel attribute
if (layerPanel !== null && treeChildren !== undefined && treeOffset !== undefined) {
let layerPanelTop = layerPanel.getBoundingClientRect().top;
Array.from(treeChildren).forEach((treeChild) => {
const indexAttribute = treeChild.getAttribute("data-index");
if (!indexAttribute) return;
const { folderIndex, entry: layer } = layers[parseInt(indexAttribute, 10)];
const rect = treeChild.getBoundingClientRect();
const position = rect.top + rect.height / 2;
const distance = position - clientY;
// Inserting above current row
if (distance > 0 && distance < closest) {
insertParentId = layer.parentId;
insertDepth = layer.depth - 1;
insertIndex = folderIndex;
highlightFolder = false;
closest = distance;
markerHeight = previousHeight || treeOffset + INSERT_MARK_OFFSET;
if (rect.top > clientY || rect.bottom < clientY) {
return;
}
// Inserting below current row
else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0) {
if (layer.childrenAllowed) {
const pointerPercentage = (clientY - rect.top) / rect.height;
if (layer.childrenAllowed) {
if (pointerPercentage < 0.25) {
insertParentId = layer.parentId;
insertDepth = layer.depth - 1;
insertIndex = folderIndex;
markerHeight = rect.top - layerPanelTop;
} else if (pointerPercentage < 0.75 || (layer.childrenPresent && layer.expanded)) {
insertParentId = layer.id;
insertDepth = layer.depth;
insertIndex = 0;
@ -253,24 +243,33 @@
insertParentId = layer.parentId;
insertDepth = layer.depth - 1;
insertIndex = folderIndex + 1;
highlightFolder = false;
markerHeight = rect.bottom - layerPanelTop;
}
} else {
if (pointerPercentage < 0.5) {
insertParentId = layer.parentId;
insertDepth = layer.depth - 1;
insertIndex = folderIndex;
markerHeight = rect.top - layerPanelTop;
} else {
insertParentId = layer.parentId;
insertDepth = layer.depth - 1;
insertIndex = folderIndex + 1;
markerHeight = rect.bottom - layerPanelTop;
}
closest = -distance;
markerHeight = index === treeChildren.length - 1 ? rect.bottom - INSERT_MARK_OFFSET : rect.bottom;
}
// Inserting with no nesting at the end of the panel
else if (closest === Infinity) {
if (layer.parentId === undefined) insertIndex = folderIndex + 1;
markerHeight = rect.bottom - INSERT_MARK_OFFSET;
}
previousHeight = rect.bottom;
});
// Dragging to the empty space below all layers
let lastLayer = treeChildren[treeChildren.length - 1];
if (lastLayer.getBoundingClientRect().bottom < clientY) {
const numberRootLayers = layers.filter((layer) => layer.entry.depth === 1).length;
insertParentId = undefined;
insertDepth = 0;
insertIndex = numberRootLayers;
markerHeight = lastLayer.getBoundingClientRect().bottom - layerPanelTop;
}
}
markerHeight -= treeOffset || 0;
return {
select,
insertParentId,
@ -370,7 +369,7 @@
<WidgetLayout layout={layersPanelOptionsLayout} />
</LayoutRow>
<LayoutRow class="list-area" scrollableY={true}>
<LayoutCol class="list" bind:this={list} on:click={() => deselectAllLayers()} on:dragover={(e) => draggable && updateInsertLine(e)} on:dragend={() => draggable && drop()}>
<LayoutCol class="list" data-layer-panel bind:this={list} on:click={() => deselectAllLayers()} on:dragover={(e) => draggable && updateInsertLine(e)} on:dragend={() => draggable && drop()}>
{#each layers as listing, index}
<LayoutRow
class="layer"

View file

@ -449,6 +449,7 @@
<path class="visibility" d={pathString} />
{/each}
<path class="all-nodes-bounding-box" d={$nodeGraph.clickTargets.allNodesBoundingBox} />
<path class="all-nodes-bounding-box" d={$nodeGraph.clickTargets.importExportsBoundingBox} />
</svg>
</div>
{/if}

View file

@ -14,7 +14,7 @@
const pointerPosition = (direction: ScrollbarDirection, e: PointerEvent): number => (direction === "Vertical" ? e.clientY : e.clientX);
const dispatch = createEventDispatcher<{ handlePosition: number; pressTrack: number }>();
const dispatch = createEventDispatcher<{ handlePosition: number; pressTrack: number; pointerup }>();
export let direction: ScrollbarDirection = "Vertical";
export let handlePosition = 0.5;
@ -102,7 +102,7 @@
});
</script>
<div class={`scrollbar-input ${direction.toLowerCase()}`}>
<div class={`scrollbar-input ${direction.toLowerCase()}`} on:pointerup={() => dispatch("pointerup")}>
<button class="arrow decrease" on:pointerdown={() => changePosition(-50)} tabindex="-1" />
<div class="scroll-track" bind:this={scrollTrack} on:pointerdown={grabArea}>
<div class="scroll-thumb" on:pointerdown={grabHandle} class:dragging style:top={thumbTop} style:bottom={thumbBottom} style:left={thumbLeft} style:right={thumbRight} />

View file

@ -175,6 +175,7 @@ export type FrontendClickTargets = {
readonly portClickTargets: string[];
readonly visibilityClickTargets: string[];
readonly allNodesBoundingBox: string;
readonly importExportsBoundingBox: string;
};
export type ContextMenuInformation = {

View file

@ -564,6 +564,13 @@ impl EditorHandle {
self.dispatch(message);
}
/// Snaps the import/export edges to a grid space when the scroll bar is released
#[wasm_bindgen(js_name = setGridAlignedEdges)]
pub fn set_grid_aligned_edges(&self) {
let message = NodeGraphMessage::SetGridAlignedEdges;
self.dispatch(message);
}
/// Creates a new document node in the node graph
#[wasm_bindgen(js_name = createNode)]
pub fn create_node(&self, node_type: String, x: i32, y: i32) {