mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Reworks canvas toolbar
This commit is contained in:
parent
7f999e9dfc
commit
83d8e69219
@ -1,9 +1,18 @@
|
|||||||
import { FormControl, FormLabel, Select, SelectProps } from '@chakra-ui/react';
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
Select,
|
||||||
|
SelectProps,
|
||||||
|
Tooltip,
|
||||||
|
TooltipProps,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
import { MouseEvent } from 'react';
|
import { MouseEvent } from 'react';
|
||||||
|
|
||||||
type IAISelectProps = SelectProps & {
|
type IAISelectProps = SelectProps & {
|
||||||
label?: string;
|
label?: string;
|
||||||
styleClass?: string;
|
styleClass?: string;
|
||||||
|
tooltip?: string;
|
||||||
|
tooltipProps?: Omit<TooltipProps, 'children'>;
|
||||||
validValues:
|
validValues:
|
||||||
| Array<number | string>
|
| Array<number | string>
|
||||||
| Array<{ key: string; value: string | number }>;
|
| Array<{ key: string; value: string | number }>;
|
||||||
@ -16,12 +25,15 @@ const IAISelect = (props: IAISelectProps) => {
|
|||||||
label,
|
label,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
validValues,
|
validValues,
|
||||||
|
tooltip,
|
||||||
|
tooltipProps,
|
||||||
size = 'sm',
|
size = 'sm',
|
||||||
fontSize = 'md',
|
fontSize = 'md',
|
||||||
styleClass,
|
styleClass,
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
return (
|
return (
|
||||||
|
<Tooltip label={tooltip} {...tooltipProps}>
|
||||||
<FormControl
|
<FormControl
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
className={`invokeai__select ${styleClass}`}
|
className={`invokeai__select ${styleClass}`}
|
||||||
@ -67,6 +79,7 @@ const IAISelect = (props: IAISelectProps) => {
|
|||||||
})}
|
})}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipProps,
|
TooltipProps,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import React, { FocusEvent, useEffect, useState } from 'react';
|
import React, { FocusEvent, useEffect, useMemo, useState } from 'react';
|
||||||
import { BiReset } from 'react-icons/bi';
|
import { BiReset } from 'react-icons/bi';
|
||||||
import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton';
|
import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@ -101,6 +101,11 @@ export default function IAISlider(props: IAIFullSliderProps) {
|
|||||||
|
|
||||||
const [localInputValue, setLocalInputValue] = useState<string>(String(value));
|
const [localInputValue, setLocalInputValue] = useState<string>(String(value));
|
||||||
|
|
||||||
|
const numberInputMax = useMemo(
|
||||||
|
() => (sliderNumberInputProps?.max ? sliderNumberInputProps.max : max),
|
||||||
|
[max, sliderNumberInputProps?.max]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (String(value) !== localInputValue && localInputValue !== '') {
|
if (String(value) !== localInputValue && localInputValue !== '') {
|
||||||
setLocalInputValue(String(value));
|
setLocalInputValue(String(value));
|
||||||
@ -108,10 +113,11 @@ export default function IAISlider(props: IAIFullSliderProps) {
|
|||||||
}, [value, localInputValue, setLocalInputValue]);
|
}, [value, localInputValue, setLocalInputValue]);
|
||||||
|
|
||||||
const handleInputBlur = (e: FocusEvent<HTMLInputElement>) => {
|
const handleInputBlur = (e: FocusEvent<HTMLInputElement>) => {
|
||||||
|
console.log(numberInputMax);
|
||||||
const clamped = _.clamp(
|
const clamped = _.clamp(
|
||||||
isInteger ? Math.floor(Number(e.target.value)) : Number(e.target.value),
|
isInteger ? Math.floor(Number(e.target.value)) : Number(e.target.value),
|
||||||
min,
|
min,
|
||||||
max
|
numberInputMax
|
||||||
);
|
);
|
||||||
setLocalInputValue(String(clamped));
|
setLocalInputValue(String(clamped));
|
||||||
onChange(clamped);
|
onChange(clamped);
|
||||||
@ -202,7 +208,7 @@ export default function IAISlider(props: IAIFullSliderProps) {
|
|||||||
{withInput && (
|
{withInput && (
|
||||||
<NumberInput
|
<NumberInput
|
||||||
min={min}
|
min={min}
|
||||||
max={max}
|
max={numberInputMax}
|
||||||
step={step}
|
step={step}
|
||||||
value={localInputValue}
|
value={localInputValue}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
|
@ -84,8 +84,8 @@ const IAICanvasBrushButtonPopover = () => {
|
|||||||
trigger="hover"
|
trigger="hover"
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label="Brush (B)"
|
aria-label="Brush Tool (B)"
|
||||||
tooltip="Brush (B)"
|
tooltip="Brush Tool (B)"
|
||||||
icon={<FaPaintBrush />}
|
icon={<FaPaintBrush />}
|
||||||
data-selected={tool === 'brush' && !isStaging}
|
data-selected={tool === 'brush' && !isStaging}
|
||||||
onClick={handleSelectBrushTool}
|
onClick={handleSelectBrushTool}
|
||||||
|
@ -77,8 +77,8 @@ const IAICanvasEraserButtonPopover = () => {
|
|||||||
trigger="hover"
|
trigger="hover"
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label="Eraser (E)"
|
aria-label="Eraser Tool (E)"
|
||||||
tooltip="Eraser (E)"
|
tooltip="Eraser Tool (E)"
|
||||||
icon={<FaEraser />}
|
icon={<FaEraser />}
|
||||||
data-selected={tool === 'eraser' && !isStaging}
|
data-selected={tool === 'eraser' && !isStaging}
|
||||||
isDisabled={isStaging}
|
isDisabled={isStaging}
|
||||||
|
@ -1,128 +0,0 @@
|
|||||||
import { Flex } from '@chakra-ui/react';
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import {
|
|
||||||
clearMask,
|
|
||||||
setIsMaskEnabled,
|
|
||||||
setLayer,
|
|
||||||
setMaskColor,
|
|
||||||
setShouldPreserveMaskedArea,
|
|
||||||
} from 'features/canvas/store/canvasSlice';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
|
||||||
import { FaMask } from 'react-icons/fa';
|
|
||||||
import IAIPopover from 'common/components/IAIPopover';
|
|
||||||
import IAICheckbox from 'common/components/IAICheckbox';
|
|
||||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
|
||||||
import IAIButton from 'common/components/IAIButton';
|
|
||||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
|
||||||
|
|
||||||
export const selector = createSelector(
|
|
||||||
[canvasSelector],
|
|
||||||
(canvas) => {
|
|
||||||
const { maskColor, layer, isMaskEnabled, shouldPreserveMaskedArea } =
|
|
||||||
canvas;
|
|
||||||
|
|
||||||
return {
|
|
||||||
layer,
|
|
||||||
maskColor,
|
|
||||||
isMaskEnabled,
|
|
||||||
shouldPreserveMaskedArea,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
memoizeOptions: {
|
|
||||||
resultEqualityCheck: _.isEqual,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const IAICanvasMaskButtonPopover = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const { layer, maskColor, isMaskEnabled, shouldPreserveMaskedArea } =
|
|
||||||
useAppSelector(selector);
|
|
||||||
|
|
||||||
useHotkeys(
|
|
||||||
['q'],
|
|
||||||
() => {
|
|
||||||
handleToggleMaskLayer();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: () => true,
|
|
||||||
preventDefault: true,
|
|
||||||
},
|
|
||||||
[layer]
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotkeys(
|
|
||||||
['shift+c'],
|
|
||||||
() => {
|
|
||||||
handleClearMask();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: () => true,
|
|
||||||
preventDefault: true,
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotkeys(
|
|
||||||
['h'],
|
|
||||||
() => {
|
|
||||||
handleToggleEnableMask();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: () => true,
|
|
||||||
preventDefault: true,
|
|
||||||
},
|
|
||||||
[isMaskEnabled]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleToggleMaskLayer = () => {
|
|
||||||
dispatch(setLayer(layer === 'mask' ? 'base' : 'mask'));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClearMask = () => dispatch(clearMask());
|
|
||||||
|
|
||||||
const handleToggleEnableMask = () =>
|
|
||||||
dispatch(setIsMaskEnabled(!isMaskEnabled));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IAIPopover
|
|
||||||
trigger="hover"
|
|
||||||
triggerComponent={
|
|
||||||
<IAIIconButton
|
|
||||||
aria-label="Select Mask Layer (Q)"
|
|
||||||
tooltip="Select Mask Layer (Q)"
|
|
||||||
data-alert={layer === 'mask'}
|
|
||||||
onClick={handleToggleMaskLayer}
|
|
||||||
icon={<FaMask />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Flex direction={'column'} gap={'0.5rem'}>
|
|
||||||
<IAIButton onClick={handleClearMask} tooltip={'Clear Mask (Shift+C)'}>
|
|
||||||
Clear Mask
|
|
||||||
</IAIButton>
|
|
||||||
<IAICheckbox
|
|
||||||
label="Enable Mask (H)"
|
|
||||||
isChecked={isMaskEnabled}
|
|
||||||
onChange={handleToggleEnableMask}
|
|
||||||
/>
|
|
||||||
<IAICheckbox
|
|
||||||
label="Preserve Masked Area"
|
|
||||||
isChecked={shouldPreserveMaskedArea}
|
|
||||||
onChange={(e) =>
|
|
||||||
dispatch(setShouldPreserveMaskedArea(e.target.checked))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<IAIColorPicker
|
|
||||||
color={maskColor}
|
|
||||||
onChange={(newColor) => dispatch(setMaskColor(newColor))}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</IAIPopover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default IAICanvasMaskButtonPopover;
|
|
@ -0,0 +1,153 @@
|
|||||||
|
import { Box, ButtonGroup, Flex } from '@chakra-ui/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import {
|
||||||
|
clearMask,
|
||||||
|
setIsMaskEnabled,
|
||||||
|
setLayer,
|
||||||
|
setMaskColor,
|
||||||
|
setShouldPreserveMaskedArea,
|
||||||
|
} from 'features/canvas/store/canvasSlice';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import { FaMask } from 'react-icons/fa';
|
||||||
|
import IAIPopover from 'common/components/IAIPopover';
|
||||||
|
import IAICheckbox from 'common/components/IAICheckbox';
|
||||||
|
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||||
|
import IAIButton from 'common/components/IAIButton';
|
||||||
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import IAISelect from 'common/components/IAISelect';
|
||||||
|
import {
|
||||||
|
CanvasLayer,
|
||||||
|
LAYER_NAMES_DICT,
|
||||||
|
} from 'features/canvas/store/canvasTypes';
|
||||||
|
import { ChangeEvent } from 'react';
|
||||||
|
import {
|
||||||
|
rgbaColorToRgbString,
|
||||||
|
rgbaColorToString,
|
||||||
|
} from 'features/canvas/util/colorToString';
|
||||||
|
|
||||||
|
export const selector = createSelector(
|
||||||
|
[canvasSelector],
|
||||||
|
(canvas) => {
|
||||||
|
const { maskColor, layer, isMaskEnabled, shouldPreserveMaskedArea } =
|
||||||
|
canvas;
|
||||||
|
|
||||||
|
return {
|
||||||
|
layer,
|
||||||
|
maskColor,
|
||||||
|
maskColorString: rgbaColorToString(maskColor),
|
||||||
|
isMaskEnabled,
|
||||||
|
shouldPreserveMaskedArea,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const IAICanvasMaskOptions = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { layer, maskColor, isMaskEnabled, shouldPreserveMaskedArea } =
|
||||||
|
useAppSelector(selector);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
['q'],
|
||||||
|
() => {
|
||||||
|
handleToggleMaskLayer();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: () => true,
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
[layer]
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
['shift+c'],
|
||||||
|
() => {
|
||||||
|
handleClearMask();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: () => true,
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
['h'],
|
||||||
|
() => {
|
||||||
|
handleToggleEnableMask();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: () => true,
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
[isMaskEnabled]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleToggleMaskLayer = () => {
|
||||||
|
dispatch(setLayer(layer === 'mask' ? 'base' : 'mask'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearMask = () => dispatch(clearMask());
|
||||||
|
|
||||||
|
const handleToggleEnableMask = () =>
|
||||||
|
dispatch(setIsMaskEnabled(!isMaskEnabled));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IAISelect
|
||||||
|
label={'Layer'}
|
||||||
|
tooltipProps={{ hasArrow: true, placement: 'top' }}
|
||||||
|
value={isMaskEnabled ? layer : 'base'}
|
||||||
|
isDisabled={!isMaskEnabled}
|
||||||
|
validValues={LAYER_NAMES_DICT}
|
||||||
|
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
|
||||||
|
dispatch(setLayer(e.target.value as CanvasLayer))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<IAIPopover
|
||||||
|
trigger="hover"
|
||||||
|
triggerComponent={
|
||||||
|
<ButtonGroup>
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Inpainting Mask Options"
|
||||||
|
tooltip="Inpainting Mask Options"
|
||||||
|
data-alert={layer === 'mask'}
|
||||||
|
icon={<FaMask />}
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Flex direction={'column'} gap={'0.5rem'}>
|
||||||
|
<IAICheckbox
|
||||||
|
label="Enable Mask (H)"
|
||||||
|
isChecked={isMaskEnabled}
|
||||||
|
onChange={handleToggleEnableMask}
|
||||||
|
/>
|
||||||
|
<IAICheckbox
|
||||||
|
label="Preserve Masked Area"
|
||||||
|
isChecked={shouldPreserveMaskedArea}
|
||||||
|
onChange={(e) =>
|
||||||
|
dispatch(setShouldPreserveMaskedArea(e.target.checked))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<IAIColorPicker
|
||||||
|
style={{ paddingTop: '0.5rem', paddingBottom: '0.5rem' }}
|
||||||
|
color={maskColor}
|
||||||
|
onChange={(newColor) => dispatch(setMaskColor(newColor))}
|
||||||
|
/>
|
||||||
|
<IAIButton onClick={handleClearMask} tooltip={'Clear Mask (Shift+C)'}>
|
||||||
|
Clear Mask
|
||||||
|
</IAIButton>
|
||||||
|
</Flex>
|
||||||
|
</IAIPopover>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAICanvasMaskOptions;
|
@ -60,8 +60,6 @@ const IAICanvasSettingsButtonPopover = () => {
|
|||||||
trigger="hover"
|
trigger="hover"
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
variant="link"
|
|
||||||
data-variant="link"
|
|
||||||
tooltip="Canvas Settings"
|
tooltip="Canvas Settings"
|
||||||
aria-label="Canvas Settings"
|
aria-label="Canvas Settings"
|
||||||
icon={<FaWrench />}
|
icon={<FaWrench />}
|
||||||
|
@ -0,0 +1,191 @@
|
|||||||
|
import { ButtonGroup, Flex } from '@chakra-ui/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import {
|
||||||
|
resetCanvas,
|
||||||
|
resetCanvasView,
|
||||||
|
resizeAndScaleCanvas,
|
||||||
|
setBrushColor,
|
||||||
|
setBrushSize,
|
||||||
|
setTool,
|
||||||
|
} from 'features/canvas/store/canvasSlice';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import {
|
||||||
|
FaArrowsAlt,
|
||||||
|
FaEraser,
|
||||||
|
FaPaintBrush,
|
||||||
|
FaSlidersH,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import {
|
||||||
|
canvasSelector,
|
||||||
|
isStagingSelector,
|
||||||
|
} from 'features/canvas/store/canvasSelectors';
|
||||||
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
|
import IAICanvasBrushButtonPopover from './IAICanvasBrushButtonPopover';
|
||||||
|
import IAICanvasEraserButtonPopover from './IAICanvasEraserButtonPopover';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import IAIPopover from 'common/components/IAIPopover';
|
||||||
|
import IAISlider from 'common/components/IAISlider';
|
||||||
|
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||||
|
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||||
|
|
||||||
|
export const selector = createSelector(
|
||||||
|
[canvasSelector, isStagingSelector, systemSelector],
|
||||||
|
(canvas, isStaging, system) => {
|
||||||
|
const { isProcessing } = system;
|
||||||
|
const { tool, brushColor, brushSize } = canvas;
|
||||||
|
|
||||||
|
return {
|
||||||
|
tool,
|
||||||
|
isStaging,
|
||||||
|
isProcessing,
|
||||||
|
brushColor,
|
||||||
|
brushColorString: rgbaColorToString(brushColor),
|
||||||
|
brushSize,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const IAICanvasToolChooserOptions = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { tool, brushColor, brushSize, brushColorString, isStaging } =
|
||||||
|
useAppSelector(selector);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
['v'],
|
||||||
|
() => {
|
||||||
|
handleSelectMoveTool();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: () => true,
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
['b'],
|
||||||
|
() => {
|
||||||
|
handleSelectBrushTool();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: () => true,
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
['e'],
|
||||||
|
() => {
|
||||||
|
handleSelectEraserTool();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: () => true,
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
[tool]
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
['['],
|
||||||
|
() => {
|
||||||
|
dispatch(setBrushSize(Math.max(brushSize - 5, 5)));
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: () => true,
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
[brushSize]
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
[']'],
|
||||||
|
() => {
|
||||||
|
dispatch(setBrushSize(Math.min(brushSize + 5, 500)));
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: () => true,
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
[brushSize]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelectBrushTool = () => dispatch(setTool('brush'));
|
||||||
|
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
|
||||||
|
const handleSelectMoveTool = () => dispatch(setTool('move'));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonGroup isAttached>
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Brush Tool (B)"
|
||||||
|
tooltip="Brush Tool (B)"
|
||||||
|
icon={<FaPaintBrush />}
|
||||||
|
data-selected={tool === 'brush' && !isStaging}
|
||||||
|
onClick={handleSelectBrushTool}
|
||||||
|
isDisabled={isStaging}
|
||||||
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Eraser Tool (E)"
|
||||||
|
tooltip="Eraser Tool (E)"
|
||||||
|
icon={<FaEraser />}
|
||||||
|
data-selected={tool === 'eraser' && !isStaging}
|
||||||
|
isDisabled={isStaging}
|
||||||
|
onClick={() => dispatch(setTool('eraser'))}
|
||||||
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Move Tool (V)"
|
||||||
|
tooltip="Move Tool (V)"
|
||||||
|
icon={<FaArrowsAlt />}
|
||||||
|
data-selected={tool === 'move' || isStaging}
|
||||||
|
onClick={handleSelectMoveTool}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<IAIPopover
|
||||||
|
trigger="hover"
|
||||||
|
triggerComponent={
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Tool Options"
|
||||||
|
tooltip="Tool Options"
|
||||||
|
icon={<FaSlidersH />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
minWidth={'15rem'}
|
||||||
|
direction={'column'}
|
||||||
|
gap={'1rem'}
|
||||||
|
width={'100%'}
|
||||||
|
>
|
||||||
|
<Flex gap={'1rem'} justifyContent="space-between">
|
||||||
|
<IAISlider
|
||||||
|
label="Size"
|
||||||
|
value={brushSize}
|
||||||
|
withInput
|
||||||
|
onChange={(newSize) => dispatch(setBrushSize(newSize))}
|
||||||
|
sliderNumberInputProps={{ max: 500 }}
|
||||||
|
inputReadOnly={false}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<IAIColorPicker
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
paddingTop: '0.5rem',
|
||||||
|
paddingBottom: '0.5rem',
|
||||||
|
}}
|
||||||
|
color={brushColor}
|
||||||
|
onChange={(newColor) => dispatch(setBrushColor(newColor))}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</IAIPopover>
|
||||||
|
</ButtonGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAICanvasToolChooserOptions;
|
@ -16,6 +16,7 @@ import {
|
|||||||
FaDownload,
|
FaDownload,
|
||||||
FaLayerGroup,
|
FaLayerGroup,
|
||||||
FaSave,
|
FaSave,
|
||||||
|
FaSlidersH,
|
||||||
FaTrash,
|
FaTrash,
|
||||||
FaUpload,
|
FaUpload,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
@ -24,7 +25,7 @@ import IAICanvasRedoButton from './IAICanvasRedoButton';
|
|||||||
import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover';
|
import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover';
|
||||||
import IAICanvasEraserButtonPopover from './IAICanvasEraserButtonPopover';
|
import IAICanvasEraserButtonPopover from './IAICanvasEraserButtonPopover';
|
||||||
import IAICanvasBrushButtonPopover from './IAICanvasBrushButtonPopover';
|
import IAICanvasBrushButtonPopover from './IAICanvasBrushButtonPopover';
|
||||||
import IAICanvasMaskButtonPopover from './IAICanvasMaskButtonPopover';
|
import IAICanvasMaskOptions from './IAICanvasMaskOptions';
|
||||||
import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas';
|
import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas';
|
||||||
import {
|
import {
|
||||||
canvasSelector,
|
canvasSelector,
|
||||||
@ -33,6 +34,7 @@ import {
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
|
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
|
||||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
|
import IAICanvasToolChooserOptions from './IAICanvasToolChooserOptions';
|
||||||
|
|
||||||
export const selector = createSelector(
|
export const selector = createSelector(
|
||||||
[canvasSelector, isStagingSelector, systemSelector],
|
[canvasSelector, isStagingSelector, systemSelector],
|
||||||
@ -58,18 +60,6 @@ const IAICanvasOutpaintingControls = () => {
|
|||||||
const { tool, isStaging, isProcessing } = useAppSelector(selector);
|
const { tool, isStaging, isProcessing } = useAppSelector(selector);
|
||||||
const canvasBaseLayer = getCanvasBaseLayer();
|
const canvasBaseLayer = getCanvasBaseLayer();
|
||||||
|
|
||||||
useHotkeys(
|
|
||||||
['v'],
|
|
||||||
() => {
|
|
||||||
handleSelectMoveTool();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: () => true,
|
|
||||||
preventDefault: true,
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
['r'],
|
['r'],
|
||||||
() => {
|
() => {
|
||||||
@ -130,8 +120,6 @@ const IAICanvasOutpaintingControls = () => {
|
|||||||
[canvasBaseLayer, isProcessing]
|
[canvasBaseLayer, isProcessing]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelectMoveTool = () => dispatch(setTool('move'));
|
|
||||||
|
|
||||||
const handleResetCanvasView = () => {
|
const handleResetCanvasView = () => {
|
||||||
const canvasBaseLayer = getCanvasBaseLayer();
|
const canvasBaseLayer = getCanvasBaseLayer();
|
||||||
if (!canvasBaseLayer) return;
|
if (!canvasBaseLayer) return;
|
||||||
@ -188,18 +176,9 @@ const IAICanvasOutpaintingControls = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inpainting-settings">
|
<div className="inpainting-settings">
|
||||||
<IAICanvasMaskButtonPopover />
|
<IAICanvasMaskOptions />
|
||||||
<ButtonGroup isAttached>
|
<IAICanvasToolChooserOptions />
|
||||||
<IAICanvasBrushButtonPopover />
|
|
||||||
<IAICanvasEraserButtonPopover />
|
|
||||||
<IAIIconButton
|
|
||||||
aria-label="Move (V)"
|
|
||||||
tooltip="Move (V)"
|
|
||||||
icon={<FaArrowsAlt />}
|
|
||||||
data-selected={tool === 'move' || isStaging}
|
|
||||||
onClick={handleSelectMoveTool}
|
|
||||||
/>
|
|
||||||
</ButtonGroup>
|
|
||||||
<ButtonGroup isAttached>
|
<ButtonGroup isAttached>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label="Merge Visible (Shift+M)"
|
aria-label="Merge Visible (Shift+M)"
|
||||||
@ -234,9 +213,7 @@ const IAICanvasOutpaintingControls = () => {
|
|||||||
<IAICanvasUndoButton />
|
<IAICanvasUndoButton />
|
||||||
<IAICanvasRedoButton />
|
<IAICanvasRedoButton />
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<ButtonGroup isAttached>
|
|
||||||
<IAICanvasSettingsButtonPopover />
|
|
||||||
</ButtonGroup>
|
|
||||||
<ButtonGroup isAttached>
|
<ButtonGroup isAttached>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label="Upload"
|
aria-label="Upload"
|
||||||
@ -250,12 +227,16 @@ const IAICanvasOutpaintingControls = () => {
|
|||||||
onClick={handleResetCanvasView}
|
onClick={handleResetCanvasView}
|
||||||
/>
|
/>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label="Reset Canvas"
|
aria-label="Clear Canvas"
|
||||||
tooltip="Reset Canvas"
|
tooltip="Clear Canvas"
|
||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
onClick={handleResetCanvas}
|
onClick={handleResetCanvas}
|
||||||
|
style={{ backgroundColor: 'var(--btn-delete-image)' }}
|
||||||
/>
|
/>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
<ButtonGroup isAttached>
|
||||||
|
<IAICanvasSettingsButtonPopover />
|
||||||
|
</ButtonGroup>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
import * as InvokeAI from 'app/invokeai';
|
import * as InvokeAI from 'app/invokeai';
|
||||||
import { IRect, Vector2d } from 'konva/lib/types';
|
import { Vector2d } from 'konva/lib/types';
|
||||||
import { RgbaColor } from 'react-colorful';
|
import { RgbaColor } from 'react-colorful';
|
||||||
|
|
||||||
export type CanvasLayer = 'base' | 'mask';
|
export const LAYER_NAMES_DICT = [
|
||||||
|
{ key: 'Base', value: 'base' },
|
||||||
|
{ key: 'Mask', value: 'mask' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const LAYER_NAMES = ['base', 'mask'] as const;
|
||||||
|
|
||||||
|
export type CanvasLayer = typeof LAYER_NAMES[number];
|
||||||
|
|
||||||
export type CanvasDrawingTool = 'brush' | 'eraser';
|
export type CanvasDrawingTool = 'brush' | 'eraser';
|
||||||
|
|
||||||
|
@ -488,7 +488,7 @@ const CurrentImageButtons = () => {
|
|||||||
tooltip="Delete Image"
|
tooltip="Delete Image"
|
||||||
aria-label="Delete Image"
|
aria-label="Delete Image"
|
||||||
isDisabled={!currentImage || !isConnected || isProcessing}
|
isDisabled={!currentImage || !isConnected || isProcessing}
|
||||||
className="delete-image-btn"
|
style={{ backgroundColor: 'var(--btn-delete-image)' }}
|
||||||
/>
|
/>
|
||||||
</DeleteImageModal>
|
</DeleteImageModal>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user