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