mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): add color swatches to mask fill
This commit is contained in:
committed by
Kent Keirsey
parent
1be1ad9794
commit
d9bd6c4e57
@@ -1,15 +1,19 @@
|
|||||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||||
import { CompositeNumberInput, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Box, CompositeNumberInput, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { RGB_COLOR_SWATCHES } from 'common/components/ColorPicker/swatches';
|
||||||
|
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { RgbColorPicker as ColorfulRgbColorPicker } from 'react-colorful';
|
import { RgbColorPicker as ColorfulRgbColorPicker } from 'react-colorful';
|
||||||
import type { ColorPickerBaseProps, RgbColor } from 'react-colorful/dist/types';
|
import type { RgbColor } from 'react-colorful/dist/types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type RgbColorPickerProps = ColorPickerBaseProps<RgbColor> & {
|
type Props = {
|
||||||
|
color: RgbColor;
|
||||||
|
onChange: (color: RgbColor) => void;
|
||||||
withNumberInput?: boolean;
|
withNumberInput?: boolean;
|
||||||
|
withSwatches?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const colorPickerPointerStyles: NonNullable<ChakraProps['sx']> = {
|
const colorPickerPointerStyles: NonNullable<ChakraProps['sx']> = {
|
||||||
width: 6,
|
width: 6,
|
||||||
height: 6,
|
height: 6,
|
||||||
@@ -20,7 +24,7 @@ const sx: ChakraProps['sx'] = {
|
|||||||
'.react-colorful__hue-pointer': colorPickerPointerStyles,
|
'.react-colorful__hue-pointer': colorPickerPointerStyles,
|
||||||
'.react-colorful__saturation-pointer': colorPickerPointerStyles,
|
'.react-colorful__saturation-pointer': colorPickerPointerStyles,
|
||||||
'.react-colorful__alpha-pointer': colorPickerPointerStyles,
|
'.react-colorful__alpha-pointer': colorPickerPointerStyles,
|
||||||
gap: 5,
|
gap: 4,
|
||||||
flexDir: 'column',
|
flexDir: 'column',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,20 +32,21 @@ const colorPickerStyles: CSSProperties = { width: '100%' };
|
|||||||
|
|
||||||
const numberInputWidth: ChakraProps['w'] = '3.5rem';
|
const numberInputWidth: ChakraProps['w'] = '3.5rem';
|
||||||
|
|
||||||
const RgbColorPicker = (props: RgbColorPickerProps) => {
|
const RgbColorPicker = (props: Props) => {
|
||||||
const { color, onChange, withNumberInput, ...rest } = props;
|
const { color, onChange, withNumberInput = false, withSwatches = false } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const handleChangeR = useCallback((r: number) => onChange({ ...color, r }), [color, onChange]);
|
const handleChangeR = useCallback((r: number) => onChange({ ...color, r }), [color, onChange]);
|
||||||
const handleChangeG = useCallback((g: number) => onChange({ ...color, g }), [color, onChange]);
|
const handleChangeG = useCallback((g: number) => onChange({ ...color, g }), [color, onChange]);
|
||||||
const handleChangeB = useCallback((b: number) => onChange({ ...color, b }), [color, onChange]);
|
const handleChangeB = useCallback((b: number) => onChange({ ...color, b }), [color, onChange]);
|
||||||
return (
|
return (
|
||||||
<Flex sx={sx}>
|
<Flex sx={sx}>
|
||||||
<ColorfulRgbColorPicker color={color} onChange={onChange} style={colorPickerStyles} {...rest} />
|
<ColorfulRgbColorPicker color={color} onChange={onChange} style={colorPickerStyles} />
|
||||||
{withNumberInput && (
|
{withNumberInput && (
|
||||||
<Flex gap={5}>
|
<Flex gap={4}>
|
||||||
<FormControl gap={0}>
|
<FormControl gap={0}>
|
||||||
<FormLabel>{t('common.red')[0]}</FormLabel>
|
<FormLabel>{t('common.red')[0]}</FormLabel>
|
||||||
<CompositeNumberInput
|
<CompositeNumberInput
|
||||||
|
flexGrow={1}
|
||||||
value={color.r}
|
value={color.r}
|
||||||
onChange={handleChangeR}
|
onChange={handleChangeR}
|
||||||
min={0}
|
min={0}
|
||||||
@@ -54,6 +59,7 @@ const RgbColorPicker = (props: RgbColorPickerProps) => {
|
|||||||
<FormControl gap={0}>
|
<FormControl gap={0}>
|
||||||
<FormLabel>{t('common.green')[0]}</FormLabel>
|
<FormLabel>{t('common.green')[0]}</FormLabel>
|
||||||
<CompositeNumberInput
|
<CompositeNumberInput
|
||||||
|
flexGrow={1}
|
||||||
value={color.g}
|
value={color.g}
|
||||||
onChange={handleChangeG}
|
onChange={handleChangeG}
|
||||||
min={0}
|
min={0}
|
||||||
@@ -66,6 +72,7 @@ const RgbColorPicker = (props: RgbColorPickerProps) => {
|
|||||||
<FormControl gap={0}>
|
<FormControl gap={0}>
|
||||||
<FormLabel>{t('common.blue')[0]}</FormLabel>
|
<FormLabel>{t('common.blue')[0]}</FormLabel>
|
||||||
<CompositeNumberInput
|
<CompositeNumberInput
|
||||||
|
flexGrow={1}
|
||||||
value={color.b}
|
value={color.b}
|
||||||
onChange={handleChangeB}
|
onChange={handleChangeB}
|
||||||
min={0}
|
min={0}
|
||||||
@@ -77,8 +84,22 @@ const RgbColorPicker = (props: RgbColorPickerProps) => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
{withSwatches && (
|
||||||
|
<Flex gap={2} justifyContent="space-between">
|
||||||
|
{RGB_COLOR_SWATCHES.map((color, i) => (
|
||||||
|
<ColorSwatch key={i} color={color} onChange={onChange} />
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(RgbColorPicker);
|
export default memo(RgbColorPicker);
|
||||||
|
|
||||||
|
const ColorSwatch = ({ color, onChange }: { color: RgbColor; onChange: (color: RgbColor) => void }) => {
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
onChange(color);
|
||||||
|
}, [color, onChange]);
|
||||||
|
return <Box role="button" onClick={onClick} h={8} w={8} bg={rgbColorToString(color)} borderRadius="base" />;
|
||||||
|
};
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||||
import { CompositeNumberInput, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Box, CompositeNumberInput, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { RGBA_COLOR_SWATCHES } from 'common/components/ColorPicker/swatches';
|
||||||
|
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { RgbaColorPicker } from 'react-colorful';
|
import { RgbaColorPicker as ColorfulRgbaColorPicker } from 'react-colorful';
|
||||||
import type { ColorPickerBaseProps, RgbaColor } from 'react-colorful/dist/types';
|
import type { RgbaColor } from 'react-colorful/dist/types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type IAIColorPickerProps = ColorPickerBaseProps<RgbaColor> & {
|
type Props = {
|
||||||
|
color: RgbaColor;
|
||||||
|
onChange: (color: RgbaColor) => void;
|
||||||
withNumberInput?: boolean;
|
withNumberInput?: boolean;
|
||||||
|
withSwatches?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const colorPickerPointerStyles: NonNullable<ChakraProps['sx']> = {
|
const colorPickerPointerStyles: NonNullable<ChakraProps['sx']> = {
|
||||||
@@ -20,7 +25,7 @@ const sx: ChakraProps['sx'] = {
|
|||||||
'.react-colorful__hue-pointer': colorPickerPointerStyles,
|
'.react-colorful__hue-pointer': colorPickerPointerStyles,
|
||||||
'.react-colorful__saturation-pointer': colorPickerPointerStyles,
|
'.react-colorful__saturation-pointer': colorPickerPointerStyles,
|
||||||
'.react-colorful__alpha-pointer': colorPickerPointerStyles,
|
'.react-colorful__alpha-pointer': colorPickerPointerStyles,
|
||||||
gap: 5,
|
gap: 4,
|
||||||
flexDir: 'column',
|
flexDir: 'column',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,8 +33,8 @@ const colorPickerStyles: CSSProperties = { width: '100%' };
|
|||||||
|
|
||||||
const numberInputWidth: ChakraProps['w'] = '3.5rem';
|
const numberInputWidth: ChakraProps['w'] = '3.5rem';
|
||||||
|
|
||||||
const IAIColorPicker = (props: IAIColorPickerProps) => {
|
const RgbaColorPicker = (props: Props) => {
|
||||||
const { color, onChange, withNumberInput, ...rest } = props;
|
const { color, onChange, withNumberInput = false, withSwatches = false } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const handleChangeR = useCallback((r: number) => onChange({ ...color, r }), [color, onChange]);
|
const handleChangeR = useCallback((r: number) => onChange({ ...color, r }), [color, onChange]);
|
||||||
const handleChangeG = useCallback((g: number) => onChange({ ...color, g }), [color, onChange]);
|
const handleChangeG = useCallback((g: number) => onChange({ ...color, g }), [color, onChange]);
|
||||||
@@ -37,9 +42,9 @@ const IAIColorPicker = (props: IAIColorPickerProps) => {
|
|||||||
const handleChangeA = useCallback((a: number) => onChange({ ...color, a }), [color, onChange]);
|
const handleChangeA = useCallback((a: number) => onChange({ ...color, a }), [color, onChange]);
|
||||||
return (
|
return (
|
||||||
<Flex sx={sx}>
|
<Flex sx={sx}>
|
||||||
<RgbaColorPicker color={color} onChange={onChange} style={colorPickerStyles} {...rest} />
|
<ColorfulRgbaColorPicker color={color} onChange={onChange} style={colorPickerStyles} />
|
||||||
{withNumberInput && (
|
{withNumberInput && (
|
||||||
<Flex gap={5}>
|
<Flex gap={2}>
|
||||||
<FormControl gap={0}>
|
<FormControl gap={0}>
|
||||||
<FormLabel>{t('common.red')[0]}</FormLabel>
|
<FormLabel>{t('common.red')[0]}</FormLabel>
|
||||||
<CompositeNumberInput
|
<CompositeNumberInput
|
||||||
@@ -90,8 +95,22 @@ const IAIColorPicker = (props: IAIColorPickerProps) => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
{withSwatches && (
|
||||||
|
<Flex gap={2} justifyContent="space-between">
|
||||||
|
{RGBA_COLOR_SWATCHES.map((color, i) => (
|
||||||
|
<ColorSwatch key={i} color={color} onChange={onChange} />
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(IAIColorPicker);
|
export default memo(RgbaColorPicker);
|
||||||
|
|
||||||
|
const ColorSwatch = ({ color, onChange }: { color: RgbaColor; onChange: (color: RgbaColor) => void }) => {
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
onChange(color);
|
||||||
|
}, [color, onChange]);
|
||||||
|
return <Box role="button" onClick={onClick} h={8} w={8} bg={rgbaColorToString(color)} borderRadius="base" />;
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
const SWATCHES = [
|
||||||
|
{ r: 0, g: 0, b: 0, a: 1 }, // black
|
||||||
|
{ r: 255, g: 255, b: 255, a: 1 }, // white
|
||||||
|
{ r: 255, g: 90, b: 94, a: 1 }, // red
|
||||||
|
{ r: 255, g: 146, b: 75, a: 1 }, // orange
|
||||||
|
{ r: 255, g: 202, b: 59, a: 1 }, // yellow
|
||||||
|
{ r: 197, g: 202, b: 48, a: 1 }, // lime
|
||||||
|
{ r: 138, g: 201, b: 38, a: 1 }, // green
|
||||||
|
{ r: 83, g: 165, b: 117, a: 1 }, // teal
|
||||||
|
{ r: 23, g: 130, b: 196, a: 1 }, // blue
|
||||||
|
{ r: 66, g: 103, b: 172, a: 1 }, // indigo
|
||||||
|
{ r: 107, g: 76, b: 147, a: 1 }, // purple
|
||||||
|
];
|
||||||
|
|
||||||
|
export const RGBA_COLOR_SWATCHES = SWATCHES;
|
||||||
|
export const RGB_COLOR_SWATCHES = SWATCHES.map(({ r, g, b }) => ({ r, g, b }));
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Box, Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger, Tooltip } from '@invoke-ai/ui-library';
|
import { Box, Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger, Tooltip } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import RgbColorPicker from 'common/components/RgbColorPicker';
|
import RgbColorPicker from 'common/components/ColorPicker/RgbColorPicker';
|
||||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||||
import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle';
|
import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle';
|
||||||
import { entityFillColorChanged, entityFillStyleChanged } from 'features/controlLayers/store/canvasSlice';
|
import { entityFillColorChanged, entityFillStyleChanged } from 'features/controlLayers/store/canvasSlice';
|
||||||
@@ -65,7 +65,7 @@ export const EntityListSelectedEntityActionBarFill = memo(() => {
|
|||||||
<PopoverContent>
|
<PopoverContent>
|
||||||
<PopoverBody minH={64}>
|
<PopoverBody minH={64}>
|
||||||
<Flex flexDir="column" gap={4}>
|
<Flex flexDir="column" gap={4}>
|
||||||
<RgbColorPicker color={fill.color} onChange={onChangeFillColor} withNumberInput />
|
<RgbColorPicker color={fill.color} onChange={onChangeFillColor} withNumberInput withSwatches />
|
||||||
<MaskFillStyle style={fill.style} onChange={onChangeFillStyle} />
|
<MaskFillStyle style={fill.style} onChange={onChangeFillStyle} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</PopoverBody>
|
</PopoverBody>
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { Box, Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger, Tooltip } from '@invoke-ai/ui-library';
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Popover,
|
||||||
|
PopoverArrow,
|
||||||
|
PopoverBody,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
Tooltip,
|
||||||
|
} from '@invoke-ai/ui-library';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
import RgbaColorPicker from 'common/components/ColorPicker/RgbaColorPicker';
|
||||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||||
import { selectCanvasSettingsSlice, settingsColorChanged } from 'features/controlLayers/store/canvasSettingsSlice';
|
import { selectCanvasSettingsSlice, settingsColorChanged } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||||
import type { RgbaColor } from 'features/controlLayers/store/types';
|
import type { RgbaColor } from 'features/controlLayers/store/types';
|
||||||
@@ -12,20 +21,6 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
const selectColor = createSelector(selectCanvasSettingsSlice, (settings) => settings.color);
|
const selectColor = createSelector(selectCanvasSettingsSlice, (settings) => settings.color);
|
||||||
|
|
||||||
const SWATCHES = [
|
|
||||||
{ r: 0, g: 0, b: 0, a: 1 }, // black
|
|
||||||
{ r: 255, g: 255, b: 255, a: 1 }, // white
|
|
||||||
{ r: 255, g: 90, b: 94, a: 1 }, // red
|
|
||||||
{ r: 255, g: 146, b: 75, a: 1 }, // orange
|
|
||||||
{ r: 255, g: 202, b: 59, a: 1 }, // yellow
|
|
||||||
{ r: 197, g: 202, b: 48, a: 1 }, // lime
|
|
||||||
{ r: 138, g: 201, b: 38, a: 1 }, // green
|
|
||||||
{ r: 83, g: 165, b: 117, a: 1 }, // teal
|
|
||||||
{ r: 23, g: 130, b: 196, a: 1 }, // blue
|
|
||||||
{ r: 66, g: 103, b: 172, a: 1 }, // indigo
|
|
||||||
{ r: 107, g: 76, b: 147, a: 1 }, // purple
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ToolColorPicker = memo(() => {
|
export const ToolColorPicker = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const fill = useAppSelector(selectColor);
|
const fill = useAppSelector(selectColor);
|
||||||
@@ -65,15 +60,9 @@ export const ToolColorPicker = memo(() => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent>
|
<PopoverContent>
|
||||||
|
<PopoverArrow />
|
||||||
<PopoverBody minH={64}>
|
<PopoverBody minH={64}>
|
||||||
<Flex flexDir="column" gap={4}>
|
<RgbaColorPicker color={fill} onChange={onChange} withNumberInput withSwatches />
|
||||||
<IAIColorPicker color={fill} onChange={onChange} withNumberInput />
|
|
||||||
<Flex gap={2} justifyContent="space-between">
|
|
||||||
{SWATCHES.map((color, i) => (
|
|
||||||
<ColorSwatch key={i} color={color} />
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</PopoverBody>
|
</PopoverBody>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
@@ -81,22 +70,3 @@ export const ToolColorPicker = memo(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ToolColorPicker.displayName = 'ToolFillColorPicker';
|
ToolColorPicker.displayName = 'ToolFillColorPicker';
|
||||||
|
|
||||||
const ColorSwatch = ({ color }: { color: RgbaColor }) => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const onClick = useCallback(() => {
|
|
||||||
dispatch(settingsColorChanged(color));
|
|
||||||
}, [color, dispatch]);
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
role="button"
|
|
||||||
onClick={onClick}
|
|
||||||
h={8}
|
|
||||||
w={8}
|
|
||||||
borderColor="base.300"
|
|
||||||
borderWidth={1}
|
|
||||||
bg={rgbaColorToString(color)}
|
|
||||||
borderRadius="base"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Box, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Box, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
import RgbaColorPicker from 'common/components/ColorPicker/RgbaColorPicker';
|
||||||
import {
|
import {
|
||||||
selectInfillColorValue,
|
selectInfillColorValue,
|
||||||
selectInfillMethod,
|
selectInfillMethod,
|
||||||
@@ -30,7 +30,7 @@ const ParamInfillColorOptions = () => {
|
|||||||
<FormControl isDisabled={infillMethod !== 'color'}>
|
<FormControl isDisabled={infillMethod !== 'color'}>
|
||||||
<FormLabel>{t('parameters.infillColorValue')}</FormLabel>
|
<FormLabel>{t('parameters.infillColorValue')}</FormLabel>
|
||||||
<Box w="full" pt={2} pb={2}>
|
<Box w="full" pt={2} pb={2}>
|
||||||
<IAIColorPicker color={infillColor} onChange={handleInfillColor} />
|
<RgbaColorPicker color={infillColor} onChange={handleInfillColor} />
|
||||||
</Box>
|
</Box>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
Reference in New Issue
Block a user