modules/controls/src/main/java/javafx/scene/control/skin/ScrollPaneSkin.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization


   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 com.sun.javafx.scene.control.skin;
  27 


  28 import com.sun.javafx.scene.traversal.ParentTraversalEngine;
  29 import javafx.animation.Animation.Status;
  30 import javafx.animation.KeyFrame;
  31 import javafx.animation.KeyValue;
  32 import javafx.animation.Timeline;
  33 import javafx.beans.InvalidationListener;
  34 import javafx.beans.Observable;
  35 import javafx.beans.property.DoubleProperty;
  36 import javafx.beans.property.DoublePropertyBase;
  37 import javafx.beans.value.ChangeListener;
  38 import javafx.beans.value.ObservableValue;
  39 import javafx.event.EventDispatcher;
  40 import javafx.event.EventHandler;
  41 import javafx.geometry.BoundingBox;
  42 import javafx.geometry.Bounds;
  43 import javafx.geometry.Orientation;
  44 import javafx.scene.AccessibleAttribute;
  45 import javafx.scene.Cursor;
  46 import javafx.scene.Node;


  47 import javafx.scene.control.ScrollBar;
  48 import javafx.scene.control.ScrollPane;
  49 import javafx.scene.control.ScrollPane.ScrollBarPolicy;

  50 import javafx.scene.input.MouseEvent;
  51 import javafx.scene.input.ScrollEvent;
  52 import javafx.scene.input.TouchEvent;
  53 import javafx.scene.layout.StackPane;
  54 import javafx.scene.shape.Rectangle;
  55 import javafx.util.Duration;
  56 import com.sun.javafx.util.Utils;
  57 import com.sun.javafx.scene.control.behavior.ScrollPaneBehavior;
  58 import com.sun.javafx.scene.traversal.TraverseListener;
  59 import static com.sun.javafx.scene.control.skin.Utils.*;
  60 import javafx.geometry.Insets;
  61 
  62 public class ScrollPaneSkin extends BehaviorSkinBase<ScrollPane, ScrollPaneBehavior> implements TraverseListener {








  63     /***************************************************************************
  64      *                                                                         *
  65      * UI Subcomponents                                                        *
  66      *                                                                         *
  67      **************************************************************************/
  68 
  69     private static final double DEFAULT_PREF_SIZE = 100.0;
  70 
  71     private static final double DEFAULT_MIN_SIZE = 36.0;
  72 
  73     private static final double DEFAULT_SB_BREADTH = 12.0;
  74     private static final double DEFAULT_EMBEDDED_SB_BREADTH = 8.0;
  75 
  76     private static final double PAN_THRESHOLD = 0.5;
  77 








  78     // state from the control
  79 
  80     private Node scrollNode;

  81 
  82     private double nodeWidth;
  83     private double nodeHeight;
  84     private boolean nodeSizeInvalid = true;
  85 
  86     private double posX;
  87     private double posY;
  88 
  89     // working state
  90 
  91     private boolean hsbvis;
  92     private boolean vsbvis;
  93     private double hsbHeight;
  94     private double vsbWidth;
  95 
  96     // substructure
  97 
  98     private StackPane viewRect;
  99     private StackPane viewContent;
 100     private double contentWidth;
 101     private double contentHeight;
 102     private StackPane corner;
 103     protected ScrollBar hsb;
 104     protected ScrollBar vsb;
 105 
 106     double pressX;
 107     double pressY;
 108     double ohvalue;
 109     double ovvalue;
 110     private Cursor saveCursor =  null;
 111     private boolean dragDetected = false;
 112     private boolean touchDetected = false;
 113     private boolean mouseDown = false;
 114 
 115     Rectangle clipRect;
 116 












 117     /***************************************************************************
 118      *                                                                         *
 119      * Constructors                                                            *
 120      *                                                                         *
 121      **************************************************************************/
 122 
 123     public ScrollPaneSkin(final ScrollPane scrollpane) {
 124         super(scrollpane, new ScrollPaneBehavior(scrollpane));
 125         initialize();
 126         // Register listeners
 127         registerChangeListener(scrollpane.contentProperty(), "NODE");
 128         registerChangeListener(scrollpane.fitToWidthProperty(), "FIT_TO_WIDTH");
 129         registerChangeListener(scrollpane.fitToHeightProperty(), "FIT_TO_HEIGHT");
 130         registerChangeListener(scrollpane.hbarPolicyProperty(), "HBAR_POLICY");
 131         registerChangeListener(scrollpane.vbarPolicyProperty(), "VBAR_POLICY");
 132         registerChangeListener(scrollpane.hvalueProperty(), "HVALUE");
 133         registerChangeListener(scrollpane.hmaxProperty(), "HMAX");
 134         registerChangeListener(scrollpane.hminProperty(), "HMIN");
 135         registerChangeListener(scrollpane.vvalueProperty(), "VVALUE");
 136         registerChangeListener(scrollpane.vmaxProperty(), "VMAX");
 137         registerChangeListener(scrollpane.vminProperty(), "VMIN");
 138         registerChangeListener(scrollpane.prefViewportWidthProperty(), "VIEWPORT_SIZE_HINT");
 139         registerChangeListener(scrollpane.prefViewportHeightProperty(), "VIEWPORT_SIZE_HINT");
 140         registerChangeListener(scrollpane.minViewportWidthProperty(), "VIEWPORT_SIZE_HINT");
 141         registerChangeListener(scrollpane.minViewportHeightProperty(), "VIEWPORT_SIZE_HINT");
 142     }
 143 
 144     private final InvalidationListener nodeListener = new InvalidationListener() {
 145         @Override public void invalidated(Observable valueModel) {
 146             if (!nodeSizeInvalid) {
 147                 final Bounds scrollNodeBounds = scrollNode.getLayoutBounds();
 148                 final double scrollNodeWidth = scrollNodeBounds.getWidth();
 149                 final double scrollNodeHeight = scrollNodeBounds.getHeight();
 150 
 151                 /*
 152                 ** if the new size causes scrollbar visibility to change, then need to relayout
 153                 ** we also need to correct the thumb size when the scrollnode's size changes 
 154                 */
 155                 if (vsbvis != determineVerticalSBVisible() || hsbvis != determineHorizontalSBVisible() ||
 156                     (scrollNodeWidth != 0.0  && nodeWidth != scrollNodeWidth) ||
 157                     (scrollNodeHeight != 0.0 && nodeHeight != scrollNodeHeight)) {
 158                     getSkinnable().requestLayout();
 159                 } else {
 160                     /**
 161                      * we just need to update scrollbars based on new scrollNode size,
 162                      * but we don't do this while dragging, there's no need,
 163                      * and it jumps, as dragging updates the scrollbar too.


 211             double oldWidth = oldBounds.getWidth();
 212             double newWidth = newBounds.getWidth();
 213             if (oldWidth > 0 && oldWidth != newWidth) {
 214                 double oldPositionX = (snapPosition(snappedLeftInset() - posX / (hsb.getMax() - hsb.getMin()) * (oldWidth - contentWidth)));
 215                 double newPositionX = (snapPosition(snappedLeftInset() - posX / (hsb.getMax() - hsb.getMin()) * (newWidth - contentWidth)));
 216 
 217                 double newValueX = (oldPositionX/newPositionX)*hsb.getValue();
 218                 if (newValueX < 0.0) {
 219                     hsb.setValue(0.0);
 220                 }
 221                 else if (newValueX < 1.0) {
 222                     hsb.setValue(newValueX);
 223                 }
 224                 else if (newValueX > 1.0) {
 225                     hsb.setValue(1.0);
 226                 }
 227             }
 228         }
 229    };
 230 
























































































































































































































































































































































































 231     private void initialize() {
 232         // requestLayout calls below should not trigger requestLayout above ScrollPane
 233 //        setManaged(false);
 234 
 235         ScrollPane control = getSkinnable();
 236         scrollNode = control.getContent();
 237 
 238         ParentTraversalEngine traversalEngine = new ParentTraversalEngine(getSkinnable());
 239         traversalEngine.addTraverseListener(this);



 240         getSkinnable().setImpl_traversalEngine(traversalEngine);
 241 
 242         if (scrollNode != null) {
 243             scrollNode.layoutBoundsProperty().addListener(nodeListener);
 244             scrollNode.layoutBoundsProperty().addListener(boundsChangeListener);
 245         }
 246 
 247         viewRect = new StackPane() {
 248 
 249             @Override
 250             protected void layoutChildren() {
 251                 viewContent.resize(getWidth(), getHeight());
 252             }
 253 
 254         };
 255         // prevent requestLayout requests from within scrollNode from percolating up
 256         viewRect.setManaged(false);
 257         viewRect.setCache(true);
 258         viewRect.getStyleClass().add("viewport");
 259 
 260         clipRect = new Rectangle();
 261         viewRect.setClip(clipRect);
 262 
 263         hsb = new ScrollBar();
 264 
 265         vsb = new ScrollBar();
 266         vsb.setOrientation(Orientation.VERTICAL);
 267 
 268         EventHandler<MouseEvent> barHandler = ev -> {
 269             getSkinnable().requestFocus();
 270         };
 271 
 272         hsb.addEventFilter(MouseEvent.MOUSE_PRESSED, barHandler);
 273         vsb.addEventFilter(MouseEvent.MOUSE_PRESSED, barHandler);


 298                 }
 299                 if (scrollNode != null) {
 300                     scrollNode.relocate(0,0);
 301                 }
 302             }
 303         };
 304         viewRect.getChildren().add(viewContent);
 305 
 306         if (scrollNode != null) {
 307             viewContent.getChildren().add(scrollNode);
 308             viewRect.nodeOrientationProperty().bind(scrollNode.nodeOrientationProperty());
 309         }
 310 
 311         getChildren().clear();
 312         getChildren().addAll(viewRect, vsb, hsb, corner);
 313 
 314         /*
 315         ** listeners, and assorted housekeeping
 316         */
 317         InvalidationListener vsbListener = valueModel -> {
 318             if (!IS_TOUCH_SUPPORTED) {
 319                 posY = Utils.clamp(getSkinnable().getVmin(), vsb.getValue(), getSkinnable().getVmax());
 320             }
 321             else {
 322                 posY = vsb.getValue();
 323             }
 324             updatePosY();
 325         };
 326         vsb.valueProperty().addListener(vsbListener);
 327 
 328         InvalidationListener hsbListener = valueModel -> {
 329             if (!IS_TOUCH_SUPPORTED) {
 330                 posX = Utils.clamp(getSkinnable().getHmin(), hsb.getValue(), getSkinnable().getHmax());
 331             }
 332             else {
 333                 posX = hsb.getValue();
 334             }
 335             updatePosX();
 336         };
 337         hsb.valueProperty().addListener(hsbListener);
 338 
 339         viewRect.setOnMousePressed(e -> {
 340             mouseDown = true;
 341             if (IS_TOUCH_SUPPORTED) {
 342                 startSBReleasedAnimation();
 343             }
 344             pressX = e.getX();
 345             pressY = e.getY();
 346             ohvalue = hsb.getValue();
 347             ovvalue = vsb.getValue();
 348         });
 349 
 350 
 351         viewRect.setOnDragDetected(e -> {
 352              if (IS_TOUCH_SUPPORTED) {
 353                  startSBReleasedAnimation();
 354              }
 355             if (getSkinnable().isPannable()) {
 356               dragDetected = true;
 357               if (saveCursor == null) {
 358                   saveCursor = getSkinnable().getCursor();
 359                   if (saveCursor == null) {
 360                       saveCursor = Cursor.DEFAULT;
 361                   }
 362                   getSkinnable().setCursor(Cursor.MOVE);
 363                   getSkinnable().requestLayout();
 364               }
 365             }
 366         });
 367 
 368         viewRect.addEventFilter(MouseEvent.MOUSE_RELEASED, e -> {
 369              mouseDown = false;
 370              if (dragDetected == true) {
 371                  if (saveCursor != null) {
 372                      getSkinnable().setCursor(saveCursor);
 373                      saveCursor = null;
 374                      getSkinnable().requestLayout();
 375                  }
 376                  dragDetected = false;
 377              }
 378 
 379              /*
 380              ** if the contents need repositioning, and there's is no
 381              ** touch event in progress, then start the repositioning.
 382              */
 383              if ((posY > getSkinnable().getVmax() || posY < getSkinnable().getVmin() ||
 384                  posX > getSkinnable().getHmax() || posX < getSkinnable().getHmin()) && !touchDetected) {
 385                  startContentsToViewport();
 386              }
 387         });
 388         viewRect.setOnMouseDragged(e -> {
 389              if (IS_TOUCH_SUPPORTED) {
 390                  startSBReleasedAnimation();
 391              }
 392             /*
 393             ** for mobile-touch we allow drag, even if not pannagle
 394             */
 395             if (getSkinnable().isPannable() || IS_TOUCH_SUPPORTED) {
 396                 double deltaX = pressX - e.getX();
 397                 double deltaY = pressY - e.getY();
 398                 /*
 399                 ** we only drag if not all of the content is visible.
 400                 */
 401                 if (hsb.getVisibleAmount() > 0.0 && hsb.getVisibleAmount() < hsb.getMax()) {
 402                     if (Math.abs(deltaX) > PAN_THRESHOLD) {
 403                         if (isReverseNodeOrientation()) {
 404                             deltaX = -deltaX;
 405                         }
 406                         double newHVal = (ohvalue + deltaX / (nodeWidth - viewRect.getWidth()) * (hsb.getMax() - hsb.getMin()));
 407                         if (!IS_TOUCH_SUPPORTED) {
 408                             if (newHVal > hsb.getMax()) {
 409                                 newHVal = hsb.getMax();
 410                             }
 411                             else if (newHVal < hsb.getMin()) {
 412                                 newHVal = hsb.getMin();
 413                             }
 414                             hsb.setValue(newHVal);
 415                         }
 416                         else {
 417                             hsb.setValue(newHVal);
 418                         }
 419                     }
 420                 }
 421                 /*
 422                 ** we only drag if not all of the content is visible.
 423                 */
 424                 if (vsb.getVisibleAmount() > 0.0 && vsb.getVisibleAmount() < vsb.getMax()) {
 425                     if (Math.abs(deltaY) > PAN_THRESHOLD) {
 426                         double newVVal = (ovvalue + deltaY / (nodeHeight - viewRect.getHeight()) * (vsb.getMax() - vsb.getMin()));
 427                         if (!IS_TOUCH_SUPPORTED) {
 428                             if (newVVal > vsb.getMax()) {
 429                                 newVVal = vsb.getMax();
 430                             }
 431                             else if (newVVal < vsb.getMin()) {
 432                                 newVVal = vsb.getMin();
 433                             }
 434                             vsb.setValue(newVVal);
 435                         }
 436                         else {
 437                             vsb.setValue(newVVal);
 438                         }
 439                     }
 440                 }
 441             }
 442             /*
 443             ** we need to consume drag events, as we don't want
 444             ** the scrollpane itself to be dragged on every mouse click
 445             */
 446             e.consume();
 447         });


 472                     !((ScrollEvent)event).isDirect()) {
 473                 tail = tail.prepend(blockEventDispatcher);
 474                 tail = tail.prepend(oldVsbEventDispatcher);
 475                 return tail.dispatchEvent(event);
 476             }
 477             return oldVsbEventDispatcher.dispatchEvent(event, tail);
 478         });
 479 
 480         /*
 481          * listen for ScrollEvents over the whole of the ScrollPane
 482          * area, the above dispatcher having removed the ScrollBars
 483          * scroll event handling.
 484          *
 485          * Note that we use viewRect here, rather than setting the eventHandler
 486          * on the ScrollPane itself. This is for RT-31582, and effectively
 487          * allows for us to prioritise handling (and consuming) the event
 488          * internally, before it is made available to users listening to events
 489          * on the control. This is consistent with the VirtualFlow-based controls.
 490          */
 491         viewRect.addEventHandler(ScrollEvent.SCROLL, event -> {
 492             if (IS_TOUCH_SUPPORTED) {
 493                 startSBReleasedAnimation();
 494             }
 495             /*
 496             ** if we're completely visible then do nothing....
 497             ** we only consume an event that we've used.
 498             */
 499             if (vsb.getVisibleAmount() < vsb.getMax()) {
 500                 double vRange = getSkinnable().getVmax()-getSkinnable().getVmin();
 501                 double vPixelValue;
 502                 if (nodeHeight > 0.0) {
 503                     vPixelValue = vRange / nodeHeight;
 504                 }
 505                 else {
 506                     vPixelValue = 0.0;
 507                 }
 508                 double newValue = vsb.getValue()+(-event.getDeltaY())*vPixelValue;
 509                 if (!IS_TOUCH_SUPPORTED) {
 510                     if ((event.getDeltaY() > 0.0 && vsb.getValue() > vsb.getMin()) ||
 511                         (event.getDeltaY() < 0.0 && vsb.getValue() < vsb.getMax())) {
 512                         vsb.setValue(newValue);
 513                         event.consume();
 514                     }
 515                 }
 516                 else {
 517                     /*
 518                     ** if there is a repositioning in progress then we only
 519                     ** set the value for 'real' events
 520                     */
 521                     if (!(((ScrollEvent)event).isInertia()) || (((ScrollEvent)event).isInertia()) && (contentsToViewTimeline == null || contentsToViewTimeline.getStatus() == Status.STOPPED)) {
 522                         vsb.setValue(newValue);
 523                         if ((newValue > vsb.getMax() || newValue < vsb.getMin()) && (!mouseDown && !touchDetected)) {
 524                             startContentsToViewport();
 525                         }
 526                         event.consume();
 527                     }
 528                 }
 529             }
 530 
 531             if (hsb.getVisibleAmount() < hsb.getMax()) {
 532                 double hRange = getSkinnable().getHmax()-getSkinnable().getHmin();
 533                 double hPixelValue;
 534                 if (nodeWidth > 0.0) {
 535                     hPixelValue = hRange / nodeWidth;
 536                 }
 537                 else {
 538                     hPixelValue = 0.0;
 539                 }
 540 
 541                 double newValue = hsb.getValue()+(-event.getDeltaX())*hPixelValue;
 542                 if (!IS_TOUCH_SUPPORTED) {
 543                     if ((event.getDeltaX() > 0.0 && hsb.getValue() > hsb.getMin()) ||
 544                         (event.getDeltaX() < 0.0 && hsb.getValue() < hsb.getMax())) {
 545                         hsb.setValue(newValue);
 546                         event.consume();
 547                     }
 548                 }
 549                 else {
 550                     /*
 551                     ** if there is a repositioning in progress then we only
 552                     ** set the value for 'real' events
 553                     */
 554                     if (!(((ScrollEvent)event).isInertia()) || (((ScrollEvent)event).isInertia()) && (contentsToViewTimeline == null || contentsToViewTimeline.getStatus() == Status.STOPPED)) {
 555                         hsb.setValue(newValue);
 556 
 557                         if ((newValue > hsb.getMax() || newValue < hsb.getMin()) && (!mouseDown && !touchDetected)) {
 558                             startContentsToViewport();
 559                         }
 560                         event.consume();
 561                     }
 562                 }
 563             }
 564         });
 565 
 566         /*
 567         ** there are certain animations that need to know if the touch is
 568         ** happening.....
 569         */
 570         getSkinnable().addEventHandler(TouchEvent.TOUCH_PRESSED, e -> {
 571             touchDetected = true;
 572             startSBReleasedAnimation();
 573             e.consume();
 574         });
 575 
 576         getSkinnable().addEventHandler(TouchEvent.TOUCH_RELEASED, e -> {
 577             touchDetected = false;
 578             e.consume();
 579         });
 580 
 581         // ScrollPanes do not block all MouseEvents by default, unlike most other UI Controls.
 582         consumeMouseEvents(false);
 583 
 584         // update skin initial state to match control (see RT-35554)
 585         hsb.setValue(control.getHvalue());
 586         vsb.setValue(control.getVvalue());
 587     }
 588 
 589 
 590     @Override protected void handleControlPropertyChanged(String p) {
 591         super.handleControlPropertyChanged(p);
 592         if ("NODE".equals(p)) {
 593             if (scrollNode != getSkinnable().getContent()) {
 594                 if (scrollNode != null) {
 595                     scrollNode.layoutBoundsProperty().removeListener(nodeListener);
 596                     scrollNode.layoutBoundsProperty().removeListener(boundsChangeListener);
 597                     viewContent.getChildren().remove(scrollNode);
 598                 }
 599                 scrollNode = getSkinnable().getContent();
 600                 if (scrollNode != null) {
 601                     nodeWidth = snapSize(scrollNode.getLayoutBounds().getWidth());
 602                     nodeHeight = snapSize(scrollNode.getLayoutBounds().getHeight());
 603                     viewContent.getChildren().setAll(scrollNode);
 604                     scrollNode.layoutBoundsProperty().addListener(nodeListener);
 605                     scrollNode.layoutBoundsProperty().addListener(boundsChangeListener);
 606                 }
 607             }
 608             getSkinnable().requestLayout();
 609         } else if ("FIT_TO_WIDTH".equals(p) || "FIT_TO_HEIGHT".equals(p)) {
 610             getSkinnable().requestLayout();
 611             viewRect.requestLayout();
 612         } else if ("HBAR_POLICY".equals(p) || "VBAR_POLICY".equals(p)) {
 613             // change might affect pref size, so requestLayout on control
 614             getSkinnable().requestLayout();
 615         } else if ("HVALUE".equals(p)) {
 616             hsb.setValue(getSkinnable().getHvalue());
 617         } else if ("HMAX".equals(p)) {
 618             hsb.setMax(getSkinnable().getHmax());
 619         } else if ("HMIN".equals(p)) {
 620             hsb.setMin(getSkinnable().getHmin());
 621         } else if ("VVALUE".equals(p)) {
 622             vsb.setValue(getSkinnable().getVvalue());
 623         } else if ("VMAX".equals(p)) {
 624             vsb.setMax(getSkinnable().getVmax());
 625         } else if ("VMIN".equals(p)) {
 626             vsb.setMin(getSkinnable().getVmin());
 627         } else if ("VIEWPORT_SIZE_HINT".equals(p)) {
 628             // change affects pref size, so requestLayout on control
 629             getSkinnable().requestLayout();
 630         }
 631     }
 632     
 633     void scrollBoundsIntoView(Bounds b) {
 634         double dx = 0.0;
 635         double dy = 0.0;
 636         if (b.getMaxX() > contentWidth) {
 637             dx = b.getMinX() - snappedLeftInset();
 638         }
 639         if (b.getMinX() < snappedLeftInset()) {
 640             dx = b.getMaxX() - contentWidth - snappedLeftInset();
 641         }
 642         if (b.getMaxY() > snappedTopInset() + contentHeight) {
 643             dy = b.getMinY() - snappedTopInset();
 644         }
 645         if (b.getMinY() < snappedTopInset()) {
 646             dy = b.getMaxY() - contentHeight - snappedTopInset();
 647         }
 648         // We want to move contentPanel's layoutX,Y by (dx,dy).
 649         // But to do this we have to set the scrollbars' values appropriately.
 650 
 651         if (dx != 0) {
 652             double sdx = dx * (hsb.getMax() - hsb.getMin()) / (nodeWidth - contentWidth);
 653             // Adjust back for some amount so that the Node border is not too close to view border
 654             sdx += -1 * Math.signum(sdx) * hsb.getUnitIncrement() / 5; // This accounts to 2% of view width
 655             hsb.setValue(hsb.getValue() + sdx);
 656             getSkinnable().requestLayout();
 657         }
 658         if (dy != 0) {
 659             double sdy = dy * (vsb.getMax() - vsb.getMin()) / (nodeHeight - contentHeight);
 660             // Adjust back for some amount so that the Node border is not too close to view border
 661             sdy += -1 * Math.signum(sdy) * vsb.getUnitIncrement() / 5; // This accounts to 2% of view height
 662             vsb.setValue(vsb.getValue() + sdy);
 663             getSkinnable().requestLayout();
 664         }
 665 
 666     }
 667 
 668     /*
 669     ** auto-scroll so node is within (0,0),(contentWidth,contentHeight)
 670     */
 671     @Override public void onTraverse(Node n, Bounds b) {
 672         scrollBoundsIntoView(b);
 673     }
 674 
 675     public void hsbIncrement() {
 676         if (hsb != null) hsb.increment();
 677     }
 678     public void hsbDecrement() {
 679         if (hsb != null) hsb.decrement();
 680     }
 681 
 682     // TODO: add page increment and decrement
 683     public void hsbPageIncrement() {
 684         if (hsb != null) hsb.increment();
 685     }
 686     // TODO: add page increment and decrement
 687     public void hsbPageDecrement() {
 688         if (hsb != null) hsb.decrement();
 689     }
 690 
 691     public void vsbIncrement() {
 692         if (vsb != null) vsb.increment();
 693     }
 694     public void vsbDecrement() {
 695         if (vsb != null) vsb.decrement();
 696     }
 697 
 698     // TODO: add page increment and decrement
 699     public void vsbPageIncrement() {
 700         if (vsb != null) vsb.increment();
 701     }
 702     // TODO: add page increment and decrement
 703     public void vsbPageDecrement() {
 704         if (vsb != null) vsb.decrement();
 705     }
 706 
 707     /***************************************************************************
 708      *                                                                         *
 709      * Layout                                                                  *
 710      *                                                                         *
 711      **************************************************************************/
 712 
 713     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 714         final ScrollPane sp = getSkinnable();
 715 
 716         double vsbWidth = computeVsbSizeHint(sp);
 717         double minWidth = vsbWidth + snappedLeftInset() + snappedRightInset();
 718 
 719         if (sp.getPrefViewportWidth() > 0) {
 720             return (sp.getPrefViewportWidth() + minWidth);
 721         }
 722         else if (sp.getContent() != null) {
 723             return (sp.getContent().prefWidth(height) + minWidth);
 724         }
 725         else {
 726             return Math.max(minWidth, DEFAULT_PREF_SIZE);
 727         }
 728     }
 729 
 730     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 731         final ScrollPane sp = getSkinnable();
 732 
 733         double hsbHeight = computeHsbSizeHint(sp);
 734         double minHeight = hsbHeight + snappedTopInset() + snappedBottomInset();


 735 
 736         if (sp.getPrefViewportHeight() > 0) {
 737             return (sp.getPrefViewportHeight() + minHeight);



 738         }
 739         else if (sp.getContent() != null) {
 740             return (sp.getContent().prefHeight(width) + minHeight);
 741         }
 742         else {
 743             return Math.max(minHeight, DEFAULT_PREF_SIZE);
 744         }


 745     }


 746 
 747     @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 748         final ScrollPane sp = getSkinnable();
 749 
 750         double vsbWidth = computeVsbSizeHint(sp);
 751         double minWidth = vsbWidth + snappedLeftInset() + snappedRightInset();
 752 
 753         if (sp.getMinViewportWidth() > 0) {
 754             return (sp.getMinViewportWidth() + minWidth);
 755         } else {
 756             double w = corner.minWidth(-1);
 757             return (w > 0) ? (3 * w) : (DEFAULT_MIN_SIZE);
 758         }
 759 





 760     }
 761 
 762     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 763         final ScrollPane sp = getSkinnable();
 764 
 765         double hsbHeight = computeHsbSizeHint(sp);
 766         double minHeight = hsbHeight + snappedTopInset() + snappedBottomInset();
 767 
 768         if (sp.getMinViewportHeight() > 0) {
 769             return (sp.getMinViewportHeight() + minHeight);
 770         } else {
 771             double h = corner.minHeight(-1);
 772             return (h > 0) ? (3 * h) : (DEFAULT_MIN_SIZE);
 773         }
 774     }
 775 
 776     /**
 777      * Computes the size that should be reserved for horizontal scrollbar in size hints (min/pref height)
 778      */
 779     private double computeHsbSizeHint(ScrollPane sp) {
 780         return ((sp.getHbarPolicy() == ScrollBarPolicy.ALWAYS) ||
 781                 (sp.getHbarPolicy() == ScrollBarPolicy.AS_NEEDED && (sp.getPrefViewportHeight() > 0 || sp.getMinViewportHeight() > 0)))
 782                 ? hsb.prefHeight(ScrollBar.USE_COMPUTED_SIZE)
 783                 : 0;
 784     }
 785 
 786     /**
 787      * Computes the size that should be reserved for vertical scrollbar in size hints (min/pref width)
 788      */
 789     private double computeVsbSizeHint(ScrollPane sp) {
 790         return ((sp.getVbarPolicy() == ScrollBarPolicy.ALWAYS) ||
 791                 (sp.getVbarPolicy() == ScrollBarPolicy.AS_NEEDED && (sp.getPrefViewportWidth() > 0
 792                         || sp.getMinViewportWidth() > 0)))
 793                 ? vsb.prefWidth(ScrollBar.USE_COMPUTED_SIZE)
 794                 : 0;
 795     }
 796 
 797     @Override protected void layoutChildren(final double x, final double y,
 798             final double w, final double h) {
 799         final ScrollPane control = getSkinnable();
 800         final Insets padding = control.getPadding();
 801         final double rightPadding = snapSize(padding.getRight());
 802         final double leftPadding = snapSize(padding.getLeft());
 803         final double topPadding = snapSize(padding.getTop());
 804         final double bottomPadding = snapSize(padding.getBottom());
 805 
 806         vsb.setMin(control.getVmin());
 807         vsb.setMax(control.getVmax());
 808 
 809         //should only do this on css setup
 810         hsb.setMin(control.getHmin());
 811         hsb.setMax(control.getHmax());
 812 
 813         contentWidth = w;
 814         contentHeight = h;
 815 
 816         /*
 817         ** we want the scrollbars to go right to the border
 818         */
 819         double hsbWidth = 0;
 820         double vsbHeight = 0;
 821 
 822         computeScrollNodeSize(contentWidth, contentHeight);
 823         computeScrollBarSize();
 824 
 825         for (int i = 0; i < 2; ++i) {
 826             vsbvis = determineVerticalSBVisible();
 827             hsbvis = determineHorizontalSBVisible();
 828 
 829             if (vsbvis && !IS_TOUCH_SUPPORTED) {
 830                 contentWidth = w - vsbWidth;
 831             }
 832             hsbWidth = w + leftPadding + rightPadding - (vsbvis ? vsbWidth : 0);
 833             if (hsbvis && !IS_TOUCH_SUPPORTED) {
 834                 contentHeight = h - hsbHeight;
 835             }
 836             vsbHeight = h + topPadding + bottomPadding - (hsbvis ? hsbHeight : 0);
 837         }
 838 
 839 
 840         if (scrollNode != null && scrollNode.isResizable()) {
 841             // maybe adjust size now that scrollbars may take up space
 842             if (vsbvis && hsbvis) {
 843                 // adjust just once to accommodate
 844                 computeScrollNodeSize(contentWidth, contentHeight);
 845 
 846             } else if (hsbvis && !vsbvis) {
 847                 computeScrollNodeSize(contentWidth, contentHeight);
 848                 vsbvis = determineVerticalSBVisible();
 849                 if (vsbvis) {
 850                     // now both are visible
 851                     contentWidth -= vsbWidth;
 852                     hsbWidth -= vsbWidth;
 853                     computeScrollNodeSize(contentWidth, contentHeight);
 854                 }
 855             } else if (vsbvis && !hsbvis) {
 856                 computeScrollNodeSize(contentWidth, contentHeight);
 857                 hsbvis = determineHorizontalSBVisible();
 858                 if (hsbvis) {
 859                     // now both are visible
 860                     contentHeight -= hsbHeight;
 861                     vsbHeight -= hsbHeight;
 862                     computeScrollNodeSize(contentWidth, contentHeight);
 863                 }
 864             }
 865         }
 866 
 867         // figure out the content area that is to be filled
 868         double cx = snappedLeftInset() - leftPadding;
 869         double cy = snappedTopInset() - topPadding;
 870 
 871         vsb.setVisible(vsbvis);
 872         if (vsbvis) {
 873             /*
 874             ** round up position of ScrollBar, round down it's size.
 875             **
 876             ** Positioning the ScrollBar
 877             **  The Padding should go between the content and the edge,
 878             **  otherwise changes in padding move the ScrollBar, and could
 879             **  in extreme cases size the ScrollBar to become unusable.
 880             **  The -1, +1 plus one bit :
 881             **   If padding in => 1 then we allow one pixel to appear as the
 882             **   outside border of the Scrollbar, and the rest on the inside.
 883             **   If padding is < 1 then we just stick to the edge.
 884             */
 885             vsb.resizeRelocate(snappedLeftInset() + w - vsbWidth + (rightPadding < 1 ? 0 : rightPadding - 1) ,
 886                     cy, vsbWidth, vsbHeight);
 887         }
 888         updateVerticalSB();
 889 
 890         hsb.setVisible(hsbvis);
 891         if (hsbvis) {
 892             /*
 893             ** round up position of ScrollBar, round down it's size.
 894             **
 895             ** Positioning the ScrollBar
 896             **  The Padding should go between the content and the edge,
 897             **  otherwise changes in padding move the ScrollBar, and could
 898             **  in extreme cases size the ScrollBar to become unusable.
 899             **  The -1, +1 plus one bit : 
 900             **   If padding in => 1 then we allow one pixel to appear as the
 901             **   outside border of the Scrollbar, and the rest on the inside.
 902             **   If padding is < 1 then we just stick to the edge.
 903             */
 904             hsb.resizeRelocate(cx, snappedTopInset() + h - hsbHeight + (bottomPadding < 1 ? 0 : bottomPadding - 1),
 905                     hsbWidth, hsbHeight);
 906         }
 907         updateHorizontalSB();
 908 
 909         viewRect.resizeRelocate(snappedLeftInset(), snappedTopInset(), snapSize(contentWidth), snapSize(contentHeight));
 910         resetClip();
 911 
 912         if (vsbvis && hsbvis) {
 913             corner.setVisible(true);
 914             double cornerWidth = vsbWidth;
 915             double cornerHeight = hsbHeight;
 916             corner.resizeRelocate(snapPosition(vsb.getLayoutX()), snapPosition(hsb.getLayoutY()), snapSize(cornerWidth), snapSize(cornerHeight));
 917         } else {
 918             corner.setVisible(false);
 919         }
 920         control.setViewportBounds(new BoundingBox(snapPosition(viewContent.getLayoutX()), snapPosition(viewContent.getLayoutY()), snapSize(contentWidth), snapSize(contentHeight)));
 921     }
 922     
 923     private void computeScrollNodeSize(double contentWidth, double contentHeight) {
 924         if (scrollNode != null) {
 925             if (scrollNode.isResizable()) {
 926                 ScrollPane control = getSkinnable();
 927                 Orientation bias = scrollNode.getContentBias();
 928                 if (bias == null) {
 929                     nodeWidth = snapSize(boundedSize(control.isFitToWidth()? contentWidth : scrollNode.prefWidth(-1),
 930                                                          scrollNode.minWidth(-1),scrollNode.maxWidth(-1)));
 931                     nodeHeight = snapSize(boundedSize(control.isFitToHeight()? contentHeight : scrollNode.prefHeight(-1),
 932                                                           scrollNode.minHeight(-1), scrollNode.maxHeight(-1)));
 933 
 934                 } else if (bias == Orientation.HORIZONTAL) {
 935                     nodeWidth = snapSize(boundedSize(control.isFitToWidth()? contentWidth : scrollNode.prefWidth(-1),
 936                                                          scrollNode.minWidth(-1),scrollNode.maxWidth(-1)));
 937                     nodeHeight = snapSize(boundedSize(control.isFitToHeight()? contentHeight : scrollNode.prefHeight(nodeWidth),
 938                                                           scrollNode.minHeight(nodeWidth),scrollNode.maxHeight(nodeWidth)));
 939 
 940                 } else { // bias == VERTICAL
 941                     nodeHeight = snapSize(boundedSize(control.isFitToHeight()? contentHeight : scrollNode.prefHeight(-1),
 942                                                           scrollNode.minHeight(-1), scrollNode.maxHeight(-1)));


 944                                                          scrollNode.minWidth(nodeHeight),scrollNode.maxWidth(nodeHeight)));
 945                 }
 946 
 947             } else {
 948                 nodeWidth = snapSize(scrollNode.getLayoutBounds().getWidth());
 949                 nodeHeight = snapSize(scrollNode.getLayoutBounds().getHeight());
 950             }
 951             nodeSizeInvalid = false;
 952         }
 953     }
 954 
 955     private boolean isReverseNodeOrientation() {
 956         return (scrollNode != null &&
 957                 getSkinnable().getEffectiveNodeOrientation() !=
 958                             scrollNode.getEffectiveNodeOrientation());
 959     }
 960 
 961     private boolean determineHorizontalSBVisible() {
 962         final ScrollPane sp = getSkinnable();
 963 
 964         if (IS_TOUCH_SUPPORTED) {
 965             return (tempVisibility && (nodeWidth > contentWidth));
 966         }
 967         else {
 968             // RT-17395: ScrollBarPolicy might be null. If so, treat it as "AS_NEEDED", which is the default
 969             ScrollBarPolicy hbarPolicy = sp.getHbarPolicy();
 970             return (ScrollBarPolicy.NEVER == hbarPolicy) ? false :
 971                    ((ScrollBarPolicy.ALWAYS == hbarPolicy) ? true :
 972                    ((sp.isFitToWidth() && scrollNode != null ? scrollNode.isResizable() : false) ?
 973                    (nodeWidth > contentWidth && scrollNode.minWidth(-1) > contentWidth) : (nodeWidth > contentWidth)));
 974         }
 975     }
 976 
 977     private boolean determineVerticalSBVisible() {
 978         final ScrollPane sp = getSkinnable();
 979 
 980         if (IS_TOUCH_SUPPORTED) {
 981             return (tempVisibility && (nodeHeight > contentHeight));
 982         }
 983         else {
 984             // RT-17395: ScrollBarPolicy might be null. If so, treat it as "AS_NEEDED", which is the default
 985             ScrollBarPolicy vbarPolicy = sp.getVbarPolicy();
 986             return (ScrollBarPolicy.NEVER == vbarPolicy) ? false :
 987                    ((ScrollBarPolicy.ALWAYS == vbarPolicy) ? true :
 988                    ((sp.isFitToHeight() && scrollNode != null ? scrollNode.isResizable() : false) ?
 989                    (nodeHeight > contentHeight && scrollNode.minHeight(-1) > contentHeight) : (nodeHeight > contentHeight)));
 990         }
 991     }
 992 
 993     private void computeScrollBarSize() {
 994         vsbWidth = snapSize(vsb.prefWidth(-1));
 995         if (vsbWidth == 0) {
 996             //            println("*** WARNING ScrollPaneSkin: can't get scroll bar width, using {DEFAULT_SB_BREADTH}");
 997             if (IS_TOUCH_SUPPORTED) {
 998                 vsbWidth = DEFAULT_EMBEDDED_SB_BREADTH;
 999             }
1000             else {
1001                 vsbWidth = DEFAULT_SB_BREADTH;
1002             }
1003         }
1004         hsbHeight = snapSize(hsb.prefHeight(-1));
1005         if (hsbHeight == 0) {
1006             //            println("*** WARNING ScrollPaneSkin: can't get scroll bar height, using {DEFAULT_SB_BREADTH}");
1007             if (IS_TOUCH_SUPPORTED) {
1008                 hsbHeight = DEFAULT_EMBEDDED_SB_BREADTH;
1009             }
1010             else {
1011                 hsbHeight = DEFAULT_SB_BREADTH;
1012             }
1013         }
1014     }
1015 
1016     private void updateHorizontalSB() {
1017         double contentRatio = nodeWidth * (hsb.getMax() - hsb.getMin());
1018         if (contentRatio > 0.0) {
1019             hsb.setVisibleAmount(contentWidth / contentRatio);
1020             hsb.setBlockIncrement(0.9 * hsb.getVisibleAmount());
1021             hsb.setUnitIncrement(0.1 * hsb.getVisibleAmount());
1022         }
1023         else {
1024             hsb.setVisibleAmount(0.0);
1025             hsb.setBlockIncrement(0.0);
1026             hsb.setUnitIncrement(0.0);
1027         }


1066         double x = isReverseNodeOrientation() ? (hsb.getMax() - (posX - hsb.getMin())) : posX;
1067         double minX = Math.min((- x / (hsb.getMax() - hsb.getMin()) * (nodeWidth - contentWidth)), 0);
1068         viewContent.setLayoutX(snapPosition(minX));
1069         if (!sp.hvalueProperty().isBound()) sp.setHvalue(Utils.clamp(sp.getHmin(), posX, sp.getHmax()));
1070         return posX;
1071     }
1072 
1073     private double updatePosY() {
1074         final ScrollPane sp = getSkinnable();
1075         double minY = Math.min((- posY / (vsb.getMax() - vsb.getMin()) * (nodeHeight - contentHeight)), 0);
1076         viewContent.setLayoutY(snapPosition(minY));
1077         if (!sp.vvalueProperty().isBound()) sp.setVvalue(Utils.clamp(sp.getVmin(), posY, sp.getVmax()));
1078         return posY;
1079     }
1080 
1081     private void resetClip() {
1082         clipRect.setWidth(snapSize(contentWidth));
1083         clipRect.setHeight(snapSize(contentHeight));
1084     }
1085 
1086     Timeline sbTouchTimeline;
1087     KeyFrame sbTouchKF1;
1088     KeyFrame sbTouchKF2;
1089     Timeline contentsToViewTimeline;
1090     KeyFrame contentsToViewKF1;
1091     KeyFrame contentsToViewKF2;
1092     KeyFrame contentsToViewKF3;
1093 
1094     private boolean tempVisibility;
1095 
1096 
1097     protected void startSBReleasedAnimation() {
1098         if (sbTouchTimeline == null) {
1099             /*
1100             ** timeline to leave the scrollbars visible for a short
1101             ** while after a scroll/drag
1102             */
1103             sbTouchTimeline = new Timeline();
1104             sbTouchKF1 = new KeyFrame(Duration.millis(0), event -> {
1105                 tempVisibility = true;
1106                 if (touchDetected == true || mouseDown == true) {
1107                     sbTouchTimeline.playFromStart();
1108                 }
1109             });
1110 
1111             sbTouchKF2 = new KeyFrame(Duration.millis(1000), event -> {
1112                 tempVisibility = false;
1113                 getSkinnable().requestLayout();
1114             });
1115             sbTouchTimeline.getKeyFrames().addAll(sbTouchKF1, sbTouchKF2);
1116         }
1117         sbTouchTimeline.playFromStart();
1118     }
1119 
1120 
1121 
1122     protected void startContentsToViewport() {
1123         double newPosX = posX;
1124         double newPosY = posY;
1125 
1126         setContentPosX(posX);
1127         setContentPosY(posY);
1128 
1129         if (posY > getSkinnable().getVmax()) {
1130             newPosY = getSkinnable().getVmax();
1131         }
1132         else if (posY < getSkinnable().getVmin()) {
1133             newPosY = getSkinnable().getVmin();
1134         }             
1135         
1136 
1137         if (posX > getSkinnable().getHmax()) {
1138             newPosX = getSkinnable().getHmax();
1139         }
1140         else if (posX < getSkinnable().getHmin()) {
1141             newPosX = getSkinnable().getHmin();
1142         }
1143 
1144         if (!IS_TOUCH_SUPPORTED) {
1145             startSBReleasedAnimation();
1146         }
1147 
1148         /*
1149         ** timeline to return the contents of the scrollpane to the viewport
1150         */
1151         if (contentsToViewTimeline != null) {
1152             contentsToViewTimeline.stop();
1153         }
1154         contentsToViewTimeline = new Timeline();
1155         /*
1156         ** short pause before animation starts
1157         */
1158         contentsToViewKF1 = new KeyFrame(Duration.millis(50));
1159         /*
1160         ** reposition
1161         */
1162         contentsToViewKF2 = new KeyFrame(Duration.millis(150), event -> {
1163             getSkinnable().requestLayout();
1164         },
1165             new KeyValue(contentPosX, newPosX),
1166             new KeyValue(contentPosY, newPosY)
1167             );
1168         /*
1169         ** block out 'aftershocks', but real events will
1170         ** still reactivate
1171         */
1172         contentsToViewKF3 = new KeyFrame(Duration.millis(1500));
1173         contentsToViewTimeline.getKeyFrames().addAll(contentsToViewKF1, contentsToViewKF2, contentsToViewKF3);
1174         contentsToViewTimeline.playFromStart();
1175     }
1176 
1177 
1178     private DoubleProperty contentPosX;
1179     private void setContentPosX(double value) { contentPosXProperty().set(value); }
1180     private double getContentPosX() { return contentPosX == null ? 0.0 : contentPosX.get(); }
1181     private DoubleProperty contentPosXProperty() {
1182         if (contentPosX == null) {
1183             contentPosX = new DoublePropertyBase() {
1184                 @Override protected void invalidated() {
1185                     hsb.setValue(getContentPosX());
1186                     getSkinnable().requestLayout();
1187                 }
1188 
1189                 @Override
1190                 public Object getBean() {
1191                     return ScrollPaneSkin.this;
1192                 }
1193 
1194                 @Override
1195                 public String getName() {
1196                     return "contentPosX";
1197                 }
1198             };
1199         }
1200         return contentPosX;
1201     }
1202 
1203     private DoubleProperty contentPosY;
1204     private void setContentPosY(double value) { contentPosYProperty().set(value); }
1205     private double getContentPosY() { return contentPosY == null ? 0.0 : contentPosY.get(); }
1206     private DoubleProperty contentPosYProperty() {
1207         if (contentPosY == null) {
1208             contentPosY = new DoublePropertyBase() {
1209                 @Override protected void invalidated() {
1210                     vsb.setValue(getContentPosY());
1211                     getSkinnable().requestLayout();
1212                 }
1213 
1214                 @Override
1215                 public Object getBean() {
1216                     return ScrollPaneSkin.this;
1217                 }
1218 
1219                 @Override
1220                 public String getName() {
1221                     return "contentPosY";
1222                 }
1223             };
1224         }
1225         return contentPosY;
1226     }
1227 
1228     @Override
1229     protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
1230         switch (attribute) {
1231             case VERTICAL_SCROLLBAR: return vsb;
1232             case HORIZONTAL_SCROLLBAR: return hsb;
1233             default: return super.queryAccessibleAttribute(attribute, parameters);
1234         }
1235     }
1236 }


   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.control.skin;
  27 
  28 import com.sun.javafx.scene.control.Properties;
  29 import com.sun.javafx.scene.control.behavior.BehaviorBase;
  30 import com.sun.javafx.scene.traversal.ParentTraversalEngine;
  31 import javafx.animation.Animation.Status;
  32 import javafx.animation.KeyFrame;
  33 import javafx.animation.KeyValue;
  34 import javafx.animation.Timeline;
  35 import javafx.beans.InvalidationListener;
  36 import javafx.beans.Observable;
  37 import javafx.beans.property.DoubleProperty;
  38 import javafx.beans.property.DoublePropertyBase;
  39 import javafx.beans.value.ChangeListener;
  40 import javafx.beans.value.ObservableValue;
  41 import javafx.event.EventDispatcher;
  42 import javafx.event.EventHandler;
  43 import javafx.geometry.BoundingBox;
  44 import javafx.geometry.Bounds;
  45 import javafx.geometry.Orientation;
  46 import javafx.scene.AccessibleAttribute;
  47 import javafx.scene.Cursor;
  48 import javafx.scene.Node;
  49 import javafx.scene.control.Button;
  50 import javafx.scene.control.Control;
  51 import javafx.scene.control.ScrollBar;
  52 import javafx.scene.control.ScrollPane;
  53 import javafx.scene.control.ScrollPane.ScrollBarPolicy;
  54 import javafx.scene.control.SkinBase;
  55 import javafx.scene.input.MouseEvent;
  56 import javafx.scene.input.ScrollEvent;
  57 import javafx.scene.input.TouchEvent;
  58 import javafx.scene.layout.StackPane;
  59 import javafx.scene.shape.Rectangle;
  60 import javafx.util.Duration;
  61 import com.sun.javafx.util.Utils;
  62 import com.sun.javafx.scene.control.behavior.ScrollPaneBehavior;

  63 import static com.sun.javafx.scene.control.skin.Utils.*;
  64 import javafx.geometry.Insets;
  65 
  66 import java.util.function.Consumer;
  67 
  68 /**
  69  * Default skin implementation for the {@link ScrollPane} control.
  70  *
  71  * @see ScrollPane
  72  * @since 9
  73  */
  74 public class ScrollPaneSkin extends SkinBase<ScrollPane> {
  75     /***************************************************************************
  76      *                                                                         *
  77      * Static fields                                                           *
  78      *                                                                         *
  79      **************************************************************************/
  80 
  81     private static final double DEFAULT_PREF_SIZE = 100.0;
  82 
  83     private static final double DEFAULT_MIN_SIZE = 36.0;
  84 
  85     private static final double DEFAULT_SB_BREADTH = 12.0;
  86     private static final double DEFAULT_EMBEDDED_SB_BREADTH = 8.0;
  87 
  88     private static final double PAN_THRESHOLD = 0.5;
  89 
  90 
  91 
  92     /***************************************************************************
  93      *                                                                         *
  94      * Private fields                                                          *
  95      *                                                                         *
  96      **************************************************************************/
  97 
  98     // state from the control
  99 
 100     private Node scrollNode;
 101     private final BehaviorBase<ScrollPane> behavior;
 102 
 103     private double nodeWidth;
 104     private double nodeHeight;
 105     private boolean nodeSizeInvalid = true;
 106 
 107     private double posX;
 108     private double posY;
 109 
 110     // working state
 111 
 112     private boolean hsbvis;
 113     private boolean vsbvis;
 114     private double hsbHeight;
 115     private double vsbWidth;
 116 
 117     // substructure
 118 
 119     private StackPane viewRect;
 120     private StackPane viewContent;
 121     private double contentWidth;
 122     private double contentHeight;
 123     private StackPane corner;
 124     ScrollBar hsb;
 125     ScrollBar vsb;
 126 
 127     double pressX;
 128     double pressY;
 129     double ohvalue;
 130     double ovvalue;
 131     private Cursor saveCursor =  null;
 132     private boolean dragDetected = false;
 133     private boolean touchDetected = false;
 134     private boolean mouseDown = false;
 135 
 136     Rectangle clipRect;
 137 
 138     Timeline sbTouchTimeline;
 139     KeyFrame sbTouchKF1;
 140     KeyFrame sbTouchKF2;
 141     Timeline contentsToViewTimeline;
 142     KeyFrame contentsToViewKF1;
 143     KeyFrame contentsToViewKF2;
 144     KeyFrame contentsToViewKF3;
 145 
 146     private boolean tempVisibility;
 147 
 148 
 149 
 150     /***************************************************************************
 151      *                                                                         *
 152      * Listeners                                                               *
 153      *                                                                         *
 154      **************************************************************************/
 155 





















 156     private final InvalidationListener nodeListener = new InvalidationListener() {
 157         @Override public void invalidated(Observable valueModel) {
 158             if (!nodeSizeInvalid) {
 159                 final Bounds scrollNodeBounds = scrollNode.getLayoutBounds();
 160                 final double scrollNodeWidth = scrollNodeBounds.getWidth();
 161                 final double scrollNodeHeight = scrollNodeBounds.getHeight();
 162 
 163                 /*
 164                 ** if the new size causes scrollbar visibility to change, then need to relayout
 165                 ** we also need to correct the thumb size when the scrollnode's size changes
 166                 */
 167                 if (vsbvis != determineVerticalSBVisible() || hsbvis != determineHorizontalSBVisible() ||
 168                         (scrollNodeWidth != 0.0  && nodeWidth != scrollNodeWidth) ||
 169                         (scrollNodeHeight != 0.0 && nodeHeight != scrollNodeHeight)) {
 170                     getSkinnable().requestLayout();
 171                 } else {
 172                     /**
 173                      * we just need to update scrollbars based on new scrollNode size,
 174                      * but we don't do this while dragging, there's no need,
 175                      * and it jumps, as dragging updates the scrollbar too.


 223             double oldWidth = oldBounds.getWidth();
 224             double newWidth = newBounds.getWidth();
 225             if (oldWidth > 0 && oldWidth != newWidth) {
 226                 double oldPositionX = (snapPosition(snappedLeftInset() - posX / (hsb.getMax() - hsb.getMin()) * (oldWidth - contentWidth)));
 227                 double newPositionX = (snapPosition(snappedLeftInset() - posX / (hsb.getMax() - hsb.getMin()) * (newWidth - contentWidth)));
 228 
 229                 double newValueX = (oldPositionX/newPositionX)*hsb.getValue();
 230                 if (newValueX < 0.0) {
 231                     hsb.setValue(0.0);
 232                 }
 233                 else if (newValueX < 1.0) {
 234                     hsb.setValue(newValueX);
 235                 }
 236                 else if (newValueX > 1.0) {
 237                     hsb.setValue(1.0);
 238                 }
 239             }
 240         }
 241     };
 242 
 243 
 244 
 245     /***************************************************************************
 246      *                                                                         *
 247      * Constructors                                                            *
 248      *                                                                         *
 249      **************************************************************************/
 250 
 251     /**
 252      * Creates a new ScrollPaneSkin instance, installing the necessary child
 253      * nodes into the Control {@link Control#getChildren() children} list, as
 254      * well as the necessary input mappings for handling key, mouse, etc events.
 255      *
 256      * @param control The control that this skin should be installed onto.
 257      */
 258     public ScrollPaneSkin(final ScrollPane control) {
 259         super(control);
 260 
 261         // install default input map for the ScrollPane control
 262         behavior = new ScrollPaneBehavior(control);
 263 //        control.setInputMap(behavior.getInputMap());
 264 
 265         initialize();
 266 
 267         // Register listeners
 268         Consumer<ObservableValue<?>> viewportSizeHintConsumer = e -> {
 269             // change affects pref size, so requestLayout on control
 270             getSkinnable().requestLayout();
 271         };
 272         registerChangeListener(control.contentProperty(), e -> {
 273             if (scrollNode != getSkinnable().getContent()) {
 274                 if (scrollNode != null) {
 275                     scrollNode.layoutBoundsProperty().removeListener(nodeListener);
 276                     scrollNode.layoutBoundsProperty().removeListener(boundsChangeListener);
 277                     viewContent.getChildren().remove(scrollNode);
 278                 }
 279                 scrollNode = getSkinnable().getContent();
 280                 if (scrollNode != null) {
 281                     nodeWidth = snapSize(scrollNode.getLayoutBounds().getWidth());
 282                     nodeHeight = snapSize(scrollNode.getLayoutBounds().getHeight());
 283                     viewContent.getChildren().setAll(scrollNode);
 284                     scrollNode.layoutBoundsProperty().addListener(nodeListener);
 285                     scrollNode.layoutBoundsProperty().addListener(boundsChangeListener);
 286                 }
 287             }
 288             getSkinnable().requestLayout();
 289         });
 290         registerChangeListener(control.fitToWidthProperty(), e -> {
 291             getSkinnable().requestLayout();
 292             viewRect.requestLayout();
 293         });
 294         registerChangeListener(control.fitToHeightProperty(), e -> {
 295             getSkinnable().requestLayout();
 296             viewRect.requestLayout();
 297         });
 298         registerChangeListener(control.hbarPolicyProperty(), e -> {
 299             // change might affect pref size, so requestLayout on control
 300             getSkinnable().requestLayout();
 301         });
 302         registerChangeListener(control.vbarPolicyProperty(), e -> {
 303             // change might affect pref size, so requestLayout on control
 304             getSkinnable().requestLayout();
 305         });
 306         registerChangeListener(control.hvalueProperty(), e -> hsb.setValue(getSkinnable().getHvalue()));
 307         registerChangeListener(control.hmaxProperty(), e -> hsb.setMax(getSkinnable().getHmax()));
 308         registerChangeListener(control.hminProperty(), e -> hsb.setMin(getSkinnable().getHmin()));
 309         registerChangeListener(control.vvalueProperty(), e -> vsb.setValue(getSkinnable().getVvalue()));
 310         registerChangeListener(control.vmaxProperty(), e -> vsb.setMax(getSkinnable().getVmax()));
 311         registerChangeListener(control.vminProperty(), e -> vsb.setMin(getSkinnable().getVmin()));
 312         registerChangeListener(control.prefViewportWidthProperty(), viewportSizeHintConsumer);
 313         registerChangeListener(control.prefViewportHeightProperty(), viewportSizeHintConsumer);
 314         registerChangeListener(control.minViewportWidthProperty(), viewportSizeHintConsumer);
 315         registerChangeListener(control.minViewportHeightProperty(), viewportSizeHintConsumer);
 316     }
 317 
 318 
 319 
 320     /***************************************************************************
 321      *                                                                         *
 322      * Properties                                                              *
 323      *                                                                         *
 324      **************************************************************************/
 325 
 326     private DoubleProperty contentPosX;
 327     private final void setContentPosX(double value) { contentPosXProperty().set(value); }
 328     private final double getContentPosX() { return contentPosX == null ? 0.0 : contentPosX.get(); }
 329     private final DoubleProperty contentPosXProperty() {
 330         if (contentPosX == null) {
 331             contentPosX = new DoublePropertyBase() {
 332                 @Override protected void invalidated() {
 333                     hsb.setValue(getContentPosX());
 334                     getSkinnable().requestLayout();
 335                 }
 336 
 337                 @Override
 338                 public Object getBean() {
 339                     return ScrollPaneSkin.this;
 340                 }
 341 
 342                 @Override
 343                 public String getName() {
 344                     return "contentPosX";
 345                 }
 346             };
 347         }
 348         return contentPosX;
 349     }
 350 
 351     private DoubleProperty contentPosY;
 352     private final void setContentPosY(double value) { contentPosYProperty().set(value); }
 353     private final double getContentPosY() { return contentPosY == null ? 0.0 : contentPosY.get(); }
 354     private final DoubleProperty contentPosYProperty() {
 355         if (contentPosY == null) {
 356             contentPosY = new DoublePropertyBase() {
 357                 @Override protected void invalidated() {
 358                     vsb.setValue(getContentPosY());
 359                     getSkinnable().requestLayout();
 360                 }
 361 
 362                 @Override
 363                 public Object getBean() {
 364                     return ScrollPaneSkin.this;
 365                 }
 366 
 367                 @Override
 368                 public String getName() {
 369                     return "contentPosY";
 370                 }
 371             };
 372         }
 373         return contentPosY;
 374     }
 375 
 376 
 377 
 378     /***************************************************************************
 379      *                                                                         *
 380      * Public API                                                              *
 381      *                                                                         *
 382      **************************************************************************/
 383 
 384     /** {@inheritDoc} */
 385     @Override public void dispose() {
 386         super.dispose();
 387 
 388         if (behavior != null) {
 389             behavior.dispose();
 390         }
 391     }
 392 
 393     /**
 394      * Returns the horizontal {@link ScrollBar} used in this ScrollPaneSkin
 395      * instance.
 396      */
 397     public final ScrollBar getHorizontalScrollBar() {
 398         return hsb;
 399     }
 400 
 401     /**
 402      * Returns the vertical {@link ScrollBar} used in this ScrollPaneSkin
 403      * instance.
 404      */
 405     public final ScrollBar getVerticalScrollBar() {
 406         return vsb;
 407     }
 408 
 409     /** {@inheritDoc} */
 410     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 411         final ScrollPane sp = getSkinnable();
 412 
 413         double vsbWidth = computeVsbSizeHint(sp);
 414         double minWidth = vsbWidth + snappedLeftInset() + snappedRightInset();
 415 
 416         if (sp.getPrefViewportWidth() > 0) {
 417             return (sp.getPrefViewportWidth() + minWidth);
 418         }
 419         else if (sp.getContent() != null) {
 420             return (sp.getContent().prefWidth(height) + minWidth);
 421         }
 422         else {
 423             return Math.max(minWidth, DEFAULT_PREF_SIZE);
 424         }
 425     }
 426 
 427     /** {@inheritDoc} */
 428     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 429         final ScrollPane sp = getSkinnable();
 430 
 431         double hsbHeight = computeHsbSizeHint(sp);
 432         double minHeight = hsbHeight + snappedTopInset() + snappedBottomInset();
 433 
 434         if (sp.getPrefViewportHeight() > 0) {
 435             return (sp.getPrefViewportHeight() + minHeight);
 436         }
 437         else if (sp.getContent() != null) {
 438             return (sp.getContent().prefHeight(width) + minHeight);
 439         }
 440         else {
 441             return Math.max(minHeight, DEFAULT_PREF_SIZE);
 442         }
 443     }
 444 
 445     /** {@inheritDoc} */
 446     @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 447         final ScrollPane sp = getSkinnable();
 448 
 449         double vsbWidth = computeVsbSizeHint(sp);
 450         double minWidth = vsbWidth + snappedLeftInset() + snappedRightInset();
 451 
 452         if (sp.getMinViewportWidth() > 0) {
 453             return (sp.getMinViewportWidth() + minWidth);
 454         } else {
 455             double w = corner.minWidth(-1);
 456             return (w > 0) ? (3 * w) : (DEFAULT_MIN_SIZE);
 457         }
 458 
 459     }
 460 
 461     /** {@inheritDoc} */
 462     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 463         final ScrollPane sp = getSkinnable();
 464 
 465         double hsbHeight = computeHsbSizeHint(sp);
 466         double minHeight = hsbHeight + snappedTopInset() + snappedBottomInset();
 467 
 468         if (sp.getMinViewportHeight() > 0) {
 469             return (sp.getMinViewportHeight() + minHeight);
 470         } else {
 471             double h = corner.minHeight(-1);
 472             return (h > 0) ? (3 * h) : (DEFAULT_MIN_SIZE);
 473         }
 474     }
 475 
 476     @Override protected void layoutChildren(final double x, final double y,
 477                                             final double w, final double h) {
 478         final ScrollPane control = getSkinnable();
 479         final Insets padding = control.getPadding();
 480         final double rightPadding = snapSize(padding.getRight());
 481         final double leftPadding = snapSize(padding.getLeft());
 482         final double topPadding = snapSize(padding.getTop());
 483         final double bottomPadding = snapSize(padding.getBottom());
 484 
 485         vsb.setMin(control.getVmin());
 486         vsb.setMax(control.getVmax());
 487 
 488         //should only do this on css setup
 489         hsb.setMin(control.getHmin());
 490         hsb.setMax(control.getHmax());
 491 
 492         contentWidth = w;
 493         contentHeight = h;
 494 
 495         /*
 496         ** we want the scrollbars to go right to the border
 497         */
 498         double hsbWidth = 0;
 499         double vsbHeight = 0;
 500 
 501         computeScrollNodeSize(contentWidth, contentHeight);
 502         computeScrollBarSize();
 503 
 504         for (int i = 0; i < 2; ++i) {
 505             vsbvis = determineVerticalSBVisible();
 506             hsbvis = determineHorizontalSBVisible();
 507 
 508             if (vsbvis && !Properties.IS_TOUCH_SUPPORTED) {
 509                 contentWidth = w - vsbWidth;
 510             }
 511             hsbWidth = w + leftPadding + rightPadding - (vsbvis ? vsbWidth : 0);
 512             if (hsbvis && !Properties.IS_TOUCH_SUPPORTED) {
 513                 contentHeight = h - hsbHeight;
 514             }
 515             vsbHeight = h + topPadding + bottomPadding - (hsbvis ? hsbHeight : 0);
 516         }
 517 
 518 
 519         if (scrollNode != null && scrollNode.isResizable()) {
 520             // maybe adjust size now that scrollbars may take up space
 521             if (vsbvis && hsbvis) {
 522                 // adjust just once to accommodate
 523                 computeScrollNodeSize(contentWidth, contentHeight);
 524 
 525             } else if (hsbvis && !vsbvis) {
 526                 computeScrollNodeSize(contentWidth, contentHeight);
 527                 vsbvis = determineVerticalSBVisible();
 528                 if (vsbvis) {
 529                     // now both are visible
 530                     contentWidth -= vsbWidth;
 531                     hsbWidth -= vsbWidth;
 532                     computeScrollNodeSize(contentWidth, contentHeight);
 533                 }
 534             } else if (vsbvis && !hsbvis) {
 535                 computeScrollNodeSize(contentWidth, contentHeight);
 536                 hsbvis = determineHorizontalSBVisible();
 537                 if (hsbvis) {
 538                     // now both are visible
 539                     contentHeight -= hsbHeight;
 540                     vsbHeight -= hsbHeight;
 541                     computeScrollNodeSize(contentWidth, contentHeight);
 542                 }
 543             }
 544         }
 545 
 546         // figure out the content area that is to be filled
 547         double cx = snappedLeftInset() - leftPadding;
 548         double cy = snappedTopInset() - topPadding;
 549 
 550         vsb.setVisible(vsbvis);
 551         if (vsbvis) {
 552             /*
 553             ** round up position of ScrollBar, round down it's size.
 554             **
 555             ** Positioning the ScrollBar
 556             **  The Padding should go between the content and the edge,
 557             **  otherwise changes in padding move the ScrollBar, and could
 558             **  in extreme cases size the ScrollBar to become unusable.
 559             **  The -1, +1 plus one bit :
 560             **   If padding in => 1 then we allow one pixel to appear as the
 561             **   outside border of the Scrollbar, and the rest on the inside.
 562             **   If padding is < 1 then we just stick to the edge.
 563             */
 564             vsb.resizeRelocate(snappedLeftInset() + w - vsbWidth + (rightPadding < 1 ? 0 : rightPadding - 1) ,
 565                     cy, vsbWidth, vsbHeight);
 566         }
 567         updateVerticalSB();
 568 
 569         hsb.setVisible(hsbvis);
 570         if (hsbvis) {
 571             /*
 572             ** round up position of ScrollBar, round down it's size.
 573             **
 574             ** Positioning the ScrollBar
 575             **  The Padding should go between the content and the edge,
 576             **  otherwise changes in padding move the ScrollBar, and could
 577             **  in extreme cases size the ScrollBar to become unusable.
 578             **  The -1, +1 plus one bit :
 579             **   If padding in => 1 then we allow one pixel to appear as the
 580             **   outside border of the Scrollbar, and the rest on the inside.
 581             **   If padding is < 1 then we just stick to the edge.
 582             */
 583             hsb.resizeRelocate(cx, snappedTopInset() + h - hsbHeight + (bottomPadding < 1 ? 0 : bottomPadding - 1),
 584                     hsbWidth, hsbHeight);
 585         }
 586         updateHorizontalSB();
 587 
 588         viewRect.resizeRelocate(snappedLeftInset(), snappedTopInset(), snapSize(contentWidth), snapSize(contentHeight));
 589         resetClip();
 590 
 591         if (vsbvis && hsbvis) {
 592             corner.setVisible(true);
 593             double cornerWidth = vsbWidth;
 594             double cornerHeight = hsbHeight;
 595             corner.resizeRelocate(snapPosition(vsb.getLayoutX()), snapPosition(hsb.getLayoutY()), snapSize(cornerWidth), snapSize(cornerHeight));
 596         } else {
 597             corner.setVisible(false);
 598         }
 599         control.setViewportBounds(new BoundingBox(snapPosition(viewContent.getLayoutX()), snapPosition(viewContent.getLayoutY()), snapSize(contentWidth), snapSize(contentHeight)));
 600     }
 601 
 602     /** {@inheritDoc} */
 603     @Override protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 604         switch (attribute) {
 605             case VERTICAL_SCROLLBAR: return vsb;
 606             case HORIZONTAL_SCROLLBAR: return hsb;
 607             default: return super.queryAccessibleAttribute(attribute, parameters);
 608         }
 609     }
 610 
 611 
 612 
 613     /***************************************************************************
 614      *                                                                         *
 615      * Private implementation                                                  *
 616      *                                                                         *
 617      **************************************************************************/
 618 
 619     private void initialize() {
 620         // requestLayout calls below should not trigger requestLayout above ScrollPane
 621 //        setManaged(false);
 622 
 623         ScrollPane control = getSkinnable();
 624         scrollNode = control.getContent();
 625 
 626         ParentTraversalEngine traversalEngine = new ParentTraversalEngine(getSkinnable());
 627         traversalEngine.addTraverseListener((node, bounds) -> {
 628             // auto-scroll so node is within (0,0),(contentWidth,contentHeight)
 629             scrollBoundsIntoView(bounds);
 630         });
 631         getSkinnable().setImpl_traversalEngine(traversalEngine);
 632 
 633         if (scrollNode != null) {
 634             scrollNode.layoutBoundsProperty().addListener(nodeListener);
 635             scrollNode.layoutBoundsProperty().addListener(boundsChangeListener);
 636         }
 637 
 638         viewRect = new StackPane() {
 639             @Override protected void layoutChildren() {


 640                 viewContent.resize(getWidth(), getHeight());
 641             }

 642         };
 643         // prevent requestLayout requests from within scrollNode from percolating up
 644         viewRect.setManaged(false);
 645         viewRect.setCache(true);
 646         viewRect.getStyleClass().add("viewport");
 647 
 648         clipRect = new Rectangle();
 649         viewRect.setClip(clipRect);
 650 
 651         hsb = new ScrollBar();
 652 
 653         vsb = new ScrollBar();
 654         vsb.setOrientation(Orientation.VERTICAL);
 655 
 656         EventHandler<MouseEvent> barHandler = ev -> {
 657             getSkinnable().requestFocus();
 658         };
 659 
 660         hsb.addEventFilter(MouseEvent.MOUSE_PRESSED, barHandler);
 661         vsb.addEventFilter(MouseEvent.MOUSE_PRESSED, barHandler);


 686                 }
 687                 if (scrollNode != null) {
 688                     scrollNode.relocate(0,0);
 689                 }
 690             }
 691         };
 692         viewRect.getChildren().add(viewContent);
 693 
 694         if (scrollNode != null) {
 695             viewContent.getChildren().add(scrollNode);
 696             viewRect.nodeOrientationProperty().bind(scrollNode.nodeOrientationProperty());
 697         }
 698 
 699         getChildren().clear();
 700         getChildren().addAll(viewRect, vsb, hsb, corner);
 701 
 702         /*
 703         ** listeners, and assorted housekeeping
 704         */
 705         InvalidationListener vsbListener = valueModel -> {
 706             if (!Properties.IS_TOUCH_SUPPORTED) {
 707                 posY = Utils.clamp(getSkinnable().getVmin(), vsb.getValue(), getSkinnable().getVmax());
 708             }
 709             else {
 710                 posY = vsb.getValue();
 711             }
 712             updatePosY();
 713         };
 714         vsb.valueProperty().addListener(vsbListener);
 715 
 716         InvalidationListener hsbListener = valueModel -> {
 717             if (!Properties.IS_TOUCH_SUPPORTED) {
 718                 posX = Utils.clamp(getSkinnable().getHmin(), hsb.getValue(), getSkinnable().getHmax());
 719             }
 720             else {
 721                 posX = hsb.getValue();
 722             }
 723             updatePosX();
 724         };
 725         hsb.valueProperty().addListener(hsbListener);
 726 
 727         viewRect.setOnMousePressed(e -> {
 728             mouseDown = true;
 729             if (Properties.IS_TOUCH_SUPPORTED) {
 730                 startSBReleasedAnimation();
 731             }
 732             pressX = e.getX();
 733             pressY = e.getY();
 734             ohvalue = hsb.getValue();
 735             ovvalue = vsb.getValue();
 736         });
 737 
 738 
 739         viewRect.setOnDragDetected(e -> {
 740              if (Properties.IS_TOUCH_SUPPORTED) {
 741                  startSBReleasedAnimation();
 742              }
 743             if (getSkinnable().isPannable()) {
 744               dragDetected = true;
 745               if (saveCursor == null) {
 746                   saveCursor = getSkinnable().getCursor();
 747                   if (saveCursor == null) {
 748                       saveCursor = Cursor.DEFAULT;
 749                   }
 750                   getSkinnable().setCursor(Cursor.MOVE);
 751                   getSkinnable().requestLayout();
 752               }
 753             }
 754         });
 755 
 756         viewRect.addEventFilter(MouseEvent.MOUSE_RELEASED, e -> {
 757              mouseDown = false;
 758              if (dragDetected == true) {
 759                  if (saveCursor != null) {
 760                      getSkinnable().setCursor(saveCursor);
 761                      saveCursor = null;
 762                      getSkinnable().requestLayout();
 763                  }
 764                  dragDetected = false;
 765              }
 766 
 767              /*
 768              ** if the contents need repositioning, and there's is no
 769              ** touch event in progress, then start the repositioning.
 770              */
 771              if ((posY > getSkinnable().getVmax() || posY < getSkinnable().getVmin() ||
 772                  posX > getSkinnable().getHmax() || posX < getSkinnable().getHmin()) && !touchDetected) {
 773                  startContentsToViewport();
 774              }
 775         });
 776         viewRect.setOnMouseDragged(e -> {
 777              if (Properties.IS_TOUCH_SUPPORTED) {
 778                  startSBReleasedAnimation();
 779              }
 780             /*
 781             ** for mobile-touch we allow drag, even if not pannagle
 782             */
 783             if (getSkinnable().isPannable() || Properties.IS_TOUCH_SUPPORTED) {
 784                 double deltaX = pressX - e.getX();
 785                 double deltaY = pressY - e.getY();
 786                 /*
 787                 ** we only drag if not all of the content is visible.
 788                 */
 789                 if (hsb.getVisibleAmount() > 0.0 && hsb.getVisibleAmount() < hsb.getMax()) {
 790                     if (Math.abs(deltaX) > PAN_THRESHOLD) {
 791                         if (isReverseNodeOrientation()) {
 792                             deltaX = -deltaX;
 793                         }
 794                         double newHVal = (ohvalue + deltaX / (nodeWidth - viewRect.getWidth()) * (hsb.getMax() - hsb.getMin()));
 795                         if (!Properties.IS_TOUCH_SUPPORTED) {
 796                             if (newHVal > hsb.getMax()) {
 797                                 newHVal = hsb.getMax();
 798                             }
 799                             else if (newHVal < hsb.getMin()) {
 800                                 newHVal = hsb.getMin();
 801                             }
 802                             hsb.setValue(newHVal);
 803                         }
 804                         else {
 805                             hsb.setValue(newHVal);
 806                         }
 807                     }
 808                 }
 809                 /*
 810                 ** we only drag if not all of the content is visible.
 811                 */
 812                 if (vsb.getVisibleAmount() > 0.0 && vsb.getVisibleAmount() < vsb.getMax()) {
 813                     if (Math.abs(deltaY) > PAN_THRESHOLD) {
 814                         double newVVal = (ovvalue + deltaY / (nodeHeight - viewRect.getHeight()) * (vsb.getMax() - vsb.getMin()));
 815                         if (!Properties.IS_TOUCH_SUPPORTED) {
 816                             if (newVVal > vsb.getMax()) {
 817                                 newVVal = vsb.getMax();
 818                             }
 819                             else if (newVVal < vsb.getMin()) {
 820                                 newVVal = vsb.getMin();
 821                             }
 822                             vsb.setValue(newVVal);
 823                         }
 824                         else {
 825                             vsb.setValue(newVVal);
 826                         }
 827                     }
 828                 }
 829             }
 830             /*
 831             ** we need to consume drag events, as we don't want
 832             ** the scrollpane itself to be dragged on every mouse click
 833             */
 834             e.consume();
 835         });


 860                     !((ScrollEvent)event).isDirect()) {
 861                 tail = tail.prepend(blockEventDispatcher);
 862                 tail = tail.prepend(oldVsbEventDispatcher);
 863                 return tail.dispatchEvent(event);
 864             }
 865             return oldVsbEventDispatcher.dispatchEvent(event, tail);
 866         });
 867 
 868         /*
 869          * listen for ScrollEvents over the whole of the ScrollPane
 870          * area, the above dispatcher having removed the ScrollBars
 871          * scroll event handling.
 872          *
 873          * Note that we use viewRect here, rather than setting the eventHandler
 874          * on the ScrollPane itself. This is for RT-31582, and effectively
 875          * allows for us to prioritise handling (and consuming) the event
 876          * internally, before it is made available to users listening to events
 877          * on the control. This is consistent with the VirtualFlow-based controls.
 878          */
 879         viewRect.addEventHandler(ScrollEvent.SCROLL, event -> {
 880             if (Properties.IS_TOUCH_SUPPORTED) {
 881                 startSBReleasedAnimation();
 882             }
 883             /*
 884             ** if we're completely visible then do nothing....
 885             ** we only consume an event that we've used.
 886             */
 887             if (vsb.getVisibleAmount() < vsb.getMax()) {
 888                 double vRange = getSkinnable().getVmax()-getSkinnable().getVmin();
 889                 double vPixelValue;
 890                 if (nodeHeight > 0.0) {
 891                     vPixelValue = vRange / nodeHeight;
 892                 }
 893                 else {
 894                     vPixelValue = 0.0;
 895                 }
 896                 double newValue = vsb.getValue()+(-event.getDeltaY())*vPixelValue;
 897                 if (!Properties.IS_TOUCH_SUPPORTED) {
 898                     if ((event.getDeltaY() > 0.0 && vsb.getValue() > vsb.getMin()) ||
 899                         (event.getDeltaY() < 0.0 && vsb.getValue() < vsb.getMax())) {
 900                         vsb.setValue(newValue);
 901                         event.consume();
 902                     }
 903                 }
 904                 else {
 905                     /*
 906                     ** if there is a repositioning in progress then we only
 907                     ** set the value for 'real' events
 908                     */
 909                     if (!(((ScrollEvent)event).isInertia()) || (((ScrollEvent)event).isInertia()) && (contentsToViewTimeline == null || contentsToViewTimeline.getStatus() == Status.STOPPED)) {
 910                         vsb.setValue(newValue);
 911                         if ((newValue > vsb.getMax() || newValue < vsb.getMin()) && (!mouseDown && !touchDetected)) {
 912                             startContentsToViewport();
 913                         }
 914                         event.consume();
 915                     }
 916                 }
 917             }
 918 
 919             if (hsb.getVisibleAmount() < hsb.getMax()) {
 920                 double hRange = getSkinnable().getHmax()-getSkinnable().getHmin();
 921                 double hPixelValue;
 922                 if (nodeWidth > 0.0) {
 923                     hPixelValue = hRange / nodeWidth;
 924                 }
 925                 else {
 926                     hPixelValue = 0.0;
 927                 }
 928 
 929                 double newValue = hsb.getValue()+(-event.getDeltaX())*hPixelValue;
 930                 if (!Properties.IS_TOUCH_SUPPORTED) {
 931                     if ((event.getDeltaX() > 0.0 && hsb.getValue() > hsb.getMin()) ||
 932                         (event.getDeltaX() < 0.0 && hsb.getValue() < hsb.getMax())) {
 933                         hsb.setValue(newValue);
 934                         event.consume();
 935                     }
 936                 }
 937                 else {
 938                     /*
 939                     ** if there is a repositioning in progress then we only
 940                     ** set the value for 'real' events
 941                     */
 942                     if (!(((ScrollEvent)event).isInertia()) || (((ScrollEvent)event).isInertia()) && (contentsToViewTimeline == null || contentsToViewTimeline.getStatus() == Status.STOPPED)) {
 943                         hsb.setValue(newValue);
 944 
 945                         if ((newValue > hsb.getMax() || newValue < hsb.getMin()) && (!mouseDown && !touchDetected)) {
 946                             startContentsToViewport();
 947                         }
 948                         event.consume();
 949                     }
 950                 }
 951             }
 952         });
 953 
 954         /*
 955         ** there are certain animations that need to know if the touch is
 956         ** happening.....
 957         */
 958         getSkinnable().addEventHandler(TouchEvent.TOUCH_PRESSED, e -> {
 959             touchDetected = true;
 960             startSBReleasedAnimation();
 961             e.consume();
 962         });
 963 
 964         getSkinnable().addEventHandler(TouchEvent.TOUCH_RELEASED, e -> {
 965             touchDetected = false;
 966             e.consume();
 967         });





















































































































































 968 
 969         // ScrollPanes do not block all MouseEvents by default, unlike most other UI Controls.
 970         consumeMouseEvents(false);
 971 
 972         // update skin initial state to match control (see RT-35554)
 973         hsb.setValue(control.getHvalue());
 974         vsb.setValue(control.getVvalue());
 975     }
 976 
 977     void scrollBoundsIntoView(Bounds b) {
 978         double dx = 0.0;
 979         double dy = 0.0;
 980         if (b.getMaxX() > contentWidth) {
 981             dx = b.getMinX() - snappedLeftInset();
 982         }
 983         if (b.getMinX() < snappedLeftInset()) {
 984             dx = b.getMaxX() - contentWidth - snappedLeftInset();
 985         }
 986         if (b.getMaxY() > snappedTopInset() + contentHeight) {
 987             dy = b.getMinY() - snappedTopInset();
 988         }
 989         if (b.getMinY() < snappedTopInset()) {
 990             dy = b.getMaxY() - contentHeight - snappedTopInset();
 991         }
 992         // We want to move contentPanel's layoutX,Y by (dx,dy).
 993         // But to do this we have to set the scrollbars' values appropriately.
 994 
 995         if (dx != 0) {
 996             double sdx = dx * (hsb.getMax() - hsb.getMin()) / (nodeWidth - contentWidth);
 997             // Adjust back for some amount so that the Node border is not too close to view border
 998             sdx += -1 * Math.signum(sdx) * hsb.getUnitIncrement() / 5; // This accounts to 2% of view width
 999             hsb.setValue(hsb.getValue() + sdx);
1000             getSkinnable().requestLayout();





1001         }
1002         if (dy != 0) {
1003             double sdy = dy * (vsb.getMax() - vsb.getMin()) / (nodeHeight - contentHeight);
1004             // Adjust back for some amount so that the Node border is not too close to view border
1005             sdy += -1 * Math.signum(sdy) * vsb.getUnitIncrement() / 5; // This accounts to 2% of view height
1006             vsb.setValue(vsb.getValue() + sdy);
1007             getSkinnable().requestLayout();
1008         }
1009 












1010     }
1011 
1012     /**
1013      * Computes the size that should be reserved for horizontal scrollbar in size hints (min/pref height)
1014      */
1015     private double computeHsbSizeHint(ScrollPane sp) {
1016         return ((sp.getHbarPolicy() == ScrollBarPolicy.ALWAYS) ||
1017                 (sp.getHbarPolicy() == ScrollBarPolicy.AS_NEEDED && (sp.getPrefViewportHeight() > 0 || sp.getMinViewportHeight() > 0)))
1018                 ? hsb.prefHeight(ScrollBar.USE_COMPUTED_SIZE)
1019                 : 0;
1020     }
1021 
1022     /**
1023      * Computes the size that should be reserved for vertical scrollbar in size hints (min/pref width)
1024      */
1025     private double computeVsbSizeHint(ScrollPane sp) {
1026         return ((sp.getVbarPolicy() == ScrollBarPolicy.ALWAYS) ||
1027                 (sp.getVbarPolicy() == ScrollBarPolicy.AS_NEEDED && (sp.getPrefViewportWidth() > 0
1028                         || sp.getMinViewportWidth() > 0)))
1029                 ? vsb.prefWidth(ScrollBar.USE_COMPUTED_SIZE)
1030                 : 0;
1031     }
1032 






























































































































