Skip to content

Commit

Permalink
feat(scene-items): allow free rotation in Edit Transform
Browse files Browse the repository at this point in the history
  • Loading branch information
blackxored committed May 31, 2023
1 parent c408532 commit 2517783
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 29 deletions.
9 changes: 9 additions & 0 deletions app/components-react/shared/inputs/RotateInput.m.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.wrapper {
:global(.ant-input-number) {
margin-left: 8px;
}

:global(.fa-undo), :global(.fa-redo) {
margin-right: 0;
}
}
68 changes: 68 additions & 0 deletions app/components-react/shared/inputs/RotateInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import { $t } from 'services/i18n';
import { NumberInput } from 'components-react/shared/inputs';
import InputWrapper from 'components-react/shared/inputs/InputWrapper';
import styles from './RotateInput.m.less';
import debounce from 'lodash/debounce';

interface RotateInputProps {
value: number;
handleInput: (val: number, isDelta?: boolean) => void;
}

const MIN_ROTATION = -359;
const MAX_ROTATION = 360;
const ROTATE_STEP = 0.1;
const DEBOUNCE_TIME = 1000;

const formatter = (value: number | string) => {
// for when the input gets clears
const val = typeof value === 'string' && !value.length ? '0.0' : value;

return `${val}°`;
};

const parser = (value: string) => {
const val = value.replace('°', '');
return Number.isNaN(Number(val)) ? 0.0 : Number(val);
};

export const RotateInput = (props: RotateInputProps) => {
const { value, handleInput } = props;

const rotateLeft = () => handleInput(-90);
const rotateRight = () => handleInput(90);
const rotate = debounce((deg: number) => handleInput(deg, false), DEBOUNCE_TIME);

// @ts-ignore
return (
<InputWrapper label={$t('Rotation')} className={styles.wrapper}>
<NumberInput
nowrap
value={value}
defaultValue={0}
min={MIN_ROTATION}
max={MAX_ROTATION}
step={ROTATE_STEP}
precision={2}
onChange={rotate}
/>

<button
className="button icon-button"
onClick={rotateLeft}
title={$t('Rotate 90 Degrees CCW')}
>
<i className="fas fa-undo" />
</button>

<button
className="button icon-button"
onClick={rotateRight}
title={$t('Rotate 90 Degrees CW')}
>
<i className="fas fa-redo" />
</button>
</InputWrapper>
);
};
1 change: 1 addition & 0 deletions app/components-react/shared/inputs/inputList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export { ColorInput } from './ColorInput';
export { CardInput } from './CardInput';
export { AutocompleteInput } from './AutocompleteInput';
export { CheckboxGroup } from './CheckboxGroup';
export { RotateInput } from './RotateInput';
35 changes: 13 additions & 22 deletions app/components-react/windows/EditTransform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import InputWrapper from 'components-react/shared/inputs/InputWrapper';
import { $t } from 'services/i18n';
import { AnchorPositions, AnchorPoint } from 'util/ScalableRectangle';
import { useVuex } from 'components-react/hooks';
import { NumberInput } from 'components-react/shared/inputs';
import Form, { useForm } from 'components-react/shared/inputs/Form';
import { NumberInput, RotateInput } from 'components-react/shared/inputs';

const dirMap = (dir: string) =>
({
Expand All @@ -21,9 +21,14 @@ export default function EditTransform() {
const { SelectionService, WindowsService, EditorCommandsService, SourcesService } = Services;

const { selection } = useVuex(() => ({ selection: SelectionService.views.globalSelection }));

/* Since we're not (and shouldn't be) deep watching `selection`,
/* `selection...#transform` is not reactive when rotation completes
*/
const { transform } = useVuex(() => ({ transform: selection.getItems()[0].transform }));

// // We only care about the attributes of the rectangle not the functionality
const [rect, setRect] = useState({ ...selection.getBoundingRect() });
const transform = selection.getItems()[0].transform;
const form = useForm();

useEffect(() => {
Expand Down Expand Up @@ -83,8 +88,9 @@ export default function EditTransform() {
};
}

function rotate(deg: number) {
EditorCommandsService.actions.executeCommand('RotateItemsCommand', selection, deg);
function rotate(deg: number, isDelta = true) {
console.log('rotating', deg, isDelta);
EditorCommandsService.actions.executeCommand('RotateItemsCommand', selection, deg, isDelta);
}

function reset() {
Expand All @@ -96,6 +102,8 @@ export default function EditTransform() {
WindowsService.actions.closeChildWindow();
}

console.log('rotation', transform.rotation);

return (
<ModalLayout footer={<Footer reset={reset} cancel={cancel} />}>
<Form form={form} layout="horizontal">
Expand All @@ -111,7 +119,7 @@ export default function EditTransform() {
rect={rect}
handleInput={setScale}
/>
<RotateInput handleInput={rotate} />
<RotateInput value={transform.rotation} handleInput={rotate} />
<CropInput transform={transform} handleInput={setCrop} />
</Form>
</ModalLayout>
Expand Down Expand Up @@ -145,23 +153,6 @@ function CoordinateInput(p: {
);
}

function RotateInput(p: { handleInput: (val: number) => void }) {
return (
<InputWrapper label={$t('Rotation')}>
<button className="button button--default" onClick={() => p.handleInput(90)}>
{$t('Rotate 90 Degrees CW')}
</button>
<button
className="button button--default"
style={{ marginLeft: '8px' }}
onClick={() => p.handleInput(-90)}
>
{$t('Rotate 90 Degrees CCW')}
</button>
</InputWrapper>
);
}

function CropInput(p: {
transform: any;
handleInput: (cropEdge: keyof ICrop) => (value: number) => void;
Expand Down
4 changes: 2 additions & 2 deletions app/services/editor-commands/commands/rotate-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Selection } from 'services/selection';
import { $t } from 'services/i18n';

export class RotateItemsCommand extends ModifyTransformCommand {
constructor(selection: Selection, private degrees: number) {
constructor(selection: Selection, private degrees: number, private isDelta = true) {
super(selection);
}

Expand All @@ -12,6 +12,6 @@ export class RotateItemsCommand extends ModifyTransformCommand {
}

modifyTransform() {
this.selection.rotate(this.degrees);
this.selection.rotate(this.degrees, this.isDelta);
}
}
9 changes: 6 additions & 3 deletions app/services/scenes/scene-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,10 +399,13 @@ export class SceneItem extends SceneItemNode {
this.setRect(rect);
}

rotate(deltaRotation: number) {
this.preservePosition(() => {
this.setTransform({ rotation: this.transform.rotation + deltaRotation });
rotate(rotation: number, isRotationDelta = true) {
const setTransform = this.setTransform.bind(this, {
rotation: isRotationDelta ? this.transform.rotation + rotation : rotation,
});

// Relative is the old system with buttons (90deg only) so we use the old preservePosition logic on it
return isRotationDelta ? this.preservePosition(setTransform) : setTransform();
}

getItemIndex(): number {
Expand Down
4 changes: 2 additions & 2 deletions app/services/selection/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,8 +531,8 @@ export class Selection {
this.getItems().forEach(item => item.centerOnAxis(CenteringAxis.Y));
}

rotate(deg: number) {
this.getItems().forEach(item => item.rotate(deg));
rotate(deg: number, isDelta = true) {
this.getItems().forEach(item => item.rotate(deg, isDelta));
}

setContentCrop() {
Expand Down

0 comments on commit 2517783

Please sign in to comment.