mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-01 15:54:58 -05:00
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:
@@ -1817,9 +1817,8 @@
|
||||
"settings": {
|
||||
"snapToGrid": {
|
||||
"label": "Snap to Grid",
|
||||
"off": "Off",
|
||||
"8": "8px",
|
||||
"64": "64px"
|
||||
"on": "On",
|
||||
"off": "Off"
|
||||
}
|
||||
},
|
||||
"HUD": {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user