1 /*
   2  * Copyright (c) 2010, 2015, 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.effect;
  27 
  28 import javafx.beans.property.DoubleProperty;
  29 import javafx.beans.property.DoublePropertyBase;
  30 import javafx.beans.property.ObjectProperty;
  31 import javafx.scene.Node;
  32 
  33 import com.sun.javafx.util.Utils;
  34 import com.sun.javafx.effect.EffectDirtyBits;
  35 import com.sun.javafx.geom.BaseBounds;
  36 import com.sun.javafx.geom.transform.BaseTransform;
  37 import com.sun.javafx.scene.BoundsAccessor;
  38 
  39 
  40 /**
  41  * An effect that renders a reflected version of the input below the
  42  * actual input content.
  43  * <p>
  44  * Note that the reflection of a {@code Node} with a {@code Reflection}
  45  * effect installed will not respond to mouse events or the containment
  46  * methods on the {@code Node}.
  47  *
  48  * <p>
  49  * Example:
  50  * <pre><code>
  51  * Reflection reflection = new Reflection();
  52  * reflection.setFraction(0.7);
  53  *
  54  * Text text = new Text();
  55  * text.setX(10.0);
  56  * text.setY(50.0);
  57  * text.setCache(true);
  58  * text.setText("Reflections on JavaFX...");
  59  * text.setFill(Color.web("0x3b596d"));
  60  * text.setFont(Font.font(null, FontWeight.BOLD, 40));
  61  * text.setEffect(reflection);
  62  * </pre></code>
  63  * <p> The code above produces the following: </p>
  64  * <p>
  65  * <img src="doc-files/reflection.png"/>
  66  * </p>
  67  * @since JavaFX 2.0
  68  */
  69 public class Reflection extends Effect {
  70     /**
  71      * Creates a new instance of Reflection with default parameters.
  72      */
  73     public Reflection() {}
  74 
  75     /**
  76      * Creates a new instance of Reflection with the specified topOffset, fraction,
  77      * topOpacity and bottomOpacity.
  78      * @param topOffset the distance between the bottom of the input and the top of the reflection
  79      * @param fraction the fraction of the input that is visible in the reflection
  80      * @param topOpacity the opacity of the reflection at its top extreme
  81      * @param bottomOpacity the opacity of the reflection at its bottom extreme
  82      * @since JavaFX 2.1
  83      */
  84     public Reflection(double topOffset, double fraction,
  85                       double topOpacity, double bottomOpacity) {
  86         setBottomOpacity(bottomOpacity);
  87         setTopOffset(topOffset);
  88         setTopOpacity(topOpacity);
  89         setFraction(fraction);
  90     }
  91 
  92     @Override
  93     com.sun.scenario.effect.Reflection impl_createImpl() {
  94         return new com.sun.scenario.effect.Reflection();
  95     };
  96     /**
  97      * The input for this {@code Effect}.
  98      * If set to {@code null}, or left unspecified, a graphical image of
  99      * the {@code Node} to which the {@code Effect} is attached will be
 100      * used as the input.
 101      * @defaultValue null
 102      */
 103     private ObjectProperty<Effect> input;
 104 
 105 
 106     public final void setInput(Effect value) {
 107         inputProperty().set(value);
 108     }
 109 
 110     public final Effect getInput() {
 111         return input == null ? null : input.get();
 112     }
 113 
 114     public final ObjectProperty<Effect> inputProperty() {
 115         if (input == null) {
 116             input = new EffectInputProperty("input");
 117         }
 118         return input;
 119     }
 120 
 121     @Override
 122     boolean impl_checkChainContains(Effect e) {
 123         Effect localInput = getInput();
 124         if (localInput == null)
 125             return false;
 126         if (localInput == e)
 127             return true;
 128         return localInput.impl_checkChainContains(e);
 129     }
 130 
 131     /**
 132      * The top offset adjustment, which is the distance between the
 133      * bottom of the input and the top of the reflection.
 134      * <pre>
 135      *       Min: n/a
 136      *       Max: n/a
 137      *   Default: 0.0
 138      *  Identity: 0.0
 139      * </pre>
 140      * @defaultValue 0.0
 141      */
 142     private DoubleProperty topOffset;
 143 
 144 
 145     public final void setTopOffset(double value) {
 146         topOffsetProperty().set(value);
 147     }
 148 
 149     public final double getTopOffset() {
 150         return topOffset == null ? 0 : topOffset.get();
 151     }
 152 
 153     public final DoubleProperty topOffsetProperty() {
 154         if (topOffset == null) {
 155             topOffset = new DoublePropertyBase() {
 156 
 157                 @Override
 158                 public void invalidated() {
 159                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 160                     effectBoundsChanged();
 161                 }
 162 
 163                 @Override
 164                 public Object getBean() {
 165                     return Reflection.this;
 166                 }
 167 
 168                 @Override
 169                 public String getName() {
 170                     return "topOffset";
 171                 }
 172             };
 173         }
 174         return topOffset;
 175     }
 176 
 177     /**
 178      * The top opacity value, which is the opacity of the reflection
 179      * at its top extreme.
 180      * <pre>
 181      *       Min: 0.0
 182      *       Max: 1.0
 183      *   Default: 0.5
 184      *  Identity: 1.0
 185      * </pre>
 186      * @defaultValue 0.5
 187      */
 188     private DoubleProperty topOpacity;
 189 
 190 
 191     public final void setTopOpacity(double value) {
 192         topOpacityProperty().set(value);
 193     }
 194 
 195     public final double getTopOpacity() {
 196         return topOpacity == null ? 0.5 : topOpacity.get();
 197     }
 198 
 199     public final DoubleProperty topOpacityProperty() {
 200         if (topOpacity == null) {
 201             topOpacity = new DoublePropertyBase(0.5) {
 202 
 203                 @Override
 204                 public void invalidated() {
 205                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 206                 }
 207 
 208                 @Override
 209                 public Object getBean() {
 210                     return Reflection.this;
 211                 }
 212 
 213                 @Override
 214                 public String getName() {
 215                     return "topOpacity";
 216                 }
 217             };
 218         }
 219         return topOpacity;
 220     }
 221 
 222     /**
 223      * The bottom opacity value, which is the opacity of the reflection
 224      * at its bottom extreme.
 225      * <pre>
 226      *       Min: 0.0
 227      *       Max: 1.0
 228      *   Default: 0.0
 229      *  Identity: 1.0
 230      * </pre>
 231      * @defaultValue 0.0
 232      */
 233     private DoubleProperty bottomOpacity;
 234 
 235 
 236     public final void setBottomOpacity(double value) {
 237         bottomOpacityProperty().set(value);
 238     }
 239 
 240     public final double getBottomOpacity() {
 241         return bottomOpacity == null ? 0 : bottomOpacity.get();
 242     }
 243 
 244     public final DoubleProperty bottomOpacityProperty() {
 245         if (bottomOpacity == null) {
 246             bottomOpacity = new DoublePropertyBase() {
 247 
 248                 @Override
 249                 public void invalidated() {
 250                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 251                 }
 252 
 253                 @Override
 254                 public Object getBean() {
 255                     return Reflection.this;
 256                 }
 257 
 258                 @Override
 259                 public String getName() {
 260                     return "bottomOpacity";
 261                 }
 262             };
 263         }
 264         return bottomOpacity;
 265     }
 266 
 267     /**
 268      * The fraction of the input that is visible in the reflection.
 269      * For example, a value of 0.5 means that only the bottom half of the
 270      * input will be visible in the reflection.
 271      * <pre>
 272      *       Min: 0.0
 273      *       Max: 1.0
 274      *   Default: 0.75
 275      *  Identity: 1.0
 276      * </pre>
 277      * @defaultValue 0.75
 278      */
 279     private DoubleProperty fraction;
 280 
 281 
 282     public final void setFraction(double value) {
 283         fractionProperty().set(value);
 284     }
 285 
 286     public final double getFraction() {
 287         return fraction == null ? 0.75 : fraction.get();
 288     }
 289 
 290     public final DoubleProperty fractionProperty() {
 291         if (fraction == null) {
 292             fraction = new DoublePropertyBase(0.75) {
 293 
 294                 @Override
 295                 public void invalidated() {
 296                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 297                     effectBoundsChanged();
 298                 }
 299 
 300                 @Override
 301                 public Object getBean() {
 302                     return Reflection.this;
 303                 }
 304 
 305                 @Override
 306                 public String getName() {
 307                     return "fraction";
 308                 }
 309             };
 310         }
 311         return fraction;
 312     }
 313 
 314     private float getClampedFraction() {
 315         return (float)Utils.clamp(0, getFraction(), 1);
 316     }
 317 
 318     private float getClampedBottomOpacity() {
 319         return (float)Utils.clamp(0, getBottomOpacity(), 1);
 320     }
 321 
 322     private float getClampedTopOpacity() {
 323         return (float)Utils.clamp(0, getTopOpacity(), 1);
 324     }
 325 
 326     @Override
 327     void impl_update() {
 328         Effect localInput = getInput();
 329         if (localInput != null) {
 330             localInput.impl_sync();
 331         }
 332 
 333         com.sun.scenario.effect.Reflection peer =
 334                 (com.sun.scenario.effect.Reflection) impl_getImpl();
 335         peer.setInput(localInput == null ? null : localInput.impl_getImpl());
 336         peer.setFraction(getClampedFraction());
 337         peer.setTopOffset((float)getTopOffset());
 338         peer.setBottomOpacity(getClampedBottomOpacity());
 339         peer.setTopOpacity(getClampedTopOpacity());
 340     }
 341 
 342     /**
 343      * @treatAsPrivate implementation detail
 344      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 345      */
 346     @Deprecated
 347     @Override
 348     public BaseBounds impl_getBounds(BaseBounds bounds,
 349                                      BaseTransform tx,
 350                                      Node node,
 351                                      BoundsAccessor boundsAccessor) {
 352         bounds = getInputBounds(bounds,
 353                                 BaseTransform.IDENTITY_TRANSFORM,
 354                                 node, boundsAccessor,
 355                                 getInput());
 356         bounds.roundOut();
 357 
 358         float x1 = bounds.getMinX();
 359         float y1 = bounds.getMaxY() + (float)getTopOffset();
 360         float z1 = bounds.getMinZ();
 361         float x2 = bounds.getMaxX();
 362         float y2 = y1 + (getClampedFraction() * bounds.getHeight());
 363         float z2 = bounds.getMaxZ();
 364 
 365         BaseBounds ret = BaseBounds.getInstance(x1, y1, z1, x2, y2, z2);
 366         ret = ret.deriveWithUnion(bounds);
 367 
 368         return transformBounds(tx, ret);
 369     }
 370 
 371     /**
 372      * @treatAsPrivate implementation detail
 373      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 374      */
 375     @Deprecated
 376     @Override
 377     public Effect impl_copy() {
 378         Reflection ref = new Reflection(this.getTopOffset(), this.getFraction(),
 379                 this.getTopOpacity(), this.getBottomOpacity());
 380         ref.setInput(ref.getInput());
 381         return ref;
 382     }
 383 }