Skip to content

Commit 6f88fe4

Browse files
committed
feat: alignment guide
1 parent be84fe5 commit 6f88fe4

3 files changed

Lines changed: 489 additions & 7 deletions

File tree

src/AppDesigner.svelte

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,13 @@
107107
import ZoomRange from '$components/base/input/ZoomRange.svelte';
108108
import DesignerCrosshair from '$components/DesignerCrosshair.svelte';
109109
import DesignerGrid from '$components/DesignerGrid.svelte';
110-
import { FINE_MOVEMENT_DELTA, FINE_MOVEMENT_SHIFT_MULTIPLIER } from '$lib/constants';
110+
import { type AlignmentLine, detectAlignments } from '$lib/alignment';
111+
import {
112+
ALIGNMENT_LINE_COLOR,
113+
ALIGNMENT_LINE_WIDTH,
114+
FINE_MOVEMENT_DELTA,
115+
FINE_MOVEMENT_SHIFT_MULTIPLIER
116+
} from '$lib/constants';
111117
import {
112118
getContextMenuItemForCircle,
113119
modifyCircle,
@@ -277,6 +283,54 @@
277283
/** Hover info tooltip showing element details and keyboard shortcuts */
278284
let hoverInfo: HoverInfo | undefined = $state();
279285
286+
/** Alignment lines to display during element dragging */
287+
let alignmentLines: AlignmentLine[] = $state([]);
288+
289+
/**
290+
* Detects and updates alignment lines for the currently dragged element
291+
*
292+
* Called during ondragmove events to show visual alignment guides.
293+
* Uses the live position from the drag event (not the stored position)
294+
* to provide real-time alignment feedback.
295+
*
296+
* @param event - Konva drag event with current position
297+
* @param draggedElement - The element being dragged (for ID and properties)
298+
*/
299+
const updateAlignmentLines = (
300+
event: KonvaDragTransformEvent,
301+
draggedElement: CircleData | RectangleData | LegData
302+
) => {
303+
// Create a copy of the element with the current drag position
304+
const liveElement = {
305+
...draggedElement,
306+
x: event.target.x(),
307+
y: event.target.y()
308+
};
309+
310+
// Combine all elements except the dragged one
311+
const allOtherElements = [
312+
...$projectStore.circles.filter((c) => c.id !== draggedElement.id),
313+
...$projectStore.rectangles.filter((r) => r.id !== draggedElement.id),
314+
...$projectStore.legs.filter((l) => l.id !== draggedElement.id)
315+
];
316+
317+
alignmentLines = detectAlignments(
318+
liveElement,
319+
allOtherElements,
320+
$projectStore.panelSettings.width,
321+
$projectStore.panelSettings.height
322+
);
323+
};
324+
325+
/**
326+
* Clears all alignment lines
327+
*
328+
* Called on dragend events to remove visual guides after dragging completes.
329+
*/
330+
const clearAlignmentLines = () => {
331+
alignmentLines = [];
332+
};
333+
280334
/**
281335
* Mouse enter event handler - shows hover info and enables fine movement
282336
*
@@ -439,8 +493,14 @@
439493
ondblclick={() => {
440494
if (mode === 'pointer') modifyCircle(circle);
441495
}}
442-
ondragend={() => updateCircleChanges()}
443-
ondragmove={(event) => limitCircle(event, circle)}
496+
ondragend={() => {
497+
updateCircleChanges();
498+
clearAlignmentLines();
499+
}}
500+
ondragmove={(event) => {
501+
limitCircle(event, circle);
502+
updateAlignmentLines(event, circle);
503+
}}
444504
onmouseenter={(event) => handleMouseEnter(event, circle)}
445505
onmouseleave={(event) => handleMouseLeave(event, circle)}
446506
opacity={0.75}
@@ -460,8 +520,14 @@
460520
ondblclick={() => {
461521
if (mode === 'pointer') modifyRectangle(rectangle);
462522
}}
463-
ondragend={() => updateRectangleChanges()}
464-
ondragmove={(event) => limitBox(event, rectangle)}
523+
ondragend={() => {
524+
updateRectangleChanges();
525+
clearAlignmentLines();
526+
}}
527+
ondragmove={(event) => {
528+
limitBox(event, rectangle);
529+
updateAlignmentLines(event, rectangle);
530+
}}
465531
onmouseenter={(event) => handleMouseEnter(event, rectangle)}
466532
onmouseleave={(event) => handleMouseLeave(event, rectangle)}
467533
opacity={0.75}
@@ -480,8 +546,14 @@
480546
ondblclick={() => {
481547
if (mode === 'pointer') deleteLegWithConfirm(leg);
482548
}}
483-
ondragend={() => updateLegChanges()}
484-
ondragmove={(event) => limitBox(event, leg)}
549+
ondragend={() => {
550+
updateLegChanges();
551+
clearAlignmentLines();
552+
}}
553+
ondragmove={(event) => {
554+
limitBox(event, leg);
555+
updateAlignmentLines(event, leg);
556+
}}
485557
onmouseenter={(event) => handleMouseEnter(event, leg)}
486558
onmouseleave={(event) => handleMouseLeave(event, leg)}
487559
opacity={0.75}
@@ -490,6 +562,17 @@
490562
bind:y={leg.y}
491563
/>
492564
{/each}
565+
{#if mode === 'pointer'}
566+
{#each alignmentLines as line}
567+
<Line
568+
listening={false}
569+
opacity={0.9}
570+
points={[line.x1, line.y1, line.x2, line.y2]}
571+
stroke={ALIGNMENT_LINE_COLOR}
572+
strokeWidth={ALIGNMENT_LINE_WIDTH}
573+
/>
574+
{/each}
575+
{/if}
493576
{#if mode === 'measure'}
494577
<DesignerGrid
495578
height={$projectStore.panelSettings.height}

0 commit comments

Comments
 (0)