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",
|
||||
"deleteSelected": "Delete Selected",
|
||||
"deleteAll": "Delete All",
|
||||
"flipHorizontal": "Flip Horizontal",
|
||||
"flipVertical": "Flip Vertical",
|
||||
"fill": {
|
||||
"fillStyle": "Fill Style",
|
||||
"solid": "Solid",
|
||||
|
@ -32,14 +32,14 @@ export const CanvasEditor = memo(() => {
|
||||
>
|
||||
<ControlLayersToolbar />
|
||||
<StageComponent />
|
||||
<Flex position="absolute" bottom={16} gap={2} align="center" justify="center">
|
||||
<Flex position="absolute" bottom={8} gap={2} align="center" justify="center">
|
||||
<CanvasManagerProviderGate>
|
||||
<StagingAreaIsStagingGate>
|
||||
<StagingAreaToolbar />
|
||||
</StagingAreaIsStagingGate>
|
||||
</CanvasManagerProviderGate>
|
||||
</Flex>
|
||||
<Flex position="absolute" bottom={16}>
|
||||
<Flex position="absolute" bottom={8}>
|
||||
<CanvasManagerProviderGate>
|
||||
<Filter />
|
||||
<Transform />
|
||||
|
@ -6,9 +6,15 @@ import {
|
||||
useEntityIdentifierContext,
|
||||
} from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useEntityAdapter } from 'features/controlLayers/hooks/useEntityAdapter';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { memo } from 'react';
|
||||
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 { t } = useTranslation();
|
||||
@ -16,14 +22,6 @@ const TransformBox = memo(() => {
|
||||
const adapter = useEntityAdapter(entityIdentifier);
|
||||
const isProcessing = useStore(adapter.transformer.$isProcessing);
|
||||
|
||||
const applyTransform = useCallback(() => {
|
||||
adapter.transformer.applyTransform();
|
||||
}, [adapter.transformer]);
|
||||
|
||||
const cancelFilter = useCallback(() => {
|
||||
adapter.transformer.stopTransform();
|
||||
}, [adapter.transformer]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
bg="base.800"
|
||||
@ -40,16 +38,49 @@ const TransformBox = memo(() => {
|
||||
<Heading size="md" color="base.300" userSelect="none">
|
||||
{t('controlLayers.tool.transform')}
|
||||
</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">
|
||||
<Button
|
||||
leftIcon={<PiCheckBold />}
|
||||
onClick={applyTransform}
|
||||
onClick={adapter.transformer.applyTransform}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('common.apply')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('common.apply')}
|
||||
</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')}
|
||||
</Button>
|
||||
</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
|
||||
// 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.
|
||||
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(),
|
||||
});
|
||||
this.syncObjectGroupWithProxyRect();
|
||||
});
|
||||
|
||||
this.konva.transformer.on('transformend', () => {
|
||||
@ -395,6 +389,53 @@ export class CanvasEntityTransformer extends CanvasModuleABC {
|
||||
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.
|
||||
* @param position The position of the parent entity
|
||||
@ -510,6 +551,12 @@ export class CanvasEntityTransformer extends CanvasModuleABC {
|
||||
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
|
||||
* original state.
|
||||
@ -520,12 +567,9 @@ export class CanvasEntityTransformer extends CanvasModuleABC {
|
||||
this.isTransforming = false;
|
||||
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.
|
||||
this.resetScale();
|
||||
|
||||
this.updatePosition();
|
||||
this.updateBbox();
|
||||
this.resetTransform();
|
||||
this.syncInteractionState();
|
||||
this.manager.stateApi.$transformingEntity.set(null);
|
||||
this.$isProcessing.set(false);
|
||||
|
Loading…
x
Reference in New Issue
Block a user