mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): split up tool chooser buttons
Prep for distinct toolbars for generation vs canvas modes
This commit is contained in:
parent
77acc7baed
commit
e4376e21dd
@ -0,0 +1,33 @@
|
|||||||
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiBoundingBoxBold } from 'react-icons/pi';
|
||||||
|
|
||||||
|
export const BboxToolButton = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isDisabled = useAppSelector((s) => s.canvasV2.session.isStaging);
|
||||||
|
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'bbox');
|
||||||
|
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
dispatch(toolChanged('bbox'));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
useHotkeys('q', onClick, [onClick]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
aria-label={`${t('controlLayers.bbox')} (Q)`}
|
||||||
|
tooltip={`${t('controlLayers.bbox')} (Q)`}
|
||||||
|
icon={<PiBoundingBoxBold />}
|
||||||
|
variant={isSelected ? 'solid' : 'outline'}
|
||||||
|
onClick={onClick}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
BboxToolButton.displayName = 'BboxToolButton';
|
@ -0,0 +1,39 @@
|
|||||||
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import { isDrawableEntityType } from 'features/controlLayers/store/types';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiPaintBrushBold } from 'react-icons/pi';
|
||||||
|
|
||||||
|
export const BrushToolButton = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'brush');
|
||||||
|
const isDisabled = useAppSelector((s) => {
|
||||||
|
const entityType = s.canvasV2.selectedEntityIdentifier?.type;
|
||||||
|
const isDrawingToolAllowed = entityType ? isDrawableEntityType(entityType) : false;
|
||||||
|
const isStaging = s.canvasV2.session.isStaging;
|
||||||
|
return !isDrawingToolAllowed || isStaging;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
dispatch(toolChanged('brush'));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
useHotkeys('b', onClick, { enabled: !isDisabled }, [isDisabled, onClick]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
aria-label={`${t('unifiedCanvas.brush')} (B)`}
|
||||||
|
tooltip={`${t('unifiedCanvas.brush')} (B)`}
|
||||||
|
icon={<PiPaintBrushBold />}
|
||||||
|
variant={isSelected ? 'solid' : 'outline'}
|
||||||
|
onClick={onClick}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
BrushToolButton.displayName = 'BrushToolButton';
|
@ -0,0 +1,39 @@
|
|||||||
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import { isDrawableEntityType } from 'features/controlLayers/store/types';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiEraserBold } from 'react-icons/pi';
|
||||||
|
|
||||||
|
export const EraserToolButton = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'eraser');
|
||||||
|
const isDisabled = useAppSelector((s) => {
|
||||||
|
const entityType = s.canvasV2.selectedEntityIdentifier?.type;
|
||||||
|
const isDrawingToolAllowed = entityType ? isDrawableEntityType(entityType) : false;
|
||||||
|
const isStaging = s.canvasV2.session.isStaging;
|
||||||
|
return !isDrawingToolAllowed || isStaging;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
dispatch(toolChanged('eraser'));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
useHotkeys('e', onClick, { enabled: !isDisabled }, [isDisabled, onClick]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
aria-label={`${t('unifiedCanvas.eraser')} (E)`}
|
||||||
|
tooltip={`${t('unifiedCanvas.eraser')} (E)`}
|
||||||
|
icon={<PiEraserBold />}
|
||||||
|
variant={isSelected ? 'solid' : 'outline'}
|
||||||
|
onClick={onClick}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
EraserToolButton.displayName = 'EraserToolButton';
|
@ -0,0 +1,35 @@
|
|||||||
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiCursorBold } from 'react-icons/pi';
|
||||||
|
|
||||||
|
export const MoveToolButton = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'move');
|
||||||
|
const isDisabled = useAppSelector(
|
||||||
|
(s) => s.canvasV2.selectedEntityIdentifier === null || s.canvasV2.session.isStaging
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
dispatch(toolChanged('move'));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
useHotkeys('v', onClick, { enabled: !isDisabled }, [isDisabled, onClick]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
aria-label={`${t('unifiedCanvas.move')} (V)`}
|
||||||
|
tooltip={`${t('unifiedCanvas.move')} (V)`}
|
||||||
|
icon={<PiCursorBold />}
|
||||||
|
variant={isSelected ? 'solid' : 'outline'}
|
||||||
|
onClick={onClick}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
MoveToolButton.displayName = 'MoveToolButton';
|
@ -0,0 +1,39 @@
|
|||||||
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import { isDrawableEntityType } from 'features/controlLayers/store/types';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiRectangleBold } from 'react-icons/pi';
|
||||||
|
|
||||||
|
export const RectToolButton = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'rect');
|
||||||
|
const isDisabled = useAppSelector((s) => {
|
||||||
|
const entityType = s.canvasV2.selectedEntityIdentifier?.type;
|
||||||
|
const isDrawingToolAllowed = entityType ? isDrawableEntityType(entityType) : false;
|
||||||
|
const isStaging = s.canvasV2.session.isStaging;
|
||||||
|
return !isDrawingToolAllowed || isStaging;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
dispatch(toolChanged('rect'));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
useHotkeys('u', onClick, { enabled: !isDisabled }, [isDisabled, onClick]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
aria-label={`${t('controlLayers.rectangle')} (U)`}
|
||||||
|
tooltip={`${t('controlLayers.rectangle')} (U)`}
|
||||||
|
icon={<PiRectangleBold />}
|
||||||
|
variant={isSelected ? 'solid' : 'outline'}
|
||||||
|
onClick={onClick}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
RectToolButton.displayName = 'RectToolButton';
|
@ -1,199 +1,25 @@
|
|||||||
import { ButtonGroup, IconButton } from '@invoke-ai/ui-library';
|
import { ButtonGroup } from '@invoke-ai/ui-library';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { BboxToolButton } from 'features/controlLayers/components/BboxToolButton';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { BrushToolButton } from 'features/controlLayers/components/BrushToolButton';
|
||||||
import {
|
import { EraserToolButton } from 'features/controlLayers/components/EraserToolButton';
|
||||||
caDeleted,
|
import { MoveToolButton } from 'features/controlLayers/components/MoveToolButton';
|
||||||
imReset,
|
import { RectToolButton } from 'features/controlLayers/components/RectToolButton';
|
||||||
ipaDeleted,
|
import { ViewToolButton } from 'features/controlLayers/components/ViewToolButton';
|
||||||
layerDeleted,
|
import { useCanvasDeleteLayerHotkey } from 'features/controlLayers/hooks/useCanvasDeleteLayerHotkey';
|
||||||
layerReset,
|
import { useCanvasResetLayerHotkey } from 'features/controlLayers/hooks/useCanvasResetLayerHotkey';
|
||||||
rgDeleted,
|
|
||||||
rgReset,
|
|
||||||
selectCanvasV2Slice,
|
|
||||||
toolChanged,
|
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
|
||||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import {
|
|
||||||
PiBoundingBoxBold,
|
|
||||||
PiCursorBold,
|
|
||||||
PiEraserBold,
|
|
||||||
PiHandBold,
|
|
||||||
PiPaintBrushBold,
|
|
||||||
PiRectangleBold,
|
|
||||||
} from 'react-icons/pi';
|
|
||||||
|
|
||||||
const DRAWING_TOOL_TYPES = ['layer', 'regional_guidance', 'inpaint_mask'];
|
|
||||||
|
|
||||||
const getIsDrawingToolEnabled = (entityIdentifier: CanvasEntityIdentifier | null) => {
|
|
||||||
if (!entityIdentifier) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return DRAWING_TOOL_TYPES.includes(entityIdentifier.type);
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectSelectedEntityIdentifier = createMemoizedSelector(
|
|
||||||
selectCanvasV2Slice,
|
|
||||||
(canvasV2State) => canvasV2State.selectedEntityIdentifier
|
|
||||||
);
|
|
||||||
|
|
||||||
export const ToolChooser: React.FC = () => {
|
export const ToolChooser: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
useCanvasResetLayerHotkey();
|
||||||
const dispatch = useAppDispatch();
|
useCanvasDeleteLayerHotkey();
|
||||||
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
|
||||||
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging);
|
|
||||||
const isDrawingToolDisabled = useMemo(
|
|
||||||
() => !getIsDrawingToolEnabled(selectedEntityIdentifier),
|
|
||||||
[selectedEntityIdentifier]
|
|
||||||
);
|
|
||||||
const isMoveToolDisabled = useMemo(() => selectedEntityIdentifier === null, [selectedEntityIdentifier]);
|
|
||||||
const tool = useAppSelector((s) => s.canvasV2.tool.selected);
|
|
||||||
|
|
||||||
const setToolToBrush = useCallback(() => {
|
|
||||||
dispatch(toolChanged('brush'));
|
|
||||||
}, [dispatch]);
|
|
||||||
useHotkeys('b', setToolToBrush, { enabled: !isDrawingToolDisabled && !isStaging }, [
|
|
||||||
isDrawingToolDisabled,
|
|
||||||
isStaging,
|
|
||||||
setToolToBrush,
|
|
||||||
]);
|
|
||||||
const setToolToEraser = useCallback(() => {
|
|
||||||
dispatch(toolChanged('eraser'));
|
|
||||||
}, [dispatch]);
|
|
||||||
useHotkeys('e', setToolToEraser, { enabled: !isDrawingToolDisabled && !isStaging }, [
|
|
||||||
isDrawingToolDisabled,
|
|
||||||
isStaging,
|
|
||||||
setToolToEraser,
|
|
||||||
]);
|
|
||||||
const setToolToRect = useCallback(() => {
|
|
||||||
dispatch(toolChanged('rect'));
|
|
||||||
}, [dispatch]);
|
|
||||||
useHotkeys('u', setToolToRect, { enabled: !isDrawingToolDisabled && !isStaging }, [
|
|
||||||
isDrawingToolDisabled,
|
|
||||||
isStaging,
|
|
||||||
setToolToRect,
|
|
||||||
]);
|
|
||||||
const setToolToMove = useCallback(() => {
|
|
||||||
dispatch(toolChanged('move'));
|
|
||||||
}, [dispatch]);
|
|
||||||
useHotkeys('v', setToolToMove, { enabled: !isMoveToolDisabled && !isStaging }, [
|
|
||||||
isMoveToolDisabled,
|
|
||||||
isStaging,
|
|
||||||
setToolToMove,
|
|
||||||
]);
|
|
||||||
const setToolToView = useCallback(() => {
|
|
||||||
dispatch(toolChanged('view'));
|
|
||||||
}, [dispatch]);
|
|
||||||
useHotkeys('h', setToolToView, [setToolToView]);
|
|
||||||
const setToolToBbox = useCallback(() => {
|
|
||||||
dispatch(toolChanged('bbox'));
|
|
||||||
}, [dispatch]);
|
|
||||||
useHotkeys('q', setToolToBbox, [setToolToBbox]);
|
|
||||||
|
|
||||||
const resetSelectedLayer = useCallback(() => {
|
|
||||||
if (selectedEntityIdentifier === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { type, id } = selectedEntityIdentifier;
|
|
||||||
if (type === 'layer') {
|
|
||||||
dispatch(layerReset({ id }));
|
|
||||||
}
|
|
||||||
if (type === 'regional_guidance') {
|
|
||||||
dispatch(rgReset({ id }));
|
|
||||||
}
|
|
||||||
if (type === 'inpaint_mask') {
|
|
||||||
dispatch(imReset());
|
|
||||||
}
|
|
||||||
}, [dispatch, selectedEntityIdentifier]);
|
|
||||||
const isResetEnabled = useMemo(
|
|
||||||
() =>
|
|
||||||
(!isStaging && selectedEntityIdentifier?.type === 'layer') ||
|
|
||||||
selectedEntityIdentifier?.type === 'regional_guidance' ||
|
|
||||||
selectedEntityIdentifier?.type === 'inpaint_mask',
|
|
||||||
[isStaging, selectedEntityIdentifier?.type]
|
|
||||||
);
|
|
||||||
useHotkeys('shift+c', resetSelectedLayer, { enabled: isResetEnabled }, [
|
|
||||||
isResetEnabled,
|
|
||||||
isStaging,
|
|
||||||
resetSelectedLayer,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const deleteSelectedLayer = useCallback(() => {
|
|
||||||
if (selectedEntityIdentifier === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { type, id } = selectedEntityIdentifier;
|
|
||||||
if (type === 'layer') {
|
|
||||||
dispatch(layerDeleted({ id }));
|
|
||||||
}
|
|
||||||
if (type === 'regional_guidance') {
|
|
||||||
dispatch(rgDeleted({ id }));
|
|
||||||
}
|
|
||||||
if (type === 'control_adapter') {
|
|
||||||
dispatch(caDeleted({ id }));
|
|
||||||
}
|
|
||||||
if (type === 'ip_adapter') {
|
|
||||||
dispatch(ipaDeleted({ id }));
|
|
||||||
}
|
|
||||||
}, [dispatch, selectedEntityIdentifier]);
|
|
||||||
const isDeleteEnabled = useMemo(
|
|
||||||
() => selectedEntityIdentifier !== null && !isStaging,
|
|
||||||
[selectedEntityIdentifier, isStaging]
|
|
||||||
);
|
|
||||||
useHotkeys('shift+d', deleteSelectedLayer, { enabled: isDeleteEnabled }, [isDeleteEnabled, deleteSelectedLayer]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup isAttached>
|
<ButtonGroup isAttached>
|
||||||
<IconButton
|
<BrushToolButton />
|
||||||
aria-label={`${t('unifiedCanvas.brush')} (B)`}
|
<EraserToolButton />
|
||||||
tooltip={`${t('unifiedCanvas.brush')} (B)`}
|
<RectToolButton />
|
||||||
icon={<PiPaintBrushBold />}
|
<MoveToolButton />
|
||||||
variant={tool === 'brush' ? 'solid' : 'outline'}
|
<ViewToolButton />
|
||||||
onClick={setToolToBrush}
|
<BboxToolButton />
|
||||||
isDisabled={isDrawingToolDisabled || isStaging}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
aria-label={`${t('unifiedCanvas.eraser')} (E)`}
|
|
||||||
tooltip={`${t('unifiedCanvas.eraser')} (E)`}
|
|
||||||
icon={<PiEraserBold />}
|
|
||||||
variant={tool === 'eraser' ? 'solid' : 'outline'}
|
|
||||||
onClick={setToolToEraser}
|
|
||||||
isDisabled={isDrawingToolDisabled || isStaging}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
aria-label={`${t('controlLayers.rectangle')} (U)`}
|
|
||||||
tooltip={`${t('controlLayers.rectangle')} (U)`}
|
|
||||||
icon={<PiRectangleBold />}
|
|
||||||
variant={tool === 'rect' ? 'solid' : 'outline'}
|
|
||||||
onClick={setToolToRect}
|
|
||||||
isDisabled={isDrawingToolDisabled || isStaging}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
aria-label={`${t('unifiedCanvas.move')} (V)`}
|
|
||||||
tooltip={`${t('unifiedCanvas.move')} (V)`}
|
|
||||||
icon={<PiCursorBold />}
|
|
||||||
variant={tool === 'move' ? 'solid' : 'outline'}
|
|
||||||
onClick={setToolToMove}
|
|
||||||
isDisabled={isMoveToolDisabled || isStaging}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
aria-label={`${t('unifiedCanvas.view')} (H)`}
|
|
||||||
tooltip={`${t('unifiedCanvas.view')} (H)`}
|
|
||||||
icon={<PiHandBold />}
|
|
||||||
variant={tool === 'view' ? 'solid' : 'outline'}
|
|
||||||
onClick={setToolToView}
|
|
||||||
isDisabled={isStaging}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
aria-label={`${t('controlLayers.bbox')} (Q)`}
|
|
||||||
tooltip={`${t('controlLayers.bbox')} (Q)`}
|
|
||||||
icon={<PiBoundingBoxBold />}
|
|
||||||
variant={tool === 'bbox' ? 'solid' : 'outline'}
|
|
||||||
onClick={setToolToBbox}
|
|
||||||
isDisabled={isStaging}
|
|
||||||
/>
|
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiHandBold } from 'react-icons/pi';
|
||||||
|
|
||||||
|
export const ViewToolButton = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'view');
|
||||||
|
const isDisabled = useAppSelector((s) => s.canvasV2.session.isStaging);
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
dispatch(toolChanged('view'));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
useHotkeys('h', onClick, [onClick]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
aria-label={`${t('unifiedCanvas.view')} (H)`}
|
||||||
|
tooltip={`${t('unifiedCanvas.view')} (H)`}
|
||||||
|
icon={<PiHandBold />}
|
||||||
|
variant={isSelected ? 'solid' : 'outline'}
|
||||||
|
onClick={onClick}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ViewToolButton.displayName = 'ViewToolButton';
|
@ -0,0 +1,50 @@
|
|||||||
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||||
|
import {
|
||||||
|
caDeleted,
|
||||||
|
ipaDeleted,
|
||||||
|
layerDeleted,
|
||||||
|
rgDeleted,
|
||||||
|
selectCanvasV2Slice,
|
||||||
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
|
const selectSelectedEntityIdentifier = createMemoizedSelector(
|
||||||
|
selectCanvasV2Slice,
|
||||||
|
(canvasV2State) => canvasV2State.selectedEntityIdentifier
|
||||||
|
);
|
||||||
|
|
||||||
|
export function useCanvasDeleteLayerHotkey() {
|
||||||
|
useAssertSingleton(useCanvasDeleteLayerHotkey.name);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
||||||
|
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging);
|
||||||
|
|
||||||
|
const deleteSelectedLayer = useCallback(() => {
|
||||||
|
if (selectedEntityIdentifier === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { type, id } = selectedEntityIdentifier;
|
||||||
|
if (type === 'layer') {
|
||||||
|
dispatch(layerDeleted({ id }));
|
||||||
|
}
|
||||||
|
if (type === 'regional_guidance') {
|
||||||
|
dispatch(rgDeleted({ id }));
|
||||||
|
}
|
||||||
|
if (type === 'control_adapter') {
|
||||||
|
dispatch(caDeleted({ id }));
|
||||||
|
}
|
||||||
|
if (type === 'ip_adapter') {
|
||||||
|
dispatch(ipaDeleted({ id }));
|
||||||
|
}
|
||||||
|
}, [dispatch, selectedEntityIdentifier]);
|
||||||
|
|
||||||
|
const isDeleteEnabled = useMemo(
|
||||||
|
() => selectedEntityIdentifier !== null && !isStaging,
|
||||||
|
[selectedEntityIdentifier, isStaging]
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys('shift+d', deleteSelectedLayer, { enabled: isDeleteEnabled }, [isDeleteEnabled, deleteSelectedLayer]);
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||||
|
import {
|
||||||
|
imReset,
|
||||||
|
layerReset,
|
||||||
|
rgReset,
|
||||||
|
selectCanvasV2Slice,
|
||||||
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
|
const selectSelectedEntityIdentifier = createMemoizedSelector(
|
||||||
|
selectCanvasV2Slice,
|
||||||
|
(canvasV2State) => canvasV2State.selectedEntityIdentifier
|
||||||
|
);
|
||||||
|
|
||||||
|
export function useCanvasResetLayerHotkey() {
|
||||||
|
useAssertSingleton(useCanvasResetLayerHotkey.name);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
||||||
|
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging);
|
||||||
|
|
||||||
|
const resetSelectedLayer = useCallback(() => {
|
||||||
|
if (selectedEntityIdentifier === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { type, id } = selectedEntityIdentifier;
|
||||||
|
if (type === 'layer') {
|
||||||
|
dispatch(layerReset({ id }));
|
||||||
|
}
|
||||||
|
if (type === 'regional_guidance') {
|
||||||
|
dispatch(rgReset({ id }));
|
||||||
|
}
|
||||||
|
if (type === 'inpaint_mask') {
|
||||||
|
dispatch(imReset());
|
||||||
|
}
|
||||||
|
}, [dispatch, selectedEntityIdentifier]);
|
||||||
|
|
||||||
|
const isResetEnabled = useMemo(
|
||||||
|
() =>
|
||||||
|
(!isStaging && selectedEntityIdentifier?.type === 'layer') ||
|
||||||
|
selectedEntityIdentifier?.type === 'regional_guidance' ||
|
||||||
|
selectedEntityIdentifier?.type === 'inpaint_mask',
|
||||||
|
[isStaging, selectedEntityIdentifier?.type]
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys('shift+c', resetSelectedLayer, { enabled: isResetEnabled }, [
|
||||||
|
isResetEnabled,
|
||||||
|
isStaging,
|
||||||
|
resetSelectedLayer,
|
||||||
|
]);
|
||||||
|
}
|
@ -655,7 +655,7 @@ const zImageFill = z.object({
|
|||||||
});
|
});
|
||||||
const zFill = z.discriminatedUnion('type', [zColorFill, zImageFill]);
|
const zFill = z.discriminatedUnion('type', [zColorFill, zImageFill]);
|
||||||
const zInpaintMaskEntity = z.object({
|
const zInpaintMaskEntity = z.object({
|
||||||
id: zId,
|
id: z.literal('inpaint_mask'),
|
||||||
type: z.literal('inpaint_mask'),
|
type: z.literal('inpaint_mask'),
|
||||||
isEnabled: z.boolean(),
|
isEnabled: z.boolean(),
|
||||||
x: z.number(),
|
x: z.number(),
|
||||||
@ -945,3 +945,9 @@ export function isDrawableEntityAdapter(
|
|||||||
): adapter is CanvasLayer | CanvasRegion | CanvasInpaintMask {
|
): adapter is CanvasLayer | CanvasRegion | CanvasInpaintMask {
|
||||||
return adapter instanceof CanvasLayer || adapter instanceof CanvasRegion || adapter instanceof CanvasInpaintMask;
|
return adapter instanceof CanvasLayer || adapter instanceof CanvasRegion || adapter instanceof CanvasInpaintMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isDrawableEntityType(
|
||||||
|
entityType: CanvasEntity['type']
|
||||||
|
): entityType is 'layer' | 'regional_guidance' | 'inpaint_mask' {
|
||||||
|
return entityType === 'layer' || entityType === 'regional_guidance' || entityType === 'inpaint_mask';
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user