From 022bb8649cec8c2a59a56e25a6869d547e19c4bb Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Thu, 18 Jul 2024 21:04:38 +1000
Subject: [PATCH] feat(ui): move tool now only moves
---
.../components/StageComponent.tsx | 4 +-
.../controlLayers/konva/CanvasLayer.ts | 81 +++++++++++++------
.../controlLayers/konva/CanvasManager.ts | 5 +-
.../controlLayers/konva/CanvasStateApi.ts | 5 ++
.../src/features/controlLayers/konva/util.ts | 9 ++-
.../src/features/controlLayers/store/types.ts | 2 +-
6 files changed, 78 insertions(+), 28 deletions(-)
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx
index 1db97cce03..31072b0fa1 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx
@@ -10,6 +10,8 @@ import { v4 as uuidv4 } from 'uuid';
const log = logger('canvas');
+const showHud = false;
+
// This will log warnings when layers > 5 - maybe use `import.meta.env.MODE === 'development'` instead?
Konva.showWarnings = false;
@@ -83,7 +85,7 @@ export const StageComponent = memo(({ asPreview = false }: Props) => {
/>
{!asPreview && (
-
+ {showHud && }
)}
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts
index b9ced230e5..df8392c4f5 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts
@@ -16,6 +16,7 @@ export class CanvasLayer {
static TRANSFORMER_NAME = `${CanvasLayer.NAME_PREFIX}_transformer`;
static GROUP_NAME = `${CanvasLayer.NAME_PREFIX}_group`;
static OBJECT_GROUP_NAME = `${CanvasLayer.NAME_PREFIX}_object-group`;
+ static BBOX_NAME = `${CanvasLayer.NAME_PREFIX}_bbox`;
private drawingBuffer: BrushLine | EraserLine | RectShape | null;
private state: LayerEntity;
@@ -39,21 +40,26 @@ export class CanvasLayer {
this.id = state.id;
this.manager = manager;
this.konva = {
- layer: new Konva.Layer({ name: CanvasLayer.LAYER_NAME, listening: false }),
- group: new Konva.Group({ name: CanvasLayer.GROUP_NAME, listening: true }),
+ layer: new Konva.Layer({ id: this.id, name: CanvasLayer.LAYER_NAME, listening: false }),
+ group: new Konva.Group({ name: CanvasLayer.GROUP_NAME, listening: false, draggable: true }),
bbox: new Konva.Rect({
- listening: true,
+ listening: false,
+ name: CanvasLayer.BBOX_NAME,
stroke: 'hsl(200deg 76% 59%)', // invokeBlue.400
+ fill: '',
+ perfectDrawEnabled: false,
+ strokeHitEnabled: false,
}),
objectGroup: new Konva.Group({ name: CanvasLayer.OBJECT_GROUP_NAME, listening: false }),
transformer: new Konva.Transformer({
name: CanvasLayer.TRANSFORMER_NAME,
shouldOverdrawWholeArea: true,
- draggable: true,
+ draggable: false,
dragDistance: 0,
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
rotateEnabled: false,
flipEnabled: false,
+ listening: false,
}),
};
@@ -71,7 +77,7 @@ export class CanvasLayer {
'layer'
);
});
- this.konva.transformer.on('dragend', () => {
+ this.konva.group.on('dragend', () => {
this.manager.stateApi.onPosChanged(
{ id: this.id, position: { x: this.konva.group.x(), y: this.konva.group.y() } },
'layer'
@@ -152,6 +158,7 @@ export class CanvasLayer {
}
}
+ this.renderBbox();
this.updateGroup(didDraw);
}
@@ -225,7 +232,12 @@ export class CanvasLayer {
}
if (didDraw) {
- this.getBbox();
+ if (this.objects.size > 0) {
+ this.getBbox();
+ } else {
+ this.bbox = null;
+ this.renderBbox();
+ }
}
this.konva.layer.visible(true);
@@ -233,26 +245,42 @@ export class CanvasLayer {
const isSelected = this.manager.stateApi.getIsSelected(this.id);
const selectedTool = this.manager.stateApi.getToolState().selected;
+ const transformerListening = selectedTool === 'transform' && isSelected;
+ const bboxListening = selectedTool === 'move' && isSelected;
+
+ this.konva.layer.listening(transformerListening || bboxListening);
+ this.konva.transformer.listening(transformerListening);
+ this.konva.group.listening(bboxListening);
+ this.konva.bbox.listening(bboxListening);
+
if (this.objects.size === 0) {
// If the layer is totally empty, reset the cache and bail out.
- this.konva.layer.listening(false);
this.konva.transformer.nodes([]);
if (this.konva.group.isCached()) {
this.konva.group.clearCache();
}
- } else if (isSelected && selectedTool === 'move') {
+ } else if (isSelected && selectedTool === 'transform') {
// When the layer is selected and being moved, we should always cache it.
// We should update the cache if we drew to the layer.
if (!this.konva.group.isCached() || didDraw) {
// this.konva.group.cache();
}
// Activate the transformer
- this.konva.layer.listening(true);
this.konva.transformer.nodes([this.konva.group]);
this.konva.transformer.forceUpdate();
- } else if (isSelected && selectedTool !== 'move') {
+ this.konva.transformer.visible(true);
+ } else if (selectedTool === 'move') {
+ // When the layer is selected and being moved, we should always cache it.
+ // We should update the cache if we drew to the layer.
+ if (!this.konva.group.isCached() || didDraw) {
+ // this.konva.group.cache();
+ }
+ // Activate the transformer
+ this.konva.transformer.nodes([]);
+ this.konva.transformer.forceUpdate();
+ this.konva.transformer.visible(false);
+ } else if (isSelected) {
// If the layer is selected but not using the move tool, we don't want the layer to be listening.
- this.konva.layer.listening(false);
// The transformer also does not need to be active.
this.konva.transformer.nodes([]);
if (isDrawingTool(selectedTool)) {
@@ -270,7 +298,6 @@ export class CanvasLayer {
}
} else if (!isSelected) {
// Unselected layers should not be listening
- this.konva.layer.listening(false);
// The transformer also does not need to be active.
this.konva.transformer.nodes([]);
// Update the layer's cache if it's not already cached or we drew to it.
@@ -281,16 +308,23 @@ export class CanvasLayer {
}
renderBbox() {
- if (!this.bbox) {
- this.konva.bbox.visible(false);
- return;
- }
- this.konva.bbox.visible(true);
- this.konva.bbox.strokeWidth(1 / this.manager.stage.scaleX());
- this.konva.bbox.setAttrs(this.bbox);
+ const isSelected = this.manager.stateApi.getIsSelected(this.id);
+ const selectedTool = this.manager.stateApi.getToolState().selected;
+
+ this.konva.bbox.setAttrs({
+ ...this.bbox,
+ strokeWidth: 1 / this.manager.stage.scaleX(),
+ visible: this.bbox !== null && selectedTool === 'move' && isSelected,
+ });
}
private _getBbox() {
+ if (this.objects.size === 0) {
+ this.bbox = null;
+ this.renderBbox();
+ return;
+ }
+
let needsPixelBbox = false;
const rect = this.konva.objectGroup.getClientRect({ skipTransform: true });
// console.log('rect', rect);
@@ -334,11 +368,12 @@ export class CanvasLayer {
(extents) => {
// console.log('extents', extents);
if (extents) {
+ const { minX, minY, maxX, maxY } = extents;
this.bbox = {
- x: extents.minX + rect.x - Math.floor(this.konva.layer.x()),
- y: extents.minY + rect.y - Math.floor(this.konva.layer.y()),
- width: extents.maxX - extents.minX,
- height: extents.maxY - extents.minY,
+ x: minX + rect.x - Math.floor(this.konva.layer.x()),
+ y: minY + rect.y - Math.floor(this.konva.layer.y()),
+ width: maxX - minX,
+ height: maxY - minY,
};
} else {
this.bbox = null;
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts
index 7c56b21a7e..a294f71506 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts
@@ -33,6 +33,7 @@ import { CanvasTool } from './CanvasTool';
import { setStageEventHandlers } from './events';
const log = logger('canvas');
+const workerLog = logger('worker');
// type Extents = {
// minX: number;
@@ -137,9 +138,9 @@ export class CanvasManager {
const { type, data } = event.data;
if (type === 'log') {
if (data.ctx) {
- log[data.level](data.ctx, data.message);
+ workerLog[data.level](data.ctx, data.message);
} else {
- log[data.level](data.message);
+ workerLog[data.level](data.message);
}
} else if (type === 'extents') {
const task = this.tasks.get(data.id);
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts
index 162789bd2e..f16fd45ec5 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts
@@ -17,6 +17,7 @@ import {
caBboxChanged,
caScaled,
caTranslated,
+ entitySelected,
eraserWidthChanged,
imBboxChanged,
imBrushLineAdded,
@@ -136,6 +137,10 @@ export class CanvasStateApi {
this.store.dispatch(imRectShapeAdded(arg));
}
};
+ onEntitySelected = (arg: { id: string; type: CanvasEntity['type'] }) => {
+ log.debug('Entity selected');
+ this.store.dispatch(entitySelected(arg));
+ };
onBboxTransformed = (bbox: IRect) => {
log.debug('Generation bbox transformed');
this.store.dispatch(bboxChanged(bbox));
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/util.ts b/invokeai/frontend/web/src/features/controlLayers/konva/util.ts
index ba6064d53b..fc764afe04 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/util.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/util.ts
@@ -1,4 +1,5 @@
import { getImageDataTransparency } from 'common/util/arrayBuffer';
+import { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { GenerationMode, Rect, RgbaColor } from 'features/controlLayers/store/types';
import { isValidLayer } from 'features/nodes/util/graph/generation/addLayers';
@@ -366,7 +367,7 @@ export function getCompositeLayerStageClone(arg: { manager: CanvasManager }): Ko
stageClone.y(0);
const validLayers = layersState.entities.filter(isValidLayer);
-
+ console.log(validLayers);
// Konva bug (?) - when iterating over the array returned from `stage.getLayers()`, if you destroy a layer, the array
// is mutated in-place and the next iteration will skip the next layer. To avoid this, we first collect the layers
// to delete in a separate array and then destroy them.
@@ -376,7 +377,10 @@ export function getCompositeLayerStageClone(arg: { manager: CanvasManager }): Ko
for (const konvaLayer of stageClone.getLayers()) {
const layer = validLayers.find((l) => l.id === konvaLayer.id());
if (!layer) {
+ console.log('deleting', konvaLayer);
toDelete.push(konvaLayer);
+ } else {
+ konvaLayer.findOne(`.${CanvasLayer.GROUP_NAME}`)?.findOne(`.${CanvasLayer.BBOX_NAME}`)?.destroy();
}
}
@@ -395,6 +399,9 @@ export function getGenerationMode(arg: { manager: CanvasManager }): GenerationMo
const inpaintMaskTransparency = getImageDataTransparency(inpaintMaskImageData);
const compositeLayer = getCompositeLayerStageClone(arg);
const compositeLayerImageData = konvaNodeToImageData(compositeLayer, { x, y, width, height });
+ imageDataToBlob(compositeLayerImageData).then((blob) => {
+ previewBlob(blob, 'composite layer');
+ });
const compositeLayerTransparency = getImageDataTransparency(compositeLayerImageData);
if (compositeLayerTransparency.isPartiallyTransparent) {
if (compositeLayerTransparency.isFullyTransparent) {
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts
index be4c3e1e9d..eaa2e32c64 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts
@@ -464,7 +464,7 @@ export const CA_PROCESSOR_DATA: CAProcessorsData = {
},
};
-const zTool = z.enum(['brush', 'eraser', 'move', 'rect', 'view', 'bbox']);
+const zTool = z.enum(['brush', 'eraser', 'move', 'rect', 'view', 'bbox', 'transform']);
export type Tool = z.infer;
export function isDrawingTool(tool: Tool): tool is 'brush' | 'eraser' | 'rect' {
return tool === 'brush' || tool === 'eraser' || tool === 'rect';