mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): add flip & reset to transform
This commit is contained in:
parent
6d209c6cc3
commit
e6153e6fa4
@ -1738,6 +1738,8 @@
|
|||||||
"unlocked": "Unlocked",
|
"unlocked": "Unlocked",
|
||||||
"deleteSelected": "Delete Selected",
|
"deleteSelected": "Delete Selected",
|
||||||
"deleteAll": "Delete All",
|
"deleteAll": "Delete All",
|
||||||
|
"flipHorizontal": "Flip Horizontal",
|
||||||
|
"flipVertical": "Flip Vertical",
|
||||||
"fill": {
|
"fill": {
|
||||||
"fillStyle": "Fill Style",
|
"fillStyle": "Fill Style",
|
||||||
"solid": "Solid",
|
"solid": "Solid",
|
||||||
|
@ -32,14 +32,14 @@ export const CanvasEditor = memo(() => {
|
|||||||
>
|
>
|
||||||
<ControlLayersToolbar />
|
<ControlLayersToolbar />
|
||||||
<StageComponent />
|
<StageComponent />
|
||||||
<Flex position="absolute" bottom={16} gap={2} align="center" justify="center">
|
<Flex position="absolute" bottom={8} gap={2} align="center" justify="center">
|
||||||
<CanvasManagerProviderGate>
|
<CanvasManagerProviderGate>
|
||||||
<StagingAreaIsStagingGate>
|
<StagingAreaIsStagingGate>
|
||||||
<StagingAreaToolbar />
|
<StagingAreaToolbar />
|
||||||
</StagingAreaIsStagingGate>
|
</StagingAreaIsStagingGate>
|
||||||
</CanvasManagerProviderGate>
|
</CanvasManagerProviderGate>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex position="absolute" bottom={16}>
|
<Flex position="absolute" bottom={8}>
|
||||||
<CanvasManagerProviderGate>
|
<CanvasManagerProviderGate>
|
||||||
<Filter />
|
<Filter />
|
||||||
<Transform />
|
<Transform />
|
||||||
|
@ -6,9 +6,15 @@ import {
|
|||||||
useEntityIdentifierContext,
|
useEntityIdentifierContext,
|
||||||
} from 'features/controlLayers/contexts/EntityIdentifierContext';
|
} from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { useEntityAdapter } from 'features/controlLayers/hooks/useEntityAdapter';
|
import { useEntityAdapter } from 'features/controlLayers/hooks/useEntityAdapter';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiCheckBold, PiXBold } from 'react-icons/pi';
|
import {
|
||||||
|
PiArrowsCounterClockwiseBold,
|
||||||
|
PiCheckBold,
|
||||||
|
PiFlipHorizontalFill,
|
||||||
|
PiFlipVerticalFill,
|
||||||
|
PiXBold,
|
||||||
|
} from 'react-icons/pi';
|
||||||
|
|
||||||
const TransformBox = memo(() => {
|
const TransformBox = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -16,14 +22,6 @@ const TransformBox = memo(() => {
|
|||||||
const adapter = useEntityAdapter(entityIdentifier);
|
const adapter = useEntityAdapter(entityIdentifier);
|
||||||
const isProcessing = useStore(adapter.transformer.$isProcessing);
|
const isProcessing = useStore(adapter.transformer.$isProcessing);
|
||||||
|
|
||||||
const applyTransform = useCallback(() => {
|
|
||||||
adapter.transformer.applyTransform();
|
|
||||||
}, [adapter.transformer]);
|
|
||||||
|
|
||||||
const cancelFilter = useCallback(() => {
|
|
||||||
adapter.transformer.stopTransform();
|
|
||||||
}, [adapter.transformer]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
bg="base.800"
|
bg="base.800"
|
||||||
@ -40,16 +38,49 @@ const TransformBox = memo(() => {
|
|||||||
<Heading size="md" color="base.300" userSelect="none">
|
<Heading size="md" color="base.300" userSelect="none">
|
||||||
{t('controlLayers.tool.transform')}
|
{t('controlLayers.tool.transform')}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
<ButtonGroup isAttached={false} size="sm" justifyContent="center">
|
||||||
|
<Button
|
||||||
|
leftIcon={<PiFlipHorizontalFill />}
|
||||||
|
onClick={adapter.transformer.flipHorizontal}
|
||||||
|
isLoading={isProcessing}
|
||||||
|
loadingText={t('controlLayers.flipHorizontal')}
|
||||||
|
>
|
||||||
|
{t('controlLayers.flipHorizontal')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
leftIcon={<PiFlipVerticalFill />}
|
||||||
|
onClick={adapter.transformer.flipVertical}
|
||||||
|
isLoading={isProcessing}
|
||||||
|
loadingText={t('controlLayers.flipVertical')}
|
||||||
|
>
|
||||||
|
{t('controlLayers.flipVertical')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
leftIcon={<PiArrowsCounterClockwiseBold />}
|
||||||
|
onClick={adapter.transformer.resetTransform}
|
||||||
|
isLoading={isProcessing}
|
||||||
|
loadingText={t('controlLayers.reset')}
|
||||||
|
>
|
||||||
|
{t('accessibility.reset')}
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
<ButtonGroup isAttached={false} size="sm" alignSelf="self-end">
|
<ButtonGroup isAttached={false} size="sm" alignSelf="self-end">
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<PiCheckBold />}
|
leftIcon={<PiCheckBold />}
|
||||||
onClick={applyTransform}
|
onClick={adapter.transformer.applyTransform}
|
||||||
isLoading={isProcessing}
|
isLoading={isProcessing}
|
||||||
loadingText={t('common.apply')}
|
loadingText={t('common.apply')}
|
||||||
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{t('common.apply')}
|
{t('common.apply')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button leftIcon={<PiXBold />} onClick={cancelFilter} isLoading={isProcessing} loadingText={t('common.cancel')}>
|
<Button
|
||||||
|
leftIcon={<PiXBold />}
|
||||||
|
onClick={adapter.transformer.stopTransform}
|
||||||
|
isLoading={isProcessing}
|
||||||
|
loadingText={t('common.cancel')}
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
{t('common.cancel')}
|
{t('common.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
@ -259,13 +259,7 @@ export class CanvasEntityTransformer extends CanvasModuleABC {
|
|||||||
// This is called when a transform anchor is dragged. By this time, the transform constraints in the above
|
// This is called when a transform anchor is dragged. By this time, the transform constraints in the above
|
||||||
// callbacks have been enforced, and the transformer has updated its nodes' attributes. We need to pass the
|
// callbacks have been enforced, and the transformer has updated its nodes' attributes. We need to pass the
|
||||||
// updated attributes to the object group, propagating the transformation on down.
|
// updated attributes to the object group, propagating the transformation on down.
|
||||||
this.parent.renderer.konva.objectGroup.setAttrs({
|
this.syncObjectGroupWithProxyRect();
|
||||||
x: this.konva.proxyRect.x(),
|
|
||||||
y: this.konva.proxyRect.y(),
|
|
||||||
scaleX: this.konva.proxyRect.scaleX(),
|
|
||||||
scaleY: this.konva.proxyRect.scaleY(),
|
|
||||||
rotation: this.konva.proxyRect.rotation(),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.konva.transformer.on('transformend', () => {
|
this.konva.transformer.on('transformend', () => {
|
||||||
@ -395,6 +389,53 @@ export class CanvasEntityTransformer extends CanvasModuleABC {
|
|||||||
this.parent.konva.layer.add(this.konva.transformer);
|
this.parent.konva.layer.add(this.konva.transformer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flipHorizontal = () => {
|
||||||
|
if (!this.isTransforming || this.$isProcessing.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flipping horizontally = flipping across the vertical axis:
|
||||||
|
// - Flip by negating the x scale
|
||||||
|
// - Restore position by translating the rect rightwards by the width of the rect
|
||||||
|
const x = this.konva.proxyRect.x();
|
||||||
|
const width = this.konva.proxyRect.width();
|
||||||
|
const scaleX = this.konva.proxyRect.scaleX();
|
||||||
|
this.konva.proxyRect.setAttrs({
|
||||||
|
scaleX: -scaleX,
|
||||||
|
x: x + width * scaleX,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.syncObjectGroupWithProxyRect();
|
||||||
|
};
|
||||||
|
|
||||||
|
flipVertical = () => {
|
||||||
|
if (!this.isTransforming || this.$isProcessing.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flipping vertically = flipping across the horizontal axis:
|
||||||
|
// - Flip by negating the y scale
|
||||||
|
// - Restore position by translating the rect downwards by the height of the rect
|
||||||
|
const y = this.konva.proxyRect.y();
|
||||||
|
const height = this.konva.proxyRect.height();
|
||||||
|
const scaleY = this.konva.proxyRect.scaleY();
|
||||||
|
this.konva.proxyRect.setAttrs({
|
||||||
|
scaleY: -scaleY,
|
||||||
|
y: y + height * scaleY,
|
||||||
|
});
|
||||||
|
this.syncObjectGroupWithProxyRect();
|
||||||
|
};
|
||||||
|
|
||||||
|
syncObjectGroupWithProxyRect = () => {
|
||||||
|
this.parent.renderer.konva.objectGroup.setAttrs({
|
||||||
|
x: this.konva.proxyRect.x(),
|
||||||
|
y: this.konva.proxyRect.y(),
|
||||||
|
scaleX: this.konva.proxyRect.scaleX(),
|
||||||
|
scaleY: this.konva.proxyRect.scaleY(),
|
||||||
|
rotation: this.konva.proxyRect.rotation(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the transformer's visual components to match the parent entity's position and bounding box.
|
* Updates the transformer's visual components to match the parent entity's position and bounding box.
|
||||||
* @param position The position of the parent entity
|
* @param position The position of the parent entity
|
||||||
@ -510,6 +551,12 @@ export class CanvasEntityTransformer extends CanvasModuleABC {
|
|||||||
this.stopTransform();
|
this.stopTransform();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
resetTransform = () => {
|
||||||
|
this.resetScale();
|
||||||
|
this.updatePosition();
|
||||||
|
this.updateBbox();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the transformation of the entity. If the transformation is in progress, the entity will be reset to its
|
* Stops the transformation of the entity. If the transformation is in progress, the entity will be reset to its
|
||||||
* original state.
|
* original state.
|
||||||
@ -520,12 +567,9 @@ export class CanvasEntityTransformer extends CanvasModuleABC {
|
|||||||
this.isTransforming = false;
|
this.isTransforming = false;
|
||||||
this.setInteractionMode('off');
|
this.setInteractionMode('off');
|
||||||
|
|
||||||
// Reset the scale of the the entity. We've either replaced the transformed objects with a rasterized image, or
|
// Reset the transform of the the entity. We've either replaced the transformed objects with a rasterized image, or
|
||||||
// canceled a transformation. In either case, the scale should be reset.
|
// canceled a transformation. In either case, the scale should be reset.
|
||||||
this.resetScale();
|
this.resetTransform();
|
||||||
|
|
||||||
this.updatePosition();
|
|
||||||
this.updateBbox();
|
|
||||||
this.syncInteractionState();
|
this.syncInteractionState();
|
||||||
this.manager.stateApi.$transformingEntity.set(null);
|
this.manager.stateApi.$transformingEntity.set(null);
|
||||||
this.$isProcessing.set(false);
|
this.$isProcessing.set(false);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user