--- old/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ScrollBarSkin.java 2015-09-03 15:21:12.131756400 -0700 +++ /dev/null 2015-09-03 15:21:13.000000000 -0700 @@ -1,641 +0,0 @@ -/* - * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.sun.javafx.scene.control.skin; - -import javafx.geometry.Orientation; -import javafx.geometry.Point2D; -import javafx.scene.AccessibleAction; -import javafx.scene.AccessibleAttribute; -import javafx.scene.AccessibleRole; -import javafx.scene.control.ScrollBar; -import javafx.scene.input.MouseButton; -import javafx.scene.input.ScrollEvent; -import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; -import javafx.scene.Node; -import com.sun.javafx.util.Utils; -import com.sun.javafx.scene.control.behavior.ScrollBarBehavior; - -public class ScrollBarSkin extends BehaviorSkinBase { - - /*************************************************************************** - * * - * UI Subcomponents * - * * - **************************************************************************/ - - public final static int DEFAULT_LENGTH = 100; - public final static int DEFAULT_WIDTH = 20; - - private StackPane thumb; - private StackPane trackBackground; - private StackPane track; - private EndButton incButton; - private EndButton decButton; - - private double trackLength; - private double thumbLength; - - private double preDragThumbPos; - private Point2D dragStart; // in the track's coord system - - private double trackPos; - - /*************************************************************************** - * * - * Constructors * - * * - **************************************************************************/ - - public ScrollBarSkin(ScrollBar scrollbar) { - super(scrollbar, new ScrollBarBehavior(scrollbar)); - initialize(); - getSkinnable().requestLayout(); - // Register listeners - registerChangeListener(scrollbar.minProperty(), "MIN"); - registerChangeListener(scrollbar.maxProperty(), "MAX"); - registerChangeListener(scrollbar.valueProperty(), "VALUE"); - registerChangeListener(scrollbar.orientationProperty(), "ORIENTATION"); - registerChangeListener(scrollbar.visibleAmountProperty(), "VISIBLE_AMOUNT"); - } - - /** - * Initializes the ScrollBarSkin. Creates the scene and sets up all the - * bindings for the group. - */ - private void initialize() { - - track = new StackPane(); - track.getStyleClass().setAll("track"); - - trackBackground = new StackPane(); - trackBackground.getStyleClass().setAll("track-background"); - - thumb = new StackPane() { - @Override - public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { - switch (attribute) { - case VALUE: return getSkinnable().getValue(); - default: return super.queryAccessibleAttribute(attribute, parameters); - } - } - }; - thumb.getStyleClass().setAll("thumb"); - thumb.setAccessibleRole(AccessibleRole.THUMB); - - - if (!IS_TOUCH_SUPPORTED) { - - incButton = new EndButton("increment-button", "increment-arrow") { - @Override - public void executeAccessibleAction(AccessibleAction action, Object... parameters) { - switch (action) { - case FIRE: - getSkinnable().increment(); - break; - default: super.executeAccessibleAction(action, parameters); - } - } - }; - incButton.setAccessibleRole(AccessibleRole.INCREMENT_BUTTON); - incButton.setOnMousePressed(me -> { - /* - ** if the tracklenght isn't greater than do nothing.... - */ - if (!thumb.isVisible() || trackLength > thumbLength) { - getBehavior().incButtonPressed(); - } - me.consume(); - }); - incButton.setOnMouseReleased(me -> { - /* - ** if the tracklenght isn't greater than do nothing.... - */ - if (!thumb.isVisible() || trackLength > thumbLength) { - getBehavior().incButtonReleased(); - } - me.consume(); - }); - - decButton = new EndButton("decrement-button", "decrement-arrow") { - @Override - public void executeAccessibleAction(AccessibleAction action, Object... parameters) { - switch (action) { - case FIRE: - getSkinnable().decrement(); - break; - default: super.executeAccessibleAction(action, parameters); - } - } - }; - decButton.setAccessibleRole(AccessibleRole.DECREMENT_BUTTON); - decButton.setOnMousePressed(me -> { - /* - ** if the tracklenght isn't greater than do nothing.... - */ - if (!thumb.isVisible() || trackLength > thumbLength) { - getBehavior().decButtonPressed(); - } - me.consume(); - }); - decButton.setOnMouseReleased(me -> { - /* - ** if the tracklenght isn't greater than do nothing.... - */ - if (!thumb.isVisible() || trackLength > thumbLength) { - getBehavior().decButtonReleased(); - } - me.consume(); - }); - } - - - track.setOnMousePressed(me -> { - if (!thumb.isPressed() && me.getButton() == MouseButton.PRIMARY) { - if (getSkinnable().getOrientation() == Orientation.VERTICAL) { - if (trackLength != 0) { - getBehavior().trackPress(me.getY() / trackLength); - me.consume(); - } - } else { - if (trackLength != 0) { - getBehavior().trackPress(me.getX() / trackLength); - me.consume(); - } - } - } - }); - - track.setOnMouseReleased(me -> { - getBehavior().trackRelease(); - me.consume(); - }); - - thumb.setOnMousePressed(me -> { - if (me.isSynthesized()) { - // touch-screen events handled by Scroll handler - me.consume(); - return; - } - /* - ** if max isn't greater than min then there is nothing to do here - */ - if (getSkinnable().getMax() > getSkinnable().getMin()) { - dragStart = thumb.localToParent(me.getX(), me.getY()); - double clampedValue = Utils.clamp(getSkinnable().getMin(), getSkinnable().getValue(), getSkinnable().getMax()); - preDragThumbPos = (clampedValue - getSkinnable().getMin()) / (getSkinnable().getMax() - getSkinnable().getMin()); - me.consume(); - } - }); - - - thumb.setOnMouseDragged(me -> { - if (me.isSynthesized()) { - // touch-screen events handled by Scroll handler - me.consume(); - return; - } - /* - ** if max isn't greater than min then there is nothing to do here - */ - if (getSkinnable().getMax() > getSkinnable().getMin()) { - /* - ** if the tracklength isn't greater then do nothing.... - */ - if (trackLength > thumbLength) { - Point2D cur = thumb.localToParent(me.getX(), me.getY()); - if (dragStart == null) { - // we're getting dragged without getting a mouse press - dragStart = thumb.localToParent(me.getX(), me.getY()); - } - double dragPos = getSkinnable().getOrientation() == Orientation.VERTICAL ? cur.getY() - dragStart.getY(): cur.getX() - dragStart.getX(); - getBehavior().thumbDragged(preDragThumbPos + dragPos / (trackLength - thumbLength)); - } - - me.consume(); - } - }); - - thumb.setOnScrollStarted(se -> { - if (se.isDirect()) { - /* - ** if max isn't greater than min then there is nothing to do here - */ - if (getSkinnable().getMax() > getSkinnable().getMin()) { - dragStart = thumb.localToParent(se.getX(), se.getY()); - double clampedValue = Utils.clamp(getSkinnable().getMin(), getSkinnable().getValue(), getSkinnable().getMax()); - preDragThumbPos = (clampedValue - getSkinnable().getMin()) / (getSkinnable().getMax() - getSkinnable().getMin()); - se.consume(); - } - } - }); - - thumb.setOnScroll(event -> { - if (event.isDirect()) { - /* - ** if max isn't greater than min then there is nothing to do here - */ - if (getSkinnable().getMax() > getSkinnable().getMin()) { - /* - ** if the tracklength isn't greater then do nothing.... - */ - if (trackLength > thumbLength) { - Point2D cur = thumb.localToParent(event.getX(), event.getY()); - if (dragStart == null) { - // we're getting dragged without getting a mouse press - dragStart = thumb.localToParent(event.getX(), event.getY()); - } - double dragPos = getSkinnable().getOrientation() == Orientation.VERTICAL ? cur.getY() - dragStart.getY(): cur.getX() - dragStart.getX(); - getBehavior().thumbDragged(/*todo*/ preDragThumbPos + dragPos / (trackLength - thumbLength)); - } - - event.consume(); - return; - } - } - }); - - - getSkinnable().addEventHandler(ScrollEvent.SCROLL, event -> { - /* - ** if the tracklength isn't greater then do nothing.... - */ - if (trackLength > thumbLength) { - - double dx = event.getDeltaX(); - double dy = event.getDeltaY(); - - /* - ** in 2.0 a horizontal scrollbar would scroll on a vertical - ** drag on a tracker-pad. We need to keep this behavior. - */ - dx = (Math.abs(dx) < Math.abs(dy) ? dy : dx); - - /* - ** we only consume an event that we've used. - */ - ScrollBar sb = (ScrollBar) getSkinnable(); - - double delta = (getSkinnable().getOrientation() == Orientation.VERTICAL ? dy : dx); - - /* - ** RT-22941 - If this is either a touch or inertia scroll - ** then we move to the position of the touch point. - * - * TODO: this fix causes RT-23406 ([ScrollBar, touch] Dragging scrollbar from the - * track on touchscreen causes flickering) - */ - if (event.isDirect()) { - if (trackLength > thumbLength) { - getBehavior().thumbDragged((getSkinnable().getOrientation() == Orientation.VERTICAL ? event.getY(): event.getX()) / trackLength); - event.consume(); - } - } - else { - if (delta > 0.0 && sb.getValue() > sb.getMin()) { - sb.decrement(); - event.consume(); - } else if (delta < 0.0 && sb.getValue() < sb.getMax()) { - sb.increment(); - event.consume(); - } - } - } - }); - - getChildren().clear(); - if (!IS_TOUCH_SUPPORTED) { - getChildren().addAll(trackBackground, incButton, decButton, track, thumb); - } - else { - getChildren().addAll(track, thumb); - } - } - - - - @Override protected void handleControlPropertyChanged(String p) { - super.handleControlPropertyChanged(p); - if ("ORIENTATION".equals(p)) { - getSkinnable().requestLayout(); - } else if ("MIN".equals(p) || "MAX".equals(p) || "VISIBLE_AMOUNT".equals(p)) { - positionThumb(); - getSkinnable().requestLayout(); - } else if ("VALUE".equals(p)) { - positionThumb(); - } - } - - /*************************************************************************** - * * - * Layout * - * * - **************************************************************************/ - - private static final double DEFAULT_EMBEDDED_SB_BREADTH = 8.0; - - /* - * Gets the breadth of the scrollbar. The "breadth" is the distance - * across the scrollbar, i.e. if vertical the width, otherwise the height. - * On desktop this is determined by the greater of the breadths of the end-buttons. - * Embedded doesn't have end-buttons, so currently we use a default breadth. - * We should change this when we get width/height css properties. - */ - double getBreadth() { - if (!IS_TOUCH_SUPPORTED) { - if (getSkinnable().getOrientation() == Orientation.VERTICAL) { - return Math.max(decButton.prefWidth(-1), incButton.prefWidth(-1)) +snappedLeftInset()+snappedRightInset(); - } else { - return Math.max(decButton.prefHeight(-1), incButton.prefHeight(-1)) +snappedTopInset()+snappedBottomInset(); - } - } - else { - if (getSkinnable().getOrientation() == Orientation.VERTICAL) { - return Math.max(DEFAULT_EMBEDDED_SB_BREADTH, DEFAULT_EMBEDDED_SB_BREADTH)+snappedLeftInset()+snappedRightInset(); - } else { - return Math.max(DEFAULT_EMBEDDED_SB_BREADTH, DEFAULT_EMBEDDED_SB_BREADTH)+snappedTopInset()+snappedBottomInset(); - } - } - } - - double minThumbLength() { - return 1.5f * getBreadth(); - } - - double minTrackLength() { - return 2.0f * getBreadth(); - } - - /* - * Minimum length is the length of the end buttons plus twice the - * minimum thumb length, which should be enough for a reasonably-sized - * track. Minimum breadth is determined by the breadths of the - * end buttons. - */ - @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { - if (getSkinnable().getOrientation() == Orientation.VERTICAL) { - return getBreadth(); - } else { - if (!IS_TOUCH_SUPPORTED) { - return decButton.minWidth(-1) + incButton.minWidth(-1) + minTrackLength()+leftInset+rightInset; - } else { - return minTrackLength()+leftInset+rightInset; - } - } - } - - @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { - if (getSkinnable().getOrientation() == Orientation.VERTICAL) { - if (!IS_TOUCH_SUPPORTED) { - return decButton.minHeight(-1) + incButton.minHeight(-1) + minTrackLength()+topInset+bottomInset; - } else { - return minTrackLength()+topInset+bottomInset; - } - } else { - return getBreadth(); - } - } - - /* - * Preferred size. The breadth is determined by the breadth of - * the end buttons. The length is a constant default length. - * Usually applications or other components will either set a - * specific length using LayoutInfo or will stretch the length - * of the scrollbar to fit a container. - */ - @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { - final ScrollBar s = getSkinnable(); - return s.getOrientation() == Orientation.VERTICAL ? getBreadth() : DEFAULT_LENGTH+leftInset+rightInset; - } - - @Override protected double computePrefHeight(double height, double topInset, double rightInset, double bottomInset, double leftInset) { - final ScrollBar s = getSkinnable(); - return s.getOrientation() == Orientation.VERTICAL ? DEFAULT_LENGTH+topInset+bottomInset : getBreadth(); - } - - @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { - final ScrollBar s = getSkinnable(); - return s.getOrientation() == Orientation.VERTICAL ? s.prefWidth(-1) : Double.MAX_VALUE; - } - - @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { - final ScrollBar s = getSkinnable(); - return s.getOrientation() == Orientation.VERTICAL ? Double.MAX_VALUE : s.prefHeight(-1); - } - - /** - * Called when ever either min, max or value changes, so thumb's layoutX, Y is recomputed. - */ - void positionThumb() { - ScrollBar s = getSkinnable(); - double clampedValue = Utils.clamp(s.getMin(), s.getValue(), s.getMax()); - trackPos = (s.getMax() - s.getMin() > 0) ? ((trackLength - thumbLength) * (clampedValue - s.getMin()) / (s.getMax() - s.getMin())) : (0.0F); - - if (!IS_TOUCH_SUPPORTED) { - if (s.getOrientation() == Orientation.VERTICAL) { - trackPos += decButton.prefHeight(-1); - } else { - trackPos += decButton.prefWidth(-1); - } - } - - thumb.setTranslateX( snapPosition(s.getOrientation() == Orientation.VERTICAL ? snappedLeftInset() : trackPos + snappedLeftInset())); - thumb.setTranslateY( snapPosition(s.getOrientation() == Orientation.VERTICAL ? trackPos + snappedTopInset() : snappedTopInset())); - } - - @Override protected void layoutChildren(final double x, final double y, - final double w, final double h) { - - final ScrollBar s = getSkinnable(); - - /** - * Compute the percentage length of thumb as (visibleAmount/range) - * if max isn't greater than min then there is nothing to do here - */ - double visiblePortion; - if (s.getMax() > s.getMin()) { - visiblePortion = s.getVisibleAmount()/(s.getMax() - s.getMin()); - } - else { - visiblePortion = 1.0; - } - - if (s.getOrientation() == Orientation.VERTICAL) { - if (!IS_TOUCH_SUPPORTED) { - double decHeight = snapSize(decButton.prefHeight(-1)); - double incHeight = snapSize(incButton.prefHeight(-1)); - - decButton.resize(w, decHeight); - incButton.resize(w, incHeight); - - trackLength = snapSize(h - (decHeight + incHeight)); - thumbLength = snapSize(Utils.clamp(minThumbLength(), (trackLength * visiblePortion), trackLength)); - - trackBackground.resizeRelocate(snapPosition(x), snapPosition(y), w, trackLength+decHeight+incHeight); - decButton.relocate(snapPosition(x), snapPosition(y)); - incButton.relocate(snapPosition(x), snapPosition(y + h - incHeight)); - track.resizeRelocate(snapPosition(x), snapPosition(y + decHeight), w, trackLength); - thumb.resize(snapSize(x >= 0 ? w : w + x), thumbLength); // Account for negative padding (see also RT-10719) - positionThumb(); - } - else { - trackLength = snapSize(h); - thumbLength = snapSize(Utils.clamp(minThumbLength(), (trackLength * visiblePortion), trackLength)); - - track.resizeRelocate(snapPosition(x), snapPosition(y), w, trackLength); - thumb.resize(snapSize(x >= 0 ? w : w + x), thumbLength); // Account for negative padding (see also RT-10719) - positionThumb(); - } - } else { - if (!IS_TOUCH_SUPPORTED) { - double decWidth = snapSize(decButton.prefWidth(-1)); - double incWidth = snapSize(incButton.prefWidth(-1)); - - decButton.resize(decWidth, h); - incButton.resize(incWidth, h); - - trackLength = snapSize(w - (decWidth + incWidth)); - thumbLength = snapSize(Utils.clamp(minThumbLength(), (trackLength * visiblePortion), trackLength)); - - trackBackground.resizeRelocate(snapPosition(x), snapPosition(y), trackLength+decWidth+incWidth, h); - decButton.relocate(snapPosition(x), snapPosition(y)); - incButton.relocate(snapPosition(x + w - incWidth), snapPosition(y)); - track.resizeRelocate(snapPosition(x + decWidth), snapPosition(y), trackLength, h); - thumb.resize(thumbLength, snapSize(y >= 0 ? h : h + y)); // Account for negative padding (see also RT-10719) - positionThumb(); - } - else { - trackLength = snapSize(w); - thumbLength = snapSize(Utils.clamp(minThumbLength(), (trackLength * visiblePortion), trackLength)); - - track.resizeRelocate(snapPosition(x), snapPosition(y), trackLength, h); - thumb.resize(thumbLength, snapSize(y >= 0 ? h : h + y)); // Account for negative padding (see also RT-10719) - positionThumb(); - } - - s.resize(snapSize(s.getWidth()), snapSize(s.getHeight())); - } - - // things should be invisible only when well below minimum length - if (s.getOrientation() == Orientation.VERTICAL && h >= (computeMinHeight(-1, (int)y , snappedRightInset(), snappedBottomInset(), (int)x) - (y+snappedBottomInset())) || - s.getOrientation() == Orientation.HORIZONTAL && w >= (computeMinWidth(-1, (int)y , snappedRightInset(), snappedBottomInset(), (int)x) - (x+snappedRightInset()))) { - trackBackground.setVisible(true); - track.setVisible(true); - thumb.setVisible(true); - if (!IS_TOUCH_SUPPORTED) { - incButton.setVisible(true); - decButton.setVisible(true); - } - } - else { - trackBackground.setVisible(false); - track.setVisible(false); - thumb.setVisible(false); - - if (!IS_TOUCH_SUPPORTED) { - /* - ** once the space is big enough for one button we - ** can look at drawing - */ - if (h >= decButton.computeMinWidth(-1)) { - decButton.setVisible(true); - } - else { - decButton.setVisible(false); - } - if (h >= incButton.computeMinWidth(-1)) { - incButton.setVisible(true); - } - else { - incButton.setVisible(false); - } - } - } - } - - - public Node getThumb() { - return thumb; - } - - public Node getTrack() { - return track; - } - - public Node getIncButton() { - return incButton; - } - - public Node getDecButton() { - return decButton; - } - - private static class EndButton extends Region { - private Region arrow; - - private EndButton(String styleClass, String arrowStyleClass) { - getStyleClass().setAll(styleClass); - arrow = new Region(); - arrow.getStyleClass().setAll(arrowStyleClass); - getChildren().setAll(arrow); - requestLayout(); - } - - @Override protected void layoutChildren() { - final double top = snappedTopInset(); - final double left = snappedLeftInset(); - final double bottom = snappedBottomInset(); - final double right = snappedRightInset(); - final double aw = snapSize(arrow.prefWidth(-1)); - final double ah = snapSize(arrow.prefHeight(-1)); - final double yPos = snapPosition((getHeight() - (top + bottom + ah)) / 2.0); - final double xPos = snapPosition((getWidth() - (left + right + aw)) / 2.0); - arrow.resizeRelocate(xPos + left, yPos + top, aw, ah); - } - - @Override protected double computeMinHeight(double width) { - return prefHeight(-1); - } - - @Override protected double computeMinWidth(double height) { - return prefWidth(-1); - } - - @Override protected double computePrefWidth(double height) { - final double left = snappedLeftInset(); - final double right = snappedRightInset(); - final double aw = snapSize(arrow.prefWidth(-1)); - return left + aw + right; - } - - @Override protected double computePrefHeight(double width) { - final double top = snappedTopInset(); - final double bottom = snappedBottomInset(); - final double ah = snapSize(arrow.prefHeight(-1)); - return top + ah + bottom; - } - } -} --- /dev/null 2015-09-03 15:21:13.000000000 -0700 +++ new/modules/controls/src/main/java/javafx/scene/control/skin/ScrollBarSkin.java 2015-09-03 15:21:11.362712400 -0700 @@ -0,0 +1,692 @@ +/* + * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javafx.scene.control.skin; + +import com.sun.javafx.scene.control.Properties; +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Orientation; +import javafx.geometry.Point2D; +import javafx.scene.AccessibleAction; +import javafx.scene.AccessibleAttribute; +import javafx.scene.AccessibleRole; +import javafx.scene.control.Accordion; +import javafx.scene.control.Button; +import javafx.scene.control.Control; +import javafx.scene.control.ScrollBar; +import javafx.scene.control.SkinBase; +import javafx.scene.input.MouseButton; +import javafx.scene.input.ScrollEvent; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.Node; +import com.sun.javafx.util.Utils; +import com.sun.javafx.scene.control.behavior.ScrollBarBehavior; + +import java.util.function.Consumer; + +/** + * Default skin implementation for the {@link ScrollBar} control. + * + * @see ScrollBar + * @since 9 + */ +public class ScrollBarSkin extends SkinBase { + + /*************************************************************************** + * * + * Private fields * + * * + **************************************************************************/ + + private final ScrollBarBehavior behavior; + + private StackPane thumb; + private StackPane trackBackground; + private StackPane track; + private EndButton incButton; + private EndButton decButton; + + private double trackLength; + private double thumbLength; + + private double preDragThumbPos; + private Point2D dragStart; // in the track's coord system + + private double trackPos; + + + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + /** + * Creates a new ScrollBarSkin instance, installing the necessary child + * nodes into the Control {@link Control#getChildren() children} list, as + * well as the necessary input mappings for handling key, mouse, etc events. + * + * @param control The control that this skin should be installed onto. + */ + public ScrollBarSkin(ScrollBar control) { + super(control); + + // install default input map for the ScrollBar control + this.behavior = new ScrollBarBehavior(control); +// control.setInputMap(behavior.getInputMap()); + + initialize(); + getSkinnable().requestLayout(); + + // Register listeners + final Consumer> consumer = e -> { + positionThumb(); + getSkinnable().requestLayout(); + }; + registerChangeListener(control.minProperty(), consumer); + registerChangeListener(control.maxProperty(), consumer); + registerChangeListener(control.visibleAmountProperty(), consumer); + registerChangeListener(control.valueProperty(), e -> positionThumb()); + registerChangeListener(control.orientationProperty(), e -> getSkinnable().requestLayout()); + } + + + + /*************************************************************************** + * * + * Public API * + * * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override public void dispose() { + super.dispose(); + + if (behavior != null) { + behavior.dispose(); + } + } + + /** {@inheritDoc} */ + @Override protected void layoutChildren(final double x, final double y, + final double w, final double h) { + + final ScrollBar s = getSkinnable(); + + /** + * Compute the percentage length of thumb as (visibleAmount/range) + * if max isn't greater than min then there is nothing to do here + */ + double visiblePortion; + if (s.getMax() > s.getMin()) { + visiblePortion = s.getVisibleAmount()/(s.getMax() - s.getMin()); + } + else { + visiblePortion = 1.0; + } + + if (s.getOrientation() == Orientation.VERTICAL) { + if (!Properties.IS_TOUCH_SUPPORTED) { + double decHeight = snapSize(decButton.prefHeight(-1)); + double incHeight = snapSize(incButton.prefHeight(-1)); + + decButton.resize(w, decHeight); + incButton.resize(w, incHeight); + + trackLength = snapSize(h - (decHeight + incHeight)); + thumbLength = snapSize(Utils.clamp(minThumbLength(), (trackLength * visiblePortion), trackLength)); + + trackBackground.resizeRelocate(snapPosition(x), snapPosition(y), w, trackLength+decHeight+incHeight); + decButton.relocate(snapPosition(x), snapPosition(y)); + incButton.relocate(snapPosition(x), snapPosition(y + h - incHeight)); + track.resizeRelocate(snapPosition(x), snapPosition(y + decHeight), w, trackLength); + thumb.resize(snapSize(x >= 0 ? w : w + x), thumbLength); // Account for negative padding (see also RT-10719) + positionThumb(); + } + else { + trackLength = snapSize(h); + thumbLength = snapSize(Utils.clamp(minThumbLength(), (trackLength * visiblePortion), trackLength)); + + track.resizeRelocate(snapPosition(x), snapPosition(y), w, trackLength); + thumb.resize(snapSize(x >= 0 ? w : w + x), thumbLength); // Account for negative padding (see also RT-10719) + positionThumb(); + } + } else { + if (!Properties.IS_TOUCH_SUPPORTED) { + double decWidth = snapSize(decButton.prefWidth(-1)); + double incWidth = snapSize(incButton.prefWidth(-1)); + + decButton.resize(decWidth, h); + incButton.resize(incWidth, h); + + trackLength = snapSize(w - (decWidth + incWidth)); + thumbLength = snapSize(Utils.clamp(minThumbLength(), (trackLength * visiblePortion), trackLength)); + + trackBackground.resizeRelocate(snapPosition(x), snapPosition(y), trackLength+decWidth+incWidth, h); + decButton.relocate(snapPosition(x), snapPosition(y)); + incButton.relocate(snapPosition(x + w - incWidth), snapPosition(y)); + track.resizeRelocate(snapPosition(x + decWidth), snapPosition(y), trackLength, h); + thumb.resize(thumbLength, snapSize(y >= 0 ? h : h + y)); // Account for negative padding (see also RT-10719) + positionThumb(); + } + else { + trackLength = snapSize(w); + thumbLength = snapSize(Utils.clamp(minThumbLength(), (trackLength * visiblePortion), trackLength)); + + track.resizeRelocate(snapPosition(x), snapPosition(y), trackLength, h); + thumb.resize(thumbLength, snapSize(y >= 0 ? h : h + y)); // Account for negative padding (see also RT-10719) + positionThumb(); + } + + s.resize(snapSize(s.getWidth()), snapSize(s.getHeight())); + } + + // things should be invisible only when well below minimum length + if (s.getOrientation() == Orientation.VERTICAL && h >= (computeMinHeight(-1, (int)y , snappedRightInset(), snappedBottomInset(), (int)x) - (y+snappedBottomInset())) || + s.getOrientation() == Orientation.HORIZONTAL && w >= (computeMinWidth(-1, (int)y , snappedRightInset(), snappedBottomInset(), (int)x) - (x+snappedRightInset()))) { + trackBackground.setVisible(true); + track.setVisible(true); + thumb.setVisible(true); + if (!Properties.IS_TOUCH_SUPPORTED) { + incButton.setVisible(true); + decButton.setVisible(true); + } + } + else { + trackBackground.setVisible(false); + track.setVisible(false); + thumb.setVisible(false); + + if (!Properties.IS_TOUCH_SUPPORTED) { + /* + ** once the space is big enough for one button we + ** can look at drawing + */ + if (h >= decButton.computeMinWidth(-1)) { + decButton.setVisible(true); + } + else { + decButton.setVisible(false); + } + if (h >= incButton.computeMinWidth(-1)) { + incButton.setVisible(true); + } + else { + incButton.setVisible(false); + } + } + } + } + + /* + * Minimum length is the length of the end buttons plus twice the + * minimum thumb length, which should be enough for a reasonably-sized + * track. Minimum breadth is determined by the breadths of the + * end buttons. + */ + /** {@inheritDoc} */ + @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + if (getSkinnable().getOrientation() == Orientation.VERTICAL) { + return getBreadth(); + } else { + if (!Properties.IS_TOUCH_SUPPORTED) { + return decButton.minWidth(-1) + incButton.minWidth(-1) + minTrackLength()+leftInset+rightInset; + } else { + return minTrackLength()+leftInset+rightInset; + } + } + } + + /** {@inheritDoc} */ + @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + if (getSkinnable().getOrientation() == Orientation.VERTICAL) { + if (!Properties.IS_TOUCH_SUPPORTED) { + return decButton.minHeight(-1) + incButton.minHeight(-1) + minTrackLength()+topInset+bottomInset; + } else { + return minTrackLength()+topInset+bottomInset; + } + } else { + return getBreadth(); + } + } + + /* + * Preferred size. The breadth is determined by the breadth of + * the end buttons. The length is a constant default length. + * Usually applications or other components will either set a + * specific length using LayoutInfo or will stretch the length + * of the scrollbar to fit a container. + */ + /** {@inheritDoc} */ + @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + final ScrollBar s = getSkinnable(); + return s.getOrientation() == Orientation.VERTICAL ? getBreadth() : Properties.DEFAULT_LENGTH+leftInset+rightInset; + } + + /** {@inheritDoc} */ + @Override protected double computePrefHeight(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + final ScrollBar s = getSkinnable(); + return s.getOrientation() == Orientation.VERTICAL ? Properties.DEFAULT_LENGTH+topInset+bottomInset : getBreadth(); + } + + /** {@inheritDoc} */ + @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + final ScrollBar s = getSkinnable(); + return s.getOrientation() == Orientation.VERTICAL ? s.prefWidth(-1) : Double.MAX_VALUE; + } + + /** {@inheritDoc} */ + @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + final ScrollBar s = getSkinnable(); + return s.getOrientation() == Orientation.VERTICAL ? Double.MAX_VALUE : s.prefHeight(-1); + } + + + + /*************************************************************************** + * * + * Private implementation * + * * + **************************************************************************/ + + /** + * Initializes the ScrollBarSkin. Creates the scene and sets up all the + * bindings for the group. + */ + private void initialize() { + + track = new StackPane(); + track.getStyleClass().setAll("track"); + + trackBackground = new StackPane(); + trackBackground.getStyleClass().setAll("track-background"); + + thumb = new StackPane() { + @Override + public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { + switch (attribute) { + case VALUE: return getSkinnable().getValue(); + default: return super.queryAccessibleAttribute(attribute, parameters); + } + } + }; + thumb.getStyleClass().setAll("thumb"); + thumb.setAccessibleRole(AccessibleRole.THUMB); + + + if (!Properties.IS_TOUCH_SUPPORTED) { + + incButton = new EndButton("increment-button", "increment-arrow") { + @Override + public void executeAccessibleAction(AccessibleAction action, Object... parameters) { + switch (action) { + case FIRE: + getSkinnable().increment(); + break; + default: super.executeAccessibleAction(action, parameters); + } + } + }; + incButton.setAccessibleRole(AccessibleRole.INCREMENT_BUTTON); + incButton.setOnMousePressed(me -> { + /* + ** if the tracklenght isn't greater than do nothing.... + */ + if (!thumb.isVisible() || trackLength > thumbLength) { + behavior.incButtonPressed(); + } + me.consume(); + }); + incButton.setOnMouseReleased(me -> { + /* + ** if the tracklenght isn't greater than do nothing.... + */ + if (!thumb.isVisible() || trackLength > thumbLength) { + behavior.incButtonReleased(); + } + me.consume(); + }); + + decButton = new EndButton("decrement-button", "decrement-arrow") { + @Override + public void executeAccessibleAction(AccessibleAction action, Object... parameters) { + switch (action) { + case FIRE: + getSkinnable().decrement(); + break; + default: super.executeAccessibleAction(action, parameters); + } + } + }; + decButton.setAccessibleRole(AccessibleRole.DECREMENT_BUTTON); + decButton.setOnMousePressed(me -> { + /* + ** if the tracklenght isn't greater than do nothing.... + */ + if (!thumb.isVisible() || trackLength > thumbLength) { + behavior.decButtonPressed(); + } + me.consume(); + }); + decButton.setOnMouseReleased(me -> { + /* + ** if the tracklenght isn't greater than do nothing.... + */ + if (!thumb.isVisible() || trackLength > thumbLength) { + behavior.decButtonReleased(); + } + me.consume(); + }); + } + + + track.setOnMousePressed(me -> { + if (!thumb.isPressed() && me.getButton() == MouseButton.PRIMARY) { + if (getSkinnable().getOrientation() == Orientation.VERTICAL) { + if (trackLength != 0) { + behavior.trackPress(me.getY() / trackLength); + me.consume(); + } + } else { + if (trackLength != 0) { + behavior.trackPress(me.getX() / trackLength); + me.consume(); + } + } + } + }); + + track.setOnMouseReleased(me -> { + behavior.trackRelease(); + me.consume(); + }); + + thumb.setOnMousePressed(me -> { + if (me.isSynthesized()) { + // touch-screen events handled by Scroll handler + me.consume(); + return; + } + /* + ** if max isn't greater than min then there is nothing to do here + */ + if (getSkinnable().getMax() > getSkinnable().getMin()) { + dragStart = thumb.localToParent(me.getX(), me.getY()); + double clampedValue = Utils.clamp(getSkinnable().getMin(), getSkinnable().getValue(), getSkinnable().getMax()); + preDragThumbPos = (clampedValue - getSkinnable().getMin()) / (getSkinnable().getMax() - getSkinnable().getMin()); + me.consume(); + } + }); + + + thumb.setOnMouseDragged(me -> { + if (me.isSynthesized()) { + // touch-screen events handled by Scroll handler + me.consume(); + return; + } + /* + ** if max isn't greater than min then there is nothing to do here + */ + if (getSkinnable().getMax() > getSkinnable().getMin()) { + /* + ** if the tracklength isn't greater then do nothing.... + */ + if (trackLength > thumbLength) { + Point2D cur = thumb.localToParent(me.getX(), me.getY()); + if (dragStart == null) { + // we're getting dragged without getting a mouse press + dragStart = thumb.localToParent(me.getX(), me.getY()); + } + double dragPos = getSkinnable().getOrientation() == Orientation.VERTICAL ? cur.getY() - dragStart.getY(): cur.getX() - dragStart.getX(); + behavior.thumbDragged(preDragThumbPos + dragPos / (trackLength - thumbLength)); + } + + me.consume(); + } + }); + + thumb.setOnScrollStarted(se -> { + if (se.isDirect()) { + /* + ** if max isn't greater than min then there is nothing to do here + */ + if (getSkinnable().getMax() > getSkinnable().getMin()) { + dragStart = thumb.localToParent(se.getX(), se.getY()); + double clampedValue = Utils.clamp(getSkinnable().getMin(), getSkinnable().getValue(), getSkinnable().getMax()); + preDragThumbPos = (clampedValue - getSkinnable().getMin()) / (getSkinnable().getMax() - getSkinnable().getMin()); + se.consume(); + } + } + }); + + thumb.setOnScroll(event -> { + if (event.isDirect()) { + /* + ** if max isn't greater than min then there is nothing to do here + */ + if (getSkinnable().getMax() > getSkinnable().getMin()) { + /* + ** if the tracklength isn't greater then do nothing.... + */ + if (trackLength > thumbLength) { + Point2D cur = thumb.localToParent(event.getX(), event.getY()); + if (dragStart == null) { + // we're getting dragged without getting a mouse press + dragStart = thumb.localToParent(event.getX(), event.getY()); + } + double dragPos = getSkinnable().getOrientation() == Orientation.VERTICAL ? cur.getY() - dragStart.getY(): cur.getX() - dragStart.getX(); + behavior.thumbDragged(/*todo*/ preDragThumbPos + dragPos / (trackLength - thumbLength)); + } + + event.consume(); + return; + } + } + }); + + + getSkinnable().addEventHandler(ScrollEvent.SCROLL, event -> { + /* + ** if the tracklength isn't greater then do nothing.... + */ + if (trackLength > thumbLength) { + + double dx = event.getDeltaX(); + double dy = event.getDeltaY(); + + /* + ** in 2.0 a horizontal scrollbar would scroll on a vertical + ** drag on a tracker-pad. We need to keep this behavior. + */ + dx = (Math.abs(dx) < Math.abs(dy) ? dy : dx); + + /* + ** we only consume an event that we've used. + */ + ScrollBar sb = (ScrollBar) getSkinnable(); + + double delta = (getSkinnable().getOrientation() == Orientation.VERTICAL ? dy : dx); + + /* + ** RT-22941 - If this is either a touch or inertia scroll + ** then we move to the position of the touch point. + * + * TODO: this fix causes RT-23406 ([ScrollBar, touch] Dragging scrollbar from the + * track on touchscreen causes flickering) + */ + if (event.isDirect()) { + if (trackLength > thumbLength) { + behavior.thumbDragged((getSkinnable().getOrientation() == Orientation.VERTICAL ? event.getY() : event.getX()) / trackLength); + event.consume(); + } + } + else { + if (delta > 0.0 && sb.getValue() > sb.getMin()) { + sb.decrement(); + event.consume(); + } else if (delta < 0.0 && sb.getValue() < sb.getMax()) { + sb.increment(); + event.consume(); + } + } + } + }); + + getChildren().clear(); + if (!Properties.IS_TOUCH_SUPPORTED) { + getChildren().addAll(trackBackground, incButton, decButton, track, thumb); + } + else { + getChildren().addAll(track, thumb); + } + } + + + /* + * Gets the breadth of the scrollbar. The "breadth" is the distance + * across the scrollbar, i.e. if vertical the width, otherwise the height. + * On desktop this is determined by the greater of the breadths of the end-buttons. + * Embedded doesn't have end-buttons, so currently we use a default breadth. + * We should change this when we get width/height css properties. + */ + double getBreadth() { + if (!Properties.IS_TOUCH_SUPPORTED) { + if (getSkinnable().getOrientation() == Orientation.VERTICAL) { + return Math.max(decButton.prefWidth(-1), incButton.prefWidth(-1)) +snappedLeftInset()+snappedRightInset(); + } else { + return Math.max(decButton.prefHeight(-1), incButton.prefHeight(-1)) +snappedTopInset()+snappedBottomInset(); + } + } + else { + if (getSkinnable().getOrientation() == Orientation.VERTICAL) { + return Math.max(Properties.DEFAULT_EMBEDDED_SB_BREADTH, Properties.DEFAULT_EMBEDDED_SB_BREADTH)+snappedLeftInset()+snappedRightInset(); + } else { + return Math.max(Properties.DEFAULT_EMBEDDED_SB_BREADTH, Properties.DEFAULT_EMBEDDED_SB_BREADTH)+snappedTopInset()+snappedBottomInset(); + } + } + } + + double minThumbLength() { + return 1.5f * getBreadth(); + } + + double minTrackLength() { + return 2.0f * getBreadth(); + } + + /** + * Called when ever either min, max or value changes, so thumb's layoutX, Y is recomputed. + */ + void positionThumb() { + ScrollBar s = getSkinnable(); + double clampedValue = Utils.clamp(s.getMin(), s.getValue(), s.getMax()); + trackPos = (s.getMax() - s.getMin() > 0) ? ((trackLength - thumbLength) * (clampedValue - s.getMin()) / (s.getMax() - s.getMin())) : (0.0F); + + if (!Properties.IS_TOUCH_SUPPORTED) { + if (s.getOrientation() == Orientation.VERTICAL) { + trackPos += decButton.prefHeight(-1); + } else { + trackPos += decButton.prefWidth(-1); + } + } + + thumb.setTranslateX( snapPosition(s.getOrientation() == Orientation.VERTICAL ? snappedLeftInset() : trackPos + snappedLeftInset())); + thumb.setTranslateY( snapPosition(s.getOrientation() == Orientation.VERTICAL ? trackPos + snappedTopInset() : snappedTopInset())); + } + + private Node getThumb() { + return thumb; + } + + private Node getTrack() { + return track; + } + + private Node getIncrementButton() { + return incButton; + } + + private Node getDecrementButton() { + return decButton; + } + + + + /*************************************************************************** + * * + * Support classes * + * * + **************************************************************************/ + + private static class EndButton extends Region { + private Region arrow; + + private EndButton(String styleClass, String arrowStyleClass) { + getStyleClass().setAll(styleClass); + arrow = new Region(); + arrow.getStyleClass().setAll(arrowStyleClass); + getChildren().setAll(arrow); + requestLayout(); + } + + @Override protected void layoutChildren() { + final double top = snappedTopInset(); + final double left = snappedLeftInset(); + final double bottom = snappedBottomInset(); + final double right = snappedRightInset(); + final double aw = snapSize(arrow.prefWidth(-1)); + final double ah = snapSize(arrow.prefHeight(-1)); + final double yPos = snapPosition((getHeight() - (top + bottom + ah)) / 2.0); + final double xPos = snapPosition((getWidth() - (left + right + aw)) / 2.0); + arrow.resizeRelocate(xPos + left, yPos + top, aw, ah); + } + + @Override protected double computeMinHeight(double width) { + return prefHeight(-1); + } + + @Override protected double computeMinWidth(double height) { + return prefWidth(-1); + } + + @Override protected double computePrefWidth(double height) { + final double left = snappedLeftInset(); + final double right = snappedRightInset(); + final double aw = snapSize(arrow.prefWidth(-1)); + return left + aw + right; + } + + @Override protected double computePrefHeight(double width) { + final double top = snappedTopInset(); + final double bottom = snappedBottomInset(); + final double ah = snapSize(arrow.prefHeight(-1)); + return top + ah + bottom; + } + } +}