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 29 30 import javafx.beans.property.DoubleProperty; 31 import javafx.beans.property.DoublePropertyBase; 32 import javafx.css.CssMetaData; 33 import javafx.css.Styleable; 34 import javafx.css.StyleableDoubleProperty; 35 import javafx.css.StyleableProperty; 36 import javafx.scene.paint.Paint; 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.List; 40 import javafx.css.converter.SizeConverter; 41 import com.sun.javafx.geom.BaseBounds; 42 import com.sun.javafx.geom.RoundRectangle2D; 43 import com.sun.javafx.geom.transform.BaseTransform; 44 import com.sun.javafx.scene.DirtyBits; 45 import com.sun.javafx.scene.NodeHelper; 46 import com.sun.javafx.scene.shape.RectangleHelper; 47 import com.sun.javafx.scene.shape.ShapeHelper; 48 import com.sun.javafx.sg.prism.NGNode; 49 import com.sun.javafx.sg.prism.NGRectangle; 50 import com.sun.javafx.sg.prism.NGShape; 51 import javafx.scene.Node; 52 53 54 /** 55 * The {@code Rectangle} class defines a rectangle 56 * with the specified size and location. By default the rectangle 57 * has sharp corners. Rounded corners can be specified by setting both of 58 * the arcWidth and arcHeight properties to positive values {@code (> 0.0)}. 59 60 * <p>Example code: the following code creates a rectangle with 20 pixel 61 * rounded corners.</p> 62 * 63 <PRE> 64 import javafx.scene.shape.*; 65 66 Rectangle r = new Rectangle(); 67 r.setX(50); 68 r.setY(50); 69 r.setWidth(200); 70 r.setHeight(100); 71 r.setArcWidth(20); 72 r.setArcHeight(20); 73 </PRE> 74 * @since JavaFX 2.0 75 */ 76 public class Rectangle extends Shape { 77 static { 78 RectangleHelper.setRectangleAccessor(new RectangleHelper.RectangleAccessor() { 79 @Override 80 public NGNode doCreatePeer(Node node) { 81 return ((Rectangle) node).doCreatePeer(); 82 } 83 84 @Override 85 public void doUpdatePeer(Node node) { 86 ((Rectangle) node).doUpdatePeer(); 87 } 88 89 @Override 90 public com.sun.javafx.geom.Shape doConfigShape(Shape shape) { 91 return ((Rectangle) shape).doConfigShape(); 92 } 93 }); 94 } 95 96 private final RoundRectangle2D shape = new RoundRectangle2D(); 97 98 private static final int NON_RECTILINEAR_TYPE_MASK = ~( 99 BaseTransform.TYPE_TRANSLATION | 100 BaseTransform.TYPE_MASK_SCALE | 101 BaseTransform.TYPE_QUADRANT_ROTATION | 102 BaseTransform.TYPE_FLIP); 103 104 { 105 // To initialize the class helper at the begining each constructor of this class 106 RectangleHelper.initHelper(this); 107 } 108 109 /** 110 * Creates an empty instance of Rectangle. 111 */ 112 public Rectangle() { 113 } 114 115 /** 116 * Creates a new instance of Rectangle with the given size. 117 * @param width width of the rectangle 118 * @param height height of the rectangle 119 */ 120 public Rectangle(double width, double height) { 121 setWidth(width); 122 setHeight(height); 123 } 124 125 /** 126 * Creates a new instance of Rectangle with the given size and fill. 127 * @param width width of the rectangle 128 * @param height height of the rectangle 129 * @param fill determines how to fill the interior of the rectangle 130 */ 131 public Rectangle(double width, double height, Paint fill) { 132 setWidth(width); 133 setHeight(height); 134 setFill(fill); 135 } 136 137 /** 138 * Creates a new instance of Rectangle with the given position and size. 139 * @param x horizontal position of the rectangle 140 * @param y vertical position of the rectangle 141 * @param width width of the rectangle 142 * @param height height of the rectangle 143 */ 144 public Rectangle(double x, double y, double width, double height) { 145 this(width, height); 146 setX(x); 147 setY(y); 148 } 149 150 /** 151 * Defines the X coordinate of the upper-left corner of the rectangle. 152 * 153 * @defaultValue 0.0 154 */ 155 private DoubleProperty x; 156 157 158 public final void setX(double value) { 159 if (x != null || value != 0.0) { 160 xProperty().set(value); 161 } 162 } 163 164 public final double getX() { 165 return x == null ? 0.0 : x.get(); 166 } 167 168 public final DoubleProperty xProperty() { 169 if (x == null) { 170 x = new DoublePropertyBase() { 171 172 @Override 173 public void invalidated() { 174 NodeHelper.markDirty(Rectangle.this, DirtyBits.NODE_GEOMETRY); 175 impl_geomChanged(); 176 } 177 178 @Override 179 public Object getBean() { 180 return Rectangle.this; 181 } 182 183 @Override 184 public String getName() { 185 return "x"; 186 } 187 }; 188 } 189 return x; 190 } 191 192 /** 193 * Defines the Y coordinate of the upper-left corner of the rectangle. 194 * 195 * @defaultValue 0.0 196 */ 197 private DoubleProperty y; 198 199 public final void setY(double value) { 200 if (y != null || value != 0.0) { 201 yProperty().set(value); 202 } 203 } 204 205 public final double getY() { 206 return y == null ? 0.0 : y.get(); 207 } 208 209 public final DoubleProperty yProperty() { 210 if (y == null) { 211 y = new DoublePropertyBase() { 212 213 @Override 214 public void invalidated() { 215 NodeHelper.markDirty(Rectangle.this, DirtyBits.NODE_GEOMETRY); 216 impl_geomChanged(); 217 } 218 219 @Override 220 public Object getBean() { 221 return Rectangle.this; 222 } 223 224 @Override 225 public String getName() { 226 return "y"; 227 } 228 }; 229 } 230 return y; 231 } 232 233 /** 234 * Defines the width of the rectangle. 235 * 236 * @defaultValue 0.0 237 */ 238 private final DoubleProperty width = new DoublePropertyBase() { 239 240 @Override 241 public void invalidated() { 242 NodeHelper.markDirty(Rectangle.this, DirtyBits.NODE_GEOMETRY); 243 impl_geomChanged(); 244 } 245 246 @Override 247 public Object getBean() { 248 return Rectangle.this; 249 } 250 251 @Override 252 public String getName() { 253 return "width"; 254 } 255 }; 256 257 public final void setWidth(double value) { 258 width.set(value); 259 } 260 261 public final double getWidth() { 262 return width.get(); 263 } 264 265 public final DoubleProperty widthProperty() { 266 return width; 267 } 268 269 /** 270 * Defines the height of the rectangle. 271 * 272 * @defaultValue 0.0 273 */ 274 private final DoubleProperty height = new DoublePropertyBase() { 275 276 @Override 277 public void invalidated() { 278 NodeHelper.markDirty(Rectangle.this, DirtyBits.NODE_GEOMETRY); 279 impl_geomChanged(); 280 } 281 282 @Override 283 public Object getBean() { 284 return Rectangle.this; 285 } 286 287 @Override 288 public String getName() { 289 return "height"; 290 } 291 }; 292 293 294 public final void setHeight(double value) { 295 height.set(value); 296 } 297 298 public final double getHeight() { 299 return height.get(); 300 } 301 302 public final DoubleProperty heightProperty() { 303 return height; 304 } 305 306 /** 307 * Defines the horizontal diameter of the arc 308 * at the four corners of the rectangle. 309 * The rectangle will have rounded corners if and only if both of 310 * the arc width and arc height properties are greater than 0.0. 311 * 312 * @defaultValue 0.0 313 */ 314 private DoubleProperty arcWidth; 315 316 317 public final void setArcWidth(double value) { 318 if (arcWidth != null || value != 0.0) { 319 arcWidthProperty().set(value); 320 } 321 } 322 323 public final double getArcWidth() { 324 return arcWidth == null ? 0.0 : arcWidth.get(); 325 } 326 327 public final DoubleProperty arcWidthProperty() { 328 if (arcWidth == null) { 329 arcWidth = new StyleableDoubleProperty() { 330 331 @Override 332 public void invalidated() { 333 NodeHelper.markDirty(Rectangle.this, DirtyBits.NODE_GEOMETRY); 334 } 335 336 @Override 337 public CssMetaData<Rectangle, Number> getCssMetaData() { 338 return StyleableProperties.ARC_WIDTH; 339 } 340 341 @Override 342 public Object getBean() { 343 return Rectangle.this; 344 } 345 346 @Override 347 public String getName() { 348 return "arcWidth"; 349 } 350 }; 351 } 352 return arcWidth; 353 } 354 355 /** 356 * Defines the vertical diameter of the arc 357 * at the four corners of the rectangle. 358 * The rectangle will have rounded corners if and only if both of 359 * the arc width and arc height properties are greater than 0.0. 360 * 361 * @defaultValue 0.0 362 */ 363 private DoubleProperty arcHeight; 364 365 366 public final void setArcHeight(double value) { 367 if (arcHeight != null || value != 0.0) { 368 arcHeightProperty().set(value); 369 } 370 } 371 372 public final double getArcHeight() { 373 return arcHeight == null ? 0.0 : arcHeight.get(); 374 } 375 376 public final DoubleProperty arcHeightProperty() { 377 if (arcHeight == null) { 378 arcHeight = new StyleableDoubleProperty() { 379 380 @Override 381 public void invalidated() { 382 NodeHelper.markDirty(Rectangle.this, DirtyBits.NODE_GEOMETRY); 383 } 384 385 @Override 386 public CssMetaData<Rectangle, Number> getCssMetaData() { 387 return StyleableProperties.ARC_HEIGHT; 388 } 389 390 @Override 391 public Object getBean() { 392 return Rectangle.this; 393 } 394 395 @Override 396 public String getName() { 397 return "arcHeight"; 398 } 399 }; 400 } 401 return arcHeight; 402 } 403 404 /* 405 * Note: This method MUST only be called via its accessor method. 406 */ 407 private NGNode doCreatePeer() { 408 return new NGRectangle(); 409 } 410 411 /*************************************************************************** 412 * * 413 * Stylesheet Handling * 414 * * 415 **************************************************************************/ 416 417 /* 418 * Super-lazy instantiation pattern from Bill Pugh. 419 */ 420 private static class StyleableProperties { 421 private static final CssMetaData<Rectangle,Number> ARC_HEIGHT = 422 new CssMetaData<Rectangle,Number>("-fx-arc-height", 423 SizeConverter.getInstance(), 0.0) { 424 425 @Override 426 public boolean isSettable(Rectangle node) { 427 return node.arcHeight == null || !node.arcHeight.isBound(); 428 } 429 430 @Override 431 public StyleableProperty<Number> getStyleableProperty(Rectangle node) { 432 return (StyleableProperty<Number>)node.arcHeightProperty(); 433 } 434 435 }; 436 private static final CssMetaData<Rectangle,Number> ARC_WIDTH = 437 new CssMetaData<Rectangle,Number>("-fx-arc-width", 438 SizeConverter.getInstance(), 0.0) { 439 440 @Override 441 public boolean isSettable(Rectangle node) { 442 return node.arcWidth == null || !node.arcWidth.isBound(); 443 } 444 445 @Override 446 public StyleableProperty<Number> getStyleableProperty(Rectangle node) { 447 return (StyleableProperty<Number>)node.arcWidthProperty(); 448 } 449 450 }; 451 452 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 453 static { 454 final List<CssMetaData<? extends Styleable, ?>> styleables = 455 new ArrayList<CssMetaData<? extends Styleable, ?>>(Shape.getClassCssMetaData()); 456 styleables.add(ARC_HEIGHT); 457 styleables.add(ARC_WIDTH); 458 STYLEABLES = Collections.unmodifiableList(styleables); 459 460 } 461 } 462 463 /** 464 * @return The CssMetaData associated with this class, which may include the 465 * CssMetaData of its super classes. 466 * @since JavaFX 8.0 467 */ 468 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 469 return StyleableProperties.STYLEABLES; 470 } 471 472 /** 473 * {@inheritDoc} 474 * 475 * @since JavaFX 8.0 476 */ 477 478 479 @Override 480 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 481 return getClassCssMetaData(); 482 } 483 484 /** 485 */ 486 @Override StrokeLineJoin convertLineJoin(StrokeLineJoin t) { 487 // If we are a round rectangle then MITER can produce anomalous 488 // results for very thin or very wide corner arcs when the bezier 489 // curves that approximate the arcs become so distorted that they 490 // shoot out MITER-like extensions. This effect complicates matters 491 // because it makes such "round" rectangles non-round, and also 492 // because it means we might have to pad the bounds to account 493 // for this rare and unpredictable circumstance. 494 // To avoid the problem, we set the Join style to BEVEL for any 495 // rounded rect. The BEVEL join style is more predictable for 496 // anomalous angles and is the simplest join style to compute in 497 // the stroking code. 498 // For non-rounded rectangles, the angles are all 90 degrees and so 499 // the computations are both simple and non-problematic so we pass on 500 // the join style unmodified to the PG layer. 501 if ((getArcWidth() > 0) && (getArcHeight() > 0)) { 502 return StrokeLineJoin.BEVEL; 503 } 504 return t; 505 } 506 507 /** 508 * @treatAsPrivate implementation detail 509 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 510 */ 511 @Deprecated 512 @Override 513 public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) { 514 // if there is no fill or stroke, then there are no bounds. The bounds 515 // must be marked empty in this case to distinguish it from 0,0,0,0 516 // which would actually contribute to the bounds of a group. 517 if (getMode() == NGShape.Mode.EMPTY) { 518 return bounds.makeEmpty(); 519 } 520 if ((getArcWidth() > 0) && (getArcHeight() > 0) 521 && ((tx.getType() & NON_RECTILINEAR_TYPE_MASK) != 0)) { 522 return computeShapeBounds(bounds, tx, ShapeHelper.configShape(this)); 523 } 524 double upad; 525 double dpad; 526 if ((getMode() == NGShape.Mode.FILL) || (getStrokeType() == StrokeType.INSIDE)) { 527 upad = dpad = 0; 528 } else { 529 upad = getStrokeWidth(); 530 if (getStrokeType() == StrokeType.CENTERED) { 531 upad /= 2.0; 532 } 533 dpad = 0.0f; 534 } 535 return computeBounds(bounds, tx, upad, dpad, getX(), getY(), getWidth(), getHeight()); 536 } 537 538 /* 539 * Note: This method MUST only be called via its accessor method. 540 */ 541 private RoundRectangle2D doConfigShape() { 542 if ((getArcWidth() > 0) && (getArcHeight() > 0)) { 543 shape.setRoundRect((float)getX(), (float)getY(), 544 (float)getWidth(), (float)getHeight(), 545 (float)getArcWidth(), (float)getArcHeight()); 546 } else { 547 shape.setRoundRect( 548 (float)getX(), (float)getY(), 549 (float)getWidth(), (float)getHeight(), 0, 0); 550 } 551 return shape; 552 } 553 554 /* 555 * Note: This method MUST only be called via its accessor method. 556 */ 557 private void doUpdatePeer() { 558 if (NodeHelper.isDirty(this, DirtyBits.NODE_GEOMETRY)) { 559 final NGRectangle peer = NodeHelper.getPeer(this); 560 peer.updateRectangle((float)getX(), 561 (float)getY(), 562 (float)getWidth(), 563 (float)getHeight(), 564 (float)getArcWidth(), 565 (float)getArcHeight()); 566 } 567 } 568 569 /** 570 * Returns a string representation of this {@code Rectangle} object. 571 * @return a string representation of this {@code Rectangle} object. 572 */ 573 @Override 574 public String toString() { 575 final StringBuilder sb = new StringBuilder("Rectangle["); 576 577 String id = getId(); 578 if (id != null) { 579 sb.append("id=").append(id).append(", "); 580 } 581 582 sb.append("x=").append(getX()); 583 sb.append(", y=").append(getY()); 584 sb.append(", width=").append(getWidth()); 585 sb.append(", height=").append(getHeight()); 586 587 sb.append(", fill=").append(getFill()); 588 589 Paint stroke = getStroke(); 590 if (stroke != null) { 591 sb.append(", stroke=").append(stroke); 592 sb.append(", strokeWidth=").append(getStrokeWidth()); 593 } 594 595 return sb.append("]").toString(); 596 } 597 }