editor: group-scale resize for multi-selection
This commit is contained in:
parent
5636de4154
commit
d70c2b62ef
@ -878,7 +878,10 @@ export function editorReducer(state: EditorRuntimeState, action: EditorAction):
|
||||
};
|
||||
|
||||
for (const node of next.doc.screen.nodes) {
|
||||
if (next.selection.ids.includes(node.id)) drag.snapshot.set(node.id, { ...node.rect });
|
||||
if (!next.selection.ids.includes(node.id)) continue;
|
||||
// Locked/hidden nodes can remain selected, but should not be transform targets.
|
||||
if (node.locked || node.hidden) continue;
|
||||
drag.snapshot.set(node.id, { ...node.rect });
|
||||
}
|
||||
|
||||
return {
|
||||
@ -897,14 +900,80 @@ export function editorReducer(state: EditorRuntimeState, action: EditorAction):
|
||||
const dy = Math.round((action.current.screenY - drag.startScreenY) / scale);
|
||||
|
||||
const handle = drag.handle;
|
||||
const target = drag.snapshot.get(drag.targetId);
|
||||
if (!target) return state;
|
||||
|
||||
const isTop = /t/.test(handle);
|
||||
const isBottom = /b/.test(handle);
|
||||
const isLeft = /l/.test(handle);
|
||||
const isRight = /r/.test(handle);
|
||||
|
||||
const bounds = action.bounds;
|
||||
|
||||
// M2: multi-select resize scales the whole selection (unlocked + visible nodes only).
|
||||
const isGroup = drag.snapshot.size > 1;
|
||||
|
||||
if (isGroup) {
|
||||
const rects = Array.from(drag.snapshot.values());
|
||||
const bbox0: Rect = {
|
||||
x: Math.min(...rects.map((r) => r.x)),
|
||||
y: Math.min(...rects.map((r) => r.y)),
|
||||
w: Math.max(...rects.map((r) => r.x + r.w)) - Math.min(...rects.map((r) => r.x)),
|
||||
h: Math.max(...rects.map((r) => r.y + r.h)) - Math.min(...rects.map((r) => r.y)),
|
||||
};
|
||||
|
||||
// Resize the selection bbox using the same handle semantics as single-node resize.
|
||||
let bbox1: Rect = {
|
||||
x: bbox0.x + (isLeft ? dx : 0),
|
||||
y: bbox0.y + (isTop ? dy : 0),
|
||||
w: bbox0.w + (isLeft ? -dx : isRight ? dx : 0),
|
||||
h: bbox0.h + (isTop ? -dy : isBottom ? dy : 0),
|
||||
};
|
||||
|
||||
// No-op axes (e.g. 't' handle should not change width/x).
|
||||
if (!isLeft && !isRight) {
|
||||
bbox1.x = bbox0.x;
|
||||
bbox1.w = bbox0.w;
|
||||
}
|
||||
if (!isTop && !isBottom) {
|
||||
bbox1.y = bbox0.y;
|
||||
bbox1.h = bbox0.h;
|
||||
}
|
||||
|
||||
// Prevent degenerate scaling.
|
||||
bbox1 = {
|
||||
...bbox1,
|
||||
w: Math.max(1, bbox1.w),
|
||||
h: Math.max(1, bbox1.h),
|
||||
};
|
||||
|
||||
bbox1 = clampRectToBounds(bbox1, bounds, 50);
|
||||
|
||||
const sx = bbox0.w ? bbox1.w / bbox0.w : 1;
|
||||
const sy = bbox0.h ? bbox1.h / bbox0.h : 1;
|
||||
|
||||
const nodes = state.doc.screen.nodes.map((n) => {
|
||||
const r0 = drag.snapshot.get(n.id);
|
||||
if (!r0) return n;
|
||||
|
||||
const nextRect: Rect = {
|
||||
x: Math.round(bbox1.x + (r0.x - bbox0.x) * sx),
|
||||
y: Math.round(bbox1.y + (r0.y - bbox0.y) * sy),
|
||||
w: Math.max(0, Math.round(r0.w * sx)),
|
||||
h: Math.max(0, Math.round(r0.h * sy)),
|
||||
};
|
||||
|
||||
return { ...n, rect: nextRect };
|
||||
});
|
||||
|
||||
return {
|
||||
...state,
|
||||
doc: { screen: { ...state.doc.screen, nodes } },
|
||||
canvas: { ...state.canvas, guides: { xs: [], ys: [] } },
|
||||
};
|
||||
}
|
||||
|
||||
const target = drag.snapshot.get(drag.targetId);
|
||||
if (!target) return state;
|
||||
|
||||
const newH = target.h + (isTop ? -dy : isBottom ? dy : 0);
|
||||
const newW = target.w + (isLeft ? -dx : isRight ? dx : 0);
|
||||
|
||||
@ -915,8 +984,6 @@ export function editorReducer(state: EditorRuntimeState, action: EditorAction):
|
||||
h: Math.max(0, newH),
|
||||
};
|
||||
|
||||
const bounds = action.bounds;
|
||||
|
||||
// goView-like: only snap when resizing a single selected node.
|
||||
const canSnap = state.selection.ids.length === 1;
|
||||
const movingId = drag.targetId;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user