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
3 changed files with 123 additions and 80 deletions

View File

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

View File

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

View File

@ -17,18 +17,19 @@ const horizontalOptions: Array<SelectableValue<HorizontalConstraint>> = [
{ label: 'Left', value: HorizontalConstraint.Left }, { label: 'Left', value: HorizontalConstraint.Left },
{ label: 'Right', value: HorizontalConstraint.Right }, { label: 'Right', value: HorizontalConstraint.Right },
{ label: 'Left and right', value: HorizontalConstraint.LeftRight }, { label: 'Left and right', value: HorizontalConstraint.LeftRight },
{ label: 'Center', value: HorizontalConstraint.Center },
{ label: 'Scale', value: HorizontalConstraint.Scale },
]; ];
const verticalOptions: Array<SelectableValue<VerticalConstraint>> = [ const verticalOptions: Array<SelectableValue<VerticalConstraint>> = [
{ label: 'Top', value: VerticalConstraint.Top }, { label: 'Top', value: VerticalConstraint.Top },
{ label: 'Bottom', value: VerticalConstraint.Bottom }, { label: 'Bottom', value: VerticalConstraint.Bottom },
{ label: 'Top and bottom', value: VerticalConstraint.TopBottom }, { 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>> = ({ export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, PanelOptions>> = ({ item }) => {
item,
onChange,
}) => {
const settings = item.settings; const settings = item.settings;
// Will force a rerender whenever the subject changes // Will force a rerender whenever the subject changes
@ -59,6 +60,12 @@ export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, P
settings.scene.save(true); 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 ( return (
<div> <div>
<VerticalGroup> <VerticalGroup>
@ -81,7 +88,7 @@ export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, P
return ( return (
<InlineFieldRow key={p}> <InlineFieldRow key={p}>
<InlineField label={p} labelWidth={8} grow={true}> <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> </InlineField>
</InlineFieldRow> </InlineFieldRow>
); );