refboard: improve crop preview and cropped bounds focus
This commit is contained in:
@@ -11,6 +11,7 @@ import type { Viewport } from 'pixi-viewport';
|
|||||||
import type { SceneItem } from './SceneManager';
|
import type { SceneItem } from './SceneManager';
|
||||||
import type { ImageObject, CropRect } from './scene-format';
|
import type { ImageObject, CropRect } from './scene-format';
|
||||||
import { getImageViewRectWorldCorners, imageViewPointToWorld, worldToImageViewPoint } from './imageTransforms';
|
import { getImageViewRectWorldCorners, imageViewPointToWorld, worldToImageViewPoint } from './imageTransforms';
|
||||||
|
import { ImageSprite } from './sprites/ImageSprite';
|
||||||
|
|
||||||
const HANDLE_SIZE = 8;
|
const HANDLE_SIZE = 8;
|
||||||
const HANDLE_HIT_SIZE = 22;
|
const HANDLE_HIT_SIZE = 22;
|
||||||
@@ -35,6 +36,7 @@ export class CropOverlay extends Container {
|
|||||||
private _border: Graphics;
|
private _border: Graphics;
|
||||||
private _handles = new Map<HandleId, Graphics>();
|
private _handles = new Map<HandleId, Graphics>();
|
||||||
private _crop: CropRect = { x: 0, y: 0, w: 1, h: 1 };
|
private _crop: CropRect = { x: 0, y: 0, w: 1, h: 1 };
|
||||||
|
private _originalCrop: CropRect | undefined;
|
||||||
private _drag: { mode: DragMode; startCrop: CropRect; startPoint: { x: number; y: number } } | null = null;
|
private _drag: { mode: DragMode; startCrop: CropRect; startPoint: { x: number; y: number } } | null = null;
|
||||||
private _onConfirm: ((item: SceneItem, crop: CropRect) => void) | null = null;
|
private _onConfirm: ((item: SceneItem, crop: CropRect) => void) | null = null;
|
||||||
private _onCancel: (() => void) | null = null;
|
private _onCancel: (() => void) | null = null;
|
||||||
@@ -91,7 +93,11 @@ export class CropOverlay extends Container {
|
|||||||
if (item.type !== 'image') return;
|
if (item.type !== 'image') return;
|
||||||
this._item = item;
|
this._item = item;
|
||||||
const imgData = item.data as ImageObject;
|
const imgData = item.data as ImageObject;
|
||||||
|
this._originalCrop = imgData.crop ? { ...imgData.crop } : undefined;
|
||||||
this._crop = imgData.crop ? { ...imgData.crop } : { x: 0, y: 0, w: 1, h: 1 };
|
this._crop = imgData.crop ? { ...imgData.crop } : { x: 0, y: 0, w: 1, h: 1 };
|
||||||
|
if (item.displayObject instanceof ImageSprite) {
|
||||||
|
item.displayObject.applyCrop(undefined);
|
||||||
|
}
|
||||||
this._viewport.plugins.pause('drag');
|
this._viewport.plugins.pause('drag');
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this._onStateChange?.(true);
|
this._onStateChange?.(true);
|
||||||
@@ -121,6 +127,9 @@ export class CropOverlay extends Container {
|
|||||||
|
|
||||||
/** Cancel cropping — restore original state. */
|
/** Cancel cropping — restore original state. */
|
||||||
cancel(): void {
|
cancel(): void {
|
||||||
|
if (this._item?.displayObject instanceof ImageSprite) {
|
||||||
|
this._item.displayObject.applyCrop(this._originalCrop);
|
||||||
|
}
|
||||||
this._cleanup();
|
this._cleanup();
|
||||||
this._onCancel?.();
|
this._onCancel?.();
|
||||||
}
|
}
|
||||||
@@ -132,6 +141,7 @@ export class CropOverlay extends Container {
|
|||||||
this._removeDomDragListeners();
|
this._removeDomDragListeners();
|
||||||
this._viewport.plugins.resume('drag');
|
this._viewport.plugins.resume('drag');
|
||||||
this._item = null;
|
this._item = null;
|
||||||
|
this._originalCrop = undefined;
|
||||||
this._drag = null;
|
this._drag = null;
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
this._onStateChange?.(false);
|
this._onStateChange?.(false);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { CropOverlay } from '../canvas/CropOverlay';
|
|||||||
import { MarkdownOverlay } from '../canvas/MarkdownOverlay';
|
import { MarkdownOverlay } from '../canvas/MarkdownOverlay';
|
||||||
import { TextSprite } from '../canvas/sprites/TextSprite';
|
import { TextSprite } from '../canvas/sprites/TextSprite';
|
||||||
import { TextSharpnessManager } from '../canvas/textSharpness';
|
import { TextSharpnessManager } from '../canvas/textSharpness';
|
||||||
|
import { getItemWorldBounds } from '../canvas/SceneManager';
|
||||||
// PresenceOverlay removed — remote selection highlighting was too heavy for minimal benefit
|
// PresenceOverlay removed — remote selection highlighting was too heavy for minimal benefit
|
||||||
import { connectSocket, disconnectSocket } from '../socket';
|
import { connectSocket, disconnectSocket } from '../socket';
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
@@ -150,13 +151,14 @@ export function useCanvasSetup(deps: CanvasSetupDeps) {
|
|||||||
const padding = 80; // screen pixels of padding around the image
|
const padding = 80; // screen pixels of padding around the image
|
||||||
const screenW = viewport.screenWidth;
|
const screenW = viewport.screenWidth;
|
||||||
const screenH = viewport.screenHeight;
|
const screenH = viewport.screenHeight;
|
||||||
const bw = item.data.w * Math.abs(item.data.sx);
|
const bounds = getItemWorldBounds(item);
|
||||||
const bh = item.data.h * Math.abs(item.data.sy);
|
const bw = bounds.w;
|
||||||
|
const bh = bounds.h;
|
||||||
const scaleX = (screenW - padding * 2) / bw;
|
const scaleX = (screenW - padding * 2) / bw;
|
||||||
const scaleY = (screenH - padding * 2) / bh;
|
const scaleY = (screenH - padding * 2) / bh;
|
||||||
const targetScale = Math.min(scaleX, scaleY, 3); // cap at 3x
|
const targetScale = Math.min(scaleX, scaleY, 3); // cap at 3x
|
||||||
const cx = item.data.x + bw / 2;
|
const cx = bounds.x + bw / 2;
|
||||||
const cy = item.data.y + bh / 2;
|
const cy = bounds.y + bh / 2;
|
||||||
viewport.animate({
|
viewport.animate({
|
||||||
time: 300,
|
time: 300,
|
||||||
position: { x: cx, y: cy },
|
position: { x: cx, y: cy },
|
||||||
@@ -481,7 +483,9 @@ export function useCanvasSetup(deps: CanvasSetupDeps) {
|
|||||||
if (item.displayObject instanceof ImageSprite) {
|
if (item.displayObject instanceof ImageSprite) {
|
||||||
item.displayObject.applyCrop(imgData.crop);
|
item.displayObject.applyCrop(imgData.crop);
|
||||||
}
|
}
|
||||||
|
scene.updateSpatialEntry(item);
|
||||||
selection.setEnabled(true);
|
selection.setEnabled(true);
|
||||||
|
selection.transformBox.update([item]);
|
||||||
onCanvasChange([item.id]);
|
onCanvasChange([item.id]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user