mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Adds color picker
This commit is contained in:
parent
d44112c209
commit
9f1c1cf2e6
@ -7,7 +7,7 @@ import {
|
||||
isStagingSelector,
|
||||
} from 'features/canvas/store/canvasSelectors';
|
||||
import IAICanvasMaskLines from './IAICanvasMaskLines';
|
||||
import IAICanvasBrushPreview from './IAICanvasBrushPreview';
|
||||
import IAICanvasToolPreview from './IAICanvasToolPreview';
|
||||
import { Vector2d } from 'konva/lib/types';
|
||||
import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox';
|
||||
import useCanvasHotkeys from '../hooks/useCanvasHotkeys';
|
||||
@ -183,7 +183,7 @@ const IAICanvas = () => {
|
||||
</Layer>
|
||||
<Layer id="preview" imageSmoothingEnabled={false}>
|
||||
{!isStaging && (
|
||||
<IAICanvasBrushPreview
|
||||
<IAICanvasToolPreview
|
||||
visible={tool !== 'move'}
|
||||
listening={false}
|
||||
/>
|
||||
|
@ -1,120 +0,0 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { GroupConfig } from 'konva/lib/Group';
|
||||
import _ from 'lodash';
|
||||
import { Circle, Group } from 'react-konva';
|
||||
import { useAppSelector } from 'app/store';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||
|
||||
const canvasBrushPreviewSelector = createSelector(
|
||||
canvasSelector,
|
||||
(canvas) => {
|
||||
const {
|
||||
cursorPosition,
|
||||
stageDimensions: { width, height },
|
||||
brushSize,
|
||||
maskColor,
|
||||
brushColor,
|
||||
tool,
|
||||
layer,
|
||||
shouldShowBrush,
|
||||
isMovingBoundingBox,
|
||||
isTransformingBoundingBox,
|
||||
stageScale,
|
||||
} = canvas;
|
||||
|
||||
return {
|
||||
cursorPosition,
|
||||
width,
|
||||
height,
|
||||
radius: brushSize / 2,
|
||||
brushColorString: rgbaColorToString(
|
||||
layer === 'mask' ? { ...maskColor, a: 0.5 } : brushColor
|
||||
),
|
||||
tool,
|
||||
shouldShowBrush,
|
||||
shouldDrawBrushPreview:
|
||||
!(
|
||||
isMovingBoundingBox ||
|
||||
isTransformingBoundingBox ||
|
||||
!cursorPosition
|
||||
) && shouldShowBrush,
|
||||
strokeWidth: 1.5 / stageScale,
|
||||
dotRadius: 1.5 / stageScale,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: _.isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Draws a black circle around the canvas brush preview.
|
||||
*/
|
||||
const IAICanvasBrushPreview = (props: GroupConfig) => {
|
||||
const { ...rest } = props;
|
||||
const {
|
||||
cursorPosition,
|
||||
width,
|
||||
height,
|
||||
radius,
|
||||
brushColorString,
|
||||
tool,
|
||||
shouldDrawBrushPreview,
|
||||
dotRadius,
|
||||
strokeWidth,
|
||||
} = useAppSelector(canvasBrushPreviewSelector);
|
||||
|
||||
if (!shouldDrawBrushPreview) return null;
|
||||
|
||||
return (
|
||||
<Group listening={false} {...rest}>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
radius={radius}
|
||||
fill={brushColorString}
|
||||
listening={false}
|
||||
globalCompositeOperation={
|
||||
tool === 'eraser' ? 'destination-out' : 'source-over'
|
||||
}
|
||||
/>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
radius={radius}
|
||||
stroke={'rgba(255,255,255,0.4)'}
|
||||
strokeWidth={strokeWidth * 2}
|
||||
strokeEnabled={true}
|
||||
listening={false}
|
||||
/>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
radius={radius}
|
||||
stroke={'rgba(0,0,0,1)'}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeEnabled={true}
|
||||
listening={false}
|
||||
/>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
radius={dotRadius * 2}
|
||||
fill={'rgba(255,255,255,0.4)'}
|
||||
listening={false}
|
||||
/>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
radius={dotRadius}
|
||||
fill={'rgba(0,0,0,1)'}
|
||||
listening={false}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export default IAICanvasBrushPreview;
|
185
frontend/src/features/canvas/components/IAICanvasToolPreview.tsx
Normal file
185
frontend/src/features/canvas/components/IAICanvasToolPreview.tsx
Normal file
@ -0,0 +1,185 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { GroupConfig } from 'konva/lib/Group';
|
||||
import _ from 'lodash';
|
||||
import { Circle, Group, Rect } from 'react-konva';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||
import { COLOR_PICKER_SIZE } from '../util/constants';
|
||||
|
||||
const canvasBrushPreviewSelector = createSelector(
|
||||
canvasSelector,
|
||||
(canvas) => {
|
||||
const {
|
||||
cursorPosition,
|
||||
stageDimensions: { width, height },
|
||||
brushSize,
|
||||
colorPickerColor,
|
||||
maskColor,
|
||||
brushColor,
|
||||
tool,
|
||||
layer,
|
||||
shouldShowBrush,
|
||||
isMovingBoundingBox,
|
||||
isTransformingBoundingBox,
|
||||
stageScale,
|
||||
} = canvas;
|
||||
|
||||
let fill = '';
|
||||
|
||||
if (layer === 'mask') {
|
||||
fill = rgbaColorToString({ ...maskColor, a: 0.5 });
|
||||
} else if (tool === 'colorPicker') {
|
||||
fill = rgbaColorToString(colorPickerColor);
|
||||
} else {
|
||||
fill = rgbaColorToString(brushColor);
|
||||
}
|
||||
|
||||
return {
|
||||
cursorPosition,
|
||||
width,
|
||||
height,
|
||||
radius: brushSize / 2,
|
||||
colorPickerSize: COLOR_PICKER_SIZE / stageScale,
|
||||
colorPickerOffset: COLOR_PICKER_SIZE / 2 / stageScale,
|
||||
colorPickerCornerRadius: COLOR_PICKER_SIZE / 5 / stageScale,
|
||||
brushColorString: fill,
|
||||
tool,
|
||||
shouldShowBrush,
|
||||
shouldDrawBrushPreview:
|
||||
!(
|
||||
isMovingBoundingBox ||
|
||||
isTransformingBoundingBox ||
|
||||
!cursorPosition
|
||||
) && shouldShowBrush,
|
||||
strokeWidth: 1.5 / stageScale,
|
||||
dotRadius: 1.5 / stageScale,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: _.isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Draws a black circle around the canvas brush preview.
|
||||
*/
|
||||
const IAICanvasToolPreview = (props: GroupConfig) => {
|
||||
const { ...rest } = props;
|
||||
const {
|
||||
cursorPosition,
|
||||
width,
|
||||
height,
|
||||
radius,
|
||||
brushColorString,
|
||||
tool,
|
||||
shouldDrawBrushPreview,
|
||||
dotRadius,
|
||||
strokeWidth,
|
||||
colorPickerSize,
|
||||
colorPickerOffset,
|
||||
colorPickerCornerRadius,
|
||||
} = useAppSelector(canvasBrushPreviewSelector);
|
||||
|
||||
if (!shouldDrawBrushPreview) return null;
|
||||
|
||||
return (
|
||||
<Group listening={false} {...rest}>
|
||||
{tool === 'colorPicker' ? (
|
||||
<>
|
||||
<Rect
|
||||
x={
|
||||
cursorPosition ? cursorPosition.x - colorPickerOffset : width / 2
|
||||
}
|
||||
y={
|
||||
cursorPosition ? cursorPosition.y - colorPickerOffset : height / 2
|
||||
}
|
||||
width={colorPickerSize}
|
||||
height={colorPickerSize}
|
||||
fill={brushColorString}
|
||||
cornerRadius={colorPickerCornerRadius}
|
||||
listening={false}
|
||||
/>
|
||||
<Rect
|
||||
x={
|
||||
cursorPosition ? cursorPosition.x - colorPickerOffset : width / 2
|
||||
}
|
||||
y={
|
||||
cursorPosition ? cursorPosition.y - colorPickerOffset : height / 2
|
||||
}
|
||||
width={colorPickerSize}
|
||||
height={colorPickerSize}
|
||||
cornerRadius={colorPickerCornerRadius}
|
||||
stroke={'rgba(255,255,255,0.4)'}
|
||||
strokeWidth={strokeWidth * 2}
|
||||
strokeEnabled={true}
|
||||
listening={false}
|
||||
/>
|
||||
<Rect
|
||||
x={
|
||||
cursorPosition ? cursorPosition.x - colorPickerOffset : width / 2
|
||||
}
|
||||
y={
|
||||
cursorPosition ? cursorPosition.y - colorPickerOffset : height / 2
|
||||
}
|
||||
width={colorPickerSize}
|
||||
height={colorPickerSize}
|
||||
cornerRadius={colorPickerCornerRadius}
|
||||
stroke={'rgba(0,0,0,1)'}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeEnabled={true}
|
||||
listening={false}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
radius={radius}
|
||||
fill={brushColorString}
|
||||
globalCompositeOperation={
|
||||
tool === 'eraser' ? 'destination-out' : 'source-over'
|
||||
}
|
||||
/>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
radius={radius}
|
||||
stroke={'rgba(255,255,255,0.4)'}
|
||||
strokeWidth={strokeWidth * 2}
|
||||
strokeEnabled={true}
|
||||
listening={false}
|
||||
/>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
radius={radius}
|
||||
stroke={'rgba(0,0,0,1)'}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeEnabled={true}
|
||||
listening={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
radius={dotRadius * 2}
|
||||
fill={'rgba(255,255,255,0.4)'}
|
||||
listening={false}
|
||||
/>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
radius={dotRadius}
|
||||
fill={'rgba(0,0,0,1)'}
|
||||
listening={false}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export default IAICanvasToolPreview;
|
@ -8,7 +8,12 @@ import {
|
||||
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||
import _ from 'lodash';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { FaEraser, FaPaintBrush, FaSlidersH } from 'react-icons/fa';
|
||||
import {
|
||||
FaEraser,
|
||||
FaEyeDropper,
|
||||
FaPaintBrush,
|
||||
FaSlidersH,
|
||||
} from 'react-icons/fa';
|
||||
import {
|
||||
canvasSelector,
|
||||
isStagingSelector,
|
||||
@ -68,6 +73,18 @@ const IAICanvasToolChooserOptions = () => {
|
||||
[tool]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
['c'],
|
||||
() => {
|
||||
handleSelectColorPickerTool();
|
||||
},
|
||||
{
|
||||
enabled: () => true,
|
||||
preventDefault: true,
|
||||
},
|
||||
[tool]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
['['],
|
||||
() => {
|
||||
@ -94,6 +111,7 @@ const IAICanvasToolChooserOptions = () => {
|
||||
|
||||
const handleSelectBrushTool = () => dispatch(setTool('brush'));
|
||||
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
|
||||
const handleSelectColorPickerTool = () => dispatch(setTool('colorPicker'));
|
||||
|
||||
return (
|
||||
<ButtonGroup isAttached>
|
||||
@ -111,7 +129,15 @@ const IAICanvasToolChooserOptions = () => {
|
||||
icon={<FaEraser />}
|
||||
data-selected={tool === 'eraser' && !isStaging}
|
||||
isDisabled={isStaging}
|
||||
onClick={() => dispatch(setTool('eraser'))}
|
||||
onClick={handleSelectEraserTool}
|
||||
/>
|
||||
<IAIIconButton
|
||||
aria-label="Color Picker (C)"
|
||||
tooltip="Color Picker (C)"
|
||||
icon={<FaEyeDropper />}
|
||||
data-selected={tool === 'colorPicker' && !isStaging}
|
||||
isDisabled={isStaging}
|
||||
onClick={handleSelectColorPickerTool}
|
||||
/>
|
||||
<IAIPopover
|
||||
trigger="hover"
|
||||
|
@ -5,13 +5,19 @@ import Konva from 'konva';
|
||||
import { KonvaEventObject } from 'konva/lib/Node';
|
||||
import _ from 'lodash';
|
||||
import { MutableRefObject, useCallback } from 'react';
|
||||
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
canvasSelector,
|
||||
isStagingSelector,
|
||||
} from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
addLine,
|
||||
commitColorPickerColor,
|
||||
setIsDrawing,
|
||||
setIsMovingStage,
|
||||
setTool,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import getScaledCursorPosition from '../util/getScaledCursorPosition';
|
||||
import useColorPicker from './useColorUnderCursor';
|
||||
|
||||
const selector = createSelector(
|
||||
[activeTabNameSelector, canvasSelector, isStagingSelector],
|
||||
@ -29,6 +35,7 @@ const selector = createSelector(
|
||||
const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { tool, isStaging } = useAppSelector(selector);
|
||||
const { commitColorUnderCursor } = useColorPicker();
|
||||
|
||||
return useCallback(
|
||||
(e: KonvaEventObject<MouseEvent | TouchEvent>) => {
|
||||
@ -41,6 +48,11 @@ const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tool === 'colorPicker') {
|
||||
commitColorUnderCursor();
|
||||
return;
|
||||
}
|
||||
|
||||
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
|
||||
|
||||
if (!scaledCursorPosition) return;
|
||||
|
@ -5,12 +5,16 @@ import Konva from 'konva';
|
||||
import { Vector2d } from 'konva/lib/types';
|
||||
import _ from 'lodash';
|
||||
import { MutableRefObject, useCallback } from 'react';
|
||||
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
canvasSelector,
|
||||
isStagingSelector,
|
||||
} from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
addPointToCurrentLine,
|
||||
setCursorPosition,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import getScaledCursorPosition from '../util/getScaledCursorPosition';
|
||||
import useColorPicker from './useColorUnderCursor';
|
||||
|
||||
const selector = createSelector(
|
||||
[activeTabNameSelector, canvasSelector, isStagingSelector],
|
||||
@ -33,6 +37,7 @@ const useCanvasMouseMove = (
|
||||
) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { isDrawing, tool, isStaging } = useAppSelector(selector);
|
||||
const { updateColorUnderCursor } = useColorPicker();
|
||||
|
||||
return useCallback(() => {
|
||||
if (!stageRef.current) return;
|
||||
@ -45,6 +50,11 @@ const useCanvasMouseMove = (
|
||||
|
||||
lastCursorPositionRef.current = scaledCursorPosition;
|
||||
|
||||
if (tool === 'colorPicker') {
|
||||
updateColorUnderCursor();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDrawing || tool === 'move' || isStaging) return;
|
||||
|
||||
didMouseMoveRef.current = true;
|
||||
@ -59,6 +69,7 @@ const useCanvasMouseMove = (
|
||||
lastCursorPositionRef,
|
||||
stageRef,
|
||||
tool,
|
||||
updateColorUnderCursor,
|
||||
]);
|
||||
};
|
||||
|
||||
|
45
frontend/src/features/canvas/hooks/useColorUnderCursor.ts
Normal file
45
frontend/src/features/canvas/hooks/useColorUnderCursor.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { useAppDispatch } from 'app/store';
|
||||
import Konva from 'konva';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
commitColorPickerColor,
|
||||
setColorPickerColor,
|
||||
} from '../store/canvasSlice';
|
||||
import {
|
||||
getCanvasBaseLayer,
|
||||
getCanvasStage,
|
||||
} from '../util/konvaInstanceProvider';
|
||||
|
||||
const useColorPicker = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const canvasBaseLayer = getCanvasBaseLayer();
|
||||
const stage = getCanvasStage();
|
||||
|
||||
return {
|
||||
updateColorUnderCursor: () => {
|
||||
if (!stage || !canvasBaseLayer) return;
|
||||
|
||||
const position = stage.getPointerPosition();
|
||||
|
||||
if (!position) return;
|
||||
|
||||
const pixelRatio = Konva.pixelRatio;
|
||||
|
||||
const [r, g, b, a] = canvasBaseLayer
|
||||
.getContext()
|
||||
.getImageData(
|
||||
position.x * pixelRatio,
|
||||
position.y * pixelRatio,
|
||||
1,
|
||||
1
|
||||
).data;
|
||||
|
||||
dispatch(setColorPickerColor({ r, g, b, a }));
|
||||
},
|
||||
commitColorUnderCursor: () => {
|
||||
dispatch(commitColorPickerColor());
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default useColorPicker;
|
@ -44,6 +44,7 @@ const initialCanvasState: CanvasState = {
|
||||
brushColor: { r: 90, g: 90, b: 255, a: 1 },
|
||||
brushSize: 50,
|
||||
canvasContainerDimensions: { width: 0, height: 0 },
|
||||
colorPickerColor: { r: 90, g: 90, b: 255, a: 1 },
|
||||
cursorPosition: null,
|
||||
doesCanvasNeedScaling: false,
|
||||
futureLayerStates: [],
|
||||
@ -345,7 +346,7 @@ export const canvasSlice = createSlice({
|
||||
addLine: (state, action: PayloadAction<number[]>) => {
|
||||
const { tool, layer, brushColor, brushSize } = state;
|
||||
|
||||
if (tool === 'move') return;
|
||||
if (tool === 'move' || tool === 'colorPicker') return;
|
||||
|
||||
const newStrokeWidth = brushSize / 2;
|
||||
|
||||
@ -683,6 +684,13 @@ export const canvasSlice = createSlice({
|
||||
) => {
|
||||
state.shouldCropToBoundingBoxOnSave = action.payload;
|
||||
},
|
||||
setColorPickerColor: (state, action: PayloadAction<RgbaColor>) => {
|
||||
state.colorPickerColor = action.payload;
|
||||
},
|
||||
commitColorPickerColor: (state) => {
|
||||
state.brushColor = state.colorPickerColor;
|
||||
state.tool = 'brush';
|
||||
},
|
||||
setMergedCanvas: (state, action: PayloadAction<CanvasImage>) => {
|
||||
state.pastLayerStates.push({
|
||||
...state.layerState,
|
||||
@ -710,6 +718,8 @@ export const {
|
||||
addLine,
|
||||
addPointToCurrentLine,
|
||||
clearMask,
|
||||
commitColorPickerColor,
|
||||
setColorPickerColor,
|
||||
commitStagingAreaImage,
|
||||
discardStagedImages,
|
||||
fitBoundingBoxToStage,
|
||||
|
@ -13,7 +13,7 @@ export type CanvasLayer = typeof LAYER_NAMES[number];
|
||||
|
||||
export type CanvasDrawingTool = 'brush' | 'eraser';
|
||||
|
||||
export type CanvasTool = CanvasDrawingTool | 'move';
|
||||
export type CanvasTool = CanvasDrawingTool | 'move' | 'colorPicker';
|
||||
|
||||
export type Dimensions = {
|
||||
width: number;
|
||||
@ -81,6 +81,7 @@ export interface CanvasState {
|
||||
brushColor: RgbaColor;
|
||||
brushSize: number;
|
||||
canvasContainerDimensions: Dimensions;
|
||||
colorPickerColor: RgbaColor,
|
||||
cursorPosition: Vector2d | null;
|
||||
doesCanvasNeedScaling: boolean;
|
||||
futureLayerStates: CanvasLayerState[];
|
||||
|
@ -12,3 +12,5 @@ export const MAX_CANVAS_SCALE = 20;
|
||||
|
||||
// padding given to initial image/bounding box when stage view is reset
|
||||
export const STAGE_PADDING_PERCENTAGE = 0.95;
|
||||
|
||||
export const COLOR_PICKER_SIZE = 60;
|
||||
|
@ -140,22 +140,22 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
|
||||
const unifiedCanvasHotkeys = [
|
||||
{
|
||||
title: 'Select Brush',
|
||||
desc: 'Selects the inpainting brush',
|
||||
desc: 'Selects the canvas brush',
|
||||
hotkey: 'B',
|
||||
},
|
||||
{
|
||||
title: 'Select Eraser',
|
||||
desc: 'Selects the inpainting eraser',
|
||||
desc: 'Selects the canvas eraser',
|
||||
hotkey: 'E',
|
||||
},
|
||||
{
|
||||
title: 'Decrease Brush Size',
|
||||
desc: 'Decreases the size of the inpainting brush/eraser',
|
||||
desc: 'Decreases the size of the canvas brush/eraser',
|
||||
hotkey: '[',
|
||||
},
|
||||
{
|
||||
title: 'Increase Brush Size',
|
||||
desc: 'Increases the size of the inpainting brush/eraser',
|
||||
desc: 'Increases the size of the canvas brush/eraser',
|
||||
hotkey: ']',
|
||||
},
|
||||
{
|
||||
@ -163,6 +163,11 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
|
||||
desc: 'Allows canvas navigation',
|
||||
hotkey: 'V',
|
||||
},
|
||||
{
|
||||
title: 'Select Color Picker',
|
||||
desc: 'Selects the canvas color picker',
|
||||
hotkey: 'C',
|
||||
},
|
||||
{
|
||||
title: 'Quick Toggle Move',
|
||||
desc: 'Temporarily toggles Move mode',
|
||||
|
Loading…
Reference in New Issue
Block a user