feat(ui): add color swatches to mask fill

This commit is contained in:
psychedelicious
2024-09-23 09:17:05 +10:00
committed by Kent Keirsey
parent 1be1ad9794
commit d9bd6c4e57
6 changed files with 92 additions and 66 deletions

View File

@@ -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" />;
};

View File

@@ -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" />;
};

View File

@@ -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 }));

View File

@@ -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>

View File

@@ -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"
/>
);
};

View File

@@ -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>