mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): split control layers from raster layers for UI and internal state, same rendering as raster layers
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { Spacer } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
|
||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||
import { ControlLayerActionsMenu } from 'features/controlLayers/components/ControlLayer/ControlLayerActionsMenu';
|
||||
import { ControlLayerControlAdapter } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapter';
|
||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const ControlLayer = memo(({ id }: Props) => {
|
||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'control_layer' }), [id]);
|
||||
|
||||
return (
|
||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||
<CanvasEntityContainer>
|
||||
<CanvasEntityHeader>
|
||||
<CanvasEntityEnabledToggle />
|
||||
<CanvasEntityTitle />
|
||||
<Spacer />
|
||||
<ControlLayerActionsMenu />
|
||||
<CanvasEntityDeleteButton />
|
||||
</CanvasEntityHeader>
|
||||
<CanvasEntitySettingsWrapper>
|
||||
<ControlLayerControlAdapter />
|
||||
</CanvasEntitySettingsWrapper>
|
||||
</CanvasEntityContainer>
|
||||
</EntityIdentifierContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
ControlLayer.displayName = 'ControlLayer';
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Menu, MenuList } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const ControlLayerActionsMenu = memo(() => {
|
||||
return (
|
||||
<Menu>
|
||||
<CanvasEntityMenuButton />
|
||||
<MenuList>
|
||||
<CanvasEntityActionMenuItems />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
});
|
||||
|
||||
ControlLayerActionsMenu.displayName = 'ControlLayerActionsMenu';
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct';
|
||||
import { Weight } from 'features/controlLayers/components/common/Weight';
|
||||
import { ControlAdapterControlModeSelect } from 'features/controlLayers/components/ControlAdapter/ControlAdapterControlModeSelect';
|
||||
import { ControlAdapterModel } from 'features/controlLayers/components/ControlAdapter/ControlAdapterModel';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useControlLayerControlAdapter } from 'features/controlLayers/hooks/useLayerControlAdapter';
|
||||
import {
|
||||
controlLayerBeginEndStepPctChanged,
|
||||
controlLayerControlModeChanged,
|
||||
controlLayerModelChanged,
|
||||
controlLayerWeightChanged,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type { ControlModeV2 } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
||||
|
||||
export const ControlLayerControlAdapter = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const controlAdapter = useControlLayerControlAdapter(entityIdentifier);
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
(beginEndStepPct: [number, number]) => {
|
||||
dispatch(controlLayerBeginEndStepPctChanged({ id: entityIdentifier.id, beginEndStepPct }));
|
||||
},
|
||||
[dispatch, entityIdentifier.id]
|
||||
);
|
||||
|
||||
const onChangeControlMode = useCallback(
|
||||
(controlMode: ControlModeV2) => {
|
||||
dispatch(controlLayerControlModeChanged({ id: entityIdentifier.id, controlMode }));
|
||||
},
|
||||
[dispatch, entityIdentifier.id]
|
||||
);
|
||||
|
||||
const onChangeWeight = useCallback(
|
||||
(weight: number) => {
|
||||
dispatch(controlLayerWeightChanged({ id: entityIdentifier.id, weight }));
|
||||
},
|
||||
[dispatch, entityIdentifier.id]
|
||||
);
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => {
|
||||
dispatch(controlLayerModelChanged({ id: entityIdentifier.id, modelConfig }));
|
||||
},
|
||||
[dispatch, entityIdentifier.id]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap={3} position="relative" w="full">
|
||||
<ControlAdapterModel modelKey={controlAdapter.model?.key ?? null} onChange={onChangeModel} />
|
||||
<Weight weight={controlAdapter.weight} onChange={onChangeWeight} />
|
||||
<BeginEndStepPct beginEndStepPct={controlAdapter.beginEndStepPct} onChange={onChangeBeginEndStepPct} />
|
||||
{controlAdapter.type === 'controlnet' && (
|
||||
<ControlAdapterControlModeSelect controlMode={controlAdapter.controlMode} onChange={onChangeControlMode} />
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
ControlLayerControlAdapter.displayName = 'ControlLayerControlAdapter';
|
||||
@@ -0,0 +1,38 @@
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityGroupTitle } from 'features/controlLayers/components/common/CanvasEntityGroupTitle';
|
||||
import { ControlLayer } from 'features/controlLayers/components/ControlLayer/ControlLayer';
|
||||
import { mapId } from 'features/controlLayers/konva/util';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
return canvasV2.controlLayers.entities.map(mapId).reverse();
|
||||
});
|
||||
|
||||
export const ControlLayerEntityList = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'control_layer'));
|
||||
const layerIds = useAppSelector(selectEntityIds);
|
||||
|
||||
if (layerIds.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (layerIds.length > 0) {
|
||||
return (
|
||||
<>
|
||||
<CanvasEntityGroupTitle
|
||||
title={t('controlLayers.controlLayers_withCount', { count: layerIds.length })}
|
||||
isSelected={isSelected}
|
||||
/>
|
||||
{layerIds.map((id) => (
|
||||
<ControlLayer key={id} id={id} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ControlLayerEntityList.displayName = 'ControlLayerEntityList';
|
||||
Reference in New Issue
Block a user