feat(ui): grid size -> snap to grid

Similar behaviour to before. When on, snaps to 64. If ctrl/cmd held, snap to 8.
This commit is contained in:
psychedelicious
2024-09-09 09:24:10 +10:00
parent b0ec3de40a
commit 400ef8cdc3
9 changed files with 59 additions and 88 deletions

View File

@@ -1817,9 +1817,8 @@
"settings": {
"snapToGrid": {
"label": "Snap to Grid",
"off": "Off",
"8": "8px",
"64": "64px"
"on": "On",
"off": "Off"
}
},
"HUD": {

View File

@@ -1,8 +1,8 @@
import { Grid } from '@invoke-ai/ui-library';
import { CanvasHUDItemAutoSave } from 'features/controlLayers/components/HUD/CanvasHUDItemAutoSave';
import { CanvasHUDItemBbox } from 'features/controlLayers/components/HUD/CanvasHUDItemBbox';
import { CanvasHUDItemGridSize } from 'features/controlLayers/components/HUD/CanvasHUDItemGridSize';
import { CanvasHUDItemScaledBbox } from 'features/controlLayers/components/HUD/CanvasHUDItemScaledBbox';
import { CanvasHUDItemSnapToGrid } from 'features/controlLayers/components/HUD/CanvasHUDItemSnapToGrid';
import { memo } from 'react';
export const CanvasHUD = memo(() => {
@@ -19,7 +19,7 @@ export const CanvasHUD = memo(() => {
>
<CanvasHUDItemBbox />
<CanvasHUDItemScaledBbox />
<CanvasHUDItemGridSize />
<CanvasHUDItemSnapToGrid />
<CanvasHUDItemAutoSave />
</Grid>
);

View File

@@ -1,24 +0,0 @@
import { useAppSelector } from 'app/store/storeHooks';
import { CanvasHUDItem } from 'features/controlLayers/components/HUD/CanvasHUDItem';
import { selectGridSize } from 'features/controlLayers/store/canvasSettingsSlice';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
export const CanvasHUDItemGridSize = memo(() => {
const { t } = useTranslation();
const snap = useAppSelector(selectGridSize);
const snapString = useMemo(() => {
switch (snap) {
case 1:
return t('controlLayers.settings.snapToGrid.off');
case 8:
return t('controlLayers.settings.snapToGrid.8');
case 64:
return t('controlLayers.settings.snapToGrid.64');
}
}, [snap, t]);
return <CanvasHUDItem label={t('controlLayers.settings.snapToGrid.label')} value={snapString} />;
});
CanvasHUDItemGridSize.displayName = 'CanvasHUDItemGridSize';

View File

@@ -0,0 +1,19 @@
import { useAppSelector } from 'app/store/storeHooks';
import { CanvasHUDItem } from 'features/controlLayers/components/HUD/CanvasHUDItem';
import { selectSnapToGrid } from 'features/controlLayers/store/canvasSettingsSlice';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
export const CanvasHUDItemSnapToGrid = memo(() => {
const { t } = useTranslation();
const snapToGrid = useAppSelector(selectSnapToGrid);
return (
<CanvasHUDItem
label={t('controlLayers.settings.snapToGrid.label')}
value={snapToGrid ? t('common.on') : t('common.off')}
/>
);
});
CanvasHUDItemSnapToGrid.displayName = 'CanvasHUDItemSnapToGrid';

View File

@@ -1,59 +1,24 @@
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectGridSize, settingsGridSizeChanged } from 'features/controlLayers/store/canvasSettingsSlice';
import { isGridSize } from 'features/controlLayers/store/types';
import { memo, useCallback, useMemo } from 'react';
import { selectSnapToGrid, settingsSnapToGridToggled } from 'features/controlLayers/store/canvasSettingsSlice';
import type { ChangeEventHandler } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const getValue = (valueString: string) => {
switch (valueString) {
case 'off':
return 1;
case '8':
return 8;
case '64':
return 64;
default:
return null;
}
};
export const CanvasSettingsGridSize = memo(() => {
export const CanvasSettingsSnapToGridCheckbox = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const gridSize = useAppSelector(selectGridSize);
const options = useMemo<ComboboxOption[]>(
() => [
{ label: t('controlLayers.settings.snapToGrid.off'), value: 'off' },
{ label: t('controlLayers.settings.snapToGrid.8'), value: '8' },
{ label: t('controlLayers.settings.snapToGrid.64'), value: '64' },
],
[t]
);
const onChange = useCallback<ComboboxOnChange>(
(v) => {
if (!v) {
return;
}
const value = getValue(v.value);
if (!isGridSize(value)) {
return;
}
dispatch(settingsGridSizeChanged(value));
},
[dispatch]
);
const value = useMemo(() => options.find((o) => getValue(o.value) === gridSize), [options, gridSize]);
const snapToGrid = useAppSelector(selectSnapToGrid);
const onChange = useCallback<ChangeEventHandler<HTMLInputElement>>(() => {
dispatch(settingsSnapToGridToggled());
}, [dispatch]);
return (
<FormControl>
<FormLabel m={0} w="50%">
{t('controlLayers.settings.snapToGrid.label')}
</FormLabel>
<Combobox options={options} value={value} onChange={onChange} isSearchable={false} />
<FormControl w="full">
<FormLabel flexGrow={1}>{t('controlLayers.settings.snapToGrid.label')}</FormLabel>
<Checkbox isChecked={snapToGrid} onChange={onChange} />
</FormControl>
);
});
CanvasSettingsGridSize.displayName = 'CanvasSettingsSnapToGrid';
CanvasSettingsSnapToGridCheckbox.displayName = 'CanvasSettingsSnapToGrid';

View File

@@ -15,7 +15,7 @@ import { CanvasSettingsClearHistoryButton } from 'features/controlLayers/compone
import { CanvasSettingsClipToBboxCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsClipToBboxCheckbox';
import { CanvasSettingsCompositeMaskedRegionsCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsCompositeMaskedRegionsCheckbox';
import { CanvasSettingsDynamicGridSwitch } from 'features/controlLayers/components/Settings/CanvasSettingsDynamicGridSwitch';
import { CanvasSettingsGridSize } from 'features/controlLayers/components/Settings/CanvasSettingsGridSize';
import { CanvasSettingsSnapToGridCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsGridSize';
import { CanvasSettingsInvertScrollCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsInvertScrollCheckbox';
import { CanvasSettingsLogDebugInfoButton } from 'features/controlLayers/components/Settings/CanvasSettingsLogDebugInfo';
import { CanvasSettingsRecalculateRectsButton } from 'features/controlLayers/components/Settings/CanvasSettingsRecalculateRectsButton';
@@ -40,8 +40,8 @@ export const CanvasSettingsPopover = memo(() => {
<CanvasSettingsInvertScrollCheckbox />
<CanvasSettingsClipToBboxCheckbox />
<CanvasSettingsCompositeMaskedRegionsCheckbox />
<CanvasSettingsSnapToGridCheckbox />
<CanvasSettingsDynamicGridSwitch />
<CanvasSettingsGridSize />
<CanvasSettingsShowHUDSwitch />
<CanvasSettingsResetButton />
<DebugSettings />

View File

@@ -277,7 +277,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase {
// If the user is not holding shift, the transform is retaining aspect ratio. It's not possible to snap to the grid
// in this case, because that would change the aspect ratio. So, we only snap to the grid when shift is held.
const gridSize = this.manager.stateApi.$shiftKey.get() ? this.manager.stateApi.getSettings().gridSize : 1;
const gridSize = this.manager.stateApi.$shiftKey.get() ? this.manager.stateApi.getGridSize() : 1;
// We need to snap the anchor to the selected grid size, but the positions provided to this callback are absolute,
// scaled coordinates. They need to be converted to stage coordinates, snapped, then converted back to absolute
@@ -380,7 +380,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase {
onDragMove = () => {
// Snap the interaction rect to the grid
const { gridSize } = this.manager.stateApi.getSettings();
const gridSize = this.manager.stateApi.getGridSize();
this.konva.proxyRect.x(roundToMultiple(this.konva.proxyRect.x(), gridSize));
this.konva.proxyRect.y(roundToMultiple(this.konva.proxyRect.y(), gridSize));

View File

@@ -212,6 +212,18 @@ export class CanvasStateApiModule extends CanvasModuleBase {
return this.runSelector(selectCanvasSettingsSlice);
};
getGridSize = (): number => {
const snapToGrid = this.getSettings().snapToGrid;
if (!snapToGrid) {
return 1;
}
const useFine = this.$ctrlKey.get() || this.$metaKey.get();
if (useFine) {
return 8;
}
return 64;
};
/**
* Gets the regions state from redux.
*/

View File

@@ -1,7 +1,7 @@
import type { PayloadAction, Selector } from '@reduxjs/toolkit';
import { createSelector, createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import type { GridSize, RgbaColor } from 'features/controlLayers/store/types';
import type { RgbaColor } from 'features/controlLayers/store/types';
type CanvasSettingsState = {
/**
@@ -59,7 +59,7 @@ type CanvasSettingsState = {
/**
* The snap-to-grid setting for the canvas.
*/
gridSize: GridSize;
snapToGrid: boolean;
// TODO(psyche): These are copied from old canvas state, need to be implemented
// imageSmoothing: boolean;
// preserveMaskedArea: boolean;
@@ -78,7 +78,7 @@ const initialState: CanvasSettingsState = {
sendToCanvas: false,
compositeMaskedRegions: false,
autoProcessFilter: true,
gridSize: 64,
snapToGrid: true,
};
export const canvasSettingsSlice = createSlice({
@@ -118,8 +118,8 @@ export const canvasSettingsSlice = createSlice({
settingsAutoProcessFilterToggled: (state) => {
state.autoProcessFilter = !state.autoProcessFilter;
},
settingsGridSizeChanged: (state, action: PayloadAction<GridSize>) => {
state.gridSize = action.payload;
settingsSnapToGridToggled: (state) => {
state.snapToGrid = !state.snapToGrid;
},
},
});
@@ -136,7 +136,7 @@ export const {
settingsSendToCanvasChanged,
settingsCompositeMaskedRegionsChanged,
settingsAutoProcessFilterToggled,
settingsGridSizeChanged,
settingsSnapToGridToggled,
} = canvasSettingsSlice.actions;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
@@ -159,4 +159,4 @@ export const selectAutoSave = createCanvasSettingsSelector((settings) => setting
export const selectDynamicGrid = createCanvasSettingsSelector((settings) => settings.dynamicGrid);
export const selectShowHUD = createCanvasSettingsSelector((settings) => settings.showHUD);
export const selectAutoProcessFilter = createCanvasSettingsSelector((settings) => settings.autoProcessFilter);
export const selectGridSize = createCanvasSettingsSelector((settings) => settings.gridSize);
export const selectSnapToGrid = createCanvasSettingsSelector((settings) => settings.snapToGrid);