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