mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
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:
parent
b180666497
commit
2f9a064d48
@ -19,7 +19,6 @@ export const CALayer = memo(({ layerId }: Props) => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isSelected = useAppSelector((s) => selectCALayerOrThrow(s.controlLayers.present, layerId).isSelected);
|
const isSelected = useAppSelector((s) => selectCALayerOrThrow(s.controlLayers.present, layerId).isSelected);
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
// Must be capture so that the layer is selected before deleting/resetting/etc
|
|
||||||
dispatch(layerSelected(layerId));
|
dispatch(layerSelected(layerId));
|
||||||
}, [dispatch, layerId]);
|
}, [dispatch, layerId]);
|
||||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
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 { IPALayerIPAdapterWrapper } from 'features/controlLayers/components/IPALayer/IPALayerIPAdapterWrapper';
|
||||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
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 = {
|
type Props = {
|
||||||
layerId: string;
|
layerId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IPALayer = memo(({ layerId }: Props) => {
|
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 { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
dispatch(layerSelected(layerId));
|
||||||
|
}, [dispatch, layerId]);
|
||||||
return (
|
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}>
|
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||||
<LayerVisibilityToggle layerId={layerId} />
|
<LayerVisibilityToggle layerId={layerId} />
|
||||||
<LayerTitle type="ip_adapter_layer" />
|
<LayerTitle type="ip_adapter_layer" />
|
||||||
|
@ -124,11 +124,11 @@ const getVectorMaskPreviewColor = (state: ControlLayersState): RgbColor => {
|
|||||||
const lastColor = rgLayers[rgLayers.length - 1]?.previewColor;
|
const lastColor = rgLayers[rgLayers.length - 1]?.previewColor;
|
||||||
return LayerColors.next(lastColor);
|
return LayerColors.next(lastColor);
|
||||||
};
|
};
|
||||||
const deselectAllLayers = (state: ControlLayersState) => {
|
const exclusivelySelectLayer = (state: ControlLayersState, layerId: string) => {
|
||||||
for (const layer of state.layers.filter(isRenderableLayer)) {
|
for (const layer of state.layers) {
|
||||||
layer.isSelected = false;
|
layer.isSelected = layer.id === layerId;
|
||||||
}
|
}
|
||||||
state.selectedLayerId = null;
|
state.selectedLayerId = layerId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const controlLayersSlice = createSlice({
|
export const controlLayersSlice = createSlice({
|
||||||
@ -137,12 +137,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
reducers: {
|
reducers: {
|
||||||
//#region Any Layer Type
|
//#region Any Layer Type
|
||||||
layerSelected: (state, action: PayloadAction<string>) => {
|
layerSelected: (state, action: PayloadAction<string>) => {
|
||||||
deselectAllLayers(state);
|
exclusivelySelectLayer(state, action.payload);
|
||||||
const layer = state.layers.find((l) => l.id === action.payload);
|
|
||||||
if (isRenderableLayer(layer)) {
|
|
||||||
layer.isSelected = true;
|
|
||||||
state.selectedLayerId = layer.id;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
layerVisibilityToggled: (state, action: PayloadAction<string>) => {
|
layerVisibilityToggled: (state, action: PayloadAction<string>) => {
|
||||||
const layer = state.layers.find((l) => l.id === action.payload);
|
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 }>
|
action: PayloadAction<{ layerId: string; controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2 }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, controlAdapter } = action.payload;
|
const { layerId, controlAdapter } = action.payload;
|
||||||
deselectAllLayers(state);
|
|
||||||
const layer: ControlAdapterLayer = {
|
const layer: ControlAdapterLayer = {
|
||||||
id: getCALayerId(layerId),
|
id: getCALayerId(layerId),
|
||||||
type: 'control_adapter_layer',
|
type: 'control_adapter_layer',
|
||||||
@ -247,16 +241,15 @@ export const controlLayersSlice = createSlice({
|
|||||||
controlAdapter,
|
controlAdapter,
|
||||||
};
|
};
|
||||||
state.layers.push(layer);
|
state.layers.push(layer);
|
||||||
state.selectedLayerId = layer.id;
|
exclusivelySelectLayer(state, layer.id);
|
||||||
},
|
},
|
||||||
prepare: (controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2) => ({
|
prepare: (controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2) => ({
|
||||||
payload: { layerId: uuidv4(), controlAdapter },
|
payload: { layerId: uuidv4(), controlAdapter },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
caLayerRecalled: (state, action: PayloadAction<ControlAdapterLayer>) => {
|
caLayerRecalled: (state, action: PayloadAction<ControlAdapterLayer>) => {
|
||||||
deselectAllLayers(state);
|
|
||||||
state.layers.push({ ...action.payload, isSelected: true });
|
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 }>) => {
|
caLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||||
const { layerId, imageDTO } = action.payload;
|
const { layerId, imageDTO } = action.payload;
|
||||||
@ -359,9 +352,11 @@ export const controlLayersSlice = createSlice({
|
|||||||
id: getIPALayerId(layerId),
|
id: getIPALayerId(layerId),
|
||||||
type: 'ip_adapter_layer',
|
type: 'ip_adapter_layer',
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
|
isSelected: true,
|
||||||
ipAdapter,
|
ipAdapter,
|
||||||
};
|
};
|
||||||
state.layers.push(layer);
|
state.layers.push(layer);
|
||||||
|
exclusivelySelectLayer(state, layer.id);
|
||||||
},
|
},
|
||||||
prepare: (ipAdapter: IPAdapterConfigV2) => ({ payload: { layerId: uuidv4(), ipAdapter } }),
|
prepare: (ipAdapter: IPAdapterConfigV2) => ({ payload: { layerId: uuidv4(), ipAdapter } }),
|
||||||
},
|
},
|
||||||
@ -431,7 +426,6 @@ export const controlLayersSlice = createSlice({
|
|||||||
rgLayerAdded: {
|
rgLayerAdded: {
|
||||||
reducer: (state, action: PayloadAction<{ layerId: string }>) => {
|
reducer: (state, action: PayloadAction<{ layerId: string }>) => {
|
||||||
const { layerId } = action.payload;
|
const { layerId } = action.payload;
|
||||||
deselectAllLayers(state);
|
|
||||||
const layer: RegionalGuidanceLayer = {
|
const layer: RegionalGuidanceLayer = {
|
||||||
id: getRGLayerId(layerId),
|
id: getRGLayerId(layerId),
|
||||||
type: 'regional_guidance_layer',
|
type: 'regional_guidance_layer',
|
||||||
@ -450,14 +444,13 @@ export const controlLayersSlice = createSlice({
|
|||||||
uploadedMaskImage: null,
|
uploadedMaskImage: null,
|
||||||
};
|
};
|
||||||
state.layers.push(layer);
|
state.layers.push(layer);
|
||||||
state.selectedLayerId = layer.id;
|
exclusivelySelectLayer(state, layer.id);
|
||||||
},
|
},
|
||||||
prepare: () => ({ payload: { layerId: uuidv4() } }),
|
prepare: () => ({ payload: { layerId: uuidv4() } }),
|
||||||
},
|
},
|
||||||
rgLayerRecalled: (state, action: PayloadAction<RegionalGuidanceLayer>) => {
|
rgLayerRecalled: (state, action: PayloadAction<RegionalGuidanceLayer>) => {
|
||||||
deselectAllLayers(state);
|
|
||||||
state.layers.push({ ...action.payload, isSelected: true });
|
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 }>) => {
|
rgLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
|
||||||
const { layerId, prompt } = action.payload;
|
const { layerId, prompt } = action.payload;
|
||||||
@ -622,7 +615,6 @@ export const controlLayersSlice = createSlice({
|
|||||||
//#region Initial Image Layer
|
//#region Initial Image Layer
|
||||||
iiLayerAdded: {
|
iiLayerAdded: {
|
||||||
reducer: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
reducer: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||||
deselectAllLayers(state);
|
|
||||||
const { layerId, imageDTO } = action.payload;
|
const { layerId, imageDTO } = action.payload;
|
||||||
// Highlander! There can be only one!
|
// Highlander! There can be only one!
|
||||||
state.layers = state.layers.filter((l) => (isInitialImageLayer(l) ? false : true));
|
state.layers = state.layers.filter((l) => (isInitialImageLayer(l) ? false : true));
|
||||||
@ -640,15 +632,14 @@ export const controlLayersSlice = createSlice({
|
|||||||
denoisingStrength: 0.75,
|
denoisingStrength: 0.75,
|
||||||
};
|
};
|
||||||
state.layers.push(layer);
|
state.layers.push(layer);
|
||||||
state.selectedLayerId = layer.id;
|
exclusivelySelectLayer(state, layer.id);
|
||||||
},
|
},
|
||||||
prepare: (imageDTO: ImageDTO | null) => ({ payload: { layerId: INITIAL_IMAGE_LAYER_ID, imageDTO } }),
|
prepare: (imageDTO: ImageDTO | null) => ({ payload: { layerId: INITIAL_IMAGE_LAYER_ID, imageDTO } }),
|
||||||
},
|
},
|
||||||
iiLayerRecalled: (state, action: PayloadAction<InitialImageLayer>) => {
|
iiLayerRecalled: (state, action: PayloadAction<InitialImageLayer>) => {
|
||||||
deselectAllLayers(state);
|
|
||||||
state.layers = state.layers.filter((l) => (isInitialImageLayer(l) ? false : true));
|
state.layers = state.layers.filter((l) => (isInitialImageLayer(l) ? false : true));
|
||||||
state.layers.push({ ...action.payload, isSelected: 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 }>) => {
|
iiLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||||
const { layerId, imageDTO } = action.payload;
|
const { layerId, imageDTO } = action.payload;
|
||||||
|
@ -48,7 +48,8 @@ export type VectorMaskRect = z.infer<typeof zVectorMaskRect>;
|
|||||||
|
|
||||||
const zLayerBase = z.object({
|
const zLayerBase = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
isEnabled: z.boolean(),
|
isEnabled: z.boolean().default(true),
|
||||||
|
isSelected: z.boolean().default(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
const zRect = z.object({
|
const zRect = z.object({
|
||||||
@ -62,7 +63,6 @@ const zRenderableLayerBase = zLayerBase.extend({
|
|||||||
y: z.number(),
|
y: z.number(),
|
||||||
bbox: zRect.nullable(),
|
bbox: zRect.nullable(),
|
||||||
bboxNeedsUpdate: z.boolean(),
|
bboxNeedsUpdate: z.boolean(),
|
||||||
isSelected: z.boolean(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const zControlAdapterLayer = zRenderableLayerBase.extend({
|
const zControlAdapterLayer = zRenderableLayerBase.extend({
|
||||||
|
@ -702,6 +702,7 @@ const renderLayers = (
|
|||||||
if (isInitialImageLayer(reduxLayer)) {
|
if (isInitialImageLayer(reduxLayer)) {
|
||||||
renderInitialImageLayer(stage, reduxLayer);
|
renderInitialImageLayer(stage, reduxLayer);
|
||||||
}
|
}
|
||||||
|
// IP Adapter layers are not rendered
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -692,8 +692,9 @@ const parseIPAdapterToIPAdapterLayer: MetadataParseFunc<IPAdapterLayer> = async
|
|||||||
|
|
||||||
const layer: IPAdapterLayer = {
|
const layer: IPAdapterLayer = {
|
||||||
id: getIPALayerId(uuidv4()),
|
id: getIPALayerId(uuidv4()),
|
||||||
isEnabled: true,
|
|
||||||
type: 'ip_adapter_layer',
|
type: 'ip_adapter_layer',
|
||||||
|
isEnabled: true,
|
||||||
|
isSelected: true,
|
||||||
ipAdapter: {
|
ipAdapter: {
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
type: 'ip_adapter',
|
type: 'ip_adapter',
|
||||||
|
Loading…
Reference in New Issue
Block a user