feat(ui): masking UX (wip - interaction state issue)

This commit is contained in:
psychedelicious
2024-10-22 21:22:39 +10:00
parent 606c4ae88c
commit 38b09d73e4
5 changed files with 70 additions and 28 deletions

View File

@@ -553,6 +553,7 @@ export abstract class CanvasEntityAdapterBase<
transformer: this.transformer.repr(),
renderer: this.renderer.repr(),
bufferRenderer: this.bufferRenderer.repr(),
segmentAnything: this.segmentAnything?.repr(),
filterer: this.filterer?.repr(),
hasCache: this.$canvasCache.get() !== null,
isLocked: this.$isLocked.get(),

View File

@@ -591,9 +591,10 @@ export class CanvasEntityTransformer extends CanvasModuleBase {
syncInteractionState = () => {
this.log.trace('Syncing interaction state');
if (this.manager.$isBusy.get() && !this.$isTransforming.get()) {
// The canvas is busy, we can't interact with the transformer
this.parent.konva.layer.listening(false);
if (this.parent.segmentAnything?.$isSegmenting.get()) {
// When segmenting, the layer should listen but the transformer should not be interactable
console.log('segmenting');
this.parent.konva.layer.listening(true);
this._setInteractionMode('off');
return;
}
@@ -601,6 +602,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase {
// Not all entities have a filterer - only raster layer and control layer adapters
if (this.parent.filterer?.$isFiltering.get()) {
// May not interact with the entity when the filter is active
console.log('filtering');
this.parent.konva.layer.listening(false);
this._setInteractionMode('off');
return;
@@ -608,6 +610,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase {
if (this.manager.stateApi.$isTransforming.get() && !this.$isTransforming.get()) {
// If another entity is being transformed, we can't interact with this transformer
console.log('other entity transforming');
this.parent.konva.layer.listening(false);
this._setInteractionMode('off');
return;
@@ -618,6 +621,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase {
if (isPendingRectCalculation || pixelRect.width === 0 || pixelRect.height === 0) {
// If the rect is being calculated, or if the rect has no width or height, we can't interact with the transformer
console.log('pending rect calculation');
this.parent.konva.layer.listening(false);
this._setInteractionMode('off');
return;
@@ -626,35 +630,49 @@ export class CanvasEntityTransformer extends CanvasModuleBase {
const tool = this.manager.tool.$tool.get();
const isSelected = this.manager.stateApi.getIsSelected(this.parent.id);
if (this.parent.$isEmpty.get()) {
// The layer is totally empty, we can just disable the layer
if (!isSelected) {
console.log('not selected');
// The layer is not selected
this.parent.konva.layer.listening(false);
this._setInteractionMode('off');
return;
}
if (isSelected && !this.$isTransforming.get() && tool === 'move') {
if (this.parent.$isEmpty.get()) {
// The layer is totally empty, we can just disable the layer
console.log('empty');
this.parent.konva.layer.listening(false);
this._setInteractionMode('off');
return;
}
if (!this.$isTransforming.get() && tool === 'move') {
// We are moving this layer, it must be listening
console.log('moving');
this.parent.konva.layer.listening(true);
this._setInteractionMode('drag');
return;
}
if (isSelected && this.$isTransforming.get()) {
if (this.$isTransforming.get()) {
console.log('transforming...');
// When transforming, we want the stage to still be movable if the view tool is selected. If the transformer is
// active, it will interrupt the stage drag events. So we should disable listening when the view tool is selected.
if (tool === 'view') {
console.log('...with view tool');
this.parent.konva.layer.listening(false);
this._setInteractionMode('off');
} else {
console.log('...not with view tool');
this.parent.konva.layer.listening(true);
this._setInteractionMode('all');
}
} else {
// The layer is not selected, or we are using a tool that doesn't need the layer to be listening - disable interaction stuff
this.parent.konva.layer.listening(false);
this._setInteractionMode('off');
return;
}
// The layer is not selected
this.parent.konva.layer.listening(false);
this._setInteractionMode('off');
};
/**

View File

@@ -113,8 +113,8 @@ export class CanvasManager extends CanvasModuleBase {
this.stagingArea.$isStaging,
this.compositor.$isBusy,
],
(isFiltering, isTransforming, isRasterizing, isStaging, isCompositing) => {
return isFiltering || isTransforming || isRasterizing || isStaging || isCompositing;
(isFiltering, isTransforming, isRasterizing, isSegmenting, isStaging, isCompositing) => {
return isFiltering || isTransforming || isRasterizing || isSegmenting || isStaging || isCompositing;
}
);

View File

@@ -6,7 +6,13 @@ import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konv
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasObjectImage } from 'features/controlLayers/konva/CanvasObject/CanvasObjectImage';
import { floorCoord, getKonvaNodeDebugAttrs, getPrefixedId, offsetCoord } from 'features/controlLayers/konva/util';
import {
addCoords,
floorCoord,
getKonvaNodeDebugAttrs,
getPrefixedId,
offsetCoord,
} from 'features/controlLayers/konva/util';
import type {
CanvasImageState,
Coordinate,
@@ -227,9 +233,11 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
return;
}
// We need to offset the cursor position by the parent entity's position + pixel rect to get the correct position
const pixelRect = this.parent.transformer.$pixelRect.get();
const position = addCoords(this.parent.state.position, pixelRect);
const normalizedPoint = offsetCoord(cursorPos.relative, { x: pixelRect.x, y: pixelRect.y });
const normalizedPoint = offsetCoord(cursorPos.relative, position);
const samPoint = this.createPoint(normalizedPoint, this.$pointType.get());
this.points.push(samPoint);
};
@@ -256,15 +264,11 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
point.konva.circle.destroy();
}
this.points = [];
this.parent.konva.layer.add(this.konva.group);
console.log({
position: this.parent.state.position,
pixelRect: this.parent.transformer.$pixelRect.get(),
nodeRect: this.parent.transformer.$nodeRect.get(),
getRelativeRect: this.parent.transformer.getRelativeRect(),
});
// Update the konva group's position to match the parent entity
const pixelRect = this.parent.transformer.$pixelRect.get();
this.konva.group.setAttrs({ x: pixelRect.x, y: pixelRect.y });
const position = addCoords(this.parent.state.position, pixelRect);
this.konva.group.setAttrs(position);
this.parent.konva.layer.add(this.konva.group);
this.parent.konva.layer.listening(true);
this.setSegmentingEventListeners();
@@ -369,12 +373,12 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
}
this.konva.compositingRect.visible(false);
this.konva.maskGroup.clearCache();
this.$pointType.set(1);
this.$isSegmenting.set(false);
this.$hasProcessed.set(false);
this.manager.stateApi.$segmentingAdapter.set(null);
this.konva.group.remove();
this.parent.konva.layer.listening(false);
this.removeSegmentingEventListeners();
this.$isSegmenting.set(false);
};
reset = () => {
@@ -395,7 +399,6 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
this.parent.bufferRenderer.clearBuffer();
this.parent.transformer.updatePosition();
this.parent.renderer.syncKonvaCache(true);
this.parent.konva.layer.listening(false);
this.imageState = null;
this.$hasProcessed.set(false);
};
@@ -404,11 +407,12 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
this.log.trace('Stopping segment anything');
this.reset();
this.$isProcessing.set(false);
this.$isSegmenting.set(false);
this.$hasProcessed.set(false);
this.manager.stateApi.$segmentingAdapter.set(null);
this.konva.group.remove();
this.parent.konva.layer.listening(false);
this.removeSegmentingEventListeners();
this.$isSegmenting.set(false);
};
getSAMPointColor(label: SAMPointLabel): RgbaColor {
@@ -428,12 +432,18 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
type: this.type,
path: this.path,
parent: this.parent.id,
points: this.getSAMPoints(),
points: this.points.map(({ id, konva, label }) => ({
id,
label,
circle: getKonvaNodeDebugAttrs(konva.circle),
})),
config: deepClone(this.config),
isSegmenting: this.$isSegmenting.get(),
konva: {
group: getKonvaNodeDebugAttrs(this.konva.group),
compositingRect: getKonvaNodeDebugAttrs(this.konva.compositingRect),
maskGroup: getKonvaNodeDebugAttrs(this.konva.maskGroup),
pointGroup: getKonvaNodeDebugAttrs(this.konva.pointGroup),
},
};
};

View File

@@ -80,6 +80,18 @@ export const offsetCoord = (coord: Coordinate, offset: Coordinate): Coordinate =
};
};
/**
* Adds two coordinates together.
* @param a The first coordinate
* @param b The second coordinate
*/
export const addCoords = (a: Coordinate, b: Coordinate): Coordinate => {
return {
x: a.x + b.x,
y: a.y + b.y,
};
};
/**
* Snaps a position to the edge of the stage if within a threshold of the edge
* @param pos The position to snap
@@ -616,6 +628,7 @@ export const getKonvaNodeDebugAttrs = (node: Konva.Node) => {
isCached: node.isCached(),
visible: node.visible(),
listening: node.listening(),
zIndex: node.zIndex(),
};
};