diff --git a/src/LayoutContext.tsx b/src/LayoutContext.tsx index bea804f..3a62a87 100644 --- a/src/LayoutContext.tsx +++ b/src/LayoutContext.tsx @@ -10,6 +10,7 @@ import { LayoutDefinition, ConstraintDefinition } from "./constraints/definition"; +import { appLayout } from "./examples/appLayout"; export interface LayoutContextInterface { @@ -56,7 +57,7 @@ const defaultConstraints: ConstraintDefinition[] = [ ]; export const LayoutProvider: FunctionComponent = ({ children }) => { - const [layout, setLayout] = useState({}); + const [layout, setLayout] = useState(appLayout); const [addingComponent, setAddingComponent] = useState(false) const [selectedComponent, setSelectedComponent] = useState(""); const [editConstraint, setEditConstraint] = useState(""); diff --git a/src/constraints/constraint.test.ts b/src/constraints/constraint.test.ts index 09cd2d3..f15b3f5 100644 --- a/src/constraints/constraint.test.ts +++ b/src/constraints/constraint.test.ts @@ -1,5 +1,5 @@ import { ResolveConstraint, ResolveConstraints } from "./constraint"; -import { ComponentInstance, Side, ConstraintInstance } from "./definition"; +import { ComponentInstance, Side, ConstraintInstance, WidthConstraintInstance, HeightConstraintInstance, RelativeConstraintInstance } from "./definition"; const ParentComponent = (): ComponentInstance => ({ name: "parent", @@ -94,4 +94,62 @@ describe("constraint resolving tests", () => { [Side.left]: 10 }); }); + + test("a simple width constraint", () => { + const testedComponent = NewComponent("nameX"); + const parent = ParentComponent(); + const constraintLeft: ConstraintInstance = { + fromInstance: testedComponent, + toInstance: parent, + resolved: Boolean(false), + fromSide: Side.left, + toSide: Side.left, + distance: 10 + }; + + const widthConstraint: WidthConstraintInstance = { + instance: testedComponent, + resolved: Boolean(false), + width: 80 + }; + + ResolveConstraints([ + constraintLeft, + widthConstraint + ]); + + expect(testedComponent.positions).toStrictEqual({ + [Side.right]: 90, + [Side.left]: 10 + }); + }); + + test("a simple height constraint", () => { + const testedComponent = NewComponent("nameX"); + const parent = ParentComponent(); + const constraintBottom: RelativeConstraintInstance = { + fromInstance: testedComponent, + toInstance: parent, + resolved: Boolean(false), + fromSide: Side.bottom, + toSide: Side.bottom, + distance: -25 + }; + + const heightConstraint: HeightConstraintInstance = { + instance: testedComponent, + resolved: Boolean(false), + height: 40 + }; + + ResolveConstraints([ + constraintBottom, + heightConstraint + ]); + + expect(testedComponent.positions).toStrictEqual({ + [Side.bottom]: 75, + [Side.top]: 35 + }); + }); }); diff --git a/src/constraints/constraint.ts b/src/constraints/constraint.ts index 1e180f3..6c54b34 100644 --- a/src/constraints/constraint.ts +++ b/src/constraints/constraint.ts @@ -1,6 +1,6 @@ -import { ConstraintInstance, ConstraintInstances } from "./definition"; +import { ConstraintInstance, ConstraintInstances, RelativeConstraintInstance, WidthConstraintInstance, Side, HeightConstraintInstance } from "./definition"; -export const ResolveConstraint = (constraint: ConstraintInstance): void => { +const ResolveRelativeConstraint = (constraint: RelativeConstraintInstance): void => { const { fromInstance, fromSide, @@ -17,6 +17,52 @@ export const ResolveConstraint = (constraint: ConstraintInstance): void => { } }; +const ResolveWidthConstraint = (constraint: WidthConstraintInstance): void => { + const { + instance, + width, + resolved + } = constraint; + if (resolved) return; + if (instance.positions[Side.left] !== undefined) { + instance.positions[Side.right] = instance.positions[Side.left]! + width + constraint.resolved = Boolean(true); + } else if (instance.positions[Side.right] !== undefined) { + instance.positions[Side.left] = instance.positions[Side.right]! - width + constraint.resolved = Boolean(true); + }; +}; + + +const ResolveHeightConstraint = (constraint: HeightConstraintInstance): void => { + const { + instance, + height, + resolved + } = constraint; + if (resolved) return; + if (instance.positions[Side.top] !== undefined) { + instance.positions[Side.bottom] = instance.positions[Side.top]! + height + constraint.resolved = Boolean(true); + } else if (instance.positions[Side.bottom] !== undefined) { + instance.positions[Side.top] = instance.positions[Side.bottom]! - height + constraint.resolved = Boolean(true); + }; +}; + + +export const ResolveConstraint = (constraint: ConstraintInstance): void => { + if (constraint.resolved) return; + const keys = Object.keys(constraint) + if (keys.includes("distance")) { + ResolveRelativeConstraint(constraint as RelativeConstraintInstance) + } else if (keys.includes("width")) { + ResolveWidthConstraint(constraint as WidthConstraintInstance) + } else if (keys.includes("height")) { + ResolveHeightConstraint(constraint as HeightConstraintInstance) + } +}; + export const isConstraintResolved = (constraint: ConstraintInstance): Boolean => constraint.resolved; diff --git a/src/constraints/definition.ts b/src/constraints/definition.ts index 8e4ffd1..591a590 100644 --- a/src/constraints/definition.ts +++ b/src/constraints/definition.ts @@ -7,6 +7,7 @@ export enum Side { left = "left" } + export interface ConstraintDefinition { component: string; fromSide: keyof typeof Side; @@ -31,7 +32,19 @@ export interface ComponentInstance { export type ComponentInstances = ComponentInstance[]; -export interface ConstraintInstance { +export interface WidthConstraintInstance { + instance: ComponentInstance; + resolved: Boolean; + width: number; +}; + +export interface HeightConstraintInstance { + instance: ComponentInstance; + resolved: Boolean; + height: number; +}; + +export interface RelativeConstraintInstance { fromInstance: ComponentInstance; fromSide: Side; toInstance: ComponentInstance; @@ -40,4 +53,6 @@ export interface ConstraintInstance { resolved: Boolean; } +export type ConstraintInstance = RelativeConstraintInstance | WidthConstraintInstance | HeightConstraintInstance + export type ConstraintInstances = ConstraintInstance[]; diff --git a/src/constraints/parser.ts b/src/constraints/parser.ts index de7f2f2..48fef58 100644 --- a/src/constraints/parser.ts +++ b/src/constraints/parser.ts @@ -39,6 +39,20 @@ export const ParseLayout = ( resolved: Boolean(false) }) ); + if(component.width !== undefined){ + constraints.push({ + instance: findComponentByName(components, name), + width: component.width, + resolved: Boolean(false) + }) + } + if(component.height !== undefined){ + constraints.push({ + instance: findComponentByName(components, name), + height: component.height, + resolved: Boolean(false) + }) + } } return [components, constraints]; diff --git a/src/examples/appLayout.ts b/src/examples/appLayout.ts new file mode 100644 index 0000000..0ecde5e --- /dev/null +++ b/src/examples/appLayout.ts @@ -0,0 +1,101 @@ +import { LayoutDefinition } from "../constraints/definition"; + +export const appLayout: LayoutDefinition = { + Sidebar: { + width:32*4, + constraints: [ + { + component: "parent", + fromSide: "top", + toSide: "top", + distance: 32 + }, + { + component: "parent", + fromSide: "bottom", + toSide: "bottom", + distance: -32 + }, + { + component: "parent", + fromSide: "left", + toSide: "left", + distance: 32 + } + ] + }, + Header: { + height:32*2, + constraints: [ + { + component: "parent", + fromSide: "top", + toSide: "top", + distance: 32 + }, + { + component: "Sidebar", + fromSide: "left", + toSide: "right", + distance: 32 + }, + { + component: "parent", + fromSide: "right", + toSide: "right", + distance: -32 + } + ] + }, + Footer: { + height:32, + constraints: [ + { + component: "parent", + fromSide: "bottom", + toSide: "bottom", + distance: -32 + }, + { + component: "Sidebar", + fromSide: "left", + toSide: "right", + distance: 32 + }, + { + component: "parent", + fromSide: "right", + toSide: "right", + distance: -32 + } + ] + }, + Content: { + constraints: [ + { + component: "Header", + fromSide: "top", + toSide: "bottom", + distance: 32 + }, + { + component: "Footer", + fromSide: "bottom", + toSide: "top", + distance: -32 + }, + { + component: "Sidebar", + fromSide: "left", + toSide: "right", + distance: 32 + }, + { + component: "parent", + fromSide: "right", + toSide: "right", + distance: -32 + } + ] + } +};