feat(ui): ip adapter layers are selectable

This is largely an internal change, and it should have been this way from the start - less tip-toeing around layer types. The user-facing change is when you click an IP Adapter layer, it is highlighted. That's it.
This commit is contained in:
psychedelicious 2024-05-09 12:46:22 +10:00 committed by Kent Keirsey
parent b180666497
commit 2f9a064d48
6 changed files with 27 additions and 28 deletions

View File

@ -19,7 +19,6 @@ export const CALayer = memo(({ layerId }: Props) => {
const dispatch = useAppDispatch();
const isSelected = useAppSelector((s) => selectCALayerOrThrow(s.controlLayers.present, layerId).isSelected);
const onClick = useCallback(() => {
// Must be capture so that the layer is selected before deleting/resetting/etc
dispatch(layerSelected(layerId));
}, [dispatch, layerId]);
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });

View File

@ -1,19 +1,26 @@
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { IPALayerIPAdapterWrapper } from 'features/controlLayers/components/IPALayer/IPALayerIPAdapterWrapper';
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
import { memo } from 'react';
import { layerSelected, selectIPALayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback } from 'react';
type Props = {
layerId: string;
};
export const IPALayer = memo(({ layerId }: Props) => {
const dispatch = useAppDispatch();
const isSelected = useAppSelector((s) => selectIPALayerOrThrow(s.controlLayers.present, layerId).isSelected);
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
const onClick = useCallback(() => {
dispatch(layerSelected(layerId));
}, [dispatch, layerId]);
return (
<LayerWrapper borderColor="base.800">
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
<LayerVisibilityToggle layerId={layerId} />
<LayerTitle type="ip_adapter_layer" />

View File

@ -124,11 +124,11 @@ const getVectorMaskPreviewColor = (state: ControlLayersState): RgbColor => {
const lastColor = rgLayers[rgLayers.length - 1]?.previewColor;
return LayerColors.next(lastColor);
};
const deselectAllLayers = (state: ControlLayersState) => {
for (const layer of state.layers.filter(isRenderableLayer)) {
layer.isSelected = false;
const exclusivelySelectLayer = (state: ControlLayersState, layerId: string) => {
for (const layer of state.layers) {
layer.isSelected = layer.id === layerId;
}
state.selectedLayerId = null;
state.selectedLayerId = layerId;
};
export const controlLayersSlice = createSlice({
@ -137,12 +137,7 @@ export const controlLayersSlice = createSlice({
reducers: {
//#region Any Layer Type
layerSelected: (state, action: PayloadAction<string>) => {
deselectAllLayers(state);
const layer = state.layers.find((l) => l.id === action.payload);
if (isRenderableLayer(layer)) {
layer.isSelected = true;
state.selectedLayerId = layer.id;
}
exclusivelySelectLayer(state, action.payload);
},
layerVisibilityToggled: (state, action: PayloadAction<string>) => {
const layer = state.layers.find((l) => l.id === action.payload);
@ -232,7 +227,6 @@ export const controlLayersSlice = createSlice({
action: PayloadAction<{ layerId: string; controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2 }>
) => {
const { layerId, controlAdapter } = action.payload;
deselectAllLayers(state);
const layer: ControlAdapterLayer = {
id: getCALayerId(layerId),
type: 'control_adapter_layer',
@ -247,16 +241,15 @@ export const controlLayersSlice = createSlice({
controlAdapter,
};
state.layers.push(layer);
state.selectedLayerId = layer.id;
exclusivelySelectLayer(state, layer.id);
},
prepare: (controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2) => ({
payload: { layerId: uuidv4(), controlAdapter },
}),
},
caLayerRecalled: (state, action: PayloadAction<ControlAdapterLayer>) => {
deselectAllLayers(state);
state.layers.push({ ...action.payload, isSelected: true });
state.selectedLayerId = action.payload.id;
exclusivelySelectLayer(state, action.payload.id);
},
caLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
const { layerId, imageDTO } = action.payload;
@ -359,9 +352,11 @@ export const controlLayersSlice = createSlice({
id: getIPALayerId(layerId),
type: 'ip_adapter_layer',
isEnabled: true,
isSelected: true,
ipAdapter,
};
state.layers.push(layer);
exclusivelySelectLayer(state, layer.id);
},
prepare: (ipAdapter: IPAdapterConfigV2) => ({ payload: { layerId: uuidv4(), ipAdapter } }),
},
@ -431,7 +426,6 @@ export const controlLayersSlice = createSlice({
rgLayerAdded: {
reducer: (state, action: PayloadAction<{ layerId: string }>) => {
const { layerId } = action.payload;
deselectAllLayers(state);
const layer: RegionalGuidanceLayer = {
id: getRGLayerId(layerId),
type: 'regional_guidance_layer',
@ -450,14 +444,13 @@ export const controlLayersSlice = createSlice({
uploadedMaskImage: null,
};
state.layers.push(layer);
state.selectedLayerId = layer.id;
exclusivelySelectLayer(state, layer.id);
},
prepare: () => ({ payload: { layerId: uuidv4() } }),
},
rgLayerRecalled: (state, action: PayloadAction<RegionalGuidanceLayer>) => {
deselectAllLayers(state);
state.layers.push({ ...action.payload, isSelected: true });
state.selectedLayerId = action.payload.id;
exclusivelySelectLayer(state, action.payload.id);
},
rgLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
const { layerId, prompt } = action.payload;
@ -622,7 +615,6 @@ export const controlLayersSlice = createSlice({
//#region Initial Image Layer
iiLayerAdded: {
reducer: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
deselectAllLayers(state);
const { layerId, imageDTO } = action.payload;
// Highlander! There can be only one!
state.layers = state.layers.filter((l) => (isInitialImageLayer(l) ? false : true));
@ -640,15 +632,14 @@ export const controlLayersSlice = createSlice({
denoisingStrength: 0.75,
};
state.layers.push(layer);
state.selectedLayerId = layer.id;
exclusivelySelectLayer(state, layer.id);
},
prepare: (imageDTO: ImageDTO | null) => ({ payload: { layerId: INITIAL_IMAGE_LAYER_ID, imageDTO } }),
},
iiLayerRecalled: (state, action: PayloadAction<InitialImageLayer>) => {
deselectAllLayers(state);
state.layers = state.layers.filter((l) => (isInitialImageLayer(l) ? false : true));
state.layers.push({ ...action.payload, isSelected: true });
state.selectedLayerId = action.payload.id;
exclusivelySelectLayer(state, action.payload.id);
},
iiLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
const { layerId, imageDTO } = action.payload;

View File

@ -48,7 +48,8 @@ export type VectorMaskRect = z.infer<typeof zVectorMaskRect>;
const zLayerBase = z.object({
id: z.string(),
isEnabled: z.boolean(),
isEnabled: z.boolean().default(true),
isSelected: z.boolean().default(true),
});
const zRect = z.object({
@ -62,7 +63,6 @@ const zRenderableLayerBase = zLayerBase.extend({
y: z.number(),
bbox: zRect.nullable(),
bboxNeedsUpdate: z.boolean(),
isSelected: z.boolean(),
});
const zControlAdapterLayer = zRenderableLayerBase.extend({

View File

@ -702,6 +702,7 @@ const renderLayers = (
if (isInitialImageLayer(reduxLayer)) {
renderInitialImageLayer(stage, reduxLayer);
}
// IP Adapter layers are not rendered
}
};

View File

@ -692,8 +692,9 @@ const parseIPAdapterToIPAdapterLayer: MetadataParseFunc<IPAdapterLayer> = async
const layer: IPAdapterLayer = {
id: getIPALayerId(uuidv4()),
isEnabled: true,
type: 'ip_adapter_layer',
isEnabled: true,
isSelected: true,
ipAdapter: {
id: uuidv4(),
type: 'ip_adapter',