javafx.scene.shape.Rectangle Maven / Gradle / Ivy
/*
* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene.shape;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.DoublePropertyBase;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleableDoubleProperty;
import javafx.css.StyleableProperty;
import javafx.scene.paint.Paint;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.css.converter.SizeConverter;
import com.sun.javafx.geom.BaseBounds;
import com.sun.javafx.geom.RoundRectangle2D;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.scene.DirtyBits;
import com.sun.javafx.scene.NodeHelper;
import com.sun.javafx.scene.shape.RectangleHelper;
import com.sun.javafx.scene.shape.ShapeHelper;
import com.sun.javafx.sg.prism.NGNode;
import com.sun.javafx.sg.prism.NGRectangle;
import com.sun.javafx.sg.prism.NGShape;
import javafx.scene.Node;
/**
* The {@code Rectangle} class defines a rectangle
* with the specified size and location. By default the rectangle
* has sharp corners. Rounded corners can be specified by setting both of
* the arcWidth and arcHeight properties to positive values {@code (> 0.0)}.
* Example code: the following code creates a rectangle with 20 pixel
* rounded corners.
*
import javafx.scene.shape.*;
Rectangle r = new Rectangle();
r.setX(50);
r.setY(50);
r.setWidth(200);
r.setHeight(100);
r.setArcWidth(20);
r.setArcHeight(20);
* @since JavaFX 2.0
*/
public class Rectangle extends Shape {
static {
RectangleHelper.setRectangleAccessor(new RectangleHelper.RectangleAccessor() {
@Override
public NGNode doCreatePeer(Node node) {
return ((Rectangle) node).doCreatePeer();
}
@Override
public void doUpdatePeer(Node node) {
((Rectangle) node).doUpdatePeer();
}
@Override
public BaseBounds doComputeGeomBounds(Node node,
BaseBounds bounds, BaseTransform tx) {
return ((Rectangle) node).doComputeGeomBounds(bounds, tx);
}
@Override
public com.sun.javafx.geom.Shape doConfigShape(Shape shape) {
return ((Rectangle) shape).doConfigShape();
}
});
}
private final RoundRectangle2D shape = new RoundRectangle2D();
private static final int NON_RECTILINEAR_TYPE_MASK = ~(
BaseTransform.TYPE_TRANSLATION |
BaseTransform.TYPE_MASK_SCALE |
BaseTransform.TYPE_QUADRANT_ROTATION |
BaseTransform.TYPE_FLIP);
{
// To initialize the class helper at the begining each constructor of this class
RectangleHelper.initHelper(this);
}
/**
* Creates an empty instance of Rectangle.
*/
public Rectangle() {
}
/**
* Creates a new instance of Rectangle with the given size.
* @param width width of the rectangle
* @param height height of the rectangle
*/
public Rectangle(double width, double height) {
setWidth(width);
setHeight(height);
}
/**
* Creates a new instance of Rectangle with the given size and fill.
* @param width width of the rectangle
* @param height height of the rectangle
* @param fill determines how to fill the interior of the rectangle
*/
public Rectangle(double width, double height, Paint fill) {
setWidth(width);
setHeight(height);
setFill(fill);
}
/**
* Creates a new instance of Rectangle with the given position and size.
* @param x horizontal position of the rectangle
* @param y vertical position of the rectangle
* @param width width of the rectangle
* @param height height of the rectangle
*/
public Rectangle(double x, double y, double width, double height) {
this(width, height);
setX(x);
setY(y);
}
/**
* Defines the X coordinate of the upper-left corner of the rectangle.
*
* @defaultValue 0.0
*/
private DoubleProperty x;
public final void setX(double value) {
if (x != null || value != 0.0) {
xProperty().set(value);
}
}
public final double getX() {
return x == null ? 0.0 : x.get();
}
public final DoubleProperty xProperty() {
if (x == null) {
x = new DoublePropertyBase() {
@Override
public void invalidated() {
NodeHelper.markDirty(Rectangle.this, DirtyBits.NODE_GEOMETRY);
NodeHelper.geomChanged(Rectangle.this);
}
@Override
public Object getBean() {
return Rectangle.this;
}
@Override
public String getName() {
return "x";
}
};
}
return x;
}
/**
* Defines the Y coordinate of the upper-left corner of the rectangle.
*
* @defaultValue 0.0
*/
private DoubleProperty y;
public final void setY(double value) {
if (y != null || value != 0.0) {
yProperty().set(value);
}
}
public final double getY() {
return y == null ? 0.0 : y.get();
}
public final DoubleProperty yProperty() {
if (y == null) {
y = new DoublePropertyBase() {
@Override
public void invalidated() {
NodeHelper.markDirty(Rectangle.this, DirtyBits.NODE_GEOMETRY);
NodeHelper.geomChanged(Rectangle.this);
}
@Override
public Object getBean() {
return Rectangle.this;
}
@Override
public String getName() {
return "y";
}
};
}
return y;
}
/**
* Defines the width of the rectangle.
*
* @defaultValue 0.0
*/
private final DoubleProperty width = new DoublePropertyBase() {
@Override
public void invalidated() {
NodeHelper.markDirty(Rectangle.this, DirtyBits.NODE_GEOMETRY);
NodeHelper.geomChanged(Rectangle.this);
}
@Override
public Object getBean() {
return Rectangle.this;
}
@Override
public String getName() {
return "width";
}
};
public final void setWidth(double value) {
width.set(value);
}
public final double getWidth() {
return width.get();
}
public final DoubleProperty widthProperty() {
return width;
}
/**
* Defines the height of the rectangle.
*
* @defaultValue 0.0
*/
private final DoubleProperty height = new DoublePropertyBase() {
@Override
public void invalidated() {
NodeHelper.markDirty(Rectangle.this, DirtyBits.NODE_GEOMETRY);
NodeHelper.geomChanged(Rectangle.this);
}
@Override
public Object getBean() {
return Rectangle.this;
}
@Override
public String getName() {
return "height";
}
};
public final void setHeight(double value) {
height.set(value);
}
public final double getHeight() {
return height.get();
}
public final DoubleProperty heightProperty() {
return height;
}
/**
* Defines the horizontal diameter of the arc
* at the four corners of the rectangle.
* The rectangle will have rounded corners if and only if both of
* the arc width and arc height properties are greater than 0.0.
*
* @defaultValue 0.0
*/
private DoubleProperty arcWidth;
public final void setArcWidth(double value) {
if (arcWidth != null || value != 0.0) {
arcWidthProperty().set(value);
}
}
public final double getArcWidth() {
return arcWidth == null ? 0.0 : arcWidth.get();
}
public final DoubleProperty arcWidthProperty() {
if (arcWidth == null) {
arcWidth = new StyleableDoubleProperty() {
@Override
public void invalidated() {
NodeHelper.markDirty(Rectangle.this, DirtyBits.NODE_GEOMETRY);
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.ARC_WIDTH;
}
@Override
public Object getBean() {
return Rectangle.this;
}
@Override
public String getName() {
return "arcWidth";
}
};
}
return arcWidth;
}
/**
* Defines the vertical diameter of the arc
* at the four corners of the rectangle.
* The rectangle will have rounded corners if and only if both of
* the arc width and arc height properties are greater than 0.0.
*
* @defaultValue 0.0
*/
private DoubleProperty arcHeight;
public final void setArcHeight(double value) {
if (arcHeight != null || value != 0.0) {
arcHeightProperty().set(value);
}
}
public final double getArcHeight() {
return arcHeight == null ? 0.0 : arcHeight.get();
}
public final DoubleProperty arcHeightProperty() {
if (arcHeight == null) {
arcHeight = new StyleableDoubleProperty() {
@Override
public void invalidated() {
NodeHelper.markDirty(Rectangle.this, DirtyBits.NODE_GEOMETRY);
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.ARC_HEIGHT;
}
@Override
public Object getBean() {
return Rectangle.this;
}
@Override
public String getName() {
return "arcHeight";
}
};
}
return arcHeight;
}
/*
* Note: This method MUST only be called via its accessor method.
*/
private NGNode doCreatePeer() {
return new NGRectangle();
}
/***************************************************************************
* *
* Stylesheet Handling *
* *
**************************************************************************/
/*
* Super-lazy instantiation pattern from Bill Pugh.
*/
private static class StyleableProperties {
private static final CssMetaData ARC_HEIGHT =
new CssMetaData("-fx-arc-height",
SizeConverter.getInstance(), 0.0) {
@Override
public boolean isSettable(Rectangle node) {
return node.arcHeight == null || !node.arcHeight.isBound();
}
@Override
public StyleableProperty getStyleableProperty(Rectangle node) {
return (StyleableProperty)node.arcHeightProperty();
}
};
private static final CssMetaData ARC_WIDTH =
new CssMetaData("-fx-arc-width",
SizeConverter.getInstance(), 0.0) {
@Override
public boolean isSettable(Rectangle node) {
return node.arcWidth == null || !node.arcWidth.isBound();
}
@Override
public StyleableProperty getStyleableProperty(Rectangle node) {
return (StyleableProperty)node.arcWidthProperty();
}
};
private static final List> STYLEABLES;
static {
final List> styleables =
new ArrayList>(Shape.getClassCssMetaData());
styleables.add(ARC_HEIGHT);
styleables.add(ARC_WIDTH);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
/**
* @return The CssMetaData associated with this class, which may include the
* CssMetaData of its superclasses.
* @since JavaFX 8.0
*/
public static List> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
/**
* {@inheritDoc}
*
* @since JavaFX 8.0
*/
@Override
public List> getCssMetaData() {
return getClassCssMetaData();
}
/**
*/
@Override StrokeLineJoin convertLineJoin(StrokeLineJoin t) {
// If we are a round rectangle then MITER can produce anomalous
// results for very thin or very wide corner arcs when the bezier
// curves that approximate the arcs become so distorted that they
// shoot out MITER-like extensions. This effect complicates matters
// because it makes such "round" rectangles non-round, and also
// because it means we might have to pad the bounds to account
// for this rare and unpredictable circumstance.
// To avoid the problem, we set the Join style to BEVEL for any
// rounded rect. The BEVEL join style is more predictable for
// anomalous angles and is the simplest join style to compute in
// the stroking code.
// For non-rounded rectangles, the angles are all 90 degrees and so
// the computations are both simple and non-problematic so we pass on
// the join style unmodified to the PG layer.
if ((getArcWidth() > 0) && (getArcHeight() > 0)) {
return StrokeLineJoin.BEVEL;
}
return t;
}
/*
* Note: This method MUST only be called via its accessor method.
*/
private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) {
// if there is no fill or stroke, then there are no bounds. The bounds
// must be marked empty in this case to distinguish it from 0,0,0,0
// which would actually contribute to the bounds of a group.
if (getMode() == NGShape.Mode.EMPTY) {
return bounds.makeEmpty();
}
if ((getArcWidth() > 0) && (getArcHeight() > 0)
&& ((tx.getType() & NON_RECTILINEAR_TYPE_MASK) != 0)) {
return computeShapeBounds(bounds, tx, ShapeHelper.configShape(this));
}
double upad;
double dpad;
if ((getMode() == NGShape.Mode.FILL) || (getStrokeType() == StrokeType.INSIDE)) {
upad = dpad = 0;
} else {
upad = getStrokeWidth();
if (getStrokeType() == StrokeType.CENTERED) {
upad /= 2.0;
}
dpad = 0.0f;
}
return computeBounds(bounds, tx, upad, dpad, getX(), getY(), getWidth(), getHeight());
}
/*
* Note: This method MUST only be called via its accessor method.
*/
private RoundRectangle2D doConfigShape() {
if ((getArcWidth() > 0) && (getArcHeight() > 0)) {
shape.setRoundRect((float)getX(), (float)getY(),
(float)getWidth(), (float)getHeight(),
(float)getArcWidth(), (float)getArcHeight());
} else {
shape.setRoundRect(
(float)getX(), (float)getY(),
(float)getWidth(), (float)getHeight(), 0, 0);
}
return shape;
}
/*
* Note: This method MUST only be called via its accessor method.
*/
private void doUpdatePeer() {
if (NodeHelper.isDirty(this, DirtyBits.NODE_GEOMETRY)) {
final NGRectangle peer = NodeHelper.getPeer(this);
peer.updateRectangle((float)getX(),
(float)getY(),
(float)getWidth(),
(float)getHeight(),
(float)getArcWidth(),
(float)getArcHeight());
}
}
/**
* Returns a string representation of this {@code Rectangle} object.
* @return a string representation of this {@code Rectangle} object.
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Rectangle[");
String id = getId();
if (id != null) {
sb.append("id=").append(id).append(", ");
}
sb.append("x=").append(getX());
sb.append(", y=").append(getY());
sb.append(", width=").append(getWidth());
sb.append(", height=").append(getHeight());
sb.append(", fill=").append(getFill());
Paint stroke = getStroke();
if (stroke != null) {
sb.append(", stroke=").append(stroke);
sb.append(", strokeWidth=").append(getStrokeWidth());
}
return sb.append("]").toString();
}
}