mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): add alt+[ and alt+] hotkeys to cycle through layers
This commit is contained in:
@@ -10,6 +10,7 @@ import { CanvasToolbarScale } from 'features/controlLayers/components/Toolbar/Ca
|
||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { useCanvasEntityQuickSwitch } from 'features/controlLayers/hooks/useCanvasEntityQuickSwitch';
|
||||
import { useCanvasUndoRedo } from 'features/controlLayers/hooks/useCanvasUndoRedo';
|
||||
import { useNextPrevEntityHotkeys } from 'features/controlLayers/hooks/useNextPrevEntity';
|
||||
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
|
||||
import { ViewerToggle } from 'features/gallery/components/ImageViewer/ViewerToggleMenu';
|
||||
import { memo } from 'react';
|
||||
@@ -17,6 +18,7 @@ import { memo } from 'react';
|
||||
export const CanvasToolbar = memo(() => {
|
||||
useCanvasUndoRedo();
|
||||
useCanvasEntityQuickSwitch();
|
||||
useNextPrevEntityHotkeys();
|
||||
|
||||
return (
|
||||
<CanvasManagerProviderGate>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable i18next/no-literal-string */
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import { canvasRedo, canvasUndo } from 'features/controlLayers/store/canvasSlice';
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import { entitySelected } from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectAllEntities, selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
||||
import type { CanvasEntityState } from 'features/controlLayers/store/types';
|
||||
import { getEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { useCallback } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
const selectNextEntityIdentifier = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
const selectedEntityIdentifier = canvas.selectedEntityIdentifier;
|
||||
const allEntities = selectAllEntities(canvas);
|
||||
let nextEntity: CanvasEntityState | null = null;
|
||||
if (!selectedEntityIdentifier) {
|
||||
nextEntity = allEntities[0] ?? null;
|
||||
} else {
|
||||
const selectedEntityIndex = allEntities.findIndex((entity) => entity.id === selectedEntityIdentifier.id);
|
||||
nextEntity = allEntities[(selectedEntityIndex + 1) % allEntities.length] ?? null;
|
||||
}
|
||||
if (!nextEntity) {
|
||||
return null;
|
||||
}
|
||||
return getEntityIdentifier(nextEntity);
|
||||
});
|
||||
|
||||
const selectPrevEntityIdentifier = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
const selectedEntityIdentifier = canvas.selectedEntityIdentifier;
|
||||
const allEntities = selectAllEntities(canvas);
|
||||
let prevEntity: CanvasEntityState | null = null;
|
||||
if (!selectedEntityIdentifier) {
|
||||
prevEntity = allEntities[0] ?? null;
|
||||
} else {
|
||||
const selectedEntityIndex = allEntities.findIndex((entity) => entity.id === selectedEntityIdentifier.id);
|
||||
prevEntity = allEntities[(selectedEntityIndex - 1 + allEntities.length) % allEntities.length] ?? null;
|
||||
}
|
||||
if (!prevEntity) {
|
||||
return null;
|
||||
}
|
||||
return getEntityIdentifier(prevEntity);
|
||||
});
|
||||
|
||||
export const useNextPrevEntityHotkeys = () => {
|
||||
useAssertSingleton('useNextPrevEntityHotkeys');
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const nextEntityIdentifier = useAppSelector(selectNextEntityIdentifier);
|
||||
const prevEntityIdentifier = useAppSelector(selectPrevEntityIdentifier);
|
||||
|
||||
const selectNextEntity = useCallback(() => {
|
||||
if (nextEntityIdentifier) {
|
||||
dispatch(entitySelected({ entityIdentifier: nextEntityIdentifier }));
|
||||
}
|
||||
}, [dispatch, nextEntityIdentifier]);
|
||||
|
||||
const selectPrevEntity = useCallback(() => {
|
||||
if (prevEntityIdentifier) {
|
||||
dispatch(entitySelected({ entityIdentifier: prevEntityIdentifier }));
|
||||
}
|
||||
}, [dispatch, prevEntityIdentifier]);
|
||||
|
||||
useHotkeys(
|
||||
// “ === alt+[
|
||||
['alt+[', '“'],
|
||||
selectPrevEntity,
|
||||
{ preventDefault: true, ignoreModifiers: true },
|
||||
[selectPrevEntity]
|
||||
);
|
||||
useHotkeys(
|
||||
// ‘ === alt+]
|
||||
['alt+]', '‘'],
|
||||
selectNextEntity,
|
||||
{ preventDefault: true, ignoreModifiers: true },
|
||||
[selectNextEntity]
|
||||
);
|
||||
};
|
||||
@@ -1,16 +1,16 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
|
||||
import {
|
||||
type CanvasControlLayerState,
|
||||
type CanvasEntityIdentifier,
|
||||
type CanvasEntityState,
|
||||
type CanvasInpaintMaskState,
|
||||
type CanvasRasterLayerState,
|
||||
type CanvasRegionalGuidanceState,
|
||||
type CanvasState,
|
||||
isDrawableEntityType,
|
||||
import type {
|
||||
CanvasControlLayerState,
|
||||
CanvasEntityIdentifier,
|
||||
CanvasEntityState,
|
||||
CanvasInpaintMaskState,
|
||||
CanvasRasterLayerState,
|
||||
CanvasRegionalGuidanceState,
|
||||
CanvasState,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { isDrawableEntityType } from 'features/controlLayers/store/types';
|
||||
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user