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:向后折叠