Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion invokeai/frontend/web/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,10 @@
"title": "Shapes Tool",
"desc": "Select the shapes tool."
},
"selectPathTool": {
"title": "Path Tool",
"desc": "Select the path tool."
},
"selectLassoTool": {
"title": "Lasso Tool",
"desc": "Select the lasso tool."
Expand Down Expand Up @@ -2707,12 +2711,14 @@
"addImageNoise": "Add $t(controlLayers.imageNoise)",
"addRasterLayer": "Add $t(controlLayers.rasterLayer)",
"addControlLayer": "Add $t(controlLayers.controlLayer)",
"addVectorLayer": "Add $t(controlLayers.vectorLayer)",
"addInpaintMask": "Add $t(controlLayers.inpaintMask)",
"addRegionalGuidance": "Add $t(controlLayers.regionalGuidance)",
"addGlobalReferenceImage": "Add $t(controlLayers.globalReferenceImage)",
"addDenoiseLimit": "Add $t(controlLayers.denoiseLimit)",
"rasterLayer": "Raster Layer",
"controlLayer": "Control Layer",
"vectorLayer": "Vector Layer",
"inpaintMask": "Inpaint Mask",
"invertMask": "Invert Mask",
"invertRegion": "Invert Region",
Expand Down Expand Up @@ -2763,6 +2769,8 @@
"rasterLayer_withCount_other": "Raster Layers",
"controlLayer_withCount_one": "$t(controlLayers.controlLayer)",
"controlLayer_withCount_other": "Control Layers",
"vectorLayer_withCount_one": "$t(controlLayers.vectorLayer)",
"vectorLayer_withCount_other": "Vector Layers",
"inpaintMask_withCount_one": "$t(controlLayers.inpaintMask)",
"inpaintMask_withCount_other": "Inpaint Masks",
"regionalGuidance_withCount_one": "$t(controlLayers.regionalGuidance)",
Expand All @@ -2772,11 +2780,13 @@
"opacity": "Opacity",
"regionalGuidance_withCount_hidden": "Regional Guidance ({{count}} hidden)",
"controlLayers_withCount_hidden": "Control Layers ({{count}} hidden)",
"vectorLayers_withCount_hidden": "Vector Layers ({{count}} hidden)",
"rasterLayers_withCount_hidden": "Raster Layers ({{count}} hidden)",
"globalReferenceImages_withCount_hidden": "Global Reference Images ({{count}} hidden)",
"inpaintMasks_withCount_hidden": "Inpaint Masks ({{count}} hidden)",
"regionalGuidance_withCount_visible": "Regional Guidance ({{count}})",
"controlLayers_withCount_visible": "Control Layers ({{count}})",
"vectorLayers_withCount_visible": "Vector Layers ({{count}})",
"rasterLayers_withCount_visible": "Raster Layers ({{count}})",
"globalReferenceImages_withCount_visible": "Global Reference Images ({{count}})",
"inpaintMasks_withCount_visible": "Inpaint Masks ({{count}})",
Expand All @@ -2794,6 +2804,7 @@
"copyRegionalGuidanceTo": "Copy $t(controlLayers.regionalGuidance) To",
"newRasterLayer": "New $t(controlLayers.rasterLayer)",
"newControlLayer": "New $t(controlLayers.controlLayer)",
"newVectorLayer": "New $t(controlLayers.vectorLayer)",
"newInpaintMask": "New $t(controlLayers.inpaintMask)",
"newRegionalGuidance": "New $t(controlLayers.regionalGuidance)",
"pasteTo": "Paste To",
Expand Down Expand Up @@ -2908,6 +2919,11 @@
"radial": "Radial",
"clip": "Clip Gradient"
},
"vectorEdit": {
"title": "Edit Vector Layer",
"discard": "Discard",
"traceAll": "Trace All Paths"
},
"lasso": {
"freehand": "Freehand",
"polygon": "Polygon",
Expand Down Expand Up @@ -2944,17 +2960,24 @@
"scaleFromCenter": "Scale from center",
"fineGrid": "Fine grid",
"commitText": "Commit",
"finishPath": "Finish path",
"acceptPathEdit": "Accept edits",
"newLine": "New line",
"cancelText": "Cancel",
"cancelPath": "Cancel path",
"discardPathEdit": "Discard edits",
"dragText": "Drag text",
"snapRotation": "Snap rotation",
"nudgeSelection": "Nudge selection"
"nudgeSelection": "Nudge selection",
"insertPathPoint": "Insert point",
"deletePathPoint": "Delete point"
}
},
"tool": {
"brush": "Brush",
"eraser": "Eraser",
"shapes": "Shapes",
"path": "Path",
"rectangle": "Rectangle",
"lasso": "Lasso",
"gradient": "Gradient",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useAddNewRegionalGuidanceWithARefImage,
useAddRasterLayer,
useAddRegionalGuidance,
useAddVectorLayer,
} from 'features/controlLayers/hooks/addLayerHooks';
import { useIsEntityTypeEnabled } from 'features/controlLayers/hooks/useIsEntityTypeEnabled';
import { memo } from 'react';
Expand All @@ -18,10 +19,12 @@ export const CanvasAddEntityButtons = memo(() => {
const addRegionalGuidance = useAddRegionalGuidance();
const addRasterLayer = useAddRasterLayer();
const addControlLayer = useAddControlLayer();
const addVectorLayer = useAddVectorLayer();
const addRegionalReferenceImage = useAddNewRegionalGuidanceWithARefImage();
const isRegionalGuidanceEnabled = useIsEntityTypeEnabled('regional_guidance');
const isControlLayerEnabled = useIsEntityTypeEnabled('control_layer');
const isInpaintLayerEnabled = useIsEntityTypeEnabled('inpaint_mask');
const isVectorLayerEnabled = useIsEntityTypeEnabled('vector_layer');

return (
<Flex w="full" h="full" justifyContent="center" gap={4}>
Expand Down Expand Up @@ -79,6 +82,16 @@ export const CanvasAddEntityButtons = memo(() => {
{t('controlLayers.controlLayer')}
</Button>
</InformationalPopover>
<Button
size="sm"
variant="ghost"
justifyContent="flex-start"
leftIcon={<PiPlusBold />}
onClick={addVectorLayer}
isDisabled={!isVectorLayerEnabled}
>
{t('controlLayers.vectorLayer')}
</Button>
<InformationalPopover feature="rasterLayer">
<Button
size="sm"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { InpaintMaskMenuItems } from 'features/controlLayers/components/InpaintM
import { RasterLayerMenuItems } from 'features/controlLayers/components/RasterLayer/RasterLayerMenuItems';
import { IPAdapterMenuItems } from 'features/controlLayers/components/RefImage/IPAdapterMenuItems';
import { RegionalGuidanceMenuItems } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItems';
import { VectorLayerMenuItems } from 'features/controlLayers/components/VectorLayer/VectorLayerMenuItems';
import { CanvasEntityStateGate } from 'features/controlLayers/contexts/CanvasEntityStateGate';
import {
EntityIdentifierContext,
Expand All @@ -29,6 +30,9 @@ const CanvasContextMenuSelectedEntityMenuItemsContent = memo(() => {
if (entityIdentifier.type === 'inpaint_mask') {
return <InpaintMaskMenuItems />;
}
if (entityIdentifier.type === 'vector_layer') {
return <VectorLayerMenuItems />;
}
if (entityIdentifier.type === 'regional_guidance') {
return <RegionalGuidanceMenuItems />;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export const CanvasEntityGroupList = memo(({ isSelected, type, children, entityI
<Spacer />
</Flex>
{type === 'raster_layer' && <RasterLayerExportPSDButton />}
<CanvasEntityMergeVisibleButton type={type} />
{type !== 'vector_layer' && <CanvasEntityMergeVisibleButton type={type} />}
<CanvasEntityTypeIsHiddenToggle type={type} />
<CanvasEntityAddOfTypeButton type={type} />
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ControlLayerEntityList } from 'features/controlLayers/components/Contro
import { InpaintMaskList } from 'features/controlLayers/components/InpaintMask/InpaintMaskList';
import { RasterLayerEntityList } from 'features/controlLayers/components/RasterLayer/RasterLayerEntityList';
import { RegionalGuidanceEntityList } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceEntityList';
import { VectorLayerEntityList } from 'features/controlLayers/components/VectorLayer/VectorLayerEntityList';
import { memo } from 'react';

export const CanvasEntityList = memo(() => {
Expand All @@ -12,6 +13,7 @@ export const CanvasEntityList = memo(() => {
<Flex flexDir="column" gap={2} data-testid="control-layers-layer-list" w="full" h="full">
<InpaintMaskList />
<RegionalGuidanceEntityList />
<VectorLayerEntityList />
<ControlLayerEntityList />
<RasterLayerEntityList />
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
useAddNewRegionalGuidanceWithARefImage,
useAddRasterLayer,
useAddRegionalGuidance,
useAddVectorLayer,
} from 'features/controlLayers/hooks/addLayerHooks';
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
import { useIsEntityTypeEnabled } from 'features/controlLayers/hooks/useIsEntityTypeEnabled';
Expand All @@ -20,9 +21,11 @@ export const EntityListGlobalActionBarAddLayerMenu = memo(() => {
const addRegionalReferenceImage = useAddNewRegionalGuidanceWithARefImage();
const addRasterLayer = useAddRasterLayer();
const addControlLayer = useAddControlLayer();
const addVectorLayer = useAddVectorLayer();
const isRegionalGuidanceEnabled = useIsEntityTypeEnabled('regional_guidance');
const isControlLayerEnabled = useIsEntityTypeEnabled('control_layer');
const isInpaintLayerEnabled = useIsEntityTypeEnabled('inpaint_mask');
const isVectorLayerEnabled = useIsEntityTypeEnabled('vector_layer');

return (
<Menu>
Expand Down Expand Up @@ -53,6 +56,9 @@ export const EntityListGlobalActionBarAddLayerMenu = memo(() => {
<MenuItem icon={<PiPlusBold />} onClick={addControlLayer} isDisabled={!isControlLayerEnabled}>
{t('controlLayers.controlLayer')}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addVectorLayer} isDisabled={!isVectorLayerEnabled}>
{t('controlLayers.vectorLayer')}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addRasterLayer}>
{t('controlLayers.rasterLayer')}
</MenuItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ToolColorPickerButton } from 'features/controlLayers/components/Tool/To
import { ToolGradientButton } from 'features/controlLayers/components/Tool/ToolGradientButton';
import { ToolLassoButton } from 'features/controlLayers/components/Tool/ToolLassoButton';
import { ToolMoveButton } from 'features/controlLayers/components/Tool/ToolMoveButton';
import { ToolPathButton } from 'features/controlLayers/components/Tool/ToolPathButton';
import { ToolShapesButton } from 'features/controlLayers/components/Tool/ToolShapesButton';
import { ToolTextButton } from 'features/controlLayers/components/Tool/ToolTextButton';
import React from 'react';
Expand All @@ -19,6 +20,7 @@ export const ToolChooser: React.FC = () => {
<ToolBrushButton />
<ToolEraserButton />
<ToolShapesButton />
<ToolPathButton />
<ToolGradientButton />
<ToolTextButton />
<ToolLassoButton />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { IconButton, Tooltip } from '@invoke-ai/ui-library';
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiBezierCurveBold } from 'react-icons/pi';

export const ToolPathButton = memo(() => {
const { t } = useTranslation();
const isSelected = useToolIsSelected('path');
const selectPath = useSelectTool('path');
const label = t('controlLayers.tool.path', { defaultValue: 'Path' });

useRegisteredHotkeys({
id: 'selectPathTool',
category: 'canvas',
callback: selectPath,
options: { enabled: !isSelected },
dependencies: [isSelected, selectPath],
});

return (
<Tooltip label={`${label} (P)`} placement="end">
<IconButton
aria-label={`${label} (P)`}
icon={<PiBezierCurveBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="solid"
onClick={selectPath}
/>
</Tooltip>
);
});

ToolPathButton.displayName = 'ToolPathButton';
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Spacer } from '@invoke-ai/ui-library';
import { CanvasEntityContainer } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityContainer';
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
import { CanvasEntityHeaderCommonActions } from 'features/controlLayers/components/common/CanvasEntityHeaderCommonActions';
import { CanvasEntityPreviewImage } from 'features/controlLayers/components/common/CanvasEntityPreviewImage';
import { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
import { CanvasEntityStateGate } from 'features/controlLayers/contexts/CanvasEntityStateGate';
import { VectorLayerAdapterGate } from 'features/controlLayers/contexts/EntityAdapterContext';
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 VectorLayer = memo(({ id }: Props) => {
const entityIdentifier = useMemo<CanvasEntityIdentifier<'vector_layer'>>(() => ({ id, type: 'vector_layer' }), [id]);

return (
<EntityIdentifierContext.Provider value={entityIdentifier}>
<VectorLayerAdapterGate>
<CanvasEntityStateGate entityIdentifier={entityIdentifier}>
<CanvasEntityContainer>
<CanvasEntityHeader>
<CanvasEntityPreviewImage />
<CanvasEntityEditableTitle />
<Spacer />
<CanvasEntityHeaderCommonActions />
</CanvasEntityHeader>
</CanvasEntityContainer>
</CanvasEntityStateGate>
</VectorLayerAdapterGate>
</EntityIdentifierContext.Provider>
);
});

VectorLayer.displayName = 'VectorLayer';
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Button, ButtonGroup, Flex, Heading, Spacer } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';

export const VectorLayerEditFooter = memo(() => {
const { t } = useTranslation();
const canvasManager = useCanvasManager();
const editSession = useStore(canvasManager.tool.tools.path.$editSession);

if (!editSession) {
return null;
}

return (
<Flex bg="base.800" borderRadius="base" p={4} minW={420} flexDir="column" gap={4} shadow="dark-lg">
<Heading size="md" color="base.300" userSelect="none">
{t('controlLayers.vectorEdit.title')}
</Heading>
<ButtonGroup isAttached={false} size="sm" w="full" alignItems="center">
<Spacer />
<Button onClick={canvasManager.tool.tools.path.acceptEditSession} variant="ghost">
{t('common.accept')}
</Button>
<Button onClick={canvasManager.tool.tools.path.cancel} variant="ghost">
{t('controlLayers.vectorEdit.discard')}
</Button>
</ButtonGroup>
</Flex>
);
});

VectorLayerEditFooter.displayName = 'VectorLayerEditFooter';
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntityGroupList } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityGroupList';
import { VectorLayer } from 'features/controlLayers/components/VectorLayer/VectorLayer';
import { selectCanvasSlice, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
import { getEntityIdentifier } from 'features/controlLayers/store/types';
import { memo } from 'react';

const selectEntityIdentifiers = createMemoizedSelector(selectCanvasSlice, (canvas) => {
return canvas.vectorLayers.entities.map(getEntityIdentifier).toReversed();
});
const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => {
return selectedEntityIdentifier?.type === 'vector_layer';
});

export const VectorLayerEntityList = memo(() => {
const isSelected = useAppSelector(selectIsSelected);
const entityIdentifiers = useAppSelector(selectEntityIdentifiers);

if (entityIdentifiers.length === 0) {
return null;
}

return (
<CanvasEntityGroupList type="vector_layer" isSelected={isSelected} entityIdentifiers={entityIdentifiers}>
{entityIdentifiers.map((entityIdentifier) => (
<VectorLayer key={entityIdentifier.id} id={entityIdentifier.id} />
))}
</CanvasEntityGroupList>
);
});

VectorLayerEntityList.displayName = 'VectorLayerEntityList';
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { IconMenuItemGroup } from 'common/components/IconMenuItem';
import { CanvasEntityMenuItemsArrange } from 'features/controlLayers/components/common/CanvasEntityMenuItemsArrange';
import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDelete';
import { CanvasEntityMenuItemsDuplicate } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDuplicate';
import { VectorLayerMenuItemsEdit } from 'features/controlLayers/components/VectorLayer/VectorLayerMenuItemsEdit';
import { VectorLayerMenuItemsTraceAll } from 'features/controlLayers/components/VectorLayer/VectorLayerMenuItemsTraceAll';
import { memo } from 'react';

export const VectorLayerMenuItems = memo(() => {
return (
<>
<VectorLayerMenuItemsEdit />
<VectorLayerMenuItemsTraceAll />
<IconMenuItemGroup>
<CanvasEntityMenuItemsArrange />
<CanvasEntityMenuItemsDuplicate />
<CanvasEntityMenuItemsDelete asIcon />
</IconMenuItemGroup>
</>
);
});

VectorLayerMenuItems.displayName = 'VectorLayerMenuItems';
Loading
Loading