Canvas: Support scale + center constraints (#48085)

This commit is contained in:
Nathan Marrs 2022-04-22 14:02:36 -07:00 committed by GitHub
parent 75ba4e98c6
commit 2b2f275a08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 123 additions and 80 deletions

View File

@ -42,12 +42,6 @@ export class ElementState implements LayerElement {
horizontal: HorizontalConstraint.Left,
};
options.placement = options.placement ?? { width: 100, height: 100, top: 0, left: 0 };
this.validatePlacement();
this.sizeStyle = {
...options.placement,
position: 'absolute',
};
const scene = this.getScene();
if (!options.name) {
const newName = scene?.getNextElementName();
@ -61,7 +55,6 @@ export class ElementState implements LayerElement {
while (trav) {
if (trav.isRoot()) {
return trav.scene;
break;
}
trav = trav.parent;
}
@ -73,48 +66,108 @@ export class ElementState implements LayerElement {
return this.options.name;
}
validatePlacement() {
const { constraint, placement } = this.options;
/** Use the configured options to update CSS style properties directly on the wrapper div **/
applyLayoutStylesToDiv() {
const { constraint } = this.options;
const { vertical, horizontal } = constraint ?? {};
const updatedPlacement = placement ?? ({} as Placement);
const placement = this.options.placement ?? ({} as Placement);
const style: React.CSSProperties = {
position: 'absolute',
};
const translate = ['0px', '0px'];
switch (vertical) {
case VerticalConstraint.Top:
updatedPlacement.top = updatedPlacement.top ?? 0;
updatedPlacement.height = updatedPlacement.height ?? 100;
delete updatedPlacement.bottom;
placement.top = placement.top ?? 0;
placement.height = placement.height ?? 100;
style.top = `${placement.top}px`;
style.height = `${placement.height}px`;
delete placement.bottom;
break;
case VerticalConstraint.Bottom:
updatedPlacement.bottom = updatedPlacement.bottom ?? 0;
updatedPlacement.height = updatedPlacement.height ?? 100;
delete updatedPlacement.top;
placement.bottom = placement.bottom ?? 0;
placement.height = placement.height ?? 100;
style.bottom = `${placement.bottom}px`;
style.height = `${placement.height}px`;
delete placement.top;
break;
case VerticalConstraint.TopBottom:
updatedPlacement.top = updatedPlacement.top ?? 0;
updatedPlacement.bottom = updatedPlacement.bottom ?? 0;
delete updatedPlacement.height;
placement.top = placement.top ?? 0;
placement.bottom = placement.bottom ?? 0;
style.top = `${placement.top}px`;
style.bottom = `${placement.bottom}px`;
delete placement.height;
break;
case VerticalConstraint.Center:
placement.top = placement.top ?? 0;
placement.height = placement.height ?? 100;
translate[1] = '-50%';
style.top = `calc(50% - ${placement.top}px)`;
style.height = `${placement.height}px`;
delete placement.bottom;
break;
case VerticalConstraint.Scale:
placement.top = placement.top ?? 0;
placement.bottom = placement.bottom ?? 0;
style.top = `${placement.top}%`;
style.bottom = `${placement.bottom}%`;
delete placement.height;
break;
}
switch (horizontal) {
case HorizontalConstraint.Left:
updatedPlacement.left = updatedPlacement.left ?? 0;
updatedPlacement.width = updatedPlacement.width ?? 100;
delete updatedPlacement.right;
placement.left = placement.left ?? 0;
placement.width = placement.width ?? 100;
style.left = `${placement.left}px`;
style.width = `${placement.width}px`;
delete placement.right;
break;
case HorizontalConstraint.Right:
updatedPlacement.right = updatedPlacement.right ?? 0;
updatedPlacement.width = updatedPlacement.width ?? 100;
delete updatedPlacement.left;
placement.right = placement.right ?? 0;
placement.width = placement.width ?? 100;
style.right = `${placement.right}px`;
style.width = `${placement.width}px`;
delete placement.left;
break;
case HorizontalConstraint.LeftRight:
updatedPlacement.left = updatedPlacement.left ?? 0;
updatedPlacement.right = updatedPlacement.right ?? 0;
delete updatedPlacement.width;
placement.left = placement.left ?? 0;
placement.right = placement.right ?? 0;
style.left = `${placement.left}px`;
style.right = `${placement.right}px`;
delete placement.width;
break;
case HorizontalConstraint.Center:
placement.left = placement.left ?? 0;
placement.width = placement.width ?? 100;
translate[0] = '-50%';
style.left = `calc(50% - ${placement.left}px)`;
style.width = `${placement.width}px`;
delete placement.right;
break;
case HorizontalConstraint.Scale:
placement.left = placement.left ?? 0;
placement.right = placement.right ?? 0;
style.left = `${placement.left}%`;
style.right = `${placement.right}%`;
delete placement.width;
break;
}
this.options.placement = updatedPlacement;
style.transform = `translate(${translate[0]}, ${translate[1]})`;
this.options.placement = placement;
this.sizeStyle = style;
if (this.div) {
for (const key in this.sizeStyle) {
this.div.style[key as any] = (this.sizeStyle as any)[key];
}
for (const key in this.dataStyle) {
this.div.style[key as any] = (this.dataStyle as any)[key];
}
}
}
setPlacementFromConstraint() {
@ -151,6 +204,17 @@ export class ElementState implements LayerElement {
placement.top = relativeTop;
placement.bottom = relativeBottom;
break;
case VerticalConstraint.Center:
const elementCenter = elementContainer ? relativeTop + height / 2 : 0;
const parentCenter = parentContainer ? parentContainer.height / 2 : 0;
const distanceFromCenter = parentCenter - elementCenter;
placement.top = distanceFromCenter;
placement.height = height;
break;
case VerticalConstraint.Scale:
placement.top = (relativeTop / (parentContainer?.height ?? height)) * 100;
placement.bottom = (relativeBottom / (parentContainer?.height ?? height)) * 100;
break;
}
switch (horizontal) {
@ -166,13 +230,22 @@ export class ElementState implements LayerElement {
placement.left = relativeLeft;
placement.right = relativeRight;
break;
case HorizontalConstraint.Center:
const elementCenter = elementContainer ? relativeLeft + width / 2 : 0;
const parentCenter = parentContainer ? parentContainer.width / 2 : 0;
const distanceFromCenter = parentCenter - elementCenter;
placement.left = distanceFromCenter;
placement.width = width;
break;
case HorizontalConstraint.Scale:
placement.left = (relativeLeft / (parentContainer?.width ?? width)) * 100;
placement.right = (relativeRight / (parentContainer?.width ?? width)) * 100;
break;
}
this.options.placement = placement;
this.sizeStyle = {
...this.options.placement,
position: 'absolute',
};
this.applyLayoutStylesToDiv();
this.revId++;
}
@ -273,45 +346,11 @@ export class ElementState implements LayerElement {
initElement = (target: HTMLDivElement) => {
this.div = target;
this.applyLayoutStylesToDiv();
};
applyDrag = (event: OnDrag) => {
const { options } = this;
const { placement, constraint } = options;
const { vertical, horizontal } = constraint ?? {};
const deltaX = event.delta[0];
const deltaY = event.delta[1];
const style = event.target.style;
const isConstrainedTop = vertical === VerticalConstraint.Top || vertical === VerticalConstraint.TopBottom;
const isConstrainedBottom = vertical === VerticalConstraint.Bottom || vertical === VerticalConstraint.TopBottom;
const isConstrainedLeft = horizontal === HorizontalConstraint.Left || horizontal === HorizontalConstraint.LeftRight;
const isConstrainedRight =
horizontal === HorizontalConstraint.Right || horizontal === HorizontalConstraint.LeftRight;
if (isConstrainedTop) {
placement!.top! += deltaY;
style.top = `${placement!.top}px`;
}
if (isConstrainedBottom) {
placement!.bottom! -= deltaY;
style.bottom = `${placement!.bottom}px`;
}
if (isConstrainedLeft) {
placement!.left! += deltaX;
style.left = `${placement!.left}px`;
}
if (isConstrainedRight) {
placement!.right! -= deltaX;
style.right = `${placement!.right}px`;
}
// TODO: Center + Scale
event.target.style.transform = event.transform;
};
// kinda like:
@ -380,14 +419,12 @@ export class ElementState implements LayerElement {
style.height = `${placement!.height}px`;
}
}
// TODO: Center + Scale
};
render() {
const { item } = this;
return (
<div key={`${this.UID}`} style={{ ...this.sizeStyle, ...this.dataStyle }} ref={this.initElement}>
<div key={this.UID} ref={this.initElement}>
<item.display key={`${this.UID}/${this.revId}`} config={this.options.config} data={this.data} />
</div>
);

View File

@ -255,21 +255,20 @@ export class Scene {
.on('drag', (event) => {
const targetedElement = this.findElementByTarget(event.target);
targetedElement!.applyDrag(event);
this.moved.next(Date.now()); // TODO only on end
})
.on('dragGroup', (e) => {
e.events.forEach((event) => {
const targetedElement = this.findElementByTarget(event.target);
targetedElement!.applyDrag(event);
});
this.moved.next(Date.now()); // TODO only on end
})
.on('dragEnd', (event) => {
const targetedElement = this.findElementByTarget(event.target);
if (targetedElement) {
targetedElement?.setPlacementFromConstraint();
}
this.moved.next(Date.now());
})
.on('resize', (event) => {
const targetedElement = this.findElementByTarget(event.target);

View File

@ -17,18 +17,19 @@ const horizontalOptions: Array<SelectableValue<HorizontalConstraint>> = [
{ label: 'Left', value: HorizontalConstraint.Left },
{ label: 'Right', value: HorizontalConstraint.Right },
{ label: 'Left and right', value: HorizontalConstraint.LeftRight },
{ label: 'Center', value: HorizontalConstraint.Center },
{ label: 'Scale', value: HorizontalConstraint.Scale },
];
const verticalOptions: Array<SelectableValue<VerticalConstraint>> = [
{ label: 'Top', value: VerticalConstraint.Top },
{ label: 'Bottom', value: VerticalConstraint.Bottom },
{ label: 'Top and bottom', value: VerticalConstraint.TopBottom },
{ label: 'Center', value: VerticalConstraint.Center },
{ label: 'Scale', value: VerticalConstraint.Scale },
];
export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, PanelOptions>> = ({
item,
onChange,
}) => {
export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, PanelOptions>> = ({ item }) => {
const settings = item.settings;
// Will force a rerender whenever the subject changes
@ -59,6 +60,12 @@ export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, P
settings.scene.save(true);
};
const onPositionChange = (value: number | undefined, placement: keyof Placement) => {
element.options.placement![placement] = value ?? element.options.placement![placement];
element.applyLayoutStylesToDiv();
settings.scene.clearCurrentSelection();
};
return (
<div>
<VerticalGroup>
@ -81,7 +88,7 @@ export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, P
return (
<InlineFieldRow key={p}>
<InlineField label={p} labelWidth={8} grow={true}>
<NumberInput value={v} onChange={(v) => console.log('TODO, edit!!!', p, v)} />
<NumberInput value={v} onChange={(v) => onPositionChange(v, p)} />
</InlineField>
</InlineFieldRow>
);