mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): wip transform mode
This commit is contained in:
parent
7f9a31ca4a
commit
65353ac1e1
@ -9,21 +9,22 @@ import { PiBoundingBoxBold } from 'react-icons/pi';
|
|||||||
export const BboxToolButton = memo(() => {
|
export const BboxToolButton = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isDisabled = useAppSelector((s) => s.canvasV2.session.isStaging);
|
const isDisabled = useAppSelector((s) => s.canvasV2.session.isStaging || s.canvasV2.tool.isTransforming);
|
||||||
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'bbox');
|
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'bbox');
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
dispatch(toolChanged('bbox'));
|
dispatch(toolChanged('bbox'));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
useHotkeys('q', onClick, [onClick]);
|
useHotkeys('q', onClick, { enabled: !isDisabled }, [onClick, isDisabled]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={`${t('controlLayers.bbox')} (Q)`}
|
aria-label={`${t('controlLayers.bbox')} (Q)`}
|
||||||
tooltip={`${t('controlLayers.bbox')} (Q)`}
|
tooltip={`${t('controlLayers.bbox')} (Q)`}
|
||||||
icon={<PiBoundingBoxBold />}
|
icon={<PiBoundingBoxBold />}
|
||||||
variant={isSelected ? 'solid' : 'outline'}
|
colorScheme={isSelected ? 'invokeBlue' : 'base'}
|
||||||
|
variant="outline"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
|
@ -15,13 +15,13 @@ export const BrushToolButton = memo(() => {
|
|||||||
const entityType = s.canvasV2.selectedEntityIdentifier?.type;
|
const entityType = s.canvasV2.selectedEntityIdentifier?.type;
|
||||||
const isDrawingToolAllowed = entityType ? isDrawableEntityType(entityType) : false;
|
const isDrawingToolAllowed = entityType ? isDrawableEntityType(entityType) : false;
|
||||||
const isStaging = s.canvasV2.session.isStaging;
|
const isStaging = s.canvasV2.session.isStaging;
|
||||||
return !isDrawingToolAllowed || isStaging;
|
return !isDrawingToolAllowed || isStaging || s.canvasV2.tool.isTransforming;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
dispatch(toolChanged('brush'));
|
dispatch(toolChanged('brush'));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
useHotkeys('b', onClick, { enabled: !isDisabled }, [isDisabled, onClick]);
|
useHotkeys('b', onClick, { enabled: !isDisabled }, [isDisabled, onClick]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -29,7 +29,8 @@ export const BrushToolButton = memo(() => {
|
|||||||
aria-label={`${t('unifiedCanvas.brush')} (B)`}
|
aria-label={`${t('unifiedCanvas.brush')} (B)`}
|
||||||
tooltip={`${t('unifiedCanvas.brush')} (B)`}
|
tooltip={`${t('unifiedCanvas.brush')} (B)`}
|
||||||
icon={<PiPaintBrushBold />}
|
icon={<PiPaintBrushBold />}
|
||||||
variant={isSelected ? 'solid' : 'outline'}
|
colorScheme={isSelected ? 'invokeBlue' : 'base'}
|
||||||
|
variant="outline"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
|
@ -15,7 +15,7 @@ export const EraserToolButton = memo(() => {
|
|||||||
const entityType = s.canvasV2.selectedEntityIdentifier?.type;
|
const entityType = s.canvasV2.selectedEntityIdentifier?.type;
|
||||||
const isDrawingToolAllowed = entityType ? isDrawableEntityType(entityType) : false;
|
const isDrawingToolAllowed = entityType ? isDrawableEntityType(entityType) : false;
|
||||||
const isStaging = s.canvasV2.session.isStaging;
|
const isStaging = s.canvasV2.session.isStaging;
|
||||||
return !isDrawingToolAllowed || isStaging;
|
return !isDrawingToolAllowed || isStaging || s.canvasV2.tool.isTransforming;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
@ -29,7 +29,8 @@ export const EraserToolButton = memo(() => {
|
|||||||
aria-label={`${t('unifiedCanvas.eraser')} (E)`}
|
aria-label={`${t('unifiedCanvas.eraser')} (E)`}
|
||||||
tooltip={`${t('unifiedCanvas.eraser')} (E)`}
|
tooltip={`${t('unifiedCanvas.eraser')} (E)`}
|
||||||
icon={<PiEraserBold />}
|
icon={<PiEraserBold />}
|
||||||
variant={isSelected ? 'solid' : 'outline'}
|
colorScheme={isSelected ? 'invokeBlue' : 'base'}
|
||||||
|
variant="outline"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
|
@ -11,7 +11,7 @@ export const MoveToolButton = memo(() => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'move');
|
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'move');
|
||||||
const isDisabled = useAppSelector(
|
const isDisabled = useAppSelector(
|
||||||
(s) => s.canvasV2.selectedEntityIdentifier === null || s.canvasV2.session.isStaging
|
(s) => s.canvasV2.selectedEntityIdentifier === null || s.canvasV2.session.isStaging || s.canvasV2.tool.isTransforming
|
||||||
);
|
);
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
@ -25,7 +25,8 @@ export const MoveToolButton = memo(() => {
|
|||||||
aria-label={`${t('unifiedCanvas.move')} (V)`}
|
aria-label={`${t('unifiedCanvas.move')} (V)`}
|
||||||
tooltip={`${t('unifiedCanvas.move')} (V)`}
|
tooltip={`${t('unifiedCanvas.move')} (V)`}
|
||||||
icon={<PiCursorBold />}
|
icon={<PiCursorBold />}
|
||||||
variant={isSelected ? 'solid' : 'outline'}
|
colorScheme={isSelected ? 'invokeBlue' : 'base'}
|
||||||
|
variant="outline"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
|
@ -15,7 +15,7 @@ export const RectToolButton = memo(() => {
|
|||||||
const entityType = s.canvasV2.selectedEntityIdentifier?.type;
|
const entityType = s.canvasV2.selectedEntityIdentifier?.type;
|
||||||
const isDrawingToolAllowed = entityType ? isDrawableEntityType(entityType) : false;
|
const isDrawingToolAllowed = entityType ? isDrawableEntityType(entityType) : false;
|
||||||
const isStaging = s.canvasV2.session.isStaging;
|
const isStaging = s.canvasV2.session.isStaging;
|
||||||
return !isDrawingToolAllowed || isStaging;
|
return !isDrawingToolAllowed || isStaging || s.canvasV2.tool.isTransforming;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
@ -29,7 +29,8 @@ export const RectToolButton = memo(() => {
|
|||||||
aria-label={`${t('controlLayers.rectangle')} (U)`}
|
aria-label={`${t('controlLayers.rectangle')} (U)`}
|
||||||
tooltip={`${t('controlLayers.rectangle')} (U)`}
|
tooltip={`${t('controlLayers.rectangle')} (U)`}
|
||||||
icon={<PiRectangleBold />}
|
icon={<PiRectangleBold />}
|
||||||
variant={isSelected ? 'solid' : 'outline'}
|
colorScheme={isSelected ? 'invokeBlue' : 'base'}
|
||||||
|
variant="outline"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
|
@ -14,23 +14,26 @@ export const ToolChooser: React.FC = () => {
|
|||||||
useCanvasResetLayerHotkey();
|
useCanvasResetLayerHotkey();
|
||||||
useCanvasDeleteLayerHotkey();
|
useCanvasDeleteLayerHotkey();
|
||||||
const isCanvasSessionActive = useAppSelector((s) => s.canvasV2.session.isActive);
|
const isCanvasSessionActive = useAppSelector((s) => s.canvasV2.session.isActive);
|
||||||
|
const isTransforming = useAppSelector((s) => s.canvasV2.tool.isTransforming);
|
||||||
|
|
||||||
if (isCanvasSessionActive) {
|
if (isCanvasSessionActive) {
|
||||||
return (
|
return (
|
||||||
<ButtonGroup isAttached>
|
<>
|
||||||
<BrushToolButton />
|
<ButtonGroup isAttached isDisabled={isTransforming}>
|
||||||
<EraserToolButton />
|
<BrushToolButton />
|
||||||
<RectToolButton />
|
<EraserToolButton />
|
||||||
<MoveToolButton />
|
<RectToolButton />
|
||||||
|
<MoveToolButton />
|
||||||
|
<ViewToolButton />
|
||||||
|
<BboxToolButton />
|
||||||
|
</ButtonGroup>
|
||||||
<TransformToolButton />
|
<TransformToolButton />
|
||||||
<ViewToolButton />
|
</>
|
||||||
<BboxToolButton />
|
|
||||||
</ButtonGroup>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup isAttached>
|
<ButtonGroup isAttached isDisabled={isTransforming}>
|
||||||
<BrushToolButton />
|
<BrushToolButton />
|
||||||
<EraserToolButton />
|
<EraserToolButton />
|
||||||
<RectToolButton />
|
<RectToolButton />
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { IconButton } from '@invoke-ai/ui-library';
|
import { Button, IconButton } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
|
import { toolIsTransformingChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -9,24 +9,41 @@ import { PiResizeBold } from 'react-icons/pi';
|
|||||||
export const TransformToolButton = memo(() => {
|
export const TransformToolButton = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'transform');
|
const isTransforming = useAppSelector((s) => s.canvasV2.tool.isTransforming);
|
||||||
const isDisabled = useAppSelector(
|
const isDisabled = useAppSelector(
|
||||||
(s) => s.canvasV2.selectedEntityIdentifier === null || s.canvasV2.session.isStaging
|
(s) => s.canvasV2.selectedEntityIdentifier === null || s.canvasV2.session.isStaging
|
||||||
);
|
);
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onTransform = useCallback(() => {
|
||||||
dispatch(toolChanged('transform'));
|
dispatch(toolIsTransformingChanged(true));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
useHotkeys(['ctrl+t', 'meta+t'], onClick, { enabled: !isDisabled }, [isDisabled, onClick]);
|
const onApplyTransformation = useCallback(() => {
|
||||||
|
false && dispatch(toolIsTransformingChanged(true));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const onCancelTransformation = useCallback(() => {
|
||||||
|
dispatch(toolIsTransformingChanged(false));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
useHotkeys(['ctrl+t', 'meta+t'], onTransform, { enabled: !isDisabled }, [isDisabled, onTransform]);
|
||||||
|
|
||||||
|
if (isTransforming) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onClick={onApplyTransformation}>Apply</Button>
|
||||||
|
<Button onClick={onCancelTransformation}>Cancel</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={`${t('unifiedCanvas.transform')} (Ctrl+T)`}
|
aria-label={`${t('unifiedCanvas.transform')} (Ctrl+T)`}
|
||||||
tooltip={`${t('unifiedCanvas.transform')} (Ctrl+T)`}
|
tooltip={`${t('unifiedCanvas.transform')} (Ctrl+T)`}
|
||||||
icon={<PiResizeBold />}
|
icon={<PiResizeBold />}
|
||||||
variant={isSelected ? 'solid' : 'outline'}
|
variant="solid"
|
||||||
onClick={onClick}
|
onClick={onTransform}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -10,19 +10,20 @@ export const ViewToolButton = memo(() => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'view');
|
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'view');
|
||||||
const isDisabled = useAppSelector((s) => s.canvasV2.session.isStaging);
|
const isDisabled = useAppSelector((s) => s.canvasV2.session.isStaging || s.canvasV2.tool.isTransforming);
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
dispatch(toolChanged('view'));
|
dispatch(toolChanged('view'));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
useHotkeys('h', onClick, [onClick]);
|
useHotkeys('h', onClick, { enabled: !isDisabled }, [onClick]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={`${t('unifiedCanvas.view')} (H)`}
|
aria-label={`${t('unifiedCanvas.view')} (H)`}
|
||||||
tooltip={`${t('unifiedCanvas.view')} (H)`}
|
tooltip={`${t('unifiedCanvas.view')} (H)`}
|
||||||
icon={<PiHandBold />}
|
icon={<PiHandBold />}
|
||||||
variant={isSelected ? 'solid' : 'outline'}
|
colorScheme={isSelected ? 'invokeBlue' : 'base'}
|
||||||
|
variant="outline"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
|
@ -5,14 +5,12 @@ import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
|
|||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import { CanvasRect } from 'features/controlLayers/konva/CanvasRect';
|
import { CanvasRect } from 'features/controlLayers/konva/CanvasRect';
|
||||||
import { mapId } from 'features/controlLayers/konva/util';
|
import { mapId } from 'features/controlLayers/konva/util';
|
||||||
import type { BrushLine, EraserLine, LayerEntity, Rect, RectShape } from 'features/controlLayers/store/types';
|
import type { BrushLine, EraserLine, LayerEntity, RectShape } from 'features/controlLayers/store/types';
|
||||||
import { isDrawingTool } from 'features/controlLayers/store/types';
|
import { isDrawingTool } from 'features/controlLayers/store/types';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
const MIN_LAYER_SIZE_PX = 10;
|
|
||||||
|
|
||||||
export class CanvasLayer {
|
export class CanvasLayer {
|
||||||
static NAME_PREFIX = 'layer';
|
static NAME_PREFIX = 'layer';
|
||||||
static LAYER_NAME = `${CanvasLayer.NAME_PREFIX}_layer`;
|
static LAYER_NAME = `${CanvasLayer.NAME_PREFIX}_layer`;
|
||||||
@ -34,14 +32,15 @@ export class CanvasLayer {
|
|||||||
layer: Konva.Layer;
|
layer: Konva.Layer;
|
||||||
bbox: Konva.Rect;
|
bbox: Konva.Rect;
|
||||||
objectGroup: Konva.Group;
|
objectGroup: Konva.Group;
|
||||||
objectGroupBbox: Konva.Rect;
|
|
||||||
positionXLine: Konva.Line;
|
|
||||||
positionYLine: Konva.Line;
|
|
||||||
transformer: Konva.Transformer;
|
transformer: Konva.Transformer;
|
||||||
interactionRect: Konva.Rect;
|
interactionRect: Konva.Rect;
|
||||||
};
|
};
|
||||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect | CanvasImage>;
|
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect | CanvasImage>;
|
||||||
bbox: Rect;
|
|
||||||
|
offsetX: number;
|
||||||
|
offsetY: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
|
||||||
getBbox = debounce(this._getBbox, 300);
|
getBbox = debounce(this._getBbox, 300);
|
||||||
|
|
||||||
@ -59,18 +58,16 @@ export class CanvasLayer {
|
|||||||
strokeHitEnabled: false,
|
strokeHitEnabled: false,
|
||||||
}),
|
}),
|
||||||
objectGroup: new Konva.Group({ name: CanvasLayer.OBJECT_GROUP_NAME, listening: false }),
|
objectGroup: new Konva.Group({ name: CanvasLayer.OBJECT_GROUP_NAME, listening: false }),
|
||||||
objectGroupBbox: new Konva.Rect({ fill: 'green', opacity: 0.5, listening: false }),
|
|
||||||
positionXLine: new Konva.Line({ stroke: 'white', strokeWidth: 1 }),
|
|
||||||
positionYLine: new Konva.Line({ stroke: 'white', strokeWidth: 1 }),
|
|
||||||
transformer: new Konva.Transformer({
|
transformer: new Konva.Transformer({
|
||||||
name: CanvasLayer.TRANSFORMER_NAME,
|
name: CanvasLayer.TRANSFORMER_NAME,
|
||||||
draggable: false,
|
draggable: true,
|
||||||
// enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
// enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
||||||
rotateEnabled: false,
|
rotateEnabled: true,
|
||||||
flipEnabled: false,
|
flipEnabled: true,
|
||||||
listening: false,
|
listening: false,
|
||||||
padding: CanvasLayer.BBOX_PADDING_PX,
|
padding: CanvasLayer.BBOX_PADDING_PX,
|
||||||
stroke: 'hsl(200deg 76% 59%)', // invokeBlue.400
|
stroke: 'hsl(200deg 76% 59%)', // invokeBlue.400
|
||||||
|
keepRatio: false,
|
||||||
}),
|
}),
|
||||||
interactionRect: new Konva.Rect({
|
interactionRect: new Konva.Rect({
|
||||||
name: CanvasLayer.INTERACTION_RECT_NAME,
|
name: CanvasLayer.INTERACTION_RECT_NAME,
|
||||||
@ -84,9 +81,6 @@ export class CanvasLayer {
|
|||||||
this.konva.layer.add(this.konva.transformer);
|
this.konva.layer.add(this.konva.transformer);
|
||||||
this.konva.layer.add(this.konva.interactionRect);
|
this.konva.layer.add(this.konva.interactionRect);
|
||||||
this.konva.layer.add(this.konva.bbox);
|
this.konva.layer.add(this.konva.bbox);
|
||||||
this.konva.layer.add(this.konva.objectGroupBbox);
|
|
||||||
this.konva.layer.add(this.konva.positionXLine);
|
|
||||||
this.konva.layer.add(this.konva.positionYLine);
|
|
||||||
|
|
||||||
this.konva.transformer.on('transformstart', () => {
|
this.konva.transformer.on('transformstart', () => {
|
||||||
console.log('>>> transformstart');
|
console.log('>>> transformstart');
|
||||||
@ -98,35 +92,35 @@ export class CanvasLayer {
|
|||||||
width: this.konva.interactionRect.width(),
|
width: this.konva.interactionRect.width(),
|
||||||
height: this.konva.interactionRect.height(),
|
height: this.konva.interactionRect.height(),
|
||||||
});
|
});
|
||||||
console.log('this.bbox', deepClone(this.bbox));
|
this.logBbox('transformstart bbox');
|
||||||
console.log('this.state.position', this.state.position);
|
console.log('this.state.position', this.state.position);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.konva.transformer.on('transform', () => {
|
this.konva.transformer.on('transform', () => {
|
||||||
// Always snap the interaction rect to the nearest pixel when transforming
|
// Always snap the interaction rect to the nearest pixel when transforming
|
||||||
|
|
||||||
const x = Math.round(this.konva.interactionRect.x());
|
// const x = Math.round(this.konva.interactionRect.x());
|
||||||
const y = Math.round(this.konva.interactionRect.y());
|
// const y = Math.round(this.konva.interactionRect.y());
|
||||||
// Snap its position
|
// // Snap its position
|
||||||
this.konva.interactionRect.x(x);
|
// this.konva.interactionRect.x(x);
|
||||||
this.konva.interactionRect.y(y);
|
// this.konva.interactionRect.y(y);
|
||||||
|
|
||||||
// Calculate the new scale of the interaction rect such that its width and height snap to the nearest pixel
|
// // Calculate the new scale of the interaction rect such that its width and height snap to the nearest pixel
|
||||||
const targetWidth = Math.max(
|
// const targetWidth = Math.max(
|
||||||
Math.round(this.konva.interactionRect.width() * Math.abs(this.konva.interactionRect.scaleX())),
|
// Math.round(this.konva.interactionRect.width() * Math.abs(this.konva.interactionRect.scaleX())),
|
||||||
MIN_LAYER_SIZE_PX
|
// MIN_LAYER_SIZE_PX
|
||||||
);
|
// );
|
||||||
const scaleX = targetWidth / this.konva.interactionRect.width();
|
// const scaleX = targetWidth / this.konva.interactionRect.width();
|
||||||
const targetHeight = Math.max(
|
// const targetHeight = Math.max(
|
||||||
Math.round(this.konva.interactionRect.height() * Math.abs(this.konva.interactionRect.scaleY())),
|
// Math.round(this.konva.interactionRect.height() * Math.abs(this.konva.interactionRect.scaleY())),
|
||||||
MIN_LAYER_SIZE_PX
|
// MIN_LAYER_SIZE_PX
|
||||||
);
|
// );
|
||||||
const scaleY = targetHeight / this.konva.interactionRect.height();
|
// const scaleY = targetHeight / this.konva.interactionRect.height();
|
||||||
|
|
||||||
// Snap the width and height (via scale) of the interaction rect
|
// // Snap the width and height (via scale) of the interaction rect
|
||||||
this.konva.interactionRect.scaleX(scaleX);
|
// this.konva.interactionRect.scaleX(scaleX);
|
||||||
this.konva.interactionRect.scaleY(scaleY);
|
// this.konva.interactionRect.scaleY(scaleY);
|
||||||
this.konva.interactionRect.rotation(0);
|
// this.konva.interactionRect.rotation(0);
|
||||||
|
|
||||||
console.log('>>> transform');
|
console.log('>>> transform');
|
||||||
console.log('activeAnchor', this.konva.transformer.getActiveAnchor());
|
console.log('activeAnchor', this.konva.transformer.getActiveAnchor());
|
||||||
@ -140,119 +134,20 @@ export class CanvasLayer {
|
|||||||
rotation: this.konva.interactionRect.rotation(),
|
rotation: this.konva.interactionRect.rotation(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle anchor-specific transformations of the layer's objects
|
this.konva.objectGroup.setAttrs({
|
||||||
const anchor = this.konva.transformer.getActiveAnchor();
|
x: this.konva.interactionRect.x(),
|
||||||
// 'top-left'
|
y: this.konva.interactionRect.y(),
|
||||||
// 'top-center'
|
scaleX: this.konva.interactionRect.scaleX(),
|
||||||
// 'top-right'
|
scaleY: this.konva.interactionRect.scaleY(),
|
||||||
// 'middle-right'
|
rotation: this.konva.interactionRect.rotation(),
|
||||||
// 'middle-left'
|
|
||||||
// 'bottom-left'
|
|
||||||
// 'bottom-center'
|
|
||||||
// 'bottom-right'
|
|
||||||
if (anchor === 'middle-right') {
|
|
||||||
// Dragging the anchor to the right
|
|
||||||
this.konva.objectGroup.setAttrs({
|
|
||||||
scaleX,
|
|
||||||
x: x - (x - this.state.position.x) * scaleX,
|
|
||||||
});
|
|
||||||
} else if (anchor === 'middle-left') {
|
|
||||||
// Dragging the anchor to the right
|
|
||||||
this.konva.objectGroup.setAttrs({
|
|
||||||
scaleX,
|
|
||||||
x: x - (x - this.state.position.x) * scaleX,
|
|
||||||
});
|
|
||||||
} else if (anchor === 'bottom-center') {
|
|
||||||
// Resize the interaction rect downwards
|
|
||||||
this.konva.objectGroup.setAttrs({
|
|
||||||
scaleY,
|
|
||||||
y: y - (y - this.state.position.y) * scaleY,
|
|
||||||
});
|
|
||||||
} else if (anchor === 'bottom-right') {
|
|
||||||
// Resize the interaction rect to the right and downwards via scale
|
|
||||||
this.konva.objectGroup.setAttrs({
|
|
||||||
scaleX,
|
|
||||||
scaleY,
|
|
||||||
x: x - (x - this.state.position.x) * scaleX,
|
|
||||||
y: y - (y - this.state.position.y) * scaleY,
|
|
||||||
});
|
|
||||||
} else if (anchor === 'top-center') {
|
|
||||||
// Resize the interaction rect to the upwards via scale & y position
|
|
||||||
this.konva.objectGroup.setAttrs({
|
|
||||||
y,
|
|
||||||
scaleY,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.konva.objectGroupBbox.setAttrs({
|
|
||||||
x: this.konva.objectGroup.x(),
|
|
||||||
y: this.konva.objectGroup.y(),
|
|
||||||
rotation: this.konva.objectGroup.rotation(),
|
|
||||||
scaleX: this.konva.objectGroup.scaleX(),
|
|
||||||
scaleY: this.konva.objectGroup.scaleY(),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// this.konva.transformer.on('transform', () => {
|
|
||||||
// // We need to snap the transform to the nearest pixel - both the position and the scale
|
|
||||||
|
|
||||||
// // Snap the interaction rect to the nearest pixel
|
|
||||||
// this.konva.interactionRect.x(Math.round(this.konva.interactionRect.x()));
|
|
||||||
// this.konva.interactionRect.y(Math.round(this.konva.interactionRect.y()));
|
|
||||||
|
|
||||||
// // Calculate the new scale of the interaction rect such that its width and height snap to the nearest pixel
|
|
||||||
// const roundedScaledWidth = Math.round(this.konva.interactionRect.width() * this.konva.interactionRect.scaleX());
|
|
||||||
// const correctedScaleX = roundedScaledWidth / this.konva.interactionRect.width();
|
|
||||||
// const roundedScaledHeight = Math.round(this.konva.interactionRect.height() * this.konva.interactionRect.scaleY());
|
|
||||||
// const correctedScaleY = roundedScaledHeight / this.konva.interactionRect.height();
|
|
||||||
|
|
||||||
// // Update the interaction rect's scale to the corrected scale
|
|
||||||
// this.konva.interactionRect.scaleX(correctedScaleX);
|
|
||||||
// this.konva.interactionRect.scaleY(correctedScaleY);
|
|
||||||
|
|
||||||
// console.log('>>> transform');
|
|
||||||
// console.log('activeAnchor', this.konva.transformer.getActiveAnchor());
|
|
||||||
// console.log('interactionRect', {
|
|
||||||
// x: this.konva.interactionRect.x(),
|
|
||||||
// y: this.konva.interactionRect.y(),
|
|
||||||
// scaleX: this.konva.interactionRect.scaleX(),
|
|
||||||
// scaleY: this.konva.interactionRect.scaleY(),
|
|
||||||
// width: this.konva.interactionRect.width(),
|
|
||||||
// height: this.konva.interactionRect.height(),
|
|
||||||
// rotation: this.konva.interactionRect.rotation(),
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Update the object group to reflect the new scale and position of the interaction rect
|
|
||||||
// this.konva.objectGroup.setAttrs({
|
|
||||||
// // The scale is the same as the interaction rect
|
|
||||||
// scaleX: this.konva.interactionRect.scaleX(),
|
|
||||||
// scaleY: this.konva.interactionRect.scaleY(),
|
|
||||||
// rotation: this.konva.interactionRect.rotation(),
|
|
||||||
// // We need to do some compensation for the new position. The bounds of the object group may be different from the
|
|
||||||
// // interaction rect/bbox, because the object group may have eraser strokes that are not included in the bbox.
|
|
||||||
// x:
|
|
||||||
// this.konva.interactionRect.x() -
|
|
||||||
// Math.abs(this.konva.interactionRect.x() - this.state.position.x) * this.konva.interactionRect.scaleX(),
|
|
||||||
// y:
|
|
||||||
// this.konva.interactionRect.y() -
|
|
||||||
// Math.abs(this.konva.interactionRect.y() - this.state.position.y) * this.konva.interactionRect.scaleY(),
|
|
||||||
// // x: this.konva.interactionRect.x() + (this.konva.interactionRect.x() - this.state.position.x) * this.konva.interactionRect.scaleX(),
|
|
||||||
// // y: this.konva.interactionRect.y() + (this.konva.interactionRect.y() - this.state.position.y) * this.konva.interactionRect.scaleY(),
|
|
||||||
// });
|
|
||||||
// this.konva.objectGroupBbox.setAttrs({
|
|
||||||
// x: this.konva.objectGroup.x(),
|
|
||||||
// y: this.konva.objectGroup.y(),
|
|
||||||
// scaleX: this.konva.objectGroup.scaleX(),
|
|
||||||
// scaleY: this.konva.objectGroup.scaleY(),
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
this.konva.transformer.on('transformend', () => {
|
this.konva.transformer.on('transformend', () => {
|
||||||
this.bbox = {
|
this.offsetX = this.konva.interactionRect.x() - this.state.position.x;
|
||||||
x: this.konva.interactionRect.x(),
|
this.offsetY = this.konva.interactionRect.y() - this.state.position.y;
|
||||||
y: this.konva.interactionRect.y(),
|
this.width = Math.round(this.konva.interactionRect.width() * this.konva.interactionRect.scaleX());
|
||||||
width: Math.round(this.konva.interactionRect.width() * this.konva.interactionRect.scaleX()),
|
this.height = Math.round(this.konva.interactionRect.height() * this.konva.interactionRect.scaleY());
|
||||||
height: Math.round(this.konva.interactionRect.height() * this.konva.interactionRect.scaleY()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// this.manager.stateApi.onPosChanged(
|
// this.manager.stateApi.onPosChanged(
|
||||||
// {
|
// {
|
||||||
// id: this.id,
|
// id: this.id,
|
||||||
@ -260,6 +155,7 @@ export class CanvasLayer {
|
|||||||
// },
|
// },
|
||||||
// 'layer'
|
// 'layer'
|
||||||
// );
|
// );
|
||||||
|
this.logBbox('transformend bbox');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.konva.interactionRect.on('dragmove', () => {
|
this.konva.interactionRect.on('dragmove', () => {
|
||||||
@ -277,19 +173,15 @@ export class CanvasLayer {
|
|||||||
// The object group is translated by the difference between the interaction rect's new and old positions (which is
|
// The object group is translated by the difference between the interaction rect's new and old positions (which is
|
||||||
// stored as this.bbox)
|
// stored as this.bbox)
|
||||||
this.konva.objectGroup.setAttrs({
|
this.konva.objectGroup.setAttrs({
|
||||||
x: this.state.position.x + this.konva.interactionRect.x() - this.bbox.x,
|
x: this.konva.interactionRect.x(),
|
||||||
y: this.state.position.y + this.konva.interactionRect.y() - this.bbox.y,
|
y: this.konva.interactionRect.y(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const rect = this.konva.objectGroup.getClientRect({ skipTransform: true });
|
|
||||||
this.konva.objectGroupBbox.setAttrs({ ...rect, x: this.konva.objectGroup.x(), y: this.konva.objectGroup.y() });
|
|
||||||
});
|
});
|
||||||
this.konva.interactionRect.on('dragend', () => {
|
this.konva.interactionRect.on('dragend', () => {
|
||||||
// Update the bbox
|
this.logBbox('dragend bbox');
|
||||||
this.bbox.x = this.konva.interactionRect.x();
|
|
||||||
this.bbox.y = this.konva.interactionRect.y();
|
|
||||||
|
|
||||||
// Update internal state
|
// Update internal state
|
||||||
|
// this.state.position = { x: this.konva.objectGroup.x(), y: this.konva.objectGroup.y() };
|
||||||
this.manager.stateApi.onPosChanged(
|
this.manager.stateApi.onPosChanged(
|
||||||
{
|
{
|
||||||
id: this.id,
|
id: this.id,
|
||||||
@ -302,7 +194,10 @@ export class CanvasLayer {
|
|||||||
this.objects = new Map();
|
this.objects = new Map();
|
||||||
this.drawingBuffer = null;
|
this.drawingBuffer = null;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.bbox = CanvasLayer.DEFAULT_BBOX_RECT;
|
this.offsetX = 0;
|
||||||
|
this.offsetY = 0;
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
|
|
||||||
console.log(this);
|
console.log(this);
|
||||||
}
|
}
|
||||||
@ -319,6 +214,32 @@ export class CanvasLayer {
|
|||||||
return this.drawingBuffer;
|
return this.drawingBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePosition() {
|
||||||
|
const scale = this.manager.stage.scaleX();
|
||||||
|
const onePixel = 1 / scale;
|
||||||
|
const bboxPadding = CanvasLayer.BBOX_PADDING_PX / scale;
|
||||||
|
|
||||||
|
this.konva.objectGroup.setAttrs({
|
||||||
|
x: this.state.position.x,
|
||||||
|
y: this.state.position.y,
|
||||||
|
offsetX: this.offsetX,
|
||||||
|
offsetY: this.offsetY,
|
||||||
|
});
|
||||||
|
this.konva.bbox.setAttrs({
|
||||||
|
x: this.state.position.x - bboxPadding,
|
||||||
|
y: this.state.position.y - bboxPadding,
|
||||||
|
width: this.width + bboxPadding * 2,
|
||||||
|
height: this.height + bboxPadding * 2,
|
||||||
|
strokeWidth: onePixel,
|
||||||
|
});
|
||||||
|
this.konva.interactionRect.setAttrs({
|
||||||
|
x: this.state.position.x,
|
||||||
|
y: this.state.position.y,
|
||||||
|
width: this.width,
|
||||||
|
height: this.height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async setDrawingBuffer(obj: BrushLine | EraserLine | RectShape | null) {
|
async setDrawingBuffer(obj: BrushLine | EraserLine | RectShape | null) {
|
||||||
if (obj) {
|
if (obj) {
|
||||||
this.drawingBuffer = obj;
|
this.drawingBuffer = obj;
|
||||||
@ -344,27 +265,7 @@ export class CanvasLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async render(state: LayerEntity) {
|
async render(state: LayerEntity) {
|
||||||
this.state = state;
|
this.state = deepClone(state);
|
||||||
|
|
||||||
// Update the layer's position and listening state
|
|
||||||
this.konva.objectGroup.setAttrs({
|
|
||||||
x: state.position.x,
|
|
||||||
y: state.position.y,
|
|
||||||
scaleX: 1,
|
|
||||||
scaleY: 1,
|
|
||||||
});
|
|
||||||
this.konva.positionXLine.points([
|
|
||||||
state.position.x,
|
|
||||||
-this.manager.stage.y(),
|
|
||||||
state.position.x,
|
|
||||||
this.manager.stage.y() + this.manager.stage.height() / this.manager.stage.scaleY(),
|
|
||||||
]);
|
|
||||||
this.konva.positionYLine.points([
|
|
||||||
-this.manager.stage.x(),
|
|
||||||
state.position.y,
|
|
||||||
this.manager.stage.x() + this.manager.stage.width() / this.manager.stage.scaleX(),
|
|
||||||
state.position.y,
|
|
||||||
]);
|
|
||||||
|
|
||||||
let didDraw = false;
|
let didDraw = false;
|
||||||
|
|
||||||
@ -465,9 +366,12 @@ export class CanvasLayer {
|
|||||||
|
|
||||||
if (didDraw) {
|
if (didDraw) {
|
||||||
if (this.objects.size > 0) {
|
if (this.objects.size > 0) {
|
||||||
// this.getBbox();
|
this.getBbox();
|
||||||
} else {
|
} else {
|
||||||
this.bbox = CanvasLayer.DEFAULT_BBOX_RECT;
|
this.offsetX = 0;
|
||||||
|
this.offsetY = 0;
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
this.renderBbox();
|
this.renderBbox();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -475,15 +379,14 @@ export class CanvasLayer {
|
|||||||
this.konva.layer.visible(true);
|
this.konva.layer.visible(true);
|
||||||
this.konva.objectGroup.opacity(this.state.opacity);
|
this.konva.objectGroup.opacity(this.state.opacity);
|
||||||
const isSelected = this.manager.stateApi.getIsSelected(this.id);
|
const isSelected = this.manager.stateApi.getIsSelected(this.id);
|
||||||
const selectedTool = this.manager.stateApi.getToolState().selected;
|
const toolState = this.manager.stateApi.getToolState();
|
||||||
|
|
||||||
const isTransforming = selectedTool === 'transform' && isSelected;
|
const isMoving = toolState.selected === 'move' && isSelected;
|
||||||
const isMoving = selectedTool === 'move' && isSelected;
|
|
||||||
|
|
||||||
this.konva.layer.listening(isTransforming || isMoving);
|
this.konva.layer.listening(toolState.isTransforming || isMoving);
|
||||||
this.konva.transformer.listening(isTransforming);
|
this.konva.transformer.listening(toolState.isTransforming);
|
||||||
this.konva.bbox.visible(isMoving);
|
this.konva.bbox.visible(isMoving);
|
||||||
this.konva.interactionRect.listening(isMoving);
|
this.konva.interactionRect.listening(toolState.isTransforming || isMoving);
|
||||||
|
|
||||||
if (this.objects.size === 0) {
|
if (this.objects.size === 0) {
|
||||||
// If the layer is totally empty, reset the cache and bail out.
|
// If the layer is totally empty, reset the cache and bail out.
|
||||||
@ -491,7 +394,7 @@ export class CanvasLayer {
|
|||||||
if (this.konva.objectGroup.isCached()) {
|
if (this.konva.objectGroup.isCached()) {
|
||||||
this.konva.objectGroup.clearCache();
|
this.konva.objectGroup.clearCache();
|
||||||
}
|
}
|
||||||
} else if (isSelected && selectedTool === 'transform') {
|
} else if (isSelected && toolState.isTransforming) {
|
||||||
// When the layer is selected and being moved, we should always cache it.
|
// When the layer is selected and being moved, we should always cache it.
|
||||||
// We should update the cache if we drew to the layer.
|
// We should update the cache if we drew to the layer.
|
||||||
if (!this.konva.objectGroup.isCached() || didDraw) {
|
if (!this.konva.objectGroup.isCached() || didDraw) {
|
||||||
@ -501,7 +404,7 @@ export class CanvasLayer {
|
|||||||
this.konva.transformer.nodes([this.konva.interactionRect]);
|
this.konva.transformer.nodes([this.konva.interactionRect]);
|
||||||
this.konva.transformer.forceUpdate();
|
this.konva.transformer.forceUpdate();
|
||||||
this.konva.transformer.visible(true);
|
this.konva.transformer.visible(true);
|
||||||
} else if (selectedTool === 'move') {
|
} else if (toolState.selected === 'move') {
|
||||||
// When the layer is selected and being moved, we should always cache it.
|
// When the layer is selected and being moved, we should always cache it.
|
||||||
// We should update the cache if we drew to the layer.
|
// We should update the cache if we drew to the layer.
|
||||||
if (!this.konva.objectGroup.isCached() || didDraw) {
|
if (!this.konva.objectGroup.isCached() || didDraw) {
|
||||||
@ -515,7 +418,7 @@ export class CanvasLayer {
|
|||||||
// If the layer is selected but not using the move tool, we don't want the layer to be listening.
|
// If the layer is selected but not using the move tool, we don't want the layer to be listening.
|
||||||
// The transformer also does not need to be active.
|
// The transformer also does not need to be active.
|
||||||
this.konva.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
if (isDrawingTool(selectedTool)) {
|
if (isDrawingTool(toolState.selected)) {
|
||||||
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
||||||
// should never be cached.
|
// should never be cached.
|
||||||
if (this.konva.objectGroup.isCached()) {
|
if (this.konva.objectGroup.isCached()) {
|
||||||
@ -540,43 +443,23 @@ export class CanvasLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderBbox() {
|
renderBbox() {
|
||||||
|
const toolState = this.manager.stateApi.getToolState();
|
||||||
|
if (toolState.isTransforming) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const isSelected = this.manager.stateApi.getIsSelected(this.id);
|
const isSelected = this.manager.stateApi.getIsSelected(this.id);
|
||||||
const selectedTool = this.manager.stateApi.getToolState().selected;
|
const hasBbox = this.width !== 0 && this.height !== 0;
|
||||||
const scale = this.manager.stage.scaleX();
|
this.konva.bbox.visible(hasBbox && isSelected && toolState.selected === 'move');
|
||||||
const hasBbox = this.bbox.width !== 0 && this.bbox.height !== 0;
|
|
||||||
|
|
||||||
this.konva.bbox.visible(hasBbox && isSelected && selectedTool === 'move');
|
|
||||||
this.konva.interactionRect.visible(hasBbox);
|
this.konva.interactionRect.visible(hasBbox);
|
||||||
const rect = this.konva.objectGroup.getClientRect({ skipTransform: true });
|
this.updatePosition();
|
||||||
this.konva.objectGroupBbox.setAttrs({
|
|
||||||
...rect,
|
|
||||||
x: this.konva.objectGroup.x(),
|
|
||||||
y: this.konva.objectGroup.y(),
|
|
||||||
scaleX: 1,
|
|
||||||
scaleY: 1,
|
|
||||||
});
|
|
||||||
this.konva.bbox.setAttrs({
|
|
||||||
x: this.bbox.x - CanvasLayer.BBOX_PADDING_PX / scale,
|
|
||||||
y: this.bbox.y - CanvasLayer.BBOX_PADDING_PX / scale,
|
|
||||||
width: this.bbox.width + (CanvasLayer.BBOX_PADDING_PX / scale) * 2,
|
|
||||||
height: this.bbox.height + (CanvasLayer.BBOX_PADDING_PX / scale) * 2,
|
|
||||||
scaleX: 1,
|
|
||||||
scaleY: 1,
|
|
||||||
strokeWidth: 1 / this.manager.stage.scaleX(),
|
|
||||||
});
|
|
||||||
this.konva.interactionRect.setAttrs({
|
|
||||||
x: this.bbox.x,
|
|
||||||
y: this.bbox.y,
|
|
||||||
width: this.bbox.width,
|
|
||||||
height: this.bbox.height,
|
|
||||||
scaleX: 1,
|
|
||||||
scaleY: 1,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getBbox() {
|
private _getBbox() {
|
||||||
if (this.objects.size === 0) {
|
if (this.objects.size === 0) {
|
||||||
this.bbox = CanvasLayer.DEFAULT_BBOX_RECT;
|
this.offsetX = 0;
|
||||||
|
this.offsetY = 0;
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
this.renderBbox();
|
this.renderBbox();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -595,16 +478,21 @@ export class CanvasLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!needsPixelBbox) {
|
if (!needsPixelBbox) {
|
||||||
if (rect.width === 0 || rect.height === 0) {
|
this.offsetX = rect.x;
|
||||||
this.bbox = CanvasLayer.DEFAULT_BBOX_RECT;
|
this.offsetY = rect.y;
|
||||||
} else {
|
this.width = rect.width;
|
||||||
this.bbox = {
|
this.height = rect.height;
|
||||||
x: this.konva.objectGroup.x() + rect.x,
|
// if (rect.width === 0 || rect.height === 0) {
|
||||||
y: this.konva.objectGroup.y() + rect.y,
|
// this.bbox = CanvasLayer.DEFAULT_BBOX_RECT;
|
||||||
width: rect.width,
|
// } else {
|
||||||
height: rect.height,
|
// this.bbox = {
|
||||||
};
|
// x: rect.x,
|
||||||
}
|
// y: rect.y,
|
||||||
|
// width: rect.width,
|
||||||
|
// height: rect.height,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
this.logBbox('new bbox from client rect');
|
||||||
this.renderBbox();
|
this.renderBbox();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -621,21 +509,34 @@ export class CanvasLayer {
|
|||||||
this.manager.requestBbox(
|
this.manager.requestBbox(
|
||||||
{ buffer: imageData.data.buffer, width: imageData.width, height: imageData.height },
|
{ buffer: imageData.data.buffer, width: imageData.width, height: imageData.height },
|
||||||
(extents) => {
|
(extents) => {
|
||||||
|
console.log('extents', extents);
|
||||||
if (extents) {
|
if (extents) {
|
||||||
const { minX, minY, maxX, maxY } = extents;
|
const { minX, minY, maxX, maxY } = extents;
|
||||||
this.bbox = {
|
this.offsetX = minX + rect.x;
|
||||||
x: this.konva.objectGroup.x() + rect.x + minX,
|
this.offsetY = minY + rect.y;
|
||||||
y: this.konva.objectGroup.y() + rect.y + minY,
|
this.width = maxX - minX;
|
||||||
width: maxX - minX,
|
this.height = maxY - minY;
|
||||||
height: maxY - minY,
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
this.bbox = CanvasLayer.DEFAULT_BBOX_RECT;
|
this.offsetX = 0;
|
||||||
|
this.offsetY = 0;
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
}
|
}
|
||||||
console.log('new bbox', deepClone(this.bbox));
|
this.logBbox('new bbox from worker');
|
||||||
this.renderBbox();
|
this.renderBbox();
|
||||||
clone.destroy();
|
clone.destroy();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logBbox(msg: string = 'bbox') {
|
||||||
|
console.log(msg, {
|
||||||
|
x: this.state.position.x,
|
||||||
|
y: this.state.position.y,
|
||||||
|
offsetX: this.offsetX,
|
||||||
|
offsetY: this.offsetY,
|
||||||
|
width: this.width,
|
||||||
|
height: this.height,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ export class CanvasTool {
|
|||||||
} else if (!isDrawableEntity) {
|
} else if (!isDrawableEntity) {
|
||||||
// Non-drawable layers don't have tools
|
// Non-drawable layers don't have tools
|
||||||
stage.container().style.cursor = 'not-allowed';
|
stage.container().style.cursor = 'not-allowed';
|
||||||
} else if (tool === 'move') {
|
} else if (tool === 'move' || toolState.isTransforming) {
|
||||||
// Move tool gets a pointer
|
// Move tool gets a pointer
|
||||||
stage.container().style.cursor = 'default';
|
stage.container().style.cursor = 'default';
|
||||||
} else if (tool === 'rect') {
|
} else if (tool === 'rect') {
|
||||||
|
@ -194,6 +194,7 @@ export const {
|
|||||||
allEntitiesDeleted,
|
allEntitiesDeleted,
|
||||||
clipToBboxChanged,
|
clipToBboxChanged,
|
||||||
canvasReset,
|
canvasReset,
|
||||||
|
toolIsTransformingChanged,
|
||||||
// bbox
|
// bbox
|
||||||
bboxChanged,
|
bboxChanged,
|
||||||
bboxScaledSizeChanged,
|
bboxScaledSizeChanged,
|
||||||
|
@ -20,4 +20,7 @@ export const toolReducers = {
|
|||||||
toolBufferChanged: (state, action: PayloadAction<Tool | null>) => {
|
toolBufferChanged: (state, action: PayloadAction<Tool | null>) => {
|
||||||
state.tool.selectedBuffer = action.payload;
|
state.tool.selectedBuffer = action.payload;
|
||||||
},
|
},
|
||||||
|
toolIsTransformingChanged: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.tool.isTransforming = action.payload;
|
||||||
|
},
|
||||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||||
|
@ -464,7 +464,7 @@ export const CA_PROCESSOR_DATA: CAProcessorsData = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const zTool = z.enum(['brush', 'eraser', 'move', 'rect', 'view', 'bbox', 'transform']);
|
const zTool = z.enum(['brush', 'eraser', 'move', 'rect', 'view', 'bbox']);
|
||||||
export type Tool = z.infer<typeof zTool>;
|
export type Tool = z.infer<typeof zTool>;
|
||||||
export function isDrawingTool(tool: Tool): tool is 'brush' | 'eraser' | 'rect' {
|
export function isDrawingTool(tool: Tool): tool is 'brush' | 'eraser' | 'rect' {
|
||||||
return tool === 'brush' || tool === 'eraser' || tool === 'rect';
|
return tool === 'brush' || tool === 'eraser' || tool === 'rect';
|
||||||
@ -850,6 +850,7 @@ export type CanvasV2State = {
|
|||||||
brush: { width: number };
|
brush: { width: number };
|
||||||
eraser: { width: number };
|
eraser: { width: number };
|
||||||
fill: RgbaColor;
|
fill: RgbaColor;
|
||||||
|
isTransforming: boolean;
|
||||||
};
|
};
|
||||||
settings: {
|
settings: {
|
||||||
imageSmoothing: boolean;
|
imageSmoothing: boolean;
|
||||||
|
Loading…
Reference in New Issue
Block a user