mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
refactor(ui): canvas v2 (wip)
This commit is contained in:
parent
c51253f5f6
commit
58c656224f
@ -19,9 +19,9 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
|
||||
let wereControlAdaptersReset = false;
|
||||
let wereControlLayersReset = false;
|
||||
|
||||
const { canvas, nodes, controlAdapters, controlLayers } = getState();
|
||||
const { canvas, nodes, controlAdapters, canvasV2 } = getState();
|
||||
deleted_images.forEach((image_name) => {
|
||||
const imageUsage = getImageUsage(canvas, nodes.present, controlAdapters, controlLayers.present, image_name);
|
||||
const imageUsage = getImageUsage(canvas, nodes.present, controlAdapters, canvasV2, image_name);
|
||||
|
||||
if (imageUsage.isCanvasImage && !wasCanvasReset) {
|
||||
dispatch(resetCanvas());
|
||||
|
@ -65,14 +65,14 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
|
||||
// Delay before starting actual work
|
||||
await delay(DEBOUNCE_MS);
|
||||
|
||||
const layer = state.controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
||||
const layer = state.canvasV2.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
||||
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We should only process if the processor settings or image have changed
|
||||
const originalLayer = originalState.controlLayers.present.layers
|
||||
const originalLayer = originalState.canvasV2.layers
|
||||
.filter(isControlAdapterLayer)
|
||||
.find((l) => l.id === layerId);
|
||||
const originalImage = originalLayer?.controlAdapter.image;
|
||||
@ -161,7 +161,7 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
|
||||
if (signal.aborted) {
|
||||
// The listener was canceled - we need to cancel the pending processor batch, if there is one (could have changed by now).
|
||||
const pendingBatchId = getState()
|
||||
.controlLayers.present.layers.filter(isControlAdapterLayer)
|
||||
.canvasV2.layers.filter(isControlAdapterLayer)
|
||||
.find((l) => l.id === layerId)?.controlAdapter.processorPendingBatchId;
|
||||
if (pendingBatchId) {
|
||||
cancelProcessorBatch(dispatch, layerId, pendingBatchId);
|
||||
|
@ -70,7 +70,7 @@ const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, ima
|
||||
};
|
||||
|
||||
const deleteControlLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||
state.controlLayers.present.layers.forEach((l) => {
|
||||
state.canvasV2.layers.forEach((l) => {
|
||||
if (isRegionalGuidanceLayer(l)) {
|
||||
if (l.ipAdapters.some((ipa) => ipa.image?.name === imageDTO.image_name)) {
|
||||
dispatch(layerDeleted(l.id));
|
||||
|
@ -79,15 +79,15 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
|
||||
const optimalDimension = getOptimalDimension(defaultModelInList);
|
||||
if (
|
||||
getIsSizeOptimal(
|
||||
state.controlLayers.present.size.width,
|
||||
state.controlLayers.present.size.height,
|
||||
state.canvasV2.size.width,
|
||||
state.canvasV2.size.height,
|
||||
optimalDimension
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const { width, height } = calculateNewSize(
|
||||
state.controlLayers.present.size.aspectRatio.value,
|
||||
state.canvasV2.size.aspectRatio.value,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { logger } from 'app/logging/logger';
|
||||
import { idbKeyValDriver } from 'app/store/enhancers/reduxRemember/driver';
|
||||
import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
|
||||
import type { JSONObject } from 'common/types';
|
||||
import { canvasPersistConfig, canvasSlice } from 'features/canvas/store/canvasSlice';
|
||||
import { canvasPersistConfig } from 'features/canvas/store/canvasSlice';
|
||||
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
|
||||
import {
|
||||
controlAdaptersV2PersistConfig,
|
||||
@ -52,7 +52,6 @@ import { listenerMiddleware } from './middleware/listenerMiddleware';
|
||||
|
||||
const allReducers = {
|
||||
[api.reducerPath]: api.reducer,
|
||||
[canvasSlice.name]: canvasSlice.reducer,
|
||||
[gallerySlice.name]: gallerySlice.reducer,
|
||||
[generationSlice.name]: generationSlice.reducer,
|
||||
[nodesSlice.name]: undoable(nodesSlice.reducer, nodesUndoableConfig),
|
||||
|
@ -59,8 +59,8 @@ const createSelector = (templates: Templates) =>
|
||||
config
|
||||
) => {
|
||||
const { model } = generation;
|
||||
const { size } = controlLayers.present;
|
||||
const { positivePrompt } = controlLayers.present;
|
||||
const { size } = canvasV2;
|
||||
const { positivePrompt } = canvasV2;
|
||||
|
||||
const { isConnected } = system;
|
||||
|
||||
@ -126,7 +126,7 @@ const createSelector = (templates: Templates) =>
|
||||
|
||||
if (activeTabName === 'generation') {
|
||||
// Handling for generation tab
|
||||
controlLayers.present.layers
|
||||
canvasV2.layers
|
||||
.filter((l) => l.isEnabled)
|
||||
.forEach((l, i) => {
|
||||
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||
|
@ -23,7 +23,7 @@ export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => {
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
const layer = canvasV2.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
return {
|
||||
canAddPositivePrompt: layer.positivePrompt === null,
|
||||
|
@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const BrushColorPicker = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const brushColor = useAppSelector((s) => s.controlLayers.present.brushColor);
|
||||
const brushColor = useAppSelector((s) => s.canvasV2.brushColor);
|
||||
const dispatch = useAppDispatch();
|
||||
const onChange = useCallback(
|
||||
(color: RgbaColor) => {
|
||||
|
@ -20,7 +20,7 @@ const formatPx = (v: number | string) => `${v} px`;
|
||||
export const BrushSize = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const brushSize = useAppSelector((s) => s.controlLayers.present.brushSize);
|
||||
const brushSize = useAppSelector((s) => s.canvasV2.brushSize);
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(brushSizeChanged(Math.round(v)));
|
||||
|
@ -19,7 +19,7 @@ type Props = {
|
||||
export const CALayer = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isSelected = useAppSelector(
|
||||
(s) => selectLayerOrThrow(s.controlLayers.present, layerId, isControlAdapterLayer).isSelected
|
||||
(s) => selectLayerOrThrow(s.canvasV2, layerId, isControlAdapterLayer).isSelected
|
||||
);
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(layerSelected(layerId));
|
||||
|
@ -28,7 +28,7 @@ type Props = {
|
||||
export const CALayerControlAdapterWrapper = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const controlAdapter = useAppSelector(
|
||||
(s) => selectLayerOrThrow(s.controlLayers.present, layerId, isControlAdapterLayer).controlAdapter
|
||||
(s) => selectLayerOrThrow(s.canvasV2, layerId, isControlAdapterLayer).controlAdapter
|
||||
);
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
|
@ -19,7 +19,7 @@ import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selectLayerIdTypePairs = createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const [renderableLayers, ipAdapterLayers] = partition(controlLayers.present.layers, isRenderableLayer);
|
||||
const [renderableLayers, ipAdapterLayers] = partition(canvasV2.layers, isRenderableLayer);
|
||||
return [...ipAdapterLayers, ...renderableLayers].map((l) => ({ id: l.id, type: l.type })).reverse();
|
||||
});
|
||||
|
||||
|
@ -8,7 +8,7 @@ import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||
export const DeleteAllLayersButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isDisabled = useAppSelector((s) => s.controlLayers.present.layers.length === 0);
|
||||
const isDisabled = useAppSelector((s) => s.canvasV2.layers.length === 0);
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(allLayersDeleted());
|
||||
}, [dispatch]);
|
||||
|
@ -14,7 +14,7 @@ export const GlobalMaskLayerOpacity = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const globalMaskLayerOpacity = useAppSelector((s) =>
|
||||
Math.round(s.controlLayers.present.globalMaskLayerOpacity * 100)
|
||||
Math.round(s.canvasV2.globalMaskLayerOpacity * 100)
|
||||
);
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
|
@ -7,8 +7,8 @@ import { memo } from 'react';
|
||||
|
||||
export const HeadsUpDisplay = memo(() => {
|
||||
const stageAttrs = useStore($stageAttrs);
|
||||
const layerCount = useAppSelector((s) => s.controlLayers.present.layers.length);
|
||||
const bbox = useAppSelector((s) => s.controlLayers.present.bbox);
|
||||
const layerCount = useAppSelector((s) => s.canvasV2.layers.length);
|
||||
const bbox = useAppSelector((s) => s.canvasV2.bbox);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" bg="blackAlpha.400" borderBottomEndRadius="base" p={2} minW={64} gap={2}>
|
||||
|
@ -25,7 +25,7 @@ type Props = {
|
||||
|
||||
export const IILayer = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const layer = useAppSelector((s) => selectLayerOrThrow(s.controlLayers.present, layerId, isInitialImageLayer));
|
||||
const layer = useAppSelector((s) => selectLayerOrThrow(s.canvasV2, layerId, isInitialImageLayer));
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(layerSelected(layerId));
|
||||
}, [dispatch, layerId]);
|
||||
|
@ -16,7 +16,7 @@ type Props = {
|
||||
export const IPALayer = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isSelected = useAppSelector(
|
||||
(s) => selectLayerOrThrow(s.controlLayers.present, layerId, isIPAdapterLayer).isSelected
|
||||
(s) => selectLayerOrThrow(s.canvasV2, layerId, isIPAdapterLayer).isSelected
|
||||
);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
const onClick = useCallback(() => {
|
||||
|
@ -22,7 +22,7 @@ type Props = {
|
||||
export const IPALayerIPAdapterWrapper = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const ipAdapter = useAppSelector(
|
||||
(s) => selectLayerOrThrow(s.controlLayers.present, layerId, isIPAdapterLayer).ipAdapter
|
||||
(s) => selectLayerOrThrow(s.canvasV2, layerId, isIPAdapterLayer).ipAdapter
|
||||
);
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
|
@ -22,10 +22,10 @@ export const LayerMenuArrangeActions = memo(({ layerId }: Props) => {
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
const layer = canvasV2.layers.find((l) => l.id === layerId);
|
||||
assert(isRenderableLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
const layerIndex = controlLayers.present.layers.findIndex((l) => l.id === layerId);
|
||||
const layerCount = controlLayers.present.layers.length;
|
||||
const layerIndex = canvasV2.layers.findIndex((l) => l.id === layerId);
|
||||
const layerCount = canvasV2.layers.length;
|
||||
return {
|
||||
canMoveForward: layerIndex < layerCount - 1,
|
||||
canMoveBackward: layerIndex > 0,
|
||||
|
@ -22,7 +22,7 @@ export const LayerMenuRGActions = memo(({ layerId }: Props) => {
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
const layer = canvasV2.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
return {
|
||||
canAddPositivePrompt: layer.positivePrompt === null,
|
||||
|
@ -37,7 +37,7 @@ export const LayerOpacity = memo(({ layerId }: Props) => {
|
||||
const selectOpacity = useMemo(
|
||||
() =>
|
||||
createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = selectLayerOrThrow(controlLayers.present, layerId, isLayerWithOpacity);
|
||||
const layer = selectLayerOrThrow(canvasV2, layerId, isLayerWithOpacity);
|
||||
return Math.round(layer.opacity * 100);
|
||||
}),
|
||||
[layerId]
|
||||
|
@ -30,14 +30,14 @@ export const RGLayer = memo(({ layerId }: Props) => {
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
const layer = canvasV2.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
return {
|
||||
color: rgbColorToString(layer.previewColor),
|
||||
hasPositivePrompt: layer.positivePrompt !== null,
|
||||
hasNegativePrompt: layer.negativePrompt !== null,
|
||||
hasIPAdapters: layer.ipAdapters.length > 0,
|
||||
isSelected: layerId === controlLayers.present.selectedLayerId,
|
||||
isSelected: layerId === canvasV2.selectedLayerId,
|
||||
autoNegative: layer.autoNegative,
|
||||
};
|
||||
}),
|
||||
|
@ -16,7 +16,7 @@ const useAutoNegative = (layerId: string) => {
|
||||
const selectAutoNegative = useMemo(
|
||||
() =>
|
||||
createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
const layer = canvasV2.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
return layer.autoNegative;
|
||||
}),
|
||||
|
@ -20,7 +20,7 @@ export const RGLayerColorPicker = memo(({ layerId }: Props) => {
|
||||
const selectColor = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
const layer = canvasV2.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an vector mask layer`);
|
||||
return layer.previewColor;
|
||||
}),
|
||||
|
@ -15,7 +15,7 @@ export const RGLayerIPAdapterList = memo(({ layerId }: Props) => {
|
||||
const selectIPAdapterIds = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.filter(isRegionalGuidanceLayer).find((l) => l.id === layerId);
|
||||
const layer = canvasV2.layers.filter(isRegionalGuidanceLayer).find((l) => l.id === layerId);
|
||||
assert(layer, `Layer ${layerId} not found`);
|
||||
return layer.ipAdapters;
|
||||
}),
|
||||
|
@ -28,7 +28,7 @@ export const RGLayerIPAdapterWrapper = memo(({ layerId, ipAdapterId, ipAdapterNu
|
||||
const onDeleteIPAdapter = useCallback(() => {
|
||||
dispatch(regionalGuidanceIPAdapterDeleted({ layerId, ipAdapterId }));
|
||||
}, [dispatch, ipAdapterId, layerId]);
|
||||
const ipAdapter = useAppSelector((s) => selectRGLayerIPAdapterOrThrow(s.controlLayers.present, layerId, ipAdapterId));
|
||||
const ipAdapter = useAppSelector((s) => selectRGLayerIPAdapterOrThrow(s.canvasV2, layerId, ipAdapterId));
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
(beginEndStepPct: [number, number]) => {
|
||||
|
@ -19,7 +19,7 @@ type Props = {
|
||||
export const RasterLayer = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isSelected = useAppSelector(
|
||||
(s) => selectLayerOrThrow(s.controlLayers.present, layerId, isRasterLayer).isSelected
|
||||
(s) => selectLayerOrThrow(s.canvasV2, layerId, isRasterLayer).isSelected
|
||||
);
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(layerSelected(layerId));
|
||||
|
@ -5,51 +5,59 @@ import { logger } from 'app/logging/logger';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||
import { HeadsUpDisplay } from 'features/controlLayers/components/HeadsUpDisplay';
|
||||
import {
|
||||
BRUSH_SPACING_PCT,
|
||||
MAX_BRUSH_SPACING_PX,
|
||||
MIN_BRUSH_SPACING_PX,
|
||||
TRANSPARENCY_CHECKER_PATTERN,
|
||||
} from 'features/controlLayers/konva/constants';
|
||||
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
|
||||
import { setStageEventHandlers } from 'features/controlLayers/konva/events';
|
||||
import { debouncedRenderers, renderers as normalRenderers } from 'features/controlLayers/konva/renderers/layers';
|
||||
import { caBboxChanged, caTranslated } from 'features/controlLayers/store/controlAdaptersSlice';
|
||||
import {
|
||||
$bbox,
|
||||
$brushSpacingPx,
|
||||
$brushWidth,
|
||||
$fill,
|
||||
$invertScroll,
|
||||
$currentFill,
|
||||
$isDrawing,
|
||||
$isMouseDown,
|
||||
$lastAddedPoint,
|
||||
$lastCursorPos,
|
||||
$lastMouseDownPos,
|
||||
$selectedLayer,
|
||||
$selectedEntity,
|
||||
$spaceKey,
|
||||
$stageAttrs,
|
||||
$tool,
|
||||
$toolBuffer,
|
||||
$toolState,
|
||||
bboxChanged,
|
||||
brushLineAdded,
|
||||
brushSizeChanged,
|
||||
eraserLineAdded,
|
||||
layerBboxChanged,
|
||||
layerTranslated,
|
||||
linePointsAdded,
|
||||
rectAdded,
|
||||
brushWidthChanged,
|
||||
eraserWidthChanged,
|
||||
selectCanvasV2Slice,
|
||||
toolBufferChanged,
|
||||
toolChanged,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { selectLayersSlice } from 'features/controlLayers/store/layersSlice';
|
||||
import { selectRegionalGuidanceSlice } from 'features/controlLayers/store/regionalGuidanceSlice';
|
||||
import {
|
||||
layerBboxChanged,
|
||||
layerBrushLineAdded,
|
||||
layerEraserLineAdded,
|
||||
layerLinePointAdded,
|
||||
layerRectAdded,
|
||||
layerTranslated,
|
||||
selectLayersSlice,
|
||||
} from 'features/controlLayers/store/layersSlice';
|
||||
import {
|
||||
rgBboxChanged,
|
||||
rgBrushLineAdded,
|
||||
rgEraserLineAdded,
|
||||
rgLinePointAdded,
|
||||
rgRectAdded,
|
||||
rgTranslated,
|
||||
selectRegionalGuidanceSlice,
|
||||
} from 'features/controlLayers/store/regionalGuidanceSlice';
|
||||
import type {
|
||||
AddBrushLineArg,
|
||||
AddEraserLineArg,
|
||||
AddPointToLineArg,
|
||||
AddRectShapeArg,
|
||||
BboxChangedArg,
|
||||
BrushLineAddedArg,
|
||||
CanvasEntity,
|
||||
EraserLineAddedArg,
|
||||
PointAddedToLineArg,
|
||||
PosChangedArg,
|
||||
RectShapeAddedArg,
|
||||
Tool,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import { clamp } from 'lodash-es';
|
||||
import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getImageDTO } from 'services/api/endpoints/images';
|
||||
@ -61,12 +69,12 @@ Konva.showWarnings = false;
|
||||
|
||||
const log = logger('controlLayers');
|
||||
|
||||
const selectBrushColor = createSelector(
|
||||
const selectBrushFill = createSelector(
|
||||
selectCanvasV2Slice,
|
||||
selectLayersSlice,
|
||||
selectRegionalGuidanceSlice,
|
||||
(canvas, layers, regionalGuidance) => {
|
||||
const rg = regionalGuidance.regions.find((i) => i.id === canvas.lastSelectedItem?.id);
|
||||
const rg = regionalGuidance.regions.find((i) => i.id === canvas.selectedEntityIdentifier?.id);
|
||||
|
||||
if (rg) {
|
||||
return rgbaColorToString({ ...rg.fill, a: regionalGuidance.opacity });
|
||||
@ -76,89 +84,121 @@ const selectBrushColor = createSelector(
|
||||
}
|
||||
);
|
||||
|
||||
const selectSelectedLayer = createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
return controlLayers.present.layers.find((l) => l.id === controlLayers.present.selectedLayerId) ?? null;
|
||||
});
|
||||
|
||||
const selectLayerCount = createSelector(selectCanvasV2Slice, (controlLayers) => controlLayers.present.layers.length);
|
||||
|
||||
const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null, asPreview: boolean) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const state = useAppSelector((s) => s.controlLayers.present);
|
||||
const tool = useStore($tool);
|
||||
const canvasV2State = useAppSelector(selectCanvasV2Slice);
|
||||
const layersState = useAppSelector((s) => s.layers);
|
||||
const controlAdaptersState = useAppSelector((s) => s.controlAdaptersV2);
|
||||
const ipAdaptersState = useAppSelector((s) => s.ipAdapters);
|
||||
const regionalGuidanceState = useAppSelector((s) => s.regionalGuidance);
|
||||
const lastCursorPos = useStore($lastCursorPos);
|
||||
const lastMouseDownPos = useStore($lastMouseDownPos);
|
||||
const isMouseDown = useStore($isMouseDown);
|
||||
const isDrawing = useStore($isDrawing);
|
||||
const brushColor = useAppSelector(selectBrushColor);
|
||||
const selectedLayer = useAppSelector(selectSelectedLayer);
|
||||
const renderers = useMemo(() => (asPreview ? debouncedRenderers : normalRenderers), [asPreview]);
|
||||
const dpr = useDevicePixelRatio({ round: false });
|
||||
const shouldInvertBrushSizeScrollDirection = useAppSelector((s) => s.canvas.shouldInvertBrushSizeScrollDirection);
|
||||
const brushSpacingPx = useMemo(
|
||||
() => clamp(state.brushSize / BRUSH_SPACING_PCT, MIN_BRUSH_SPACING_PX, MAX_BRUSH_SPACING_PX),
|
||||
[state.brushSize]
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
$fill.set(brushColor);
|
||||
$brushWidth.set(state.brushSize);
|
||||
$brushSpacingPx.set(brushSpacingPx);
|
||||
$selectedLayer.set(selectedLayer);
|
||||
$invertScroll.set(shouldInvertBrushSizeScrollDirection);
|
||||
$bbox.set(state.bbox);
|
||||
const brushColor = useAppSelector(selectBrushFill);
|
||||
const selectedEntity = useMemo(() => {
|
||||
const identifier = canvasV2State.selectedEntityIdentifier;
|
||||
if (!identifier) {
|
||||
return null;
|
||||
} else if (identifier.type === 'layer') {
|
||||
return layersState.layers.find((i) => i.id === identifier.id) ?? null;
|
||||
} else if (identifier.type === 'control_adapter') {
|
||||
return controlAdaptersState.controlAdapters.find((i) => i.id === identifier.id) ?? null;
|
||||
} else if (identifier.type === 'ip_adapter') {
|
||||
return ipAdaptersState.ipAdapters.find((i) => i.id === identifier.id) ?? null;
|
||||
} else if (identifier.type === 'regional_guidance') {
|
||||
return regionalGuidanceState.regions.find((i) => i.id === identifier.id) ?? null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [
|
||||
brushSpacingPx,
|
||||
brushColor,
|
||||
selectedLayer,
|
||||
shouldInvertBrushSizeScrollDirection,
|
||||
state.brushSize,
|
||||
state.selectedLayerId,
|
||||
state.brushColor,
|
||||
state.bbox,
|
||||
canvasV2State.selectedEntityIdentifier,
|
||||
controlAdaptersState.controlAdapters,
|
||||
ipAdaptersState.ipAdapters,
|
||||
layersState.layers,
|
||||
regionalGuidanceState.regions,
|
||||
]);
|
||||
|
||||
const onLayerPosChanged = useCallback(
|
||||
(layerId: string, x: number, y: number) => {
|
||||
dispatch(layerTranslated({ layerId, x, y }));
|
||||
const currentFill = useMemo(() => {
|
||||
if (selectedEntity && selectedEntity.type === 'regional_guidance') {
|
||||
return { ...selectedEntity.fill, a: regionalGuidanceState.opacity };
|
||||
}
|
||||
return canvasV2State.tool.fill;
|
||||
}, [canvasV2State.tool.fill, regionalGuidanceState.opacity, selectedEntity]);
|
||||
|
||||
const renderers = useMemo(() => (asPreview ? debouncedRenderers : normalRenderers), [asPreview]);
|
||||
const dpr = useDevicePixelRatio({ round: false });
|
||||
|
||||
useLayoutEffect(() => {
|
||||
$toolState.set(canvasV2State.tool);
|
||||
$selectedEntity.set(selectedEntity);
|
||||
$bbox.set(canvasV2State.bbox);
|
||||
$currentFill.set(currentFill);
|
||||
}, [selectedEntity, canvasV2State.tool, canvasV2State.bbox, currentFill]);
|
||||
|
||||
const onPosChanged = useCallback(
|
||||
(arg: PosChangedArg, entityType: CanvasEntity['type']) => {
|
||||
if (entityType === 'layer') {
|
||||
dispatch(layerTranslated(arg));
|
||||
} else if (entityType === 'control_adapter') {
|
||||
dispatch(caTranslated(arg));
|
||||
} else if (entityType === 'regional_guidance') {
|
||||
dispatch(rgTranslated(arg));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const onBboxChanged = useCallback(
|
||||
(layerId: string, bbox: IRect | null) => {
|
||||
dispatch(layerBboxChanged({ layerId, bbox }));
|
||||
(arg: BboxChangedArg, entityType: CanvasEntity['type']) => {
|
||||
if (entityType === 'layer') {
|
||||
dispatch(layerBboxChanged(arg));
|
||||
} else if (entityType === 'control_adapter') {
|
||||
dispatch(caBboxChanged(arg));
|
||||
} else if (entityType === 'regional_guidance') {
|
||||
dispatch(rgBboxChanged(arg));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const onBrushLineAdded = useCallback(
|
||||
(arg: AddBrushLineArg) => {
|
||||
dispatch(brushLineAdded(arg));
|
||||
(arg: BrushLineAddedArg, entityType: CanvasEntity['type']) => {
|
||||
if (entityType === 'layer') {
|
||||
dispatch(layerBrushLineAdded(arg));
|
||||
} else if (entityType === 'regional_guidance') {
|
||||
dispatch(rgBrushLineAdded(arg));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const onEraserLineAdded = useCallback(
|
||||
(arg: AddEraserLineArg) => {
|
||||
dispatch(eraserLineAdded(arg));
|
||||
(arg: EraserLineAddedArg, entityType: CanvasEntity['type']) => {
|
||||
if (entityType === 'layer') {
|
||||
dispatch(layerEraserLineAdded(arg));
|
||||
} else if (entityType === 'regional_guidance') {
|
||||
dispatch(rgEraserLineAdded(arg));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const onPointAddedToLine = useCallback(
|
||||
(arg: AddPointToLineArg) => {
|
||||
dispatch(linePointsAdded(arg));
|
||||
(arg: PointAddedToLineArg, entityType: CanvasEntity['type']) => {
|
||||
if (entityType === 'layer') {
|
||||
dispatch(layerLinePointAdded(arg));
|
||||
} else if (entityType === 'regional_guidance') {
|
||||
dispatch(rgLinePointAdded(arg));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const onRectShapeAdded = useCallback(
|
||||
(arg: AddRectShapeArg) => {
|
||||
dispatch(rectAdded(arg));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const onBrushSizeChanged = useCallback(
|
||||
(size: number) => {
|
||||
dispatch(brushSizeChanged(size));
|
||||
(arg: RectShapeAddedArg, entityType: CanvasEntity['type']) => {
|
||||
if (entityType === 'layer') {
|
||||
dispatch(layerRectAdded(arg));
|
||||
} else if (entityType === 'regional_guidance') {
|
||||
dispatch(rgRectAdded(arg));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
@ -168,6 +208,30 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const onBrushWidthChanged = useCallback(
|
||||
(width: number) => {
|
||||
dispatch(brushWidthChanged(width));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const onEraserWidthChanged = useCallback(
|
||||
(width: number) => {
|
||||
dispatch(eraserWidthChanged(width));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const setTool = useCallback(
|
||||
(tool: Tool) => {
|
||||
dispatch(toolChanged(tool));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const setToolBuffer = useCallback(
|
||||
(toolBuffer: Tool | null) => {
|
||||
dispatch(toolBufferChanged(toolBuffer));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
log.trace('Initializing stage');
|
||||
@ -189,32 +253,29 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
|
||||
|
||||
const cleanup = setStageEventHandlers({
|
||||
stage,
|
||||
getTool: $tool.get,
|
||||
setTool: $tool.set,
|
||||
getToolBuffer: $toolBuffer.get,
|
||||
setToolBuffer: $toolBuffer.set,
|
||||
getToolState: $toolState.get,
|
||||
setTool,
|
||||
setToolBuffer,
|
||||
getIsDrawing: $isDrawing.get,
|
||||
setIsDrawing: $isDrawing.set,
|
||||
getIsMouseDown: $isMouseDown.get,
|
||||
setIsMouseDown: $isMouseDown.set,
|
||||
getBrushColor: $fill.get,
|
||||
getBrushSize: $brushWidth.get,
|
||||
getBrushSpacingPx: $brushSpacingPx.get,
|
||||
getSelectedLayer: $selectedLayer.get,
|
||||
getSelectedEntity: $selectedEntity.get,
|
||||
getLastAddedPoint: $lastAddedPoint.get,
|
||||
setLastAddedPoint: $lastAddedPoint.set,
|
||||
getLastCursorPos: $lastCursorPos.get,
|
||||
setLastCursorPos: $lastCursorPos.set,
|
||||
getLastMouseDownPos: $lastMouseDownPos.get,
|
||||
setLastMouseDownPos: $lastMouseDownPos.set,
|
||||
getShouldInvert: $invertScroll.get,
|
||||
getSpaceKey: $spaceKey.get,
|
||||
setStageAttrs: $stageAttrs.set,
|
||||
onBrushSizeChanged,
|
||||
onBrushLineAdded,
|
||||
onEraserLineAdded,
|
||||
onPointAddedToLine,
|
||||
onRectShapeAdded,
|
||||
onBrushWidthChanged,
|
||||
onEraserWidthChanged,
|
||||
getCurrentFill: $currentFill.get,
|
||||
});
|
||||
|
||||
return () => {
|
||||
@ -224,12 +285,15 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
|
||||
}, [
|
||||
asPreview,
|
||||
onBrushLineAdded,
|
||||
onBrushSizeChanged,
|
||||
onBrushWidthChanged,
|
||||
onEraserLineAdded,
|
||||
onPointAddedToLine,
|
||||
onRectShapeAdded,
|
||||
stage,
|
||||
container,
|
||||
onEraserWidthChanged,
|
||||
setTool,
|
||||
setToolBuffer,
|
||||
]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@ -267,29 +331,26 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
|
||||
log.trace('Rendering tool preview');
|
||||
renderers.renderToolPreview(
|
||||
stage,
|
||||
tool,
|
||||
brushColor,
|
||||
selectedLayer?.type ?? null,
|
||||
state.globalMaskLayerOpacity,
|
||||
canvasV2State.tool,
|
||||
currentFill,
|
||||
selectedEntity,
|
||||
lastCursorPos,
|
||||
lastMouseDownPos,
|
||||
state.brushSize,
|
||||
isDrawing,
|
||||
isMouseDown
|
||||
);
|
||||
}, [
|
||||
asPreview,
|
||||
brushColor,
|
||||
canvasV2State.tool,
|
||||
currentFill,
|
||||
isDrawing,
|
||||
isMouseDown,
|
||||
lastCursorPos,
|
||||
lastMouseDownPos,
|
||||
renderers,
|
||||
selectedLayer?.type,
|
||||
selectedEntity,
|
||||
stage,
|
||||
state.brushSize,
|
||||
state.globalMaskLayerOpacity,
|
||||
tool,
|
||||
]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@ -300,8 +361,8 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
|
||||
log.trace('Rendering bbox preview');
|
||||
renderers.renderBboxPreview(
|
||||
stage,
|
||||
state.bbox,
|
||||
tool,
|
||||
canvasV2State.bbox,
|
||||
canvasV2State.tool.selected,
|
||||
$bbox.get,
|
||||
onBboxTransformed,
|
||||
$shift.get,
|
||||
@ -309,21 +370,41 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
|
||||
$meta.get,
|
||||
$alt.get
|
||||
);
|
||||
}, [asPreview, onBboxTransformed, renderers, stage, state.bbox, tool]);
|
||||
}, [asPreview, canvasV2State.bbox, canvasV2State.tool.selected, onBboxTransformed, renderers, stage]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
log.trace('Rendering layers');
|
||||
renderers.renderLayers(stage, state.layers, state.globalMaskLayerOpacity, tool, getImageDTO, onLayerPosChanged);
|
||||
}, [stage, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged, renderers]);
|
||||
renderers.renderLayers(
|
||||
stage,
|
||||
layersState.layers,
|
||||
controlAdaptersState.controlAdapters,
|
||||
regionalGuidanceState.regions,
|
||||
regionalGuidanceState.opacity,
|
||||
canvasV2State.tool.selected,
|
||||
selectedEntity,
|
||||
getImageDTO,
|
||||
onPosChanged
|
||||
);
|
||||
}, [
|
||||
stage,
|
||||
renderers,
|
||||
layersState.layers,
|
||||
controlAdaptersState.controlAdapters,
|
||||
regionalGuidanceState.regions,
|
||||
regionalGuidanceState.opacity,
|
||||
onPosChanged,
|
||||
canvasV2State.tool.selected,
|
||||
selectedEntity,
|
||||
]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (asPreview) {
|
||||
// Preview should not check for transparency
|
||||
return;
|
||||
}
|
||||
log.trace('Updating bboxes');
|
||||
debouncedRenderers.updateBboxes(stage, state.layers, onBboxChanged);
|
||||
}, [stage, asPreview, state.layers, onBboxChanged]);
|
||||
// useLayoutEffect(() => {
|
||||
// if (asPreview) {
|
||||
// // Preview should not check for transparency
|
||||
// return;
|
||||
// }
|
||||
// log.trace('Updating bboxes');
|
||||
// debouncedRenderers.updateBboxes(stage, state.layers, onBboxChanged);
|
||||
// }, [stage, asPreview, state.layers, onBboxChanged]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
Konva.pixelRatio = dpr;
|
||||
@ -395,7 +476,7 @@ StageComponent.displayName = 'StageComponent';
|
||||
|
||||
const NoLayersFallback = () => {
|
||||
const { t } = useTranslation();
|
||||
const layerCount = useAppSelector(selectLayerCount);
|
||||
const layerCount = useAppSelector((s) => s.layers.layers.length);
|
||||
if (layerCount) {
|
||||
return null;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import {
|
||||
} from 'react-icons/pi';
|
||||
|
||||
const selectIsDisabled = createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const selectedLayer = controlLayers.present.layers.find((l) => l.id === controlLayers.present.selectedLayerId);
|
||||
const selectedLayer = canvasV2.layers.find((l) => l.id === canvasV2.selectedLayerId);
|
||||
return selectedLayer?.type !== 'regional_guidance_layer' && selectedLayer?.type !== 'raster_layer';
|
||||
});
|
||||
|
||||
@ -29,7 +29,7 @@ export const ToolChooser: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isDisabled = useAppSelector(selectIsDisabled);
|
||||
const selectedLayerId = useAppSelector((s) => s.controlLayers.present.selectedLayerId);
|
||||
const selectedLayerId = useAppSelector((s) => s.canvasV2.selectedLayerId);
|
||||
const tool = useStore($tool);
|
||||
|
||||
const setToolToBrush = useCallback(() => {
|
||||
|
@ -102,7 +102,7 @@ export const useAddIPAdapterToIPALayer = (layerId: string) => {
|
||||
|
||||
export const useAddIILayer = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isDisabled = useAppSelector((s) => Boolean(s.controlLayers.present.layers.find(isInitialImageLayer)));
|
||||
const isDisabled = useAppSelector((s) => Boolean(s.canvasV2.layers.find(isInitialImageLayer)));
|
||||
const addIILayer = useCallback(() => {
|
||||
dispatch(iiLayerAdded(null));
|
||||
}, [dispatch]);
|
||||
|
@ -10,7 +10,7 @@ export const useLayerPositivePrompt = (layerId: string) => {
|
||||
const selectLayer = useMemo(
|
||||
() =>
|
||||
createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
const layer = canvasV2.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
assert(layer.positivePrompt !== null, `Layer ${layerId} does not have a positive prompt`);
|
||||
return layer.positivePrompt;
|
||||
@ -25,7 +25,7 @@ export const useLayerNegativePrompt = (layerId: string) => {
|
||||
const selectLayer = useMemo(
|
||||
() =>
|
||||
createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
const layer = canvasV2.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
assert(layer.negativePrompt !== null, `Layer ${layerId} does not have a negative prompt`);
|
||||
return layer.negativePrompt;
|
||||
@ -40,7 +40,7 @@ export const useLayerIsEnabled = (layerId: string) => {
|
||||
const selectLayer = useMemo(
|
||||
() =>
|
||||
createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
const layer = canvasV2.layers.find((l) => l.id === layerId);
|
||||
assert(layer, `Layer ${layerId} not found`);
|
||||
return layer.isEnabled;
|
||||
}),
|
||||
@ -54,7 +54,7 @@ export const useLayerType = (layerId: string) => {
|
||||
const selectLayer = useMemo(
|
||||
() =>
|
||||
createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
const layer = canvasV2.layers.find((l) => l.id === layerId);
|
||||
assert(layer, `Layer ${layerId} not found`);
|
||||
return layer.type;
|
||||
}),
|
||||
@ -68,7 +68,7 @@ export const useCALayerOpacity = (layerId: string) => {
|
||||
const selectLayer = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
||||
const layer = canvasV2.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
||||
assert(layer, `Layer ${layerId} not found`);
|
||||
return { opacity: Math.round(layer.opacity * 100), isFilterEnabled: layer.isFilterEnabled };
|
||||
}),
|
||||
|
@ -2,27 +2,27 @@ import { calculateNewBrushSize } from 'features/canvas/hooks/useCanvasZoom';
|
||||
import { CANVAS_SCALE_BY, MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from 'features/canvas/util/constants';
|
||||
import { getScaledFlooredCursorPosition } from 'features/controlLayers/konva/util';
|
||||
import type {
|
||||
AddBrushLineArg,
|
||||
AddEraserLineArg,
|
||||
AddPointToLineArg,
|
||||
AddRectShapeArg,
|
||||
LayerData,
|
||||
BrushLineAddedArg,
|
||||
CanvasEntity,
|
||||
CanvasV2State,
|
||||
EraserLineAddedArg,
|
||||
PointAddedToLineArg,
|
||||
RectShapeAddedArg,
|
||||
RgbaColor,
|
||||
StageAttrs,
|
||||
Tool,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { DEFAULT_RGBA_COLOR } from 'features/controlLayers/store/types';
|
||||
import type Konva from 'konva';
|
||||
import type { Vector2d } from 'konva/lib/types';
|
||||
import { clamp } from 'lodash-es';
|
||||
import type { RgbaColor } from 'react-colorful';
|
||||
|
||||
import { PREVIEW_TOOL_GROUP_ID } from './naming';
|
||||
|
||||
type Arg = {
|
||||
stage: Konva.Stage;
|
||||
getTool: () => Tool;
|
||||
getToolState: () => CanvasV2State['tool'];
|
||||
getCurrentFill: () => RgbaColor;
|
||||
setTool: (tool: Tool) => void;
|
||||
getToolBuffer: () => Tool | null;
|
||||
setToolBuffer: (tool: Tool | null) => void;
|
||||
getIsDrawing: () => boolean;
|
||||
setIsDrawing: (isDrawing: boolean) => void;
|
||||
@ -35,17 +35,14 @@ type Arg = {
|
||||
getLastAddedPoint: () => Vector2d | null;
|
||||
setLastAddedPoint: (pos: Vector2d | null) => void;
|
||||
setStageAttrs: (attrs: StageAttrs) => void;
|
||||
getBrushColor: () => RgbaColor;
|
||||
getBrushSize: () => number;
|
||||
getBrushSpacingPx: () => number;
|
||||
getSelectedLayer: () => LayerData | null;
|
||||
getShouldInvert: () => boolean;
|
||||
getSelectedEntity: () => CanvasEntity | null;
|
||||
getSpaceKey: () => boolean;
|
||||
onBrushLineAdded: (arg: AddBrushLineArg) => void;
|
||||
onEraserLineAdded: (arg: AddEraserLineArg) => void;
|
||||
onPointAddedToLine: (arg: AddPointToLineArg) => void;
|
||||
onRectShapeAdded: (arg: AddRectShapeArg) => void;
|
||||
onBrushSizeChanged: (size: number) => void;
|
||||
onBrushLineAdded: (arg: BrushLineAddedArg, entityType: CanvasEntity['type']) => void;
|
||||
onEraserLineAdded: (arg: EraserLineAddedArg, entityType: CanvasEntity['type']) => void;
|
||||
onPointAddedToLine: (arg: PointAddedToLineArg, entityType: CanvasEntity['type']) => void;
|
||||
onRectShapeAdded: (arg: RectShapeAddedArg, entityType: CanvasEntity['type']) => void;
|
||||
onBrushWidthChanged: (size: number) => void;
|
||||
onEraserWidthChanged: (size: number) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -72,30 +69,41 @@ const updateLastCursorPos = (stage: Konva.Stage, setLastCursorPos: Arg['setLastC
|
||||
* @param onPointAddedToLine The callback to add a point to a line
|
||||
*/
|
||||
const maybeAddNextPoint = (
|
||||
selectedLayer: LayerData,
|
||||
selectedEntity: CanvasEntity,
|
||||
currentPos: Vector2d,
|
||||
getToolState: Arg['getToolState'],
|
||||
getLastAddedPoint: Arg['getLastAddedPoint'],
|
||||
setLastAddedPoint: Arg['setLastAddedPoint'],
|
||||
getBrushSpacingPx: Arg['getBrushSpacingPx'],
|
||||
onPointAddedToLine: Arg['onPointAddedToLine']
|
||||
) => {
|
||||
if (selectedEntity.type !== 'layer' && selectedEntity.type !== 'regional_guidance') {
|
||||
return;
|
||||
}
|
||||
// Continue the last line
|
||||
const lastAddedPoint = getLastAddedPoint();
|
||||
const toolState = getToolState();
|
||||
const minSpacingPx = toolState.selected === 'brush' ? toolState.brush.width * 0.05 : toolState.eraser.width * 0.05;
|
||||
if (lastAddedPoint) {
|
||||
// Dispatching redux events impacts perf substantially - using brush spacing keeps dispatches to a reasonable number
|
||||
if (Math.hypot(lastAddedPoint.x - currentPos.x, lastAddedPoint.y - currentPos.y) < getBrushSpacingPx()) {
|
||||
if (Math.hypot(lastAddedPoint.x - currentPos.x, lastAddedPoint.y - currentPos.y) < minSpacingPx) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setLastAddedPoint(currentPos);
|
||||
onPointAddedToLine({ layerId, point: [currentPos.x - selectedLayer.x, currentPos.y - selectedLayer.y] });
|
||||
onPointAddedToLine(
|
||||
{
|
||||
id: selectedEntity.id,
|
||||
point: [currentPos.x - selectedEntity.x, currentPos.y - selectedEntity.y],
|
||||
},
|
||||
selectedEntity.type
|
||||
);
|
||||
};
|
||||
|
||||
export const setStageEventHandlers = ({
|
||||
stage,
|
||||
getTool,
|
||||
getToolState,
|
||||
getCurrentFill,
|
||||
setTool,
|
||||
getToolBuffer,
|
||||
setToolBuffer,
|
||||
getIsDrawing,
|
||||
setIsDrawing,
|
||||
@ -108,17 +116,14 @@ export const setStageEventHandlers = ({
|
||||
getLastAddedPoint,
|
||||
setLastAddedPoint,
|
||||
setStageAttrs,
|
||||
getBrushColor,
|
||||
getBrushSize,
|
||||
getBrushSpacingPx,
|
||||
getSelectedLayer,
|
||||
getShouldInvert,
|
||||
getSelectedEntity,
|
||||
getSpaceKey,
|
||||
onBrushLineAdded,
|
||||
onEraserLineAdded,
|
||||
onPointAddedToLine,
|
||||
onRectShapeAdded,
|
||||
onBrushSizeChanged,
|
||||
onBrushWidthChanged: onBrushSizeChanged,
|
||||
onEraserWidthChanged: onEraserSizeChanged,
|
||||
}: Arg): (() => void) => {
|
||||
//#region mouseenter
|
||||
stage.on('mouseenter', (e) => {
|
||||
@ -126,7 +131,7 @@ export const setStageEventHandlers = ({
|
||||
if (!stage) {
|
||||
return;
|
||||
}
|
||||
const tool = getTool();
|
||||
const tool = getToolState().selected;
|
||||
stage.findOne<Konva.Layer>(`#${PREVIEW_TOOL_GROUP_ID}`)?.visible(tool === 'brush' || tool === 'eraser');
|
||||
});
|
||||
|
||||
@ -137,13 +142,13 @@ export const setStageEventHandlers = ({
|
||||
return;
|
||||
}
|
||||
setIsMouseDown(true);
|
||||
const tool = getTool();
|
||||
const toolState = getToolState();
|
||||
const pos = updateLastCursorPos(stage, setLastCursorPos);
|
||||
const selectedLayer = getSelectedLayer();
|
||||
if (!pos || !selectedLayer) {
|
||||
const selectedEntity = getSelectedEntity();
|
||||
if (!pos || !selectedEntity) {
|
||||
return;
|
||||
}
|
||||
if (selectedLayer.type !== 'regional_guidance_layer' && selectedLayer.type !== 'raster_layer') {
|
||||
if (selectedEntity.type !== 'regional_guidance' && selectedEntity.type !== 'layer') {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -155,23 +160,37 @@ export const setStageEventHandlers = ({
|
||||
setIsDrawing(true);
|
||||
setLastMouseDownPos(pos);
|
||||
|
||||
if (tool === 'brush') {
|
||||
onBrushLineAdded({
|
||||
layerId: selectedLayer.id,
|
||||
points: [pos.x - selectedLayer.x, pos.y - selectedLayer.y, pos.x - selectedLayer.x, pos.y - selectedLayer.y],
|
||||
color: selectedLayer.type === 'raster_layer' ? getBrushColor() : DEFAULT_RGBA_COLOR,
|
||||
});
|
||||
if (toolState.selected === 'brush') {
|
||||
onBrushLineAdded(
|
||||
{
|
||||
id: selectedEntity.id,
|
||||
points: [
|
||||
pos.x - selectedEntity.x,
|
||||
pos.y - selectedEntity.y,
|
||||
pos.x - selectedEntity.x,
|
||||
pos.y - selectedEntity.y,
|
||||
],
|
||||
color: getCurrentFill(),
|
||||
width: toolState.brush.width,
|
||||
},
|
||||
selectedEntity.type
|
||||
);
|
||||
}
|
||||
|
||||
if (tool === 'eraser') {
|
||||
onEraserLineAdded({
|
||||
layerId: selectedLayer.id,
|
||||
points: [pos.x - selectedLayer.x, pos.y - selectedLayer.y, pos.x - selectedLayer.x, pos.y - selectedLayer.y],
|
||||
});
|
||||
}
|
||||
|
||||
if (tool === 'rect') {
|
||||
// Setting the last mouse down pos starts a rect
|
||||
if (toolState.selected === 'eraser') {
|
||||
onEraserLineAdded(
|
||||
{
|
||||
id: selectedEntity.id,
|
||||
points: [
|
||||
pos.x - selectedEntity.x,
|
||||
pos.y - selectedEntity.y,
|
||||
pos.x - selectedEntity.x,
|
||||
pos.y - selectedEntity.y,
|
||||
],
|
||||
width: toolState.eraser.width,
|
||||
},
|
||||
selectedEntity.type
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -183,12 +202,12 @@ export const setStageEventHandlers = ({
|
||||
}
|
||||
setIsMouseDown(false);
|
||||
const pos = getLastCursorPos();
|
||||
const selectedLayer = getSelectedLayer();
|
||||
const selectedEntity = getSelectedEntity();
|
||||
|
||||
if (!pos || !selectedLayer) {
|
||||
if (!pos || !selectedEntity) {
|
||||
return;
|
||||
}
|
||||
if (selectedLayer.type !== 'regional_guidance_layer' && selectedLayer.type !== 'raster_layer') {
|
||||
if (selectedEntity.type !== 'regional_guidance' && selectedEntity.type !== 'layer') {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -197,21 +216,24 @@ export const setStageEventHandlers = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const tool = getTool();
|
||||
const toolState = getToolState();
|
||||
|
||||
if (tool === 'rect') {
|
||||
if (toolState.selected === 'rect') {
|
||||
const lastMouseDownPos = getLastMouseDownPos();
|
||||
if (lastMouseDownPos) {
|
||||
onRectShapeAdded({
|
||||
layerId: selectedLayer.id,
|
||||
rect: {
|
||||
x: Math.min(pos.x, lastMouseDownPos.x),
|
||||
y: Math.min(pos.y, lastMouseDownPos.y),
|
||||
width: Math.abs(pos.x - lastMouseDownPos.x),
|
||||
height: Math.abs(pos.y - lastMouseDownPos.y),
|
||||
onRectShapeAdded(
|
||||
{
|
||||
id: selectedEntity.id,
|
||||
rect: {
|
||||
x: Math.min(pos.x, lastMouseDownPos.x),
|
||||
y: Math.min(pos.y, lastMouseDownPos.y),
|
||||
width: Math.abs(pos.x - lastMouseDownPos.x),
|
||||
height: Math.abs(pos.y - lastMouseDownPos.y),
|
||||
},
|
||||
color: getCurrentFill(),
|
||||
},
|
||||
color: selectedLayer.type === 'raster_layer' ? getBrushColor() : DEFAULT_RGBA_COLOR,
|
||||
});
|
||||
selectedEntity.type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,16 +247,18 @@ export const setStageEventHandlers = ({
|
||||
if (!stage) {
|
||||
return;
|
||||
}
|
||||
const tool = getTool();
|
||||
const toolState = getToolState();
|
||||
const pos = updateLastCursorPos(stage, setLastCursorPos);
|
||||
const selectedLayer = getSelectedLayer();
|
||||
const selectedEntity = getSelectedEntity();
|
||||
|
||||
stage.findOne<Konva.Layer>(`#${PREVIEW_TOOL_GROUP_ID}`)?.visible(tool === 'brush' || tool === 'eraser');
|
||||
stage
|
||||
.findOne<Konva.Layer>(`#${PREVIEW_TOOL_GROUP_ID}`)
|
||||
?.visible(toolState.selected === 'brush' || toolState.selected === 'eraser');
|
||||
|
||||
if (!pos || !selectedLayer) {
|
||||
if (!pos || !selectedEntity) {
|
||||
return;
|
||||
}
|
||||
if (selectedLayer.type !== 'regional_guidance_layer' && selectedLayer.type !== 'raster_layer') {
|
||||
if (selectedEntity.type !== 'regional_guidance' && selectedEntity.type !== 'layer') {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -247,45 +271,49 @@ export const setStageEventHandlers = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (tool === 'brush') {
|
||||
if (toolState.selected === 'brush') {
|
||||
if (getIsDrawing()) {
|
||||
// Continue the last line
|
||||
maybeAddNextPoint(
|
||||
selectedLayer.id,
|
||||
pos,
|
||||
getLastAddedPoint,
|
||||
setLastAddedPoint,
|
||||
getBrushSpacingPx,
|
||||
onPointAddedToLine
|
||||
);
|
||||
maybeAddNextPoint(selectedEntity, pos, getToolState, getLastAddedPoint, setLastAddedPoint, onPointAddedToLine);
|
||||
} else {
|
||||
// Start a new line
|
||||
onBrushLineAdded({
|
||||
layerId: selectedLayer.id,
|
||||
points: [pos.x - selectedLayer.x, pos.y - selectedLayer.y, pos.x - selectedLayer.x, pos.y - selectedLayer.y],
|
||||
color: selectedLayer.type === 'raster_layer' ? getBrushColor() : DEFAULT_RGBA_COLOR,
|
||||
});
|
||||
onBrushLineAdded(
|
||||
{
|
||||
id: selectedEntity.id,
|
||||
points: [
|
||||
pos.x - selectedEntity.x,
|
||||
pos.y - selectedEntity.y,
|
||||
pos.x - selectedEntity.x,
|
||||
pos.y - selectedEntity.y,
|
||||
],
|
||||
width: toolState.brush.width,
|
||||
color: getCurrentFill(),
|
||||
},
|
||||
selectedEntity.type
|
||||
);
|
||||
setIsDrawing(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (tool === 'eraser') {
|
||||
if (toolState.selected === 'eraser') {
|
||||
if (getIsDrawing()) {
|
||||
// Continue the last line
|
||||
maybeAddNextPoint(
|
||||
selectedLayer.id,
|
||||
pos,
|
||||
getLastAddedPoint,
|
||||
setLastAddedPoint,
|
||||
getBrushSpacingPx,
|
||||
onPointAddedToLine
|
||||
);
|
||||
maybeAddNextPoint(selectedEntity, pos, getToolState, getLastAddedPoint, setLastAddedPoint, onPointAddedToLine);
|
||||
} else {
|
||||
// Start a new line
|
||||
onEraserLineAdded({
|
||||
layerId: selectedLayer.id,
|
||||
points: [pos.x - selectedLayer.x, pos.y - selectedLayer.y, pos.x - selectedLayer.x, pos.y - selectedLayer.y],
|
||||
});
|
||||
onEraserLineAdded(
|
||||
{
|
||||
id: selectedEntity.id,
|
||||
points: [
|
||||
pos.x - selectedEntity.x,
|
||||
pos.y - selectedEntity.y,
|
||||
pos.x - selectedEntity.x,
|
||||
pos.y - selectedEntity.y,
|
||||
],
|
||||
width: toolState.eraser.width,
|
||||
},
|
||||
selectedEntity.type
|
||||
);
|
||||
setIsDrawing(true);
|
||||
}
|
||||
}
|
||||
@ -301,15 +329,15 @@ export const setStageEventHandlers = ({
|
||||
setIsDrawing(false);
|
||||
setLastCursorPos(null);
|
||||
setLastMouseDownPos(null);
|
||||
const selectedLayer = getSelectedLayer();
|
||||
const tool = getTool();
|
||||
const selectedEntity = getSelectedEntity();
|
||||
const toolState = getToolState();
|
||||
|
||||
stage.findOne<Konva.Layer>(`#${PREVIEW_TOOL_GROUP_ID}`)?.visible(false);
|
||||
|
||||
if (!pos || !selectedLayer) {
|
||||
if (!pos || !selectedEntity) {
|
||||
return;
|
||||
}
|
||||
if (selectedLayer.type !== 'regional_guidance_layer' && selectedLayer.type !== 'raster_layer') {
|
||||
if (selectedEntity.type !== 'regional_guidance' && selectedEntity.type !== 'layer') {
|
||||
return;
|
||||
}
|
||||
if (getSpaceKey()) {
|
||||
@ -317,12 +345,11 @@ export const setStageEventHandlers = ({
|
||||
return;
|
||||
}
|
||||
if (getIsMouseDown()) {
|
||||
if (tool === 'brush') {
|
||||
onPointAddedToLine({ layerId: selectedLayer.id, point: [pos.x, pos.y] });
|
||||
if (toolState.selected === 'brush') {
|
||||
onPointAddedToLine({ id: selectedEntity.id, point: [pos.x, pos.y] }, selectedEntity.type);
|
||||
}
|
||||
|
||||
if (tool === 'eraser') {
|
||||
onPointAddedToLine({ layerId: selectedLayer.id, point: [pos.x, pos.y] });
|
||||
if (toolState.selected === 'eraser') {
|
||||
onPointAddedToLine({ id: selectedEntity.id, point: [pos.x, pos.y] }, selectedEntity.type);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -331,12 +358,17 @@ export const setStageEventHandlers = ({
|
||||
e.evt.preventDefault();
|
||||
|
||||
if (e.evt.ctrlKey || e.evt.metaKey) {
|
||||
const toolState = getToolState();
|
||||
let delta = e.evt.deltaY;
|
||||
if (getShouldInvert()) {
|
||||
if (toolState.invertScroll) {
|
||||
delta = -delta;
|
||||
}
|
||||
// Holding ctrl or meta while scrolling changes the brush size
|
||||
onBrushSizeChanged(calculateNewBrushSize(getBrushSize(), delta));
|
||||
if (toolState.selected === 'brush') {
|
||||
onBrushSizeChanged(calculateNewBrushSize(toolState.brush.width, delta));
|
||||
} else if (toolState.selected === 'eraser') {
|
||||
onEraserSizeChanged(calculateNewBrushSize(toolState.eraser.width, delta));
|
||||
}
|
||||
} else {
|
||||
// We need the absolute cursor position - not the scaled position
|
||||
const cursorPos = stage.getPointerPosition();
|
||||
@ -396,7 +428,7 @@ export const setStageEventHandlers = ({
|
||||
setIsDrawing(false);
|
||||
setLastMouseDownPos(null);
|
||||
} else if (e.key === ' ') {
|
||||
setToolBuffer(getTool());
|
||||
setToolBuffer(getToolState().selected);
|
||||
setTool('view');
|
||||
}
|
||||
};
|
||||
@ -408,7 +440,7 @@ export const setStageEventHandlers = ({
|
||||
return;
|
||||
}
|
||||
if (e.key === ' ') {
|
||||
const toolBuffer = getToolBuffer();
|
||||
const toolBuffer = getToolState().selectedBuffer;
|
||||
setTool(toolBuffer ?? 'move');
|
||||
setToolBuffer(null);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters';
|
||||
import { CA_LAYER_IMAGE_NAME, CA_LAYER_NAME, getCALayerImageId } from 'features/controlLayers/konva/naming';
|
||||
import type { ControlAdapterLayer } from 'features/controlLayers/store/types';
|
||||
import type { ControlAdapterData } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
@ -12,11 +12,11 @@ import type { ImageDTO } from 'services/api/types';
|
||||
/**
|
||||
* Creates a control adapter layer.
|
||||
* @param stage The konva stage
|
||||
* @param layerState The control adapter layer state
|
||||
* @param ca The control adapter layer state
|
||||
*/
|
||||
const createCALayer = (stage: Konva.Stage, layerState: ControlAdapterLayer): Konva.Layer => {
|
||||
const createCALayer = (stage: Konva.Stage, ca: ControlAdapterData): Konva.Layer => {
|
||||
const konvaLayer = new Konva.Layer({
|
||||
id: layerState.id,
|
||||
id: ca.id,
|
||||
name: CA_LAYER_NAME,
|
||||
imageSmoothingEnabled: false,
|
||||
listening: false,
|
||||
@ -44,16 +44,16 @@ const createCALayerImage = (konvaLayer: Konva.Layer, imageEl: HTMLImageElement):
|
||||
* the konva image.
|
||||
* @param stage The konva stage
|
||||
* @param konvaLayer The konva layer
|
||||
* @param layerState The control adapter layer state
|
||||
* @param ca The control adapter layer state
|
||||
* @param getImageDTO A function to retrieve an image DTO from the server, used to update the image source
|
||||
*/
|
||||
const updateCALayerImageSource = async (
|
||||
stage: Konva.Stage,
|
||||
konvaLayer: Konva.Layer,
|
||||
layerState: ControlAdapterLayer,
|
||||
ca: ControlAdapterData,
|
||||
getImageDTO: (imageName: string) => Promise<ImageDTO | null>
|
||||
): Promise<void> => {
|
||||
const image = layerState.controlAdapter.processedImage ?? layerState.controlAdapter.image;
|
||||
const image = ca.processedImage ?? ca.image;
|
||||
if (image) {
|
||||
const imageName = image.name;
|
||||
const imageDTO = await getImageDTO(imageName);
|
||||
@ -61,7 +61,7 @@ const updateCALayerImageSource = async (
|
||||
return;
|
||||
}
|
||||
const imageEl = new Image();
|
||||
const imageId = getCALayerImageId(layerState.id, imageName);
|
||||
const imageId = getCALayerImageId(ca.id, imageName);
|
||||
imageEl.onload = () => {
|
||||
// Find the existing image or create a new one - must find using the name, bc the id may have just changed
|
||||
const konvaImage =
|
||||
@ -72,7 +72,7 @@ const updateCALayerImageSource = async (
|
||||
id: imageId,
|
||||
image: imageEl,
|
||||
});
|
||||
updateCALayerImageAttrs(stage, konvaImage, layerState);
|
||||
updateCALayerImageAttrs(stage, konvaImage, ca);
|
||||
// Must cache after this to apply the filters
|
||||
konvaImage.cache();
|
||||
imageEl.id = imageId;
|
||||
@ -87,36 +87,33 @@ const updateCALayerImageSource = async (
|
||||
* Updates the image attributes for a control adapter layer's image (width, height, visibility, opacity, filters).
|
||||
* @param stage The konva stage
|
||||
* @param konvaImage The konva image
|
||||
* @param layerState The control adapter layer state
|
||||
* @param ca The control adapter layer state
|
||||
*/
|
||||
|
||||
const updateCALayerImageAttrs = (
|
||||
stage: Konva.Stage,
|
||||
konvaImage: Konva.Image,
|
||||
layerState: ControlAdapterLayer
|
||||
): void => {
|
||||
const updateCALayerImageAttrs = (stage: Konva.Stage, konvaImage: Konva.Image, ca: ControlAdapterData): void => {
|
||||
let needsCache = false;
|
||||
// Konva erroneously reports NaN for width and height when the stage is hidden. This causes errors when caching,
|
||||
// but it doesn't seem to break anything.
|
||||
// TODO(psyche): Investigate and report upstream.
|
||||
const hasFilter = konvaImage.filters() !== null && konvaImage.filters().length > 0;
|
||||
const filter = konvaImage.filters()[0] ?? null;
|
||||
const filterNeedsUpdate = (filter === null && ca.filter !== 'none') || (filter && filter.name !== ca.filter);
|
||||
if (
|
||||
konvaImage.x() !== layerState.x ||
|
||||
konvaImage.y() !== layerState.y ||
|
||||
konvaImage.visible() !== layerState.isEnabled ||
|
||||
hasFilter !== layerState.isFilterEnabled
|
||||
konvaImage.x() !== ca.x ||
|
||||
konvaImage.y() !== ca.y ||
|
||||
konvaImage.visible() !== ca.isEnabled ||
|
||||
filterNeedsUpdate
|
||||
) {
|
||||
konvaImage.setAttrs({
|
||||
opacity: layerState.opacity,
|
||||
opacity: ca.opacity,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
visible: layerState.isEnabled,
|
||||
filters: layerState.isFilterEnabled ? [LightnessToAlphaFilter] : [],
|
||||
visible: ca.isEnabled,
|
||||
filters: ca.filter === LightnessToAlphaFilter.name ? [LightnessToAlphaFilter] : [],
|
||||
});
|
||||
needsCache = true;
|
||||
}
|
||||
if (konvaImage.opacity() !== layerState.opacity) {
|
||||
konvaImage.opacity(layerState.opacity);
|
||||
if (konvaImage.opacity() !== ca.opacity) {
|
||||
konvaImage.opacity(ca.opacity);
|
||||
}
|
||||
if (needsCache) {
|
||||
konvaImage.cache();
|
||||
@ -127,16 +124,16 @@ const updateCALayerImageAttrs = (
|
||||
* Renders a control adapter layer. If the layer doesn't already exist, it is created. Otherwise, the layer is updated
|
||||
* with the current image source and attributes.
|
||||
* @param stage The konva stage
|
||||
* @param layerState The control adapter layer state
|
||||
* @param ca The control adapter layer state
|
||||
* @param getImageDTO A function to retrieve an image DTO from the server, used to update the image source
|
||||
*/
|
||||
export const renderCALayer = (
|
||||
stage: Konva.Stage,
|
||||
layerState: ControlAdapterLayer,
|
||||
ca: ControlAdapterData,
|
||||
zIndex: number,
|
||||
getImageDTO: (imageName: string) => Promise<ImageDTO | null>
|
||||
): void => {
|
||||
const konvaLayer = stage.findOne<Konva.Layer>(`#${layerState.id}`) ?? createCALayer(stage, layerState);
|
||||
const konvaLayer = stage.findOne<Konva.Layer>(`#${ca.id}`) ?? createCALayer(stage, ca);
|
||||
|
||||
konvaLayer.zIndex(zIndex);
|
||||
|
||||
@ -146,8 +143,8 @@ export const renderCALayer = (
|
||||
let imageSourceNeedsUpdate = false;
|
||||
|
||||
if (canvasImageSource instanceof HTMLImageElement) {
|
||||
const image = layerState.controlAdapter.processedImage ?? layerState.controlAdapter.image;
|
||||
if (image && canvasImageSource.id !== getCALayerImageId(layerState.id, image.name)) {
|
||||
const image = ca.processedImage ?? ca.image;
|
||||
if (image && canvasImageSource.id !== getCALayerImageId(ca.id, image.name)) {
|
||||
imageSourceNeedsUpdate = true;
|
||||
} else if (!image) {
|
||||
imageSourceNeedsUpdate = true;
|
||||
@ -157,8 +154,8 @@ export const renderCALayer = (
|
||||
}
|
||||
|
||||
if (imageSourceNeedsUpdate) {
|
||||
updateCALayerImageSource(stage, konvaLayer, layerState, getImageDTO);
|
||||
updateCALayerImageSource(stage, konvaLayer, ca, getImageDTO);
|
||||
} else if (konvaImage) {
|
||||
updateCALayerImageAttrs(stage, konvaImage, layerState);
|
||||
updateCALayerImageAttrs(stage, konvaImage, ca);
|
||||
}
|
||||
};
|
||||
|
@ -2,19 +2,17 @@ import { DEBOUNCE_MS } from 'features/controlLayers/konva/constants';
|
||||
import { PREVIEW_LAYER_ID } from 'features/controlLayers/konva/naming';
|
||||
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 { renderBboxPreview, renderToolPreview } from 'features/controlLayers/konva/renderers/previewLayer';
|
||||
import { renderRasterLayer } from 'features/controlLayers/konva/renderers/rasterLayer';
|
||||
import { renderRGLayer } from 'features/controlLayers/konva/renderers/rgLayer';
|
||||
import { mapId, selectRenderableLayers } from 'features/controlLayers/konva/util';
|
||||
import type { LayerData, Tool } from 'features/controlLayers/store/types';
|
||||
import {
|
||||
isControlAdapterLayer,
|
||||
isInitialImageLayer,
|
||||
isInpaintMaskLayer,
|
||||
isRasterLayer,
|
||||
isRegionalGuidanceLayer,
|
||||
isRenderableLayer,
|
||||
import type {
|
||||
CanvasEntity,
|
||||
ControlAdapterData,
|
||||
LayerData,
|
||||
PosChangedArg,
|
||||
RegionalGuidanceData,
|
||||
Tool,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import type Konva from 'konva';
|
||||
import { debounce } from 'lodash-es';
|
||||
@ -27,43 +25,42 @@ import type { ImageDTO } from 'services/api/types';
|
||||
/**
|
||||
* Renders the layers on the stage.
|
||||
* @param stage The konva stage
|
||||
* @param layerStates Array of all layer states
|
||||
* @param globalMaskLayerOpacity The global mask layer opacity
|
||||
* @param layers Array of all layer states
|
||||
* @param rgGlobalOpacity The global mask layer opacity
|
||||
* @param tool The current tool
|
||||
* @param getImageDTO A function to retrieve an image DTO from the server, used to update the image source
|
||||
* @param onLayerPosChanged Callback for when the layer's position changes
|
||||
* @param onPosChanged Callback for when the layer's position changes
|
||||
*/
|
||||
const renderLayers = (
|
||||
stage: Konva.Stage,
|
||||
layerStates: LayerData[],
|
||||
globalMaskLayerOpacity: number,
|
||||
layers: LayerData[],
|
||||
controlAdapters: ControlAdapterData[],
|
||||
regions: RegionalGuidanceData[],
|
||||
rgGlobalOpacity: number,
|
||||
tool: Tool,
|
||||
selectedEntity: CanvasEntity | null,
|
||||
getImageDTO: (imageName: string) => Promise<ImageDTO | null>,
|
||||
onLayerPosChanged?: (layerId: string, x: number, y: number) => void
|
||||
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
|
||||
): void => {
|
||||
const layerIds = layerStates.filter(isRenderableLayer).map(mapId);
|
||||
const renderableIds = [...layers.map(mapId), ...controlAdapters.map(mapId), ...regions.map(mapId)];
|
||||
// Remove un-rendered layers
|
||||
for (const konvaLayer of stage.find<Konva.Layer>(selectRenderableLayers)) {
|
||||
if (!layerIds.includes(konvaLayer.id())) {
|
||||
if (!renderableIds.includes(konvaLayer.id())) {
|
||||
konvaLayer.destroy();
|
||||
}
|
||||
}
|
||||
// We'll need to ensure the tool preview layer is on top of the rest of the layers
|
||||
let zIndex = 0;
|
||||
for (const layer of layerStates) {
|
||||
if (isRegionalGuidanceLayer(layer)) {
|
||||
renderRGLayer(stage, layer, globalMaskLayerOpacity, tool, zIndex, onLayerPosChanged);
|
||||
} else if (isControlAdapterLayer(layer)) {
|
||||
renderCALayer(stage, layer, zIndex, getImageDTO);
|
||||
} else if (isInitialImageLayer(layer)) {
|
||||
renderIILayer(stage, layer, zIndex, getImageDTO);
|
||||
} else if (isRasterLayer(layer)) {
|
||||
renderRasterLayer(stage, layer, tool, zIndex, onLayerPosChanged);
|
||||
} else if (isInpaintMaskLayer(layer)) {
|
||||
//
|
||||
}
|
||||
// IP Adapter layers are not rendered
|
||||
// Increment the z-index for the tool layer
|
||||
for (const layer of layers) {
|
||||
renderRasterLayer(stage, layer, tool, zIndex, onPosChanged);
|
||||
zIndex++;
|
||||
}
|
||||
for (const ca of controlAdapters) {
|
||||
renderCALayer(stage, ca, zIndex, getImageDTO);
|
||||
zIndex++;
|
||||
}
|
||||
for (const rg of regions) {
|
||||
renderRGLayer(stage, rg, rgGlobalOpacity, tool, zIndex, selectedEntity, onPosChanged);
|
||||
zIndex++;
|
||||
}
|
||||
// Arrange the tool preview layer
|
||||
|
@ -5,7 +5,13 @@ import {
|
||||
LAYER_BBOX_NAME,
|
||||
PREVIEW_GENERATION_BBOX_DUMMY_RECT,
|
||||
} from 'features/controlLayers/konva/naming';
|
||||
import type { BrushLine, EraserLine, ImageObject, LayerData, RectShape } from 'features/controlLayers/store/types';
|
||||
import type {
|
||||
BrushLine,
|
||||
CanvasEntity,
|
||||
EraserLine,
|
||||
ImageObject,
|
||||
RectShape,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { DEFAULT_RGBA_COLOR } from 'features/controlLayers/store/types';
|
||||
import { t } from 'i18next';
|
||||
import Konva from 'konva';
|
||||
@ -174,12 +180,12 @@ export const createImageObjectGroup = async (
|
||||
|
||||
/**
|
||||
* Creates a bounding box rect for a layer.
|
||||
* @param layerState The layer state for the layer to create the bounding box for
|
||||
* @param entity 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: LayerData, konvaLayer: Konva.Layer): Konva.Rect => {
|
||||
export const createBboxRect = (entity: CanvasEntity, konvaLayer: Konva.Layer): Konva.Rect => {
|
||||
const rect = new Konva.Rect({
|
||||
id: getLayerBboxId(layerState.id),
|
||||
id: getLayerBboxId(entity.id),
|
||||
name: LAYER_BBOX_NAME,
|
||||
strokeWidth: 1,
|
||||
visible: false,
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
PREVIEW_TOOL_GROUP_ID,
|
||||
} from 'features/controlLayers/konva/naming';
|
||||
import { selectRenderableLayers } from 'features/controlLayers/konva/util';
|
||||
import type { LayerData, RgbaColor, Tool } from 'features/controlLayers/store/types';
|
||||
import type { CanvasEntity, CanvasV2State, RgbaColor, Tool } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||
import { atom } from 'nanostores';
|
||||
@ -327,8 +327,8 @@ export const getToolPreviewGroup = (stage: Konva.Stage): Konva.Group => {
|
||||
* Renders the preview layer.
|
||||
* @param stage The konva stage
|
||||
* @param tool The selected tool
|
||||
* @param color The selected layer's color
|
||||
* @param selectedLayerType The selected layer's type
|
||||
* @param currentFill The selected layer's color
|
||||
* @param selectedEntity The selected layer's type
|
||||
* @param globalMaskLayerOpacity The global mask layer opacity
|
||||
* @param cursorPos The cursor position
|
||||
* @param lastMouseDownPos The position of the last mouse down event - used for the rect tool
|
||||
@ -336,17 +336,16 @@ export const getToolPreviewGroup = (stage: Konva.Stage): Konva.Group => {
|
||||
*/
|
||||
export const renderToolPreview = (
|
||||
stage: Konva.Stage,
|
||||
tool: Tool,
|
||||
brushColor: RgbaColor,
|
||||
selectedLayerType: LayerData['type'] | null,
|
||||
globalMaskLayerOpacity: number,
|
||||
toolState: CanvasV2State['tool'],
|
||||
currentFill: RgbaColor,
|
||||
selectedEntity: CanvasEntity | null,
|
||||
cursorPos: Vector2d | null,
|
||||
lastMouseDownPos: Vector2d | null,
|
||||
brushSize: number,
|
||||
isDrawing: boolean,
|
||||
isMouseDown: boolean
|
||||
): void => {
|
||||
const layerCount = stage.find(selectRenderableLayers).length;
|
||||
const tool = toolState.selected;
|
||||
// Update the stage's pointer style
|
||||
if (tool === 'view') {
|
||||
// View gets a hand
|
||||
@ -354,7 +353,7 @@ export const renderToolPreview = (
|
||||
} else if (layerCount === 0) {
|
||||
// We have no layers, so we should not render any tool
|
||||
stage.container().style.cursor = 'default';
|
||||
} else if (selectedLayerType !== 'regional_guidance_layer' && selectedLayerType !== 'raster_layer') {
|
||||
} else if (selectedEntity?.type !== 'regional_guidance' && selectedEntity?.type !== 'layer') {
|
||||
// Non-mask-guidance layers don't have tools
|
||||
stage.container().style.cursor = 'not-allowed';
|
||||
} else if (tool === 'move') {
|
||||
@ -377,7 +376,7 @@ export const renderToolPreview = (
|
||||
if (
|
||||
!cursorPos ||
|
||||
layerCount === 0 ||
|
||||
(selectedLayerType !== 'regional_guidance_layer' && selectedLayerType !== 'raster_layer')
|
||||
(selectedEntity?.type !== 'regional_guidance' && selectedEntity?.type !== 'layer')
|
||||
) {
|
||||
// We can bail early if the mouse isn't over the stage or there are no layers
|
||||
toolPreviewGroup.visible(false);
|
||||
@ -394,24 +393,25 @@ export const renderToolPreview = (
|
||||
if (cursorPos && (tool === 'brush' || tool === 'eraser')) {
|
||||
// Update the fill circle
|
||||
const brushPreviewFill = brushPreviewGroup.findOne<Konva.Circle>(`#${PREVIEW_BRUSH_FILL_ID}`);
|
||||
const radius = (tool === 'brush' ? toolState.brush.width : toolState.eraser.width) / 2;
|
||||
brushPreviewFill?.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
radius: brushSize / 2,
|
||||
fill: isDrawing ? '' : rgbaColorToString(brushColor),
|
||||
radius,
|
||||
fill: isDrawing ? '' : rgbaColorToString(currentFill),
|
||||
globalCompositeOperation: tool === 'brush' ? 'source-over' : 'destination-out',
|
||||
});
|
||||
|
||||
// Update the inner border of the brush preview
|
||||
const brushPreviewInner = brushPreviewGroup.findOne<Konva.Circle>(`#${PREVIEW_BRUSH_BORDER_INNER_ID}`);
|
||||
brushPreviewInner?.setAttrs({ x: cursorPos.x, y: cursorPos.y, radius: brushSize / 2 });
|
||||
brushPreviewInner?.setAttrs({ x: cursorPos.x, y: cursorPos.y, radius });
|
||||
|
||||
// Update the outer border of the brush preview
|
||||
const brushPreviewOuter = brushPreviewGroup.findOne<Konva.Circle>(`#${PREVIEW_BRUSH_BORDER_OUTER_ID}`);
|
||||
brushPreviewOuter?.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
radius: brushSize / 2 + 1,
|
||||
radius: radius + 1,
|
||||
});
|
||||
|
||||
brushPreviewGroup.visible(true);
|
||||
@ -426,7 +426,7 @@ export const renderToolPreview = (
|
||||
y: Math.min(cursorPos.y, lastMouseDownPos.y),
|
||||
width: Math.abs(cursorPos.x - lastMouseDownPos.x),
|
||||
height: Math.abs(cursorPos.y - lastMouseDownPos.y),
|
||||
fill: rgbaColorToString(brushColor),
|
||||
fill: rgbaColorToString(currentFill),
|
||||
});
|
||||
rectPreview?.visible(true);
|
||||
} else {
|
||||
|
@ -1,6 +1,4 @@
|
||||
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,
|
||||
@ -9,7 +7,6 @@ import {
|
||||
RASTER_LAYER_RECT_SHAPE_NAME,
|
||||
} from 'features/controlLayers/konva/naming';
|
||||
import {
|
||||
createBboxRect,
|
||||
createBrushLine,
|
||||
createEraserLine,
|
||||
createImageObjectGroup,
|
||||
@ -17,7 +14,7 @@ import {
|
||||
createRectShape,
|
||||
} from 'features/controlLayers/konva/renderers/objects';
|
||||
import { mapId, selectRasterObjects } from 'features/controlLayers/konva/util';
|
||||
import type { RasterLayer, Tool } from 'features/controlLayers/store/types';
|
||||
import type { CanvasEntity, LayerData, PosChangedArg, Tool } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
|
||||
/**
|
||||
@ -28,12 +25,12 @@ import Konva from 'konva';
|
||||
* Creates a raster layer.
|
||||
* @param stage The konva stage
|
||||
* @param layerState The raster layer state
|
||||
* @param onLayerPosChanged Callback for when the layer's position changes
|
||||
* @param onPosChanged Callback for when the layer's position changes
|
||||
*/
|
||||
const createRasterLayer = (
|
||||
stage: Konva.Stage,
|
||||
layerState: RasterLayer,
|
||||
onLayerPosChanged?: (layerId: string, x: number, y: number) => void
|
||||
layerState: LayerData,
|
||||
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
|
||||
): Konva.Layer => {
|
||||
// This layer hasn't been added to the konva state yet
|
||||
const konvaLayer = new Konva.Layer({
|
||||
@ -45,9 +42,9 @@ const createRasterLayer = (
|
||||
|
||||
// When a drag on the layer finishes, update the layer's position in state. During the drag, konva handles changing
|
||||
// the position - we do not need to call this on the `dragmove` event.
|
||||
if (onLayerPosChanged) {
|
||||
if (onPosChanged) {
|
||||
konvaLayer.on('dragend', function (e) {
|
||||
onLayerPosChanged(layerState.id, Math.floor(e.target.x()), Math.floor(e.target.y()));
|
||||
onPosChanged({ id: layerState.id, x: Math.floor(e.target.x()), y: Math.floor(e.target.y()) }, 'layer');
|
||||
});
|
||||
}
|
||||
|
||||
@ -61,17 +58,17 @@ const createRasterLayer = (
|
||||
* @param stage The konva stage
|
||||
* @param layerState The regional guidance layer state
|
||||
* @param tool The current tool
|
||||
* @param onLayerPosChanged Callback for when the layer's position changes
|
||||
* @param onPosChanged Callback for when the layer's position changes
|
||||
*/
|
||||
export const renderRasterLayer = async (
|
||||
stage: Konva.Stage,
|
||||
layerState: RasterLayer,
|
||||
layerState: LayerData,
|
||||
tool: Tool,
|
||||
zIndex: number,
|
||||
onLayerPosChanged?: (layerId: string, x: number, y: number) => void
|
||||
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
|
||||
) => {
|
||||
const konvaLayer =
|
||||
stage.findOne<Konva.Layer>(`#${layerState.id}`) ?? createRasterLayer(stage, layerState, onLayerPosChanged);
|
||||
stage.findOne<Konva.Layer>(`#${layerState.id}`) ?? createRasterLayer(stage, layerState, onPosChanged);
|
||||
|
||||
// Update the layer's position and listening state
|
||||
konvaLayer.setAttrs({
|
||||
@ -128,23 +125,23 @@ export const renderRasterLayer = async (
|
||||
konvaLayer.visible(layerState.isEnabled);
|
||||
}
|
||||
|
||||
const bboxRect = konvaLayer.findOne<Konva.Rect>(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(layerState, konvaLayer);
|
||||
// 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 : '',
|
||||
strokeWidth: 1 / stage.scaleX(),
|
||||
});
|
||||
} else {
|
||||
bboxRect.visible(false);
|
||||
}
|
||||
// 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 : '',
|
||||
// strokeWidth: 1 / stage.scaleX(),
|
||||
// });
|
||||
// } else {
|
||||
// bboxRect.visible(false);
|
||||
// }
|
||||
|
||||
konvaObjectGroup.opacity(layerState.opacity);
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
createRectShape,
|
||||
} from 'features/controlLayers/konva/renderers/objects';
|
||||
import { mapId, selectVectorMaskObjects } from 'features/controlLayers/konva/util';
|
||||
import type { RegionalGuidanceLayer, Tool } from 'features/controlLayers/store/types';
|
||||
import type { CanvasEntity, PosChangedArg, RegionalGuidanceData, Tool } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
|
||||
/**
|
||||
@ -41,17 +41,17 @@ const createCompositingRect = (konvaLayer: Konva.Layer): Konva.Rect => {
|
||||
/**
|
||||
* Creates a regional guidance layer.
|
||||
* @param stage The konva stage
|
||||
* @param layerState The regional guidance layer state
|
||||
* @param rg The regional guidance layer state
|
||||
* @param onLayerPosChanged Callback for when the layer's position changes
|
||||
*/
|
||||
const createRGLayer = (
|
||||
stage: Konva.Stage,
|
||||
layerState: RegionalGuidanceLayer,
|
||||
onLayerPosChanged?: (layerId: string, x: number, y: number) => void
|
||||
rg: RegionalGuidanceData,
|
||||
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
|
||||
): Konva.Layer => {
|
||||
// This layer hasn't been added to the konva state yet
|
||||
const konvaLayer = new Konva.Layer({
|
||||
id: layerState.id,
|
||||
id: rg.id,
|
||||
name: RG_LAYER_NAME,
|
||||
draggable: true,
|
||||
dragDistance: 0,
|
||||
@ -59,9 +59,9 @@ const createRGLayer = (
|
||||
|
||||
// When a drag on the layer finishes, update the layer's position in state. During the drag, konva handles changing
|
||||
// the position - we do not need to call this on the `dragmove` event.
|
||||
if (onLayerPosChanged) {
|
||||
if (onPosChanged) {
|
||||
konvaLayer.on('dragend', function (e) {
|
||||
onLayerPosChanged(layerState.id, Math.floor(e.target.x()), Math.floor(e.target.y()));
|
||||
onPosChanged({ id: rg.id, x: Math.floor(e.target.x()), y: Math.floor(e.target.y()) }, 'regional_guidance');
|
||||
});
|
||||
}
|
||||
|
||||
@ -73,32 +73,32 @@ const createRGLayer = (
|
||||
/**
|
||||
* Renders a raster layer.
|
||||
* @param stage The konva stage
|
||||
* @param layerState The regional guidance layer state
|
||||
* @param rg The regional guidance layer state
|
||||
* @param globalMaskLayerOpacity The global mask layer opacity
|
||||
* @param tool The current tool
|
||||
* @param onLayerPosChanged Callback for when the layer's position changes
|
||||
* @param onPosChanged Callback for when the layer's position changes
|
||||
*/
|
||||
export const renderRGLayer = (
|
||||
stage: Konva.Stage,
|
||||
layerState: RegionalGuidanceLayer,
|
||||
rg: RegionalGuidanceData,
|
||||
globalMaskLayerOpacity: number,
|
||||
tool: Tool,
|
||||
zIndex: number,
|
||||
onLayerPosChanged?: (layerId: string, x: number, y: number) => void
|
||||
selectedEntity: CanvasEntity | null,
|
||||
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
|
||||
): void => {
|
||||
const konvaLayer =
|
||||
stage.findOne<Konva.Layer>(`#${layerState.id}`) ?? createRGLayer(stage, layerState, onLayerPosChanged);
|
||||
const konvaLayer = stage.findOne<Konva.Layer>(`#${rg.id}`) ?? createRGLayer(stage, rg, onPosChanged);
|
||||
|
||||
// Update the layer's position and listening state
|
||||
konvaLayer.setAttrs({
|
||||
listening: tool === 'move', // The layer only listens when using the move tool - otherwise the stage is handling mouse events
|
||||
x: Math.floor(layerState.x),
|
||||
y: Math.floor(layerState.y),
|
||||
x: Math.floor(rg.x),
|
||||
y: Math.floor(rg.y),
|
||||
zIndex,
|
||||
});
|
||||
|
||||
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
|
||||
const rgbColor = rgbColorToString(layerState.previewColor);
|
||||
const rgbColor = rgbColorToString(rg.fill);
|
||||
|
||||
const konvaObjectGroup =
|
||||
konvaLayer.findOne<Konva.Group>(`.${RG_LAYER_OBJECT_GROUP_NAME}`) ??
|
||||
@ -107,7 +107,7 @@ export const renderRGLayer = (
|
||||
// We use caching to handle "global" layer opacity, but caching is expensive and we should only do it when required.
|
||||
let groupNeedsCache = false;
|
||||
|
||||
const objectIds = layerState.objects.map(mapId);
|
||||
const objectIds = rg.objects.map(mapId);
|
||||
// Destroy any objects that are no longer in the redux state
|
||||
for (const objectNode of konvaObjectGroup.find(selectVectorMaskObjects)) {
|
||||
if (!objectIds.includes(objectNode.id())) {
|
||||
@ -116,7 +116,7 @@ export const renderRGLayer = (
|
||||
}
|
||||
}
|
||||
|
||||
for (const obj of layerState.objects) {
|
||||
for (const obj of rg.objects) {
|
||||
if (obj.type === 'brush_line') {
|
||||
const konvaBrushLine =
|
||||
stage.findOne<Konva.Line>(`#${obj.id}`) ?? createBrushLine(obj, konvaObjectGroup, RG_LAYER_BRUSH_LINE_NAME);
|
||||
@ -160,8 +160,8 @@ export const renderRGLayer = (
|
||||
}
|
||||
|
||||
// Only update layer visibility if it has changed.
|
||||
if (konvaLayer.visible() !== layerState.isEnabled) {
|
||||
konvaLayer.visible(layerState.isEnabled);
|
||||
if (konvaLayer.visible() !== rg.isEnabled) {
|
||||
konvaLayer.visible(rg.isEnabled);
|
||||
groupNeedsCache = true;
|
||||
}
|
||||
|
||||
@ -173,6 +173,7 @@ export const renderRGLayer = (
|
||||
|
||||
const compositingRect =
|
||||
konvaLayer.findOne<Konva.Rect>(`.${COMPOSITING_RECT_NAME}`) ?? createCompositingRect(konvaLayer);
|
||||
const isSelected = selectedEntity?.id === rg.id;
|
||||
|
||||
/**
|
||||
* When the group is selected, we use a rect of the selected preview color, composited over the shapes. This allows
|
||||
@ -185,7 +186,7 @@ export const renderRGLayer = (
|
||||
* Instead, with the special handling, the effect is as if you drew all the shapes at 100% opacity, flattened them to
|
||||
* a single raster image, and _then_ applied the 50% opacity.
|
||||
*/
|
||||
if (layerState.isSelected && tool !== 'move') {
|
||||
if (isSelected && tool !== 'move') {
|
||||
// We must clear the cache first so Konva will re-draw the group with the new compositing rect
|
||||
if (konvaObjectGroup.isCached()) {
|
||||
konvaObjectGroup.clearCache();
|
||||
@ -195,7 +196,7 @@ export const renderRGLayer = (
|
||||
|
||||
compositingRect.setAttrs({
|
||||
// The rect should be the size of the layer - use the fast method if we don't have a pixel-perfect bbox already
|
||||
...(!layerState.bboxNeedsUpdate && layerState.bbox ? layerState.bbox : getLayerBboxFast(konvaLayer)),
|
||||
...(!rg.bboxNeedsUpdate && rg.bbox ? rg.bbox : getLayerBboxFast(konvaLayer)),
|
||||
fill: rgbColor,
|
||||
opacity: globalMaskLayerOpacity,
|
||||
// Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes)
|
||||
@ -215,18 +216,18 @@ export const renderRGLayer = (
|
||||
konvaObjectGroup.opacity(globalMaskLayerOpacity);
|
||||
}
|
||||
|
||||
const bboxRect = konvaLayer.findOne<Konva.Rect>(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(layerState, konvaLayer);
|
||||
const bboxRect = konvaLayer.findOne<Konva.Rect>(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(rg, konvaLayer);
|
||||
|
||||
if (layerState.bbox) {
|
||||
const active = !layerState.bboxNeedsUpdate && layerState.isSelected && tool === 'move';
|
||||
if (rg.bbox) {
|
||||
const active = !rg.bboxNeedsUpdate && 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 : '',
|
||||
x: rg.bbox.x,
|
||||
y: rg.bbox.y,
|
||||
width: rg.bbox.width,
|
||||
height: rg.bbox.height,
|
||||
stroke: isSelected ? BBOX_SELECTED_STROKE : '',
|
||||
});
|
||||
} else {
|
||||
bboxRect.visible(false);
|
||||
|
@ -11,21 +11,12 @@ import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/
|
||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||
import { atom } from 'nanostores';
|
||||
|
||||
import type {
|
||||
CanvasV2State,
|
||||
ControlAdapterData,
|
||||
IPAdapterData,
|
||||
LayerData,
|
||||
RegionalGuidanceData,
|
||||
RgbaColor,
|
||||
StageAttrs,
|
||||
Tool,
|
||||
} from './types';
|
||||
import type { CanvasEntity, CanvasV2State, RgbaColor, StageAttrs, Tool } from './types';
|
||||
import { DEFAULT_RGBA_COLOR } from './types';
|
||||
|
||||
const initialState: CanvasV2State = {
|
||||
_version: 3,
|
||||
lastSelectedItem: null,
|
||||
selectedEntityIdentifier: null,
|
||||
prompts: {
|
||||
positivePrompt: '',
|
||||
negativePrompt: '',
|
||||
@ -113,6 +104,12 @@ export const canvasV2Slice = createSlice({
|
||||
invertScrollChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.tool.invertScroll = action.payload;
|
||||
},
|
||||
toolChanged: (state, action: PayloadAction<Tool>) => {
|
||||
state.tool.selected = action.payload;
|
||||
},
|
||||
toolBufferChanged: (state, action: PayloadAction<Tool | null>) => {
|
||||
state.tool.selectedBuffer = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(modelChanged, (state, action) => {
|
||||
@ -146,6 +143,8 @@ export const {
|
||||
eraserWidthChanged,
|
||||
fillChanged,
|
||||
invertScrollChanged,
|
||||
toolChanged,
|
||||
toolBufferChanged,
|
||||
} = canvasV2Slice.actions;
|
||||
|
||||
export const selectCanvasV2Slice = (state: RootState) => state.canvasV2;
|
||||
@ -173,18 +172,9 @@ export const $stageAttrs = atom<StageAttrs>({
|
||||
|
||||
// Some nanostores that are manually synced to redux state to provide imperative access
|
||||
// TODO(psyche):
|
||||
export const $tool = atom<Tool>('brush');
|
||||
export const $toolBuffer = atom<Tool | null>(null);
|
||||
export const $brushWidth = atom<number>(0);
|
||||
export const $brushSpacingPx = atom<number>(0);
|
||||
export const $eraserWidth = atom<number>(0);
|
||||
export const $eraserSpacingPx = atom<number>(0);
|
||||
export const $fill = atom<RgbaColor>(DEFAULT_RGBA_COLOR);
|
||||
export const $selectedLayer = atom<LayerData | null>(null);
|
||||
export const $selectedRG = atom<RegionalGuidanceData | null>(null);
|
||||
export const $selectedCA = atom<ControlAdapterData | null>(null);
|
||||
export const $selectedIPA = atom<IPAdapterData | null>(null);
|
||||
export const $invertScroll = atom(false);
|
||||
export const $toolState = atom<CanvasV2State['tool']>(deepClone(initialState.tool));
|
||||
export const $currentFill = atom<RgbaColor>(DEFAULT_RGBA_COLOR);
|
||||
export const $selectedEntity = atom<CanvasEntity | null>(null);
|
||||
export const $bbox = atom<IRect>({ x: 0, y: 0, width: 0, height: 0 });
|
||||
|
||||
export const canvasV2PersistConfig: PersistConfig<CanvasV2State> = {
|
||||
|
@ -7,12 +7,12 @@ import type { IRect } from 'konva/lib/types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import type {
|
||||
AddBrushLineArg,
|
||||
AddEraserLineArg,
|
||||
AddImageObjectArg,
|
||||
AddPointToLineArg,
|
||||
AddRectShapeArg,
|
||||
BrushLineAddedArg,
|
||||
EraserLineAddedArg,
|
||||
ImageObjectAddedArg,
|
||||
LayerData,
|
||||
PointAddedToLineArg,
|
||||
RectShapeAddedArg,
|
||||
} from './types';
|
||||
import { isLine } from './types';
|
||||
|
||||
@ -133,7 +133,7 @@ export const layersSlice = createSlice({
|
||||
moveToStart(state.layers, layer);
|
||||
},
|
||||
layerBrushLineAdded: {
|
||||
reducer: (state, action: PayloadAction<AddBrushLineArg & { lineId: string }>) => {
|
||||
reducer: (state, action: PayloadAction<BrushLineAddedArg & { lineId: string }>) => {
|
||||
const { id, points, lineId, color, width } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
@ -149,12 +149,12 @@ export const layersSlice = createSlice({
|
||||
});
|
||||
layer.bboxNeedsUpdate = true;
|
||||
},
|
||||
prepare: (payload: AddBrushLineArg) => ({
|
||||
prepare: (payload: BrushLineAddedArg) => ({
|
||||
payload: { ...payload, lineId: uuidv4() },
|
||||
}),
|
||||
},
|
||||
layerEraserLineAdded: {
|
||||
reducer: (state, action: PayloadAction<AddEraserLineArg & { lineId: string }>) => {
|
||||
reducer: (state, action: PayloadAction<EraserLineAddedArg & { lineId: string }>) => {
|
||||
const { id, points, lineId, width } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
@ -169,11 +169,11 @@ export const layersSlice = createSlice({
|
||||
});
|
||||
layer.bboxNeedsUpdate = true;
|
||||
},
|
||||
prepare: (payload: AddEraserLineArg) => ({
|
||||
prepare: (payload: EraserLineAddedArg) => ({
|
||||
payload: { ...payload, lineId: uuidv4() },
|
||||
}),
|
||||
},
|
||||
layerLinePointAdded: (state, action: PayloadAction<AddPointToLineArg>) => {
|
||||
layerLinePointAdded: (state, action: PayloadAction<PointAddedToLineArg>) => {
|
||||
const { id, point } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
@ -187,7 +187,7 @@ export const layersSlice = createSlice({
|
||||
layer.bboxNeedsUpdate = true;
|
||||
},
|
||||
layerRectAdded: {
|
||||
reducer: (state, action: PayloadAction<AddRectShapeArg & { rectId: string }>) => {
|
||||
reducer: (state, action: PayloadAction<RectShapeAddedArg & { rectId: string }>) => {
|
||||
const { id, rect, rectId, color } = action.payload;
|
||||
if (rect.height === 0 || rect.width === 0) {
|
||||
// Ignore zero-area rectangles
|
||||
@ -200,18 +200,15 @@ export const layersSlice = createSlice({
|
||||
layer.objects.push({
|
||||
type: 'rect_shape',
|
||||
id: getRectShapeId(id, rectId),
|
||||
x: rect.x - layer.x,
|
||||
y: rect.y - layer.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
...rect,
|
||||
color,
|
||||
});
|
||||
layer.bboxNeedsUpdate = true;
|
||||
},
|
||||
prepare: (payload: AddRectShapeArg) => ({ payload: { ...payload, rectId: uuidv4() } }),
|
||||
prepare: (payload: RectShapeAddedArg) => ({ payload: { ...payload, rectId: uuidv4() } }),
|
||||
},
|
||||
layerImageAdded: {
|
||||
reducer: (state, action: PayloadAction<AddImageObjectArg & { imageId: string }>) => {
|
||||
reducer: (state, action: PayloadAction<ImageObjectAddedArg & { imageId: string }>) => {
|
||||
const { id, imageId, imageDTO } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
@ -229,7 +226,7 @@ export const layersSlice = createSlice({
|
||||
});
|
||||
layer.bboxNeedsUpdate = true;
|
||||
},
|
||||
prepare: (payload: AddImageObjectArg) => ({ payload: { ...payload, imageId: uuidv4() } }),
|
||||
prepare: (payload: ImageObjectAddedArg) => ({ payload: { ...payload, imageId: uuidv4() } }),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -14,11 +14,11 @@ import { assert } from 'tsafe';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import type {
|
||||
AddBrushLineArg,
|
||||
AddEraserLineArg,
|
||||
AddPointToLineArg,
|
||||
AddRectShapeArg,
|
||||
BrushLineAddedArg,
|
||||
EraserLineAddedArg,
|
||||
IPAdapterData,
|
||||
PointAddedToLineArg,
|
||||
RectShapeAddedArg,
|
||||
RegionalGuidanceData,
|
||||
RgbColor,
|
||||
} from './types';
|
||||
@ -306,7 +306,7 @@ export const regionalGuidanceSlice = createSlice({
|
||||
ipa.clipVisionModel = clipVisionModel;
|
||||
},
|
||||
rgBrushLineAdded: {
|
||||
reducer: (state, action: PayloadAction<AddBrushLineArg & { lineId: string }>) => {
|
||||
reducer: (state, action: PayloadAction<BrushLineAddedArg & { lineId: string }>) => {
|
||||
const { id, points, lineId, color, width } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
@ -315,21 +315,19 @@ export const regionalGuidanceSlice = createSlice({
|
||||
rg.objects.push({
|
||||
id: getBrushLineId(id, lineId),
|
||||
type: 'brush_line',
|
||||
// Points must be offset by the layer's x and y coordinates
|
||||
// TODO: Handle this in the event listener?
|
||||
points: [points[0] - rg.x, points[1] - rg.y, points[2] - rg.x, points[3] - rg.y],
|
||||
points,
|
||||
strokeWidth: width,
|
||||
color,
|
||||
});
|
||||
rg.bboxNeedsUpdate = true;
|
||||
rg.imageCache = null;
|
||||
},
|
||||
prepare: (payload: AddBrushLineArg) => ({
|
||||
prepare: (payload: BrushLineAddedArg) => ({
|
||||
payload: { ...payload, lineId: uuidv4() },
|
||||
}),
|
||||
},
|
||||
rgEraserLineAdded: {
|
||||
reducer: (state, action: PayloadAction<AddEraserLineArg & { lineId: string }>) => {
|
||||
reducer: (state, action: PayloadAction<EraserLineAddedArg & { lineId: string }>) => {
|
||||
const { id, points, lineId, width } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
@ -338,19 +336,17 @@ export const regionalGuidanceSlice = createSlice({
|
||||
rg.objects.push({
|
||||
id: getEraserLineId(id, lineId),
|
||||
type: 'eraser_line',
|
||||
// Points must be offset by the layer's x and y coordinates
|
||||
// TODO: Handle this in the event listener?
|
||||
points: [points[0] - rg.x, points[1] - rg.y, points[2] - rg.x, points[3] - rg.y],
|
||||
points,
|
||||
strokeWidth: width,
|
||||
});
|
||||
rg.bboxNeedsUpdate = true;
|
||||
rg.imageCache = null;
|
||||
},
|
||||
prepare: (payload: AddEraserLineArg) => ({
|
||||
prepare: (payload: EraserLineAddedArg) => ({
|
||||
payload: { ...payload, lineId: uuidv4() },
|
||||
}),
|
||||
},
|
||||
rgLinePointAdded: (state, action: PayloadAction<AddPointToLineArg>) => {
|
||||
rgLinePointAdded: (state, action: PayloadAction<PointAddedToLineArg>) => {
|
||||
const { id, point } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
@ -360,14 +356,12 @@ export const regionalGuidanceSlice = createSlice({
|
||||
if (!lastObject || !isLine(lastObject)) {
|
||||
return;
|
||||
}
|
||||
// Points must be offset by the layer's x and y coordinates
|
||||
// TODO: Handle this in the event listener
|
||||
lastObject.points.push(point[0] - rg.x, point[1] - rg.y);
|
||||
lastObject.points.push(...point);
|
||||
rg.bboxNeedsUpdate = true;
|
||||
rg.imageCache = null;
|
||||
},
|
||||
rgRectAdded: {
|
||||
reducer: (state, action: PayloadAction<AddRectShapeArg & { rectId: string }>) => {
|
||||
reducer: (state, action: PayloadAction<RectShapeAddedArg & { rectId: string }>) => {
|
||||
const { id, rect, rectId, color } = action.payload;
|
||||
if (rect.height === 0 || rect.width === 0) {
|
||||
// Ignore zero-area rectangles
|
||||
@ -380,16 +374,13 @@ export const regionalGuidanceSlice = createSlice({
|
||||
rg.objects.push({
|
||||
type: 'rect_shape',
|
||||
id: getRectShapeId(id, rectId),
|
||||
x: rect.x - rg.x,
|
||||
y: rect.y - rg.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
...rect,
|
||||
color,
|
||||
});
|
||||
rg.bboxNeedsUpdate = true;
|
||||
rg.imageCache = null;
|
||||
},
|
||||
prepare: (payload: AddRectShapeArg) => ({ payload: { ...payload, rectId: uuidv4() } }),
|
||||
prepare: (payload: RectShapeAddedArg) => ({ payload: { ...payload, rectId: uuidv4() } }),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters';
|
||||
import {
|
||||
zBeginEndStepPct,
|
||||
zCLIPVisionModelV2,
|
||||
@ -243,7 +244,7 @@ const zInpaintMaskData = z.object({
|
||||
});
|
||||
export type InpaintMaskData = z.infer<typeof zInpaintMaskData>;
|
||||
|
||||
const zFilter = z.enum(['none', 'lightness_to_alpha']);
|
||||
const zFilter = z.enum(['none', LightnessToAlphaFilter.name]);
|
||||
export type Filter = z.infer<typeof zFilter>;
|
||||
|
||||
const zControlAdapterData = z.object({
|
||||
@ -271,21 +272,12 @@ export type ControlAdapterConfig = Pick<
|
||||
'weight' | 'image' | 'processedImage' | 'processorConfig' | 'beginEndStepPct' | 'model' | 'controlMode'
|
||||
>;
|
||||
|
||||
const zCanvasItemIdentifier = z.object({
|
||||
type: z.enum([
|
||||
zLayerData.shape.type.value,
|
||||
zIPAdapterData.shape.type.value,
|
||||
zControlAdapterData.shape.type.value,
|
||||
zRegionalGuidanceData.shape.type.value,
|
||||
zInpaintMaskData.shape.type.value,
|
||||
]),
|
||||
id: zId,
|
||||
});
|
||||
type CanvasItemIdentifier = z.infer<typeof zCanvasItemIdentifier>;
|
||||
export type CanvasEntity = LayerData | IPAdapterData | ControlAdapterData | RegionalGuidanceData | InpaintMaskData;
|
||||
export type CanvasEntityIdentifier = Pick<CanvasEntity, 'id' | 'type'>;
|
||||
|
||||
export type CanvasV2State = {
|
||||
_version: 3;
|
||||
lastSelectedItem: CanvasItemIdentifier | null;
|
||||
selectedEntityIdentifier: CanvasEntityIdentifier | null;
|
||||
prompts: {
|
||||
positivePrompt: ParameterPositivePrompt;
|
||||
negativePrompt: ParameterNegativePrompt;
|
||||
@ -314,11 +306,17 @@ export type CanvasV2State = {
|
||||
};
|
||||
|
||||
export type StageAttrs = { x: number; y: number; width: number; height: number; scale: number };
|
||||
export type AddEraserLineArg = { id: string; points: [number, number, number, number]; width: number };
|
||||
export type AddBrushLineArg = AddEraserLineArg & { color: RgbaColor };
|
||||
export type AddPointToLineArg = { id: string; point: [number, number] };
|
||||
export type AddRectShapeArg = { id: string; rect: IRect; color: RgbaColor };
|
||||
export type AddImageObjectArg = { id: string; imageDTO: ImageDTO };
|
||||
export type PosChangedArg = { id: string; x: number; y: number };
|
||||
export type BboxChangedArg = { id: string; bbox: IRect };
|
||||
export type EraserLineAddedArg = {
|
||||
id: string;
|
||||
points: [number, number, number, number];
|
||||
width: number;
|
||||
};
|
||||
export type BrushLineAddedArg = EraserLineAddedArg & { color: RgbaColor };
|
||||
export type PointAddedToLineArg = { id: string; point: [number, number] };
|
||||
export type RectShapeAddedArg = { id: string; rect: IRect; color: RgbaColor };
|
||||
export type ImageObjectAddedArg = { id: string; imageDTO: ImageDTO };
|
||||
|
||||
//#region Type guards
|
||||
export const isLine = (obj: LayerObject): obj is BrushLine | EraserLine => {
|
||||
|
@ -34,7 +34,7 @@ const selectImageUsages = createMemoizedSelector(
|
||||
const { imagesToDelete } = deleteImageModal;
|
||||
|
||||
const allImageUsage = (imagesToDelete ?? []).map(({ image_name }) =>
|
||||
getImageUsage(canvas, nodes, controlAdapters, controlLayers.present, image_name)
|
||||
getImageUsage(canvas, nodes, controlAdapters, canvasV2, image_name)
|
||||
);
|
||||
|
||||
const imageUsageSummary: ImageUsage = {
|
||||
|
@ -84,7 +84,7 @@ export const selectImageUsage = createMemoizedSelector(
|
||||
}
|
||||
|
||||
const imagesUsage = imagesToDelete.map((i) =>
|
||||
getImageUsage(canvas, nodes, controlAdapters, controlLayers.present, i.image_name)
|
||||
getImageUsage(canvas, nodes, controlAdapters, canvasV2, i.image_name)
|
||||
);
|
||||
|
||||
return imagesUsage;
|
||||
|
@ -45,7 +45,7 @@ const DeleteBoardModal = (props: Props) => {
|
||||
[selectCanvasSlice, selectNodesSlice, selectControlAdaptersSlice, selectCanvasV2Slice],
|
||||
(canvas, nodes, controlAdapters, controlLayers) => {
|
||||
const allImageUsage = (boardImageNames ?? []).map((imageName) =>
|
||||
getImageUsage(canvas, nodes, controlAdapters, controlLayers.present, imageName)
|
||||
getImageUsage(canvas, nodes, controlAdapters, canvasV2, imageName)
|
||||
);
|
||||
|
||||
const imageUsageSummary: ImageUsage = {
|
||||
|
@ -10,7 +10,7 @@ import { CANVAS_COHERENCE_NOISE, METADATA, NOISE, POSITIVE_CONDITIONING } from '
|
||||
|
||||
export const prepareLinearUIBatch = (state: RootState, graph: NonNullableGraph, prepend: boolean): BatchConfig => {
|
||||
const { iterations, model, shouldRandomizeSeed, seed } = state.generation;
|
||||
const { shouldConcatPrompts } = state.controlLayers.present;
|
||||
const { shouldConcatPrompts } = state.canvasV2;
|
||||
const { prompts, seedBehaviour } = state.dynamicPrompts;
|
||||
|
||||
const data: Batch['data'] = [];
|
||||
|
@ -73,7 +73,7 @@ export const addControlLayers = async (
|
||||
): Promise<LayerData[]> => {
|
||||
const isSDXL = base === 'sdxl';
|
||||
|
||||
const validLayers = state.controlLayers.present.layers.filter((l) => isValidLayer(l, base));
|
||||
const validLayers = state.canvasV2.layers.filter((l) => isValidLayer(l, base));
|
||||
|
||||
const validControlAdapters = validLayers.filter(isControlAdapterLayer).map((l) => l.controlAdapter);
|
||||
for (const ca of validControlAdapters) {
|
||||
@ -259,7 +259,7 @@ export const addControlLayers = async (
|
||||
}
|
||||
}
|
||||
|
||||
g.upsertMetadata({ control_layers: { layers: validLayers, version: state.controlLayers.present._version } });
|
||||
g.upsertMetadata({ control_layers: { layers: validLayers, version: state.canvasV2._version } });
|
||||
return validLayers;
|
||||
};
|
||||
//#endregion
|
||||
@ -421,7 +421,7 @@ const addInitialImageLayerToGraph = (
|
||||
) => {
|
||||
const { vaePrecision } = state.generation;
|
||||
const { refinerModel, refinerStart } = state.sdxl;
|
||||
const { width, height } = state.controlLayers.present.size;
|
||||
const { width, height } = state.canvasV2.size;
|
||||
assert(layer.isEnabled, 'Initial image layer is not enabled');
|
||||
assert(layer.image, 'Initial image layer has no image');
|
||||
|
||||
@ -567,8 +567,8 @@ const buildControlImage = (
|
||||
*/
|
||||
const getRGLayerBlobs = async (layerIds?: string[], preview: boolean = false): Promise<Record<string, Blob>> => {
|
||||
const state = getStore().getState();
|
||||
const { layers } = state.controlLayers.present;
|
||||
const { width, height } = state.controlLayers.present.size;
|
||||
const { layers } = state.canvasV2;
|
||||
const { width, height } = state.canvasV2.size;
|
||||
const reduxLayers = layers.filter(isRegionalGuidanceLayer);
|
||||
const container = document.createElement('div');
|
||||
const stage = new Konva.Stage({ container, width, height });
|
||||
|
@ -74,7 +74,7 @@ export const addHRF = (
|
||||
vaeSource: Invocation<'vae_loader'> | Invocation<'main_model_loader'> | Invocation<'seamless'>
|
||||
): Invocation<'l2i'> => {
|
||||
const { hrfStrength, hrfEnabled, hrfMethod } = state.hrf;
|
||||
const { width, height } = state.controlLayers.present.size;
|
||||
const { width, height } = state.canvasV2.size;
|
||||
const optimalDimension = selectOptimalDimension(state);
|
||||
const { newWidth: hrfWidth, newHeight: hrfHeight } = calculateHrfRes(optimalDimension, width, height);
|
||||
|
||||
|
@ -22,7 +22,7 @@ export const getPresetModifiedPrompts = (
|
||||
state: RootState
|
||||
): { positivePrompt: string; negativePrompt: string; positiveStylePrompt?: string; negativeStylePrompt?: string } => {
|
||||
const { positivePrompt, negativePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } =
|
||||
state.controlLayers.present;
|
||||
state.canvasV2.prompts;
|
||||
const { activeStylePresetId } = state.stylePreset;
|
||||
|
||||
if (activeStylePresetId) {
|
||||
|
@ -13,7 +13,7 @@ import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
|
||||
|
||||
export const ParamNegativePrompt = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const prompt = useAppSelector((s) => s.controlLayers.present.negativePrompt);
|
||||
const prompt = useAppSelector((s) => s.canvasV2.prompts.negativePrompt);
|
||||
const viewMode = useAppSelector((s) => s.stylePreset.viewMode);
|
||||
const activeStylePresetId = useAppSelector((s) => s.stylePreset.activeStylePresetId);
|
||||
|
||||
|
@ -17,7 +17,7 @@ import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
|
||||
|
||||
export const ParamPositivePrompt = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const prompt = useAppSelector((s) => s.controlLayers.present.positivePrompt);
|
||||
const prompt = useAppSelector((s) => s.canvasV2.positivePrompt);
|
||||
const baseModel = useAppSelector((s) => s.generation.model)?.base;
|
||||
const viewMode = useAppSelector((s) => s.stylePreset.viewMode);
|
||||
const activeStylePresetId = useAppSelector((s) => s.stylePreset.activeStylePresetId);
|
||||
|
@ -15,7 +15,7 @@ const selectPromptsCount = createSelector(
|
||||
selectCanvasV2Slice,
|
||||
selectDynamicPromptsSlice,
|
||||
(controlLayers, dynamicPrompts) =>
|
||||
getShouldProcessPrompt(controlLayers.present.positivePrompt) ? dynamicPrompts.prompts.length : 1
|
||||
getShouldProcessPrompt(canvasV2.positivePrompt) ? dynamicPrompts.prompts.length : 1
|
||||
);
|
||||
|
||||
type Props = {
|
||||
|
@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const ParamSDXLNegativeStylePrompt = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const prompt = useAppSelector((s) => s.controlLayers.present.negativePrompt2);
|
||||
const prompt = useAppSelector((s) => s.canvasV2.negativePrompt2);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const handleChange = useCallback(
|
||||
|
@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const ParamSDXLPositiveStylePrompt = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const prompt = useAppSelector((s) => s.controlLayers.present.positivePrompt2);
|
||||
const prompt = useAppSelector((s) => s.canvasV2.positivePrompt2);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const handleChange = useCallback(
|
||||
|
@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { PiLinkSimpleBold, PiLinkSimpleBreakBold } from 'react-icons/pi';
|
||||
|
||||
export const SDXLConcatButton = memo(() => {
|
||||
const shouldConcatPrompts = useAppSelector((s) => s.controlLayers.present.shouldConcatPrompts);
|
||||
const shouldConcatPrompts = useAppSelector((s) => s.canvasV2.shouldConcatPrompts);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
@ -42,7 +42,7 @@ const selector = createMemoizedSelector(
|
||||
badges.push('locked');
|
||||
}
|
||||
} else {
|
||||
const { aspectRatio, width, height } = controlLayers.present.size;
|
||||
const { aspectRatio, width, height } = canvasV2.size;
|
||||
badges.push(`${width}×${height}`);
|
||||
badges.push(aspectRatio.id);
|
||||
if (aspectRatio.isLocked) {
|
||||
|
@ -9,9 +9,9 @@ import { memo, useCallback } from 'react';
|
||||
|
||||
export const ImageSizeLinear = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const width = useAppSelector((s) => s.controlLayers.present.size.width);
|
||||
const height = useAppSelector((s) => s.controlLayers.present.size.height);
|
||||
const aspectRatioState = useAppSelector((s) => s.controlLayers.present.size.aspectRatio);
|
||||
const width = useAppSelector((s) => s.canvasV2.size.width);
|
||||
const height = useAppSelector((s) => s.canvasV2.size.height);
|
||||
const aspectRatioState = useAppSelector((s) => s.canvasV2.size.aspectRatio);
|
||||
|
||||
const onChangeWidth = useCallback(
|
||||
(width: number) => {
|
||||
|
@ -10,7 +10,7 @@ export const buildPresetModifiedPrompt = (presetPrompt: string, currentPrompt: s
|
||||
};
|
||||
|
||||
export const usePresetModifiedPrompts = () => {
|
||||
const { positivePrompt, negativePrompt } = useAppSelector((s) => s.controlLayers.present);
|
||||
const { positivePrompt, negativePrompt } = useAppSelector((s) => s.canvasV2.prompts);
|
||||
|
||||
const activeStylePresetId = useAppSelector((s) => s.stylePreset.activeStylePresetId);
|
||||
|
||||
|
@ -44,7 +44,7 @@ const ParametersPanelTextToImage = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const controlLayersCount = useAppSelector((s) => s.controlLayers.present.layers.length);
|
||||
const controlLayersCount = useAppSelector((s) => s.canvasV2.layers.length);
|
||||
const controlLayersTitle = useMemo(() => {
|
||||
if (controlLayersCount === 0) {
|
||||
return t('controlLayers.controlLayers');
|
||||
|
Loading…
Reference in New Issue
Block a user