fix(ui): prevent flash when applying transform

This commit is contained in:
psychedelicious 2024-07-31 14:27:23 +10:00
parent a455f12581
commit 4f5b755117
4 changed files with 34 additions and 55 deletions

View File

@ -7,7 +7,7 @@ import { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasRect } from 'features/controlLayers/konva/CanvasRect';
import { getBrushLineId, getEraserLineId, getRectShapeId } from 'features/controlLayers/konva/naming';
import { konvaNodeToBlob, mapId, previewBlob } from 'features/controlLayers/konva/util';
import { layerAllObjectsDeletedExceptOne, layerRasterized } from 'features/controlLayers/store/canvasV2Slice';
import { layerRasterized } from 'features/controlLayers/store/canvasV2Slice';
import {
type BrushLine,
type CanvasV2State,
@ -55,7 +55,6 @@ export class CanvasLayer {
isTransforming: boolean;
isPendingBboxCalculation: boolean;
rasterizedObjectId: string | null;
rect: Rect;
bbox: Rect;
@ -191,7 +190,6 @@ export class CanvasLayer {
this._bboxNeedsUpdate = true;
this.isTransforming = false;
this._isFirstRender = true;
this.rasterizedObjectId = null;
this.isPendingBboxCalculation = false;
this._log = this.manager.getLogger(`layer_${this.id}`);
}
@ -218,7 +216,7 @@ export class CanvasLayer {
return;
}
const drawingBuffer = this._drawingBuffer;
this.setDrawingBuffer(null);
await this.setDrawingBuffer(null);
// We need to give the objects a fresh ID else they will be considered the same object when they are re-rendered as
// a non-buffer object, and we won't trigger things like bbox calculation
@ -248,12 +246,12 @@ export class CanvasLayer {
this._log.debug('Updating');
const { position, objects, opacity, isEnabled } = state;
if (this._isFirstRender || position !== this._state.position) {
await this.updatePosition({ position });
}
if (this._isFirstRender || objects !== this._state.objects) {
await this.updateObjects({ objects });
}
if (this._isFirstRender || position !== this._state.position) {
await this.updatePosition({ position });
}
if (this._isFirstRender || opacity !== this._state.opacity) {
await this.updateOpacity({ opacity });
}
@ -270,14 +268,14 @@ export class CanvasLayer {
this._isFirstRender = false;
}
async updateVisibility(arg?: { isEnabled: boolean }) {
updateVisibility(arg?: { isEnabled: boolean }) {
this._log.trace('Updating visibility');
const isEnabled = get(arg, 'isEnabled', this._state.isEnabled);
const hasObjects = this.objects.size > 0 || this._drawingBuffer !== null;
this.konva.layer.visible(isEnabled || hasObjects);
}
async updatePosition(arg?: { position: Coordinate }) {
updatePosition(arg?: { position: Coordinate }) {
this._log.trace('Updating position');
const position = get(arg, 'position', this._state.position);
const bboxPadding = this.manager.getScaledBboxPadding();
@ -331,23 +329,15 @@ export class CanvasLayer {
if (didUpdate) {
this.calculateBbox();
}
if (this.isTransforming && this.rasterizedObjectId) {
this.manager._store.dispatch(layerAllObjectsDeletedExceptOne({ id: this.id, objectId: this.rasterizedObjectId }));
this.isTransforming = false;
this.rasterizedObjectId = null;
}
}
async updateOpacity(arg?: { opacity: number }) {
updateOpacity(arg?: { opacity: number }) {
this._log.trace('Updating opacity');
const opacity = get(arg, 'opacity', this._state.opacity);
this.konva.objectGroup.opacity(opacity);
}
async updateInteraction(arg?: { toolState: CanvasV2State['tool']; isSelected: boolean }) {
updateInteraction(arg?: { toolState: CanvasV2State['tool']; isSelected: boolean }) {
this._log.trace('Updating interaction');
const toolState = get(arg, 'toolState', this.manager.stateApi.getToolState());
@ -396,7 +386,7 @@ export class CanvasLayer {
}
}
async updateBbox() {
updateBbox() {
this._log.trace('Updating bbox');
if (this.isPendingBboxCalculation) {
@ -442,7 +432,7 @@ export class CanvasLayer {
});
}
async syncStageScale() {
syncStageScale() {
this._log.trace('Syncing scale to stage');
const onePixel = this.manager.getScaledPixel();
@ -519,7 +509,7 @@ export class CanvasLayer {
return false;
}
async startTransform() {
startTransform() {
this._log.debug('Starting transform');
this.isTransforming = true;
@ -539,7 +529,7 @@ export class CanvasLayer {
this.konva.bbox.visible(false);
}
async resetScale() {
resetScale() {
const attrs = {
scaleX: 1,
scaleY: 1,
@ -550,39 +540,37 @@ export class CanvasLayer {
this.konva.interactionRect.setAttrs(attrs);
}
async applyTransform() {
this._log.debug('Applying transform');
async rasterizeLayer() {
this._log.debug('Rasterizing layer');
const objectGroupClone = this.konva.objectGroup.clone();
const interactionRectClone = this.konva.interactionRect.clone();
const rect = interactionRectClone.getClientRect();
const blob = await konvaNodeToBlob(objectGroupClone, rect);
if (this.manager._isDebugging) {
previewBlob(blob, 'transformed layer');
previewBlob(blob, 'Rasterized layer');
}
const imageDTO = await uploadImage(blob, `${this.id}_transform.png`, 'other', true);
const imageDTO = await uploadImage(blob, `${this.id}_rasterized.png`, 'other', true);
const { dispatch } = getStore();
const imageObject = imageDTOToImageObject(this.id, uuidv4(), imageDTO);
dispatch(layerRasterized({ id: this.id, imageObject, position: { x: rect.x, y: rect.y } }));
this.rasterizedObjectId = imageObject.id;
await this._renderObject(imageObject, true);
for (const obj of this.objects.values()) {
if (obj.id !== imageObject.id) {
obj.konva.group.visible(false);
}
}
this.resetScale();
dispatch(layerRasterized({ id: this.id, imageObject, position: { x: rect.x, y: rect.y } }));
}
async finalizeTransform() {
//
}
async cancelTransform() {
this._log.debug('Canceling transform');
stopTransform() {
this._log.debug('Stopping transform');
this.isTransforming = false;
this.resetScale();
await this.updatePosition({ position: this._state.position });
await this.updateBbox();
await this.updateInteraction({
toolState: this.manager.stateApi.getToolState(),
isSelected: this.manager.stateApi.getIsSelected(this.id),
});
this.updatePosition();
this.updateBbox();
this.updateInteraction();
}
getDefaultRect(): Rect {

View File

@ -299,10 +299,11 @@ export class CanvasManager {
this.onTransform?.(true);
}
applyTransform() {
async applyTransform() {
const layer = this.getTransformingLayer();
if (layer) {
layer.applyTransform();
await layer.rasterizeLayer();
layer.stopTransform();
}
this.onTransform?.(false);
}
@ -310,7 +311,7 @@ export class CanvasManager {
cancelTransform() {
const layer = this.getTransformingLayer();
if (layer) {
layer.cancelTransform();
layer.stopTransform();
}
this.onTransform?.(false);
}

View File

@ -220,7 +220,6 @@ export const {
layerTranslated,
layerBboxChanged,
layerImageAdded,
layerAllObjectsDeletedExceptOne,
layerAllDeleted,
layerImageCacheChanged,
layerScaled,

View File

@ -259,19 +259,10 @@ export const layersReducers = {
if (!layer) {
return;
}
layer.objects.push(imageObject);
layer.objects = [imageObject];
layer.position = position;
state.layers.imageCache = null;
},
layerAllObjectsDeletedExceptOne: (state, action: PayloadAction<{ id: string; objectId: string }>) => {
const { id, objectId } = action.payload;
const layer = selectLayer(state, id);
if (!layer) {
return;
}
layer.objects = layer.objects.filter((obj) => obj.id === objectId);
state.layers.imageCache = null;
},
} satisfies SliceCaseReducers<CanvasV2State>;
const scalePoints = (points: number[], scaleX: number, scaleY: number) => {