fix(ui): edge case where entity isn't visible until interacting with canvas

To trigger the edge case:
- Have an empty layer and non-empty layer
- Select the non-empty layer
- Refresh the page
- Select to the empty layer without doing any other action
- You may be unable to draw on the layer
- Zoom in/out slightly
- You can now draw on it

The problem was not syncing visibility when a layer is selected, leaving the layer hidden. This indirectly disabled interactions.

The fix is to listen for changes to the layer's selected status and sync visibility when that changes.
This commit is contained in:
psychedelicious
2024-10-11 15:19:33 +10:00
committed by Kent Keirsey
parent 7185363f17
commit da3f85dd8b
2 changed files with 29 additions and 23 deletions

View File

@@ -17,6 +17,7 @@ import {
} from 'features/controlLayers/store/canvasSettingsSlice';
import {
buildEntityIsHiddenSelector,
buildSelectIsSelected,
selectBboxRect,
selectCanvasSlice,
selectEntity,
@@ -165,6 +166,7 @@ export abstract class CanvasEntityAdapterBase<
};
selectIsHidden: Selector<RootState, boolean>;
selectIsSelected: Selector<RootState, boolean>;
/**
* The Konva nodes that make up the entity adapter:
@@ -249,6 +251,7 @@ export abstract class CanvasEntityAdapterBase<
this.state = state;
this.selectIsHidden = buildEntityIsHiddenSelector(this.entityIdentifier);
this.selectIsSelected = buildSelectIsSelected(this.entityIdentifier);
/**
* There are a number of reason we may need to show or hide a layer:
@@ -257,6 +260,7 @@ export abstract class CanvasEntityAdapterBase<
* - Staging status changes and `isolatedStagingPreview` is enabled
* - Global filtering status changes and `isolatedFilteringPreview` is enabled
* - Global transforming status changes and `isolatedTransformingPreview` is enabled
* - The entity is selected or deselected (only selected and onscreen entities are rendered)
*/
this.subscriptions.add(this.manager.stateApi.createStoreSubscription(this.selectIsHidden, this.syncVisibility));
this.subscriptions.add(
@@ -267,6 +271,7 @@ export abstract class CanvasEntityAdapterBase<
this.manager.stateApi.createStoreSubscription(selectIsolatedTransformingPreview, this.syncVisibility)
);
this.subscriptions.add(this.manager.stateApi.$transformingAdapter.listen(this.syncVisibility));
this.subscriptions.add(this.manager.stateApi.createStoreSubscription(this.selectIsSelected, this.syncVisibility));
/**
* The tool preview may need to be updated when the entity is locked or disabled. For example, when we disable the
@@ -305,21 +310,8 @@ export abstract class CanvasEntityAdapterBase<
syncIsOnscreen = () => {
const stageRect = this.manager.stage.getScaledStageRect();
const entityRect = this.transformer.$pixelRect.get();
const position = this.manager.stateApi.runSelector(this.selectPosition);
if (!position) {
return;
}
const entityRectRelativeToStage = {
x: entityRect.x + position.x,
y: entityRect.y + position.y,
width: entityRect.width,
height: entityRect.height,
};
const intersection = getRectIntersection(stageRect, entityRectRelativeToStage);
const isOnScreen = this.checkIntersection(stageRect);
const prevIsOnScreen = this.$isOnScreen.get();
const isOnScreen = intersection.width > 0 && intersection.height > 0;
this.$isOnScreen.set(isOnScreen);
if (prevIsOnScreen !== isOnScreen) {
this.log.trace(`Moved ${isOnScreen ? 'on-screen' : 'off-screen'}`);
@@ -329,10 +321,19 @@ export abstract class CanvasEntityAdapterBase<
syncIntersectsBbox = () => {
const bboxRect = this.manager.stateApi.getBbox().rect;
const intersectsBbox = this.checkIntersection(bboxRect);
const prevIntersectsBbox = this.$intersectsBbox.get();
this.$intersectsBbox.set(intersectsBbox);
if (prevIntersectsBbox !== intersectsBbox) {
this.log.trace(`Moved ${intersectsBbox ? 'into bbox' : 'out of bbox'}`);
}
};
checkIntersection = (rect: Rect): boolean => {
const entityRect = this.transformer.$pixelRect.get();
const position = this.manager.stateApi.runSelector(this.selectPosition);
if (!position) {
return;
return false;
}
const entityRectRelativeToStage = {
x: entityRect.x + position.x,
@@ -340,14 +341,9 @@ export abstract class CanvasEntityAdapterBase<
width: entityRect.width,
height: entityRect.height,
};
const intersection = getRectIntersection(bboxRect, entityRectRelativeToStage);
const prevIntersectsBbox = this.$intersectsBbox.get();
const intersectsBbox = intersection.width > 0 && intersection.height > 0;
this.$intersectsBbox.set(intersectsBbox);
if (prevIntersectsBbox !== intersectsBbox) {
this.log.trace(`Moved ${intersectsBbox ? 'into bbox' : 'out of bbox'}`);
}
const intersection = getRectIntersection(rect, entityRectRelativeToStage);
const doesIntersect = intersection.width > 0 && intersection.height > 0;
return doesIntersect;
};
initialize = async () => {

View File

@@ -339,6 +339,16 @@ export const buildEntityIsHiddenSelector = (entityIdentifier: CanvasEntityIdenti
);
};
/**
* Builds a selector taht selects if the entity is selected.
*/
export const buildSelectIsSelected = (entityIdentifier: CanvasEntityIdentifier) => {
return createSelector(
selectSelectedEntityIdentifier,
(selectedEntityIdentifier) => selectedEntityIdentifier?.id === entityIdentifier.id
);
};
export const selectWidth = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.width);
export const selectHeight = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.height);
export const selectAspectRatioID = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.aspectRatio.id);