mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): move tool now only moves
This commit is contained in:
parent
778ee2c679
commit
7bdfd3ef5f
@ -10,6 +10,8 @@ import { v4 as uuidv4 } from 'uuid';
|
|||||||
|
|
||||||
const log = logger('canvas');
|
const log = logger('canvas');
|
||||||
|
|
||||||
|
const showHud = false;
|
||||||
|
|
||||||
// This will log warnings when layers > 5 - maybe use `import.meta.env.MODE === 'development'` instead?
|
// This will log warnings when layers > 5 - maybe use `import.meta.env.MODE === 'development'` instead?
|
||||||
Konva.showWarnings = false;
|
Konva.showWarnings = false;
|
||||||
|
|
||||||
@ -83,7 +85,7 @@ export const StageComponent = memo(({ asPreview = false }: Props) => {
|
|||||||
/>
|
/>
|
||||||
{!asPreview && (
|
{!asPreview && (
|
||||||
<Flex position="absolute" top={0} insetInlineStart={0} pointerEvents="none">
|
<Flex position="absolute" top={0} insetInlineStart={0} pointerEvents="none">
|
||||||
<HeadsUpDisplay />
|
{showHud && <HeadsUpDisplay />}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -16,6 +16,7 @@ export class CanvasLayer {
|
|||||||
static TRANSFORMER_NAME = `${CanvasLayer.NAME_PREFIX}_transformer`;
|
static TRANSFORMER_NAME = `${CanvasLayer.NAME_PREFIX}_transformer`;
|
||||||
static GROUP_NAME = `${CanvasLayer.NAME_PREFIX}_group`;
|
static GROUP_NAME = `${CanvasLayer.NAME_PREFIX}_group`;
|
||||||
static OBJECT_GROUP_NAME = `${CanvasLayer.NAME_PREFIX}_object-group`;
|
static OBJECT_GROUP_NAME = `${CanvasLayer.NAME_PREFIX}_object-group`;
|
||||||
|
static BBOX_NAME = `${CanvasLayer.NAME_PREFIX}_bbox`;
|
||||||
|
|
||||||
private drawingBuffer: BrushLine | EraserLine | RectShape | null;
|
private drawingBuffer: BrushLine | EraserLine | RectShape | null;
|
||||||
private state: LayerEntity;
|
private state: LayerEntity;
|
||||||
@ -39,21 +40,26 @@ export class CanvasLayer {
|
|||||||
this.id = state.id;
|
this.id = state.id;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.konva = {
|
this.konva = {
|
||||||
layer: new Konva.Layer({ name: CanvasLayer.LAYER_NAME, listening: false }),
|
layer: new Konva.Layer({ id: this.id, name: CanvasLayer.LAYER_NAME, listening: false }),
|
||||||
group: new Konva.Group({ name: CanvasLayer.GROUP_NAME, listening: true }),
|
group: new Konva.Group({ name: CanvasLayer.GROUP_NAME, listening: false, draggable: true }),
|
||||||
bbox: new Konva.Rect({
|
bbox: new Konva.Rect({
|
||||||
listening: true,
|
listening: false,
|
||||||
|
name: CanvasLayer.BBOX_NAME,
|
||||||
stroke: 'hsl(200deg 76% 59%)', // invokeBlue.400
|
stroke: 'hsl(200deg 76% 59%)', // invokeBlue.400
|
||||||
|
fill: '',
|
||||||
|
perfectDrawEnabled: 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 }),
|
||||||
transformer: new Konva.Transformer({
|
transformer: new Konva.Transformer({
|
||||||
name: CanvasLayer.TRANSFORMER_NAME,
|
name: CanvasLayer.TRANSFORMER_NAME,
|
||||||
shouldOverdrawWholeArea: true,
|
shouldOverdrawWholeArea: true,
|
||||||
draggable: true,
|
draggable: false,
|
||||||
dragDistance: 0,
|
dragDistance: 0,
|
||||||
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
||||||
rotateEnabled: false,
|
rotateEnabled: false,
|
||||||
flipEnabled: false,
|
flipEnabled: false,
|
||||||
|
listening: false,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -71,7 +77,7 @@ export class CanvasLayer {
|
|||||||
'layer'
|
'layer'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.konva.transformer.on('dragend', () => {
|
this.konva.group.on('dragend', () => {
|
||||||
this.manager.stateApi.onPosChanged(
|
this.manager.stateApi.onPosChanged(
|
||||||
{ id: this.id, position: { x: this.konva.group.x(), y: this.konva.group.y() } },
|
{ id: this.id, position: { x: this.konva.group.x(), y: this.konva.group.y() } },
|
||||||
'layer'
|
'layer'
|
||||||
@ -152,6 +158,7 @@ export class CanvasLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.renderBbox();
|
||||||
this.updateGroup(didDraw);
|
this.updateGroup(didDraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,7 +232,12 @@ export class CanvasLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (didDraw) {
|
if (didDraw) {
|
||||||
this.getBbox();
|
if (this.objects.size > 0) {
|
||||||
|
this.getBbox();
|
||||||
|
} else {
|
||||||
|
this.bbox = null;
|
||||||
|
this.renderBbox();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.konva.layer.visible(true);
|
this.konva.layer.visible(true);
|
||||||
@ -233,26 +245,42 @@ export class CanvasLayer {
|
|||||||
const isSelected = this.manager.stateApi.getIsSelected(this.id);
|
const isSelected = this.manager.stateApi.getIsSelected(this.id);
|
||||||
const selectedTool = this.manager.stateApi.getToolState().selected;
|
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 (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.
|
||||||
this.konva.layer.listening(false);
|
|
||||||
this.konva.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
if (this.konva.group.isCached()) {
|
if (this.konva.group.isCached()) {
|
||||||
this.konva.group.clearCache();
|
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.
|
// 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.group.isCached() || didDraw) {
|
if (!this.konva.group.isCached() || didDraw) {
|
||||||
// this.konva.group.cache();
|
// this.konva.group.cache();
|
||||||
}
|
}
|
||||||
// Activate the transformer
|
// Activate the transformer
|
||||||
this.konva.layer.listening(true);
|
|
||||||
this.konva.transformer.nodes([this.konva.group]);
|
this.konva.transformer.nodes([this.konva.group]);
|
||||||
this.konva.transformer.forceUpdate();
|
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.
|
// 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.
|
// The transformer also does not need to be active.
|
||||||
this.konva.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
if (isDrawingTool(selectedTool)) {
|
if (isDrawingTool(selectedTool)) {
|
||||||
@ -270,7 +298,6 @@ export class CanvasLayer {
|
|||||||
}
|
}
|
||||||
} else if (!isSelected) {
|
} else if (!isSelected) {
|
||||||
// Unselected layers should not be listening
|
// Unselected layers should not be listening
|
||||||
this.konva.layer.listening(false);
|
|
||||||
// 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([]);
|
||||||
// Update the layer's cache if it's not already cached or we drew to it.
|
// Update the layer's cache if it's not already cached or we drew to it.
|
||||||
@ -281,16 +308,23 @@ export class CanvasLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderBbox() {
|
renderBbox() {
|
||||||
if (!this.bbox) {
|
const isSelected = this.manager.stateApi.getIsSelected(this.id);
|
||||||
this.konva.bbox.visible(false);
|
const selectedTool = this.manager.stateApi.getToolState().selected;
|
||||||
return;
|
|
||||||
}
|
this.konva.bbox.setAttrs({
|
||||||
this.konva.bbox.visible(true);
|
...this.bbox,
|
||||||
this.konva.bbox.strokeWidth(1 / this.manager.stage.scaleX());
|
strokeWidth: 1 / this.manager.stage.scaleX(),
|
||||||
this.konva.bbox.setAttrs(this.bbox);
|
visible: this.bbox !== null && selectedTool === 'move' && isSelected,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getBbox() {
|
private _getBbox() {
|
||||||
|
if (this.objects.size === 0) {
|
||||||
|
this.bbox = null;
|
||||||
|
this.renderBbox();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let needsPixelBbox = false;
|
let needsPixelBbox = false;
|
||||||
const rect = this.konva.objectGroup.getClientRect({ skipTransform: true });
|
const rect = this.konva.objectGroup.getClientRect({ skipTransform: true });
|
||||||
// console.log('rect', rect);
|
// console.log('rect', rect);
|
||||||
@ -334,11 +368,12 @@ export class CanvasLayer {
|
|||||||
(extents) => {
|
(extents) => {
|
||||||
// console.log('extents', extents);
|
// console.log('extents', extents);
|
||||||
if (extents) {
|
if (extents) {
|
||||||
|
const { minX, minY, maxX, maxY } = extents;
|
||||||
this.bbox = {
|
this.bbox = {
|
||||||
x: extents.minX + rect.x - Math.floor(this.konva.layer.x()),
|
x: minX + rect.x - Math.floor(this.konva.layer.x()),
|
||||||
y: extents.minY + rect.y - Math.floor(this.konva.layer.y()),
|
y: minY + rect.y - Math.floor(this.konva.layer.y()),
|
||||||
width: extents.maxX - extents.minX,
|
width: maxX - minX,
|
||||||
height: extents.maxY - extents.minY,
|
height: maxY - minY,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
this.bbox = null;
|
this.bbox = null;
|
||||||
|
@ -33,6 +33,7 @@ import { CanvasTool } from './CanvasTool';
|
|||||||
import { setStageEventHandlers } from './events';
|
import { setStageEventHandlers } from './events';
|
||||||
|
|
||||||
const log = logger('canvas');
|
const log = logger('canvas');
|
||||||
|
const workerLog = logger('worker');
|
||||||
|
|
||||||
// type Extents = {
|
// type Extents = {
|
||||||
// minX: number;
|
// minX: number;
|
||||||
@ -137,9 +138,9 @@ export class CanvasManager {
|
|||||||
const { type, data } = event.data;
|
const { type, data } = event.data;
|
||||||
if (type === 'log') {
|
if (type === 'log') {
|
||||||
if (data.ctx) {
|
if (data.ctx) {
|
||||||
log[data.level](data.ctx, data.message);
|
workerLog[data.level](data.ctx, data.message);
|
||||||
} else {
|
} else {
|
||||||
log[data.level](data.message);
|
workerLog[data.level](data.message);
|
||||||
}
|
}
|
||||||
} else if (type === 'extents') {
|
} else if (type === 'extents') {
|
||||||
const task = this.tasks.get(data.id);
|
const task = this.tasks.get(data.id);
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
caBboxChanged,
|
caBboxChanged,
|
||||||
caScaled,
|
caScaled,
|
||||||
caTranslated,
|
caTranslated,
|
||||||
|
entitySelected,
|
||||||
eraserWidthChanged,
|
eraserWidthChanged,
|
||||||
imBboxChanged,
|
imBboxChanged,
|
||||||
imBrushLineAdded,
|
imBrushLineAdded,
|
||||||
@ -136,6 +137,10 @@ export class CanvasStateApi {
|
|||||||
this.store.dispatch(imRectShapeAdded(arg));
|
this.store.dispatch(imRectShapeAdded(arg));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
onEntitySelected = (arg: { id: string; type: CanvasEntity['type'] }) => {
|
||||||
|
log.debug('Entity selected');
|
||||||
|
this.store.dispatch(entitySelected(arg));
|
||||||
|
};
|
||||||
onBboxTransformed = (bbox: IRect) => {
|
onBboxTransformed = (bbox: IRect) => {
|
||||||
log.debug('Generation bbox transformed');
|
log.debug('Generation bbox transformed');
|
||||||
this.store.dispatch(bboxChanged(bbox));
|
this.store.dispatch(bboxChanged(bbox));
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { getImageDataTransparency } from 'common/util/arrayBuffer';
|
import { getImageDataTransparency } from 'common/util/arrayBuffer';
|
||||||
|
import { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import type { GenerationMode, Rect, RgbaColor } from 'features/controlLayers/store/types';
|
import type { GenerationMode, Rect, RgbaColor } from 'features/controlLayers/store/types';
|
||||||
import { isValidLayer } from 'features/nodes/util/graph/generation/addLayers';
|
import { isValidLayer } from 'features/nodes/util/graph/generation/addLayers';
|
||||||
@ -366,7 +367,7 @@ export function getCompositeLayerStageClone(arg: { manager: CanvasManager }): Ko
|
|||||||
stageClone.y(0);
|
stageClone.y(0);
|
||||||
|
|
||||||
const validLayers = layersState.entities.filter(isValidLayer);
|
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
|
// 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
|
// 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.
|
// 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()) {
|
for (const konvaLayer of stageClone.getLayers()) {
|
||||||
const layer = validLayers.find((l) => l.id === konvaLayer.id());
|
const layer = validLayers.find((l) => l.id === konvaLayer.id());
|
||||||
if (!layer) {
|
if (!layer) {
|
||||||
|
console.log('deleting', konvaLayer);
|
||||||
toDelete.push(konvaLayer);
|
toDelete.push(konvaLayer);
|
||||||
|
} else {
|
||||||
|
konvaLayer.findOne<Konva.Group>(`.${CanvasLayer.GROUP_NAME}`)?.findOne(`.${CanvasLayer.BBOX_NAME}`)?.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,6 +399,9 @@ export function getGenerationMode(arg: { manager: CanvasManager }): GenerationMo
|
|||||||
const inpaintMaskTransparency = getImageDataTransparency(inpaintMaskImageData);
|
const inpaintMaskTransparency = getImageDataTransparency(inpaintMaskImageData);
|
||||||
const compositeLayer = getCompositeLayerStageClone(arg);
|
const compositeLayer = getCompositeLayerStageClone(arg);
|
||||||
const compositeLayerImageData = konvaNodeToImageData(compositeLayer, { x, y, width, height });
|
const compositeLayerImageData = konvaNodeToImageData(compositeLayer, { x, y, width, height });
|
||||||
|
imageDataToBlob(compositeLayerImageData).then((blob) => {
|
||||||
|
previewBlob(blob, 'composite layer');
|
||||||
|
});
|
||||||
const compositeLayerTransparency = getImageDataTransparency(compositeLayerImageData);
|
const compositeLayerTransparency = getImageDataTransparency(compositeLayerImageData);
|
||||||
if (compositeLayerTransparency.isPartiallyTransparent) {
|
if (compositeLayerTransparency.isPartiallyTransparent) {
|
||||||
if (compositeLayerTransparency.isFullyTransparent) {
|
if (compositeLayerTransparency.isFullyTransparent) {
|
||||||
|
@ -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<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';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user