1 /*
   2  * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.shape;
  27 
  28 import com.sun.javafx.geom.BaseBounds;
  29 import com.sun.javafx.geom.Ellipse2D;
  30 import com.sun.javafx.geom.transform.BaseTransform;
  31 import com.sun.javafx.scene.DirtyBits;
  32 import com.sun.javafx.scene.NodeHelper;
  33 import com.sun.javafx.scene.shape.CircleHelper;
  34 import com.sun.javafx.scene.shape.ShapeHelper;
  35 import com.sun.javafx.sg.prism.NGCircle;
  36 import com.sun.javafx.sg.prism.NGNode;
  37 import com.sun.javafx.sg.prism.NGShape;
  38 import javafx.beans.property.DoubleProperty;
  39 import javafx.beans.property.DoublePropertyBase;
  40 import javafx.scene.Node;
  41 import javafx.scene.paint.Paint;
  42 
  43 /**
  44  * The {@code Circle} class creates a new circle
  45  * with the specified radius and center location measured in pixels
  46  *
  47  * Example usage. The following code creates a circle with radius 50px centered
  48  * at (100,100)px.
  49  *
  50 <PRE>
  51 import javafx.scene.shape.*;
  52 
  53 Circle circle = new Circle();
  54 circle.setCenterX(100.0f);
  55 circle.setCenterY(100.0f);
  56 circle.setRadius(50.0f);
  57 }
  58 </PRE>
  59  * @since JavaFX 2.0
  60  */
  61 public class Circle extends Shape {
  62     static {
  63         CircleHelper.setCircleAccessor(new CircleHelper.CircleAccessor() {
  64             @Override
  65             public NGNode doCreatePeer(Node node) {
  66                 return ((Circle) node).doCreatePeer();
  67             }
  68 
  69             @Override
  70             public void doUpdatePeer(Node node) {
  71                 ((Circle) node).doUpdatePeer();
  72             }
  73 
  74             @Override
  75             public com.sun.javafx.geom.Shape doConfigShape(Shape shape) {
  76                 return ((Circle) shape).doConfigShape();
  77             }
  78         });
  79     }
  80 
  81     private final Ellipse2D shape = new Ellipse2D();
  82 
  83     {
  84         // To initialize the class helper at the begining each constructor of this class
  85         CircleHelper.initHelper(this);
  86     }
  87 
  88     /**
  89      * Creates a new instance of Circle with a specified radius.
  90      * @param radius the radius of the circle in pixels
  91      */
  92     public Circle(double radius) {
  93         setRadius(radius);
  94     }
  95 
  96     /**
  97      * Creates a new instance of Circle with a specified radius and fill.
  98      * @param radius the radius of the circle
  99      * @param fill determines how to fill the interior of the Circle
 100      */
 101     public Circle(double radius, Paint fill) {
 102         setRadius(radius);
 103         setFill(fill);
 104     }
 105 
 106     /**
 107      * Creates an empty instance of Circle.
 108      */
 109     public Circle() {
 110     }
 111 
 112     /**
 113      * Creates a new instance of Circle with a specified position and radius.
 114      * @param centerX the horizontal position of the center of the circle in pixels
 115      * @param centerY the vertical position of the center of the circle in pixels
 116      * @param radius the radius of the circle in pixels
 117      */
 118     public Circle(double centerX, double centerY, double radius) {
 119         setCenterX(centerX);
 120         setCenterY(centerY);
 121         setRadius(radius);
 122     }
 123 
 124     /**
 125      * Creates a new instance of Circle with a specified position, radius and fill.
 126      * @param centerX the horizontal position of the center of the circle in pixels
 127      * @param centerY the vertical position of the center of the circle in pixels
 128      * @param radius the radius of the circle in pixels
 129      * @param fill determines how to fill the interior of the Circle
 130      */
 131     public Circle(double centerX, double centerY, double radius, Paint fill) {
 132         setCenterX(centerX);
 133         setCenterY(centerY);
 134         setRadius(radius);
 135         setFill(fill);
 136     }
 137 
 138     /**
 139      * Defines the horizontal position of the center of the circle in pixels.
 140      *
 141      * @defaultValue 0.0
 142      */
 143     private DoubleProperty centerX;
 144 
 145 
 146 
 147     public final void setCenterX(double value) {
 148         if (centerX != null || value != 0.0) {
 149             centerXProperty().set(value);
 150         }
 151     }
 152 
 153     public final double getCenterX() {
 154         return centerX == null ? 0.0 : centerX.get();
 155     }
 156 
 157     public final DoubleProperty centerXProperty() {
 158         if (centerX == null) {
 159             centerX = new DoublePropertyBase(0.0) {
 160 
 161                 @Override
 162                 public void invalidated() {
 163                     NodeHelper.markDirty(Circle.this, DirtyBits.NODE_GEOMETRY);
 164                     impl_geomChanged();
 165                 }
 166 
 167                 @Override
 168                 public Object getBean() {
 169                     return Circle.this;
 170                 }
 171 
 172                 @Override
 173                 public String getName() {
 174                     return "centerX";
 175                 }
 176             };
 177         }
 178         return centerX;
 179     }
 180 
 181     /**
 182      * Defines the vertical position of the center of the circle in pixels.
 183      *
 184      * @defaultValue 0.0
 185      */
 186     private DoubleProperty centerY;
 187 
 188 
 189 
 190     public final void setCenterY(double value) {
 191         if (centerY != null || value != 0.0) {
 192             centerYProperty().set(value);
 193         }
 194     }
 195 
 196     public final double getCenterY() {
 197         return centerY == null ? 0.0 : centerY.get();
 198     }
 199 
 200     public final DoubleProperty centerYProperty() {
 201         if (centerY == null) {
 202             centerY = new DoublePropertyBase(0.0) {
 203 
 204                 @Override
 205                 public void invalidated() {
 206                     NodeHelper.markDirty(Circle.this, DirtyBits.NODE_GEOMETRY);
 207                     impl_geomChanged();
 208                 }
 209 
 210                 @Override
 211                 public Object getBean() {
 212                     return Circle.this;
 213                 }
 214 
 215                 @Override
 216                 public String getName() {
 217                     return "centerY";
 218                 }
 219             };
 220         }
 221         return centerY;
 222     }
 223 
 224     /**
 225      * Defines the radius of the circle in pixels.
 226      *
 227      * @defaultValue 0.0
 228      */
 229     private final DoubleProperty radius = new DoublePropertyBase() {
 230 
 231         @Override
 232         public void invalidated() {
 233             NodeHelper.markDirty(Circle.this, DirtyBits.NODE_GEOMETRY);
 234             impl_geomChanged();
 235         }
 236 
 237         @Override
 238         public Object getBean() {
 239             return Circle.this;
 240         }
 241 
 242         @Override
 243         public String getName() {
 244             return "radius";
 245         }
 246     };
 247 
 248     public final void setRadius(double value) {
 249         radius.set(value);
 250     }
 251 
 252     public final double getRadius() {
 253         return radius.get();
 254     }
 255 
 256     public final DoubleProperty radiusProperty() {
 257         return radius;
 258     }
 259 
 260     /**
 261      */
 262     @Override StrokeLineJoin convertLineJoin(StrokeLineJoin t) {
 263         // The MITER join style can produce anomalous results for very thin or
 264         // very wide ellipses when the bezier curves that approximate the arcs
 265         // become so distorted that they shoot out MITER-like extensions.  This
 266         // effect complicates matters because it makes the circles very non-round,
 267         // and also because it means we might have to pad the bounds to account
 268         // for this rare and unpredictable circumstance.
 269         // To avoid the problem, we set the Join style to BEVEL for any
 270         // circle.  The BEVEL join style is more predictable for
 271         // anomalous angles and is the simplest join style to compute in
 272         // the stroking code.
 273         // These problems do not necessarily happen for circles which have a
 274         // fixed and balanced aspect ratio, but why waste time computing a
 275         // conversion of a MITER join style when it has no advantage for
 276         // circles and technically requires more computation?
 277         return StrokeLineJoin.BEVEL;
 278     }
 279 
 280     /*
 281      * Note: This method MUST only be called via its accessor method.
 282      */
 283     private  NGNode doCreatePeer() {
 284         return new NGCircle();
 285     }
 286 
 287     /**
 288      * @treatAsPrivate implementation detail
 289      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 290      */
 291     @Deprecated
 292     @Override public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
 293         // if there is no fill or stroke, then there are no bounds. The bounds
 294         // must be marked empty in this case to distinguish it from 0,0,0,0
 295         // which would actually contribute to the bounds of a group.
 296         if (getMode() == NGShape.Mode.EMPTY) {
 297             return bounds.makeEmpty();
 298         }
 299 
 300         final double cX = getCenterX();
 301         final double cY = getCenterY();
 302 
 303         if ((tx.getType() & ~(BaseTransform.TYPE_MASK_ROTATION | BaseTransform.TYPE_TRANSLATION)) == 0) {
 304 
 305             double tCX = cX * tx.getMxx() + cY * tx.getMxy() + tx.getMxt();
 306             double tCY = cX * tx.getMyx() + cY * tx.getMyy() + tx.getMyt();
 307             double r = getRadius();
 308 
 309             if (getMode() != NGShape.Mode.FILL && getStrokeType() != StrokeType.INSIDE) {
 310                 double upad = getStrokeWidth();
 311                 if (getStrokeType() == StrokeType.CENTERED) {
 312                     upad /= 2.0f;
 313                 }
 314                 r += upad;
 315             }
 316 
 317             return bounds.deriveWithNewBounds((float) (tCX - r), (float) (tCY - r), 0,
 318                     (float) (tCX + r), (float) (tCY + r), 0);
 319         } else if ((tx.getType() & ~(BaseTransform.TYPE_MASK_SCALE | BaseTransform.TYPE_TRANSLATION | BaseTransform.TYPE_FLIP)) == 0) {
 320             final double r = getRadius();
 321             final double x = getCenterX() - r;
 322             final double y = getCenterY() - r;
 323             final double width = 2.0 * r;
 324             final double height = width;
 325             double upad;
 326             if (getMode() == NGShape.Mode.FILL || getStrokeType() == StrokeType.INSIDE) {
 327                 upad = 0.0f;
 328             } else {
 329                 upad = getStrokeWidth();
 330             }
 331             return computeBounds(bounds, tx, upad, 0, x, y, width, height);
 332         }
 333 
 334         return computeShapeBounds(bounds, tx, ShapeHelper.configShape(this));
 335     }
 336 
 337     /*
 338      * Note: This method MUST only be called via its accessor method.
 339      */
 340     private Ellipse2D doConfigShape() {
 341         double r = getRadius();
 342         shape.setFrame(
 343             (float)(getCenterX() - r), // x
 344             (float)(getCenterY() - r), // y
 345             (float)(r * 2.0), // w
 346             (float)(r * 2.0)); // h
 347         return shape;
 348     }
 349 
 350     /*
 351      * Note: This method MUST only be called via its accessor method.
 352      */
 353     private void doUpdatePeer() {
 354         if (NodeHelper.isDirty(this, DirtyBits.NODE_GEOMETRY)) {
 355             final NGCircle peer = NodeHelper.getPeer(this);
 356             peer.updateCircle((float)getCenterX(),
 357                 (float)getCenterY(),
 358                 (float)getRadius());
 359         }
 360     }
 361 
 362     /**
 363      * Returns a string representation of this {@code Circle} object.
 364      * @return a string representation of this {@code Circle} object.
 365      */
 366     @Override
 367     public String toString() {
 368         final StringBuilder sb = new StringBuilder("Circle[");
 369 
 370         String id = getId();
 371         if (id != null) {
 372             sb.append("id=").append(id).append(", ");
 373         }
 374 
 375         sb.append("centerX=").append(getCenterX());
 376         sb.append(", centerY=").append(getCenterY());
 377         sb.append(", radius=").append(getRadius());
 378 
 379         sb.append(", fill=").append(getFill());
 380 
 381         Paint stroke = getStroke();
 382         if (stroke != null) {
 383             sb.append(", stroke=").append(stroke);
 384             sb.append(", strokeWidth=").append(getStrokeWidth());
 385         }
 386 
 387         return sb.append("]").toString();
 388     }
 389 }
 390