com.codename1.components.SplitPane Maven / Gradle / Ivy
/*
* Copyright (c) 2012, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.components;
import com.codename1.ui.Button;
import com.codename1.ui.CN;
import com.codename1.ui.Component;
import static com.codename1.ui.ComponentSelector.$;
import com.codename1.ui.ComponentSelector.ComponentClosure;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.FontImage;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.LayeredLayout;
import com.codename1.ui.layouts.LayeredLayout.LayeredLayoutConstraint;
import com.codename1.ui.layouts.LayeredLayout.LayeredLayoutConstraint.Inset;
import com.codename1.ui.plaf.Border;
import java.util.HashSet;
import java.util.Set;
/**
* A Split Pane component.
* A split pane can either be horizontal or vertical, and provides a draggable divider
* between two components. If the {@link #orientation} is {@link #HORIZONTAL_SPLIT}, then the
* child components will be laid out horizontally (side by side with a vertical bar as a divider).
* If the {@link #orientation} is {@link #VERTICAL_SPLIT}, then the components are laid out vertically (one above
* the other.
*
* The bar divider bar includes arrows to collapse and expand the divider also.
*
* Splitpane UI
* The following is an example of a UI that is built around a split pane. This has an outer "horizontal" split pane,
* and the left side has a vertical split pane.
*
* Collapsed:
*
* Expanded:
*
*
* @author Steve Hannah
*/
public class SplitPane extends Container {
/**
* An object to configure settings for a SplitPane. Build an instance of this
* class to define such things as the divider thickness, the insets, and the
* UIIDs to use for the various icons (collapse/expand/drag) on the divider.
* Once you have set all of the settings, you can pass this to {@link #SplitPane(com.codename1.components.SplitPane.Settings, com.codename1.ui.Component, com.codename1.ui.Component) }
* to create the corresponding SplitPane instance.
*/
public static class Settings {
private int orientation=HORIZONTAL_SPLIT;
// leave dividerUIID null so that we create the compound border by default.
// If the dev wants to override the UIID then they're on their own for the border.
private String dividerUIID=null;
private String expandButtonUIID="Label";
private String collapseButtonUIID="Label";
private String dragHandleUIID="Label";
private float dividerThicknessMM=3;
private boolean showExpandCollapseButtons=true;
private boolean showDragHandle=true;
private String minInset="0";
private String preferredInset="50%";
private String maxInset="100%";
private char expandMaterialIcon, collapseMaterialIcon, dragHandleMaterialIcon;
private Image expandIcon, collapseIcon, dragHandleIcon;
/**
* Creates a new Settings with default values.
*/
public Settings() {
}
/**
* Creates a new Settings with the provided orientation, and insets.
* @param orientation The orientation. One of {@link #HORIZONTAL_SPLIT} or {@link #VERTICAL_SPLIT}.
* @param minInset The minimum allowable inset for the divider. The inset should be expressed as a string with both a value and a unit. E.g. "75%", "50mm", "200px".
* @param preferredInset The default preferred inset for the divider. The inset should be expressed as a string with both value and unit. E.g. "75%", "50mm", "20px".
* @param maxInset The maximum allowable inset for the divider. The inset should be expressed as a string with both a value and a unit. E.g. "75%", "50mm", "20px".
*/
public Settings(int orientation, String minInset, String preferredInset, String maxInset) {
this.orientation = orientation;
this.minInset = minInset;
this.preferredInset = preferredInset;
this.maxInset = maxInset;
}
/**
* Sets the orientation.
* @param orientation The orientation. One of {@link #HORIZONTAL_SPLIT} or {@link #VERTICAL_SPLIT}.
* @return Self for chaining.
*/
public Settings orientation(int orientation) {
this.orientation = orientation;
return this;
}
/**
* Sets the UIID to use for the expand button. Default is "Label"
* @param expandButtonUIID The UIID to use for the expand button.
* @return Self for chaining.
* @see #collapseButtonUIID(java.lang.String)
* @see #dragHandleUIID(java.lang.String)
* @see #buttonUIIDs(java.lang.String)
*/
public Settings expandButtonUIID(String expandButtonUIID) {
this.expandButtonUIID = expandButtonUIID;
return this;
}
/**
* Sets the UIID to use for the collapse button. Default is "Label"
* @param collapseButtonUIID The UIID to use for the collapse button.
* @return Self for chaining.
* @see #dragHandleUIID(java.lang.String)
* @see #buttonUIIDs(java.lang.String)
* @see #expandButtonUIID(java.lang.String)
*/
public Settings collapseButtonUIID(String collapseButtonUIID) {
this.collapseButtonUIID = collapseButtonUIID;
return this;
}
/**
* Sets the UIID to use for the drag handle on the divider. Default is "Label"
* @param dragHandleUIID The UIID to use for the drag handle of the divider.
* @return Self for chaining.
* @see #buttonUIIDs(java.lang.String)
* @see #expandButtonUIID(java.lang.String)
* @see #collapseButtonUIID(java.lang.String)
*/
public Settings dragHandleUIID(String dragHandleUIID) {
this.dragHandleUIID = dragHandleUIID;
return this;
}
/**
* Sets the UIID to use for all of the buttons on the divider. This includes the drag handle,
* the collapse button, and the expand button. This is a convenience method that is equivalent of
* calling {@link #expandButtonUIID(java.lang.String) }, {@link #collapseButtonUIID(java.lang.String)},
* and {@link #dragHandleUIID(java.lang.String) } all with the same value.
* @param uiid The UIID to use for the buttons on the divider.
* @return Self for chaining.
*/
public Settings buttonUIIDs(String uiid) {
this.dragHandleUIID = uiid;
this.collapseButtonUIID = uiid;
this.expandButtonUIID = uiid;
return this;
}
/**
* Sets the icon to use for the collapse button.
* @param icon
* @return
*/
public Settings collapseIcon(Image icon) {
this.collapseIcon = icon;
return this;
}
/**
* Sets the icon to use for the expand button.
* @param icon
* @return
*/
public Settings expandIcon(Image icon) {
this.expandIcon = icon;
return this;
}
/**
* Sets the icon to use for the drag handle.
* @param icon
* @return
*/
public Settings dragHandleIcon(Image icon) {
this.dragHandleIcon = icon;
return this;
}
/**
* Sets the material icon to use for the collapse button.
* @param icon
* @return
*/
public Settings collapseMaterialIcon(char icon) {
this.collapseMaterialIcon = icon;
return this;
}
/**
* Sets the material icon to use for the expand button.
* @param icon
* @return
*/
public Settings expandMaterialIcon(char icon) {
this.expandMaterialIcon = icon;
return this;
}
/**
* Sets the material icon to use for the drag handle.
* @param icon
* @return
*/
public Settings dragHandleMaterialIcon(char icon) {
this.dragHandleMaterialIcon = icon;
return this;
}
/**
* Sets the preferred divider thickness in Millimetres.
* @param dividerThicknessMM The divider thickness in Millimetres.
* @return Self for chaining.
*/
public Settings dividerThicknessMM(float dividerThicknessMM) {
this.dividerThicknessMM = dividerThicknessMM;
return this;
}
/**
* A custom UIID to use for the divider. Leave null to use default.
* @param uiid The custom UIID
* @return self for chaining.
*/
public Settings dividerUIID(String uiid) {
dividerUIID = uiid;
return this;
}
/**
* Sets the minimum inset for the divider.
* @param minInset The minimum allowable inset for the divider. The inset should be expressed as a string with both a value and a unit. E.g. "75%", "50mm", "200px".
* @return Self for chaining.
*/
public Settings minInset(String minInset) {
this.minInset = minInset;
return this;
}
/**
* Sets the preferred inset for the divider.
* @param preferredInset The preferred inset for the divider. The inset should be expressed as a string with both a value and a unit. E.g. "75%", "50mm", "200px".
* @return Self for chaining.
*/
public Settings preferredInset(String preferredInset) {
this.preferredInset = preferredInset;
return this;
}
/**
* Sets the max inset for the divider.
* @param maxInset The max inset for the divider. The inset should be expressed as a string with both a value and a unit. E.g. "75%", "50mm", "200px".
* @return
*/
public Settings maxInset(String maxInset) {
this.maxInset = maxInset;
return this;
}
/**
* Sets the min, preferred, and max insets in a single method. This is equivalent of calling {@link #minInset(java.lang.String) },
* {@link #maxInset(java.lang.String) }, and {@link #preferredInset(java.lang.String) } separately.
* @param min The min inset for the divider. The inset should be expressed as a string with both a value and a unit. E.g. "75%", "50mm", "200px".
* @param preferred The preferred inset for the divider. The inset should be expressed as a string with both a value and a unit. E.g. "75%", "50mm", "200px".
* @param max The max inset for the divider.
* @return Self for chaining.
*/
public Settings insets(String min, String preferred, String max) {
this.minInset = min;
this.preferredInset = preferred;
this.maxInset = max;
return this;
}
/**
* Set whether to show the expand/collapse buttons on the divider. Default is {@literal true}.
* @param show {@literal true} to show the expand/collapse buttons. {@literal false} to hide them.
* @return Self for chaining.
*/
public Settings showExpandCollapseButtons(boolean show) {
this.showExpandCollapseButtons = show;
return this;
}
/**
* Set whether to show the drag handle on the divider. Default is {@literal true}.
* @param show {@literal true} to show the expand/collapse buttons. {@literal false} to hide them.
* @return Self for chaining.
*/
public Settings showDragHandle(boolean show) {
this.showDragHandle = show;
return this;
}
}
/**
* The orientation. One of {@link #HORIZONTAL_SPLIT} or {@link #VERTICAL_SPLIT}
*/
private final int orientation;
/**
* Constant used for orientation.
*/
public static final int HORIZONTAL_SPLIT = 0;
/**
* Constant used for orientation.
*/
public static final int VERTICAL_SPLIT = 1;
/**
* Container for the top or left component.
*/
private final Container topOrLeft;
/**
* Container for the bottom or right component.
*/
private final Container bottomOrRight;
/**
* UIID to use for the expand button
*/
private String expandButtonUIID = "Label";
/**
* UIID to use for the collapse button
*/
private String collapseButtonUIID = "Label";
/**
* UIID to use for the drag handle on the divider
*/
private String dragHandleUIID = "Label";
/**
* Material icon for expand button.
*/
private char expandMaterialIcon,
/**
* Material icon for collapse button.
*/
collapseMaterialIcon,
/**
* Material icon for drag handle.
*/
dragHandleMaterialIcon;
/**
* Icon for expand button.
*/
private Image expandIcon,
/**
* Icon for collapse button.
*/
collapseIcon,
/**
* Icon or drag handle
*/
dragHandleIcon;
/**
* The UIID for the divider. Default is null so that we can generate the style and border
* manually.
*/
private String dividerUIID = null;
/**
* The preferred divider thickness in millimetres
*/
private float dividerThicknessMM = 3;
/**
* Whether to show the expand/collapse buttons.
*/
private boolean showExpandCollapseButtons=true;
/**
* Whether to show the drag handle.
*/
private boolean showDragHandle=true;
/**
* The draggable divider.
*/
private final Divider divider;
/**
* The minimum allowable inset for the divider.
*/
private LayeredLayoutConstraint minInset;
/**
* The maximum allowable inset for the divider.
*/
private LayeredLayoutConstraint maxInset;
/**
* The starting preferred inset for the divider. This will be changed over the life of the
* split pane. Any time the user explicitly drags the divider to a new location, that location
* will become the new preferred inset.
*/
private LayeredLayoutConstraint preferredInset;
/**
* Flag to indicate that the split pane is expanded.
*/
private boolean isExpanded;
/**
* Flag to indicate that the split pane is collapsed.
*/
private boolean isCollapsed;
/**
* Creates a new SplitPane.
* @param settings The settings for the split pane (e.g. insets, styles, etc...).
* @param topOrLeft The component to place in the "top" (for vertical), or "left" (for horizontal).
* @param bottomOrRight The component to place in the "bottom" (for vertical) or "right" (for horizontal).
*/
public SplitPane(Settings settings, Component topOrLeft, Component bottomOrRight) {
super(new LayeredLayout());
int orientation = settings.orientation;
String minInset = settings.minInset;
String preferredInset = settings.preferredInset;
String maxInset = settings.maxInset;
this.orientation = orientation;
this.topOrLeft = BorderLayout.center(topOrLeft);
this.bottomOrRight = BorderLayout.center(bottomOrRight);
this.expandButtonUIID = settings.expandButtonUIID;
this.collapseButtonUIID = settings.collapseButtonUIID;
this.dragHandleUIID = settings.dragHandleUIID;
this.dividerThicknessMM = settings.dividerThicknessMM;
this.showDragHandle = settings.showDragHandle;
this.showExpandCollapseButtons = settings.showExpandCollapseButtons;
this.dividerUIID = settings.dividerUIID;
divider = new Divider();
add(this.topOrLeft).add(this.bottomOrRight).add(divider);
LayeredLayout l = (LayeredLayout)getLayout();
this.preferredInset = initDividerInset(l.createConstraint(), preferredInset);
this.minInset = initDividerInset(l.createConstraint(), minInset);
this.maxInset = initDividerInset(l.createConstraint(), maxInset);
l.setInsets(this.topOrLeft, "0 0 0 0")
.setInsets(this.topOrLeft, "0 0 0 0");
this.preferredInset.copyTo(l.getOrCreateConstraint(divider));
switch (orientation) {
case HORIZONTAL_SPLIT : {
l.setReferenceComponentRight(this.topOrLeft, divider, 1f);
l.setReferenceComponentLeft(this.bottomOrRight, divider, 1f);
break;
}
default : {
l.setReferenceComponentBottom(this.topOrLeft, divider, 1f);
l.setReferenceComponentTop(this.bottomOrRight, divider, 1f);
break;
}
}
}
/**
* Creates a new SplitPane.
* @param orientation Either {@link #HORIZONTAL_SPLIT} or {@link #VERTICAL_SPLIT}
* @param topOrLeft The component to place in the "top" (for vertical), or "left" (for horizontal).
* @param bottomOrRight The component to place in the "bottom" (for vertical) or "right" (for horizontal).
* @param minInset The minimum allowable inset for the divider. The inset should be expressed as a string with both a value and a unit. E.g. "75%", "50mm", "200px".
* @param preferredInset The default preferred inset for the divider. The inset should be expressed as a string with both value and unit. E.g. "75%", "50mm", "20px".
* @param maxInset The maximum allowable inset for the divider. The inset should be expressed as a string with both a value and a unit. E.g. "75%", "50mm", "20px".
*/
public SplitPane(int orientation, Component topOrLeft, Component bottomOrRight, String minInset, String preferredInset, String maxInset) {
this(new Settings(orientation, minInset, preferredInset, maxInset), topOrLeft, bottomOrRight);
}
/**
* Changes the minimum, preferred, and maximum insets of the split pane. This will also
* update the current divider position to the supplied preferred inset.
* @param minInset The minimum inset. Can be expressed in pixels (px), millimetres (mm), or percent (%). E.g. "25%"
* @param preferredInset The preferred inset. Can be expressed in pixels (px), millimetres (mm), or percent (%). E.g. "25%"
* @param maxInset Can be expressed in pixels (px), millimetres (mm), or percent (%). E.g. "25%"
*/
public void changeInsets(String minInset, String preferredInset, String maxInset) {
LayeredLayout l = (LayeredLayout)getLayout();
initDividerInset(l.createConstraint(), preferredInset).copyTo(this.preferredInset);
initDividerInset(l.createConstraint(), minInset).copyTo(this.minInset);
initDividerInset(l.createConstraint(), maxInset).copyTo(this.maxInset);
l.setInsets(this.topOrLeft, "0 0 0 0")
.setInsets(this.topOrLeft, "0 0 0 0");
this.preferredInset.copyTo(l.getOrCreateConstraint(divider));
}
/**
* The active inset of the divider.
* @return
*/
private Inset getDividerInset() {
LayeredLayoutConstraint cnst = ((LayeredLayout)getLayout()).getOrCreateConstraint(divider);
return getFixedInset(cnst);
}
/**
* Gets the inset of the divider that is flexible.
* @return
*/
private Inset getAutoInset() {
LayeredLayoutConstraint cnst = ((LayeredLayout)getLayout()).getOrCreateConstraint(divider);
return getAutoInset(cnst);
}
/**
* Gets the inset of the provided constraint that is fixed.
* @param cnst
* @return
*/
private Inset getFixedInset(LayeredLayoutConstraint cnst) {
switch (orientation) {
case VERTICAL_SPLIT :
return cnst.top();
default:
return cnst.left();
}
}
private Inset getMinDividerInset() {
return getFixedInset(minInset);
}
private Inset getMaxDividerInset() {
return getFixedInset(maxInset);
}
private void setDividerInset(String inset) {
getDividerInset().setValueAsString(inset);
clampInset();
}
private Inset getAutoInset(LayeredLayoutConstraint cnst) {
switch (orientation) {
case VERTICAL_SPLIT:
return cnst.bottom();
default:
return cnst.right();
}
}
private Set getZeroInsets(LayeredLayoutConstraint cnst) {
Set out = new HashSet();
switch (orientation) {
case VERTICAL_SPLIT:
out.add(cnst.left());
out.add(cnst.right());
break;
default :
out.add(cnst.top());
out.add(cnst.bottom());
}
return out;
}
private LayeredLayoutConstraint initDividerInset(LayeredLayoutConstraint cnst, String insetVal) {
getFixedInset(cnst).setValueAsString(insetVal);
getAutoInset(cnst).setValueAsString("auto");
for (Inset i : getZeroInsets(cnst)) {
i.setValueAsString("0");
}
return cnst;
}
private void clampInset() {
int px = getDividerInset().getAbsolutePixels(divider);
isCollapsed = false;
isExpanded = false;
Inset minInset = getMinDividerInset();
if (minInset.getAbsolutePixels(divider) >= px) {
minInset.copyTo(getDividerInset());
isCollapsed = true;
isExpanded = false;
}
Inset maxInset = getMaxDividerInset();
if (maxInset.getAbsolutePixels(divider) <= px) {
maxInset.copyTo(getDividerInset());
isExpanded = true;
isCollapsed = false;
}
px = getAutoInset().getAbsolutePixels(divider);
if (px < 0) {
// Make sure that the divider is fully visible
getDividerInset().translatePixels(px, true, divider.getParent());
isExpanded = true;
isCollapsed = false;
}
}
private void setTopOrLeftComponent(Component cmp) {
topOrLeft.removeAll();
topOrLeft.add(BorderLayout.CENTER, cmp);
}
/**
* Sets the component that should be placed in the top section of the split pane.
* @param cmp The component to place on top.
*/
public void setTop(Component cmp) {
setTopOrLeftComponent(cmp);
}
/**
* Sets the component that should be placed in the left section of the split pane.
* @param cmp The component to place on the left.
*/
public void setLeft(Component cmp) {
setTopOrLeftComponent(cmp);
}
private void setBottomOrRightComponent(Component cmp) {
bottomOrRight.removeAll();
bottomOrRight.add(BorderLayout.CENTER, cmp);
}
/**
* Sets the component to be placed on the bottom of the split pane.
* @param cmp The component to place on the bottom.
*/
public void setBottom(Component cmp) {
setBottomOrRightComponent(cmp);
}
/**
* Gets the component that is currently placed in the bottom or right of the split pane.
* @return
*/
public Component getBottomOrRightComponent() {
for (Component c : bottomOrRight) {
return c;
}
return null;
}
/**
* Gets the component that is currently placed in the bottom of the split pane.
* @return
*/
public Component getBottom() {
return getBottomOrRightComponent();
}
/**
* Gets the component that is currently placed in the right of the split pane.
* @return
*/
public Component getRight() {
return getBottomOrRightComponent();
}
/**
* Gets the component that is currently placed in the top or left of the split pane.
* @return
*/
public Component getTopOrLeftComponent() {
for (Component c : topOrLeft) {
return c;
}
return null;
}
/**
* Gets the component that is currently placed in the top of the split pane.
* @return
*/
public Component getTop() {
return getTopOrLeftComponent();
}
/**
* Gets the component that is currently placed in the left of the split pane.
* @return
*/
public Component getLeft() {
return getTopOrLeftComponent();
}
/**
* Sets the component to be placed on the right of the split pane.
* @param cmp The component to place on the right.
*/
public void setRight(Component cmp) {
setBottomOrRightComponent(cmp);
}
/**
* Toggles the split pane between collapsed state and preferred state. E.g. If the inset is currently
* not collapsed, it will collapse it. If it is collapsed, it will open to the last position that the user
* selected.
*/
public void toggleCollapsePreferred() {
if (isCollapsed) {
expand();
} else if (isExpanded) {
collapse(true);
} else {
collapse();
}
}
/**
* Toggles the split pane between expanded state and preferred state. E.g. If the inset is currently expanded,
* then it will be moved to the last position that the user selected. If it is not expanded, it will expand it all the way.
*/
public void toggleExpandPreferred() {
if (isExpanded) {
collapse();
} else if (isCollapsed) {
expand(true);
} else {
expand();
}
}
/**
* Expands the split pane. If it is currently completely collapsed, it will transition to the preferred
* position. If it is in the preferred position, it will expand all the way.
*/
public void expand() {
expand(false);
}
/**
* Expands the split pane. It will either expand it to the preferred position, or the maximum position
* depending on the value of the {@literal force} parameter.
* @param force If this is true, then it will only expand "all the way". It will skip the preferred position if it is
* currently in collapsed state.
*/
public void expand(boolean force) {
if (isCollapsed && !force) {
getFixedInset(preferredInset).copyTo(getDividerInset());
clampInset();
isCollapsed = false;
SplitPane.this.animateLayout(300);
} else if (isExpanded) {
// do nothing
} else {
getFixedInset(maxInset).copyTo(getDividerInset());
clampInset();
isExpanded = true;
SplitPane.this.animateLayout(300);
}
}
/**
* Collapses the aplit pane. If it is currently expanded, then it will shift to the preferred posiiton. If it is
* already in the preferred position, it will collapse all the way to the minimum position.
*/
public void collapse() {
collapse(false);
}
/**
* Collapses the split pane.
* @param force True to force it to collapse to minimum position (skipping preferred position if it is in expanded state).
*/
public void collapse(boolean force) {
if (isCollapsed) {
// do nothing
} else if (isExpanded && !force) {
getFixedInset(preferredInset).copyTo(getDividerInset());
clampInset();
isExpanded = false;
SplitPane.this.animateLayout(300);
} else {
getFixedInset(minInset).copyTo(getDividerInset());
clampInset();
isCollapsed = true;
SplitPane.this.animateLayout(300);
}
}
/**
* Sets the inset of the divider explicitly. This The inset is measured from the top for
* vertical split panes and the left for horizontal split panes. Setting this to "50%" will
* move the divider to the middle point. Setting it to "0" would set it all the way to the
* left/top. This will clamp the value at the minimum and maximum offsets if clamp is true.
* @param inset
* @param clamp True to clamp the inset to prevent it from running off the page.
*/
public void setInset(String inset, boolean clamp) {
getDividerInset().setValueAsString(inset);
isExpanded = false;
isCollapsed = false;
if (clamp) {
clampInset();
}
}
/**
* Sets the inset of the divider explicitly. This The inset is measured from the top for
* vertical split panes and the left for horizontal split panes. Setting this to "50%" will
* move the divider to the middle point. Setting it to "0" would set it all the way to the
* left/top. This will clamp the value at the minimum and maximum offsets.
* @param inset
*/
public void setInset(String inset) {
setInset(inset, true);
}
/**
* Sets the preferred inset of this split pane. The preferred inset will be automatically
* changed whenever the user explicitly moves the divider to a new position.
* @param inset The inset. E.g. "2mm", "25%", "200px".
*/
public void setPreferredInset(String inset) {
getFixedInset(preferredInset).setValueAsString(inset);
}
/**
* Gets the string value of the preferred inset. E.g. "25mm", or "50%". Note: The preferred
* inset is changed automatically when the user drags it to a new location so the value returned here
* may be different than the inset supplied in the constructor.
* @return The current preferred inset of the divider.
*/
public String getPreferredInset() {
return getFixedInset(preferredInset).getValueAsString();
}
/**
* Sets the minimum inset allowed for the divider.
* @param inset The inset. E.g. "2mm", "10%", "200px"
*/
public void setMinInset(String inset) {
getFixedInset(minInset).setValueAsString(inset);
}
/**
* Gets the string value of the minimum inset of the divider. E.g. "25mm", or "50%".
* @return
*/
public String getMinInset() {
return getFixedInset(minInset).getValueAsString();
}
/**
* Sets the maximum inset allowed for the divider.
* @param inset The inset. E.g. "2mm", "10%", "200px"
*/
public void setMaxInset(String inset) {
getFixedInset(maxInset).setValueAsString(inset);
}
/**
* Gets the string value of the maximum inset of the divider. E.g. "25mm", or "50%"
* @return
*/
public String getMaxInset() {
return getFixedInset(maxInset).getValueAsString();
}
/**
* Internal component used as the divider. This responds to drag events and
* updates its own insets. The parent layout is layerd layout, and the left and
* right containers are anchored to the divider so they are automatically resized
* according to the divider's location.
*
*/
private class Divider extends Container {
int pressedX, pressedY, draggedX, draggedY;
LayeredLayoutConstraint pressedPreferredConstraint;
LayeredLayoutConstraint pressedConstraint;
private final Button btnCollapse;
private final Button btnExpand;
private final Label dragHandle;
private boolean inDrag;
private char getCollapseMaterialIcon() {
if (collapseMaterialIcon != 0) {
return collapseMaterialIcon;
}
switch (orientation) {
case HORIZONTAL_SPLIT:
return 0xe314;
default:
return 0xe316;
}
}
private char getExpandMaterialIcon() {
if (expandMaterialIcon != 0) {
return expandMaterialIcon;
}
switch (orientation) {
case HORIZONTAL_SPLIT:
return 0xe315;
default :
return 0xe313;
}
}
private Image getDragIconImage() {
Image img = null;
if (dragHandleIcon != null) {
img = dragHandleIcon;
} else {
char materialIcon = FontImage.MATERIAL_DRAG_HANDLE;
if (dragHandleMaterialIcon != 0) {
materialIcon = dragHandleMaterialIcon;
}
img = FontImage.createMaterial(materialIcon, getStyle(), 3);
}
switch (orientation) {
case HORIZONTAL_SPLIT:
return img.rotate90Degrees(true);
default:
return img;
}
}
private int getDragCursor() {
return orientation == HORIZONTAL_SPLIT ? Component.E_RESIZE_CURSOR : Component.N_RESIZE_CURSOR;
}
private Border createBorder() {
return orientation == HORIZONTAL_SPLIT ? Border.createCompoundBorder(
Border.createEmpty(),
Border.createEmpty(),
Border.createBevelRaised(),
Border.createBevelRaised()) :
Border.createCompoundBorder(
Border.createBevelRaised(),
Border.createBevelRaised(),
Border.createEmpty(),
Border.createEmpty()
);
}
Divider() {
super(new LayeredLayout());
if (dividerUIID != null) {
setUIID(dividerUIID);
}
btnCollapse = $(new Button())
.setUIID(collapseButtonUIID)
.setCursor(Component.HAND_CURSOR)
.each(new ComponentClosure() {
@Override
public void call(Component c) {
if (collapseIcon != null) {
((Label)c).setIcon(collapseIcon);
} else {
((Label)c).setMaterialIcon(getCollapseMaterialIcon());
}
}
})
.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
collapse();
}
})
.selectAllStyles()
.setMargin(0)
.setPadding(0)
.asComponent(Button.class);
btnExpand = $(new Button())
.setCursor(Component.HAND_CURSOR)
.setUIID(expandButtonUIID)
.each(new ComponentClosure() {
@Override
public void call(Component c) {
if (expandIcon != null) {
((Label)c).setIcon(expandIcon);
} else {
((Label)c).setMaterialIcon(getExpandMaterialIcon());
}
}
})
.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
expand();
}
})
.selectAllStyles()
.setMargin(0)
.setPadding(0)
.asComponent(Button.class);
dragHandle = $(new Label())
.setUIID(dragHandleUIID)
.setIcon(getDragIconImage())
.setMargin(0)
.setIgnorePointerEvents(true)
.setPadding(0)
.setDraggable(true)
.setCursor(getDragCursor())
.asComponent(Label.class);
if (showExpandCollapseButtons) {
add(btnCollapse).add(btnExpand);
}
if (showDragHandle) {
add(dragHandle);
}
boolean isDesktop = CN.isDesktop();
LayeredLayout l = (LayeredLayout)getLayout();
switch (orientation) {
case HORIZONTAL_SPLIT: {
l.setInsets(btnCollapse, "0 0 auto 0")
.setInsets(btnExpand, "0 0 auto 0")
.setInsets(dragHandle, "auto auto auto auto")
.setReferenceComponentTop(btnExpand, btnCollapse, 1f);
if (!isDesktop) {
// On tablets and phones it is difficult to use the collapse
// expand buttons when they are adjacent.
// On these devices we'll place them at opposite ends of the divider
l.setInsets(btnExpand, "auto 0 0 0")
.setReferenceComponentTop(btnExpand, null, 1f);
}
break;
}
default: {
l.setInsets(btnCollapse, "0 auto 0 0")
.setInsets(btnExpand, "0 auto 0 0")
.setInsets(dragHandle, "auto auto auto auto")
.setReferenceComponentLeft(btnExpand, btnCollapse, 1f);
if (!isDesktop) {
// On tablets and phones it is difficult to use the collapse
// expand buttons when they are adjacent.
// On these devices we'll place them at opposite ends of the divider
l.setInsets(btnExpand, "0 0 0 auto")
.setReferenceComponentLeft(btnExpand, null, 1f);
}
}
}
if (dividerUIID == null) {
$(this)
.setBorder(createBorder())
.setCursor(getDragCursor())
.setDraggable(true)
;
} else {
$(this)
.setCursor(getDragCursor())
.setDraggable(true)
;
}
}
@Override
protected boolean isStickyDrag() {
return true;
}
@Override
protected void initComponent() {
super.initComponent();
getComponentForm().setEnableCursors(true);
}
@Override
protected Dimension calcPreferredSize() {
Display d = Display.getInstance();
switch (orientation) {
case VERTICAL_SPLIT : return new Dimension(d.getDisplayWidth(), d.convertToPixels(dividerThicknessMM));
default: return new Dimension(d.convertToPixels(dividerThicknessMM), d.getDisplayHeight());
}
}
@Override
public void pointerPressed(int x, int y) {
super.pointerPressed(x, y);
pressedX = x;
pressedY = y;
pressedConstraint = ((LayeredLayout)getLayout()).getOrCreateConstraint(this).copy();
pressedPreferredConstraint = preferredInset.copy();
inDrag = true;
pointerDragged(x, y);
}
@Override
public void pointerDragged(int x, int y) {
super.pointerDragged(x, y);
if (!inDrag) {
return;
}
setVisible(true);
draggedX = x;
draggedY = y;
updateInsets();
SplitPane.this.revalidate();
}
@Override
public void pointerReleased(int x, int y) {
super.pointerReleased(x, y);
inDrag = false;
}
@Override
protected void dragFinished(int x, int y) {
super.dragFinished(x, y);
if (!isExpanded && !isCollapsed) {
getDividerInset().constraint().copyTo(preferredInset);
}
inDrag = false;
}
private void updateInsets() {
LayeredLayout ll = (LayeredLayout)SplitPane.this.getLayout();
LayeredLayoutConstraint cnst = pressedConstraint.copy();
int diff = 0;
if (orientation == HORIZONTAL_SPLIT) {
diff = draggedX - pressedX;
cnst.left().translatePixels(diff, false, getParent());
} else {
diff = draggedY - pressedY;
cnst.top().translatePixels(diff, false, getParent());
}
cnst.copyTo(ll.getOrCreateConstraint(this));
clampInset();
}
@Override
protected Image getDragImage() {
return null;
}
@Override
protected void drawDraggedImage(Graphics g, Image img, int x, int y) {
}
@Override
protected int getDragRegionStatus(int x, int y) {
switch (orientation) {
case HORIZONTAL_SPLIT:
return Component.DRAG_REGION_IMMEDIATELY_DRAG_X;
default:
return Component.DRAG_REGION_IMMEDIATELY_DRAG_Y;
}
}
}
}