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