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';
|
||||
|
||||
type IAISelectProps = SelectProps & {
|
||||
label?: string;
|
||||
styleClass?: string;
|
||||
tooltip?: string;
|
||||
tooltipProps?: Omit<TooltipProps, 'children'>;
|
||||
validValues:
|
||||
| Array<number | string>
|
||||
| Array<{ key: string; value: string | number }>;
|
||||
@ -16,57 +25,61 @@ const IAISelect = (props: IAISelectProps) => {
|
||||
label,
|
||||
isDisabled,
|
||||
validValues,
|
||||
tooltip,
|
||||
tooltipProps,
|
||||
size = 'sm',
|
||||
fontSize = 'md',
|
||||
styleClass,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<FormControl
|
||||
isDisabled={isDisabled}
|
||||
className={`invokeai__select ${styleClass}`}
|
||||
onClick={(e: MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
e.nativeEvent.stopPropagation();
|
||||
e.nativeEvent.cancelBubble = true;
|
||||
}}
|
||||
>
|
||||
{label && (
|
||||
<FormLabel
|
||||
className="invokeai__select-label"
|
||||
fontSize={fontSize}
|
||||
marginBottom={1}
|
||||
flexGrow={2}
|
||||
whiteSpace="nowrap"
|
||||
>
|
||||
{label}
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<Select
|
||||
className="invokeai__select-picker"
|
||||
fontSize={fontSize}
|
||||
size={size}
|
||||
{...rest}
|
||||
<Tooltip label={tooltip} {...tooltipProps}>
|
||||
<FormControl
|
||||
isDisabled={isDisabled}
|
||||
className={`invokeai__select ${styleClass}`}
|
||||
onClick={(e: MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
e.nativeEvent.stopPropagation();
|
||||
e.nativeEvent.cancelBubble = true;
|
||||
}}
|
||||
>
|
||||
{validValues.map((opt) => {
|
||||
return typeof opt === 'string' || typeof opt === 'number' ? (
|
||||
<option key={opt} value={opt} className="invokeai__select-option">
|
||||
{opt}
|
||||
</option>
|
||||
) : (
|
||||
<option
|
||||
key={opt.value}
|
||||
value={opt.value}
|
||||
className="invokeai__select-option"
|
||||
>
|
||||
{opt.key}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{label && (
|
||||
<FormLabel
|
||||
className="invokeai__select-label"
|
||||
fontSize={fontSize}
|
||||
marginBottom={1}
|
||||
flexGrow={2}
|
||||
whiteSpace="nowrap"
|
||||
>
|
||||
{label}
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<Select
|
||||
className="invokeai__select-picker"
|
||||
fontSize={fontSize}
|
||||
size={size}
|
||||
{...rest}
|
||||
>
|
||||
{validValues.map((opt) => {
|
||||
return typeof opt === 'string' || typeof opt === 'number' ? (
|
||||
<option key={opt} value={opt} className="invokeai__select-option">
|
||||
{opt}
|
||||
</option>
|
||||
) : (
|
||||
<option
|
||||
key={opt.value}
|
||||
value={opt.value}
|
||||
className="invokeai__select-option"
|
||||
>
|
||||
{opt.key}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
Tooltip,
|
||||
TooltipProps,
|
||||
} 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 IAIIconButton, { IAIIconButtonProps } from './IAIIconButton';
|
||||
import _ from 'lodash';
|
||||
@ -101,6 +101,11 @@ export default function IAISlider(props: IAIFullSliderProps) {
|
||||
|
||||
const [localInputValue, setLocalInputValue] = useState<string>(String(value));
|
||||
|
||||
const numberInputMax = useMemo(
|
||||
() => (sliderNumberInputProps?.max ? sliderNumberInputProps.max : max),
|
||||
[max, sliderNumberInputProps?.max]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (String(value) !== localInputValue && localInputValue !== '') {
|
||||
setLocalInputValue(String(value));
|
||||
@ -108,10 +113,11 @@ export default function IAISlider(props: IAIFullSliderProps) {
|
||||
}, [value, localInputValue, setLocalInputValue]);
|
||||
|
||||
const handleInputBlur = (e: FocusEvent<HTMLInputElement>) => {
|
||||
console.log(numberInputMax);
|
||||
const clamped = _.clamp(
|
||||
isInteger ? Math.floor(Number(e.target.value)) : Number(e.target.value),
|
||||
min,
|
||||
max
|
||||
numberInputMax
|
||||
);
|
||||
setLocalInputValue(String(clamped));
|
||||
onChange(clamped);
|
||||
@ -202,7 +208,7 @@ export default function IAISlider(props: IAIFullSliderProps) {
|
||||
{withInput && (
|
||||
<NumberInput
|
||||
min={min}
|
||||
max={max}
|
||||
max={numberInputMax}
|
||||
step={step}
|
||||
value={localInputValue}
|
||||
onChange={handleInputChange}
|
||||
|
@ -84,8 +84,8 @@ const IAICanvasBrushButtonPopover = () => {
|
||||
trigger="hover"
|
||||
triggerComponent={
|
||||
<IAIIconButton
|
||||
aria-label="Brush (B)"
|
||||
tooltip="Brush (B)"
|
||||
aria-label="Brush Tool (B)"
|
||||
tooltip="Brush Tool (B)"
|
||||
icon={<FaPaintBrush />}
|
||||
data-selected={tool === 'brush' && !isStaging}
|
||||
onClick={handleSelectBrushTool}
|
||||
|
@ -77,8 +77,8 @@ const IAICanvasEraserButtonPopover = () => {
|
||||
trigger="hover"
|
||||
triggerComponent={
|
||||
<IAIIconButton
|
||||
aria-label="Eraser (E)"
|
||||
tooltip="Eraser (E)"
|
||||
aria-label="Eraser Tool (E)"
|
||||
tooltip="Eraser Tool (E)"
|
||||
icon={<FaEraser />}
|
||||
data-selected={tool === 'eraser' && !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"
|
||||
triggerComponent={
|
||||
<IAIIconButton
|
||||
variant="link"
|
||||
data-variant="link"
|
||||
tooltip="Canvas Settings"
|
||||
aria-label="Canvas Settings"
|
||||
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,
|
||||
FaLayerGroup,
|
||||
FaSave,
|
||||
FaSlidersH,
|
||||
FaTrash,
|
||||
FaUpload,
|
||||
} from 'react-icons/fa';
|
||||
@ -24,7 +25,7 @@ import IAICanvasRedoButton from './IAICanvasRedoButton';
|
||||
import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover';
|
||||
import IAICanvasEraserButtonPopover from './IAICanvasEraserButtonPopover';
|
||||
import IAICanvasBrushButtonPopover from './IAICanvasBrushButtonPopover';
|
||||
import IAICanvasMaskButtonPopover from './IAICanvasMaskButtonPopover';
|
||||
import IAICanvasMaskOptions from './IAICanvasMaskOptions';
|
||||
import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas';
|
||||
import {
|
||||
canvasSelector,
|
||||
@ -33,6 +34,7 @@ import {
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import IAICanvasToolChooserOptions from './IAICanvasToolChooserOptions';
|
||||
|
||||
export const selector = createSelector(
|
||||
[canvasSelector, isStagingSelector, systemSelector],
|
||||
@ -58,18 +60,6 @@ const IAICanvasOutpaintingControls = () => {
|
||||
const { tool, isStaging, isProcessing } = useAppSelector(selector);
|
||||
const canvasBaseLayer = getCanvasBaseLayer();
|
||||
|
||||
useHotkeys(
|
||||
['v'],
|
||||
() => {
|
||||
handleSelectMoveTool();
|
||||
},
|
||||
{
|
||||
enabled: () => true,
|
||||
preventDefault: true,
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
['r'],
|
||||
() => {
|
||||
@ -130,8 +120,6 @@ const IAICanvasOutpaintingControls = () => {
|
||||
[canvasBaseLayer, isProcessing]
|
||||
);
|
||||
|
||||
const handleSelectMoveTool = () => dispatch(setTool('move'));
|
||||
|
||||
const handleResetCanvasView = () => {
|
||||
const canvasBaseLayer = getCanvasBaseLayer();
|
||||
if (!canvasBaseLayer) return;
|
||||
@ -188,18 +176,9 @@ const IAICanvasOutpaintingControls = () => {
|
||||
|
||||
return (
|
||||
<div className="inpainting-settings">
|
||||
<IAICanvasMaskButtonPopover />
|
||||
<ButtonGroup isAttached>
|
||||
<IAICanvasBrushButtonPopover />
|
||||
<IAICanvasEraserButtonPopover />
|
||||
<IAIIconButton
|
||||
aria-label="Move (V)"
|
||||
tooltip="Move (V)"
|
||||
icon={<FaArrowsAlt />}
|
||||
data-selected={tool === 'move' || isStaging}
|
||||
onClick={handleSelectMoveTool}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<IAICanvasMaskOptions />
|
||||
<IAICanvasToolChooserOptions />
|
||||
|
||||
<ButtonGroup isAttached>
|
||||
<IAIIconButton
|
||||
aria-label="Merge Visible (Shift+M)"
|
||||
@ -234,9 +213,7 @@ const IAICanvasOutpaintingControls = () => {
|
||||
<IAICanvasUndoButton />
|
||||
<IAICanvasRedoButton />
|
||||
</ButtonGroup>
|
||||
<ButtonGroup isAttached>
|
||||
<IAICanvasSettingsButtonPopover />
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup isAttached>
|
||||
<IAIIconButton
|
||||
aria-label="Upload"
|
||||
@ -250,12 +227,16 @@ const IAICanvasOutpaintingControls = () => {
|
||||
onClick={handleResetCanvasView}
|
||||
/>
|
||||
<IAIIconButton
|
||||
aria-label="Reset Canvas"
|
||||
tooltip="Reset Canvas"
|
||||
aria-label="Clear Canvas"
|
||||
tooltip="Clear Canvas"
|
||||
icon={<FaTrash />}
|
||||
onClick={handleResetCanvas}
|
||||
style={{ backgroundColor: 'var(--btn-delete-image)' }}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup isAttached>
|
||||
<IAICanvasSettingsButtonPopover />
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,8 +1,15 @@
|
||||
import * as InvokeAI from 'app/invokeai';
|
||||
import { IRect, Vector2d } from 'konva/lib/types';
|
||||
import { Vector2d } from 'konva/lib/types';
|
||||
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';
|
||||
|
||||
|
@ -488,7 +488,7 @@ const CurrentImageButtons = () => {
|
||||
tooltip="Delete Image"
|
||||
aria-label="Delete Image"
|
||||
isDisabled={!currentImage || !isConnected || isProcessing}
|
||||
className="delete-image-btn"
|
||||
style={{ backgroundColor: 'var(--btn-delete-image)' }}
|
||||
/>
|
||||
</DeleteImageModal>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user