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 { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
caDeleted,
|
||||
imReset,
|
||||
ipaDeleted,
|
||||
layerDeleted,
|
||||
layerReset,
|
||||
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
|
||||
);
|
||||
import { ButtonGroup } from '@invoke-ai/ui-library';
|
||||
import { BboxToolButton } from 'features/controlLayers/components/BboxToolButton';
|
||||
import { BrushToolButton } from 'features/controlLayers/components/BrushToolButton';
|
||||
import { EraserToolButton } from 'features/controlLayers/components/EraserToolButton';
|
||||
import { MoveToolButton } from 'features/controlLayers/components/MoveToolButton';
|
||||
import { RectToolButton } from 'features/controlLayers/components/RectToolButton';
|
||||
import { ViewToolButton } from 'features/controlLayers/components/ViewToolButton';
|
||||
import { useCanvasDeleteLayerHotkey } from 'features/controlLayers/hooks/useCanvasDeleteLayerHotkey';
|
||||
import { useCanvasResetLayerHotkey } from 'features/controlLayers/hooks/useCanvasResetLayerHotkey';
|
||||
|
||||
export const ToolChooser: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
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]);
|
||||
useCanvasResetLayerHotkey();
|
||||
useCanvasDeleteLayerHotkey();
|
||||
|
||||
return (
|
||||
<ButtonGroup isAttached>
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.brush')} (B)`}
|
||||
tooltip={`${t('unifiedCanvas.brush')} (B)`}
|
||||
icon={<PiPaintBrushBold />}
|
||||
variant={tool === 'brush' ? 'solid' : 'outline'}
|
||||
onClick={setToolToBrush}
|
||||
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}
|
||||
/>
|
||||
<BrushToolButton />
|
||||
<EraserToolButton />
|
||||
<RectToolButton />
|
||||
<MoveToolButton />
|
||||
<ViewToolButton />
|
||||
<BboxToolButton />
|
||||
</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 zInpaintMaskEntity = z.object({
|
||||
id: zId,
|
||||
id: z.literal('inpaint_mask'),
|
||||
type: z.literal('inpaint_mask'),
|
||||
isEnabled: z.boolean(),
|
||||
x: z.number(),
|
||||
@ -945,3 +945,9 @@ export function isDrawableEntityAdapter(
|
||||
): adapter is CanvasLayer | CanvasRegion | 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…
Reference in New Issue
Block a user