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

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
   1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.scene.control.skin;
  27 

  28 import javafx.beans.property.DoubleProperty;
  29 import javafx.css.StyleableBooleanProperty;
  30 import javafx.css.StyleableDoubleProperty;
  31 import javafx.css.StyleableObjectProperty;
  32 import javafx.css.CssMetaData;
  33 
  34 import com.sun.javafx.css.converters.BooleanConverter;
  35 import com.sun.javafx.css.converters.EnumConverter;
  36 import com.sun.javafx.css.converters.SizeConverter;
  37 import com.sun.javafx.scene.control.behavior.PaginationBehavior;
  38 
  39 import java.util.ArrayList;
  40 import java.util.Collections;
  41 import java.util.List;
  42 
  43 import javafx.animation.*;
  44 import javafx.application.Platform;
  45 import javafx.beans.property.BooleanProperty;
  46 import javafx.beans.property.ObjectProperty;
  47 import javafx.beans.value.ChangeListener;
  48 import javafx.beans.value.ObservableValue;
  49 import javafx.beans.value.WritableValue;
  50 import javafx.collections.ListChangeListener;
  51 import javafx.css.Styleable;
  52 import javafx.css.StyleableProperty;
  53 import javafx.event.ActionEvent;
  54 import javafx.event.EventHandler;
  55 import javafx.geometry.HPos;
  56 import javafx.geometry.Insets;
  57 import javafx.geometry.Pos;
  58 import javafx.geometry.Side;
  59 import javafx.geometry.VPos;
  60 import javafx.scene.AccessibleAction;
  61 import javafx.scene.AccessibleAttribute;
  62 import javafx.scene.AccessibleRole;
  63 import javafx.scene.Node;
  64 import javafx.scene.control.*;
  65 import javafx.scene.input.MouseEvent;
  66 import javafx.scene.input.TouchEvent;
  67 import javafx.scene.layout.HBox;
  68 import javafx.scene.layout.StackPane;
  69 import javafx.scene.shape.Rectangle;
  70 import javafx.util.Duration;
  71 
  72 import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString;
  73 
  74 public class PaginationSkin extends BehaviorSkinBase<Pagination, PaginationBehavior>  {












  75 
  76     private static final Duration DURATION = new Duration(125.0);
  77     private static final double SWIPE_THRESHOLD = 0.30;
  78     private static final double TOUCH_THRESHOLD = 15;









  79 
  80     private Pagination pagination;
  81     private StackPane currentStackPane;
  82     private StackPane nextStackPane;
  83     private Timeline timeline;
  84     private Rectangle clipRect;
  85 
  86     private NavigationControl navigation;
  87     private int fromIndex;
  88     private int previousIndex;
  89     private int currentIndex;
  90     private int toIndex;
  91     private int pageCount;
  92     private int maxPageIndicatorCount;
  93 














  94     private boolean animate = true;
  95 
  96     public PaginationSkin(final Pagination pagination) {
  97         super(pagination, new PaginationBehavior(pagination));


















































  98 
  99 //        setManaged(false);
 100         clipRect = new Rectangle();
 101         getSkinnable().setClip(clipRect);
 102 
 103         this.pagination = pagination;
 104 
 105         this.currentStackPane = new StackPane();
 106         currentStackPane.getStyleClass().add("page");
 107 
 108         this.nextStackPane = new StackPane();
 109         nextStackPane.getStyleClass().add("page");
 110         nextStackPane.setVisible(false);
 111 
 112         resetIndexes(true);
 113 
 114         this.navigation = new NavigationControl();
 115 
 116         getChildren().addAll(currentStackPane, nextStackPane, navigation);
 117 
 118         pagination.maxPageIndicatorCountProperty().addListener(o -> {
 119             resetIndiciesAndNav();
 120         });
 121 
 122         registerChangeListener(pagination.widthProperty(), "WIDTH");
 123         registerChangeListener(pagination.heightProperty(), "HEIGHT");
 124         registerChangeListener(pagination.pageCountProperty(), "PAGE_COUNT");
 125         registerChangeListener(pagination.pageFactoryProperty(), "PAGE_FACTORY");











 126 
 127         initializeSwipeAndTouchHandlers();
 128     }
 129 
 130     protected void resetIndiciesAndNav() {
 131         resetIndexes(false);
 132         navigation.initializePageIndicators();
 133         navigation.updatePageIndicators();


















































































































 134     }
 135 
 136     public void selectNext() {








































































































 137         if (getCurrentPageIndex() < getPageCount() - 1) {
 138             pagination.setCurrentPageIndex(getCurrentPageIndex() + 1);
 139         }
 140     }
 141 
 142     public void selectPrevious() {
 143         if (getCurrentPageIndex() > 0) {
 144             pagination.setCurrentPageIndex(getCurrentPageIndex() - 1);
 145         }
 146     }
 147 
 148     private double startTouchPos;
 149     private double lastTouchPos;
 150     private long startTouchTime;
 151     private long lastTouchTime;
 152     private double touchVelocity;
 153     private boolean touchThresholdBroken;
 154     private int touchEventId = -1;
 155     private boolean nextPageReached = false;
 156     private boolean setInitialDirection = false;
 157     private int direction;
 158 
 159     private void initializeSwipeAndTouchHandlers() {
 160         final Pagination control = getSkinnable();
 161 
 162         getSkinnable().addEventHandler(TouchEvent.TOUCH_PRESSED, e -> {
 163             if (touchEventId == -1) {
 164                 touchEventId = e.getTouchPoint().getId();
 165             }
 166             if (touchEventId != e.getTouchPoint().getId()) {
 167                 return;
 168             }
 169             lastTouchPos = startTouchPos = e.getTouchPoint().getX();
 170             lastTouchTime = startTouchTime = System.currentTimeMillis();
 171             touchThresholdBroken = false;
 172             e.consume();
 173         });
 174 
 175         getSkinnable().addEventHandler(TouchEvent.TOUCH_MOVED, e -> {
 176             if (touchEventId != e.getTouchPoint().getId()) {
 177                 return;


 354             }
 355         }
 356         return false;
 357     }
 358 
 359     private int getPageCount() {
 360         if (getSkinnable().getPageCount() < 1) {
 361             return 1;
 362         }
 363         return getSkinnable().getPageCount();
 364     }
 365 
 366     private int getMaxPageIndicatorCount() {
 367         return getSkinnable().getMaxPageIndicatorCount();
 368     }
 369 
 370     private int getCurrentPageIndex() {
 371         return getSkinnable().getCurrentPageIndex();
 372     }
 373 
 374     private static final Interpolator interpolator = Interpolator.SPLINE(0.4829, 0.5709, 0.6803, 0.9928);
 375     private int currentAnimatedIndex;
 376     private boolean hasPendingAnimation = false;
 377 
 378     private void animateSwitchPage() {
 379         if (timeline != null) {
 380             timeline.setRate(8);
 381             hasPendingAnimation = true;
 382             return;
 383         }
 384 
 385         // We are handling a touch event if nextPane's page has already been
 386         // created and visible == true.
 387         if (!nextStackPane.isVisible()) {
 388             if (!createPage(nextStackPane, currentAnimatedIndex)) {
 389                 // The next page does not exist just return without starting
 390                 // any animation.
 391                 return;
 392             }
 393         }
 394         if (nextPageReached) {
 395             // No animation is needed when the next page is already showing
 396             // and in the correct position.  Just swap the panes and return
 397             swapPanes();


 431                 }
 432                 nextStackPane.setVisible(true);
 433                 timeline = new Timeline();
 434                 KeyFrame k1 = new KeyFrame(Duration.millis(0),
 435                     new KeyValue(currentStackPane.translateXProperty(),
 436                         useTranslateX ? currentStackPane.getTranslateX() : 0,
 437                         interpolator),
 438                     new KeyValue(nextStackPane.translateXProperty(),
 439                         useTranslateX ? nextStackPane.getTranslateX() : -currentStackPane.getWidth(),
 440                         interpolator));
 441                 KeyFrame k2 = new KeyFrame(DURATION,
 442                     swipeAnimationEndEventHandler,
 443                     new KeyValue(currentStackPane.translateXProperty(), currentStackPane.getWidth(), interpolator),
 444                     new KeyValue(nextStackPane.translateXProperty(), 0, interpolator));
 445                 timeline.getKeyFrames().setAll(k1, k2);
 446                 timeline.play();
 447             }
 448         });
 449     }
 450 
 451     private EventHandler<ActionEvent> swipeAnimationEndEventHandler = new EventHandler<ActionEvent>() {
 452         @Override public void handle(ActionEvent t) {
 453             swapPanes();
 454             timeline = null;
 455 
 456             if (hasPendingAnimation) {
 457                 animateSwitchPage();
 458                 hasPendingAnimation = false;
 459             }
 460         }
 461     };
 462 
 463     private void swapPanes() {
 464         StackPane temp = currentStackPane;
 465         currentStackPane = nextStackPane;
 466         nextStackPane = temp;
 467 
 468         currentStackPane.setTranslateX(0);
 469         currentStackPane.setCache(false);
 470 
 471         nextStackPane.setTranslateX(0);
 472         nextStackPane.setCache(false);
 473         nextStackPane.setVisible(false);
 474         nextStackPane.getChildren().clear();
 475     }
 476 
 477     // If the swipe hasn't reached the THRESHOLD we want to animate the clamping.
 478     private void animateClamping(boolean rightToLeft) {
 479         if (rightToLeft) {  // animate right to left
 480             timeline = new Timeline();
 481             KeyFrame k1 = new KeyFrame(Duration.millis(0),
 482                 new KeyValue(currentStackPane.translateXProperty(), currentStackPane.getTranslateX(), interpolator),


 484             KeyFrame k2 = new KeyFrame(DURATION,
 485                 clampAnimationEndEventHandler,
 486                 new KeyValue(currentStackPane.translateXProperty(), 0, interpolator),
 487                 new KeyValue(nextStackPane.translateXProperty(), currentStackPane.getWidth(), interpolator));
 488             timeline.getKeyFrames().setAll(k1, k2);
 489             timeline.play();
 490         } else { // animate left to right
 491             timeline = new Timeline();
 492             KeyFrame k1 = new KeyFrame(Duration.millis(0),
 493                 new KeyValue(currentStackPane.translateXProperty(), currentStackPane.getTranslateX(), interpolator),
 494                 new KeyValue(nextStackPane.translateXProperty(), nextStackPane.getTranslateX(), interpolator));
 495             KeyFrame k2 = new KeyFrame(DURATION,
 496                 clampAnimationEndEventHandler,
 497                 new KeyValue(currentStackPane.translateXProperty(), 0, interpolator),
 498                 new KeyValue(nextStackPane.translateXProperty(), -currentStackPane.getWidth(), interpolator));
 499             timeline.getKeyFrames().setAll(k1, k2);
 500             timeline.play();
 501         }
 502     }
 503 
 504     private EventHandler<ActionEvent> clampAnimationEndEventHandler = new EventHandler<ActionEvent>() {
 505         @Override public void handle(ActionEvent t) {
 506             currentStackPane.setTranslateX(0);
 507             nextStackPane.setTranslateX(0);
 508             nextStackPane.setVisible(false);
 509             timeline = null;
 510         }
 511     };
 512 
 513     /** The size of the gap between number buttons and arrow buttons */
 514     private final DoubleProperty arrowButtonGap = new StyleableDoubleProperty(60.0) {
 515         @Override public Object getBean() {
 516             return PaginationSkin.this;
 517         }
 518         @Override public String getName() {
 519             return "arrowButtonGap";
 520         }
 521         @Override public CssMetaData<Pagination,Number> getCssMetaData() {
 522             return StyleableProperties.ARROW_BUTTON_GAP;
 523         }
 524     };
 525     private DoubleProperty arrowButtonGapProperty() {
 526         return arrowButtonGap;
 527     }
 528 
 529     private BooleanProperty arrowsVisible;
 530     public final void setArrowsVisible(boolean value) { arrowsVisibleProperty().set(value); }
 531     public final boolean isArrowsVisible() { return arrowsVisible == null ? DEFAULT_ARROW_VISIBLE : arrowsVisible.get(); }
 532     public final BooleanProperty arrowsVisibleProperty() {
 533         if (arrowsVisible == null) {
 534             arrowsVisible = new StyleableBooleanProperty(DEFAULT_ARROW_VISIBLE) {
 535                 @Override
 536                 protected void invalidated() {
 537                     getSkinnable().requestLayout();
 538                 }
 539 
 540                 @Override
 541                 public CssMetaData<Pagination,Boolean> getCssMetaData() {
 542                     return StyleableProperties.ARROWS_VISIBLE;
 543                 }
 544 
 545                 @Override
 546                 public Object getBean() {
 547                     return PaginationSkin.this;
 548                 }
 549 
 550                 @Override
 551                 public String getName() {
 552                     return "arrowVisible";
 553                 }
 554             };
 555         }
 556         return arrowsVisible;
 557     }
 558 
 559     private BooleanProperty pageInformationVisible;
 560     public final void setPageInformationVisible(boolean value) { pageInformationVisibleProperty().set(value); }
 561     public final boolean isPageInformationVisible() { return pageInformationVisible == null ? DEFAULT_PAGE_INFORMATION_VISIBLE : pageInformationVisible.get(); }
 562     public final BooleanProperty pageInformationVisibleProperty() {
 563         if (pageInformationVisible == null) {
 564             pageInformationVisible = new StyleableBooleanProperty(DEFAULT_PAGE_INFORMATION_VISIBLE) {
 565                 @Override
 566                 protected void invalidated() {
 567                     getSkinnable().requestLayout();
 568                 }
 569 
 570                 @Override
 571                 public CssMetaData<Pagination,Boolean> getCssMetaData() {
 572                     return StyleableProperties.PAGE_INFORMATION_VISIBLE;
 573                 }
 574 
 575                 @Override
 576                 public Object getBean() {
 577                     return PaginationSkin.this;
 578                 }
 579 
 580                 @Override
 581                 public String getName() {
 582                     return "pageInformationVisible";
 583                 }
 584             };
 585         }
 586         return pageInformationVisible;
 587     }
 588 
 589     private ObjectProperty<Side> pageInformationAlignment;
 590     public final void setPageInformationAlignment(Side value) { pageInformationAlignmentProperty().set(value); }
 591     public final Side getPageInformationAlignment() { return pageInformationAlignment == null ? DEFAULT_PAGE_INFORMATION_ALIGNMENT : pageInformationAlignment.get(); }
 592     public final ObjectProperty<Side> pageInformationAlignmentProperty() {
 593         if (pageInformationAlignment == null) {
 594             pageInformationAlignment = new StyleableObjectProperty<Side>(Side.BOTTOM) {
 595                 @Override
 596                 protected void invalidated() {
 597                     getSkinnable().requestLayout();
 598                 }
 599 
 600                 @Override
 601                 public CssMetaData<Pagination,Side> getCssMetaData() {
 602                     return StyleableProperties.PAGE_INFORMATION_ALIGNMENT;
 603                 }
 604 
 605                 @Override
 606                 public Object getBean() {
 607                     return PaginationSkin.this;
 608                 }
 609 
 610                 @Override
 611                 public String getName() {
 612                     return "pageInformationAlignment";
 613                 }
 614             };
 615         }
 616         return pageInformationAlignment;
 617     }
 618 
 619     private BooleanProperty tooltipVisible;
 620     public final void setTooltipVisible(boolean value) { tooltipVisibleProperty().set(value); }
 621     public final boolean isTooltipVisible() { return tooltipVisible == null ? DEFAULT_TOOLTIP_VISIBLE : tooltipVisible.get(); }
 622     public final BooleanProperty tooltipVisibleProperty() {
 623         if (tooltipVisible == null) {
 624             tooltipVisible = new StyleableBooleanProperty(DEFAULT_TOOLTIP_VISIBLE) {
 625                 @Override
 626                 protected void invalidated() {
 627                     getSkinnable().requestLayout();
 628                 }
 629 
 630                 @Override
 631                 public CssMetaData<Pagination,Boolean> getCssMetaData() {
 632                     return StyleableProperties.TOOLTIP_VISIBLE;
 633                 }
 634 
 635                 @Override
 636                 public Object getBean() {
 637                     return PaginationSkin.this;
 638                 }
 639 
 640                 @Override
 641                 public String getName() {
 642                     return "tooltipVisible";
 643                 }
 644             };
 645         }
 646         return tooltipVisible;
 647     }
 648 
 649     @Override protected void handleControlPropertyChanged(String p) {
 650         super.handleControlPropertyChanged(p);
 651         if ("PAGE_FACTORY".equals(p)) {
 652             if (animate && timeline != null) {
 653                 // If we are in the middle of a page animation.
 654                 // Speedup and finish the animation then update the page factory.
 655                 timeline.setRate(8);
 656                 timeline.setOnFinished(arg0 -> {
 657                     resetIndiciesAndNav();
 658                 });
 659                 return;
 660             }
 661             resetIndiciesAndNav();
 662         } else if ("PAGE_COUNT".equals(p)) {
 663             resetIndiciesAndNav();
 664         } else if ("WIDTH".equals(p)) {
 665             clipRect.setWidth(getSkinnable().getWidth());
 666         } else if ("HEIGHT".equals(p)) {
 667             clipRect.setHeight(getSkinnable().getHeight());
 668         }
 669 
 670         getSkinnable().requestLayout();
 671     }
 672 
 673     @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 674         double navigationWidth = navigation.isVisible() ? snapSize(navigation.minWidth(height)) : 0;
 675         return leftInset + Math.max(currentStackPane.minWidth(height), navigationWidth) + rightInset;
 676     }
 677 
 678     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 679         double navigationHeight = navigation.isVisible() ? snapSize(navigation.minHeight(width)) : 0;
 680         return topInset + currentStackPane.minHeight(width) + navigationHeight + bottomInset;
 681     }
 682 
 683     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 684         double navigationWidth = navigation.isVisible() ? snapSize(navigation.prefWidth(height)) : 0;
 685         return leftInset + Math.max(currentStackPane.prefWidth(height), navigationWidth) + rightInset;
 686     }
 687 
 688     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 689         double navigationHeight = navigation.isVisible() ? snapSize(navigation.prefHeight(width)) : 0;
 690         return topInset + currentStackPane.prefHeight(width) + navigationHeight + bottomInset;
 691     }
 692 
 693     @Override protected void layoutChildren(final double x, final double y,
 694             final double w, final double h) {
 695         double navigationHeight = navigation.isVisible() ? snapSize(navigation.prefHeight(-1)) : 0;
 696         double stackPaneHeight = snapSize(h - navigationHeight);
 697 
 698         layoutInArea(currentStackPane, x, y, w, stackPaneHeight, 0, HPos.CENTER, VPos.CENTER);
 699         layoutInArea(nextStackPane, x, y, w, stackPaneHeight, 0, HPos.CENTER, VPos.CENTER);
 700         layoutInArea(navigation, x, stackPaneHeight, w, navigationHeight, 0, HPos.CENTER, VPos.CENTER);
 701     }
 702 
 703     @Override
 704     protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 705         switch (attribute) {
 706             case FOCUS_ITEM: return navigation.indicatorButtons.getSelectedToggle();
 707             case ITEM_COUNT: return navigation.indicatorButtons.getToggles().size();
 708             case ITEM_AT_INDEX: {
 709                 Integer index = (Integer)parameters[0];
 710                 if (index == null) return null;
 711                 return navigation.indicatorButtons.getToggles().get(index);
 712             }
 713             default: return super.queryAccessibleAttribute(attribute, parameters);
 714         }
 715     }
 716 
 717     class NavigationControl extends StackPane {
 718 
 719         private HBox controlBox;
 720         private Button leftArrowButton;
 721         private StackPane leftArrow;
 722         private Button rightArrowButton;
 723         private StackPane rightArrow;
 724         private ToggleGroup indicatorButtons;
 725         private Label pageInformation;
 726         private double previousWidth = -1;
 727         private double minButtonSize = -1;
 728 
 729         public NavigationControl() {
 730             getStyleClass().setAll("pagination-control");
 731 
 732             // redirect mouse events to behavior
 733             addEventHandler(MouseEvent.MOUSE_PRESSED,  (e) -> getBehavior().mousePressed(e));
 734             addEventHandler(MouseEvent.MOUSE_RELEASED, (e) -> getBehavior().mouseReleased(e));
 735             addEventHandler(MouseEvent.MOUSE_ENTERED,  (e) -> getBehavior().mouseEntered(e));
 736             addEventHandler(MouseEvent.MOUSE_EXITED,   (e) -> getBehavior().mouseExited(e));
 737 
 738             controlBox = new HBox();
 739             controlBox.getStyleClass().add("control-box");
 740 
 741             leftArrowButton = new Button();
 742             leftArrowButton.setAccessibleText(getString("Accessibility.title.Pagination.PreviousButton"));
 743             minButtonSize = leftArrowButton.getFont().getSize() * 2;
 744             leftArrowButton.fontProperty().addListener((arg0, arg1, newFont) -> {
 745                 minButtonSize = newFont.getSize() * 2;
 746                 for(Node child: controlBox.getChildren()) {
 747                     ((Control)child).setMinSize(minButtonSize, minButtonSize);
 748                 }
 749                 // We want to relayout the indicator buttons because the size has changed.
 750                 requestLayout();
 751             });
 752             leftArrowButton.setMinSize(minButtonSize, minButtonSize);
 753             leftArrowButton.prefWidthProperty().bind(leftArrowButton.minWidthProperty());
 754             leftArrowButton.prefHeightProperty().bind(leftArrowButton.minHeightProperty());
 755             leftArrowButton.getStyleClass().add("left-arrow-button");
 756             leftArrowButton.setFocusTraversable(false);


1369                 @Override public StyleableProperty<Number> getStyleableProperty(Pagination n) {
1370                     final PaginationSkin skin = (PaginationSkin) n.getSkin();
1371                     return (StyleableProperty<Number>)(WritableValue<Number>)skin.arrowButtonGapProperty();
1372                 }
1373             };
1374 
1375         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
1376         static {
1377             final List<CssMetaData<? extends Styleable, ?>> styleables =
1378                 new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData());
1379             styleables.add(ARROWS_VISIBLE);
1380             styleables.add(PAGE_INFORMATION_VISIBLE);
1381             styleables.add(PAGE_INFORMATION_ALIGNMENT);
1382             styleables.add(TOOLTIP_VISIBLE);
1383             styleables.add(ARROW_BUTTON_GAP);
1384             STYLEABLES = Collections.unmodifiableList(styleables);
1385         }
1386     }
1387 
1388     /**
1389      * @return The CssMetaData associated with this class, which may include the
1390      * CssMetaData of its super classes.
1391      */
1392     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1393         return StyleableProperties.STYLEABLES;
1394     }
1395 
1396     /**
1397      * {@inheritDoc}
1398      */
1399     @Override
1400     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
1401         return getClassCssMetaData();
1402     }
1403 
1404 }
   1 /*
   2  * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control.skin;
  27 
  28 import com.sun.javafx.scene.control.skin.Utils;
  29 import javafx.beans.property.DoubleProperty;
  30 import javafx.css.StyleableBooleanProperty;
  31 import javafx.css.StyleableDoubleProperty;
  32 import javafx.css.StyleableObjectProperty;
  33 import javafx.css.CssMetaData;
  34 
  35 import javafx.css.converter.BooleanConverter;
  36 import javafx.css.converter.EnumConverter;
  37 import javafx.css.converter.SizeConverter;
  38 import com.sun.javafx.scene.control.behavior.PaginationBehavior;
  39 
  40 import java.util.ArrayList;
  41 import java.util.Collections;
  42 import java.util.List;
  43 
  44 import javafx.animation.*;
  45 import javafx.application.Platform;
  46 import javafx.beans.property.BooleanProperty;
  47 import javafx.beans.property.ObjectProperty;
  48 import javafx.beans.value.ChangeListener;

  49 import javafx.beans.value.WritableValue;
  50 import javafx.collections.ListChangeListener;
  51 import javafx.css.Styleable;
  52 import javafx.css.StyleableProperty;
  53 import javafx.event.ActionEvent;
  54 import javafx.event.EventHandler;
  55 import javafx.geometry.HPos;
  56 import javafx.geometry.Insets;
  57 import javafx.geometry.Pos;
  58 import javafx.geometry.Side;
  59 import javafx.geometry.VPos;
  60 import javafx.scene.AccessibleAction;
  61 import javafx.scene.AccessibleAttribute;
  62 import javafx.scene.AccessibleRole;
  63 import javafx.scene.Node;
  64 import javafx.scene.control.*;
  65 import javafx.scene.input.MouseEvent;
  66 import javafx.scene.input.TouchEvent;
  67 import javafx.scene.layout.HBox;
  68 import javafx.scene.layout.StackPane;
  69 import javafx.scene.shape.Rectangle;
  70 import javafx.util.Duration;
  71 
  72 import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString;
  73 
  74 /**
  75  * Default skin implementation for the {@link Pagination} control.
  76  *
  77  * @see Pagination
  78  * @since 9
  79  */
  80 public class PaginationSkin extends SkinBase<Pagination> {
  81 
  82     /***************************************************************************
  83      *                                                                         *
  84      * Static fields                                                           *
  85      *                                                                         *
  86      **************************************************************************/
  87 
  88     private static final Duration DURATION = new Duration(125.0);
  89     private static final double SWIPE_THRESHOLD = 0.30;
  90     private static final double TOUCH_THRESHOLD = 15;
  91     private static final Interpolator interpolator = Interpolator.SPLINE(0.4829, 0.5709, 0.6803, 0.9928);
  92 
  93 
  94 
  95     /***************************************************************************
  96      *                                                                         *
  97      * Private fields                                                          *
  98      *                                                                         *
  99      **************************************************************************/
 100 
 101     private Pagination pagination;
 102     private StackPane currentStackPane;
 103     private StackPane nextStackPane;
 104     private Timeline timeline;
 105     private Rectangle clipRect;
 106 
 107     private NavigationControl navigation;
 108     private int fromIndex;
 109     private int previousIndex;
 110     private int currentIndex;
 111     private int toIndex;
 112     private int pageCount;
 113     private int maxPageIndicatorCount;
 114 
 115     private double startTouchPos;
 116     private double lastTouchPos;
 117     private long startTouchTime;
 118     private long lastTouchTime;
 119     private double touchVelocity;
 120     private boolean touchThresholdBroken;
 121     private int touchEventId = -1;
 122     private boolean nextPageReached = false;
 123     private boolean setInitialDirection = false;
 124     private int direction;
 125 
 126     private int currentAnimatedIndex;
 127     private boolean hasPendingAnimation = false;
 128 
 129     private boolean animate = true;
 130 
 131     private final PaginationBehavior behavior;
 132 
 133 
 134 
 135     /***************************************************************************
 136      *                                                                         *
 137      * Listeners                                                               *
 138      *                                                                         *
 139      **************************************************************************/
 140 
 141     private EventHandler<ActionEvent> swipeAnimationEndEventHandler = new EventHandler<ActionEvent>() {
 142         @Override public void handle(ActionEvent t) {
 143             swapPanes();
 144             timeline = null;
 145 
 146             if (hasPendingAnimation) {
 147                 animateSwitchPage();
 148                 hasPendingAnimation = false;
 149             }
 150         }
 151     };
 152 
 153     private EventHandler<ActionEvent> clampAnimationEndEventHandler = new EventHandler<ActionEvent>() {
 154         @Override public void handle(ActionEvent t) {
 155             currentStackPane.setTranslateX(0);
 156             nextStackPane.setTranslateX(0);
 157             nextStackPane.setVisible(false);
 158             timeline = null;
 159         }
 160     };
 161 
 162 
 163 
 164     /***************************************************************************
 165      *                                                                         *
 166      * Constructors                                                            *
 167      *                                                                         *
 168      **************************************************************************/
 169 
 170     /**
 171      * Creates a new PaginationSkin instance, installing the necessary child
 172      * nodes into the Control {@link Control#getChildren() children} list, as
 173      * well as the necessary input mappings for handling key, mouse, etc events.
 174      *
 175      * @param control The control that this skin should be installed onto.
 176      */
 177     public PaginationSkin(final Pagination control) {
 178         super(control);
 179 
 180         // install default input map for the Pagination control
 181         behavior = new PaginationBehavior(control);
 182 //        control.setInputMap(behavior.getInputMap());
 183 
 184 //        setManaged(false);
 185         clipRect = new Rectangle();
 186         getSkinnable().setClip(clipRect);
 187 
 188         this.pagination = control;
 189 
 190         this.currentStackPane = new StackPane();
 191         currentStackPane.getStyleClass().add("page");
 192 
 193         this.nextStackPane = new StackPane();
 194         nextStackPane.getStyleClass().add("page");
 195         nextStackPane.setVisible(false);
 196 
 197         resetIndexes(true);
 198 
 199         this.navigation = new NavigationControl();
 200 
 201         getChildren().addAll(currentStackPane, nextStackPane, navigation);
 202 
 203         control.maxPageIndicatorCountProperty().addListener(o -> {
 204             resetIndiciesAndNav();
 205         });
 206 
 207         registerChangeListener(control.widthProperty(), e -> clipRect.setWidth(getSkinnable().getWidth()));
 208         registerChangeListener(control.heightProperty(), e -> clipRect.setHeight(getSkinnable().getHeight()));
 209         registerChangeListener(control.pageCountProperty(), e -> resetIndiciesAndNav());
 210         registerChangeListener(control.pageFactoryProperty(), e -> {
 211             if (animate && timeline != null) {
 212                 // If we are in the middle of a page animation.
 213                 // Speedup and finish the animation then update the page factory.
 214                 timeline.setRate(8);
 215                 timeline.setOnFinished(arg0 -> {
 216                     resetIndiciesAndNav();
 217                 });
 218                 return;
 219             }
 220             resetIndiciesAndNav();
 221         });
 222 
 223         initializeSwipeAndTouchHandlers();
 224     }
 225 
 226 
 227 
 228     /***************************************************************************
 229      *                                                                         *
 230      * Properties                                                              *
 231      *                                                                         *
 232      **************************************************************************/
 233 
 234     /** The size of the gap between number buttons and arrow buttons */
 235     private final DoubleProperty arrowButtonGap = new StyleableDoubleProperty(60.0) {
 236         @Override public Object getBean() {
 237             return PaginationSkin.this;
 238         }
 239         @Override public String getName() {
 240             return "arrowButtonGap";
 241         }
 242         @Override public CssMetaData<Pagination,Number> getCssMetaData() {
 243             return StyleableProperties.ARROW_BUTTON_GAP;
 244         }
 245     };
 246     private final DoubleProperty arrowButtonGapProperty() {
 247         return arrowButtonGap;
 248     }
 249     private final double getArrowButtonGap() {
 250         return arrowButtonGap.get();
 251     }
 252     private final void setArrowButtonGap(double value) {
 253         arrowButtonGap.set(value);
 254     }
 255 
 256     private BooleanProperty arrowsVisible;
 257     private final void setArrowsVisible(boolean value) { arrowsVisibleProperty().set(value); }
 258     private final boolean isArrowsVisible() { return arrowsVisible == null ? DEFAULT_ARROW_VISIBLE : arrowsVisible.get(); }
 259     private final BooleanProperty arrowsVisibleProperty() {
 260         if (arrowsVisible == null) {
 261             arrowsVisible = new StyleableBooleanProperty(DEFAULT_ARROW_VISIBLE) {
 262                 @Override
 263                 protected void invalidated() {
 264                     getSkinnable().requestLayout();
 265                 }
 266 
 267                 @Override
 268                 public CssMetaData<Pagination,Boolean> getCssMetaData() {
 269                     return StyleableProperties.ARROWS_VISIBLE;
 270                 }
 271 
 272                 @Override
 273                 public Object getBean() {
 274                     return PaginationSkin.this;
 275                 }
 276 
 277                 @Override
 278                 public String getName() {
 279                     return "arrowVisible";
 280                 }
 281             };
 282         }
 283         return arrowsVisible;
 284     }
 285 
 286     private BooleanProperty pageInformationVisible;
 287     private final void setPageInformationVisible(boolean value) { pageInformationVisibleProperty().set(value); }
 288     private final boolean isPageInformationVisible() { return pageInformationVisible == null ? DEFAULT_PAGE_INFORMATION_VISIBLE : pageInformationVisible.get(); }
 289     private final BooleanProperty pageInformationVisibleProperty() {
 290         if (pageInformationVisible == null) {
 291             pageInformationVisible = new StyleableBooleanProperty(DEFAULT_PAGE_INFORMATION_VISIBLE) {
 292                 @Override
 293                 protected void invalidated() {
 294                     getSkinnable().requestLayout();
 295                 }
 296 
 297                 @Override
 298                 public CssMetaData<Pagination,Boolean> getCssMetaData() {
 299                     return StyleableProperties.PAGE_INFORMATION_VISIBLE;
 300                 }
 301 
 302                 @Override
 303                 public Object getBean() {
 304                     return PaginationSkin.this;
 305                 }
 306 
 307                 @Override
 308                 public String getName() {
 309                     return "pageInformationVisible";
 310                 }
 311             };
 312         }
 313         return pageInformationVisible;
 314     }
 315 
 316     private ObjectProperty<Side> pageInformationAlignment;
 317     private final void setPageInformationAlignment(Side value) { pageInformationAlignmentProperty().set(value); }
 318     private final Side getPageInformationAlignment() { return pageInformationAlignment == null ? DEFAULT_PAGE_INFORMATION_ALIGNMENT : pageInformationAlignment.get(); }
 319     private final ObjectProperty<Side> pageInformationAlignmentProperty() {
 320         if (pageInformationAlignment == null) {
 321             pageInformationAlignment = new StyleableObjectProperty<Side>(Side.BOTTOM) {
 322                 @Override
 323                 protected void invalidated() {
 324                     getSkinnable().requestLayout();
 325                 }
 326 
 327                 @Override
 328                 public CssMetaData<Pagination,Side> getCssMetaData() {
 329                     return StyleableProperties.PAGE_INFORMATION_ALIGNMENT;
 330                 }
 331 
 332                 @Override
 333                 public Object getBean() {
 334                     return PaginationSkin.this;
 335                 }
 336 
 337                 @Override
 338                 public String getName() {
 339                     return "pageInformationAlignment";
 340                 }
 341             };
 342         }
 343         return pageInformationAlignment;
 344     }
 345 
 346     private BooleanProperty tooltipVisible;
 347     private final void setTooltipVisible(boolean value) { tooltipVisibleProperty().set(value); }
 348     private final boolean isTooltipVisible() { return tooltipVisible == null ? DEFAULT_TOOLTIP_VISIBLE : tooltipVisible.get(); }
 349     private final BooleanProperty tooltipVisibleProperty() {
 350         if (tooltipVisible == null) {
 351             tooltipVisible = new StyleableBooleanProperty(DEFAULT_TOOLTIP_VISIBLE) {
 352                 @Override
 353                 protected void invalidated() {
 354                     getSkinnable().requestLayout();
 355                 }
 356 
 357                 @Override
 358                 public CssMetaData<Pagination,Boolean> getCssMetaData() {
 359                     return StyleableProperties.TOOLTIP_VISIBLE;
 360                 }
 361 
 362                 @Override
 363                 public Object getBean() {
 364                     return PaginationSkin.this;
 365                 }
 366 
 367                 @Override
 368                 public String getName() {
 369                     return "tooltipVisible";
 370                 }
 371             };
 372         }
 373         return tooltipVisible;
 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     /** {@inheritDoc} */
 394     @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 395         double navigationWidth = navigation.isVisible() ? snapSize(navigation.minWidth(height)) : 0;
 396         return leftInset + Math.max(currentStackPane.minWidth(height), navigationWidth) + rightInset;
 397     }
 398 
 399     /** {@inheritDoc} */
 400     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 401         double navigationHeight = navigation.isVisible() ? snapSize(navigation.minHeight(width)) : 0;
 402         return topInset + currentStackPane.minHeight(width) + navigationHeight + bottomInset;
 403     }
 404 
 405     /** {@inheritDoc} */
 406     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 407         double navigationWidth = navigation.isVisible() ? snapSize(navigation.prefWidth(height)) : 0;
 408         return leftInset + Math.max(currentStackPane.prefWidth(height), navigationWidth) + rightInset;
 409     }
 410 
 411     /** {@inheritDoc} */
 412     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 413         double navigationHeight = navigation.isVisible() ? snapSize(navigation.prefHeight(width)) : 0;
 414         return topInset + currentStackPane.prefHeight(width) + navigationHeight + bottomInset;
 415     }
 416 
 417     /** {@inheritDoc} */
 418     @Override protected void layoutChildren(final double x, final double y,
 419                                             final double w, final double h) {
 420         double navigationHeight = navigation.isVisible() ? snapSize(navigation.prefHeight(-1)) : 0;
 421         double stackPaneHeight = snapSize(h - navigationHeight);
 422 
 423         layoutInArea(currentStackPane, x, y, w, stackPaneHeight, 0, HPos.CENTER, VPos.CENTER);
 424         layoutInArea(nextStackPane, x, y, w, stackPaneHeight, 0, HPos.CENTER, VPos.CENTER);
 425         layoutInArea(navigation, x, stackPaneHeight, w, navigationHeight, 0, HPos.CENTER, VPos.CENTER);
 426     }
 427 
 428     /** {@inheritDoc} */
 429     @Override protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 430         switch (attribute) {
 431             case FOCUS_ITEM: return navigation.indicatorButtons.getSelectedToggle();
 432             case ITEM_COUNT: return navigation.indicatorButtons.getToggles().size();
 433             case ITEM_AT_INDEX: {
 434                 Integer index = (Integer)parameters[0];
 435                 if (index == null) return null;
 436                 return navigation.indicatorButtons.getToggles().get(index);
 437             }
 438             default: return super.queryAccessibleAttribute(attribute, parameters);
 439         }
 440     }
 441 
 442 
 443 
 444     /***************************************************************************
 445      *                                                                         *
 446      * Private implementation                                                  *
 447      *                                                                         *
 448      **************************************************************************/
 449 
 450     private void selectNext() {
 451         if (getCurrentPageIndex() < getPageCount() - 1) {
 452             pagination.setCurrentPageIndex(getCurrentPageIndex() + 1);
 453         }
 454     }
 455 
 456     private void selectPrevious() {
 457         if (getCurrentPageIndex() > 0) {
 458             pagination.setCurrentPageIndex(getCurrentPageIndex() - 1);
 459         }
 460     }
 461 
 462     private void resetIndiciesAndNav() {
 463         resetIndexes(false);
 464         navigation.initializePageIndicators();
 465         navigation.updatePageIndicators();
 466     }





 467 
 468     private void initializeSwipeAndTouchHandlers() {
 469         final Pagination control = getSkinnable();
 470 
 471         getSkinnable().addEventHandler(TouchEvent.TOUCH_PRESSED, e -> {
 472             if (touchEventId == -1) {
 473                 touchEventId = e.getTouchPoint().getId();
 474             }
 475             if (touchEventId != e.getTouchPoint().getId()) {
 476                 return;
 477             }
 478             lastTouchPos = startTouchPos = e.getTouchPoint().getX();
 479             lastTouchTime = startTouchTime = System.currentTimeMillis();
 480             touchThresholdBroken = false;
 481             e.consume();
 482         });
 483 
 484         getSkinnable().addEventHandler(TouchEvent.TOUCH_MOVED, e -> {
 485             if (touchEventId != e.getTouchPoint().getId()) {
 486                 return;


 663             }
 664         }
 665         return false;
 666     }
 667 
 668     private int getPageCount() {
 669         if (getSkinnable().getPageCount() < 1) {
 670             return 1;
 671         }
 672         return getSkinnable().getPageCount();
 673     }
 674 
 675     private int getMaxPageIndicatorCount() {
 676         return getSkinnable().getMaxPageIndicatorCount();
 677     }
 678 
 679     private int getCurrentPageIndex() {
 680         return getSkinnable().getCurrentPageIndex();
 681     }
 682 




 683     private void animateSwitchPage() {
 684         if (timeline != null) {
 685             timeline.setRate(8);
 686             hasPendingAnimation = true;
 687             return;
 688         }
 689 
 690         // We are handling a touch event if nextPane's page has already been
 691         // created and visible == true.
 692         if (!nextStackPane.isVisible()) {
 693             if (!createPage(nextStackPane, currentAnimatedIndex)) {
 694                 // The next page does not exist just return without starting
 695                 // any animation.
 696                 return;
 697             }
 698         }
 699         if (nextPageReached) {
 700             // No animation is needed when the next page is already showing
 701             // and in the correct position.  Just swap the panes and return
 702             swapPanes();


 736                 }
 737                 nextStackPane.setVisible(true);
 738                 timeline = new Timeline();
 739                 KeyFrame k1 = new KeyFrame(Duration.millis(0),
 740                     new KeyValue(currentStackPane.translateXProperty(),
 741                         useTranslateX ? currentStackPane.getTranslateX() : 0,
 742                         interpolator),
 743                     new KeyValue(nextStackPane.translateXProperty(),
 744                         useTranslateX ? nextStackPane.getTranslateX() : -currentStackPane.getWidth(),
 745                         interpolator));
 746                 KeyFrame k2 = new KeyFrame(DURATION,
 747                     swipeAnimationEndEventHandler,
 748                     new KeyValue(currentStackPane.translateXProperty(), currentStackPane.getWidth(), interpolator),
 749                     new KeyValue(nextStackPane.translateXProperty(), 0, interpolator));
 750                 timeline.getKeyFrames().setAll(k1, k2);
 751                 timeline.play();
 752             }
 753         });
 754     }
 755 












 756     private void swapPanes() {
 757         StackPane temp = currentStackPane;
 758         currentStackPane = nextStackPane;
 759         nextStackPane = temp;
 760 
 761         currentStackPane.setTranslateX(0);
 762         currentStackPane.setCache(false);
 763 
 764         nextStackPane.setTranslateX(0);
 765         nextStackPane.setCache(false);
 766         nextStackPane.setVisible(false);
 767         nextStackPane.getChildren().clear();
 768     }
 769 
 770     // If the swipe hasn't reached the THRESHOLD we want to animate the clamping.
 771     private void animateClamping(boolean rightToLeft) {
 772         if (rightToLeft) {  // animate right to left
 773             timeline = new Timeline();
 774             KeyFrame k1 = new KeyFrame(Duration.millis(0),
 775                 new KeyValue(currentStackPane.translateXProperty(), currentStackPane.getTranslateX(), interpolator),


 777             KeyFrame k2 = new KeyFrame(DURATION,
 778                 clampAnimationEndEventHandler,
 779                 new KeyValue(currentStackPane.translateXProperty(), 0, interpolator),
 780                 new KeyValue(nextStackPane.translateXProperty(), currentStackPane.getWidth(), interpolator));
 781             timeline.getKeyFrames().setAll(k1, k2);
 782             timeline.play();
 783         } else { // animate left to right
 784             timeline = new Timeline();
 785             KeyFrame k1 = new KeyFrame(Duration.millis(0),
 786                 new KeyValue(currentStackPane.translateXProperty(), currentStackPane.getTranslateX(), interpolator),
 787                 new KeyValue(nextStackPane.translateXProperty(), nextStackPane.getTranslateX(), interpolator));
 788             KeyFrame k2 = new KeyFrame(DURATION,
 789                 clampAnimationEndEventHandler,
 790                 new KeyValue(currentStackPane.translateXProperty(), 0, interpolator),
 791                 new KeyValue(nextStackPane.translateXProperty(), -currentStackPane.getWidth(), interpolator));
 792             timeline.getKeyFrames().setAll(k1, k2);
 793             timeline.play();
 794         }
 795     }
 796 






















































 797 















 798 
 799     /***************************************************************************
 800      *                                                                         *
 801      * Support classes                                                         *
 802      *                                                                         *
 803      **************************************************************************/








































































































































 804 
 805     class NavigationControl extends StackPane {
 806 
 807         private HBox controlBox;
 808         private Button leftArrowButton;
 809         private StackPane leftArrow;
 810         private Button rightArrowButton;
 811         private StackPane rightArrow;
 812         private ToggleGroup indicatorButtons;
 813         private Label pageInformation;
 814         private double previousWidth = -1;
 815         private double minButtonSize = -1;
 816 
 817         public NavigationControl() {
 818             getStyleClass().setAll("pagination-control");
 819 
 820             // redirect mouse events to behavior
 821             addEventHandler(MouseEvent.MOUSE_PRESSED, behavior::mousePressed);



 822 
 823             controlBox = new HBox();
 824             controlBox.getStyleClass().add("control-box");
 825 
 826             leftArrowButton = new Button();
 827             leftArrowButton.setAccessibleText(getString("Accessibility.title.Pagination.PreviousButton"));
 828             minButtonSize = leftArrowButton.getFont().getSize() * 2;
 829             leftArrowButton.fontProperty().addListener((arg0, arg1, newFont) -> {
 830                 minButtonSize = newFont.getSize() * 2;
 831                 for(Node child: controlBox.getChildren()) {
 832                     ((Control)child).setMinSize(minButtonSize, minButtonSize);
 833                 }
 834                 // We want to relayout the indicator buttons because the size has changed.
 835                 requestLayout();
 836             });
 837             leftArrowButton.setMinSize(minButtonSize, minButtonSize);
 838             leftArrowButton.prefWidthProperty().bind(leftArrowButton.minWidthProperty());
 839             leftArrowButton.prefHeightProperty().bind(leftArrowButton.minHeightProperty());
 840             leftArrowButton.getStyleClass().add("left-arrow-button");
 841             leftArrowButton.setFocusTraversable(false);


1454                 @Override public StyleableProperty<Number> getStyleableProperty(Pagination n) {
1455                     final PaginationSkin skin = (PaginationSkin) n.getSkin();
1456                     return (StyleableProperty<Number>)(WritableValue<Number>)skin.arrowButtonGapProperty();
1457                 }
1458             };
1459 
1460         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
1461         static {
1462             final List<CssMetaData<? extends Styleable, ?>> styleables =
1463                 new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData());
1464             styleables.add(ARROWS_VISIBLE);
1465             styleables.add(PAGE_INFORMATION_VISIBLE);
1466             styleables.add(PAGE_INFORMATION_ALIGNMENT);
1467             styleables.add(TOOLTIP_VISIBLE);
1468             styleables.add(ARROW_BUTTON_GAP);
1469             STYLEABLES = Collections.unmodifiableList(styleables);
1470         }
1471     }
1472 
1473     /**
1474      * Returns the CssMetaData associated with this class, which may include the
1475      * CssMetaData of its super classes.
1476      */
1477     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1478         return StyleableProperties.STYLEABLES;
1479     }
1480 
1481     /**
1482      * {@inheritDoc}
1483      */
1484     @Override
1485     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
1486         return getClassCssMetaData();
1487     }
1488 
1489 }