1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 package unlock;
  33 
  34 import java.net.URL;
  35 import java.util.ResourceBundle;
  36 
  37 import javafx.animation.FadeTransition;
  38 import javafx.animation.ParallelTransition;
  39 import javafx.animation.SequentialTransition;
  40 import javafx.animation.Transition;
  41 import javafx.event.ActionEvent;
  42 import javafx.fxml.FXML;
  43 import javafx.scene.Node;
  44 import javafx.scene.control.Button;
  45 import javafx.scene.input.KeyEvent;
  46 import javafx.scene.shape.Rectangle;
  47 import javafx.util.Callback;
  48 import javafx.util.Duration;
  49 
  50 /**
  51  * The controller for our 'Unlock' application, see 'Unlock.fxml'.
  52  * This class has all the logic to open the theater's doors using JavaFX
  53  * transitions.
  54  */
  55 public final class UnlockController {
  56 
  57     @FXML // ResourceBundle that was given to the FXMLLoader
  58     private ResourceBundle resources;
  59     @FXML // URL location of the FXML file that was given to the FXMLLoader
  60     private URL location;
  61     @FXML
  62     private Node root;
  63     @FXML // fx:id="pad"
  64     private Keypad pad; // Value injected by FXMLLoader
  65     @FXML // fx:id="error"
  66     private Rectangle error; // Value injected by FXMLLoader
  67     @FXML // fx:id="lock"
  68     private Button lock; // Value injected by FXMLLoader
  69     @FXML // fx:id="okleft"
  70     private Rectangle okleft; // Value injected by FXMLLoader
  71     @FXML // fx:id="okright"
  72     private Rectangle okright; // Value injected by FXMLLoader
  73     @FXML // fx:id="unlockbottom"
  74     private Rectangle unlockbottom; // Value injected by FXMLLoader
  75     @FXML // fx:id="unlocktop"
  76     private Rectangle unlocktop; // Value injected by FXMLLoader
  77     private boolean open = false;
  78 
  79     private final static class HeightTransition extends Transition {
  80 
  81         final Rectangle node;
  82         final double height;
  83 
  84         public HeightTransition(Duration duration, Rectangle node) {
  85             this(duration, node, node.getHeight());
  86         }
  87 
  88         public HeightTransition(Duration duration, Rectangle node, double height) {
  89             this.node = node;
  90             this.height = height;
  91             this.setCycleDuration(duration);
  92         }
  93 
  94         public Duration getDuration() {
  95             return getCycleDuration();
  96         }
  97 
  98         @Override
  99         protected void interpolate(double frac) {
 100             this.node.setHeight((1.0 - frac) * height);
 101         }
 102     }
 103 
 104     private final static class HeightAndLayoutYTransition extends Transition {
 105 
 106         final Rectangle node;
 107         final double height;
 108         final double layoutY;
 109 
 110         public HeightAndLayoutYTransition(Duration duration, Rectangle node) {
 111             this(duration, node, node.getHeight(), node.getLayoutY());
 112         }
 113 
 114         public HeightAndLayoutYTransition(Duration duration, Rectangle node, double height, double layoutY) {
 115             this.node = node;
 116             this.height = height;
 117             this.layoutY = layoutY;
 118             this.setCycleDuration(duration);
 119         }
 120 
 121         public Duration getDuration() {
 122             return getCycleDuration();
 123         }
 124 
 125         @Override
 126         protected void interpolate(double frac) {
 127             this.node.setHeight((1.0 - frac) * height);
 128             this.node.setLayoutY((1.0 + frac) * layoutY);
 129         }
 130     }
 131 
 132     private final static class WidthTransition extends Transition {
 133 
 134         final Rectangle node;
 135         final double width;
 136 
 137         public WidthTransition(Duration duration, Rectangle node) {
 138             this(duration, node, node.getWidth());
 139         }
 140 
 141         public WidthTransition(Duration duration, Rectangle node, double width) {
 142             this.node = node;
 143             this.width = width;
 144             this.setCycleDuration(duration);
 145         }
 146 
 147         public Duration getDuration() {
 148             return getCycleDuration();
 149         }
 150 
 151         @Override
 152         protected void interpolate(double frac) {
 153             this.node.setWidth((1.0 - frac) * width);
 154         }
 155     }
 156 
 157     private final static class WidthAndLayoutXTransition extends Transition {
 158 
 159         final Rectangle node;
 160         final double width;
 161         final double layoutX;
 162 
 163         public WidthAndLayoutXTransition(Duration duration, Rectangle node) {
 164             this(duration, node, node.getWidth(), node.getLayoutX());
 165         }
 166 
 167         public WidthAndLayoutXTransition(Duration duration, Rectangle node, double width, double layoutX) {
 168             this.node = node;
 169             this.width = width;
 170             this.layoutX = layoutX;
 171             this.setCycleDuration(duration);
 172         }
 173 
 174         public Duration getDuration() {
 175             return getCycleDuration();
 176         }
 177 
 178         @Override
 179         protected void interpolate(double frac) {
 180             this.node.setWidth((1.0 - frac) * width);
 181             this.node.setLayoutX((1.0 + frac) * layoutX);
 182         }
 183     }
 184 
 185     private FadeTransition fadeOut(final Duration duration, final Node node) {
 186         final FadeTransition fadeOut = new FadeTransition(duration, node);
 187         fadeOut.setFromValue(1);
 188         fadeOut.setToValue(0);
 189         fadeOut.setOnFinished(arg0 -> node.setVisible(false));
 190         return fadeOut;
 191     }
 192 
 193     // Handler for Button[fx:id="lock"] onAction
 194     @FXML
 195     void unlockPressed(ActionEvent event) {
 196         // handle the event here
 197         lock.setDisable(true);
 198         root.requestFocus();
 199 
 200         final FadeTransition fadeLockButton = fadeOut(Duration.valueOf("1s"), lock);
 201         final HeightTransition openLockTop = new HeightTransition(Duration.valueOf("2s"), unlocktop);
 202         openLockTop.setOnFinished(arg0 -> {
 203             unlocktop.setVisible(false);
 204             unlocktop.setHeight(openLockTop.height);
 205         });
 206 
 207         final HeightAndLayoutYTransition openLockBottom = new HeightAndLayoutYTransition(Duration.valueOf("2s"), unlockbottom);
 208         openLockBottom.setOnFinished(arg0 -> {
 209             unlockbottom.setVisible(false);
 210             unlockbottom.setHeight(openLockBottom.height);
 211             unlockbottom.setLayoutY(openLockBottom.layoutY);
 212         });
 213         final ParallelTransition openLock = new ParallelTransition(openLockTop, openLockBottom);
 214         final SequentialTransition unlock = new SequentialTransition(fadeLockButton, openLock);
 215         unlock.play();
 216     }
 217 
 218     private void resetVisibility() {
 219         pad.setOpacity(1.0);
 220         lock.setOpacity(1.0);
 221         pad.setVisible(true);
 222         lock.setVisible(true);
 223         lock.setDisable(false);
 224         okright.setVisible(true);
 225         okleft.setVisible(true);
 226         unlocktop.setVisible(true);
 227         unlockbottom.setVisible(true);
 228     }
 229 
 230     // Handler for AnchorPane[id="AnchorPane"] onKeyPressed
 231     private void keyboardKeyPressed(KeyEvent event) {
 232         if (" ".equals(event.getCharacter())) {
 233             // When "Hello World" is displayed (the theater is open) - pressing
 234             // the space bar will reinitialize the application.
 235             if (open) {
 236                 // Reinitializing the application...
 237                 open = false;
 238                 resetVisibility();
 239                 lock.requestFocus();
 240             }
 241         }
 242     }
 243 
 244     private final class ValidateCallback implements Callback<String, Boolean> {
 245         private ValidateCallback() {
 246         }
 247         @Override
 248         public Boolean call(String param) {
 249             final boolean accessGranted = "1234".equals(param);
 250             if (accessGranted) {
 251                 grantAccess();
 252             } else {
 253                 rejectAccess();
 254             }
 255             return accessGranted;
 256         }
 257 
 258     }
 259 
 260     @FXML // This method is called by the FXMLLoader when initialization is complete
 261     void initialize() {
 262         assert error != null : "fx:id=\"error\" was not injected: check your FXML file 'Unlock.fxml'.";
 263         assert lock != null : "fx:id=\"lock\" was not injected: check your FXML file 'Unlock.fxml'.";
 264         assert okleft != null : "fx:id=\"okleft\" was not injected: check your FXML file 'Unlock.fxml'.";
 265         assert okright != null : "fx:id=\"okright\" was not injected: check your FXML file 'Unlock.fxml'.";
 266         assert pad != null : "fx:id=\"pad\" was not injected: check your FXML file 'Unlock.fxml'.";
 267         assert root != null : "fx:id=\"root\" was not injected: check your FXML file 'Unlock.fxml'.";
 268         assert unlockbottom != null : "fx:id=\"unlockbottom\" was not injected: check your FXML file 'Unlock.fxml'.";
 269         assert unlocktop != null : "fx:id=\"unlocktop\" was not injected: check your FXML file 'Unlock.fxml'.";
 270 
 271         // Set pin validation for the keypad
 272         pad.setValidateCallback(new ValidateCallback());
 273 
 274         // Reset visibility and opacity of nodes - useful if you left your
 275         // FXML in a 'bad' state
 276         resetVisibility();
 277 
 278         // Add event handler to the root - used to handle the space bar key at the
 279         // end of the application
 280         root.addEventHandler(KeyEvent.KEY_TYPED, event -> {
 281             if (event instanceof KeyEvent) {
 282                 keyboardKeyPressed((KeyEvent) event);
 283             }
 284         });
 285     }
 286 
 287     private void grantAccess() {
 288         root.requestFocus();
 289         FadeTransition fadeOutPad = fadeOut(Duration.valueOf("1s"), pad);
 290 
 291         final WidthTransition openOkLeft =
 292                 new WidthTransition(Duration.valueOf("2s"), okleft);
 293         openOkLeft.setOnFinished(arg0 -> {
 294             okleft.setVisible(false);
 295             okleft.setWidth(openOkLeft.width);
 296         });
 297 
 298         final WidthAndLayoutXTransition openOkRight =
 299                 new WidthAndLayoutXTransition(openOkLeft.getDuration(), okright);
 300         openOkRight.setOnFinished(arg0 -> {
 301             okright.setVisible(false);
 302             okright.setWidth(openOkRight.width);
 303             okright.setLayoutX(openOkRight.layoutX);
 304         });
 305 
 306         final ParallelTransition openOk =
 307                 new ParallelTransition(openOkLeft, openOkRight);
 308 
 309         final SequentialTransition okTrans =
 310                 new SequentialTransition(fadeOutPad, openOk);
 311         okTrans.setOnFinished(arg0 -> {
 312             open = true;
 313             root.requestFocus();
 314         });
 315         okTrans.play();
 316     }
 317 
 318     private void rejectAccess() {
 319         FadeTransition errorTrans = new FadeTransition(Duration.valueOf("500ms"), error);
 320         errorTrans.setFromValue(0.0);
 321         errorTrans.setToValue(1.0);
 322         errorTrans.setCycleCount(2);
 323         errorTrans.setAutoReverse(true);
 324         errorTrans.play();
 325     }
 326 }