/* * Copyright (c) 2010, 2016, 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 javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; 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.Accordion; import javafx.scene.control.Control; import javafx.scene.control.SkinBase; import javafx.scene.control.SplitPane; import javafx.scene.input.MouseEvent; import javafx.scene.layout.StackPane; import javafx.scene.shape.Rectangle; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ListIterator; /** * Default skin implementation for the {@link SplitPane} control. * * @see SplitPane * @since 9 */ public class SplitPaneSkin extends SkinBase { /*************************************************************************** * * * Private fields * * * **************************************************************************/ private ObservableList contentRegions; private ObservableList contentDividers; private boolean horizontal; /*************************************************************************** * * * Constructors * * * **************************************************************************/ /** * Creates a new SplitPaneSkin 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 SplitPaneSkin(final SplitPane control) { super(control); // control.setManaged(false); horizontal = getSkinnable().getOrientation() == Orientation.HORIZONTAL; contentRegions = FXCollections.observableArrayList(); contentDividers = FXCollections.observableArrayList(); int index = 0; for (Node n: getSkinnable().getItems()) { addContent(index++, n); } initializeContentListener(); for (SplitPane.Divider d: getSkinnable().getDividers()) { addDivider(d); } registerChangeListener(control.orientationProperty(), e -> { this.horizontal = getSkinnable().getOrientation() == Orientation.HORIZONTAL; this.previousSize = -1; for (ContentDivider c: contentDividers) { c.setGrabberStyle(horizontal); } getSkinnable().requestLayout(); }); registerChangeListener(control.widthProperty(), e -> getSkinnable().requestLayout()); registerChangeListener(control.heightProperty(), e -> getSkinnable().requestLayout()); } /*************************************************************************** * * * Public API * * * **************************************************************************/ /** {@inheritDoc} */ @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 resizeList = new ArrayList(); 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; if (horizontal) { c.setArea(snapSpaceX(percentage * w)); } else { c.setArea(snapSpaceY(percentage * 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) { if (c == null) continue; double max = horizontal ? c.maxWidth(-1) : c.maxHeight(-1); double 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 availableList = new ArrayList(); List storageList = new ArrayList(); List spaceRequestor = new ArrayList(); 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; } /** {@inheritDoc} */ @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; } } /** {@inheritDoc} */ @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; } } /** {@inheritDoc} */ @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; } } /** {@inheritDoc} */ @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 implementation * * * **************************************************************************/ 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)) { c.dispose(); getChildren().remove(c); contentRegions.remove(c); break; } } } private void initializeContentListener() { getSkinnable().getItems().addListener((ListChangeListener) 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); } }); } 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 posPropertyListener = new PosPropertyListener(c); c.setPosPropertyListener(posPropertyListener); d.positionProperty().addListener(posPropertyListener); initializeDivderEventHandlers(c); contentDividers.add(c); getChildren().add(c); } private void removeAllDividers() { ListIterator 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 initializeDivderEventHandlers(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(); setAndCheckAbsoluteDividerPos(divider, Math.ceil(divider.getInitialPos() + delta)); e.consume(); }); } 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; } // 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 available, double size) { if (available.isEmpty()) { return size; } size = horizontal ? snapSizeX(size) : snapSizeY(size); int portion = (int)(size)/available.size(); int remainder; while (size > 0 && !available.isEmpty()) { Iterator 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 available) { if (available.isEmpty()) { return size; } size = horizontal ? snapSizeX(size) : snapSizeY(size); int portion = (int)(size)/available.size(); int remainder; while (size > 0 && !available.isEmpty()) { Iterator 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); } } } private double previousSize = -1; private int lastDividerUpdate = 0; private boolean resize = false; private boolean checkDividerPos = true; private void setAndCheckAbsoluteDividerPos(ContentDivider divider, double value) { double oldPos = divider.getDividerPos(); setAbsoluteDividerPos(divider, value); checkDividerPosition(divider, value, oldPos); } /*************************************************************************** * * * Support classes * * * **************************************************************************/ // This listener is to be removed from 'removed' dividers and added to 'added' dividers class PosPropertyListener implements ChangeListener { ContentDivider divider; public PosPropertyListener(ContentDivider divider) { this.divider = divider; } @Override public void changed(ObservableValue observable, Number oldValue, Number newValue) { if (checkDividerPos) { // When checking is enforced, we know that the position was set explicitly divider.posExplicit = true; } getSkinnable().requestLayout(); } } 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 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 getPosPropertyListener() { return listener; } public void setPosPropertyListener(ChangeListener 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); } private void dispose() { getChildren().remove(content); } @Override protected double computeMaxWidth(double height) { return snapSizeX(content.maxWidth(height)); } @Override protected double computeMaxHeight(double width) { return snapSizeY(content.maxHeight(width)); } } }