mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-02 01:24:56 -05:00
feat(ui): streamlined state flow
This commit is contained in:
@@ -7,7 +7,7 @@ export const CanvasSettingsRecalculateRectsButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const canvasManager = useCanvasManager();
|
||||
const onClick = useCallback(() => {
|
||||
for (const adapter of canvasManager.adapters.getAll()) {
|
||||
for (const adapter of canvasManager.getAllAdapters()) {
|
||||
adapter.transformer.requestRectCalculation();
|
||||
}
|
||||
}, [canvasManager]);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import type { CanvasControlLayerAdapter } from 'features/controlLayers/konva/CanvasControlLayerAdapter';
|
||||
import type { CanvasInpaintMaskAdapter } from 'features/controlLayers/konva/CanvasInpaintMaskAdapter';
|
||||
import type { CanvasRasterLayerAdapter } from 'features/controlLayers/konva/CanvasRasterLayerAdapter';
|
||||
import type { CanvasRegionalGuidanceAdapter } from 'features/controlLayers/konva/CanvasRegionalGuidanceAdapter';
|
||||
import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntityAdapterControlLayer';
|
||||
import type { CanvasEntityAdapterInpaintMask } from 'features/controlLayers/konva/CanvasEntityAdapterInpaintMask';
|
||||
import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntityAdapterRasterLayer';
|
||||
import type { CanvasEntityAdapterRegionalGuidance } from 'features/controlLayers/konva/CanvasEntityAdapterRegionalGuidance';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { createContext, memo, useContext, useMemo, useSyncExternalStore } from 'react';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
const EntityAdapterContext = createContext<
|
||||
CanvasRasterLayerAdapter | CanvasControlLayerAdapter | CanvasInpaintMaskAdapter | CanvasRegionalGuidanceAdapter | null
|
||||
CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer | CanvasEntityAdapterInpaintMask | CanvasEntityAdapterRegionalGuidance | null
|
||||
>(null);
|
||||
|
||||
export const RasterLayerAdapterGate = memo(({ children }: PropsWithChildren) => {
|
||||
@@ -93,10 +93,10 @@ export const RegionalGuidanceAdapterGate = memo(({ children }: PropsWithChildren
|
||||
RegionalGuidanceAdapterGate.displayName = 'RegionalGuidanceAdapterGate';
|
||||
|
||||
export const useEntityAdapter = ():
|
||||
| CanvasRasterLayerAdapter
|
||||
| CanvasControlLayerAdapter
|
||||
| CanvasInpaintMaskAdapter
|
||||
| CanvasRegionalGuidanceAdapter => {
|
||||
| CanvasEntityAdapterRasterLayer
|
||||
| CanvasEntityAdapterControlLayer
|
||||
| CanvasEntityAdapterInpaintMask
|
||||
| CanvasEntityAdapterRegionalGuidance => {
|
||||
const adapter = useContext(EntityAdapterContext);
|
||||
assert(adapter, 'useEntityAdapter must be used within a CanvasRasterLayerAdapterGate');
|
||||
return adapter;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import type { CanvasControlLayerAdapter } from 'features/controlLayers/konva/CanvasControlLayerAdapter';
|
||||
import type { CanvasInpaintMaskAdapter } from 'features/controlLayers/konva/CanvasInpaintMaskAdapter';
|
||||
import type { CanvasRasterLayerAdapter } from 'features/controlLayers/konva/CanvasRasterLayerAdapter';
|
||||
import type { CanvasRegionalGuidanceAdapter } from 'features/controlLayers/konva/CanvasRegionalGuidanceAdapter';
|
||||
import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntityAdapterControlLayer';
|
||||
import type { CanvasEntityAdapterInpaintMask } from 'features/controlLayers/konva/CanvasEntityAdapterInpaintMask';
|
||||
import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntityAdapterRasterLayer';
|
||||
import type { CanvasEntityAdapterRegionalGuidance } from 'features/controlLayers/konva/CanvasEntityAdapterRegionalGuidance';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { useMemo } from 'react';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export const useEntityAdapter = (
|
||||
entityIdentifier: CanvasEntityIdentifier
|
||||
): CanvasRasterLayerAdapter | CanvasControlLayerAdapter | CanvasInpaintMaskAdapter | CanvasRegionalGuidanceAdapter => {
|
||||
): CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer | CanvasEntityAdapterInpaintMask | CanvasEntityAdapterRegionalGuidance => {
|
||||
const canvasManager = useCanvasManager();
|
||||
|
||||
const adapter = useMemo(() => {
|
||||
|
||||
@@ -196,6 +196,7 @@ export class CanvasBackgroundModule extends CanvasModuleBase {
|
||||
destroy = () => {
|
||||
this.log.trace('Destroying module');
|
||||
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
this.subscriptions.clear();
|
||||
this.konva.layer.destroy();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { roundToMultiple, roundToMultipleMin } from 'common/util/roundDownToMult
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import type { Coordinate, Rect } from 'features/controlLayers/store/types';
|
||||
import type { CanvasState, Coordinate, Rect } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import { atom } from 'nanostores';
|
||||
import type { Logger } from 'roarr';
|
||||
@@ -52,6 +52,8 @@ export class CanvasBboxModule extends CanvasModuleBase {
|
||||
*/
|
||||
$aspectRatioBuffer = atom(0);
|
||||
|
||||
state: CanvasState['bbox'];
|
||||
|
||||
constructor(manager: CanvasManager) {
|
||||
super();
|
||||
this.id = getPrefixedId(this.type);
|
||||
@@ -63,8 +65,8 @@ export class CanvasBboxModule extends CanvasModuleBase {
|
||||
this.log.debug('Creating bbox module');
|
||||
|
||||
// Set the initial aspect ratio buffer per app state.
|
||||
const bbox = this.manager.stateApi.getBbox();
|
||||
this.$aspectRatioBuffer.set(bbox.rect.width / bbox.rect.height);
|
||||
this.state = this.manager.stateApi.getBbox();
|
||||
this.$aspectRatioBuffer.set(this.state.rect.width / this.state.rect.height);
|
||||
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: `${this.type}:group`, listening: true }),
|
||||
@@ -75,10 +77,10 @@ export class CanvasBboxModule extends CanvasModuleBase {
|
||||
listening: false,
|
||||
strokeEnabled: false,
|
||||
draggable: true,
|
||||
x: bbox.rect.x,
|
||||
y: bbox.rect.y,
|
||||
width: bbox.rect.width,
|
||||
height: bbox.rect.height,
|
||||
x: this.state.rect.x,
|
||||
y: this.state.rect.y,
|
||||
width: this.state.rect.width,
|
||||
height: this.state.rect.height,
|
||||
}),
|
||||
transformer: new Konva.Transformer({
|
||||
name: `${this.type}:transformer`,
|
||||
@@ -112,8 +114,18 @@ export class CanvasBboxModule extends CanvasModuleBase {
|
||||
|
||||
// We will listen to the tool state to determine if the bbox should be visible or not.
|
||||
this.subscriptions.add(this.manager.tool.$tool.listen(this.render));
|
||||
this.subscriptions.add(this.manager.stateApi.store.subscribe(this.sync));
|
||||
}
|
||||
|
||||
sync = () => {
|
||||
const prevState = this.state;
|
||||
this.state = this.manager.stateApi.getBbox();
|
||||
|
||||
if (this.state !== prevState) {
|
||||
this.render();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the bbox. The bbox is only visible when the tool is set to 'bbox'.
|
||||
*/
|
||||
@@ -147,6 +159,7 @@ export class CanvasBboxModule extends CanvasModuleBase {
|
||||
destroy = () => {
|
||||
this.log.trace('Destroying module');
|
||||
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
this.subscriptions.clear();
|
||||
this.konva.group.destroy();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasControlLayerState, CanvasEntityIdentifier, Rect } from 'features/controlLayers/store/types';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { omit } from 'lodash-es';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export class CanvasControlLayerAdapter extends CanvasEntityAdapterBase<CanvasControlLayerState> {
|
||||
static TYPE = 'control_layer_adapter';
|
||||
private _state: CanvasControlLayerState | null = null;
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<'control_layer'>, manager: CanvasManager) {
|
||||
super(entityIdentifier, manager, CanvasControlLayerAdapter.TYPE);
|
||||
}
|
||||
|
||||
get state(): CanvasControlLayerState {
|
||||
if (this._state) {
|
||||
return this._state;
|
||||
}
|
||||
const state = this.manager.stateApi.getControlLayersState().entities.find((layer) => layer.id === this.id);
|
||||
assert(state, `State not found for ${this.id}`);
|
||||
return state;
|
||||
}
|
||||
|
||||
set state(state: CanvasControlLayerState) {
|
||||
const prevState = this._state;
|
||||
this._state = state;
|
||||
this.render(state, prevState);
|
||||
}
|
||||
|
||||
private render = async (state: CanvasControlLayerState, prevState: CanvasControlLayerState | null): Promise<void> => {
|
||||
if (prevState && prevState === state) {
|
||||
this.log.trace('State unchanged, skipping update');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prevState || state.isEnabled !== prevState.isEnabled) {
|
||||
this.log.trace('Updating visibility');
|
||||
this.konva.layer.visible(state.isEnabled);
|
||||
this.renderer.syncCache(state.isEnabled);
|
||||
}
|
||||
if (!prevState || state.isLocked !== prevState.isLocked) {
|
||||
this.transformer.syncInteractionState();
|
||||
}
|
||||
if (!prevState || state.objects !== prevState.objects) {
|
||||
const didRender = await this.renderer.render();
|
||||
if (didRender) {
|
||||
this.transformer.requestRectCalculation();
|
||||
}
|
||||
}
|
||||
if (!prevState || state.position !== prevState.position) {
|
||||
this.transformer.updatePosition();
|
||||
}
|
||||
if (!prevState || state.opacity !== prevState.opacity) {
|
||||
this.renderer.updateOpacity();
|
||||
}
|
||||
if (!prevState || state.withTransparencyEffect !== prevState.withTransparencyEffect) {
|
||||
this.renderer.updateTransparencyEffect();
|
||||
}
|
||||
|
||||
if (!prevState) {
|
||||
// First render
|
||||
this.transformer.updateBbox();
|
||||
}
|
||||
};
|
||||
|
||||
getCanvas = (rect?: Rect): HTMLCanvasElement => {
|
||||
this.log.trace({ rect }, 'Getting canvas');
|
||||
// The opacity may have been changed in response to user selecting a different entity category, so we must restore
|
||||
// the original opacity before rendering the canvas
|
||||
const attrs: GroupConfig = { opacity: this.state.opacity };
|
||||
const canvas = this.renderer.getCanvas(rect, attrs);
|
||||
return canvas;
|
||||
};
|
||||
|
||||
getHashableState = (): SerializableObject => {
|
||||
const keysToOmit: (keyof CanvasControlLayerState)[] = ['name', 'controlAdapter', 'withTransparencyEffect'];
|
||||
return omit(this.state, keysToOmit);
|
||||
};
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import { CanvasEntityObjectRenderer } from 'features/controlLayers/konva/CanvasE
|
||||
import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntityTransformer';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import { selectEntity } from 'features/controlLayers/store/selectors';
|
||||
import type { CanvasEntityIdentifier, CanvasRenderableEntityState, Rect } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { Logger } from 'roarr';
|
||||
import stableHash from 'stable-hash';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export abstract class CanvasEntityAdapterBase<
|
||||
T extends CanvasRenderableEntityState = CanvasRenderableEntityState,
|
||||
@@ -41,6 +43,16 @@ export abstract class CanvasEntityAdapterBase<
|
||||
*/
|
||||
renderer: CanvasEntityObjectRenderer;
|
||||
|
||||
/**
|
||||
* The entity's state.
|
||||
*/
|
||||
state: T;
|
||||
|
||||
/**
|
||||
* A set of subscriptions to stores.
|
||||
*/
|
||||
subscriptions = new Set<() => void>();
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<T['type']>, manager: CanvasManager, adapterType: string) {
|
||||
super();
|
||||
this.type = adapterType;
|
||||
@@ -61,22 +73,101 @@ export abstract class CanvasEntityAdapterBase<
|
||||
}),
|
||||
};
|
||||
|
||||
this.manager.stage.addLayer(this.konva.layer);
|
||||
|
||||
// On creation, we need to get the latest snapshot of the entity's state from the store.
|
||||
const initialState = this.getSnapshot();
|
||||
assert(initialState !== undefined, 'Missing entity state on creation');
|
||||
this.state = initialState;
|
||||
|
||||
this.renderer = new CanvasEntityObjectRenderer(this);
|
||||
this.transformer = new CanvasEntityTransformer(this);
|
||||
}
|
||||
|
||||
abstract get state(): T;
|
||||
|
||||
abstract set state(state: T);
|
||||
|
||||
abstract getCanvas: (rect?: Rect) => HTMLCanvasElement;
|
||||
|
||||
abstract getHashableState: () => SerializableObject;
|
||||
|
||||
isInteractable = (): boolean => {
|
||||
return this.state.isEnabled && !this.state.isLocked;
|
||||
/**
|
||||
* Gets the latest snapshot of the entity's state from the store. If the entity does not exist, returns undefined.
|
||||
*/
|
||||
getSnapshot = (): T | undefined => {
|
||||
return selectEntity(this.manager.stateApi.getCanvasState(), this.entityIdentifier) as T | undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Syncs the entity state with the canvas. This includes rendering the entity's objects, handling visibility,
|
||||
* positioning, opacity, locked state, and any other properties.
|
||||
*
|
||||
* Implementations should be minimal and should only update the canvas if the state has changed. However, if `force`
|
||||
* is true, the entity should be updated regardless of whether the state has changed.
|
||||
*
|
||||
* If the entity cannot be rendered, it should be destroyed.
|
||||
*/
|
||||
abstract sync: (force?: boolean) => void;
|
||||
|
||||
/**
|
||||
* Gets the canvas element for the entity. If `rect` is provided, the canvas will be clipped to that rectangle.
|
||||
*/
|
||||
abstract getCanvas: (rect?: Rect) => HTMLCanvasElement;
|
||||
|
||||
/**
|
||||
* Gets a hashable representation of the entity's state.
|
||||
*/
|
||||
abstract getHashableState: () => SerializableObject;
|
||||
|
||||
/**
|
||||
* Synchronizes the enabled state of the entity with the canvas.
|
||||
*/
|
||||
syncIsEnabled = () => {
|
||||
this.log.trace('Updating visibility');
|
||||
this.konva.layer.visible(this.state.isEnabled);
|
||||
this.renderer.syncCache(this.state.isEnabled);
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronizes the entity's objects with the canvas.
|
||||
*/
|
||||
syncObjects = () => {
|
||||
this.renderer.render().then((didRender) => {
|
||||
if (didRender) {
|
||||
// If the objects have changed, we need to recalculate the transformer's bounding box.
|
||||
this.transformer.requestRectCalculation();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronizes the entity's position with the canvas.
|
||||
*/
|
||||
syncPosition = () => {
|
||||
this.transformer.updatePosition();
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronizes the entity's opacity with the canvas.
|
||||
*/
|
||||
syncOpacity = () => {
|
||||
this.renderer.updateOpacity();
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronizes the entity's locked state with the canvas.
|
||||
*/
|
||||
syncIsLocked = () => {
|
||||
// The only thing we need to do is update the transformer's interaction state. For tool interactions, like drawing
|
||||
// shapes, we defer to the CanvasToolModule to handle the locked state.
|
||||
this.transformer.syncInteractionState();
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the entity is interactable. An entity is interactable if it is enabled, not locked, and its type is not
|
||||
* hidden.
|
||||
*/
|
||||
getIsInteractable = (): boolean => {
|
||||
return this.state.isEnabled && !this.state.isLocked && !this.manager.stateApi.getIsTypeHidden(this.state.type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a hash of the entity's state, as provided by `getHashableState`. If `extra` is provided, it will be included in
|
||||
* the hash.
|
||||
*/
|
||||
hash = (extra?: SerializableObject): string => {
|
||||
const arg = {
|
||||
state: this.getHashableState(),
|
||||
@@ -87,9 +178,12 @@ export abstract class CanvasEntityAdapterBase<
|
||||
|
||||
destroy = (): void => {
|
||||
this.log.debug('Destroying module');
|
||||
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
this.subscriptions.clear();
|
||||
this.renderer.destroy();
|
||||
this.transformer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
this.manager.deleteAdapter(this.entityIdentifier);
|
||||
};
|
||||
|
||||
repr = () => {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasControlLayerState, CanvasEntityIdentifier, Rect } from 'features/controlLayers/store/types';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
export class CanvasEntityAdapterControlLayer extends CanvasEntityAdapterBase<CanvasControlLayerState> {
|
||||
static TYPE = 'control_layer_adapter';
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<'control_layer'>, manager: CanvasManager) {
|
||||
super(entityIdentifier, manager, CanvasEntityAdapterControlLayer.TYPE);
|
||||
this.subscriptions.add(this.manager.stateApi.store.subscribe(this.sync));
|
||||
this.sync(true);
|
||||
}
|
||||
|
||||
sync = (force?: boolean) => {
|
||||
const prevState = this.state;
|
||||
const state = this.getSnapshot();
|
||||
|
||||
if (!state) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
|
||||
if (!force && prevState === this.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (force || this.state.isEnabled !== prevState.isEnabled) {
|
||||
this.syncIsEnabled();
|
||||
}
|
||||
if (force || this.state.isLocked !== prevState.isLocked) {
|
||||
this.syncIsLocked();
|
||||
}
|
||||
if (force || this.state.objects !== prevState.objects) {
|
||||
this.syncObjects();
|
||||
}
|
||||
if (force || this.state.position !== prevState.position) {
|
||||
this.syncPosition();
|
||||
}
|
||||
if (force || this.state.opacity !== prevState.opacity) {
|
||||
this.syncOpacity();
|
||||
}
|
||||
if (force || this.state.withTransparencyEffect !== prevState.withTransparencyEffect) {
|
||||
this.renderer.updateTransparencyEffect();
|
||||
}
|
||||
};
|
||||
|
||||
syncTransparencyEffect = () => {
|
||||
this.renderer.updateTransparencyEffect();
|
||||
};
|
||||
|
||||
getCanvas = (rect?: Rect): HTMLCanvasElement => {
|
||||
this.log.trace({ rect }, 'Getting canvas');
|
||||
// The opacity may have been changed in response to user selecting a different entity category, so we must restore
|
||||
// the original opacity before rendering the canvas
|
||||
const attrs: GroupConfig = { opacity: this.state.opacity };
|
||||
const canvas = this.renderer.getCanvas(rect, attrs);
|
||||
return canvas;
|
||||
};
|
||||
|
||||
getHashableState = (): SerializableObject => {
|
||||
const keysToOmit: (keyof CanvasControlLayerState)[] = ['name', 'controlAdapter', 'withTransparencyEffect'];
|
||||
return omit(this.state, keysToOmit);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasEntityIdentifier, CanvasInpaintMaskState, Rect } from 'features/controlLayers/store/types';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
export class CanvasEntityAdapterInpaintMask extends CanvasEntityAdapterBase<CanvasInpaintMaskState> {
|
||||
static TYPE = 'inpaint_mask_adapter';
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<'inpaint_mask'>, manager: CanvasManager) {
|
||||
super(entityIdentifier, manager, CanvasEntityAdapterInpaintMask.TYPE);
|
||||
this.subscriptions.add(this.manager.stateApi.store.subscribe(this.sync));
|
||||
this.sync(true);
|
||||
}
|
||||
|
||||
sync = (force?: boolean) => {
|
||||
const prevState = this.state;
|
||||
const state = this.getSnapshot();
|
||||
|
||||
if (!state) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
|
||||
if (!force && prevState === this.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (force || this.state.isEnabled !== prevState.isEnabled) {
|
||||
this.syncIsEnabled();
|
||||
}
|
||||
if (force || this.state.isLocked !== prevState.isLocked) {
|
||||
this.syncIsLocked();
|
||||
}
|
||||
if (force || this.state.objects !== prevState.objects) {
|
||||
this.syncObjects();
|
||||
}
|
||||
if (force || this.state.position !== prevState.position) {
|
||||
this.syncPosition();
|
||||
}
|
||||
if (force || this.state.opacity !== prevState.opacity) {
|
||||
this.syncOpacity();
|
||||
}
|
||||
if (force || this.state.fill !== prevState.fill) {
|
||||
this.syncCompositingRectFill();
|
||||
}
|
||||
if (force) {
|
||||
this.syncCompositingRectSize();
|
||||
}
|
||||
};
|
||||
|
||||
syncCompositingRectSize = () => {
|
||||
this.renderer.updateCompositingRectSize();
|
||||
};
|
||||
|
||||
syncCompositingRectFill = () => {
|
||||
this.renderer.updateCompositingRectFill();
|
||||
};
|
||||
|
||||
getHashableState = (): SerializableObject => {
|
||||
const keysToOmit: (keyof CanvasInpaintMaskState)[] = ['fill', 'name', 'opacity'];
|
||||
return omit(this.state, keysToOmit);
|
||||
};
|
||||
|
||||
getCanvas = (rect?: Rect): HTMLCanvasElement => {
|
||||
// The opacity may have been changed in response to user selecting a different entity category, and the mask regions
|
||||
// should be fully opaque - set opacity to 1 before rendering the canvas
|
||||
const attrs: GroupConfig = { opacity: 1 };
|
||||
const canvas = this.renderer.getCanvas(rect, attrs);
|
||||
return canvas;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasEntityIdentifier, CanvasRasterLayerState, Rect } from 'features/controlLayers/store/types';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
export class CanvasEntityAdapterRasterLayer extends CanvasEntityAdapterBase<CanvasRasterLayerState> {
|
||||
static TYPE = 'raster_layer_adapter';
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<'raster_layer'>, manager: CanvasManager) {
|
||||
super(entityIdentifier, manager, CanvasEntityAdapterRasterLayer.TYPE);
|
||||
this.subscriptions.add(this.manager.stateApi.store.subscribe(this.sync));
|
||||
this.sync(true);
|
||||
}
|
||||
|
||||
sync = (force?: boolean) => {
|
||||
const prevState = this.state;
|
||||
const state = this.getSnapshot();
|
||||
|
||||
if (!state) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
|
||||
if (!force && prevState === this.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (force || this.state.isEnabled !== prevState.isEnabled) {
|
||||
this.syncIsEnabled();
|
||||
}
|
||||
if (force || this.state.isLocked !== prevState.isLocked) {
|
||||
this.syncIsLocked();
|
||||
}
|
||||
if (force || this.state.objects !== prevState.objects) {
|
||||
this.syncObjects();
|
||||
}
|
||||
if (force || this.state.position !== prevState.position) {
|
||||
this.syncPosition();
|
||||
}
|
||||
if (force || this.state.opacity !== prevState.opacity) {
|
||||
this.syncOpacity();
|
||||
}
|
||||
};
|
||||
|
||||
getCanvas = (rect?: Rect): HTMLCanvasElement => {
|
||||
this.log.trace({ rect }, 'Getting canvas');
|
||||
// The opacity may have been changed in response to user selecting a different entity category, so we must restore
|
||||
// the original opacity before rendering the canvas
|
||||
const attrs: GroupConfig = { opacity: this.state.opacity };
|
||||
const canvas = this.renderer.getCanvas(rect, attrs);
|
||||
return canvas;
|
||||
};
|
||||
|
||||
getHashableState = (): SerializableObject => {
|
||||
const keysToOmit: (keyof CanvasRasterLayerState)[] = ['name'];
|
||||
return omit(this.state, keysToOmit);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasEntityIdentifier, CanvasRegionalGuidanceState, Rect } from 'features/controlLayers/store/types';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
export class CanvasEntityAdapterRegionalGuidance extends CanvasEntityAdapterBase<CanvasRegionalGuidanceState> {
|
||||
static TYPE = 'regional_guidance_adapter';
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>, manager: CanvasManager) {
|
||||
super(entityIdentifier, manager, CanvasEntityAdapterRegionalGuidance.TYPE);
|
||||
this.subscriptions.add(this.manager.stateApi.store.subscribe(this.sync));
|
||||
this.sync(true);
|
||||
}
|
||||
|
||||
sync = (force?: boolean) => {
|
||||
const prevState = this.state;
|
||||
const state = this.getSnapshot();
|
||||
|
||||
if (!state) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
|
||||
if (!force && prevState === this.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (force || this.state.isEnabled !== prevState.isEnabled) {
|
||||
this.syncIsEnabled();
|
||||
}
|
||||
if (force || this.state.isLocked !== prevState.isLocked) {
|
||||
this.syncIsLocked();
|
||||
}
|
||||
if (force || this.state.objects !== prevState.objects) {
|
||||
this.syncObjects();
|
||||
}
|
||||
if (force || this.state.position !== prevState.position) {
|
||||
this.syncPosition();
|
||||
}
|
||||
if (force || this.state.opacity !== prevState.opacity) {
|
||||
this.syncOpacity();
|
||||
}
|
||||
if (force || this.state.fill !== prevState.fill) {
|
||||
this.syncCompositingRectFill();
|
||||
}
|
||||
if (force) {
|
||||
this.syncCompositingRectSize();
|
||||
}
|
||||
};
|
||||
|
||||
syncCompositingRectSize = () => {
|
||||
this.renderer.updateCompositingRectSize();
|
||||
};
|
||||
|
||||
syncCompositingRectFill = () => {
|
||||
this.renderer.updateCompositingRectFill();
|
||||
};
|
||||
|
||||
getHashableState = (): SerializableObject => {
|
||||
const keysToOmit: (keyof CanvasRegionalGuidanceState)[] = ['fill', 'name', 'opacity'];
|
||||
return omit(this.state, keysToOmit);
|
||||
};
|
||||
|
||||
getCanvas = (rect?: Rect): HTMLCanvasElement => {
|
||||
// The opacity may have been changed in response to user selecting a different entity category, and the mask regions
|
||||
// should be fully opaque - set opacity to 1 before rendering the canvas
|
||||
const attrs: GroupConfig = { opacity: 1 };
|
||||
const canvas = this.renderer.getCanvas(rect, attrs);
|
||||
return canvas;
|
||||
};
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import type {
|
||||
CanvasEraserLineState,
|
||||
CanvasImageState,
|
||||
CanvasRectState,
|
||||
Fill,
|
||||
Rect,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
||||
@@ -234,9 +233,13 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase {
|
||||
}
|
||||
};
|
||||
|
||||
updateCompositingRectFill = (fill: Fill) => {
|
||||
updateCompositingRectFill = () => {
|
||||
this.log.trace('Updating compositing rect fill');
|
||||
|
||||
assert(this.konva.compositing, 'Missing compositing rect');
|
||||
assert(this.parent.state.type === 'inpaint_mask' || this.parent.state.type === 'regional_guidance');
|
||||
|
||||
const fill = this.parent.state.fill;
|
||||
|
||||
if (fill.style === 'solid') {
|
||||
this.konva.compositing.rect.setAttrs({
|
||||
@@ -621,6 +624,7 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase {
|
||||
destroy = () => {
|
||||
this.log.debug('Destroying module');
|
||||
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
this.subscriptions.clear();
|
||||
for (const renderer of this.renderers.values()) {
|
||||
renderer.destroy();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { CanvasControlLayerAdapter } from 'features/controlLayers/konva/CanvasControlLayerAdapter';
|
||||
import { CanvasInpaintMaskAdapter } from 'features/controlLayers/konva/CanvasInpaintMaskAdapter';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import { CanvasRasterLayerAdapter } from 'features/controlLayers/konva/CanvasRasterLayerAdapter';
|
||||
import { CanvasRegionalGuidanceAdapter } from 'features/controlLayers/konva/CanvasRegionalGuidanceAdapter';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import type { CanvasSessionState } from 'features/controlLayers/store/canvasSessionSlice';
|
||||
import { type CanvasState, getEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import type { Logger } from 'roarr';
|
||||
|
||||
@@ -54,65 +49,42 @@ export class CanvasEntityRendererModule extends CanvasModuleBase {
|
||||
this.renderControlLayers(prevState, state);
|
||||
this.renderRegionalGuidance(prevState, state);
|
||||
this.renderInpaintMasks(state, prevState);
|
||||
this.renderBbox(state, prevState);
|
||||
this.arrangeEntities(state, prevState);
|
||||
this.manager.tool.syncCursorStyle();
|
||||
};
|
||||
|
||||
renderRasterLayers = async (state: CanvasState, prevState: CanvasState | null) => {
|
||||
renderRasterLayers = (state: CanvasState, prevState: CanvasState | null) => {
|
||||
const adapterMap = this.manager.adapters.rasterLayers;
|
||||
|
||||
if (!prevState || state.rasterLayers.isHidden !== prevState.rasterLayers.isHidden) {
|
||||
for (const adapter of adapterMap.values()) {
|
||||
adapter.renderer.updateOpacity();
|
||||
adapter.syncOpacity();
|
||||
}
|
||||
}
|
||||
|
||||
if (!prevState || state.rasterLayers.entities !== prevState.rasterLayers.entities) {
|
||||
for (const entityAdapter of adapterMap.values()) {
|
||||
if (!state.rasterLayers.entities.find((l) => l.id === entityAdapter.id)) {
|
||||
await entityAdapter.destroy();
|
||||
adapterMap.delete(entityAdapter.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const entityState of state.rasterLayers.entities) {
|
||||
let adapter = adapterMap.get(entityState.id);
|
||||
if (!adapter) {
|
||||
adapter = new CanvasRasterLayerAdapter(getEntityIdentifier(entityState), this.manager);
|
||||
adapterMap.set(adapter.id, adapter);
|
||||
this.manager.stage.addLayer(adapter.konva.layer);
|
||||
if (!adapterMap.has(entityState.id)) {
|
||||
this.manager.createAdapter(getEntityIdentifier(entityState));
|
||||
}
|
||||
adapter.state = entityState;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderControlLayers = async (prevState: CanvasState | null, state: CanvasState) => {
|
||||
renderControlLayers = (prevState: CanvasState | null, state: CanvasState) => {
|
||||
const adapterMap = this.manager.adapters.controlLayers;
|
||||
|
||||
if (!prevState || state.controlLayers.isHidden !== prevState.controlLayers.isHidden) {
|
||||
for (const adapter of adapterMap.values()) {
|
||||
adapter.renderer.updateOpacity();
|
||||
adapter.syncOpacity();
|
||||
}
|
||||
}
|
||||
|
||||
if (!prevState || state.controlLayers.entities !== prevState.controlLayers.entities) {
|
||||
for (const entityAdapter of adapterMap.values()) {
|
||||
if (!state.controlLayers.entities.find((l) => l.id === entityAdapter.id)) {
|
||||
await entityAdapter.destroy();
|
||||
adapterMap.delete(entityAdapter.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const entityState of state.controlLayers.entities) {
|
||||
let adapter = adapterMap.get(entityState.id);
|
||||
if (!adapter) {
|
||||
adapter = new CanvasControlLayerAdapter(getEntityIdentifier(entityState), this.manager);
|
||||
adapterMap.set(adapter.id, adapter);
|
||||
this.manager.stage.addLayer(adapter.konva.layer);
|
||||
if (!adapterMap.has(entityState.id)) {
|
||||
this.manager.createAdapter(getEntityIdentifier(entityState));
|
||||
}
|
||||
adapter.state = entityState;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -122,31 +94,15 @@ export class CanvasEntityRendererModule extends CanvasModuleBase {
|
||||
|
||||
if (!prevState || state.regions.isHidden !== prevState.regions.isHidden) {
|
||||
for (const adapter of adapterMap.values()) {
|
||||
adapter.renderer.updateOpacity();
|
||||
adapter.syncOpacity();
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!prevState ||
|
||||
state.regions.entities !== prevState.regions.entities ||
|
||||
state.selectedEntityIdentifier?.id !== prevState.selectedEntityIdentifier?.id
|
||||
) {
|
||||
// Destroy the konva nodes for nonexistent entities
|
||||
for (const canvasRegion of adapterMap.values()) {
|
||||
if (!state.regions.entities.find((rg) => rg.id === canvasRegion.id)) {
|
||||
canvasRegion.destroy();
|
||||
adapterMap.delete(canvasRegion.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!prevState || state.regions.entities !== prevState.regions.entities) {
|
||||
for (const entityState of state.regions.entities) {
|
||||
let adapter = adapterMap.get(entityState.id);
|
||||
if (!adapter) {
|
||||
adapter = new CanvasRegionalGuidanceAdapter(getEntityIdentifier(entityState), this.manager);
|
||||
adapterMap.set(adapter.id, adapter);
|
||||
this.manager.stage.addLayer(adapter.konva.layer);
|
||||
if (!adapterMap.has(entityState.id)) {
|
||||
this.manager.createAdapter(getEntityIdentifier(entityState));
|
||||
}
|
||||
adapter.state = entityState;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -156,47 +112,19 @@ export class CanvasEntityRendererModule extends CanvasModuleBase {
|
||||
|
||||
if (!prevState || state.inpaintMasks.isHidden !== prevState.inpaintMasks.isHidden) {
|
||||
for (const adapter of adapterMap.values()) {
|
||||
adapter.renderer.updateOpacity();
|
||||
adapter.syncOpacity();
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!prevState ||
|
||||
state.inpaintMasks.entities !== prevState.inpaintMasks.entities ||
|
||||
state.selectedEntityIdentifier?.id !== prevState.selectedEntityIdentifier?.id
|
||||
) {
|
||||
// Destroy the konva nodes for nonexistent entities
|
||||
for (const adapter of adapterMap.values()) {
|
||||
if (!state.inpaintMasks.entities.find((rg) => rg.id === adapter.id)) {
|
||||
adapter.destroy();
|
||||
adapterMap.delete(adapter.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!prevState || state.inpaintMasks.entities !== prevState.inpaintMasks.entities) {
|
||||
for (const entityState of state.inpaintMasks.entities) {
|
||||
let adapter = adapterMap.get(entityState.id);
|
||||
if (!adapter) {
|
||||
adapter = new CanvasInpaintMaskAdapter(getEntityIdentifier(entityState), this.manager);
|
||||
adapterMap.set(adapter.id, adapter);
|
||||
this.manager.stage.addLayer(adapter.konva.layer);
|
||||
if (!adapterMap.has(entityState.id)) {
|
||||
this.manager.createAdapter(getEntityIdentifier(entityState));
|
||||
}
|
||||
adapter.state = entityState;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderBbox = (state: CanvasState, prevState: CanvasState | null) => {
|
||||
if (!prevState || state.bbox !== prevState.bbox) {
|
||||
this.manager.bbox.render();
|
||||
}
|
||||
};
|
||||
|
||||
renderStagingArea = async (session: CanvasSessionState, prevSession: CanvasSessionState | null) => {
|
||||
if (!prevSession || session !== prevSession) {
|
||||
await this.manager.stagingArea.render();
|
||||
}
|
||||
};
|
||||
|
||||
arrangeEntities = (state: CanvasState, prevState: CanvasState | null) => {
|
||||
if (
|
||||
!prevState ||
|
||||
|
||||
@@ -472,7 +472,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase {
|
||||
const tool = this.manager.tool.$tool.get();
|
||||
const isSelected = this.manager.stateApi.getIsSelected(this.parent.id);
|
||||
|
||||
if (!this.parent.renderer.hasObjects() || !this.parent.isInteractable()) {
|
||||
if (!this.parent.renderer.hasObjects() || !this.parent.getIsInteractable()) {
|
||||
// The layer is totally empty, we can just disable the layer
|
||||
this.parent.konva.layer.listening(false);
|
||||
this.setInteractionMode('off');
|
||||
@@ -788,6 +788,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase {
|
||||
destroy = () => {
|
||||
this.log.debug('Destroying module');
|
||||
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
this.subscriptions.clear();
|
||||
this.konva.outlineRect.destroy();
|
||||
this.konva.transformer.destroy();
|
||||
this.konva.proxyRect.destroy();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import type { CanvasControlLayerAdapter } from 'features/controlLayers/konva/CanvasControlLayerAdapter';
|
||||
import type { CanvasInpaintMaskAdapter } from 'features/controlLayers/konva/CanvasInpaintMaskAdapter';
|
||||
import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntityAdapterControlLayer';
|
||||
import type { CanvasEntityAdapterInpaintMask } from 'features/controlLayers/konva/CanvasEntityAdapterInpaintMask';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import type { CanvasRasterLayerAdapter } from 'features/controlLayers/konva/CanvasRasterLayerAdapter';
|
||||
import type { CanvasRegionalGuidanceAdapter } from 'features/controlLayers/konva/CanvasRegionalGuidanceAdapter';
|
||||
import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntityAdapterRasterLayer';
|
||||
import type { CanvasEntityAdapterRegionalGuidance } from 'features/controlLayers/konva/CanvasEntityAdapterRegionalGuidance';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import type { CanvasEntityIdentifier, CanvasImageState, FilterConfig } from 'features/controlLayers/store/types';
|
||||
import { IMAGE_FILTERS, imageDTOToImageObject } from 'features/controlLayers/store/types';
|
||||
@@ -25,10 +25,10 @@ export class CanvasFilterModule extends CanvasModuleBase {
|
||||
imageState: CanvasImageState | null = null;
|
||||
|
||||
$adapter = atom<
|
||||
| CanvasRasterLayerAdapter
|
||||
| CanvasControlLayerAdapter
|
||||
| CanvasInpaintMaskAdapter
|
||||
| CanvasRegionalGuidanceAdapter
|
||||
| CanvasEntityAdapterRasterLayer
|
||||
| CanvasEntityAdapterControlLayer
|
||||
| CanvasEntityAdapterInpaintMask
|
||||
| CanvasEntityAdapterRegionalGuidance
|
||||
| null
|
||||
>(null);
|
||||
$isFiltering = computed(this.$adapter, (adapter) => Boolean(adapter));
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasEntityIdentifier, CanvasInpaintMaskState, Rect } from 'features/controlLayers/store/types';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { omit } from 'lodash-es';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export class CanvasInpaintMaskAdapter extends CanvasEntityAdapterBase<CanvasInpaintMaskState> {
|
||||
static TYPE = 'inpaint_mask_adapter';
|
||||
|
||||
/**
|
||||
* The last known state of the entity.
|
||||
*/
|
||||
private _state: CanvasInpaintMaskState | null = null;
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<'inpaint_mask'>, manager: CanvasManager) {
|
||||
super(entityIdentifier, manager, CanvasInpaintMaskAdapter.TYPE);
|
||||
}
|
||||
|
||||
get state(): CanvasInpaintMaskState {
|
||||
if (this._state) {
|
||||
return this._state;
|
||||
}
|
||||
const state = this.manager.stateApi.getInpaintMasksState().entities.find((layer) => layer.id === this.id);
|
||||
assert(state, `State not found for ${this.id}`);
|
||||
return state;
|
||||
}
|
||||
|
||||
set state(state: CanvasInpaintMaskState) {
|
||||
this._state = state;
|
||||
}
|
||||
|
||||
update = async (state: CanvasInpaintMaskState) => {
|
||||
const prevState = this.state;
|
||||
this.state = state;
|
||||
|
||||
if (prevState && prevState === state) {
|
||||
this.log.trace('State unchanged, skipping update');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prevState || state.isEnabled !== prevState.isEnabled) {
|
||||
this.log.trace('Updating visibility');
|
||||
this.konva.layer.visible(state.isEnabled);
|
||||
this.renderer.syncCache(state.isEnabled);
|
||||
}
|
||||
if (!prevState || state.objects !== prevState.objects) {
|
||||
const didRender = await this.renderer.render();
|
||||
if (didRender) {
|
||||
this.transformer.requestRectCalculation();
|
||||
}
|
||||
}
|
||||
if (!prevState || state.position !== prevState.position) {
|
||||
this.transformer.updatePosition();
|
||||
}
|
||||
if (!prevState || state.opacity !== prevState.opacity) {
|
||||
this.renderer.updateOpacity();
|
||||
}
|
||||
if (!prevState || state.isLocked !== prevState.isLocked) {
|
||||
this.transformer.syncInteractionState();
|
||||
}
|
||||
if (!prevState || state.fill !== prevState.fill) {
|
||||
this.renderer.updateCompositingRectFill(state.fill);
|
||||
}
|
||||
|
||||
if (!prevState) {
|
||||
this.renderer.updateCompositingRectSize();
|
||||
}
|
||||
|
||||
if (!prevState) {
|
||||
this.transformer.updateBbox();
|
||||
}
|
||||
};
|
||||
|
||||
getHashableState = (): SerializableObject => {
|
||||
const keysToOmit: (keyof CanvasInpaintMaskState)[] = ['fill', 'name', 'opacity'];
|
||||
return omit(this.state, keysToOmit);
|
||||
};
|
||||
|
||||
getCanvas = (rect?: Rect): HTMLCanvasElement => {
|
||||
// The opacity may have been changed in response to user selecting a different entity category, and the mask regions
|
||||
// should be fully opaque - set opacity to 1 before rendering the canvas
|
||||
const attrs: GroupConfig = { opacity: 1 };
|
||||
const canvas = this.renderer.getCanvas(rect, attrs);
|
||||
return canvas;
|
||||
};
|
||||
}
|
||||
@@ -6,23 +6,31 @@ import { SyncableMap } from 'common/util/SyncableMap/SyncableMap';
|
||||
import { CanvasBboxModule } from 'features/controlLayers/konva/CanvasBboxModule';
|
||||
import { CanvasCacheModule } from 'features/controlLayers/konva/CanvasCacheModule';
|
||||
import { CanvasCompositorModule } from 'features/controlLayers/konva/CanvasCompositorModule';
|
||||
import type { CanvasControlLayerAdapter } from 'features/controlLayers/konva/CanvasControlLayerAdapter';
|
||||
import { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntityAdapterControlLayer';
|
||||
import { CanvasEntityAdapterInpaintMask } from 'features/controlLayers/konva/CanvasEntityAdapterInpaintMask';
|
||||
import { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntityAdapterRasterLayer';
|
||||
import { CanvasEntityAdapterRegionalGuidance } from 'features/controlLayers/konva/CanvasEntityAdapterRegionalGuidance';
|
||||
import { CanvasEntityRendererModule } from 'features/controlLayers/konva/CanvasEntityRendererModule';
|
||||
import { CanvasFilterModule } from 'features/controlLayers/konva/CanvasFilterModule';
|
||||
import type { CanvasInpaintMaskAdapter } from 'features/controlLayers/konva/CanvasInpaintMaskAdapter';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import { CanvasProgressImageModule } from 'features/controlLayers/konva/CanvasProgressImageModule';
|
||||
import type { CanvasRasterLayerAdapter } from 'features/controlLayers/konva/CanvasRasterLayerAdapter';
|
||||
import type { CanvasRegionalGuidanceAdapter } from 'features/controlLayers/konva/CanvasRegionalGuidanceAdapter';
|
||||
import { CanvasStageModule } from 'features/controlLayers/konva/CanvasStageModule';
|
||||
import { CanvasStagingAreaModule } from 'features/controlLayers/konva/CanvasStagingAreaModule';
|
||||
import { CanvasToolModule } from 'features/controlLayers/konva/CanvasToolModule';
|
||||
import { CanvasWorkerModule } from 'features/controlLayers/konva/CanvasWorkerModule.js';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import {
|
||||
type CanvasEntityIdentifier,
|
||||
isControlLayerEntityIdentifier,
|
||||
isInpaintMaskEntityIdentifier,
|
||||
isRasterLayerEntityIdentifier,
|
||||
isRegionalGuidanceEntityIdentifier,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { Atom } from 'nanostores';
|
||||
import { atom, computed } from 'nanostores';
|
||||
import type { Logger } from 'roarr';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import { CanvasBackgroundModule } from './CanvasBackgroundModule';
|
||||
import { CanvasStateApiModule } from './CanvasStateApiModule';
|
||||
@@ -41,23 +49,10 @@ export class CanvasManager extends CanvasModuleBase {
|
||||
socket: AppSocket;
|
||||
|
||||
adapters = {
|
||||
rasterLayers: new SyncableMap<string, CanvasRasterLayerAdapter>(),
|
||||
controlLayers: new SyncableMap<string, CanvasControlLayerAdapter>(),
|
||||
regionMasks: new SyncableMap<string, CanvasRegionalGuidanceAdapter>(),
|
||||
inpaintMasks: new SyncableMap<string, CanvasInpaintMaskAdapter>(),
|
||||
getAll: (): (
|
||||
| CanvasRasterLayerAdapter
|
||||
| CanvasControlLayerAdapter
|
||||
| CanvasRegionalGuidanceAdapter
|
||||
| CanvasInpaintMaskAdapter
|
||||
)[] => {
|
||||
return [
|
||||
...this.adapters.rasterLayers.values(),
|
||||
...this.adapters.controlLayers.values(),
|
||||
...this.adapters.regionMasks.values(),
|
||||
...this.adapters.inpaintMasks.values(),
|
||||
];
|
||||
},
|
||||
rasterLayers: new SyncableMap<string, CanvasEntityAdapterRasterLayer>(),
|
||||
controlLayers: new SyncableMap<string, CanvasEntityAdapterControlLayer>(),
|
||||
regionMasks: new SyncableMap<string, CanvasEntityAdapterRegionalGuidance>(),
|
||||
inpaintMasks: new SyncableMap<string, CanvasEntityAdapterInpaintMask>(),
|
||||
};
|
||||
|
||||
stateApi: CanvasStateApiModule;
|
||||
@@ -137,6 +132,53 @@ export class CanvasManager extends CanvasModuleBase {
|
||||
});
|
||||
}
|
||||
|
||||
deleteAdapter = (entityIdentifier: CanvasEntityIdentifier): boolean => {
|
||||
switch (entityIdentifier.type) {
|
||||
case 'raster_layer':
|
||||
return this.adapters.rasterLayers.delete(entityIdentifier.id);
|
||||
case 'control_layer':
|
||||
return this.adapters.controlLayers.delete(entityIdentifier.id);
|
||||
case 'regional_guidance':
|
||||
return this.adapters.regionMasks.delete(entityIdentifier.id);
|
||||
case 'inpaint_mask':
|
||||
return this.adapters.inpaintMasks.delete(entityIdentifier.id);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
getAllAdapters = (): (
|
||||
| CanvasEntityAdapterRasterLayer
|
||||
| CanvasEntityAdapterControlLayer
|
||||
| CanvasEntityAdapterRegionalGuidance
|
||||
| CanvasEntityAdapterInpaintMask
|
||||
)[] => {
|
||||
return [
|
||||
...this.adapters.rasterLayers.values(),
|
||||
...this.adapters.controlLayers.values(),
|
||||
...this.adapters.regionMasks.values(),
|
||||
...this.adapters.inpaintMasks.values(),
|
||||
];
|
||||
};
|
||||
|
||||
createAdapter = (entityIdentifier: CanvasEntityIdentifier): void => {
|
||||
if (isRasterLayerEntityIdentifier(entityIdentifier)) {
|
||||
const adapter = new CanvasEntityAdapterRasterLayer(entityIdentifier, this);
|
||||
this.adapters.rasterLayers.set(adapter.id, adapter);
|
||||
} else if (isControlLayerEntityIdentifier(entityIdentifier)) {
|
||||
const adapter = new CanvasEntityAdapterControlLayer(entityIdentifier, this);
|
||||
this.adapters.controlLayers.set(adapter.id, adapter);
|
||||
} else if (isRegionalGuidanceEntityIdentifier(entityIdentifier)) {
|
||||
const adapter = new CanvasEntityAdapterRegionalGuidance(entityIdentifier, this);
|
||||
this.adapters.regionMasks.set(adapter.id, adapter);
|
||||
} else if (isInpaintMaskEntityIdentifier(entityIdentifier)) {
|
||||
const adapter = new CanvasEntityAdapterInpaintMask(entityIdentifier, this);
|
||||
this.adapters.inpaintMasks.set(adapter.id, adapter);
|
||||
} else {
|
||||
assert(false, 'Unhandled entity type');
|
||||
}
|
||||
};
|
||||
|
||||
enableDebugging() {
|
||||
this._isDebugging = true;
|
||||
this.logDebugInfo();
|
||||
@@ -163,7 +205,7 @@ export class CanvasManager extends CanvasModuleBase {
|
||||
destroy = () => {
|
||||
this.log.debug('Destroying module');
|
||||
|
||||
for (const adapter of this.adapters.getAll()) {
|
||||
for (const adapter of this.getAllAdapters()) {
|
||||
adapter.destroy();
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ export abstract class CanvasModuleBase {
|
||||
* destroy = () => {
|
||||
* this.log('Destroying module');
|
||||
* this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
* this.subscriptions.clear();
|
||||
* this.konva.group.destroy();
|
||||
* };
|
||||
* ```
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasEntityIdentifier, CanvasRasterLayerState, Rect } from 'features/controlLayers/store/types';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { omit } from 'lodash-es';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export class CanvasRasterLayerAdapter extends CanvasEntityAdapterBase<CanvasRasterLayerState> {
|
||||
static TYPE = 'raster_layer_adapter';
|
||||
/**
|
||||
* The last known state of the entity.
|
||||
*/
|
||||
private _state: CanvasRasterLayerState | null = null;
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<'raster_layer'>, manager: CanvasManager) {
|
||||
super(entityIdentifier, manager, CanvasRasterLayerAdapter.TYPE);
|
||||
}
|
||||
|
||||
get state(): CanvasRasterLayerState {
|
||||
if (this._state) {
|
||||
return this._state;
|
||||
}
|
||||
const state = this.manager.stateApi.getRasterLayersState().entities.find((layer) => layer.id === this.id);
|
||||
assert(state, `State not found for ${this.id}`);
|
||||
return state;
|
||||
}
|
||||
|
||||
set state(state: CanvasRasterLayerState) {
|
||||
const prevState = this._state;
|
||||
this._state = state;
|
||||
this.render(state, prevState);
|
||||
}
|
||||
|
||||
private render = async (state: CanvasRasterLayerState, prevState: CanvasRasterLayerState | null): Promise<void> => {
|
||||
if (prevState && prevState === state) {
|
||||
this.log.trace('State unchanged, skipping update');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prevState || state.isEnabled !== prevState.isEnabled) {
|
||||
this.log.trace('Updating visibility');
|
||||
this.konva.layer.visible(state.isEnabled);
|
||||
this.renderer.syncCache(state.isEnabled);
|
||||
}
|
||||
if (!prevState || state.isLocked !== prevState.isLocked) {
|
||||
this.transformer.syncInteractionState();
|
||||
}
|
||||
if (!prevState || state.objects !== prevState.objects) {
|
||||
const didRender = await this.renderer.render();
|
||||
if (didRender) {
|
||||
this.transformer.requestRectCalculation();
|
||||
}
|
||||
}
|
||||
if (!prevState || state.position !== prevState.position) {
|
||||
this.transformer.updatePosition();
|
||||
}
|
||||
if (!prevState || state.opacity !== prevState.opacity) {
|
||||
this.renderer.updateOpacity();
|
||||
}
|
||||
if (!prevState) {
|
||||
// First render
|
||||
this.transformer.updateBbox();
|
||||
}
|
||||
};
|
||||
|
||||
getCanvas = (rect?: Rect): HTMLCanvasElement => {
|
||||
this.log.trace({ rect }, 'Getting canvas');
|
||||
// The opacity may have been changed in response to user selecting a different entity category, so we must restore
|
||||
// the original opacity before rendering the canvas
|
||||
const attrs: GroupConfig = { opacity: this.state.opacity };
|
||||
const canvas = this.renderer.getCanvas(rect, attrs);
|
||||
return canvas;
|
||||
};
|
||||
|
||||
getHashableState = (): SerializableObject => {
|
||||
const keysToOmit: (keyof CanvasRasterLayerState)[] = ['name'];
|
||||
return omit(this.state, keysToOmit);
|
||||
};
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasEntityIdentifier, CanvasRegionalGuidanceState, Rect } from 'features/controlLayers/store/types';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { omit } from 'lodash-es';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export class CanvasRegionalGuidanceAdapter extends CanvasEntityAdapterBase<CanvasRegionalGuidanceState> {
|
||||
static TYPE = 'regional_guidance_adapter';
|
||||
|
||||
/**
|
||||
* The last known state of the entity.
|
||||
*/
|
||||
private _state: CanvasRegionalGuidanceState | null = null;
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>, manager: CanvasManager) {
|
||||
super(entityIdentifier, manager, CanvasRegionalGuidanceAdapter.TYPE);
|
||||
}
|
||||
|
||||
get state(): CanvasRegionalGuidanceState {
|
||||
if (this._state) {
|
||||
return this._state;
|
||||
}
|
||||
const state = this.manager.stateApi.getRegionsState().entities.find((layer) => layer.id === this.id);
|
||||
assert(state, `State not found for ${this.id}`);
|
||||
return state;
|
||||
}
|
||||
|
||||
set state(state: CanvasRegionalGuidanceState) {
|
||||
const prevState = this._state;
|
||||
this._state = state;
|
||||
this.render(state, prevState);
|
||||
}
|
||||
|
||||
render = async (state: CanvasRegionalGuidanceState, prevState: CanvasRegionalGuidanceState | null) => {
|
||||
if (prevState && prevState === state) {
|
||||
this.log.trace('State unchanged, skipping update');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prevState || state.isEnabled !== prevState.isEnabled) {
|
||||
this.log.trace('Updating visibility');
|
||||
this.konva.layer.visible(state.isEnabled);
|
||||
this.renderer.syncCache(state.isEnabled);
|
||||
}
|
||||
if (!prevState || state.objects !== prevState.objects) {
|
||||
const didRender = await this.renderer.render();
|
||||
if (didRender) {
|
||||
this.transformer.requestRectCalculation();
|
||||
}
|
||||
}
|
||||
if (!prevState || state.position !== prevState.position) {
|
||||
this.transformer.updatePosition();
|
||||
}
|
||||
if (!prevState || state.opacity !== prevState.opacity) {
|
||||
this.renderer.updateOpacity();
|
||||
}
|
||||
if (!prevState || state.isLocked !== prevState.isLocked) {
|
||||
this.transformer.syncInteractionState();
|
||||
}
|
||||
if (!prevState || state.fill !== prevState.fill) {
|
||||
this.renderer.updateCompositingRectFill(state.fill);
|
||||
}
|
||||
|
||||
if (!prevState) {
|
||||
this.renderer.updateCompositingRectSize();
|
||||
}
|
||||
|
||||
if (!prevState) {
|
||||
this.transformer.updateBbox();
|
||||
}
|
||||
};
|
||||
|
||||
getHashableState = (): SerializableObject => {
|
||||
const keysToOmit: (keyof CanvasRegionalGuidanceState)[] = ['fill', 'name', 'opacity'];
|
||||
return omit(this.state, keysToOmit);
|
||||
};
|
||||
|
||||
getCanvas = (rect?: Rect): HTMLCanvasElement => {
|
||||
// The opacity may have been changed in response to user selecting a different entity category, and the mask regions
|
||||
// should be fully opaque - set opacity to 1 before rendering the canvas
|
||||
const attrs: GroupConfig = { opacity: 1 };
|
||||
const canvas = this.renderer.getCanvas(rect, attrs);
|
||||
return canvas;
|
||||
};
|
||||
}
|
||||
@@ -116,7 +116,7 @@ export class CanvasStageModule extends CanvasModuleBase {
|
||||
getVisibleRect = (type?: Exclude<CanvasEntityIdentifier['type'], 'ip_adapter'>): Rect => {
|
||||
const rects = [];
|
||||
|
||||
for (const adapter of this.manager.adapters.getAll()) {
|
||||
for (const adapter of this.manager.getAllAdapters()) {
|
||||
if (!adapter.state.isEnabled) {
|
||||
continue;
|
||||
}
|
||||
@@ -322,6 +322,7 @@ export class CanvasStageModule extends CanvasModuleBase {
|
||||
destroy = () => {
|
||||
this.log.debug('Destroying module');
|
||||
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
this.subscriptions.clear();
|
||||
this.konva.stage.destroy();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ export class CanvasStagingAreaModule extends CanvasModuleBase {
|
||||
destroy = () => {
|
||||
this.log.debug('Destroying module');
|
||||
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
this.subscriptions.clear();
|
||||
if (this.image) {
|
||||
this.image.destroy();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { $alt, $ctrl, $meta, $shift } from '@invoke-ai/ui-library';
|
||||
import type { AppStore } from 'app/store/store';
|
||||
import type { CanvasControlLayerAdapter } from 'features/controlLayers/konva/CanvasControlLayerAdapter';
|
||||
import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntityAdapterControlLayer';
|
||||
import type { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasInpaintMaskAdapter } from 'features/controlLayers/konva/CanvasInpaintMaskAdapter';
|
||||
import type { CanvasEntityAdapterInpaintMask } from 'features/controlLayers/konva/CanvasEntityAdapterInpaintMask';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import type { CanvasRasterLayerAdapter } from 'features/controlLayers/konva/CanvasRasterLayerAdapter';
|
||||
import type { CanvasRegionalGuidanceAdapter } from 'features/controlLayers/konva/CanvasRegionalGuidanceAdapter';
|
||||
import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntityAdapterRasterLayer';
|
||||
import type { CanvasEntityAdapterRegionalGuidance } from 'features/controlLayers/konva/CanvasEntityAdapterRegionalGuidance';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import type { CanvasSettingsState } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import {
|
||||
@@ -54,25 +54,25 @@ type EntityStateAndAdapter =
|
||||
id: string;
|
||||
type: CanvasRasterLayerState['type'];
|
||||
state: CanvasRasterLayerState;
|
||||
adapter: CanvasRasterLayerAdapter;
|
||||
adapter: CanvasEntityAdapterRasterLayer;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
type: CanvasControlLayerState['type'];
|
||||
state: CanvasControlLayerState;
|
||||
adapter: CanvasControlLayerAdapter;
|
||||
adapter: CanvasEntityAdapterControlLayer;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
type: CanvasInpaintMaskState['type'];
|
||||
state: CanvasInpaintMaskState;
|
||||
adapter: CanvasInpaintMaskAdapter;
|
||||
adapter: CanvasEntityAdapterInpaintMask;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
type: CanvasRegionalGuidanceState['type'];
|
||||
state: CanvasRegionalGuidanceState;
|
||||
adapter: CanvasRegionalGuidanceAdapter;
|
||||
adapter: CanvasEntityAdapterRegionalGuidance;
|
||||
};
|
||||
|
||||
export class CanvasStateApiModule extends CanvasModuleBase {
|
||||
|
||||
@@ -136,7 +136,6 @@ export class CanvasToolModule extends CanvasModuleBase {
|
||||
};
|
||||
|
||||
syncCursorStyle = () => {
|
||||
this.log.trace('Syncing cursor style');
|
||||
const stage = this.manager.stage;
|
||||
const isMouseDown = this.$isMouseDown.get();
|
||||
const tool = this.$tool.get();
|
||||
@@ -147,7 +146,7 @@ export class CanvasToolModule extends CanvasModuleBase {
|
||||
stage.setCursor('not-allowed');
|
||||
} else if (this.manager.$isBusy.get()) {
|
||||
stage.setCursor('not-allowed');
|
||||
} else if (!this.manager.stateApi.getSelectedEntity()?.adapter.isInteractable()) {
|
||||
} else if (!this.manager.stateApi.getSelectedEntity()?.adapter.getIsInteractable()) {
|
||||
stage.setCursor('not-allowed');
|
||||
} else if (tool === 'colorPicker' || tool === 'brush' || tool === 'eraser') {
|
||||
stage.setCursor('none');
|
||||
@@ -283,7 +282,7 @@ export class CanvasToolModule extends CanvasModuleBase {
|
||||
return false;
|
||||
} else if (this.manager.filter.$isFiltering.get()) {
|
||||
return false;
|
||||
} else if (!this.manager.stateApi.getSelectedEntity()?.adapter.isInteractable()) {
|
||||
} else if (!this.manager.stateApi.getSelectedEntity()?.adapter.getIsInteractable()) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
@@ -708,6 +707,7 @@ export class CanvasToolModule extends CanvasModuleBase {
|
||||
destroy = () => {
|
||||
this.log.debug('Destroying module');
|
||||
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
this.subscriptions.clear();
|
||||
this.konva.group.destroy();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -790,6 +790,30 @@ export function isDrawableEntityType(
|
||||
);
|
||||
}
|
||||
|
||||
export function isRasterLayerEntityIdentifier(
|
||||
entityIdentifier: CanvasEntityIdentifier
|
||||
): entityIdentifier is CanvasEntityIdentifier<'raster_layer'> {
|
||||
return entityIdentifier.type === 'raster_layer';
|
||||
}
|
||||
|
||||
export function isControlLayerEntityIdentifier(
|
||||
entityIdentifier: CanvasEntityIdentifier
|
||||
): entityIdentifier is CanvasEntityIdentifier<'control_layer'> {
|
||||
return entityIdentifier.type === 'control_layer';
|
||||
}
|
||||
|
||||
export function isInpaintMaskEntityIdentifier(
|
||||
entityIdentifier: CanvasEntityIdentifier
|
||||
): entityIdentifier is CanvasEntityIdentifier<'inpaint_mask'> {
|
||||
return entityIdentifier.type === 'inpaint_mask';
|
||||
}
|
||||
|
||||
export function isRegionalGuidanceEntityIdentifier(
|
||||
entityIdentifier: CanvasEntityIdentifier
|
||||
): entityIdentifier is CanvasEntityIdentifier<'regional_guidance'> {
|
||||
return entityIdentifier.type === 'regional_guidance';
|
||||
}
|
||||
|
||||
export function isRenderableEntity(entity: CanvasEntityState): entity is CanvasRenderableEntityState {
|
||||
return isDrawableEntityType(entity.type);
|
||||
}
|
||||
@@ -803,5 +827,5 @@ export const getEntityIdentifier = <T extends CanvasEntityType>(
|
||||
export const isMaskEntityIdentifier = (
|
||||
entityIdentifier: CanvasEntityIdentifier
|
||||
): entityIdentifier is CanvasEntityIdentifier<'inpaint_mask' | 'regional_guidance'> => {
|
||||
return entityIdentifier.type === 'inpaint_mask' || entityIdentifier.type === 'regional_guidance';
|
||||
return isInpaintMaskEntityIdentifier(entityIdentifier) || isRegionalGuidanceEntityIdentifier(entityIdentifier);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user