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 BaseBounds doComputeGeomBounds(Node node,
  76                     BaseBounds bounds, BaseTransform tx) {
  77                 return ((Circle) node).doComputeGeomBounds(bounds, tx);
  78             }
  79 
  80             @Override
  81             public com.sun.javafx.geom.Shape doConfigShape(Shape shape) {
  82                 return ((Circle) shape).doConfigShape();
  83             }
  84         });
  85     }
  86 
  87     private final Ellipse2D shape = new Ellipse2D();
  88 
  89     {
  90         // To initialize the class helper at the begining each constructor of this class
  91         CircleHelper.initHelper(this);
  92     }
  93 
  94     /**
  95      * Creates a new instance of Circle with a specified radius.
  96      * @param radius the radius of the circle in pixels
  97      */
  98     public Circle(double radius) {
  99         setRadius(radius);
 100     }
 101 
 102     /**
 103      * Creates a new instance of Circle with a specified radius and fill.
 104      * @param radius the radius of the circle
 105      * @param fill determines how to fill the interior of the Circle
 106      */
 107     public Circle(double radius, Paint fill) {
 108         setRadius(radius);
 109         setFill(fill);
 110     }
 111 
 112     /**
 113      * Creates an empty instance of Circle.
 114      */
 115     public Circle() {
 116     }
 117 
 118     /**
 119      * Creates a new instance of Circle with a specified position and radius.
 120      * @param centerX the horizontal position of the center of the circle in pixels
 121      * @param centerY the vertical position of the center of the circle in pixels
 122      * @param radius the radius of the circle in pixels
 123      */
 124     public Circle(double centerX, double centerY, double radius) {
 125         setCenterX(centerX);
 126         setCenterY(centerY);
 127         setRadius(radius);
 128     }
 129 
 130     /**
 131      * Creates a new instance of Circle with a specified position, radius and fill.
 132      * @param centerX the horizontal position of the center of the circle in pixels
 133      * @param centerY the vertical position of the center of the circle in pixels
 134      * @param radius the radius of the circle in pixels
 135      * @param fill determines how to fill the interior of the Circle
 136      */
 137     public Circle(double centerX, double centerY, double radius, Paint fill) {
 138         setCenterX(centerX);
 139         setCenterY(centerY);
 140         setRadius(radius);
 141         setFill(fill);
 142     }
 143 
 144     /**
 145      * Defines the horizontal position of the center of the circle in pixels.
 146      *
 147      * @defaultValue 0.0
 148      */
 149     private DoubleProperty centerX;
 150 
 151 
 152 
 153     public final void setCenterX(double value) {
 154         if (centerX != null || value != 0.0) {
 155             centerXProperty().set(value);
 156         }
 157     }
 158 
 159     public final double getCenterX() {
 160         return centerX == null ? 0.0 : centerX.get();
 161     }
 162 
 163     public final DoubleProperty centerXProperty() {
 164         if (centerX == null) {
 165             centerX = new DoublePropertyBase(0.0) {
 166 
 167                 @Override
 168                 public void invalidated() {
 169                     NodeHelper.markDirty(Circle.this, DirtyBits.NODE_GEOMETRY);
 170                     NodeHelper.geomChanged(Circle.this);
 171                 }
 172 
 173                 @Override
 174                 public Object getBean() {
 175                     return Circle.this;
 176                 }
 177 
 178                 @Override
 179                 public String getName() {
 180                     return "centerX";
 181                 }
 182             };
 183         }
 184         return centerX;
 185     }
 186 
 187     /**
 188      * Defines the vertical position of the center of the circle in pixels.
 189      *
 190      * @defaultValue 0.0
 191      */
 192     private DoubleProperty centerY;
 193 
 194 
 195 
 196     public final void setCenterY(double value) {
 197         if (centerY != null || value != 0.0) {
 198             centerYProperty().set(value);
 199         }
 200     }
 201 
 202     public final double getCenterY() {
 203         return centerY == null ? 0.0 : centerY.get();
 204     }
 205 
 206     public final DoubleProperty centerYProperty() {
 207         if (centerY == null) {
 208             centerY = new DoublePropertyBase(0.0) {
 209 
 210                 @Override
 211                 public void invalidated() {
 212                     NodeHelper.markDirty(Circle.this, DirtyBits.NODE_GEOMETRY);
 213                     NodeHelper.geomChanged(Circle.this);
 214                 }
 215 
 216                 @Override
 217                 public Object getBean() {
 218                     return Circle.this;
 219                 }
 220 
 221                 @Override
 222                 public String getName() {
 223                     return "centerY";
 224                 }
 225             };
 226         }
 227         return centerY;
 228     }
 229 
 230     /**
 231      * Defines the radius of the circle in pixels.
 232      *
 233      * @defaultValue 0.0
 234      */
 235     private final DoubleProperty radius = new DoublePropertyBase() {
 236 
 237         @Override
 238         public void invalidated() {
 239             NodeHelper.markDirty(Circle.this, DirtyBits.NODE_GEOMETRY);
 240             NodeHelper.geomChanged(Circle.this);
 241         }
 242 
 243         @Override
 244         public Object getBean() {
 245             return Circle.this;
 246         }
 247 
 248         @Override
 249         public String getName() {
 250             return "radius";
 251         }
 252     };
 253 
 254     public final void setRadius(double value) {
 255         radius.set(value);
 256     }
 257 
 258     public final double getRadius() {
 259         return radius.get();
 260     }
 261 
 262     public final DoubleProperty radiusProperty() {
 263         return radius;
 264     }
 265 
 266     /**
 267      */
 268     @Override StrokeLineJoin convertLineJoin(StrokeLineJoin t) {
 269         // The MITER join style can produce anomalous results for very thin or
 270         // very wide ellipses when the bezier curves that approximate the arcs
 271         // become so distorted that they shoot out MITER-like extensions.  This
 272         // effect complicates matters because it makes the circles very non-round,
 273         // and also because it means we might have to pad the bounds to account
 274         // for this rare and unpredictable circumstance.
 275         // To avoid the problem, we set the Join style to BEVEL for any
 276         // circle.  The BEVEL join style is more predictable for
 277         // anomalous angles and is the simplest join style to compute in
 278         // the stroking code.
 279         // These problems do not necessarily happen for circles which have a
 280         // fixed and balanced aspect ratio, but why waste time computing a
 281         // conversion of a MITER join style when it has no advantage for
 282         // circles and technically requires more computation?
 283         return StrokeLineJoin.BEVEL;
 284     }
 285 
 286     /*
 287      * Note: This method MUST only be called via its accessor method.
 288      */
 289     private  NGNode doCreatePeer() {
 290         return new NGCircle();
 291     }
 292 
 293     /*
 294      * Note: This method MUST only be called via its accessor method.
 295      */
 296     private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) {
 297         // if there is no fill or stroke, then there are no bounds. The bounds
 298         // must be marked empty in this case to distinguish it from 0,0,0,0
 299         // which would actually contribute to the bounds of a group.
 300         if (getMode() == NGShape.Mode.EMPTY) {
 301             return bounds.makeEmpty();
 302         }
 303 
 304         final double cX = getCenterX();
 305         final double cY = getCenterY();
 306 
 307         if ((tx.getType() & ~(BaseTransform.TYPE_MASK_ROTATION | BaseTransform.TYPE_TRANSLATION)) == 0) {
 308 
 309             double tCX = cX * tx.getMxx() + cY * tx.getMxy() + tx.getMxt();
 310             double tCY = cX * tx.getMyx() + cY * tx.getMyy() + tx.getMyt();
 311             double r = getRadius();
 312 
 313             if (getMode() != NGShape.Mode.FILL && getStrokeType() != StrokeType.INSIDE) {
 314                 double upad = getStrokeWidth();
 315                 if (getStrokeType() == StrokeType.CENTERED) {
 316                     upad /= 2.0f;
 317                 }
 318                 r += upad;
 319             }
 320 
 321             return bounds.deriveWithNewBounds((float) (tCX - r), (float) (tCY - r), 0,
 322                     (float) (tCX + r), (float) (tCY + r), 0);
 323         } else if ((tx.getType() & ~(BaseTransform.TYPE_MASK_SCALE | BaseTransform.TYPE_TRANSLATION | BaseTransform.TYPE_FLIP)) == 0) {
 324             final double r = getRadius();
 325             final double x = getCenterX() - r;
 326             final double y = getCenterY() - r;
 327             final double width = 2.0 * r;
 328             final double height = width;
 329             double upad;
 330             if (getMode() == NGShape.Mode.FILL || getStrokeType() == StrokeType.INSIDE) {
 331                 upad = 0.0f;
 332             } else {
 333                 upad = getStrokeWidth();
 334             }
 335             return computeBounds(bounds, tx, upad, 0, x, y, width, height);
 336         }
 337 
 338         return computeShapeBounds(bounds, tx, ShapeHelper.configShape(this));
 339     }
 340 
 341     /*
 342      * Note: This method MUST only be called via its accessor method.
 343      */
 344     private Ellipse2D doConfigShape() {
 345         double r = getRadius();
 346         shape.setFrame(
 347             (float)(getCenterX() - r), // x
 348             (float)(getCenterY() - r), // y
 349             (float)(r * 2.0), // w
 350             (float)(r * 2.0)); // h
 351         return shape;
 352     }
 353 
 354     /*
 355      * Note: This method MUST only be called via its accessor method.
 356      */
 357     private void doUpdatePeer() {
 358         if (NodeHelper.isDirty(this, DirtyBits.NODE_GEOMETRY)) {
 359             final NGCircle peer = NodeHelper.getPeer(this);
 360             peer.updateCircle((float)getCenterX(),
 361                 (float)getCenterY(),
 362                 (float)getRadius());
 363         }
 364     }
 365 
 366     /**
 367      * Returns a string representation of this {@code Circle} object.
 368      * @return a string representation of this {@code Circle} object.
 369      */
 370     @Override
 371     public String toString() {
 372         final StringBuilder sb = new StringBuilder("Circle[");
 373 
 374         String id = getId();
 375         if (id != null) {
 376             sb.append("id=").append(id).append(", ");
 377         }
 378 
 379         sb.append("centerX=").append(getCenterX());
 380         sb.append(", centerY=").append(getCenterY());
 381         sb.append(", radius=").append(getRadius());
 382 
 383         sb.append(", fill=").append(getFill());
 384 
 385         Paint stroke = getStroke();
 386         if (stroke != null) {
 387             sb.append(", stroke=").append(stroke);
 388             sb.append(", strokeWidth=").append(getStrokeWidth());
 389         }
 390 
 391         return sb.append("]").toString();
 392     }
 393 }
 394