Skip to content

Commit

Permalink
[1831] Add geometric primitives with tests
Browse files Browse the repository at this point in the history
Bug: #1831
Signed-off-by: Pierre-Charles David <[email protected]>
  • Loading branch information
pcdavid committed Mar 17, 2023
1 parent 1d4e5e1 commit e3c9097
Show file tree
Hide file tree
Showing 7 changed files with 715 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.components.diagrams.layout.experimental;

import org.eclipse.sirius.components.diagrams.layoutdata.Position;
import org.eclipse.sirius.components.diagrams.layoutdata.Size;

/**
* Possible values for anchoring a box inside a container.
*
* @author pcdavid
*/
public enum Anchor {
TOP_LEFT,
TOP_CENTER,
TOP_RIGHT,

MIDDLE_LEFT,
MIDDLE_CENTER,
MIDDLE_RIGHT,

BOTTOM_LEFT,
BOTTOM_CENTER,
BOTTOM_RIGHT;

public Position apply(Rectangle container, Size boxSize) {
return switch (this) {
case TOP_LEFT -> container.topLeft();
case TOP_CENTER -> container.topCenter().translate(-boxSize.width() / 2.0, 0);
case TOP_RIGHT -> container.topRight().translate(-boxSize.width(), 0.0);

case MIDDLE_LEFT -> container.middleLeft().translate(0.0, -boxSize.height() / 2.0);
case MIDDLE_CENTER -> container.middleCenter().translate(-boxSize.width() / 2.0, -boxSize.height() / 2.0);
case MIDDLE_RIGHT -> container.middleRight().translate(-boxSize.width(), -boxSize.height() / 2.0);

case BOTTOM_LEFT -> container.bottomLeft().translate(0.0, -boxSize.height());
case BOTTOM_CENTER -> container.bottomCenter().translate(-boxSize.width() / 2.0, -boxSize.height());
case BOTTOM_RIGHT -> container.bottomRight().translate(-boxSize.width(), -boxSize.height());
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.components.diagrams.layout.experimental;

/**
* Specifies the offsets in all 4 directions. Can be used to represent a margin, border, or padding.
*
* @author pcdavid
*/
public record Offsets(double top, double bottom, double left, double right) {

private static final Offsets EMPTY = Offsets.of(0.0);

public Offsets(double value) {
this(value, value, value, value);
}

public Offsets(double top, double bottom, double left, double right) {
this.top = checkNonNegative(top, "top");
this.bottom = checkNonNegative(bottom, "bottom");
this.left = checkNonNegative(left, "left");
this.right = checkNonNegative(right, "right");
}

public static Offsets of(double value) {
return new Offsets(value);
}

public static Offsets empty() {
return EMPTY;
}

public double width() {
return this.left + this.right;
}

public double height() {
return this.top + this.bottom;
}

public Offsets combine(Offsets other) {
return new Offsets(this.top + other.top, this.bottom + other.bottom, this.left + other.left, this.right + other.right);
}

public Builder newOffsets() {
return new Builder();
}

private static double checkNonNegative(double value, String name) {
if (value < 0) {
throw new IllegalArgumentException(name + " can not be negative");
}
return value;
}

public static class Builder {
private double top;

private double bottom;

private double left;

private double right;

public Builder vertical(double value) {
return this.top(value).bottom(value);
}

public Builder horizontal(double value) {
return this.left(value).right(value);
}

public Builder top(double newTop) {
this.top = checkNonNegative(newTop, "top");
return this;
}

public Builder bottom(double newBottom) {
this.bottom = checkNonNegative(newBottom, "bottom");
return this;
}

public Builder left(double newLeft) {
this.left = checkNonNegative(newLeft, "left");
return this;
}

public Builder right(double newRight) {
this.right = checkNonNegative(newRight, "right");
return this;
}

public Offsets build() {
return new Offsets(this.top, this.bottom, this.left, this.right);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.components.diagrams.layout.experimental;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import org.eclipse.sirius.components.diagrams.layoutdata.Position;
import org.eclipse.sirius.components.diagrams.layoutdata.Size;

/**
* Represents a rectangular area.
*
* @author pcdavid
*/
public record Rectangle(double x, double y, double width, double height) {

public Rectangle(double x, double y, double width, double height) {
if (width < 0) {
throw new IllegalArgumentException("width can not be negative");
}
if (height < 0) {
throw new IllegalArgumentException("height can not be negative");
}
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}

public Rectangle(Position position, Size size) {
this(position.x(), position.y(), size.width(), size.height());
}

public Rectangle translate(double dx, double dy) {
return new Rectangle(this.x + dx, this.y + dy, this.width, this.height);
}

public Rectangle moveTo(Position newPosition) {
return new Rectangle(newPosition.x(), newPosition.y(), this.width, this.height);
}

public Rectangle moveTo(double newX, double newY) {
return new Rectangle(newX, newY, this.width, this.height);
}

public Rectangle resize(double newWidth, double newHeight) {
return new Rectangle(this.x, this.y, newWidth, newHeight);
}

public Rectangle expand(Offsets offsets) {
return new Rectangle(this.x - offsets.left(), this.y - offsets.top(), this.width + offsets.width(), this.height + offsets.height());
}

public Rectangle shrink(Offsets offsets) {
return new Rectangle(this.x + offsets.left(), this.y + offsets.top(), this.width - offsets.width(), this.height - offsets.height());
}

public Rectangle union(Rectangle... others) {
List<Rectangle> rectangles = new ArrayList<>();
rectangles.add(this);
rectangles.addAll(Arrays.asList(others));

double minX = rectangles.stream().map(Rectangle::x).min(Comparator.naturalOrder()).orElse(0.0);
double minY = rectangles.stream().map(Rectangle::y).min(Comparator.naturalOrder()).orElse(0.0);
double maxX = rectangles.stream().map(r -> r.x + r.width).max(Comparator.naturalOrder()).orElse(0.0);
double maxY = rectangles.stream().map(r -> r.y + r.height).max(Comparator.naturalOrder()).orElse(0.0);

return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}

public Rectangle intersection(Rectangle other) {
double topleftX = Math.max(this.x, other.x);
double topleftY = Math.max(this.y, other.y);
double bottomrightX = Math.min(this.bottomRight().x(), other.bottomRight().x());
double bottomrightY = Math.min(this.bottomRight().y(), other.bottomRight().y());
if (topleftX > bottomrightX || topleftY > bottomrightY) {
return new Rectangle(0, 0, 0, 0);
} else {
return new Rectangle(topleftX, topleftY, bottomrightX - topleftX, bottomrightY - topleftY);
}
}

public boolean isEmpty() {
return this.width == 0.0 || this.height == 0.0;
}

public boolean overlaps(Rectangle other) {
return !this.intersection(other).isEmpty();
}

public boolean includes(Rectangle other) {
return this.intersection(other).equals(other);
}

public Size size() {
return new Size(this.width, this.height);
}

public Position topLeft() {
return new Position(this.x, this.y);
}

public Position topCenter() {
return this.topLeft().midPoint(this.topRight());
}

public Position topRight() {
return new Position(this.x + this.width, this.y);
}

public Position middleLeft() {
return this.topLeft().midPoint(this.bottomLeft());
}

public Position middleCenter() {
return this.topLeft().midPoint(this.bottomRight());
}

public Position middleRight() {
return this.topRight().midPoint(this.bottomRight());
}

public Position bottomLeft() {
return new Position(this.x, this.y + this.height);
}

public Position bottomCenter() {
return this.bottomLeft().midPoint(this.bottomRight());
}

public Position bottomRight() {
return new Position(this.x + this.width, this.y + this.height);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.components.diagrams.layout.experimental;

import static org.assertj.core.api.Assertions.assertThat;

import org.eclipse.sirius.components.diagrams.layoutdata.Position;
import org.eclipse.sirius.components.diagrams.layoutdata.Size;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
* Tests for the box anchoring logic.
*
* @author pcdavid
*/
public class AnchorTests {

private Rectangle container;
private Size boxSize;

@BeforeEach
public void setup() {
this.container = new Rectangle(0, 0, 100, 100);
this.boxSize = new Size(10, 10);
}

@Test
public void testTopLeft() {
assertThat(Anchor.TOP_LEFT.apply(this.container, this.boxSize)).isEqualTo(new Position(0, 0));
}

@Test
public void testTopCenter() {
assertThat(Anchor.TOP_CENTER.apply(this.container, this.boxSize)).isEqualTo(new Position(45.0, 0));
}

@Test
public void testTopRight() {
assertThat(Anchor.TOP_RIGHT.apply(this.container, this.boxSize)).isEqualTo(new Position(90.0, 0));
}

@Test
public void testMiddleLeft() {
assertThat(Anchor.MIDDLE_LEFT.apply(this.container, this.boxSize)).isEqualTo(new Position(0.0, 45.0));
}

@Test
public void testMiddleCenter() {
assertThat(Anchor.MIDDLE_CENTER.apply(this.container, this.boxSize)).isEqualTo(new Position(45.0, 45.0));
}

@Test
public void testMiddleRight() {
assertThat(Anchor.MIDDLE_RIGHT.apply(this.container, this.boxSize)).isEqualTo(new Position(90.0, 45.0));
}

@Test
public void testBottomLeft() {
assertThat(Anchor.BOTTOM_LEFT.apply(this.container, this.boxSize)).isEqualTo(new Position(0.0, 90.0));
}

@Test
public void testBottomCenter() {
assertThat(Anchor.BOTTOM_CENTER.apply(this.container, this.boxSize)).isEqualTo(new Position(45.0, 90.0));
}

@Test
public void testBottomRight() {
assertThat(Anchor.BOTTOM_RIGHT.apply(this.container, this.boxSize)).isEqualTo(new Position(90.0, 90.0));
}
}
Loading

0 comments on commit e3c9097

Please sign in to comment.