mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-03 21:08:18 +00:00
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:
parent
60707c0369
commit
193f757c45
16 changed files with 494 additions and 190 deletions
|
@ -514,6 +514,7 @@
|
|||
handlePosition={scrollbarPos.x}
|
||||
on:handlePosition={({ detail }) => panCanvasX(detail)}
|
||||
on:pressTrack={({ detail }) => pageX(detail)}
|
||||
on:pointerup={() => editor.handle.setGridAlignedEdges()}
|
||||
/>
|
||||
</LayoutRow>
|
||||
</LayoutCol>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -175,6 +175,7 @@ export type FrontendClickTargets = {
|
|||
readonly portClickTargets: string[];
|
||||
readonly visibilityClickTargets: string[];
|
||||
readonly allNodesBoundingBox: string;
|
||||
readonly importExportsBoundingBox: string;
|
||||
};
|
||||
|
||||
export type ContextMenuInformation = {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue