javaFx的分割面板增加折叠展开功能



import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.WeakHashMap;

import javafx.beans.DefaultProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.WritableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.Node;

import javafx.css.StyleableObjectProperty;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;

import com.sun.javafx.css.converters.EnumConverter;

import javafx.css.Styleable;
import javafx.css.StyleableProperty;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;

/**
 * <p>A control that has two or more sides, each separated by a divider, which can be
 * dragged by the user to give more space to one of the sides, resulting in
 * the other side shrinking by an equal amount.</p>
 *
 * <p>{@link Node Nodes} can be positioned horizontally next to each other, or stacked
 * vertically. This can be controlled by setting the {@link #orientationProperty()}.</p>
 *
 * <p> The dividers in a SplitPane have the following behavior
 * <ul>
 * <li>Dividers cannot overlap another divider</li>
 * <li>Dividers cannot overlap a node.</li>
 * <li>Dividers moving to the left/top will stop when the node's min size is reached.</li>
 * <li>Dividers moving to the right/bottom will stop when the node's max size is reached.</li>
 * </ul>
 *
 * <p>Nodes needs to be placed inside a layout container before they are added
 * into the SplitPane.  If the node is not inside a layout container
 * the maximum and minimum position of the divider will be the
 * maximum and minimum size of the content.
 * </p>
 *
 * <p>A divider's position ranges from 0 to 1.0(inclusive).  A position of 0 will place the
 * divider at the left/top most edge of the SplitPane plus the minimum size of the node.  A
 * position of 1.0 will place the divider at the right/bottom most edge of the SplitPane minus the
 * minimum size of the node.  A divider position of 0.5 will place the
 * the divider in the middle of the SplitPane.  Setting the divider position greater
 * than the node's maximum size position will result in the divider being set at the
 * node's maximum size position.  Setting the divider position less than the node's minimum size position
 * will result in the divider being set at the node's minimum size position. Therefore the value set in
 * {@link #setDividerPosition} and {@link #setDividerPositions} may not be the same as the value returned by
 * {@link #getDividerPositions}.
 * </p>
 *
 * <p>If there are more than two nodes in the SplitPane and the divider positions are set in such a
 * way that the dividers cannot fit the nodes the dividers will be automatically adjusted by the SplitPane.
 * <p>For example we have three nodes whose sizes and divider positions are
 * </p>
 * <pre>
 * Node 1: min 25 max 100
 * Node 2: min 100 max 200
 * Node 3: min 25 max 50
 * divider 1: 0.40
 * divider 2: 0.45
 * </pre>
 *
 * <p>The result will be Node 1 size will be its pref size and divider 1 will be positioned 0.40,
 * Node 2 size will be its min size and divider 2 position will be the min size of Node 2 plus
 * divider 1 position, and the remaining space will be given to Node 3.
 * </p>
 *
 * <p>
 * SplitPane sets focusTraversable to false.
 * </p>
 *
 * <p>Example:</p>
 * <pre><code>
 * SplitPane sp = new SplitPane();
 * final StackPane sp1 = new StackPane();
 * sp1.getItems().add(new Button("Button One"));
 * final StackPane sp2 = new StackPane();
 * sp2.getItems().add(new Button("Button Two"));
 * final StackPane sp3 = new StackPane();
 * sp3.getItems().add(new Button("Button Three"));
 * sp.getItems().addAll(sp1, sp2, sp3);
 * sp.setDividerPositions(0.3f, 0.6f, 0.9f);
 * </code></pre>
 *
 * @since JavaFX 2.0
 */
@DefaultProperty("items")
public class SplitPane extends Control {

    /********************************************************************
     *  static methods
     ********************************************************************/
    private static final String RESIZABLE_WITH_PARENT = "resizable-with-parent";

    /**
     * Sets a node in the SplitPane to be resizable or not when the SplitPane is
     * resized.  By default all node are resizable.  Setting value to false will
     * prevent the node from being resized.
     * @param node A node in the SplitPane.
     * @param value true if the node is resizable or false if not resizable.
     * @since JavaFX 2.1
     */
    public static void setResizableWithParent(Node node, Boolean value) {
        if (value == null) {
            node.getProperties().remove(RESIZABLE_WITH_PARENT);
        } else {
            node.getProperties().put(RESIZABLE_WITH_PARENT, value);
        }
    }

    /**
     * Return true if the node is resizable when the parent container is resized false otherwise.
     * @param node A node in the SplitPane.
     * @defaultValue true
     * @return true if the node is resizable false otherwise.
     * @since JavaFX 2.1
     */
    public static Boolean isResizableWithParent(Node node) {
        if (node.hasProperties()) {
            Object value = node.getProperties().get(RESIZABLE_WITH_PARENT);
            if (value != null) {
                return (Boolean)value;
            }
        }
        return true;
    }

    /***************************************************************************
     *                                                                         *
     * Constructors                                                            *
     *                                                                         *
     **************************************************************************/

    /**
     * Creates a new SplitPane with no content.
     */
    public SplitPane() {
        this((Node[])null);
    }

    /**
     * Creates a new SplitPane with the given items set as the content to split
     * between one or more dividers.
     *
     * @param items The items to place inside the SplitPane.
     * @since JavaFX 8u40
     */
    public SplitPane(Node... items) {
        getStyleClass().setAll(DEFAULT_STYLE_CLASS);
        // focusTraversable is styleable through css. Calling setFocusTraversable
        // makes it look to css like the user set the value and css will not
        // override. Initializing focusTraversable by calling applyStyle with a
        // null StyleOrigin ensures that css will be able to override the value.
        ((StyleableProperty<Boolean>)(WritableValue<Boolean>)focusTraversableProperty()).applyStyle(null, Boolean.FALSE);

        getItems().addListener(new ListChangeListener<Node>() {
            @Override public void onChanged(Change<? extends Node> c) {
                while (c.next()) {
                    int from = c.getFrom();
                    int index = from;
                    for (int i = 0; i < c.getRemovedSize(); i++) {
                        if (index < dividers.size()) {
                            dividerCache.put(index, Double.MAX_VALUE);
                        } else if (index == dividers.size()) {
                            if (!dividers.isEmpty()) {
                                if (c.wasReplaced()) {
                                    dividerCache.put(index - 1, dividers.get(index - 1).getPosition());
                                } else {
                                    dividerCache.put(index - 1, Double.MAX_VALUE);
                                }
                            }
                        }
                        index++;
                    }
                    for (int i = 0; i < dividers.size(); i++) {
                        if (dividerCache.get(i) == null) {
                            dividerCache.put(i, dividers.get(i).getPosition());
                        }
                    }
                }
                dividers.clear();
                for (int i = 0; i < getItems().size() - 1; i++) {
                    if (dividerCache.containsKey(i) && dividerCache.get(i) != Double.MAX_VALUE) {
                        Divider d = new Divider();
                        d.setPosition(dividerCache.get(i));
                        dividers.add(d);
                    } else {
                        dividers.add(new Divider());
                    }
                    dividerCache.remove(i);
                }
            }
        });

        if (items != null) {
            getItems().addAll(items);
        }

        // initialize pseudo-class state
        pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, true);
    }

    /***************************************************************************
     *                                                                         *
     * Properties                                                              *
     *                                                                         *
     **************************************************************************/

    // --- Vertical
    private ObjectProperty<Orientation> orientation;

    /**
     * <p>This property controls how the SplitPane should be displayed to the
     * user. {@link javafx.geometry.Orientation#HORIZONTAL} will result in
     * two (or more) nodes being placed next to each other horizontally, whilst
     * {@link javafx.geometry.Orientation#VERTICAL} will result in the nodes being
     * stacked vertically.</p>
     *
     */
    public final void setOrientation(Orientation value) {
        orientationProperty().set(value);
    };

    /**
     * The orientation for the SplitPane.
     * @return The orientation for the SplitPane.
     */
    public final Orientation getOrientation() {
        return orientation == null ? Orientation.HORIZONTAL : orientation.get();
    }

    /**
     * The orientation for the SplitPane.
     */
    public final ObjectProperty<Orientation> orientationProperty() {
        if (orientation == null) {
            orientation = new StyleableObjectProperty<Orientation>(Orientation.HORIZONTAL) {
                @Override public void invalidated() {
                    final boolean isVertical = (get() == Orientation.VERTICAL);
                    pseudoClassStateChanged(VERTICAL_PSEUDOCLASS_STATE,    isVertical);
                    pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, !isVertical);
                }

                @Override public CssMetaData<SplitPane,Orientation> getCssMetaData() {
                    return StyleableProperties.ORIENTATION;
                }

                @Override
                public Object getBean() {
                    return SplitPane.this;
                }

                @Override
                public String getName() {
                    return "orientation";
                }
            };
        }
        return orientation;
    }



    /***************************************************************************
     *                                                                         *
     * Instance Variables                                                      *
     *                                                                         *
     **************************************************************************/

    private final ObservableList<Node> items = FXCollections.observableArrayList();

    private final ObservableList<Divider> dividers = FXCollections.observableArrayList();
    private final ObservableList<Divider> unmodifiableDividers = FXCollections.unmodifiableObservableList(dividers);

    // Cache the divider positions if the items have not been created.
    private final WeakHashMap<Integer, Double> dividerCache = new WeakHashMap<Integer, Double>();

    private final ObservableList<DividerButton> dividerButtons = FXCollections.observableArrayList();

    /***************************************************************************
     *                                                                         *
     * Public API                                                              *
     *                                                                         *
     **************************************************************************/

    /**
     * Returns an ObservableList which can be use to modify the contents of the SplitPane.
     * The order the nodes are placed into this list will be the same order in the SplitPane.
     *
     * @return the list of items in this SplitPane.
     */
    public ObservableList<Node> getItems() {
        return items;
    }

    /**
     * Returns an unmodifiable list of all the dividers in this SplitPane.
     *
     * @return the list of dividers.
     */
//    @ReturnsUnmodifiableCollection
    public ObservableList<Divider> getDividers() {
        return unmodifiableDividers;
    }

    public ObservableList<DividerButton> getDividerButtons() {
        return this.dividerButtons;
    }

    /**
     * Sets the position of the divider at the specified divider index.
     *
     * @param dividerIndex the index of the divider.
     * @param position the divider position, between 0.0 and 1.0 (inclusive).
     */
    public void setDividerPosition(int dividerIndex, double position) {
        if (getDividers().size() <= dividerIndex)  {
            dividerCache.put(dividerIndex, position);
            return;
        }
        if (dividerIndex >= 0) {
            getDividers().get(dividerIndex).setPosition(position);
        }
    }

    /**
     * Sets the position of the divider
     *
     * @param positions the divider position, between 0.0 and 1.0 (inclusive).
     */
    public void setDividerPositions(double... positions) {
        if (dividers.isEmpty()) {
            for (int i = 0; i < positions.length; i++) {
                dividerCache.put(i, positions[i]);
            }
            return;
        }
        for (int i = 0; i < positions.length && i < dividers.size(); i++) {
            dividers.get(i).setPosition(positions[i]);
        }
    }

    public void setDividerButtons(DividerType... types) {
        this.dividerButtons.clear();
        for (int i = 0; i < types.length && i < this.dividers.size(); i++) {
            DividerButton dividerType = new DividerButton();
            dividerType.setDividerType(types[i]);
            this.dividerButtons.add(dividerType);
        }
    }

    /**
     * Returns an array of double containing the position of each divider.
     *
     * @return an array of double containing the position of each divider.
     */
    public double[] getDividerPositions() {
        double[] positions = new double[dividers.size()];
        for (int i = 0; i < dividers.size(); i++) {
            positions[i] = dividers.get(i).getPosition();
        }
        return positions;
    }

    /** {@inheritDoc} */
    @Override protected Skin<?> createDefaultSkin() {
        return new SplitPaneSkin(this);
    }

    /***************************************************************************
     *                                                                         *
     *                         Stylesheet Handling                             *
     *                                                                         *
     **************************************************************************/

    private static final String DEFAULT_STYLE_CLASS = "split-pane";

    /** @treatAsPrivate */
    private static class StyleableProperties {
        private static final CssMetaData<SplitPane,Orientation> ORIENTATION =
                new CssMetaData<SplitPane,Orientation>("-fx-orientation",
                        new EnumConverter<Orientation>(Orientation.class),
                        Orientation.HORIZONTAL) {

                    @Override
                    public Orientation getInitialValue(SplitPane node) {
                        // A vertical SplitPane should remain vertical
                        return node.getOrientation();
                    }

                    @Override
                    public boolean isSettable(SplitPane n) {
                        return n.orientation == null || !n.orientation.isBound();
                    }

                    @Override
                    public StyleableProperty<Orientation> getStyleableProperty(SplitPane n) {
                        return (StyleableProperty<Orientation>)(WritableValue<Orientation>)n.orientationProperty();
                    }
                };

        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
        static {
            final List<CssMetaData<? extends Styleable, ?>> styleables =
                    new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
            styleables.add(ORIENTATION);
            STYLEABLES = Collections.unmodifiableList(styleables);
        }
    }

    /**
     * @return The CssMetaData associated with this class, which may include the
     * CssMetaData of its super classes.
     * @since JavaFX 8.0
     */
    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
        return StyleableProperties.STYLEABLES;
    }

    /**
     * {@inheritDoc}
     * @since JavaFX 8.0
     */
    @Override
    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
        return getClassCssMetaData();
    }

    private static final PseudoClass VERTICAL_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("vertical");
    private static final PseudoClass HORIZONTAL_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("horizontal");

    /**
     * Most Controls return true for focusTraversable, so Control overrides
     * this method to return true, but SplitPane returns false for
     * focusTraversable's initial value; hence the override of the override.
     * This method is called from CSS code to get the correct initial value.
     * @treatAsPrivate implementation detail
     */
    @Deprecated @Override
    protected /*do not make final*/ Boolean impl_cssGetFocusTraversableInitialValue() {
        return Boolean.FALSE;
    }


    /***************************************************************************
     *                                                                         *
     * Support Classes                                                         *
     *                                                                         *
     **************************************************************************/

    /**
     * Represents a single divider in the SplitPane.
     * @since JavaFX 2.0
     */
    public static class Divider {

        /**
         * Creates a default Divider instance.
         */
        public Divider() {

        }

        /**
         * <p>Represents the location where the divider should ideally be
         * positioned, between 0.0 and 1.0 (inclusive). 0.0 represents the
         * left- or top-most point, and 1.0 represents the right- or bottom-most
         * point (depending on the horizontal property). The SplitPane will attempt
         * to get the divider to the point requested, but it must take into account
         * the minimum width/height of the nodes contained within it.</p>
         *
         * <p>As the user drags the SplitPane divider around this property will
         * be updated to always represent its current location.</p>
         *
         * @defaultValue 0.5
         */
        private DoubleProperty position;
        public final void setPosition(double value) {
            positionProperty().set(value);
        }

        public final double getPosition() {
            return position == null ? 0.5F : position.get();
        }

        public final DoubleProperty positionProperty() {
            if (position == null) {
                position = new SimpleDoubleProperty(this, "position", 0.5F);// {
//                    @Override protected void invalidated() {
//                        if (get() < 0) {
//                            this.value = value;
//                        } else if (get() > 1) {
//                            this.value = value;
//                        }
//                    }
//                };
            }
            return position;
        }
    }

    /**
     * 折叠的按钮向前还是向后
     */
    public static class DividerButton {
        public DividerButton() {
        }

        private DividerType dividerType;

        public DividerType getDividerType() {
            return dividerType;
        }

        public void setDividerType(DividerType dividerType) {
            this.dividerType = dividerType;
        }
    }
}

 



import com.sun.javafx.scene.control.skin.BehaviorSkinBase;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Orientation;
import javafx.geometry.VPos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;

import java.util.*;

import com.sun.javafx.scene.control.behavior.BehaviorBase;

public class SplitPaneSkin extends BehaviorSkinBase<SplitPane, BehaviorBase<SplitPane>> {

    private ObservableList<Content> contentRegions;
    private ObservableList<ContentDivider> contentDividers;
    private boolean horizontal;
    private ObservableList<ContentDividerButton> contentDividerButtons;

    public SplitPaneSkin(final SplitPane splitPane) {
        super(splitPane, new BehaviorBase<>(splitPane, Collections.emptyList()));
//        splitPane.setManaged(false);
        horizontal = getSkinnable().getOrientation() == Orientation.HORIZONTAL;

        contentRegions = FXCollections.<Content>observableArrayList();
        contentDividers = FXCollections.<ContentDivider>observableArrayList();
        this.contentDividerButtons = FXCollections.observableArrayList();

        int index = 0;
        for (Node n: getSkinnable().getItems()) {
            addContent(index++, n);
        }
        initializeContentListener();

        for (SplitPane.Divider d: getSkinnable().getDividers()) {
            addDivider(d);
        }

        for (SplitPane.DividerButton button : getSkinnable().getDividerButtons()) {
            this.addDividerButton(button);
        }

        registerChangeListener(splitPane.orientationProperty(), "ORIENTATION");
        registerChangeListener(splitPane.widthProperty(), "WIDTH");
        registerChangeListener(splitPane.heightProperty(), "HEIGHT");
    }

    private void addContent(int index, Node n) {
        Content c = new Content(n);
        contentRegions.add(index, c);
        getChildren().add(index, c);
    }

    private void removeContent(Node n) {
        for (Content c: contentRegions) {
            if (c.getContent().equals(n)) {
                getChildren().remove(c);
                contentRegions.remove(c);
                break;
            }
        }
    }

    private void initializeContentListener() {
        getSkinnable().getItems().addListener((ListChangeListener<Node>) c -> {
            while (c.next()) {
                if (c.wasPermutated() || c.wasUpdated()) {
                    /**
                     * the contents were either moved, or updated.
                     * rebuild the contents to re-sync
                     */
                    getChildren().clear();
                    contentRegions.clear();
                    int index = 0;
                    for (Node n : c.getList()) {
                        addContent(index++, n);
                    }

                } else {
                    for (Node n : c.getRemoved()) {
                        removeContent(n);
                    }

                    int index = c.getFrom();
                    for (Node n : c.getAddedSubList()) {
                        addContent(index++, n);
                    }
                }
            }
            // TODO there may be a more efficient way than rebuilding all the dividers
            // everytime the list changes.
            removeAllDividers();
            for (SplitPane.Divider d: getSkinnable().getDividers()) {
                addDivider(d);
            }
        });
    }

    // This listener is to be removed from 'removed' dividers and added to 'added' dividers
    class PosPropertyListener implements ChangeListener<Number> {
        ContentDivider divider;

        public PosPropertyListener(ContentDivider divider) {
            this.divider = divider;
        }

        @Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
            if (checkDividerPos) {
                // When checking is enforced, we know that the position was set explicitly
                divider.posExplicit = true;
            }
            getSkinnable().requestLayout();
        }
    }

    private void checkDividerPosition(ContentDivider divider, double newPos, double oldPos) {
        double dividerWidth = divider.prefWidth(-1);
        Content left = getLeft(divider);
        Content right = getRight(divider);
        double minLeft = left == null ? 0 : (horizontal) ? left.minWidth(-1) : left.minHeight(-1);
        double minRight = right == null ? 0 : (horizontal) ? right.minWidth(-1) : right.minHeight(-1);
        double maxLeft = left == null ? 0 :
                left.getContent() != null ? (horizontal) ? left.getContent().maxWidth(-1) : left.getContent().maxHeight(-1) : 0;
        double maxRight = right == null ? 0 :
                right.getContent() != null ? (horizontal) ? right.getContent().maxWidth(-1) : right.getContent().maxHeight(-1) : 0;

        double previousDividerPos = 0;
        double nextDividerPos = getSize();
        int index = contentDividers.indexOf(divider);

        if (index - 1 >= 0) {
            previousDividerPos = contentDividers.get(index - 1).getDividerPos();
            if (previousDividerPos == -1) {
                // Get the divider position if it hasn't been initialized.
                previousDividerPos = getAbsoluteDividerPos(contentDividers.get(index - 1));
            }
        }
        if (index + 1 < contentDividers.size()) {
            nextDividerPos = contentDividers.get(index + 1).getDividerPos();
            if (nextDividerPos == -1) {
                // Get the divider position if it hasn't been initialized.
                nextDividerPos = getAbsoluteDividerPos(contentDividers.get(index + 1));
            }
        }

        // Set the divider into the correct position by looking at the max and min content sizes.
        checkDividerPos = false;
        if (newPos > oldPos) {
            double max = previousDividerPos == 0 ? maxLeft : previousDividerPos + dividerWidth + maxLeft;
            double min = nextDividerPos - minRight - dividerWidth;
            double stopPos = Math.min(max, min);
            if (newPos >= stopPos) {
                setAbsoluteDividerPos(divider, stopPos);
            } else {
                double rightMax = nextDividerPos - maxRight - dividerWidth;
                if (newPos <= rightMax) {
                    setAbsoluteDividerPos(divider, rightMax);
                } else {
                    setAbsoluteDividerPos(divider, newPos);
                }
            }
        } else {
            double max = nextDividerPos - maxRight - dividerWidth;
            double min = previousDividerPos == 0 ? minLeft : previousDividerPos + minLeft + dividerWidth;
            double stopPos = Math.max(max, min);
            if (newPos <= stopPos) {
                setAbsoluteDividerPos(divider, stopPos);
            } else {
                double leftMax = previousDividerPos + maxLeft + dividerWidth;
                if (newPos >= leftMax) {
                    setAbsoluteDividerPos(divider, leftMax);
                } else {
                    setAbsoluteDividerPos(divider, newPos);
                }
            }
        }
        checkDividerPos = true;
    }

    private void addDivider(SplitPane.Divider d) {
        ContentDivider c = new ContentDivider(d);
        c.setInitialPos(d.getPosition());
        c.setDividerPos(-1);
        ChangeListener<Number> posPropertyListener = new PosPropertyListener(c);
        c.setPosPropertyListener(posPropertyListener);
        d.positionProperty().addListener(posPropertyListener);
        initializeDividerEventHandlers(c);
        contentDividers.add(c);
        getChildren().add(c);
    }

    /**
     * TODO 添加折叠按钮
     * @param button
     */
    private void addDividerButton(SplitPane.DividerButton button) {
        ContentDividerButton dividerButton = new ContentDividerButton(button);
        if (button.getDividerType() != null) {
            this.initializeDividerButtonEventHandlers(dividerButton);
        }

        this.contentDividerButtons.add(dividerButton);
        this.getChildren().add(dividerButton);
    }

    private void removeAllDividers() {
        ListIterator<ContentDivider> dividers = contentDividers.listIterator();
        while (dividers.hasNext()) {
            ContentDivider c = dividers.next();
            getChildren().remove(c);
            c.getDivider().positionProperty().removeListener(c.getPosPropertyListener());
            dividers.remove();
        }
        lastDividerUpdate = 0;
    }

    private void initializeDividerEventHandlers(final ContentDivider divider) {
        // TODO: do we need to consume all mouse events?
        // they only bubble to the skin which consumes them by default
        divider.addEventHandler(MouseEvent.ANY, event -> {
            event.consume();
        });

        divider.setOnMousePressed(e -> {
            if (horizontal) {
                divider.setInitialPos(divider.getDividerPos());
                divider.setPressPos(e.getSceneX());
                divider.setPressPos(getSkinnable().getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT
                        ? getSkinnable().getWidth() - e.getSceneX() : e.getSceneX());
            } else {
                divider.setInitialPos(divider.getDividerPos());
                divider.setPressPos(e.getSceneY());
            }
            e.consume();
        });

        divider.setOnMouseDragged(e -> {
            double delta = 0;
            if (horizontal) {
                delta = getSkinnable().getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT
                        ? getSkinnable().getWidth() - e.getSceneX() : e.getSceneX();
            } else {
                delta = e.getSceneY();
            }
            delta -= divider.getPressPos();
            int index = contentDividers.indexOf(divider);
            ContentDividerButton dividerButton = contentDividerButtons.get(index);
            if (dividerButton != null && !dividerButton.open.get()) {
                dividerButton.open.set(true);
            }
            setAndCheckAbsoluteDividerPos(divider, Math.ceil(divider.getInitialPos() + delta));
            e.consume();
        });
    }

    private void initializeDividerButtonEventHandlers(final ContentDividerButton dividerButton) {
        // TODO: do we need to consume all mouse events?
        // they only bubble to the skin which consumes them by default
        dividerButton.addEventHandler(MouseEvent.ANY, event -> {
            event.consume();
        });

        dividerButton.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                int index = contentDividerButtons.indexOf(dividerButton);

                if (contentDividers.size() > index) {
                    boolean open = dividerButton.open.get();
                    double value = 0.;
                    ContentDivider contentDivider = contentDividers.get(index);

                    if (open) {
                        double oldPos = contentDivider.getDividerPos();
                        dividerButton.setDividerPos(oldPos);
                        dividerButton.open.set(false);

                        if (dividerButton.getDivider().getDividerType() == DividerType.BELOW) {
                            Content content = contentRegions.get(index + 1);
                            value = oldPos + content.getArea();
                        }

                    } else {
                        dividerButton.open.set(true);
                        value = dividerButton.getDividerPos();
                    }
                    setAndCheckAbsoluteDividerPos(contentDivider, value);
//                    contentDivider.setVisible(dividerButton.open.get());
//                    contentDivider.setManaged(dividerButton.open.get());
                }
            }
        });
    }

    private Content getLeft(ContentDivider d) {
        int index = contentDividers.indexOf(d);
        if (index != -1) {
            return contentRegions.get(index);
        }
        return null;
    }

    private Content getRight(ContentDivider d) {
        int index = contentDividers.indexOf(d);
        if (index != -1) {
            return contentRegions.get(index + 1);
        }
        return null;
    }

    @Override protected void handleControlPropertyChanged(String property) {
        super.handleControlPropertyChanged(property);
        if ("ORIENTATION".equals(property)) {
            this.horizontal = getSkinnable().getOrientation() == Orientation.HORIZONTAL;
            this.previousSize = -1;
            for (ContentDivider c: contentDividers) {
                c.setGrabberStyle(horizontal);
            }
            getSkinnable().requestLayout();
        } else if ("WIDTH".equals(property) || "HEIGHT".equals(property)) {
            getSkinnable().requestLayout();
        }
    }

    // Value is the left edge of the divider
    private void setAbsoluteDividerPos(ContentDivider divider, double value) {
        if (getSkinnable().getWidth() > 0 && getSkinnable().getHeight() > 0 && divider != null) {
            SplitPane.Divider paneDivider = divider.getDivider();
            divider.setDividerPos(value);
            double size = getSize();
            if (size != 0) {
                // Adjust the position to the center of the
                // divider and convert its position to a percentage.
                double pos = value + divider.prefWidth(-1)/2;
                paneDivider.setPosition(pos / size);
            } else {
                paneDivider.setPosition(0);
            }
        }
    }

    // Updates the divider with the SplitPane.Divider's position
    // The value updated to SplitPane.Divider will be the center of the divider.
    // The returned position will be the left edge of the divider
    private double getAbsoluteDividerPos(ContentDivider divider) {
        if (getSkinnable().getWidth() > 0 && getSkinnable().getHeight() > 0 && divider != null) {
            SplitPane.Divider paneDivider = divider.getDivider();
            double newPos = posToDividerPos(divider, paneDivider.getPosition());
            divider.setDividerPos(newPos);
            return newPos;
        }
        return 0;
    }

    // Returns the left edge of the divider at pos
    // Pos is the percentage location from SplitPane.Divider.
    private double posToDividerPos(ContentDivider divider, double pos) {
        double newPos = getSize() * pos;
        if (pos == 1) {
            newPos -= divider.prefWidth(-1);
        } else {
            newPos -= divider.prefWidth(-1)/2;
        }
        return Math.round(newPos);
    }

    private double totalMinSize() {
        double dividerWidth = !contentDividers.isEmpty() ? contentDividers.size() * contentDividers.get(0).prefWidth(-1) : 0;
        double minSize = 0;
        for (Content c: contentRegions) {
            if (horizontal) {
                minSize += c.minWidth(-1);
            } else {
                minSize += c.minHeight(-1);
            }
        }
        return minSize + dividerWidth;
    }

    private double getSize() {
        final SplitPane s = getSkinnable();
        double size = totalMinSize();
        if (horizontal) {
            if (s.getWidth() > size) {
                size = s.getWidth() - snappedLeftInset() - snappedRightInset();
            }
        } else {
            if (s.getHeight() > size) {
                size = s.getHeight() - snappedTopInset() - snappedBottomInset();
            }
        }
        return size;
    }

    // Evenly distribute the size to the available list.
    // size is the amount to distribute.
    private double distributeTo(List<Content> available, double size) {
        if (available.isEmpty()) {
            return size;
        }

        size = snapSize(size);
        int portion = (int)(size)/available.size();
        int remainder;

        while (size > 0 && !available.isEmpty()) {
            Iterator<Content> i = available.iterator();
            while (i.hasNext()) {
                Content c = i.next();
                double max = Math.min((horizontal ? c.maxWidth(-1) : c.maxHeight(-1)), Double.MAX_VALUE);
                double min = horizontal ? c.minWidth(-1) : c.minHeight(-1);

                // We have too much space
                if (c.getArea() >= max) {
                    c.setAvailable(c.getArea() - min);
                    i.remove();
                    continue;
                }
                // Not enough space
                if (portion >= (max - c.getArea())) {
                    size -= (max - c.getArea());
                    c.setArea(max);
                    c.setAvailable(max - min);
                    i.remove();
                } else {
                    // Enough space
                    c.setArea(c.getArea() + portion);
                    c.setAvailable(c.getArea() - min);
                    size -= portion;
                }
                if ((int)size == 0) {
                    return size;
                }
            }
            if (available.isEmpty()) {
                // We reached the max size for everything just return
                return size;
            }
            portion = (int)(size)/available.size();
            remainder = (int)(size)%available.size();
            if (portion == 0 && remainder != 0) {
                portion = remainder;
                remainder = 0;
            }
        }
        return size;
    }

    // Evenly distribute the size from the available list.
    // size is the amount to distribute.
    private double distributeFrom(double size, List<Content> available) {
        if (available.isEmpty()) {
            return size;
        }

        size = snapSize(size);
        int portion = (int)(size)/available.size();
        int remainder;

        while (size > 0 && !available.isEmpty()) {
            Iterator<Content> i = available.iterator();
            while (i.hasNext()) {
                Content c = i.next();
                //not enough space taking available and setting min
                if (portion >= c.getAvailable()) {
                    c.setArea(c.getArea() - c.getAvailable()); // Min size
                    size -= c.getAvailable();
                    c.setAvailable(0);
                    i.remove();
                } else {
                    //enough space
                    c.setArea(c.getArea() - portion);
                    c.setAvailable(c.getAvailable() - portion);
                    size -= portion;
                }
                if ((int)size == 0) {
                    return size;
                }
            }
            if (available.isEmpty()) {
                // We reached the min size for everything just return
                return size;
            }
            portion = (int)(size)/available.size();
            remainder = (int)(size)%available.size();
            if (portion == 0 && remainder != 0) {
                portion = remainder;
                remainder = 0;
            }
        }
        return size;
    }

    private void setupContentAndDividerForLayout() {
        // Set all the value to prepare for layout
        double dividerWidth = contentDividers.isEmpty() ? 0 : contentDividers.get(0).prefWidth(-1);
        double startX = 0;
        double startY = 0;
        for (Content c: contentRegions) {
            if (resize && !c.isResizableWithParent()) {
                c.setArea(c.getResizableWithParentArea());
            }

            c.setX(startX);
            c.setY(startY);
            if (horizontal) {
                startX += (c.getArea() + dividerWidth);
            } else {
                startY += (c.getArea() + dividerWidth);
            }
        }

        startX = 0;
        startY = 0;
        // The dividers are already in the correct positions.  Disable
        // checking the divider positions.
        checkDividerPos = false;
        for (int i = 0; i < contentDividers.size(); i++) {
            ContentDivider d = contentDividers.get(i);
            if (horizontal) {
                startX += getLeft(d).getArea() + (i == 0 ? 0 : dividerWidth);
            } else {
                startY += getLeft(d).getArea() + (i == 0 ? 0 : dividerWidth);
            }
            d.setX(startX);
            d.setY(startY);
            setAbsoluteDividerPos(d, (horizontal ? d.getX() : d.getY()));
            d.posExplicit = false;
        }
        checkDividerPos = true;
    }

    private void layoutDividersAndContent(double width, double height) {
        final double paddingX = snappedLeftInset();
        final double paddingY = snappedTopInset();
        final double dividerWidth = contentDividers.isEmpty() ? 0 : contentDividers.get(0).prefWidth(-1);

        for (Content c: contentRegions) {
//            System.out.println("LAYOUT " + c.getId() + " PANELS X " + c.getX() + " Y " + c.getY() + " W " + (horizontal ? c.getArea() : width) + " H " + (horizontal ? height : c.getArea()));
            if (horizontal) {
                c.setClipSize(c.getArea(), height);
                layoutInArea(c, c.getX() + paddingX, c.getY() + paddingY, c.getArea(), height,
                        0/*baseline*/,HPos.CENTER, VPos.CENTER);
            } else {
                c.setClipSize(width, c.getArea());
                layoutInArea(c, c.getX() + paddingX, c.getY() + paddingY, width, c.getArea(),
                        0/*baseline*/,HPos.CENTER, VPos.CENTER);
            }
        }
        for (ContentDivider c: contentDividers) {
//            System.out.println("LAYOUT DIVIDERS X " + c.getX() + " Y " + c.getY() + " W " + (horizontal ? dividerWidth : width) + " H " + (horizontal ? height : dividerWidth));
            if (horizontal) {
                c.resize(dividerWidth, height);
                positionInArea(c, c.getX() + paddingX, c.getY() + paddingY, dividerWidth, height,
                        /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
            } else {
                c.resize(width, dividerWidth);
                positionInArea(c, c.getX() + paddingX, c.getY() + paddingY, width, dividerWidth,
                        /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
            }
        }

        int index = 0;
        for (ContentDividerButton button : contentDividerButtons) {
//            System.out.println("LAYOUT DIVIDERS X " + c.getX() + " Y " + c.getY() + " W " + (horizontal ? dividerWidth : width) + " H " + (horizontal ? height : dividerWidth));
            if (button.getDivider().getDividerType() == null) {
                continue;
            }
            ContentDivider contentDivider = contentDividers.get(index);
            if (horizontal) {
                button.resize(dividerWidth, 30);
                positionInArea(button, contentDivider.getX() + paddingX, contentDivider.getY() + paddingY + (height/2) - 15, dividerWidth, 30,
                        /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
            } else {
                button.resize(30, dividerWidth);
                positionInArea(button, contentDivider.getX() + paddingX  + (width/2) - 15, contentDivider.getY() + paddingY, 30, dividerWidth,
                        /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
            }
            index++;
        }
    }

    private double previousSize = -1;
    private int lastDividerUpdate = 0;
    private boolean resize = false;
    private boolean checkDividerPos = true;

    @Override protected void layoutChildren(final double x, final double y,
                                            final double w, final double h) {
        final SplitPane s = getSkinnable();
        final double sw = s.getWidth();
        final double sh = s.getHeight();

        if (!s.isVisible() ||
                (horizontal ? sw == 0 : sh == 0) ||
                contentRegions.isEmpty()) {
            return;
        }

        double dividerWidth = contentDividers.isEmpty() ? 0 : contentDividers.get(0).prefWidth(-1);

        if (contentDividers.size() > 0 && previousSize != -1 && previousSize != (horizontal ? sw  : sh)) {
            //This algorithm adds/subtracts a little to each panel on every resize
            List<Content> resizeList = new ArrayList<Content>();
            for (Content c: contentRegions) {
                if (c.isResizableWithParent()) {
                    resizeList.add(c);
                }
            }

            double delta = (horizontal ? s.getWidth() : s.getHeight()) - previousSize;
            boolean growing = delta > 0;

            delta = Math.abs(delta);

            if (delta != 0 && !resizeList.isEmpty()) {
                int portion = (int)(delta)/resizeList.size();
                int remainder = (int)delta%resizeList.size();
                int size = 0;
                if (portion == 0) {
                    portion = remainder;
                    size = remainder;
                    remainder = 0;
                } else {
                    size = portion * resizeList.size();
                }

                while (size > 0 && !resizeList.isEmpty()) {
                    if (growing) {
                        lastDividerUpdate++;
                    } else {
                        lastDividerUpdate--;
                        if (lastDividerUpdate < 0) {
                            lastDividerUpdate = contentRegions.size() - 1;
                        }
                    }
                    int id = lastDividerUpdate%contentRegions.size();
                    Content content = contentRegions.get(id);
                    if (content.isResizableWithParent() && resizeList.contains(content)) {
                        double area = content.getArea();
                        if (growing) {
                            double max = horizontal ? content.maxWidth(-1) : content.maxHeight(-1);
                            if ((area + portion) <= max) {
                                area += portion;
                            } else {
                                resizeList.remove(content);
                                continue;
                            }
                        } else {
                            double min = horizontal ? content.minWidth(-1) : content.minHeight(-1);
                            if ((area - portion) >= min) {
                                area -= portion;
                            } else {
                                resizeList.remove(content);
                                continue;
                            }
                        }
                        content.setArea(area);
                        size -= portion;
                        if (size == 0 && remainder != 0) {
                            portion = remainder;
                            size = remainder;
                            remainder = 0;
                        } else if (size == 0) {
                            break;
                        }
                    }
                }

                // If we are resizing the window save the current area into
                // resizableWithParentArea.  We use this value during layout.
                {
                    for (Content c: contentRegions) {
                        c.setResizableWithParentArea(c.getArea());
                        c.setAvailable(0);
                    }
                }
                resize = true;
            }

            previousSize = horizontal ? sw : sh;
        } else {
            previousSize = horizontal ? sw : sh;
        }

        // If the window is less than the min size we want to resize
        // proportionally
        double minSize = totalMinSize();
        if (minSize > (horizontal ? w : h)) {
            double percentage = 0;
            for (int i = 0; i < contentRegions.size(); i++) {
                Content c = contentRegions.get(i);
                double min = horizontal ? c.minWidth(-1) : c.minHeight(-1);
                percentage = min/minSize;
                c.setArea(snapSpace(percentage * (horizontal ? w : h)));
                c.setAvailable(0);
            }
            setupContentAndDividerForLayout();
            layoutDividersAndContent(w, h);
            resize = false;
            return;
        }

        for(int trys = 0; trys < 10; trys++) {
            // Compute the area in between each divider.
            ContentDivider previousDivider = null;
            ContentDivider divider = null;
            for (int i = 0; i < contentRegions.size(); i++) {
                double space = 0;
                if (i < contentDividers.size()) {
                    divider = contentDividers.get(i);
                    if (divider.posExplicit) {
                        checkDividerPosition(divider, posToDividerPos(divider, divider.d.getPosition()),
                                divider.getDividerPos());
                    }
                    if (i == 0) {
                        // First panel
                        space = getAbsoluteDividerPos(divider);
                    } else {
                        double newPos = getAbsoluteDividerPos(previousDivider) + dividerWidth;
                        // Middle panels
                        if (getAbsoluteDividerPos(divider) <= getAbsoluteDividerPos(previousDivider)) {
                            // The current divider and the previous divider share the same position
                            // or the current divider position is less than the previous position.
                            // We will set the divider next to the previous divider.
                            setAndCheckAbsoluteDividerPos(divider, newPos);
                        }
                        space = getAbsoluteDividerPos(divider) - newPos;
                    }
                } else if (i == contentDividers.size()) {
                    // Last panel
                    space = (horizontal ? w : h) - (previousDivider != null ? getAbsoluteDividerPos(previousDivider) + dividerWidth : 0);
                }
                if (!resize || divider.posExplicit) {
                    contentRegions.get(i).setArea(space);
                }
                previousDivider = divider;
            }

            // Compute the amount of space we have available.
            // Available is amount of space we can take from a panel before we reach its min.
            // If available is negative we don't have enough space and we will
            // proportionally take the space from the other availables.  If we have extra space
            // we will porportionally give it to the others
            double spaceRequested = 0;
            double extraSpace = 0;
            for (Content c: contentRegions) {
                double max = 0;
                double min = 0;
                if (c != null) {
                    max = horizontal ? c.maxWidth(-1) : c.maxHeight(-1);
                    min = horizontal ? c.minWidth(-1) : c.minHeight(-1);
                }

                if (c.getArea() >= max) {
                    // Add the space that needs to be distributed to the others
                    extraSpace += (c.getArea() - max);
                    c.setArea(max);
                }
                c.setAvailable(c.getArea() - min);
                if (c.getAvailable() < 0) {
                    spaceRequested += c.getAvailable();
                }
            }

            spaceRequested = Math.abs(spaceRequested);

            // Add the panels where we can take space from
            List<Content> availableList = new ArrayList<Content>();
            List<Content> storageList = new ArrayList<Content>();
            List<Content> spaceRequestor = new ArrayList<Content>();
            double available = 0;
            for (Content c: contentRegions) {
                if (c.getAvailable() >= 0) {
                    available += c.getAvailable();
                    availableList.add(c);
                }

                if (resize && !c.isResizableWithParent()) {
                    // We are making the SplitPane bigger and will need to
                    // distribute the extra space.
                    if (c.getArea() >= c.getResizableWithParentArea()) {
                        extraSpace += (c.getArea() - c.getResizableWithParentArea());
                    } else {
                        // We are making the SplitPane smaller and will need to
                        // distribute the space requested.
                        spaceRequested += (c.getResizableWithParentArea() - c.getArea());
                    }
                    c.setAvailable(0);
                }
                // Add the panels where we can add space to;
                if (resize) {
                    if (c.isResizableWithParent()) {
                        storageList.add(c);
                    }
                } else {
                    storageList.add(c);
                }
                // List of panels that need space.
                if (c.getAvailable() < 0) {
                    spaceRequestor.add(c);
                }
            }

            if (extraSpace > 0) {
                extraSpace = distributeTo(storageList, extraSpace);
                // After distributing add any panels that may still need space to the
                // spaceRequestor list.
                spaceRequested = 0;
                spaceRequestor.clear();
                available = 0;
                availableList.clear();
                for (Content c: contentRegions) {
                    if (c.getAvailable() < 0) {
                        spaceRequested += c.getAvailable();
                        spaceRequestor.add(c);
                    } else {
                        available += c.getAvailable();
                        availableList.add(c);
                    }
                }
                spaceRequested = Math.abs(spaceRequested);
            }

            if (available >= spaceRequested) {
                for (Content requestor: spaceRequestor) {
                    double min = horizontal ? requestor.minWidth(-1) : requestor.minHeight(-1);
                    requestor.setArea(min);
                    requestor.setAvailable(0);
                }
                // After setting all the space requestors to their min we have to
                // redistribute the space requested to any panel that still
                // has available space.
                if (spaceRequested > 0 && !spaceRequestor.isEmpty()) {
                    distributeFrom(spaceRequested, availableList);
                }

                // Only for resizing.  We should have all the panel areas
                // available computed.  We can total them up and see
                // how much space we have left or went over and redistribute.
                if (resize) {
                    double total = 0;
                    for (Content c: contentRegions) {
                        if (c.isResizableWithParent()) {
                            total += c.getArea();
                        } else {
                            total += c.getResizableWithParentArea();
                        }
                    }
                    total += (dividerWidth * contentDividers.size());
                    if (total < (horizontal ? w : h)) {
                        extraSpace += ((horizontal ? w : h) - total);
                        distributeTo(storageList, extraSpace);
                    } else {
                        spaceRequested += (total - (horizontal ? w : h));
                        distributeFrom(spaceRequested, storageList);
                    }
                }
            }

            setupContentAndDividerForLayout();

            // Check the bounds of every panel
            boolean passed = true;
            for (Content c: contentRegions) {
                double max = horizontal ? c.maxWidth(-1) : c.maxHeight(-1);
                double min = horizontal ? c.minWidth(-1) : c.minHeight(-1);
                if (c.getArea() < min || c.getArea() > max) {
                    passed = false;
                    break;
                }
            }
            if (passed) {
                break;
            }
        }

        layoutDividersAndContent(w, h);
        resize = false;
    }

    private void setAndCheckAbsoluteDividerPos(ContentDivider divider, double value) {
        double oldPos = divider.getDividerPos();
        setAbsoluteDividerPos(divider, value);
        checkDividerPosition(divider, value, oldPos);
    }

    @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
        double minWidth = 0;
        double maxMinWidth = 0;
        for (Content c: contentRegions) {
            minWidth += c.minWidth(-1);
            maxMinWidth = Math.max(maxMinWidth, c.minWidth(-1));
        }
        for (ContentDivider d: contentDividers) {
            minWidth += d.prefWidth(-1);
        }
        if (horizontal) {
            return minWidth + leftInset + rightInset;
        } else {
            return maxMinWidth + leftInset + rightInset;
        }
    }

    @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
        double minHeight = 0;
        double maxMinHeight = 0;
        for (Content c: contentRegions) {
            minHeight += c.minHeight(-1);
            maxMinHeight = Math.max(maxMinHeight, c.minHeight(-1));
        }
        for (ContentDivider d: contentDividers) {
            minHeight += d.prefWidth(-1);
        }
        if (horizontal) {
            return maxMinHeight + topInset + bottomInset;
        } else {
            return minHeight + topInset + bottomInset;
        }
    }

    @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
        double prefWidth = 0;
        double prefMaxWidth = 0;
        for (Content c: contentRegions) {
            prefWidth += c.prefWidth(-1);
            prefMaxWidth = Math.max(prefMaxWidth, c.prefWidth(-1));
        }
        for (ContentDivider d: contentDividers) {
            prefWidth += d.prefWidth(-1);
        }
        if (horizontal) {
            return prefWidth + leftInset + rightInset;
        } else {
            return prefMaxWidth + leftInset + rightInset;
        }
    }

    @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
        double prefHeight = 0;
        double maxPrefHeight = 0;
        for (Content c: contentRegions) {
            prefHeight += c.prefHeight(-1);
            maxPrefHeight = Math.max(maxPrefHeight, c.prefHeight(-1));
        }
        for (ContentDivider d: contentDividers) {
            prefHeight += d.prefWidth(-1);
        }
        if (horizontal) {
            return maxPrefHeight + topInset + bottomInset;
        } else {
            return prefHeight + topInset + bottomInset;
        }
    }


//    private void printDividerPositions() {
//        for (int i = 0; i < contentDividers.size(); i++) {
//            System.out.print("DIVIDER[" + i + "] " + contentDividers.get(i).getDividerPos() + " ");
//        }
//        System.out.println("");
//    }
//
//    private void printAreaAndAvailable() {
//        for (int i = 0; i < contentRegions.size(); i++) {
//            System.out.print("AREA[" + i + "] " + contentRegions.get(i).getArea() + " ");
//        }
//        System.out.println("");
//        for (int i = 0; i < contentRegions.size(); i++) {
//            System.out.print("AVAILABLE[" + i + "] " + contentRegions.get(i).getAvailable() + " ");
//        }
//        System.out.println("");
//        for (int i = 0; i < contentRegions.size(); i++) {
//            System.out.print("RESIZABLEWTIHPARENT[" + i + "] " + contentRegions.get(i).getResizableWithParentArea() + " ");
//        }
//        System.out.println("");
//    }

    class ContentDivider extends StackPane {
        private double initialPos;
        private double dividerPos;
        private double pressPos;
        private SplitPane.Divider d;
        private StackPane grabber;
        private double x;
        private double y;
        private boolean posExplicit;
        private ChangeListener<Number> listener;

        public ContentDivider(SplitPane.Divider d) {
            getStyleClass().setAll("split-pane-divider");

            this.d = d;
            this.initialPos = 0;
            this.dividerPos = 0;
            this.pressPos = 0;

            grabber = new StackPane() {
                @Override protected double computeMinWidth(double height) {
                    return 0;
                }

                @Override protected double computeMinHeight(double width) {
                    return 0;
                }

                @Override protected double computePrefWidth(double height) {
                    return snappedLeftInset() + snappedRightInset();
                }

                @Override protected double computePrefHeight(double width) {
                    return snappedTopInset() + snappedBottomInset();
                }

                @Override protected double computeMaxWidth(double height) {
                    return computePrefWidth(-1);
                }

                @Override protected double computeMaxHeight(double width) {
                    return computePrefHeight(-1);
                }
            };
            setGrabberStyle(horizontal);
            getChildren().add(grabber);

            // TODO register a listener for SplitPane.Divider position
        }

        public SplitPane.Divider getDivider() {
            return this.d;
        }

        public final void setGrabberStyle(boolean horizontal) {
            grabber.getStyleClass().clear();
            grabber.getStyleClass().setAll("vertical-grabber");
            setCursor(Cursor.V_RESIZE);
            if (horizontal) {
                grabber.getStyleClass().setAll("horizontal-grabber");
                setCursor(Cursor.H_RESIZE);
            }
        }

        public double getInitialPos() {
            return initialPos;
        }

        public void setInitialPos(double initialPos) {
            this.initialPos = initialPos;
        }

        public double getDividerPos() {
            return dividerPos;
        }

        public void setDividerPos(double dividerPos) {
            this.dividerPos = dividerPos;
        }

        public double getPressPos() {
            return pressPos;
        }

        public void setPressPos(double pressPos) {
            this.pressPos = pressPos;
        }

        // TODO remove x and y and replace with dividerpos.
        public double getX() {
            return x;
        }

        public void setX(double x) {
            this.x = x;
        }

        public double getY() {
            return y;
        }

        public void setY(double y) {
            this.y = y;
        }

        public ChangeListener<Number> getPosPropertyListener() {
            return listener;
        }

        public void setPosPropertyListener(ChangeListener<Number> listener) {
            this.listener = listener;
        }

        @Override protected double computeMinWidth(double height) {
            return computePrefWidth(height);
        }

        @Override protected double computeMinHeight(double width) {
            return computePrefHeight(width);
        }

        @Override protected double computePrefWidth(double height) {
            return snappedLeftInset() + snappedRightInset();
        }

        @Override protected double computePrefHeight(double width) {
            return snappedTopInset() + snappedBottomInset();
        }

        @Override protected double computeMaxWidth(double height) {
            return computePrefWidth(height);
        }

        @Override protected double computeMaxHeight(double width) {
            return computePrefHeight(width);
        }

        @Override protected void layoutChildren() {
            double grabberWidth = grabber.prefWidth(-1);
            double grabberHeight = grabber.prefHeight(-1);
            double grabberX = (getWidth() - grabberWidth)/2;
            double grabberY = (getHeight() - grabberHeight)/2;
            grabber.resize(grabberWidth, grabberHeight);
            positionInArea(grabber, grabberX, grabberY, grabberWidth, grabberHeight,
                    /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
        }
    }

    static class Content extends StackPane {
        private Node content;
        private Rectangle clipRect;
        private double x;
        private double y;
        private double area;
        private double resizableWithParentArea;
        private double available;

        public Content(Node n) {
            this.clipRect = new Rectangle();
            setClip(clipRect);
            this.content = n;
            if (n != null) {
                getChildren().add(n);
            }
            this.x = 0;
            this.y = 0;
        }

        public Node getContent() {
            return content;
        }

        public double getX() {
            return x;
        }

        public void setX(double x) {
            this.x = x;
        }

        public double getY() {
            return y;
        }

        public void setY(double y) {
            this.y = y;
        }

        // This is the area of the panel.  This will be used as the
        // width/height during layout.
        public double getArea() {
            return area;
        }

        public void setArea(double area) {
            this.area = area;
        }

        // This is the minimum available area for other panels to use
        // if they need more space.
        public double getAvailable() {
            return available;
        }

        public void setAvailable(double available) {
            this.available = available;
        }

        public boolean isResizableWithParent() {
            return SplitPane.isResizableWithParent(content);
        }

        public double getResizableWithParentArea() {
            return resizableWithParentArea;
        }

        // This is used to save the current area during resizing when
        // isResizeableWithParent equals false.
        public void setResizableWithParentArea(double resizableWithParentArea) {
            if (!isResizableWithParent()) {
                this.resizableWithParentArea = resizableWithParentArea;
            } else {
                this.resizableWithParentArea = 0;
            }
        }

        protected void setClipSize(double w, double h) {
            clipRect.setWidth(w);
            clipRect.setHeight(h);
        }

        @Override protected double computeMaxWidth(double height) {
            return snapSize(content.maxWidth(height));
        }

        @Override protected double computeMaxHeight(double width) {
            return snapSize(content.maxHeight(width));
        }
    }

    class ContentDividerButton extends StackPane {
        private double initialPos;
        private double dividerPos;
        private double pressPos;
        private SplitPane.DividerButton d;
        private StackPane grabber;
        private double x;
        private double y;
        private BooleanProperty open;

        public ContentDividerButton(SplitPane.DividerButton d) {
            getStyleClass().setAll("split-pane-divider");

            this.d = d;
            this.initialPos = 0;
            this.dividerPos = 0;
            this.pressPos = 0;
            this.open = new SimpleBooleanProperty(true);

            grabber = new StackPane() {
                @Override protected double computeMinWidth(double height) {
                    return 0;
                }

                @Override protected double computeMinHeight(double width) {
                    return 0;
                }

                @Override protected double computePrefWidth(double height) {
                    return snappedLeftInset() + snappedRightInset();
                }

                @Override protected double computePrefHeight(double width) {
                    return snappedTopInset() + snappedBottomInset();
                }

                @Override protected double computeMaxWidth(double height) {
                    return computePrefWidth(-1);
                }

                @Override protected double computeMaxHeight(double width) {
                    return computePrefHeight(-1);
                }
            };
            setGrabberStyle(horizontal);
            getChildren().add(grabber);

            // TODO register a listener for SplitPane.Divider position
        }

        public SplitPane.DividerButton getDivider() {
            return this.d;
        }

        public final void setGrabberStyle(boolean horizontal) {
            grabber.getStyleClass().clear();
            setCursor(Cursor.HAND);
            if (horizontal) {
                if (this.getDivider().getDividerType() == DividerType.ABOVE) {
                    Polygon polygon = new Polygon(0, 15, 5, 0, 5, 30);
                    grabber.getChildren().add(polygon);
                } else {
                    Polygon polygon = new Polygon(5, 15, 0, 0, 0, 30);
                    grabber.getChildren().add(polygon);
                }
            } else {
                if (getDivider().getDividerType() == DividerType.ABOVE) {
                    Polygon polygon = new Polygon(0, 5, 15, 0, 5, 30);
                    grabber.getChildren().add(polygon);
                } else {
                    Polygon polygon = new Polygon(0, 0, 15, 5, 0, 30);
                    grabber.getChildren().add(polygon);
                }
            }

            this.open.addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                    grabber.getChildren().clear();
                    if (newValue) {
                        if (horizontal) {
                            if (getDivider().getDividerType() == DividerType.ABOVE) {
                                Polygon polygon = new Polygon(0, 15, 5, 0, 5, 30);
                                grabber.getChildren().add(polygon);
                            } else {
                                Polygon polygon = new Polygon(5, 15, 0, 0, 0, 30);
                                grabber.getChildren().add(polygon);
                            }
                        } else {
                            if (getDivider().getDividerType() == DividerType.ABOVE) {
                                Polygon polygon = new Polygon(0, 5, 15, 0, 5, 30);
                                grabber.getChildren().add(polygon);
                            } else {
                                Polygon polygon = new Polygon(0, 0, 15, 5, 0, 30);
                                grabber.getChildren().add(polygon);
                            }
                        }
                    } else {
                        if (horizontal) {
                            if (getDivider().getDividerType() == DividerType.ABOVE) {
                                Polygon polygon = new Polygon(5, 15, 0, 0, 0, 30);
                                grabber.getChildren().add(polygon);
                            } else {
                                Polygon polygon = new Polygon(0, 15, 5, 0, 5, 30);
                                grabber.getChildren().add(polygon);
                            }
                        } else {
                            if (getDivider().getDividerType() == DividerType.ABOVE) {
                                Polygon polygon = new Polygon(0, 0, 15, 5, 0, 30);
                                grabber.getChildren().add(polygon);
                            } else {
                                Polygon polygon = new Polygon(0, 5, 15, 0, 5, 30);
                                grabber.getChildren().add(polygon);
                            }
                        }
                    }
                }
            });
        }

        public double getInitialPos() {
            return initialPos;
        }

        public void setInitialPos(double initialPos) {
            this.initialPos = initialPos;
        }

        public double getDividerPos() {
            return dividerPos;
        }

        public void setDividerPos(double dividerPos) {
            this.dividerPos = dividerPos;
        }

        public double getPressPos() {
            return pressPos;
        }

        public void setPressPos(double pressPos) {
            this.pressPos = pressPos;
        }

        // TODO remove x and y and replace with dividerpos.
        public double getX() {
            return x;
        }

        public void setX(double x) {
            this.x = x;
        }

        public double getY() {
            return y;
        }

        public void setY(double y) {
            this.y = y;
        }

        @Override protected double computeMinWidth(double height) {
            return computePrefWidth(height);
        }

        @Override protected double computeMinHeight(double width) {
            return computePrefHeight(width);
        }

        @Override protected double computePrefWidth(double height) {
            return snappedLeftInset() + snappedRightInset();
        }

        @Override protected double computePrefHeight(double width) {
            return snappedTopInset() + snappedBottomInset();
        }

        @Override protected double computeMaxWidth(double height) {
            return computePrefWidth(height);
        }

        @Override protected double computeMaxHeight(double width) {
            return computePrefHeight(width);
        }

        @Override protected void layoutChildren() {
            double grabberWidth = grabber.prefWidth(-1);
            double grabberHeight = grabber.prefHeight(-1);
            double grabberX = (getWidth() - grabberWidth)/2;
            double grabberY = (getHeight() - grabberHeight)/2;
            grabber.resize(grabberWidth, grabberHeight);
            positionInArea(grabber, grabberX, grabberY, grabberWidth, grabberHeight,
                    /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
        }
    }
}

 


public enum DividerType {
    BELOW, ABOVE;
}

 

DEMO:

        TextField textField = new TextField("AAAA");
        textField.setMinWidth(0);
        SplitPane splitPane = new SplitPane(new StackPane(textField), new VBox(), new VBox());
        splitPane.setDividerButtons(DividerType.ABOVE, DividerType.BELOW);
        // ABOVE:向前折叠,BELOW:向后折叠

 

上一篇:分离调试/开发代码java / javafx的最佳实践


下一篇:关联 JavaFX 和 FXML 和 CSS 文件介绍