javafx.scene.control.skin.SplitPaneSkin Maven / Gradle / Ivy
/*
* Copyright (c) 2010, 2022, 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 java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
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.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 com.sun.javafx.scene.control.ListenerHelper;
/**
* 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 ListenerHelper contentDividerListenerHelper;
private boolean horizontal;
/**
* Flag which is used to determine whether we need to request layout when a divider position changed or not.
* E.g. We don't want to request layout when we are changing the divider position in
* {@link #layoutChildren(double, double, double, double)} since we are currently doing the layout.
* See also: JDK-8277122
*/
private boolean duringLayout;
/* *************************************************************************
* *
* 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);
horizontal = getSkinnable().getOrientation() == Orientation.HORIZONTAL;
contentRegions = FXCollections.observableArrayList();
contentDividers = FXCollections.observableArrayList();
int index = 0;
for (Node n: getSkinnable().getItems()) {
addContent(index++, n);
}
initializeContentListener();
addDividers();
ListenerHelper lh = ListenerHelper.get(this);
lh.addChangeListener(control.orientationProperty(), (v) -> {
this.horizontal = getSkinnable().getOrientation() == Orientation.HORIZONTAL;
this.previousSize = -1;
for (ContentDivider c: contentDividers) {
c.setGrabberStyle(horizontal);
}
getSkinnable().requestLayout();
});
lh.addChangeListener(control.widthProperty(), (v) -> getSkinnable().requestLayout());
lh.addChangeListener(control.heightProperty(), (v) -> getSkinnable().requestLayout());
}
/* *************************************************************************
* *
* Public API *
* *
**************************************************************************/
@Override
public void dispose() {
removeAllDividers();
super.dispose();
}
/** {@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 ((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;
}
duringLayout = true;
// 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;
duringLayout = 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);
duringLayout = false;
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() {
ListenerHelper.get(this).addListChangeListener(getSkinnable().getItems(), (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);
}
}
}
removeAllDividers();
addDividers();
});
}
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 addDividers() {
contentDividerListenerHelper = new ListenerHelper();
for (SplitPane.Divider d : getSkinnable().getDividers()) {
ContentDivider c = new ContentDivider(d);
c.setInitialPos(d.getPosition());
c.setDividerPos(-1);
ChangeListener li = new PosPropertyListener(c);
contentDividerListenerHelper.addChangeListener(d.positionProperty(), li);
initializeDividerEventHandlers(c);
contentDividers.add(c);
getChildren().add(c);
}
}
private void removeAllDividers() {
ListIterator dividers = contentDividers.listIterator();
while (dividers.hasNext()) {
ContentDivider c = dividers.next();
getChildren().remove(c);
dividers.remove();
}
lastDividerUpdate = 0;
if (contentDividerListenerHelper != null) {
contentDividerListenerHelper.disconnect();
contentDividerListenerHelper = null;
}
}
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();
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 extends Number> observable, Number oldValue, Number newValue) {
if (checkDividerPos) {
// When checking is enforced, we know that the position was set explicitly
divider.posExplicit = true;
}
if (!duringLayout) {
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;
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;
}
@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));
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy