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 }