mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Inpainting Controls Code Spitting and Performance
Codesplit the entirety of the inpainting controls. Created new selectors for each and every component to ensure there are no unnecessary re-renders. App feels a lot smoother.
This commit is contained in:
parent
868e4b2db8
commit
db46e12f2b
File diff suppressed because one or more lines are too long
2
frontend/dist/index.html
vendored
2
frontend/dist/index.html
vendored
@ -6,7 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
||||||
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
|
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
|
||||||
<script type="module" crossorigin src="./assets/index.9ad2f384.js"></script>
|
<script type="module" crossorigin src="./assets/index.ae6d2a5e.js"></script>
|
||||||
<link rel="stylesheet" href="./assets/index.14c578ee.css">
|
<link rel="stylesheet" href="./assets/index.14c578ee.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -1,442 +1,53 @@
|
|||||||
import { useToast } from '@chakra-ui/react';
|
import InpaintingBrushControl from './InpaintingControls/InpaintingBrushControl';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import InpaintingEraserControl from './InpaintingControls/InpaintingEraserControl';
|
||||||
import {
|
import InpaintingMaskControl from './InpaintingControls/InpaintingMaskControl';
|
||||||
FaEraser,
|
import InpaintingUndoControl from './InpaintingControls/InpaintingUndoControl';
|
||||||
FaMask,
|
import InpaintingRedoControl from './InpaintingControls/InpaintingRedoControl';
|
||||||
FaPaintBrush,
|
import InpaintingClearImageControl from './InpaintingControls/InpaintingClearImageControl';
|
||||||
FaPalette,
|
import InpaintingSplitLayoutControl from './InpaintingControls/InpaintingSplitLayoutControl';
|
||||||
FaPlus,
|
|
||||||
FaRedo,
|
|
||||||
FaTrash,
|
|
||||||
FaUndo,
|
|
||||||
} from 'react-icons/fa';
|
|
||||||
import { BiHide, BiShow } from 'react-icons/bi';
|
|
||||||
import { VscSplitHorizontal } from 'react-icons/vsc';
|
|
||||||
import { useAppDispatch, useAppSelector } from '../../../app/store';
|
|
||||||
import IAIIconButton from '../../../common/components/IAIIconButton';
|
|
||||||
import {
|
|
||||||
clearMask,
|
|
||||||
redo,
|
|
||||||
setMaskColor,
|
|
||||||
setBrushSize,
|
|
||||||
setShouldShowBrushPreview,
|
|
||||||
setTool,
|
|
||||||
undo,
|
|
||||||
setShouldShowMask,
|
|
||||||
setShouldInvertMask,
|
|
||||||
setNeedsCache,
|
|
||||||
toggleShouldLockBoundingBox,
|
|
||||||
clearImageToInpaint,
|
|
||||||
} from './inpaintingSlice';
|
|
||||||
|
|
||||||
import { MdInvertColors, MdInvertColorsOff } from 'react-icons/md';
|
|
||||||
import IAISlider from '../../../common/components/IAISlider';
|
|
||||||
import IAINumberInput from '../../../common/components/IAINumberInput';
|
|
||||||
import { inpaintingControlsSelector } from './inpaintingSliceSelectors';
|
|
||||||
import IAIPopover from '../../../common/components/IAIPopover';
|
|
||||||
import IAIColorPicker from '../../../common/components/IAIColorPicker';
|
|
||||||
import { RgbaColor } from 'react-colorful';
|
|
||||||
import { setShowDualDisplay } from '../../options/optionsSlice';
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
const InpaintingControls = () => {
|
const InpaintingControls = () => {
|
||||||
const {
|
// const { shouldShowMask, activeTabName } = useAppSelector(
|
||||||
tool,
|
// inpaintingControlsSelector
|
||||||
brushSize,
|
// );
|
||||||
maskColor,
|
|
||||||
shouldInvertMask,
|
|
||||||
shouldShowMask,
|
|
||||||
canUndo,
|
|
||||||
canRedo,
|
|
||||||
isMaskEmpty,
|
|
||||||
activeTabName,
|
|
||||||
showDualDisplay,
|
|
||||||
} = useAppSelector(inpaintingControlsSelector);
|
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
// const dispatch = useAppDispatch();
|
||||||
const toast = useToast();
|
|
||||||
|
|
||||||
// Button State Controllers
|
|
||||||
const [maskOptionsOpen, setMaskOptionsOpen] = useState<boolean>(false);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hotkeys
|
* Hotkeys
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Decrease brush size
|
|
||||||
useHotkeys(
|
|
||||||
'[',
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (brushSize - 5 > 0) {
|
|
||||||
handleChangeBrushSize(brushSize - 5);
|
|
||||||
} else {
|
|
||||||
handleChangeBrushSize(1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
|
||||||
},
|
|
||||||
[activeTabName, shouldShowMask, brushSize]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Increase brush size
|
|
||||||
useHotkeys(
|
|
||||||
']',
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleChangeBrushSize(brushSize + 5);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
|
||||||
},
|
|
||||||
[activeTabName, shouldShowMask, brushSize]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Decrease mask opacity
|
|
||||||
useHotkeys(
|
|
||||||
'shift+[',
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleChangeMaskColor({
|
|
||||||
...maskColor,
|
|
||||||
a: Math.max(maskColor.a - 0.05, 0),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
|
||||||
},
|
|
||||||
[activeTabName, shouldShowMask, maskColor.a]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Increase mask opacity
|
|
||||||
useHotkeys(
|
|
||||||
'shift+]',
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleChangeMaskColor({
|
|
||||||
...maskColor,
|
|
||||||
a: Math.min(maskColor.a + 0.05, 100),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
|
||||||
},
|
|
||||||
[activeTabName, shouldShowMask, maskColor.a]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set tool to eraser
|
|
||||||
useHotkeys(
|
|
||||||
'e',
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (activeTabName !== 'inpainting' || !shouldShowMask) return;
|
|
||||||
handleSelectEraserTool();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
|
||||||
},
|
|
||||||
[activeTabName, shouldShowMask]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set tool to brush
|
|
||||||
useHotkeys(
|
|
||||||
'b',
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleSelectBrushTool();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
|
||||||
},
|
|
||||||
[activeTabName, shouldShowMask]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Toggle lock bounding box
|
// Toggle lock bounding box
|
||||||
useHotkeys(
|
// useHotkeys(
|
||||||
'shift+q',
|
// 'shift+q',
|
||||||
(e: KeyboardEvent) => {
|
// (e: KeyboardEvent) => {
|
||||||
e.preventDefault();
|
// e.preventDefault();
|
||||||
dispatch(toggleShouldLockBoundingBox());
|
// dispatch(toggleShouldLockBoundingBox());
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
// enabled: activeTabName === 'inpainting' && shouldShowMask,
|
||||||
},
|
// },
|
||||||
[activeTabName, shouldShowMask]
|
// [activeTabName, shouldShowMask]
|
||||||
);
|
// );
|
||||||
|
|
||||||
// Undo
|
|
||||||
useHotkeys(
|
|
||||||
'cmd+z, control+z',
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleUndo();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: activeTabName === 'inpainting' && shouldShowMask && canUndo,
|
|
||||||
},
|
|
||||||
[activeTabName, shouldShowMask, canUndo]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Redo
|
|
||||||
useHotkeys(
|
|
||||||
'cmd+shift+z, control+shift+z, control+y, cmd+y',
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleRedo();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: activeTabName === 'inpainting' && shouldShowMask && canRedo,
|
|
||||||
},
|
|
||||||
[activeTabName, shouldShowMask, canRedo]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Show/hide mask
|
|
||||||
useHotkeys(
|
|
||||||
'h',
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleToggleShouldShowMask();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: activeTabName === 'inpainting',
|
|
||||||
},
|
|
||||||
[activeTabName, shouldShowMask]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Invert mask
|
|
||||||
useHotkeys(
|
|
||||||
'shift+m',
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleToggleShouldInvertMask();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
|
||||||
},
|
|
||||||
[activeTabName, shouldInvertMask, shouldShowMask]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Clear mask
|
|
||||||
useHotkeys(
|
|
||||||
'shift+c',
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleClearMask();
|
|
||||||
toast({
|
|
||||||
title: 'Mask Cleared',
|
|
||||||
status: 'success',
|
|
||||||
duration: 2500,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: activeTabName === 'inpainting' && shouldShowMask && !isMaskEmpty,
|
|
||||||
},
|
|
||||||
[activeTabName, isMaskEmpty, shouldShowMask]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Toggle split view
|
|
||||||
useHotkeys(
|
|
||||||
'shift+j',
|
|
||||||
() => {
|
|
||||||
handleDualDisplay();
|
|
||||||
},
|
|
||||||
[showDualDisplay]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClearMask = () => {
|
|
||||||
dispatch(clearMask());
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
|
|
||||||
|
|
||||||
const handleSelectBrushTool = () => dispatch(setTool('brush'));
|
|
||||||
|
|
||||||
const handleChangeBrushSize = (v: number) => {
|
|
||||||
dispatch(setShouldShowBrushPreview(true));
|
|
||||||
dispatch(setBrushSize(v));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleToggleShouldShowMask = () =>
|
|
||||||
dispatch(setShouldShowMask(!shouldShowMask));
|
|
||||||
|
|
||||||
const handleToggleShouldInvertMask = () =>
|
|
||||||
dispatch(setShouldInvertMask(!shouldInvertMask));
|
|
||||||
|
|
||||||
const handleShowBrushPreview = () => {
|
|
||||||
dispatch(setShouldShowBrushPreview(true));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleHideBrushPreview = () => {
|
|
||||||
dispatch(setShouldShowBrushPreview(false));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangeMaskColor = (newColor: RgbaColor) => {
|
|
||||||
dispatch(setMaskColor(newColor));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUndo = () => dispatch(undo());
|
|
||||||
|
|
||||||
const handleRedo = () => dispatch(redo());
|
|
||||||
|
|
||||||
const handleDualDisplay = () => {
|
|
||||||
dispatch(setShowDualDisplay(!showDualDisplay));
|
|
||||||
dispatch(setNeedsCache(true));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClearImage = () => {
|
|
||||||
dispatch(clearImageToInpaint());
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inpainting-settings">
|
<div className="inpainting-settings">
|
||||||
<div className="inpainting-buttons-group">
|
<div className="inpainting-buttons-group">
|
||||||
<IAIPopover
|
<InpaintingBrushControl />
|
||||||
trigger="hover"
|
<InpaintingEraserControl />
|
||||||
onOpen={handleShowBrushPreview}
|
|
||||||
onClose={handleHideBrushPreview}
|
|
||||||
triggerComponent={
|
|
||||||
<IAIIconButton
|
|
||||||
aria-label="Brush (B)"
|
|
||||||
tooltip="Brush (B)"
|
|
||||||
icon={<FaPaintBrush />}
|
|
||||||
onClick={handleSelectBrushTool}
|
|
||||||
data-selected={tool === 'brush'}
|
|
||||||
isDisabled={!shouldShowMask}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="inpainting-slider-numberinput">
|
|
||||||
<IAISlider
|
|
||||||
label="Brush Size"
|
|
||||||
value={brushSize}
|
|
||||||
onChange={handleChangeBrushSize}
|
|
||||||
min={1}
|
|
||||||
max={200}
|
|
||||||
width="100px"
|
|
||||||
focusThumbOnChange={false}
|
|
||||||
isDisabled={!shouldShowMask}
|
|
||||||
/>
|
|
||||||
<IAINumberInput
|
|
||||||
value={brushSize}
|
|
||||||
onChange={handleChangeBrushSize}
|
|
||||||
width={'80px'}
|
|
||||||
min={1}
|
|
||||||
max={999}
|
|
||||||
isDisabled={!shouldShowMask}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</IAIPopover>
|
|
||||||
<IAIIconButton
|
|
||||||
aria-label="Eraser (E)"
|
|
||||||
tooltip="Eraser (E)"
|
|
||||||
icon={<FaEraser />}
|
|
||||||
onClick={handleSelectEraserTool}
|
|
||||||
data-selected={tool === 'eraser'}
|
|
||||||
isDisabled={!shouldShowMask}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="inpainting-buttons-group">
|
<div className="inpainting-buttons-group">
|
||||||
<IAIPopover
|
<InpaintingMaskControl />
|
||||||
trigger="hover"
|
|
||||||
onOpen={() => setMaskOptionsOpen(true)}
|
|
||||||
onClose={() => setMaskOptionsOpen(false)}
|
|
||||||
triggerComponent={
|
|
||||||
<IAIIconButton
|
|
||||||
aria-label="Mask Options"
|
|
||||||
tooltip="Mask Options"
|
|
||||||
icon={<FaMask />}
|
|
||||||
cursor={'pointer'}
|
|
||||||
data-selected={maskOptionsOpen}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="inpainting-button-dropdown">
|
|
||||||
<IAIIconButton
|
|
||||||
aria-label="Hide/Show Mask (H)"
|
|
||||||
tooltip="Hide/Show Mask (H)"
|
|
||||||
data-selected={!shouldShowMask}
|
|
||||||
icon={
|
|
||||||
shouldShowMask ? <BiShow size={22} /> : <BiHide size={22} />
|
|
||||||
}
|
|
||||||
onClick={handleToggleShouldShowMask}
|
|
||||||
/>
|
|
||||||
<IAIIconButton
|
|
||||||
tooltip="Invert Mask Display (Shift+M)"
|
|
||||||
aria-label="Invert Mask Display (Shift+M)"
|
|
||||||
data-selected={shouldInvertMask}
|
|
||||||
icon={
|
|
||||||
shouldInvertMask ? (
|
|
||||||
<MdInvertColors size={22} />
|
|
||||||
) : (
|
|
||||||
<MdInvertColorsOff size={22} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={handleToggleShouldInvertMask}
|
|
||||||
isDisabled={!shouldShowMask}
|
|
||||||
/>
|
|
||||||
<IAIPopover
|
|
||||||
trigger="hover"
|
|
||||||
placement="right"
|
|
||||||
styleClass="inpainting-color-picker"
|
|
||||||
triggerComponent={
|
|
||||||
<IAIIconButton
|
|
||||||
aria-label="Mask Color"
|
|
||||||
tooltip="Mask Color"
|
|
||||||
icon={<FaPalette />}
|
|
||||||
isDisabled={!shouldShowMask}
|
|
||||||
cursor={'pointer'}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<IAIColorPicker
|
|
||||||
color={maskColor}
|
|
||||||
onChange={handleChangeMaskColor}
|
|
||||||
/>
|
|
||||||
</IAIPopover>
|
|
||||||
</div>
|
|
||||||
</IAIPopover>
|
|
||||||
<IAIIconButton
|
|
||||||
aria-label="Clear Mask (Shift+C)"
|
|
||||||
tooltip="Clear Mask (Shift+C)"
|
|
||||||
icon={<FaPlus size={18} style={{ transform: 'rotate(45deg)' }} />}
|
|
||||||
onClick={handleClearMask}
|
|
||||||
isDisabled={isMaskEmpty || !shouldShowMask}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="inpainting-buttons-group">
|
<div className="inpainting-buttons-group">
|
||||||
<IAIIconButton
|
<InpaintingUndoControl />
|
||||||
aria-label="Undo"
|
<InpaintingRedoControl />
|
||||||
tooltip="Undo"
|
|
||||||
icon={<FaUndo />}
|
|
||||||
onClick={handleUndo}
|
|
||||||
isDisabled={!canUndo || !shouldShowMask}
|
|
||||||
/>
|
|
||||||
<IAIIconButton
|
|
||||||
aria-label="Redo"
|
|
||||||
tooltip="Redo"
|
|
||||||
icon={<FaRedo />}
|
|
||||||
onClick={handleRedo}
|
|
||||||
isDisabled={!canRedo || !shouldShowMask}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="inpainting-buttons-group">
|
<div className="inpainting-buttons-group">
|
||||||
<IAIIconButton
|
<InpaintingClearImageControl />
|
||||||
aria-label="Clear Image"
|
|
||||||
tooltip="Clear Image"
|
|
||||||
icon={<FaTrash size={16} />}
|
|
||||||
onClick={handleClearImage}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<IAIIconButton
|
<InpaintingSplitLayoutControl />
|
||||||
aria-label="Split Layout (Shift+J)"
|
|
||||||
tooltip="Split Layout (Shift+J)"
|
|
||||||
icon={<VscSplitHorizontal />}
|
|
||||||
data-selected={showDualDisplay}
|
|
||||||
onClick={handleDualDisplay}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,148 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import React from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { FaPaintBrush } from 'react-icons/fa';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../app/store';
|
||||||
|
import IAIIconButton from '../../../../common/components/IAIIconButton';
|
||||||
|
import IAINumberInput from '../../../../common/components/IAINumberInput';
|
||||||
|
import IAIPopover from '../../../../common/components/IAIPopover';
|
||||||
|
import IAISlider from '../../../../common/components/IAISlider';
|
||||||
|
import { activeTabNameSelector } from '../../../options/optionsSelectors';
|
||||||
|
|
||||||
|
import {
|
||||||
|
InpaintingState,
|
||||||
|
setBrushSize,
|
||||||
|
setShouldShowBrushPreview,
|
||||||
|
setTool,
|
||||||
|
} from '../inpaintingSlice';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const inpaintingBrushSelector = createSelector(
|
||||||
|
[(state: RootState) => state.inpainting, activeTabNameSelector],
|
||||||
|
(inpainting: InpaintingState, activeTabName) => {
|
||||||
|
const { tool, brushSize, shouldShowMask } = inpainting;
|
||||||
|
|
||||||
|
return {
|
||||||
|
tool,
|
||||||
|
brushSize,
|
||||||
|
shouldShowMask,
|
||||||
|
activeTabName,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function InpaintingBrushControl() {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { tool, brushSize, shouldShowMask, activeTabName } = useAppSelector(
|
||||||
|
inpaintingBrushSelector
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelectBrushTool = () => dispatch(setTool('brush'));
|
||||||
|
|
||||||
|
const handleShowBrushPreview = () => {
|
||||||
|
dispatch(setShouldShowBrushPreview(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHideBrushPreview = () => {
|
||||||
|
dispatch(setShouldShowBrushPreview(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeBrushSize = (v: number) => {
|
||||||
|
dispatch(setShouldShowBrushPreview(true));
|
||||||
|
dispatch(setBrushSize(v));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hotkeys
|
||||||
|
|
||||||
|
// Decrease brush size
|
||||||
|
useHotkeys(
|
||||||
|
'[',
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (brushSize - 5 > 0) {
|
||||||
|
handleChangeBrushSize(brushSize - 5);
|
||||||
|
} else {
|
||||||
|
handleChangeBrushSize(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
||||||
|
},
|
||||||
|
[activeTabName, shouldShowMask, brushSize]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Increase brush size
|
||||||
|
useHotkeys(
|
||||||
|
']',
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleChangeBrushSize(brushSize + 5);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
||||||
|
},
|
||||||
|
[activeTabName, shouldShowMask, brushSize]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set tool to brush
|
||||||
|
useHotkeys(
|
||||||
|
'b',
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSelectBrushTool();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
||||||
|
},
|
||||||
|
[activeTabName, shouldShowMask]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIPopover
|
||||||
|
trigger="hover"
|
||||||
|
onOpen={handleShowBrushPreview}
|
||||||
|
onClose={handleHideBrushPreview}
|
||||||
|
triggerComponent={
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Brush (B)"
|
||||||
|
tooltip="Brush (B)"
|
||||||
|
icon={<FaPaintBrush />}
|
||||||
|
onClick={handleSelectBrushTool}
|
||||||
|
data-selected={tool === 'brush'}
|
||||||
|
isDisabled={!shouldShowMask}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="inpainting-slider-numberinput">
|
||||||
|
<IAISlider
|
||||||
|
label="Brush Size"
|
||||||
|
value={brushSize}
|
||||||
|
onChange={handleChangeBrushSize}
|
||||||
|
min={1}
|
||||||
|
max={200}
|
||||||
|
width="100px"
|
||||||
|
focusThumbOnChange={false}
|
||||||
|
isDisabled={!shouldShowMask}
|
||||||
|
/>
|
||||||
|
<IAINumberInput
|
||||||
|
value={brushSize}
|
||||||
|
onChange={handleChangeBrushSize}
|
||||||
|
width={'80px'}
|
||||||
|
min={1}
|
||||||
|
max={999}
|
||||||
|
isDisabled={!shouldShowMask}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</IAIPopover>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FaTrash } from 'react-icons/fa';
|
||||||
|
import { useAppDispatch } from '../../../../app/store';
|
||||||
|
import IAIIconButton from '../../../../common/components/IAIIconButton';
|
||||||
|
import { clearImageToInpaint } from '../inpaintingSlice';
|
||||||
|
|
||||||
|
export default function InpaintingClearImageControl() {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleClearImage = () => {
|
||||||
|
dispatch(clearImageToInpaint());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Clear Image"
|
||||||
|
tooltip="Clear Image"
|
||||||
|
icon={<FaTrash size={16} />}
|
||||||
|
onClick={handleClearImage}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import React from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { FaEraser } from 'react-icons/fa';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../app/store';
|
||||||
|
import IAIIconButton from '../../../../common/components/IAIIconButton';
|
||||||
|
import { InpaintingState, setTool } from '../inpaintingSlice';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { activeTabNameSelector } from '../../../options/optionsSelectors';
|
||||||
|
|
||||||
|
const inpaintingEraserSelector = createSelector(
|
||||||
|
[(state: RootState) => state.inpainting, activeTabNameSelector],
|
||||||
|
(inpainting: InpaintingState, activeTabName) => {
|
||||||
|
const { tool, shouldShowMask } = inpainting;
|
||||||
|
|
||||||
|
return {
|
||||||
|
tool,
|
||||||
|
shouldShowMask,
|
||||||
|
activeTabName,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function InpaintingEraserControl() {
|
||||||
|
const { tool, shouldShowMask, activeTabName } = useAppSelector(
|
||||||
|
inpaintingEraserSelector
|
||||||
|
);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
|
||||||
|
|
||||||
|
// Hotkeys
|
||||||
|
// Set tool to eraser
|
||||||
|
useHotkeys(
|
||||||
|
'e',
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (activeTabName !== 'inpainting' || !shouldShowMask) return;
|
||||||
|
handleSelectEraserTool();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
||||||
|
},
|
||||||
|
[activeTabName, shouldShowMask]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Eraser (E)"
|
||||||
|
tooltip="Eraser (E)"
|
||||||
|
icon={<FaEraser />}
|
||||||
|
onClick={handleSelectEraserTool}
|
||||||
|
data-selected={tool === 'eraser'}
|
||||||
|
isDisabled={!shouldShowMask}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { FaMask } from 'react-icons/fa';
|
||||||
|
|
||||||
|
import IAIIconButton from '../../../../common/components/IAIIconButton';
|
||||||
|
import IAIPopover from '../../../../common/components/IAIPopover';
|
||||||
|
|
||||||
|
import InpaintingMaskVisibilityControl from './InpaintingMaskControls/InpaintingMaskVisibilityControl';
|
||||||
|
import InpaintingMaskInvertControl from './InpaintingMaskControls/InpaintingMaskInvertControl';
|
||||||
|
import InpaintingMaskColorPicker from './InpaintingMaskControls/InpaintingMaskColorPicker';
|
||||||
|
import InpaintingMaskClear from './InpaintingMaskControls/InpaintingMaskClear';
|
||||||
|
|
||||||
|
export default function InpaintingMaskControl() {
|
||||||
|
const [maskOptionsOpen, setMaskOptionsOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IAIPopover
|
||||||
|
trigger="hover"
|
||||||
|
onOpen={() => setMaskOptionsOpen(true)}
|
||||||
|
onClose={() => setMaskOptionsOpen(false)}
|
||||||
|
triggerComponent={
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Mask Options"
|
||||||
|
tooltip="Mask Options"
|
||||||
|
icon={<FaMask />}
|
||||||
|
cursor={'pointer'}
|
||||||
|
data-selected={maskOptionsOpen}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="inpainting-button-dropdown">
|
||||||
|
<InpaintingMaskVisibilityControl />
|
||||||
|
<InpaintingMaskInvertControl />
|
||||||
|
<InpaintingMaskColorPicker />
|
||||||
|
</div>
|
||||||
|
</IAIPopover>
|
||||||
|
<InpaintingMaskClear />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import React from 'react';
|
||||||
|
import { FaPlus } from 'react-icons/fa';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../../app/store';
|
||||||
|
import IAIIconButton from '../../../../../common/components/IAIIconButton';
|
||||||
|
import { activeTabNameSelector } from '../../../../options/optionsSelectors';
|
||||||
|
import { clearMask, InpaintingState } from '../../inpaintingSlice';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { useToast } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const inpaintingMaskClearSelector = createSelector(
|
||||||
|
[(state: RootState) => state.inpainting, activeTabNameSelector],
|
||||||
|
(inpainting: InpaintingState, activeTabName) => {
|
||||||
|
const { shouldShowMask, lines } = inpainting;
|
||||||
|
|
||||||
|
return { shouldShowMask, activeTabName, isMaskEmpty: lines.length === 0 };
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function InpaintingMaskClear() {
|
||||||
|
const { shouldShowMask, activeTabName, isMaskEmpty } = useAppSelector(
|
||||||
|
inpaintingMaskClearSelector
|
||||||
|
);
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const handleClearMask = () => {
|
||||||
|
dispatch(clearMask());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear mask
|
||||||
|
useHotkeys(
|
||||||
|
'shift+c',
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleClearMask();
|
||||||
|
toast({
|
||||||
|
title: 'Mask Cleared',
|
||||||
|
status: 'success',
|
||||||
|
duration: 2500,
|
||||||
|
isClosable: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: activeTabName === 'inpainting' && shouldShowMask && !isMaskEmpty,
|
||||||
|
},
|
||||||
|
[activeTabName, isMaskEmpty, shouldShowMask]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Clear Mask (Shift+C)"
|
||||||
|
tooltip="Clear Mask (Shift+C)"
|
||||||
|
icon={<FaPlus size={18} style={{ transform: 'rotate(45deg)' }} />}
|
||||||
|
onClick={handleClearMask}
|
||||||
|
isDisabled={isMaskEmpty || !shouldShowMask}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { RgbaColor } from 'react-colorful';
|
||||||
|
import { FaPalette } from 'react-icons/fa';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../../app/store';
|
||||||
|
import IAIColorPicker from '../../../../../common/components/IAIColorPicker';
|
||||||
|
import IAIIconButton from '../../../../../common/components/IAIIconButton';
|
||||||
|
import IAIPopover from '../../../../../common/components/IAIPopover';
|
||||||
|
import { InpaintingState, setMaskColor } from '../../inpaintingSlice';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { activeTabNameSelector } from '../../../../options/optionsSelectors';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
|
const inpaintingMaskColorPickerSelector = createSelector(
|
||||||
|
[(state: RootState) => state.inpainting, activeTabNameSelector],
|
||||||
|
(inpainting: InpaintingState, activeTabName) => {
|
||||||
|
const { shouldShowMask, maskColor } = inpainting;
|
||||||
|
|
||||||
|
return { shouldShowMask, maskColor, activeTabName };
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function InpaintingMaskColorPicker() {
|
||||||
|
const { shouldShowMask, maskColor, activeTabName } = useAppSelector(
|
||||||
|
inpaintingMaskColorPickerSelector
|
||||||
|
);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const handleChangeMaskColor = (newColor: RgbaColor) => {
|
||||||
|
dispatch(setMaskColor(newColor));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hotkeys
|
||||||
|
// Decrease mask opacity
|
||||||
|
useHotkeys(
|
||||||
|
'shift+[',
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleChangeMaskColor({
|
||||||
|
...maskColor,
|
||||||
|
a: Math.max(maskColor.a - 0.05, 0),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
||||||
|
},
|
||||||
|
[activeTabName, shouldShowMask, maskColor.a]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Increase mask opacity
|
||||||
|
useHotkeys(
|
||||||
|
'shift+]',
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleChangeMaskColor({
|
||||||
|
...maskColor,
|
||||||
|
a: Math.min(maskColor.a + 0.05, 100),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
||||||
|
},
|
||||||
|
[activeTabName, shouldShowMask, maskColor.a]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIPopover
|
||||||
|
trigger="hover"
|
||||||
|
placement="right"
|
||||||
|
styleClass="inpainting-color-picker"
|
||||||
|
triggerComponent={
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Mask Color"
|
||||||
|
tooltip="Mask Color"
|
||||||
|
icon={<FaPalette />}
|
||||||
|
isDisabled={!shouldShowMask}
|
||||||
|
cursor={'pointer'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IAIColorPicker color={maskColor} onChange={handleChangeMaskColor} />
|
||||||
|
</IAIPopover>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import React from 'react';
|
||||||
|
import { MdInvertColors, MdInvertColorsOff } from 'react-icons/md';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../../app/store';
|
||||||
|
import IAIIconButton from '../../../../../common/components/IAIIconButton';
|
||||||
|
import { InpaintingState, setShouldInvertMask } from '../../inpaintingSlice';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { activeTabNameSelector } from '../../../../options/optionsSelectors';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
|
const inpaintingMaskInvertSelector = createSelector(
|
||||||
|
[(state: RootState) => state.inpainting, activeTabNameSelector],
|
||||||
|
(inpainting: InpaintingState, activeTabName) => {
|
||||||
|
const { shouldShowMask, shouldInvertMask } = inpainting;
|
||||||
|
|
||||||
|
return { shouldInvertMask, shouldShowMask, activeTabName };
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function InpaintingMaskInvertControl() {
|
||||||
|
const { shouldInvertMask, shouldShowMask, activeTabName } = useAppSelector(
|
||||||
|
inpaintingMaskInvertSelector
|
||||||
|
);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleToggleShouldInvertMask = () =>
|
||||||
|
dispatch(setShouldInvertMask(!shouldInvertMask));
|
||||||
|
|
||||||
|
// Invert mask
|
||||||
|
useHotkeys(
|
||||||
|
'shift+m',
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleToggleShouldInvertMask();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
||||||
|
},
|
||||||
|
[activeTabName, shouldInvertMask, shouldShowMask]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip="Invert Mask Display (Shift+M)"
|
||||||
|
aria-label="Invert Mask Display (Shift+M)"
|
||||||
|
data-selected={shouldInvertMask}
|
||||||
|
icon={
|
||||||
|
shouldInvertMask ? (
|
||||||
|
<MdInvertColors size={22} />
|
||||||
|
) : (
|
||||||
|
<MdInvertColorsOff size={22} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={handleToggleShouldInvertMask}
|
||||||
|
isDisabled={!shouldShowMask}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { BiHide, BiShow } from 'react-icons/bi';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../../app/store';
|
||||||
|
import IAIIconButton from '../../../../../common/components/IAIIconButton';
|
||||||
|
import { activeTabNameSelector } from '../../../../options/optionsSelectors';
|
||||||
|
import { InpaintingState, setShouldShowMask } from '../../inpaintingSlice';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const inpaintingMaskVisibilitySelector = createSelector(
|
||||||
|
[(state: RootState) => state.inpainting, activeTabNameSelector],
|
||||||
|
(inpainting: InpaintingState, activeTabName) => {
|
||||||
|
const { shouldShowMask } = inpainting;
|
||||||
|
|
||||||
|
return { shouldShowMask, activeTabName };
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function InpaintingMaskVisibilityControl() {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const { shouldShowMask, activeTabName } = useAppSelector(
|
||||||
|
inpaintingMaskVisibilitySelector
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleToggleShouldShowMask = () =>
|
||||||
|
dispatch(setShouldShowMask(!shouldShowMask));
|
||||||
|
// Hotkeys
|
||||||
|
// Show/hide mask
|
||||||
|
useHotkeys(
|
||||||
|
'h',
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleToggleShouldShowMask();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: activeTabName === 'inpainting',
|
||||||
|
},
|
||||||
|
[activeTabName, shouldShowMask]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Hide/Show Mask (H)"
|
||||||
|
tooltip="Hide/Show Mask (H)"
|
||||||
|
data-selected={!shouldShowMask}
|
||||||
|
icon={shouldShowMask ? <BiShow size={22} /> : <BiHide size={22} />}
|
||||||
|
onClick={handleToggleShouldShowMask}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import React from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { FaRedo } from 'react-icons/fa';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../app/store';
|
||||||
|
import IAIIconButton from '../../../../common/components/IAIIconButton';
|
||||||
|
import { activeTabNameSelector } from '../../../options/optionsSelectors';
|
||||||
|
import { InpaintingState, redo } from '../inpaintingSlice';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const inpaintingRedoSelector = createSelector(
|
||||||
|
[(state: RootState) => state.inpainting, activeTabNameSelector],
|
||||||
|
(inpainting: InpaintingState, activeTabName) => {
|
||||||
|
const { futureLines, shouldShowMask } = inpainting;
|
||||||
|
|
||||||
|
return {
|
||||||
|
canRedo: futureLines.length > 0,
|
||||||
|
shouldShowMask,
|
||||||
|
activeTabName,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function InpaintingRedoControl() {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { canRedo, shouldShowMask, activeTabName } = useAppSelector(
|
||||||
|
inpaintingRedoSelector
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRedo = () => dispatch(redo());
|
||||||
|
|
||||||
|
// Hotkeys
|
||||||
|
|
||||||
|
// Redo
|
||||||
|
useHotkeys(
|
||||||
|
'cmd+shift+z, control+shift+z, control+y, cmd+y',
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleRedo();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: activeTabName === 'inpainting' && shouldShowMask && canRedo,
|
||||||
|
},
|
||||||
|
[activeTabName, shouldShowMask, canRedo]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Redo"
|
||||||
|
tooltip="Redo"
|
||||||
|
icon={<FaRedo />}
|
||||||
|
onClick={handleRedo}
|
||||||
|
isDisabled={!canRedo || !shouldShowMask}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { VscSplitHorizontal } from 'react-icons/vsc';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../app/store';
|
||||||
|
import IAIIconButton from '../../../../common/components/IAIIconButton';
|
||||||
|
import { setShowDualDisplay } from '../../../options/optionsSlice';
|
||||||
|
import { setNeedsCache } from '../inpaintingSlice';
|
||||||
|
|
||||||
|
export default function InpaintingSplitLayoutControl() {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const showDualDisplay = useAppSelector(
|
||||||
|
(state: RootState) => state.options.showDualDisplay
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDualDisplay = () => {
|
||||||
|
dispatch(setShowDualDisplay(!showDualDisplay));
|
||||||
|
dispatch(setNeedsCache(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hotkeys
|
||||||
|
// Toggle split view
|
||||||
|
useHotkeys(
|
||||||
|
'shift+j',
|
||||||
|
() => {
|
||||||
|
handleDualDisplay();
|
||||||
|
},
|
||||||
|
[showDualDisplay]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Split Layout (Shift+J)"
|
||||||
|
tooltip="Split Layout (Shift+J)"
|
||||||
|
icon={<VscSplitHorizontal />}
|
||||||
|
data-selected={showDualDisplay}
|
||||||
|
onClick={handleDualDisplay}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import React from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { FaUndo } from 'react-icons/fa';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../app/store';
|
||||||
|
import IAIIconButton from '../../../../common/components/IAIIconButton';
|
||||||
|
import { InpaintingState, undo } from '../inpaintingSlice';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { activeTabNameSelector } from '../../../options/optionsSelectors';
|
||||||
|
|
||||||
|
const inpaintingUndoSelector = createSelector(
|
||||||
|
[(state: RootState) => state.inpainting, activeTabNameSelector],
|
||||||
|
(inpainting: InpaintingState, activeTabName) => {
|
||||||
|
const { pastLines, shouldShowMask } = inpainting;
|
||||||
|
|
||||||
|
return {
|
||||||
|
canUndo: pastLines.length > 0,
|
||||||
|
shouldShowMask,
|
||||||
|
activeTabName,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function InpaintingUndoControl() {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const { canUndo, shouldShowMask, activeTabName } = useAppSelector(
|
||||||
|
inpaintingUndoSelector
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleUndo = () => dispatch(undo());
|
||||||
|
|
||||||
|
// Hotkeys
|
||||||
|
// Undo
|
||||||
|
useHotkeys(
|
||||||
|
'cmd+z, control+z',
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleUndo();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: activeTabName === 'inpainting' && shouldShowMask && canUndo,
|
||||||
|
},
|
||||||
|
[activeTabName, shouldShowMask, canUndo]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Undo"
|
||||||
|
tooltip="Undo"
|
||||||
|
icon={<FaUndo />}
|
||||||
|
onClick={handleUndo}
|
||||||
|
isDisabled={!canUndo || !shouldShowMask}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user