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) {
|
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 {
|
return {
|
||||||
@ -897,14 +900,80 @@ export function editorReducer(state: EditorRuntimeState, action: EditorAction):
|
|||||||
const dy = Math.round((action.current.screenY - drag.startScreenY) / scale);
|
const dy = Math.round((action.current.screenY - drag.startScreenY) / scale);
|
||||||
|
|
||||||
const handle = drag.handle;
|
const handle = drag.handle;
|
||||||
const target = drag.snapshot.get(drag.targetId);
|
|
||||||
if (!target) return state;
|
|
||||||
|
|
||||||
const isTop = /t/.test(handle);
|
const isTop = /t/.test(handle);
|
||||||
const isBottom = /b/.test(handle);
|
const isBottom = /b/.test(handle);
|
||||||
const isLeft = /l/.test(handle);
|
const isLeft = /l/.test(handle);
|
||||||
const isRight = /r/.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 newH = target.h + (isTop ? -dy : isBottom ? dy : 0);
|
||||||
const newW = target.w + (isLeft ? -dx : isRight ? dx : 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),
|
h: Math.max(0, newH),
|
||||||
};
|
};
|
||||||
|
|
||||||
const bounds = action.bounds;
|
|
||||||
|
|
||||||
// goView-like: only snap when resizing a single selected node.
|
// goView-like: only snap when resizing a single selected node.
|
||||||
const canSnap = state.selection.ids.length === 1;
|
const canSnap = state.selection.ids.length === 1;
|
||||||
const movingId = drag.targetId;
|
const movingId = drag.targetId;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user