/*******************************************************************************
* Copyright (c) 2011, 2015 itemis AG and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alexander Nyßen (itemis AG) - initial API and implementation
* Matthias Wienand (itemis AG) - contribution for Bugzilla #355997
*
*******************************************************************************/
package org.eclipse.gef.geometry.planar;
import org.eclipse.gef.geometry.euclidean.Angle;
import org.eclipse.gef.geometry.internal.utils.PrecisionUtils;
/**
* Represents the geometric shape of a rounded rectangle, i.e. a rectangle with
* rounded corners.
*
* <pre>
* arc-width
* width
* .-------------------.
* .--------.
* +----+-----+-----+----+
* / / | ^ ^ |
* arc- | | | | | |
* height | | + <- arc end point -> +
* | | | |
* | \ | |
* height | + +
* | | |
* | | |
* | + <- arc end point -> +
* | | | | |
* \ | v v |
* +----+-----+-----+----+
* </pre>
*
* The maximum value for the arc-width is the width of the rectangle and the
* maximum value for the arc-height is the height of the rectangle. For the
* maximal values, the end points of the arcs are at the centers of the sides of
* the rectangle.
* <p>
* Note that while all manipulations (e.g. within shrink, expand) within this
* class are based on double precision, all comparisons (e.g. within contains,
* intersects, equals, etc.) are based on a limited precision (with an accuracy
* defined within {@link PrecisionUtils}) to compensate for rounding effects.
*
* @author anyssen
* @author mwienand
*
*/
public final class RoundedRectangle
extends AbstractRectangleBasedGeometry<RoundedRectangle, PolyBezier>
implements IShape {
private static final long serialVersionUID = 1L;
private double arcWidth;
private double arcHeight;
/**
* Constructs a new {@link RoundedRectangle} from the given bounds and arc
* values.
*
* @param x
* the x-coordinate of the new {@link RoundedRectangle}'s bounds
* @param y
* the y-coordinate of the new {@link RoundedRectangle}'s bounds
* @param width
* the width of the new {@link RoundedRectangle}'s bounds
* @param height
* the height of the new {@link RoundedRectangle}'s bounds
* @param arcWidth
* the arc width of the new {@link RoundedRectangle} rounded
* corners
* @param arcHeight
* the arc height of the new {@link RoundedRectangle} rounded
* corners
*/
public RoundedRectangle(double x, double y, double width, double height,
double arcWidth, double arcHeight) {
super(x, y, width, height);
this.arcWidth = arcWidth;
this.arcHeight = arcHeight;
}
/**
* Constructs a new {@link RoundedRectangle} from the bounds of the given
* {@link Rectangle} and the given arc values.
*
* @param r
* the {@link Rectangle}, whose bounds are used to initialize the
* x, y, width, and height values of the new
* {@link RoundedRectangle}
* @param arcWidth
* the arc width of the new {@link RoundedRectangle} rounded
* corners
* @param arcHeight
* the arc height of the new {@link RoundedRectangle} rounded
* corners
*/
public RoundedRectangle(Rectangle r, double arcWidth, double arcHeight) {
this(r.getX(), r.getY(), r.getWidth(), r.getHeight(), arcWidth,
arcHeight);
}
@Override
public boolean contains(IGeometry g) {
return ShapeUtils.contains(this, g);
}
/**
* @see IGeometry#contains(Point)
*/
@Override
public boolean contains(final Point p) {
// quick rejection via bounds
final Rectangle testRect = getBounds();
if (!testRect.contains(p)) {
return false;
}
// limit arc width and arc height
double aw = getEffectiveArcWidth() / 2;
double ah = getEffectiveArcHeight() / 2;
// check for containment within the inner rectangles
testRect.setBounds(x, y + ah, width, height - 2 * ah);
if (testRect.contains(p)) {
return true;
}
testRect.setBounds(x + aw, y, width - 2 * aw, height);
if (testRect.contains(p)) {
return true;
}
// check the arcs
final Ellipse e = new Ellipse(x, y, 2 * aw, 2 * ah);
if (e.contains(p)) {
return true;
}
e.setBounds(x, y + height - 2 * ah, 2 * aw, 2 * ah);
if (e.contains(p)) {
return true;
}
e.setBounds(x + width - 2 * aw, y, 2 * aw, 2 * ah);
if (e.contains(p)) {
return true;
}
e.setBounds(x + width - 2 * aw, y + height - 2 * ah, 2 * aw, 2 * ah);
if (e.contains(p)) {
return true;
}
return false;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof RoundedRectangle)) {
return false;
}
RoundedRectangle o = (RoundedRectangle) obj;
return PrecisionUtils.equal(x, o.x) && PrecisionUtils.equal(y, o.y)
&& PrecisionUtils.equal(width, o.width)
&& PrecisionUtils.equal(height, o.height)
&& PrecisionUtils.equal(arcWidth, o.arcWidth)
&& PrecisionUtils.equal(arcHeight, o.arcHeight);
}
/**
* Returns the arc height of this {@link RoundedRectangle}, which is the
* height of the arc used to define its rounded corners.
*
* @return the arc height
*/
public double getArcHeight() {
return arcHeight;
}
/**
* Returns the arc width of this {@link RoundedRectangle}, which is the
* width of the arc used to define its rounded corners.
*
* @return the arc height
*/
public double getArcWidth() {
return arcWidth;
}
/**
* Returns the bottom edge of this {@link RoundedRectangle}.
*
* @return the bottom edge of this {@link RoundedRectangle}.
*/
public Line getBottom() {
double aw = getEffectiveArcWidth() / 2;
return new Line(x + aw, y + height, x + width - aw, y + height);
}
/**
* Returns the bottom left {@link Arc} of this {@link RoundedRectangle}.
*
* @return the bottom left {@link Arc} of this {@link RoundedRectangle}.
*/
public Arc getBottomLeftArc() {
double aw = getEffectiveArcWidth() / 2;
double ah = getEffectiveArcHeight() / 2;
return new Arc(x, y + height - 2 * ah, 2 * aw, 2 * ah,
Angle.fromDeg(180), Angle.fromDeg(90));
}
/**
* Returns the bottom right {@link Arc} of this {@link RoundedRectangle}.
*
* @return the bottom right {@link Arc} of this {@link RoundedRectangle}.
*/
public Arc getBottomRightArc() {
double aw = getEffectiveArcWidth() / 2;
double ah = getEffectiveArcHeight() / 2;
return new Arc(x + width - 2 * aw, y + height - 2 * ah, 2 * aw, 2 * ah,
Angle.fromDeg(270), Angle.fromDeg(90));
}
/**
* @see IGeometry#getCopy()
*/
@Override
public RoundedRectangle getCopy() {
return new RoundedRectangle(x, y, width, height, arcWidth, arcHeight);
}
/**
* Returns the effective arc height, i.e. clamped to the range
* <code>[0;height]</code>.
*
* @return the effective arc height, i.e. clamped to the range
* <code>[0;height]</code>.
*/
protected double getEffectiveArcHeight() {
double ah = arcHeight < 0 ? 0 : arcHeight;
return ah > height ? height : ah;
}
/**
* Returns the effective arc width, i.e. clamped to the range
* <code>[0;width]</code>.
*
* @return the effective arc width, i.e. clamped to the range
* <code>[0;width]</code>.
*/
protected double getEffectiveArcWidth() {
double aw = arcWidth < 0 ? 0 : arcWidth;
return aw > width ? width : aw;
}
/**
* Returns the left edge of this {@link RoundedRectangle}.
*
* @return the left edge of this {@link RoundedRectangle}.
*/
public Line getLeft() {
double ah = getEffectiveArcHeight() / 2;
return new Line(x, y + ah, x, y + height - ah);
}
@Override
public PolyBezier getOutline() {
return ShapeUtils.getOutline(this);
}
/**
* @see org.eclipse.gef.geometry.planar.IShape#getOutlineSegments()
*/
@Override
public BezierCurve[] getOutlineSegments() {
double aw = getEffectiveArcWidth() / 2;
double ah = getEffectiveArcHeight() / 2;
return new BezierCurve[] {
ShapeUtils.computeEllipticalArcApproximation(x + width - 2 * aw,
y, 2 * aw, 2 * ah, Angle.fromDeg(0), Angle.fromDeg(90)),
new Line(x + width - aw, y, x + aw, y),
ShapeUtils.computeEllipticalArcApproximation(x, y, 2 * aw,
2 * ah, Angle.fromDeg(90), Angle.fromDeg(180)),
new Line(x, y + ah, x, y + height - ah),
ShapeUtils.computeEllipticalArcApproximation(x,
y + height - 2 * ah, 2 * aw, 2 * ah, Angle.fromDeg(180),
Angle.fromDeg(270)),
new Line(x + aw, y + height, x + width - aw, y + height),
ShapeUtils.computeEllipticalArcApproximation(x + width - 2 * aw,
y + height - 2 * ah, 2 * aw, 2 * ah, Angle.fromDeg(270),
Angle.fromDeg(360)),
new Line(x + width, y + height - ah, x + width, y + ah) };
}
/**
* Returns the right edge of this {@link RoundedRectangle}.
*
* @return the right edge of this {@link RoundedRectangle}.
*/
public Line getRight() {
double ah = getEffectiveArcHeight() / 2;
return new Line(x + width, y + ah, x + width, y + height - ah);
}
@Override
public PolyBezier getRotatedCCW(Angle angle) {
return getOutline().rotateCCW(angle);
}
@Override
public PolyBezier getRotatedCCW(Angle angle, double cx, double cy) {
return getOutline().rotateCCW(angle, cx, cy);
}
@Override
public PolyBezier getRotatedCCW(Angle angle, Point center) {
return getOutline().rotateCCW(angle, center);
}
@Override
public PolyBezier getRotatedCW(Angle angle) {
return getOutline().rotateCW(angle);
}
@Override
public PolyBezier getRotatedCW(Angle angle, double cx, double cy) {
return getOutline().rotateCW(angle, cx, cy);
}
@Override
public PolyBezier getRotatedCW(Angle angle, Point center) {
return getOutline().rotateCW(angle, center);
}
/**
* Returns the top edge of this {@link RoundedRectangle}.
*
* @return the top edge of this {@link RoundedRectangle}.
*/
public Line getTop() {
double aw = getEffectiveArcWidth() / 2;
return new Line(x + aw, y, x + width - aw, y);
}
/**
* Returns the top left {@link Arc} of this {@link RoundedRectangle}.
*
* @return the top left {@link Arc} of this {@link RoundedRectangle}.
*/
public Arc getTopLeftArc() {
return new Arc(x, y, getEffectiveArcWidth(), getEffectiveArcHeight(),
Angle.fromDeg(90), Angle.fromDeg(90));
}
/**
* Returns the top right {@link Arc} of this {@link RoundedRectangle}.
*
* @return the top right {@link Arc} of this {@link RoundedRectangle}.
*/
public Arc getTopRightArc() {
double aw = getEffectiveArcWidth() / 2;
double ah = getEffectiveArcHeight() / 2;
return new Arc(x + width - 2 * aw, y, 2 * aw, 2 * ah, Angle.fromDeg(0),
Angle.fromDeg(90));
}
/**
* @see IGeometry#getTransformed(AffineTransform)
*/
@Override
public CurvedPolygon getTransformed(AffineTransform t) {
return new CurvedPolygon(getOutlineSegments()).getTransformed(t);
}
/**
* Sets the arc height of this {@link RoundedRectangle}, which is the height
* of the arc used to define its rounded corners.
*
* @param arcHeight
* the new arc height
* @return <code>this</code> for convenience
*/
public RoundedRectangle setArcHeight(double arcHeight) {
this.arcHeight = arcHeight;
return this;
}
/**
* Sets the arc width of this {@link RoundedRectangle}, which is the width
* of the arc used to define its rounded corners.
*
* @param arcWidth
* the new arc width
* @return <code>this</code> for convenience
*/
public RoundedRectangle setArcWidth(double arcWidth) {
this.arcWidth = arcWidth;
return this;
}
@Override
public Path toPath() {
return CurveUtils.toPath(getOutlineSegments()).close();
}
@Override
public String toString() {
return "RoundedRectangle(" + x + ", " + y + ", " + width + ", " + height //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ ", " + arcWidth + ", " + arcHeight + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
}