feat(ui): add canvas background style

This commit is contained in:
psychedelicious 2024-08-16 20:59:32 +10:00
parent cb293fd7ac
commit 2950775fa7
9 changed files with 101 additions and 1 deletions

View File

@ -1716,6 +1716,12 @@
"view": "View",
"transform": "Transform",
"eyeDropper": "Eye Dropper"
},
"background": {
"backgroundStyle": "Background Style",
"solid": "Solid",
"checkerboard": "Checkerboard",
"dynamicGrid": "Dynamic Grid"
}
},
"upscaling": {

View File

@ -0,0 +1,50 @@
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { canvasBackgroundStyleChanged } from 'features/controlLayers/store/canvasV2Slice';
import { isCanvasBackgroundStyle } from 'features/controlLayers/store/types';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
export const CanvasSettingsBackgroundStyle = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const canvasBackgroundStyle = useAppSelector((s) => s.canvasV2.settings.canvasBackgroundStyle);
const onChange = useCallback<ComboboxOnChange>(
(v) => {
if (!isCanvasBackgroundStyle(v?.value)) {
return;
}
dispatch(canvasBackgroundStyleChanged(v.value));
},
[dispatch]
);
const options = useMemo<ComboboxOption[]>(() => {
return [
{
value: 'solid',
label: t('controlLayers.background.solid'),
},
{
value: 'checkerboard',
label: t('controlLayers.background.checkerboard'),
},
{
value: 'dynamicGrid',
label: t('controlLayers.background.dynamicGrid'),
},
];
}, [t]);
const value = useMemo(() => options.find((o) => o.value === canvasBackgroundStyle), [options, canvasBackgroundStyle]);
return (
<FormControl orientation="vertical">
<FormLabel m={0}>{t('controlLayers.background.backgroundStyle')}</FormLabel>
<Combobox value={value} options={options} onChange={onChange} isSearchable={false} />
</FormControl>
);
});
CanvasSettingsBackgroundStyle.displayName = 'CanvasSettingsBackgroundStyle';

View File

@ -12,6 +12,7 @@ import {
} from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { CanvasSettingsBackgroundStyle } from 'features/controlLayers/components/CanvasSettingsBackgroundStyle';
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager';
import {
clipToBboxChanged,
@ -71,6 +72,7 @@ const ControlLayersSettingsPopover = () => {
<FormLabel flexGrow={1}>{t('unifiedCanvas.clipToBbox')}</FormLabel>
<Checkbox isChecked={clipToBbox} onChange={onChangeClipToBbox} />
</FormControl>
<CanvasSettingsBackgroundStyle />
<Button onClick={invalidateRasterizationCaches} size="sm">
Invalidate Rasterization Caches
</Button>

View File

@ -3,8 +3,10 @@ import { useStore } from '@nanostores/react';
import { $socket } from 'app/hooks/useSocketIO';
import { logger } from 'app/logging/logger';
import { useAppStore } from 'app/store/nanostores/store';
import { useAppSelector } from 'app/store/storeHooks';
import { HeadsUpDisplay } from 'features/controlLayers/components/HeadsUpDisplay';
import { $canvasManager, CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
import Konva from 'konva';
import { memo, useCallback, useEffect, useLayoutEffect, useState } from 'react';
import { useDevicePixelRatio } from 'use-device-pixel-ratio';
@ -52,6 +54,8 @@ type Props = {
};
export const StageComponent = memo(({ asPreview = false }: Props) => {
const canvasBackgroundStyle = useAppSelector((s) => s.canvasV2.settings.canvasBackgroundStyle);
const [stage] = useState(
() =>
new Konva.Stage({
@ -77,6 +81,17 @@ export const StageComponent = memo(({ asPreview = false }: Props) => {
return (
<Flex position="relative" w="full" h="full">
{canvasBackgroundStyle === 'checkerboard' && (
<Flex
position="absolute"
bgImage={TRANSPARENCY_CHECKER_PATTERN}
top={0}
right={0}
bottom={0}
left={0}
opacity={0.1}
/>
)}
<Flex
position="absolute"
top={0}

View File

@ -34,6 +34,15 @@ export class CanvasBackground {
}
render() {
const settings = this.manager.stateApi.getSettings();
if (settings.canvasBackgroundStyle !== 'dynamicGrid') {
this.konva.layer.visible(false);
return;
}
this.konva.layer.visible(true);
this.konva.layer.zIndex(0);
const scale = this.manager.stage.scaleX();
const gridSpacing = CanvasBackground.getGridSpacing(scale);

View File

@ -270,6 +270,13 @@ export class CanvasManager {
return;
}
if (
this._isFirstRender ||
state.settings.canvasBackgroundStyle !== this._prevState.settings.canvasBackgroundStyle
) {
this.background.render();
}
if (this._isFirstRender || state.rasterLayers.entities !== this._prevState.rasterLayers.entities) {
this.log.debug('Rendering raster layers');

View File

@ -94,6 +94,7 @@ const initialState: CanvasV2State = {
showHUD: true,
clipToBbox: false,
cropToBboxOnSave: false,
canvasBackgroundStyle: 'dynamicGrid',
},
compositing: {
maskBlur: 16,
@ -474,6 +475,7 @@ export const {
clipToBboxChanged,
canvasReset,
rasterizationCachesInvalidated,
canvasBackgroundStyleChanged,
// All entities
entitySelected,
entityNameChanged,

View File

@ -1,8 +1,11 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import type { CanvasV2State } from 'features/controlLayers/store/types';
import type { CanvasBackgroundStyle, CanvasV2State } from 'features/controlLayers/store/types';
export const settingsReducers = {
clipToBboxChanged: (state, action: PayloadAction<boolean>) => {
state.settings.clipToBbox = action.payload;
},
canvasBackgroundStyleChanged: (state, action: PayloadAction<CanvasBackgroundStyle>) => {
state.settings.canvasBackgroundStyle = action.payload;
},
} satisfies SliceCaseReducers<CanvasV2State>;

View File

@ -839,6 +839,11 @@ export type StagingAreaImage = {
offsetY: number;
};
const zCanvasBackgroundStyle = z.enum(['checkerboard', 'dynamicGrid', 'solid']);
export type CanvasBackgroundStyle = z.infer<typeof zCanvasBackgroundStyle>;
export const isCanvasBackgroundStyle = (v: unknown): v is CanvasBackgroundStyle =>
zCanvasBackgroundStyle.safeParse(v).success;
export type CanvasV2State = {
_version: 3;
selectedEntityIdentifier: CanvasEntityIdentifier | null;
@ -863,6 +868,7 @@ export type CanvasV2State = {
preserveMaskedArea: boolean;
cropToBboxOnSave: boolean;
clipToBbox: boolean;
canvasBackgroundStyle: CanvasBackgroundStyle;
};
bbox: {
rect: {