feat(ui): layers manage their own bbox

This commit is contained in:
psychedelicious 2024-06-06 18:44:16 +10:00
parent 915357a6c1
commit 1985944659
6 changed files with 62 additions and 72 deletions

View File

@ -281,15 +281,6 @@ const useStageRenderer = (
state.size.height,
]);
useLayoutEffect(() => {
log.trace('Rendering bbox');
if (asPreview) {
// Preview should not display bboxes
return;
}
renderers.renderBboxes(stage, state.layers, tool);
}, [stage, asPreview, state.layers, tool, onBboxChanged, renderers]);
useLayoutEffect(() => {
if (asPreview) {
// Preview should not check for transparency

View File

@ -1,13 +1,12 @@
import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
import { imageDataToDataURL } from 'features/canvas/util/blobToDataURL';
import { BBOX_SELECTED_STROKE } from 'features/controlLayers/konva/constants';
import {
getLayerBboxId,
LAYER_BBOX_NAME,
RASTER_LAYER_OBJECT_GROUP_NAME,
RG_LAYER_OBJECT_GROUP_NAME,
} from 'features/controlLayers/konva/naming';
import type { Layer, Tool } from 'features/controlLayers/store/types';
import { createBboxRect } from 'features/controlLayers/konva/renderers/objects';
import type { Layer } from 'features/controlLayers/store/types';
import { isRegionalGuidanceLayer, isRGOrRasterlayer } from 'features/controlLayers/store/types';
import Konva from 'konva';
import type { IRect } from 'konva/lib/types';
@ -175,22 +174,6 @@ export const getLayerBboxFast = (layer: Konva.Layer): IRect => {
};
};
/**
* Creates a bounding box rect for a layer.
* @param layerState The layer state for the layer to create the bounding box for
* @param konvaLayer The konva layer to attach the bounding box to
*/
const createBboxRect = (layerState: Layer, konvaLayer: Konva.Layer): Konva.Rect => {
const rect = new Konva.Rect({
id: getLayerBboxId(layerState.id),
name: LAYER_BBOX_NAME,
strokeWidth: 1,
visible: false,
});
konvaLayer.add(rect);
return rect;
};
const filterRGChildren = (node: Konva.Node): boolean => node.name() === RG_LAYER_OBJECT_GROUP_NAME;
const filterRasterChildren = (node: Konva.Node): boolean => node.name() === RASTER_LAYER_OBJECT_GROUP_NAME;
@ -230,42 +213,3 @@ export const updateBboxes = (
}
}
};
/**
* Renders the bounding boxes for the layers.
* @param stage The konva stage
* @param layerStates An array of layers to draw bboxes for
* @param tool The current tool
* @returns
*/
export const renderBboxes = (stage: Konva.Stage, layerStates: Layer[], tool: Tool): void => {
// Hide all bboxes so they don't interfere with getClientRect
for (const bboxRect of stage.find<Konva.Rect>(`.${LAYER_BBOX_NAME}`)) {
bboxRect.visible(false);
bboxRect.listening(false);
}
// No selected layer or not using the move tool - nothing more to do here
if (tool !== 'move') {
return;
}
for (const layer of layerStates.filter(isRGOrRasterlayer)) {
if (!layer.bbox) {
continue;
}
const konvaLayer = stage.findOne<Konva.Layer>(`#${layer.id}`);
assert(konvaLayer, `Layer ${layer.id} not found in stage`);
const bboxRect = konvaLayer.findOne<Konva.Rect>(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(layer, konvaLayer);
bboxRect.setAttrs({
visible: !layer.bboxNeedsUpdate,
listening: layer.isSelected,
x: layer.bbox.x,
y: layer.bbox.y,
width: layer.bbox.width,
height: layer.bbox.height,
stroke: layer.isSelected ? BBOX_SELECTED_STROKE : '',
});
}
};

View File

@ -1,7 +1,7 @@
import { DEBOUNCE_MS } from 'features/controlLayers/konva/constants';
import { BACKGROUND_LAYER_ID, TOOL_PREVIEW_LAYER_ID } from 'features/controlLayers/konva/naming';
import { renderBackground } from 'features/controlLayers/konva/renderers/background';
import { renderBboxes, updateBboxes } from 'features/controlLayers/konva/renderers/bbox';
import { updateBboxes } from 'features/controlLayers/konva/renderers/bbox';
import { renderCALayer } from 'features/controlLayers/konva/renderers/caLayer';
import { renderIILayer } from 'features/controlLayers/konva/renderers/iiLayer';
import { renderNoLayersMessage } from 'features/controlLayers/konva/renderers/noLayersMessage';
@ -90,7 +90,6 @@ const renderLayers = (
export const renderers = {
renderToolPreview,
renderLayers,
renderBboxes,
renderBackground,
renderNoLayersMessage,
arrangeLayers,
@ -105,7 +104,6 @@ export const renderers = {
const getDebouncedRenderers = (ms = DEBOUNCE_MS): typeof renderers => ({
renderToolPreview: debounce(renderToolPreview, ms),
renderLayers: debounce(renderLayers, ms),
renderBboxes: debounce(renderBboxes, ms),
renderBackground: debounce(renderBackground, ms),
renderNoLayersMessage: debounce(renderNoLayersMessage, ms),
arrangeLayers: debounce(arrangeLayers, ms),

View File

@ -1,6 +1,6 @@
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { getObjectGroupId } from 'features/controlLayers/konva/naming';
import type { BrushLine, EraserLine, ImageObject, RectShape } from 'features/controlLayers/store/types';
import { getLayerBboxId, getObjectGroupId, LAYER_BBOX_NAME } from 'features/controlLayers/konva/naming';
import type { BrushLine, EraserLine, ImageObject, Layer, RectShape } from 'features/controlLayers/store/types';
import { DEFAULT_RGBA_COLOR } from 'features/controlLayers/store/types';
import { t } from 'i18next';
import Konva from 'konva';
@ -193,6 +193,23 @@ export const createImageObjectGroup = async (
});
return konvaImageGroup;
};
/**
* Creates a bounding box rect for a layer.
* @param layerState The layer state for the layer to create the bounding box for
* @param konvaLayer The konva layer to attach the bounding box to
*/
export const createBboxRect = (layerState: Layer, konvaLayer: Konva.Layer): Konva.Rect => {
const rect = new Konva.Rect({
id: getLayerBboxId(layerState.id),
name: LAYER_BBOX_NAME,
strokeWidth: 1,
visible: false,
});
konvaLayer.add(rect);
return rect;
};
/**
* Creates a konva group for a layer's objects.
* @param konvaLayer The konva layer to add the object group to

View File

@ -1,4 +1,6 @@
import { BBOX_SELECTED_STROKE } from 'features/controlLayers/konva/constants';
import {
LAYER_BBOX_NAME,
RASTER_LAYER_BRUSH_LINE_NAME,
RASTER_LAYER_ERASER_LINE_NAME,
RASTER_LAYER_IMAGE_NAME,
@ -7,6 +9,7 @@ import {
RASTER_LAYER_RECT_SHAPE_NAME,
} from 'features/controlLayers/konva/naming';
import {
createBboxRect,
createBrushLine,
createEraserLine,
createImageObjectGroup,
@ -141,5 +144,22 @@ export const renderRasterLayer = async (
konvaLayer.visible(layerState.isEnabled);
}
const bboxRect = konvaLayer.findOne<Konva.Rect>(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(layerState, konvaLayer);
if (layerState.bbox) {
const active = !layerState.bboxNeedsUpdate && layerState.isSelected && tool === 'move';
bboxRect.setAttrs({
visible: active,
listening: active,
x: layerState.bbox.x,
y: layerState.bbox.y,
width: layerState.bbox.width,
height: layerState.bbox.height,
stroke: layerState.isSelected ? BBOX_SELECTED_STROKE : '',
});
} else {
bboxRect.visible(false);
}
konvaObjectGroup.opacity(layerState.opacity);
};

View File

@ -1,6 +1,8 @@
import { rgbColorToString } from 'features/canvas/util/colorToString';
import { BBOX_SELECTED_STROKE } from 'features/controlLayers/konva/constants';
import {
COMPOSITING_RECT_NAME,
LAYER_BBOX_NAME,
RG_LAYER_BRUSH_LINE_NAME,
RG_LAYER_ERASER_LINE_NAME,
RG_LAYER_NAME,
@ -9,6 +11,7 @@ import {
} from 'features/controlLayers/konva/naming';
import { getLayerBboxFast } from 'features/controlLayers/konva/renderers/bbox';
import {
createBboxRect,
createBrushLine,
createEraserLine,
createObjectGroup,
@ -227,4 +230,21 @@ export const renderRGLayer = (
// Updating group opacity does not require re-caching
konvaObjectGroup.opacity(globalMaskLayerOpacity);
}
const bboxRect = konvaLayer.findOne<Konva.Rect>(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(layerState, konvaLayer);
if (layerState.bbox) {
const active = !layerState.bboxNeedsUpdate && layerState.isSelected && tool === 'move';
bboxRect.setAttrs({
visible: active,
listening: active,
x: layerState.bbox.x,
y: layerState.bbox.y,
width: layerState.bbox.width,
height: layerState.bbox.height,
stroke: layerState.isSelected ? BBOX_SELECTED_STROKE : '',
});
} else {
bboxRect.visible(false);
}
};