feat(ui): trying to fix flicker after transform

This commit is contained in:
psychedelicious 2024-07-30 21:10:45 +10:00
parent ea02323095
commit 1ddea87c35
4 changed files with 51 additions and 15 deletions

View File

@ -7,15 +7,16 @@ import { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasRect } from 'features/controlLayers/konva/CanvasRect'; import { CanvasRect } from 'features/controlLayers/konva/CanvasRect';
import { getBrushLineId, getEraserLineId, getRectShapeId } from 'features/controlLayers/konva/naming'; import { getBrushLineId, getEraserLineId, getRectShapeId } from 'features/controlLayers/konva/naming';
import { konvaNodeToBlob, mapId, previewBlob } from 'features/controlLayers/konva/util'; import { konvaNodeToBlob, mapId, previewBlob } from 'features/controlLayers/konva/util';
import { layerRasterized } from 'features/controlLayers/store/canvasV2Slice'; import { layerAllObjectsDeletedExceptOne, layerRasterized } from 'features/controlLayers/store/canvasV2Slice';
import type { import {
BrushLine, type BrushLine,
CanvasV2State, type CanvasV2State,
Coordinate, type Coordinate,
EraserLine, type EraserLine,
LayerEntity, imageDTOToImageObject,
Rect, type LayerEntity,
RectShape, type Rect,
type RectShape,
} from 'features/controlLayers/store/types'; } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import { debounce, get } from 'lodash-es'; import { debounce, get } from 'lodash-es';
@ -53,6 +54,8 @@ export class CanvasLayer {
_isFirstRender: boolean; _isFirstRender: boolean;
isTransforming: boolean; isTransforming: boolean;
isPendingBboxCalculation: boolean;
rasterizedObjectId: string | null;
rect: Rect; rect: Rect;
bbox: Rect; bbox: Rect;
@ -188,6 +191,8 @@ export class CanvasLayer {
this._bboxNeedsUpdate = true; this._bboxNeedsUpdate = true;
this.isTransforming = false; this.isTransforming = false;
this._isFirstRender = true; this._isFirstRender = true;
this.rasterizedObjectId = null;
this.isPendingBboxCalculation = false;
this._log = this.manager.getLogger(`layer_${this.id}`); this._log = this.manager.getLogger(`layer_${this.id}`);
} }
@ -326,6 +331,12 @@ export class CanvasLayer {
if (didUpdate) { if (didUpdate) {
this.calculateBbox(); 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 }) { async updateOpacity(arg?: { opacity: number }) {
@ -388,6 +399,10 @@ export class CanvasLayer {
async updateBbox() { async updateBbox() {
this._log.trace('Updating bbox'); this._log.trace('Updating bbox');
if (this.isPendingBboxCalculation) {
return;
}
// If the bbox has no width or height, that means the layer is fully transparent. This can happen if it is only // If the bbox has no width or height, that means the layer is fully transparent. This can happen if it is only
// eraser lines, fully clipped brush lines or if it has been fully erased. // eraser lines, fully clipped brush lines or if it has been fully erased.
if (this.bbox.width === 0 || this.bbox.height === 0) { if (this.bbox.width === 0 || this.bbox.height === 0) {
@ -547,11 +562,16 @@ export class CanvasLayer {
} }
const imageDTO = await uploadImage(blob, `${this.id}_transform.png`, 'other', true); const imageDTO = await uploadImage(blob, `${this.id}_transform.png`, 'other', true);
const { dispatch } = getStore(); const { dispatch } = getStore();
dispatch(layerRasterized({ id: this.id, imageDTO, position: { x: rect.x, y: rect.y } })); const imageObject = imageDTOToImageObject(this.id, uuidv4(), imageDTO);
this.isTransforming = false; dispatch(layerRasterized({ id: this.id, imageObject, position: { x: rect.x, y: rect.y } }));
this.rasterizedObjectId = imageObject.id;
this.resetScale(); this.resetScale();
} }
async finalizeTransform() {
//
}
async cancelTransform() { async cancelTransform() {
this._log.debug('Canceling transform'); this._log.debug('Canceling transform');
@ -572,9 +592,12 @@ export class CanvasLayer {
calculateBbox = debounce(() => { calculateBbox = debounce(() => {
this._log.debug('Calculating bbox'); this._log.debug('Calculating bbox');
this.isPendingBboxCalculation = true;
if (this.objects.size === 0) { if (this.objects.size === 0) {
this.rect = this.getDefaultRect(); this.rect = this.getDefaultRect();
this.bbox = this.getDefaultRect(); this.bbox = this.getDefaultRect();
this.isPendingBboxCalculation = false;
this.updateBbox(); this.updateBbox();
return; return;
} }
@ -607,6 +630,7 @@ export class CanvasLayer {
if (!needsPixelBbox) { if (!needsPixelBbox) {
this.rect = deepClone(rect); this.rect = deepClone(rect);
this.bbox = deepClone(rect); this.bbox = deepClone(rect);
this.isPendingBboxCalculation = false;
this._log.trace({ bbox: this.bbox, rect: this.rect }, 'Got bbox from client rect'); this._log.trace({ bbox: this.bbox, rect: this.rect }, 'Got bbox from client rect');
this.updateBbox(); this.updateBbox();
return; return;
@ -636,6 +660,7 @@ export class CanvasLayer {
} else { } else {
this.bbox = deepClone(rect); this.bbox = deepClone(rect);
} }
this.isPendingBboxCalculation = false;
this._log.trace({ bbox: this.bbox, rect: this.rect, extents }, `Got bbox from worker`); this._log.trace({ bbox: this.bbox, rect: this.rect, extents }, `Got bbox from worker`);
this.updateBbox(); this.updateBbox();
clone.destroy(); clone.destroy();

View File

@ -329,7 +329,7 @@ export class CanvasManager {
for (const canvasLayer of this.layers.values()) { for (const canvasLayer of this.layers.values()) {
if (!state.layers.entities.find((l) => l.id === canvasLayer.id)) { if (!state.layers.entities.find((l) => l.id === canvasLayer.id)) {
this.log.debug(`Destroying deleted layer ${canvasLayer.id}`); this.log.debug(`Destroying deleted layer ${canvasLayer.id}`);
canvasLayer.destroy(); await canvasLayer.destroy();
this.layers.delete(canvasLayer.id); this.layers.delete(canvasLayer.id);
} }
} }

View File

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

View File

@ -10,6 +10,7 @@ import type {
CanvasV2State, CanvasV2State,
Coordinate, Coordinate,
EraserLine, EraserLine,
ImageObject,
ImageObjectAddedArg, ImageObjectAddedArg,
LayerEntity, LayerEntity,
PositionChangedArg, PositionChangedArg,
@ -252,16 +253,25 @@ export const layersReducers = {
const { imageDTO } = action.payload; const { imageDTO } = action.payload;
state.layers.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null; state.layers.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
}, },
layerRasterized: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO; position: Coordinate }>) => { layerRasterized: (state, action: PayloadAction<{ id: string; imageObject: ImageObject; position: Coordinate }>) => {
const { id, imageDTO, position } = action.payload; const { id, imageObject, position } = action.payload;
const layer = selectLayer(state, id); const layer = selectLayer(state, id);
if (!layer) { if (!layer) {
return; return;
} }
layer.objects = [imageDTOToImageObject(id, uuidv4(), imageDTO)]; layer.objects.push(imageObject);
layer.position = position; layer.position = position;
state.layers.imageCache = null; 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>; } satisfies SliceCaseReducers<CanvasV2State>;
const scalePoints = (points: number[], scaleX: number, scaleY: number) => { const scalePoints = (points: number[], scaleX: number, scaleY: number) => {