Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(scene-items): free rotation from Edit Transform window #4586

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/components-react/shared/inputs/NumberInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import InputWrapper from './InputWrapper';
import { InputNumberProps } from 'antd/lib/input-number';

// select which features from the antd lib we are going to use
const ANT_NUMBER_FEATURES = ['min', 'max', 'step'] as const;
const ANT_NUMBER_FEATURES = ['min', 'max', 'step', 'precision'] as const;

type TProps = TSlobsInputProps<{}, number, InputNumberProps, ValuesOf<typeof ANT_NUMBER_FEATURES>>;

Expand Down
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;
}
}
67 changes: 67 additions & 0 deletions app/components-react/shared/inputs/RotateInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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);

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';
32 changes: 10 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,8 @@ export default function EditTransform() {
};
}

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

function reset() {
Expand All @@ -111,7 +116,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 +150,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