1033     private void computeScrollNodeSize(double contentWidth, double contentHeight) {
1034         if (scrollNode != null) {
1035             if (scrollNode.isResizable()) {
1036                 ScrollPane control = getSkinnable();
1037                 Orientation bias = scrollNode.getContentBias();
1038                 if (bias == null) {
1039                     nodeWidth = snapSize(boundedSize(control.isFitToWidth()? contentWidth : scrollNode.prefWidth(-1),
1040                                                          scrollNode.minWidth(-1),scrollNode.maxWidth(-1)));
1041                     nodeHeight = snapSize(boundedSize(control.isFitToHeight()? contentHeight : scrollNode.prefHeight(-1),
1042                                                           scrollNode.minHeight(-1), scrollNode.maxHeight(-1)));
1043 
1044                 } else if (bias == Orientation.HORIZONTAL) {
1045                     nodeWidth = snapSize(boundedSize(control.isFitToWidth()? contentWidth : scrollNode.prefWidth(-1),
1046                                                          scrollNode.minWidth(-1),scrollNode.maxWidth(-1)));
1047                     nodeHeight = snapSize(boundedSize(control.isFitToHeight()? contentHeight : scrollNode.prefHeight(nodeWidth),
1048                                                           scrollNode.minHeight(nodeWidth),scrollNode.maxHeight(nodeWidth)));
1049 
1050                 } else { // bias == VERTICAL
1051                     nodeHeight = snapSize(boundedSize(control.isFitToHeight()? contentHeight : scrollNode.prefHeight(-1),
1052                                                           scrollNode.minHeight(-1), scrollNode.maxHeight(-1)));


1054                                                          scrollNode.minWidth(nodeHeight),scrollNode.maxWidth(nodeHeight)));
1055                 }
1056 
1057             } else {
1058                 nodeWidth = snapSize(scrollNode.getLayoutBounds().getWidth());
1059                 nodeHeight = snapSize(scrollNode.getLayoutBounds().getHeight());
1060             }
1061             nodeSizeInvalid = false;
1062         }
1063     }
1064 
1065     private boolean isReverseNodeOrientation() {
1066         return (scrollNode != null &&
1067                 getSkinnable().getEffectiveNodeOrientation() !=
1068                             scrollNode.getEffectiveNodeOrientation());
1069     }
1070 
1071     private boolean determineHorizontalSBVisible() {
1072         final ScrollPane sp = getSkinnable();
1073 
1074         if (Properties.IS_TOUCH_SUPPORTED) {
1075             return (tempVisibility && (nodeWidth > contentWidth));
1076         }
1077         else {
1078             // RT-17395: ScrollBarPolicy might be null. If so, treat it as "AS_NEEDED", which is the default
1079             ScrollBarPolicy hbarPolicy = sp.getHbarPolicy();
1080             return (ScrollBarPolicy.NEVER == hbarPolicy) ? false :
1081                    ((ScrollBarPolicy.ALWAYS == hbarPolicy) ? true :
1082                    ((sp.isFitToWidth() && scrollNode != null ? scrollNode.isResizable() : false) ?
1083                    (nodeWidth > contentWidth && scrollNode.minWidth(-1) > contentWidth) : (nodeWidth > contentWidth)));
1084         }
1085     }
1086 
1087     private boolean determineVerticalSBVisible() {
1088         final ScrollPane sp = getSkinnable();
1089 
1090         if (Properties.IS_TOUCH_SUPPORTED) {
1091             return (tempVisibility && (nodeHeight > contentHeight));
1092         }
1093         else {
1094             // RT-17395: ScrollBarPolicy might be null. If so, treat it as "AS_NEEDED", which is the default
1095             ScrollBarPolicy vbarPolicy = sp.getVbarPolicy();
1096             return (ScrollBarPolicy.NEVER == vbarPolicy) ? false :
1097                    ((ScrollBarPolicy.ALWAYS == vbarPolicy) ? true :
1098                    ((sp.isFitToHeight() && scrollNode != null ? scrollNode.isResizable() : false) ?
1099                    (nodeHeight > contentHeight && scrollNode.minHeight(-1) > contentHeight) : (nodeHeight > contentHeight)));
1100         }
1101     }
1102 
1103     private void computeScrollBarSize() {
1104         vsbWidth = snapSize(vsb.prefWidth(-1));
1105         if (vsbWidth == 0) {
1106             //            println("*** WARNING ScrollPaneSkin: can't get scroll bar width, using {DEFAULT_SB_BREADTH}");
1107             if (Properties.IS_TOUCH_SUPPORTED) {
1108                 vsbWidth = DEFAULT_EMBEDDED_SB_BREADTH;
1109             }
1110             else {
1111                 vsbWidth = DEFAULT_SB_BREADTH;
1112             }
1113         }
1114         hsbHeight = snapSize(hsb.prefHeight(-1));
1115         if (hsbHeight == 0) {
1116             //            println("*** WARNING ScrollPaneSkin: can't get scroll bar height, using {DEFAULT_SB_BREADTH}");
1117             if (Properties.IS_TOUCH_SUPPORTED) {
1118                 hsbHeight = DEFAULT_EMBEDDED_SB_BREADTH;
1119             }
1120             else {
1121                 hsbHeight = DEFAULT_SB_BREADTH;
1122             }
1123         }
1124     }
1125 
1126     private void updateHorizontalSB() {
1127         double contentRatio = nodeWidth * (hsb.getMax() - hsb.getMin());
1128         if (contentRatio > 0.0) {
1129             hsb.setVisibleAmount(contentWidth / contentRatio);
1130             hsb.setBlockIncrement(0.9 * hsb.getVisibleAmount());
1131             hsb.setUnitIncrement(0.1 * hsb.getVisibleAmount());
1132         }
1133         else {
1134             hsb.setVisibleAmount(0.0);
1135             hsb.setBlockIncrement(0.0);
1136             hsb.setUnitIncrement(0.0);
1137         }


1176         double x = isReverseNodeOrientation() ? (hsb.getMax() - (posX - hsb.getMin())) : posX;
1177         double minX = Math.min((- x / (hsb.getMax() - hsb.getMin()) * (nodeWidth - contentWidth)), 0);
1178         viewContent.setLayoutX(snapPosition(minX));
1179         if (!sp.hvalueProperty().isBound()) sp.setHvalue(Utils.clamp(sp.getHmin(), posX, sp.getHmax()));
1180         return posX;
1181     }
1182 
1183     private double updatePosY() {
1184         final ScrollPane sp = getSkinnable();
1185         double minY = Math.min((- posY / (vsb.getMax() - vsb.getMin()) * (nodeHeight - contentHeight)), 0);
1186         viewContent.setLayoutY(snapPosition(minY));
1187         if (!sp.vvalueProperty().isBound()) sp.setVvalue(Utils.clamp(sp.getVmin(), posY, sp.getVmax()));
1188         return posY;
1189     }
1190 
1191     private void resetClip() {
1192         clipRect.setWidth(snapSize(contentWidth));
1193         clipRect.setHeight(snapSize(contentHeight));
1194     }
1195 
1196     private void startSBReleasedAnimation() {











1197         if (sbTouchTimeline == null) {
1198             /*
1199             ** timeline to leave the scrollbars visible for a short
1200             ** while after a scroll/drag
1201             */
1202             sbTouchTimeline = new Timeline();
1203             sbTouchKF1 = new KeyFrame(Duration.millis(0), event -> {
1204                 tempVisibility = true;
1205                 if (touchDetected == true || mouseDown == true) {
1206                     sbTouchTimeline.playFromStart();
1207                 }
1208             });
1209 
1210             sbTouchKF2 = new KeyFrame(Duration.millis(1000), event -> {
1211                 tempVisibility = false;
1212                 getSkinnable().requestLayout();
1213             });
1214             sbTouchTimeline.getKeyFrames().addAll(sbTouchKF1, sbTouchKF2);
1215         }
1216         sbTouchTimeline.playFromStart();
1217     }
1218 
1219     private void startContentsToViewport() {


1220         double newPosX = posX;
1221         double newPosY = posY;
1222 
1223         setContentPosX(posX);
1224         setContentPosY(posY);
1225 
1226         if (posY > getSkinnable().getVmax()) {
1227             newPosY = getSkinnable().getVmax();
1228         }
1229         else if (posY < getSkinnable().getVmin()) {
1230             newPosY = getSkinnable().getVmin();
1231         }
1232 
1233 
1234         if (posX > getSkinnable().getHmax()) {
1235             newPosX = getSkinnable().getHmax();
1236         }
1237         else if (posX < getSkinnable().getHmin()) {
1238             newPosX = getSkinnable().getHmin();
1239         }
1240 
1241         if (!Properties.IS_TOUCH_SUPPORTED) {
1242             startSBReleasedAnimation();
1243         }
1244 
1245         /*
1246         ** timeline to return the contents of the scrollpane to the viewport
1247         */
1248         if (contentsToViewTimeline != null) {
1249             contentsToViewTimeline.stop();
1250         }
1251         contentsToViewTimeline = new Timeline();
1252         /*
1253         ** short pause before animation starts
1254         */
1255         contentsToViewKF1 = new KeyFrame(Duration.millis(50));
1256         /*
1257         ** reposition
1258         */
1259         contentsToViewKF2 = new KeyFrame(Duration.millis(150), event -> {
1260             getSkinnable().requestLayout();
1261         },
1262                 new KeyValue(contentPosX, newPosX),
1263                 new KeyValue(contentPosY, newPosY)
1264         );
1265         /*
1266         ** block out 'aftershocks', but real events will
1267         ** still reactivate
1268         */
1269         contentsToViewKF3 = new KeyFrame(Duration.millis(1500));
1270         contentsToViewTimeline.getKeyFrames().addAll(contentsToViewKF1, contentsToViewKF2, contentsToViewKF3);
1271         contentsToViewTimeline.playFromStart();




























































1272     }
1273 }