mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat: ✨ highlight focused regions
adds a region wrapper with a highlight effect when that region is focused, this behavior can be toggled as a setting
This commit is contained in:
committed by
psychedelicious
parent
09bf7c35eb
commit
02b91e8e7b
@@ -1,28 +1,28 @@
|
||||
import { Divider, Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useFocusRegion } from 'common/hooks/focus';
|
||||
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
|
||||
import { CanvasAddEntityButtons } from 'features/controlLayers/components/CanvasAddEntityButtons';
|
||||
import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityList';
|
||||
import { EntityListSelectedEntityActionBar } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBar';
|
||||
import { selectHasEntities } from 'features/controlLayers/store/selectors';
|
||||
import { memo, useRef } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { ParamDenoisingStrength } from './ParamDenoisingStrength';
|
||||
|
||||
export const CanvasLayersPanelContent = memo(() => {
|
||||
const hasEntities = useAppSelector(selectHasEntities);
|
||||
const layersPanelFocusRef = useRef<HTMLDivElement>(null);
|
||||
useFocusRegion('layers', layersPanelFocusRef);
|
||||
|
||||
return (
|
||||
<Flex ref={layersPanelFocusRef} flexDir="column" gap={2} w="full" h="full">
|
||||
<EntityListSelectedEntityActionBar />
|
||||
<Divider py={0} />
|
||||
<ParamDenoisingStrength />
|
||||
<Divider py={0} />
|
||||
{!hasEntities && <CanvasAddEntityButtons />}
|
||||
{hasEntities && <CanvasEntityList />}
|
||||
</Flex>
|
||||
<FocusRegionWrapper region="layers" w="full" h="full">
|
||||
<Flex flexDir="column" gap={2} w="full" h="full">
|
||||
<EntityListSelectedEntityActionBar />
|
||||
<Divider py={0} />
|
||||
<ParamDenoisingStrength />
|
||||
<Divider py={0} />
|
||||
{!hasEntities && <CanvasAddEntityButtons />}
|
||||
{hasEntities && <CanvasEntityList />}
|
||||
</Flex>
|
||||
</FocusRegionWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ContextMenu, Flex, IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useFocusRegion } from 'common/hooks/focus';
|
||||
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
|
||||
import { CanvasAlertsPreserveMask } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsPreserveMask';
|
||||
import { CanvasAlertsSelectedEntityStatus } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSelectedEntityStatus';
|
||||
import { CanvasAlertsSendingToGallery } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSendingTo';
|
||||
@@ -18,7 +18,7 @@ import { Transform } from 'features/controlLayers/components/Transform/Transform
|
||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { selectDynamicGrid, selectShowHUD } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import { GatedImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { PiDotsThreeOutlineVerticalFill } from 'react-icons/pi';
|
||||
|
||||
import { CanvasAlertsInvocationProgress } from './CanvasAlerts/CanvasAlertsInvocationProgress';
|
||||
@@ -35,7 +35,6 @@ const MenuContent = () => {
|
||||
};
|
||||
|
||||
export const CanvasMainPanelContent = memo(() => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const dynamicGrid = useAppSelector(selectDynamicGrid);
|
||||
const showHUD = useAppSelector(selectShowHUD);
|
||||
|
||||
@@ -43,82 +42,81 @@ export const CanvasMainPanelContent = memo(() => {
|
||||
return <MenuContent />;
|
||||
}, []);
|
||||
|
||||
useFocusRegion('canvas', ref);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
tabIndex={-1}
|
||||
ref={ref}
|
||||
borderRadius="base"
|
||||
position="relative"
|
||||
flexDirection="column"
|
||||
height="full"
|
||||
width="full"
|
||||
gap={2}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
overflow="hidden"
|
||||
>
|
||||
<CanvasManagerProviderGate>
|
||||
<CanvasToolbar />
|
||||
</CanvasManagerProviderGate>
|
||||
<ContextMenu<HTMLDivElement> renderMenu={renderMenu} withLongPress={false}>
|
||||
{(ref) => (
|
||||
<Flex
|
||||
ref={ref}
|
||||
position="relative"
|
||||
w="full"
|
||||
h="full"
|
||||
bg={dynamicGrid ? 'base.850' : 'base.900'}
|
||||
borderRadius="base"
|
||||
overflow="hidden"
|
||||
>
|
||||
<InvokeCanvasComponent />
|
||||
<CanvasManagerProviderGate>
|
||||
<Flex
|
||||
position="absolute"
|
||||
flexDir="column"
|
||||
top={1}
|
||||
insetInlineStart={1}
|
||||
pointerEvents="none"
|
||||
gap={2}
|
||||
alignItems="flex-start"
|
||||
>
|
||||
{showHUD && <CanvasHUD />}
|
||||
<CanvasAlertsSelectedEntityStatus />
|
||||
<CanvasAlertsPreserveMask />
|
||||
<CanvasAlertsSendingToGallery />
|
||||
<CanvasAlertsInvocationProgress />
|
||||
</Flex>
|
||||
<Flex position="absolute" top={1} insetInlineEnd={1}>
|
||||
<Menu>
|
||||
<MenuButton as={IconButton} icon={<PiDotsThreeOutlineVerticalFill />} colorScheme="base" />
|
||||
<MenuContent />
|
||||
</Menu>
|
||||
</Flex>
|
||||
</CanvasManagerProviderGate>
|
||||
</Flex>
|
||||
)}
|
||||
</ContextMenu>
|
||||
<Flex position="absolute" bottom={4} gap={2} align="center" justify="center">
|
||||
<FocusRegionWrapper region="canvas" w="full" h="full">
|
||||
<Flex
|
||||
tabIndex={-1}
|
||||
borderRadius="base"
|
||||
position="relative"
|
||||
flexDirection="column"
|
||||
height="full"
|
||||
width="full"
|
||||
gap={2}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
overflow="hidden"
|
||||
>
|
||||
<CanvasManagerProviderGate>
|
||||
<StagingAreaIsStagingGate>
|
||||
<StagingAreaToolbar />
|
||||
</StagingAreaIsStagingGate>
|
||||
<CanvasToolbar />
|
||||
</CanvasManagerProviderGate>
|
||||
</Flex>
|
||||
<Flex position="absolute" bottom={4}>
|
||||
<ContextMenu<HTMLDivElement> renderMenu={renderMenu} withLongPress={false}>
|
||||
{(ref) => (
|
||||
<Flex
|
||||
ref={ref}
|
||||
position="relative"
|
||||
w="full"
|
||||
h="full"
|
||||
bg={dynamicGrid ? 'base.850' : 'base.900'}
|
||||
borderRadius="base"
|
||||
overflow="hidden"
|
||||
>
|
||||
<InvokeCanvasComponent />
|
||||
<CanvasManagerProviderGate>
|
||||
<Flex
|
||||
position="absolute"
|
||||
flexDir="column"
|
||||
top={1}
|
||||
insetInlineStart={1}
|
||||
pointerEvents="none"
|
||||
gap={2}
|
||||
alignItems="flex-start"
|
||||
>
|
||||
{showHUD && <CanvasHUD />}
|
||||
<CanvasAlertsSelectedEntityStatus />
|
||||
<CanvasAlertsPreserveMask />
|
||||
<CanvasAlertsSendingToGallery />
|
||||
<CanvasAlertsInvocationProgress />
|
||||
</Flex>
|
||||
<Flex position="absolute" top={1} insetInlineEnd={1}>
|
||||
<Menu>
|
||||
<MenuButton as={IconButton} icon={<PiDotsThreeOutlineVerticalFill />} colorScheme="base" />
|
||||
<MenuContent />
|
||||
</Menu>
|
||||
</Flex>
|
||||
</CanvasManagerProviderGate>
|
||||
</Flex>
|
||||
)}
|
||||
</ContextMenu>
|
||||
<Flex position="absolute" bottom={4} gap={2} align="center" justify="center">
|
||||
<CanvasManagerProviderGate>
|
||||
<StagingAreaIsStagingGate>
|
||||
<StagingAreaToolbar />
|
||||
</StagingAreaIsStagingGate>
|
||||
</CanvasManagerProviderGate>
|
||||
</Flex>
|
||||
<Flex position="absolute" bottom={4}>
|
||||
<CanvasManagerProviderGate>
|
||||
<Filter />
|
||||
<Transform />
|
||||
<SelectObject />
|
||||
</CanvasManagerProviderGate>
|
||||
</Flex>
|
||||
<CanvasManagerProviderGate>
|
||||
<Filter />
|
||||
<Transform />
|
||||
<SelectObject />
|
||||
<CanvasDropArea />
|
||||
</CanvasManagerProviderGate>
|
||||
<GatedImageViewer />
|
||||
</Flex>
|
||||
<CanvasManagerProviderGate>
|
||||
<CanvasDropArea />
|
||||
</CanvasManagerProviderGate>
|
||||
<GatedImageViewer />
|
||||
</Flex>
|
||||
</FocusRegionWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Button, Collapse, Divider, Flex, IconButton, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useFocusRegion } from 'common/hooks/focus';
|
||||
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
|
||||
import { GalleryHeader } from 'features/gallery/components/GalleryHeader';
|
||||
import { selectBoardSearchText } from 'features/gallery/store/gallerySelectors';
|
||||
import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
|
||||
@@ -26,8 +26,6 @@ const GalleryPanelContent = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const boardSearchDisclosure = useDisclosure({ defaultIsOpen: !!boardSearchText.length });
|
||||
const imperativePanelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
||||
const galleryPanelFocusRef = useRef<HTMLDivElement>(null);
|
||||
useFocusRegion('gallery', galleryPanelFocusRef);
|
||||
|
||||
const boardsListPanelOptions = useMemo<UsePanelOptions>(
|
||||
() => ({
|
||||
@@ -50,7 +48,7 @@ const GalleryPanelContent = () => {
|
||||
}, [boardSearchText.length, boardSearchDisclosure, boardsListPanel, dispatch]);
|
||||
|
||||
return (
|
||||
<Flex ref={galleryPanelFocusRef} position="relative" flexDirection="column" h="full" w="full" tabIndex={-1}>
|
||||
<FocusRegionWrapper region="gallery" position="relative" flexDirection="column" h="full" w="full">
|
||||
<Flex alignItems="center" justifyContent="space-between" w="full">
|
||||
<Flex flexGrow={1} flexBasis={0}>
|
||||
<Button
|
||||
@@ -99,7 +97,7 @@ const GalleryPanelContent = () => {
|
||||
<Gallery />
|
||||
</Panel>
|
||||
</PanelGroup>
|
||||
</Flex>
|
||||
</FocusRegionWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Flex, IconButton } from '@invoke-ai/ui-library';
|
||||
import { Box, IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useFocusRegion } from 'common/hooks/focus';
|
||||
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import { CompareToolbar } from 'features/gallery/components/ImageViewer/CompareToolbar';
|
||||
import CurrentImagePreview from 'features/gallery/components/ImageViewer/CurrentImagePreview';
|
||||
@@ -9,7 +9,7 @@ import { ImageComparisonDroppable } from 'features/gallery/components/ImageViewe
|
||||
import { ViewerToolbar } from 'features/gallery/components/ImageViewer/ViewerToolbar';
|
||||
import { selectHasImageToCompare } from 'features/gallery/store/gallerySelectors';
|
||||
import type { ReactNode } from 'react';
|
||||
import { memo, useRef } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiXBold } from 'react-icons/pi';
|
||||
@@ -29,15 +29,12 @@ export const ImageViewer = memo(({ closeButton }: Props) => {
|
||||
useAssertSingleton('ImageViewer');
|
||||
const hasImageToCompare = useAppSelector(selectHasImageToCompare);
|
||||
const [containerRef, containerDims] = useMeasure<HTMLDivElement>();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useFocusRegion('viewer', ref, useFocusRegionOptions);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
ref={ref}
|
||||
tabIndex={-1}
|
||||
layerStyle="first"
|
||||
borderRadius="base"
|
||||
<FocusRegionWrapper
|
||||
region="viewer"
|
||||
w="full"
|
||||
h="full"
|
||||
position="absolute"
|
||||
flexDirection="column"
|
||||
top={0}
|
||||
@@ -47,6 +44,8 @@ export const ImageViewer = memo(({ closeButton }: Props) => {
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
overflow="hidden"
|
||||
layerStyle="first"
|
||||
{...useFocusRegionOptions}
|
||||
>
|
||||
{hasImageToCompare && <CompareToolbar />}
|
||||
{!hasImageToCompare && <ViewerToolbar closeButton={closeButton} />}
|
||||
@@ -55,7 +54,7 @@ export const ImageViewer = memo(({ closeButton }: Props) => {
|
||||
{hasImageToCompare && <ImageComparison containerDims={containerDims} />}
|
||||
</Box>
|
||||
<ImageComparisonDroppable />
|
||||
</Flex>
|
||||
</FocusRegionWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import { useFocusRegion } from 'common/hooks/focus';
|
||||
import { AddNodeCmdk } from 'features/nodes/components/flow/AddNodeCmdk/AddNodeCmdk';
|
||||
import TopPanel from 'features/nodes/components/flow/panels/TopPanel/TopPanel';
|
||||
import WorkflowEditorSettings from 'features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings';
|
||||
import { memo, useRef } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiFlowArrowBold } from 'react-icons/pi';
|
||||
import { useGetOpenAPISchemaQuery } from 'services/api/endpoints/appInfo';
|
||||
@@ -16,13 +15,10 @@ import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel';
|
||||
const NodeEditor = () => {
|
||||
const { data, isLoading } = useGetOpenAPISchemaQuery();
|
||||
const { t } = useTranslation();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useFocusRegion('workflows', ref);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
tabIndex={-1}
|
||||
ref={ref}
|
||||
<FocusRegionWrapper
|
||||
region="workflows"
|
||||
layerStyle="first"
|
||||
position="relative"
|
||||
width="full"
|
||||
@@ -42,7 +38,7 @@ const NodeEditor = () => {
|
||||
)}
|
||||
<WorkflowEditorSettings />
|
||||
{isLoading && <IAINoContentFallback label={t('nodes.loadingNodes')} icon={PiFlowArrowBold} />}
|
||||
</Flex>
|
||||
</FocusRegionWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
selectSystemShouldAntialiasProgressImage,
|
||||
selectSystemShouldConfirmOnDelete,
|
||||
selectSystemShouldConfirmOnNewSession,
|
||||
selectSystemShouldEnableHighlightFocusedRegions,
|
||||
selectSystemShouldEnableInformationalPopovers,
|
||||
selectSystemShouldEnableModelDescriptions,
|
||||
selectSystemShouldShowInvocationProgressDetail,
|
||||
@@ -38,6 +39,7 @@ import {
|
||||
setShouldConfirmOnDelete,
|
||||
setShouldEnableInformationalPopovers,
|
||||
setShouldEnableModelDescriptions,
|
||||
setShouldHighlightFocusedRegions,
|
||||
setShouldShowInvocationProgressDetail,
|
||||
shouldAntialiasProgressImageChanged,
|
||||
shouldConfirmOnNewSessionToggled,
|
||||
@@ -106,6 +108,7 @@ const SettingsModal = ({ config = defaultConfig, children }: SettingsModalProps)
|
||||
const shouldUseWatermarker = useAppSelector(selectSystemShouldUseWatermarker);
|
||||
const shouldEnableInformationalPopovers = useAppSelector(selectSystemShouldEnableInformationalPopovers);
|
||||
const shouldEnableModelDescriptions = useAppSelector(selectSystemShouldEnableModelDescriptions);
|
||||
const shouldHighlightFocusedRegions = useAppSelector(selectSystemShouldEnableHighlightFocusedRegions);
|
||||
const shouldConfirmOnNewSession = useAppSelector(selectSystemShouldConfirmOnNewSession);
|
||||
const shouldShowInvocationProgressDetail = useAppSelector(selectSystemShouldShowInvocationProgressDetail);
|
||||
const onToggleConfirmOnNewSession = useCallback(() => {
|
||||
@@ -182,6 +185,13 @@ const SettingsModal = ({ config = defaultConfig, children }: SettingsModalProps)
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleChangeShouldHighlightFocusedRegions = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setShouldHighlightFocusedRegions(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{cloneElement(children, {
|
||||
@@ -263,6 +273,13 @@ const SettingsModal = ({ config = defaultConfig, children }: SettingsModalProps)
|
||||
onChange={handleChangeShouldEnableModelDescriptions}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>{t('settings.enableHighlightFocusedRegions')}</FormLabel>
|
||||
<Switch
|
||||
isChecked={shouldHighlightFocusedRegions}
|
||||
onChange={handleChangeShouldHighlightFocusedRegions}
|
||||
/>
|
||||
</FormControl>
|
||||
</StickyScrollable>
|
||||
|
||||
{Boolean(config?.shouldShowDeveloperSettings) && (
|
||||
|
||||
@@ -22,6 +22,7 @@ const initialSystemState: SystemState = {
|
||||
logLevel: 'debug',
|
||||
logNamespaces: [...zLogNamespace.options],
|
||||
shouldShowInvocationProgressDetail: false,
|
||||
shouldHighlightFocusedRegions: true,
|
||||
};
|
||||
|
||||
export const systemSlice = createSlice({
|
||||
@@ -68,6 +69,9 @@ export const systemSlice = createSlice({
|
||||
setShouldShowInvocationProgressDetail(state, action: PayloadAction<boolean>) {
|
||||
state.shouldShowInvocationProgressDetail = action.payload;
|
||||
},
|
||||
setShouldHighlightFocusedRegions(state, action: PayloadAction<boolean>) {
|
||||
state.shouldHighlightFocusedRegions = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -84,6 +88,7 @@ export const {
|
||||
setShouldEnableModelDescriptions,
|
||||
shouldConfirmOnNewSessionToggled,
|
||||
setShouldShowInvocationProgressDetail,
|
||||
setShouldHighlightFocusedRegions,
|
||||
} = systemSlice.actions;
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
@@ -121,6 +126,9 @@ export const selectSystemShouldEnableInformationalPopovers = createSystemSelecto
|
||||
export const selectSystemShouldEnableModelDescriptions = createSystemSelector(
|
||||
(system) => system.shouldEnableModelDescriptions
|
||||
);
|
||||
export const selectSystemShouldEnableHighlightFocusedRegions = createSystemSelector(
|
||||
(system) => system.shouldHighlightFocusedRegions
|
||||
);
|
||||
export const selectSystemShouldConfirmOnNewSession = createSystemSelector((system) => system.shouldConfirmOnNewSession);
|
||||
export const selectSystemShouldShowInvocationProgressDetail = createSystemSelector(
|
||||
(system) => system.shouldShowInvocationProgressDetail
|
||||
|
||||
@@ -43,4 +43,5 @@ export interface SystemState {
|
||||
logLevel: LogLevel;
|
||||
logNamespaces: LogNamespace[];
|
||||
shouldShowInvocationProgressDetail: boolean;
|
||||
shouldHighlightFocusedRegions: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user