feat(renderer): enhance image rendering with asset synchronization and clipping
This commit is contained in:
@@ -31,6 +31,36 @@ describe("transform commands", () => {
|
||||
expect(updated.document.artboards[0]?.bounds).toEqual({ x: 0, y: 0, w: 110, h: 100 });
|
||||
});
|
||||
|
||||
test("shift constrains body moves to the dominant axis", () => {
|
||||
const started = transformBeginCommand.execute(
|
||||
{ state: artboardState() },
|
||||
{ target: { type: "artboard", id: "a1" }, handle: "body", point: { x: 0, y: 0 }, initialBounds: { x: 0, y: 0, w: 100, h: 80 } },
|
||||
);
|
||||
const updated = transformUpdateCommand.execute({ state: started }, { point: { x: 10, y: 20 }, shiftKey: true });
|
||||
|
||||
expect(updated.document.artboards[0]?.bounds).toEqual({ x: 0, y: 20, w: 100, h: 80 });
|
||||
});
|
||||
|
||||
test("shift constrains corner resizes to the original proportions", () => {
|
||||
const started = transformBeginCommand.execute(
|
||||
{ state: artboardState() },
|
||||
{ target: { type: "artboard", id: "a1" }, handle: "se", point: { x: 0, y: 0 }, initialBounds: { x: 0, y: 0, w: 100, h: 80 } },
|
||||
);
|
||||
const updated = transformUpdateCommand.execute({ state: started }, { point: { x: 50, y: 1 }, shiftKey: true });
|
||||
|
||||
expect(updated.document.artboards[0]?.bounds).toEqual({ x: 0, y: 0, w: 150, h: 120 });
|
||||
});
|
||||
|
||||
test("shift keeps the opposite corner anchored during proportional resize", () => {
|
||||
const started = transformBeginCommand.execute(
|
||||
{ state: artboardState() },
|
||||
{ target: { type: "artboard", id: "a1" }, handle: "nw", point: { x: 0, y: 0 }, initialBounds: { x: 0, y: 0, w: 100, h: 80 } },
|
||||
);
|
||||
const updated = transformUpdateCommand.execute({ state: started }, { point: { x: -50, y: -1 }, shiftKey: true });
|
||||
|
||||
expect(updated.document.artboards[0]?.bounds).toEqual({ x: -50, y: -40, w: 150, h: 120 });
|
||||
});
|
||||
|
||||
test("ends transform session", () => {
|
||||
const started = transformBeginCommand.execute(
|
||||
{ state: artboardState() },
|
||||
|
||||
@@ -13,6 +13,7 @@ export type TransformBeginPayload = {
|
||||
|
||||
export type TransformUpdatePayload = {
|
||||
point: Vec2D;
|
||||
shiftKey?: boolean;
|
||||
};
|
||||
|
||||
export const transformBeginCommand: Command<TransformBeginPayload> = {
|
||||
@@ -41,10 +42,15 @@ export const transformUpdateCommand: Command<TransformUpdatePayload> = {
|
||||
const session = state.editor.transformSession;
|
||||
if (!session) return state;
|
||||
|
||||
const nextBounds = transformBounds(session.initialBounds, session.handle, {
|
||||
x: payload.point.x - session.startPoint.x,
|
||||
y: payload.point.y - session.startPoint.y,
|
||||
});
|
||||
const nextBounds = transformBounds(
|
||||
session.initialBounds,
|
||||
session.handle,
|
||||
{
|
||||
x: payload.point.x - session.startPoint.x,
|
||||
y: payload.point.y - session.startPoint.y,
|
||||
},
|
||||
payload.shiftKey === true,
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
@@ -71,8 +77,11 @@ export const transformEndCommand: Command = {
|
||||
|
||||
export const transformCommands = [transformBeginCommand, transformUpdateCommand, transformEndCommand] satisfies Command<unknown>[];
|
||||
|
||||
function transformBounds(bounds: Rect, handle: TransformHandle, delta: Vec2D): Rect {
|
||||
if (handle === "body") return { ...bounds, x: bounds.x + delta.x, y: bounds.y + delta.y };
|
||||
function transformBounds(bounds: Rect, handle: TransformHandle, delta: Vec2D, constrained = false): Rect {
|
||||
if (handle === "body") {
|
||||
const constrainedDelta = constrained ? constrainMoveDelta(delta) : delta;
|
||||
return { ...bounds, x: bounds.x + constrainedDelta.x, y: bounds.y + constrainedDelta.y };
|
||||
}
|
||||
|
||||
let x = bounds.x;
|
||||
let y = bounds.y;
|
||||
@@ -90,7 +99,31 @@ function transformBounds(bounds: Rect, handle: TransformHandle, delta: Vec2D): R
|
||||
}
|
||||
if (handle.includes("s")) h = bounds.h + delta.y;
|
||||
|
||||
return normalizeRect({ x, y, w, h });
|
||||
const resized = normalizeRect({ x, y, w, h });
|
||||
if (!constrained || !isCornerHandle(handle)) return resized;
|
||||
|
||||
return proportionalCornerResize(bounds, handle, resized);
|
||||
}
|
||||
|
||||
function constrainMoveDelta(delta: Vec2D): Vec2D {
|
||||
return Math.abs(delta.x) >= Math.abs(delta.y) ? { x: delta.x, y: 0 } : { x: 0, y: delta.y };
|
||||
}
|
||||
|
||||
function isCornerHandle(handle: TransformHandle) {
|
||||
return handle.length === 2;
|
||||
}
|
||||
|
||||
function proportionalCornerResize(initial: Rect, handle: TransformHandle, resized: Rect): Rect {
|
||||
const aspectRatio = initial.w / initial.h;
|
||||
const widthScale = resized.w / initial.w;
|
||||
const heightScale = resized.h / initial.h;
|
||||
const scale = Math.abs(widthScale - 1) >= Math.abs(heightScale - 1) ? widthScale : heightScale;
|
||||
const w = Math.max(1, initial.w * scale);
|
||||
const h = Math.max(1, w / aspectRatio);
|
||||
const x = handle.includes("w") ? initial.x + initial.w - w : initial.x;
|
||||
const y = handle.includes("n") ? initial.y + initial.h - h : initial.y;
|
||||
|
||||
return { x, y, w, h };
|
||||
}
|
||||
|
||||
function normalizeRect(rect: Rect): Rect {
|
||||
|
||||
Reference in New Issue
Block a user