mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-10-27 08:40:39 +00:00
Fix copy/paste transform consistency — group-uniform scale + translate
Before: pasteLayersFromClipboard clamped each item's w / h individually to the target bounds, then re-centred any item whose box landed out-of-bounds. With multiple copied items, this: - Collapsed every out-of-bounds item onto the same centred position (lost their relative positions). - Gave each item a different effective scale (lost their relative sizes). So a "Copy All" → cross-mode paste turned a tidy 4-layer composition into a pile of overlapping clamped boxes. Now: compute the bounding box once across every clipboard item, pick a single scale (only shrinks — never enlarges — so a paste into a larger target keeps items at their original size), and apply one shared translation that centres the (possibly scaled) bbox in the target. Both axes use the same scale, preserving the group's aspect ratio. Items missing w / h fall back to 70%-of-target uniformly, so the default-size path can't desync either. Result: - Two items copied with their original gap and a 2:1 size ratio retain that gap and ratio after paste, regardless of target size. - A wide cover → narrow disc paste shrinks everything proportionally rather than collapsing edges onto the centre. - Same-mode paste with everything already in-bounds is unchanged (scale stays at 1; translation is identity). TODO.md: move the resolved colour-picker work to "Shipped (recent)". Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
d57e6026da
commit
c82267c6a1
2 files changed
+40
-11
No files matched your search
@@ -7,10 +7,6 @@ Running list of deferred work. Detailed specs for the larger items live in
|
||||
|
||||
- **Slide deck PDF export.** Add multi-slide PDF export for slide
|
||||
presentations, producing one page per slide.
|
||||
- **Color picker fixes (vector tools).** The in-app HSV picker now works in
|
||||
the pixel editor; verify the OS-native `<input type="color">` flow in the
|
||||
vector tool panels (Pen / Shape / Text fill + stroke). If the same Brave
|
||||
R=NaN bug bites there, port the in-app HSV picker over.
|
||||
- **Copy / paste transform consistency.** Audit Copy, Paste, Copy All, and
|
||||
cross-target paste so copied objects keep consistent position and scale
|
||||
relative to the other copied objects, without unexpected misalignment or
|
||||
@@ -81,6 +77,13 @@ Each has a full spec in `docs/`.
|
||||
|
||||
## Shipped (recent)
|
||||
|
||||
- **Pixel-editor colour picker** — ported the OS native `<input type="color">`
|
||||
over from the vector tools (same swatch styling; opens the OS picker with
|
||||
its built-in eyedropper on click). Removed the Screen-Capture-API screen-
|
||||
eyedropper workaround (~170 lines of dead code). Palette swatches and
|
||||
the in-canvas eyedropper tool now sync the picker too, so grabbing a
|
||||
palette colour as the starting point for a tweaked variant just works.
|
||||
|
||||
- **Pixel Art** — `docs/PIXEL_ART_FEATURE.md`. Phases A–D all shipped:
|
||||
embedded `pixelart` layer + standalone `casewrap-pixelart` design,
|
||||
PNG / sprite-sheet / GIF export, drawing tools (line / rect / ellipse,
|
||||
|
||||
+33
-7
@@ -7873,16 +7873,42 @@
|
||||
pushHistory(`paste ${items.length} layer${items.length === 1 ? '' : 's'}`);
|
||||
const target = currentPasteTarget();
|
||||
const bounds = targetBounds(target);
|
||||
// Compute the bounding box over all clipboard items in source coords.
|
||||
// Each item missing w/h falls back to a 70%-of-target default — same
|
||||
// as the previous behaviour, applied uniformly so it can't desync
|
||||
// items within the same paste.
|
||||
const defW = bounds.w * 0.7, defH = bounds.h * 0.7;
|
||||
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||
for (const it of items) {
|
||||
const w = typeof it.w === 'number' && it.w > 0 ? it.w : defW;
|
||||
const h = typeof it.h === 'number' && it.h > 0 ? it.h : defH;
|
||||
const x = typeof it.x === 'number' ? it.x : 0;
|
||||
const y = typeof it.y === 'number' ? it.y : 0;
|
||||
if (x < minX) minX = x;
|
||||
if (y < minY) minY = y;
|
||||
if (x + w > maxX) maxX = x + w;
|
||||
if (y + h > maxY) maxY = y + h;
|
||||
}
|
||||
const srcW = Math.max(0.01, maxX - minX);
|
||||
const srcH = Math.max(0.01, maxY - minY);
|
||||
// Single scale factor — only shrinks (never enlarges) so a paste into
|
||||
// a larger target keeps the items at their original size. Both axes
|
||||
// share the same scale so the group's aspect ratio is preserved.
|
||||
const scale = Math.min(1, bounds.w / srcW, bounds.h / srcH);
|
||||
// Centre the (possibly scaled) bbox within the target.
|
||||
const tx = bounds.x + (bounds.w - srcW * scale) / 2 - minX * scale;
|
||||
const ty = bounds.y + (bounds.h - srcH * scale) / 2 - minY * scale;
|
||||
let newLast = null;
|
||||
const newIds = [];
|
||||
for (const item of items) {
|
||||
const w = Math.max(0.1, Math.min(item.w || bounds.w * 0.7, bounds.w));
|
||||
const h = Math.max(0.1, Math.min(item.h || bounds.h * 0.7, bounds.h));
|
||||
let x = typeof item.x === 'number' ? item.x : bounds.x + (bounds.w - w) / 2;
|
||||
let y = typeof item.y === 'number' ? item.y : bounds.y + (bounds.h - h) / 2;
|
||||
// Clamp inside the target bounds so cross-sub-mode pastes don't land off-canvas.
|
||||
if (x < bounds.x || x + w > bounds.x + bounds.w) x = bounds.x + (bounds.w - w) / 2;
|
||||
if (y < bounds.y || y + h > bounds.y + bounds.h) y = bounds.y + (bounds.h - h) / 2;
|
||||
const w0 = typeof item.w === 'number' && item.w > 0 ? item.w : defW;
|
||||
const h0 = typeof item.h === 'number' && item.h > 0 ? item.h : defH;
|
||||
const x0 = typeof item.x === 'number' ? item.x : 0;
|
||||
const y0 = typeof item.y === 'number' ? item.y : 0;
|
||||
const w = Math.max(0.01, w0 * scale);
|
||||
const h = Math.max(0.01, h0 * scale);
|
||||
const x = x0 * scale + tx;
|
||||
const y = y0 * scale + ty;
|
||||
const layer = { ...item, id: makeLayerId(), target, x, y, w, h, z: nextLayerZ() };
|
||||
state.layers.push(layer);
|
||||
newLast = layer;
|
||||
|
||||
Reference in new issue
Block a user