'ello.
Another little custom component. This one implements a “Split Panel”, i.e. it can contain two children, with a divider that may be dragged. It supports both horizontal and vertical dividers, as well as one-touch expanders.
There is one little issue. When expanded to the left or the bottom, the buttons seem to lose their click area a bit. It sort of looks like an old bug, but is most likely something stupid i’ve done. I’ll keep trying to fix this, but maybe @t0neg0d has an idea?
The obligatory video …
[video]SplitPanel - YouTube
And the source …
[java]
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector4f;
import tonegod.gui.controls.buttons.ButtonAdapter;
import tonegod.gui.core.Element;
import tonegod.gui.core.ElementManager;
import tonegod.gui.core.layouts.AbstractLayout;
import tonegod.gui.core.layouts.LayoutHelper;
import tonegod.gui.core.utils.UIDUtil;
/**
-
A container that takes two children, and presents a divider bar that may be dragged.
-
Space removed from one side is given to the other and vice versa.
-
<p>
-
Also supported are ‘one touch expander’ buttons that move the divider to the
-
extremities.
-
@author rockfire
*/
public class SplitPanel extends Element {private Orientation orientation;
private Element leftOrTop;
private Element rightOrBottom;
private float dividerLocation = Float.MIN_VALUE;
private float defaultDividerLocationRatio = 0.5f;
private final int dividerSize;
private final ButtonAdapter divider;
private final ButtonAdapter expandLeft;
private final ButtonAdapter expandRight;
private Float beforeExpand;
private boolean useOneTouchExpanders = true;
private final Vector4f margins;/**
- Creates a new instance of the SplitPanel control
- @param screen The screen control the Element is to be added to
-
@param orientation The orientation of the split
*/
public SplitPanel(ElementManager screen, Orientation orientation) {
this(screen, Vector2f.ZERO, screen.getStyle(“SplitPanel”).getVector2f(“defaultSize”), orientation);
}
/**
- Creates a new instance of the SplitPanel control
- @param screen The screen control the Element is to be added to
- @param position A Vector2f containing the x/y position of the Element
- @param dimensions A Vector2f containing the width/height dimensions of the Element
- @param resizeBorders A Vector4f containing the border information used when
- resizing the default image (x = N, y = W, z = E, w = S)
-
@param orientation The orientation of the split
*/
public SplitPanel(ElementManager screen, Vector2f position, Vector2f dimensions, Orientation orientation) {
this(screen, position, dimensions, screen.getStyle(“SplitPanel”).getVector4f(“resizeBorders”),
screen.getStyle(“SplitPanel”).getString(“defaultImg”), orientation);
}
/**
-
Creates a new instance of the SplitPanel control
-
@param screen The screen control the Element is to be added to
-
@param position A Vector2f containing the x/y position of the Element
-
@param dimensions A Vector2f containing the width/height dimensions of the Element
-
@param resizeBorders A Vector4f containing the border information used when
-
resizing the default image (x = N, y = W, z = E, w = S)
-
@param defaultImg The default image to use for the Button
-
@param orientation The orientation of the split
*/
public SplitPanel(ElementManager screen, Vector2f position, Vector2f dimensions, Vector4f resizeBorders, String defaultImg, Orientation orientation) {
super(screen, UIDUtil.getUID(), position, dimensions, resizeBorders, defaultImg);
this.orientation = orientation;// TODO get from style
Vector2f min = screen.getStyle(“SplitPanel”).getVector2f(“minSize”);
if (min != null) {
setMinDimensions(min);
}
margins = screen.getStyle(“SplitPanel”).getVector4f(“margins”);
dividerSize = screen.getStyle(“SplitPanel”).getInt(“dividerSize”);// Layout
setLayout(new SplitLayout(screen));// Divider
divider = new ButtonAdapter(screen) {
private boolean dragged;@Override public void onButtonMouseLeftDown(MouseButtonEvent evt, boolean toggled) { dragged = false; } @Override public void onButtonMouseLeftUp(MouseButtonEvent evt, boolean toggled) { if (!dragged) { if (beforeExpand != null) { dividerLocation = beforeExpand; beforeExpand = null; } else { // Expand to whichever is the closer side beforeExpand = dividerLocation; if (dividerLocation > (SplitPanel.this.getWidth() / 2f)) { dividerLocation = Float.MAX_VALUE; } else { dividerLocation = 0; } } } SplitPanel.this.layoutChildren(); onExpandLeftOrTop(); } @Override public void controlMoveHook() { dragged = true; dividerMoved(); }
};
divider.setIsMovable(true);
divider.setMinDimensions(new Vector2f(dividerSize, dividerSize));
divider.addClippingLayer(divider);
addChild(divider);// Expand left
expandLeft = new ButtonAdapter(screen, Vector2f.ZERO, new Vector2f(dividerSize, dividerSize)) {
@Override
public void onButtonMouseLeftUp(MouseButtonEvent evt, boolean toggled) {
if (beforeExpand != null) {
dividerLocation = beforeExpand;
beforeExpand = null;
} else {
beforeExpand = dividerLocation;
dividerLocation = 0;
}
SplitPanel.this.layoutChildren();
onExpandLeftOrTop();
}
};
expandLeft.setIsMovable(false);
expandLeft.setMinDimensions(new Vector2f(dividerSize, dividerSize));
expandLeft.addClippingLayer(expandLeft);
addChild(expandLeft);// Expand right
expandRight = new ButtonAdapter(screen, Vector2f.ZERO, new Vector2f(dividerSize, dividerSize)) {
@Override
public void onButtonMouseLeftUp(MouseButtonEvent evt, boolean toggled) {
if (beforeExpand != null) {
dividerLocation = beforeExpand;
beforeExpand = null;
} else {
beforeExpand = getDividerLocation();
dividerLocation = Float.MAX_VALUE;
}
SplitPanel.this.layoutChildren();
onExpandRightOrBottom();
}
};
expandRight.setIsMovable(false);
expandRight.setMinDimensions(new Vector2f(dividerSize, dividerSize));
expandRight.addClippingLayer(expandRight);// This
addChild(expandRight);
reconfigureDivider(orientation);
}
@Override
public void controlResizeHook() {
super.controlResizeHook();
layoutChildren();
}/**
- Get the current divider location.
-
@return divider location
*/
public float getDividerLocation() {
return dividerLocation;
}
/**
- Set the current divider location.
-
@param dividerLocation divider location
*/
public void setDividerLocation(float dividerLocation) {
this.dividerLocation = dividerLocation;
layoutChildren();
}
/**
- Get the default divider location ratio which determines where the divider starts.
- For example, if you have a HORIZONTAL split, and set this value to
- 0.25, the divider will start a quarter of its width from the left.
- @return default divider location ration
-
@see #setDefaultDividerLocationRatio(float)
*/
public float getDefaultDividerLocationRatio() {
return defaultDividerLocationRatio;
}
/**
- Set the default divider location ratio which determines where the divider starts.
- For example, if you have a HORIZONTAL split, and set this value to
- 0.25, the divider will start a quarter of its width from the left.
- @param defaultDividerLocationRatio default divider location ration
-
@see #setDefaultDividerLocationRatio(float)
*/
public void setDefaultDividerLocationRatio(float defaultDividerLocationRatio) {
this.defaultDividerLocationRatio = defaultDividerLocationRatio;
layoutChildren();
}
/**
-
Set the element that is placed in the left hand side (for HORIZONTAL orientation)
-
or the top (for VERTICAL orientation).
-
@param leftOrTop left or top element
*/
public void setLeftOrTop(Element leftOrTop) {
if (this.leftOrTop != null) {
removeChild(leftOrTop);
}
this.leftOrTop = leftOrTop;leftOrTop.addClippingLayer(leftOrTop);
addChild(leftOrTop);
layoutChildren();
}
/**
- Set the element that is placed in the right hand side (for HORIZONTAL orientation)
- or the bottom (for VERTICAL orientation).
-
@param rightOrBottom right or bottom element
*/
public void setRightOrBottom(Element rightOrBottom) {
if (this.rightOrBottom != null) {
removeChild(rightOrBottom);
}
this.rightOrBottom = rightOrBottom;
rightOrBottom.addClippingLayer(rightOrBottom);
addChild(rightOrBottom);
layoutChildren();
}
/**
- Get the element that is placed in the left hand side (for HORIZONTAL orientation)
- or the top (for VERTICAL orientation).
-
@param leftOrTop left or top element
*/
public Element getLeftOrTop() {
return leftOrTop;
}
/**
- Get the element that is placed in the right hand side (for HORIZONTAL orientation)
- or the bottom (for VERTICAL orientation).
-
@return right or bottom element
*/
public Element getRightOrBottom() {
return rightOrBottom;
}
/**
- Get the orientation.
-
@return orientation
*/
public Orientation getOrientation() {
return orientation;
}
/**
- Set the orientation.
-
@param orientation orientation
*/
public void setOrientation(Orientation orientation) {
this.orientation = orientation;
reconfigureDivider(orientation);
layoutChildren();
}
/**
- Set whether the one touch expander buttons are visible.
-
@param useOneTouchExpanders use one touch expanders
*/
public void setUseOneTouchExpanders(boolean useOneTouchExpanders) {
if (useOneTouchExpanders != this.useOneTouchExpanders) {
this.useOneTouchExpanders = useOneTouchExpanders;
expandLeft.setIsVisible(useOneTouchExpanders);
expandRight.setIsVisible(useOneTouchExpanders);
layoutChildren();
}
}
/**
- Get whether the one touch expander buttons are visible.
-
@return use one touch expanders
*/
public boolean getUseOneTouchExpanders(boolean useOneTouchExpanders) {
return useOneTouchExpanders;
}
/**
- Invoked when the expanders are used to expand to the right or bottom.
*/
protected void onExpandRightOrBottom() {
// For sub-classes to override
}/**
- Invoked when the expanders are used to expand to the left or top
*/
protected void onExpandLeftOrTop() {
// For sub-classes to override
}
/**
- Invoked when the divider is moved
*/
protected void onDividerMoved() {
// For sub-classes to override
}
protected void dividerMoved() {
beforeExpand = null;
dividerLocation = orientation.equals(Orientation.HORIZONTAL) ? divider.getX() : divider.getY();
layoutChildren();
onDividerMoved();
}private void reconfigureDivider(Orientation orientation) {
final boolean v = orientation.equals(Orientation.VERTICAL);
divider.setResizeN(v);
divider.setResizeS(v);
divider.setResizeE(!v);
divider.setResizeW(!v);
expandLeft.setButtonIcon(dividerSize / 2f, dividerSize / 2f, screen.getStyle(“Common”).getString(v ? “arrowDown” : “arrowLeft”));
expandRight.setButtonIcon(dividerSize / 2f, dividerSize / 2f, screen.getStyle(“Common”).getString(v ? “arrowUp” : “arrowRight”));
String styleName = “SplitPanel#Divider#” + orientation.name();
divider.borders.set(screen.getStyle(styleName).getVector4f(“resizeBorders”));
divider.setColorMap(screen.getStyle(styleName).getString(“defaultImg”));
divider.setButtonHoverInfo(screen.getStyle(styleName).getString(“hoverImg”), screen.getStyle(styleName).getColorRGBA(“hoverColor”));
divider.setButtonPressedInfo(screen.getStyle(styleName).getString(“pressedImg”), screen.getStyle(styleName).getColorRGBA(“pressedrColor”));
}class SplitLayout extends AbstractLayout {
public SplitLayout(ElementManager screen) { super(screen); margins.set(SplitPanel.this.margins); } public void resize() { layoutChildren(); } public void setOwner(Element owner) { this.owner = owner; } public void layoutChildren() { float space; float actualDividerLocation; float leftOrTopWidth; float rightOrBottomWidth; float childSize; LayoutHelper.reset(); LayoutHelper.setPadding(padding.x, padding.y, padding.x, padding.w); LayoutHelper.advanceX(margins.x); LayoutHelper.advanceY(margins.y); if (orientation.equals(Orientation.HORIZONTAL)) { // Space available // Keep divider within bounds if (dividerLocation != Float.MIN_VALUE) { if (dividerLocation < margins.y) { dividerLocation = margins.y; } else if (dividerLocation > owner.getWidth() - dividerSize - margins.z) { dividerLocation = owner.getWidth() - dividerSize - margins.z; } actualDividerLocation = (int) (dividerLocation); } else { actualDividerLocation = (int) (owner.getWidth() * defaultDividerLocationRatio); } // Sizes leftOrTopWidth = actualDividerLocation - margins.y; rightOrBottomWidth = owner.getWidth() - actualDividerLocation - margins.z - dividerSize; childSize = owner.getHeight() - margins.x - margins.w; // Do the actual layout // Layout left if (leftOrTop != null) { if (leftOrTopWidth > 0) { leftOrTop.show(); leftOrTop.setPosition(LayoutHelper.position()); leftOrTop.resize((int) (leftOrTop.getAbsoluteX() + leftOrTopWidth), (int) (leftOrTop.getAbsoluteY() + childSize), Element.Borders.SE); LayoutHelper.advanceX(leftOrTopWidth); } else { leftOrTop.hide(); } } // Layout divider if (useOneTouchExpanders) { expandLeft.setPosition(LayoutHelper.position()); LayoutHelper.advanceY(dividerSize); expandRight.setPosition(LayoutHelper.position()); LayoutHelper.advanceY(dividerSize); } divider.setPosition(LayoutHelper.position()); divider.setDimensions(dividerSize, childSize - (useOneTouchExpanders ? dividerSize * 2 : 0)); if (useOneTouchExpanders) { LayoutHelper.advanceY(-(dividerSize * 2)); } LayoutHelper.advanceX(dividerSize); // Layout right if (rightOrBottom != null) { if (rightOrBottomWidth > 0) { rightOrBottom.show(); rightOrBottom.setPosition(LayoutHelper.position()); rightOrBottom.resize((int) (rightOrBottom.getAbsoluteX() + rightOrBottomWidth), (int) (rightOrBottom.getAbsoluteY() + childSize), Element.Borders.SE); LayoutHelper.advanceX(rightOrBottomWidth); } else { rightOrBottom.hide(); } } } else { // Space available space = owner.getHeight() - dividerSize; // Keep divider within bounds if (dividerLocation != Float.MIN_VALUE) { if (dividerLocation < margins.x) { dividerLocation = margins.x; } else if (dividerLocation > owner.getHeight() - dividerSize - margins.w) { dividerLocation = owner.getHeight() - dividerSize - margins.w; } actualDividerLocation = (int) (dividerLocation); } else { actualDividerLocation = (int) (space - (space * defaultDividerLocationRatio)); } // Sizes leftOrTopWidth = actualDividerLocation - margins.x; rightOrBottomWidth = owner.getHeight() - actualDividerLocation - margins.w - dividerSize; childSize = owner.getWidth() - margins.y - margins.z; // Left if (rightOrBottom != null) { rightOrBottom.setPosition(LayoutHelper.position()); rightOrBottom.resize((int) (leftOrTop.getAbsoluteX() + childSize), (int) (rightOrBottom.getAbsoluteY() + leftOrTopWidth), Element.Borders.SE); LayoutHelper.advanceY(leftOrTopWidth); } // Layout divider if (useOneTouchExpanders) { expandLeft.setPosition(LayoutHelper.position()); LayoutHelper.advanceX(dividerSize); expandRight.setPosition(LayoutHelper.position()); LayoutHelper.advanceX(dividerSize); } divider.setPosition(LayoutHelper.position()); divider.setDimensions(childSize - (useOneTouchExpanders ? dividerSize * 2 : 0), dividerSize); LayoutHelper.advanceY(dividerSize); if (useOneTouchExpanders) { LayoutHelper.advanceX(-(dividerSize * 2)); } // Layout right if (leftOrTop != null) { leftOrTop.setPosition(LayoutHelper.position()); leftOrTop.resize((int) (leftOrTop.getAbsoluteX() + childSize), (int) (leftOrTop.getAbsoluteY() + rightOrBottomWidth), Element.Borders.SE); LayoutHelper.advanceY(rightOrBottomWidth); } } owner.updateClippingLayers(); // Clean up LayoutHelper.reset(); } protected Vector4f getMargins() { return margins; }
}
}
[/java]
And the style XML
[java]
<element name="SplitPanel">
<font></font>
<attributes>
<property name="resizeBorders" type="Vector4f">
<x value="10" />
<y value="10" />
<z value="10" />
<w value="10" />
</property>
<property name="margins" type="Vector4f">
<x value="4" />
<y value="4" />
<z value="4" />
<w value="4" />
</property>
<property name="defaultSize" type="Vector2f">
<x value="350" />
<y value="200" />
</property>
<property name="dividerSize" type="int" value="16" />
</attributes>
<images>
<property name="defaultImg" type="String" value="tonegod/gui/style/def/Window/panel_x.png" />
</images>
<effects>
</effects>
</element>
<element name="SplitPanel#Divider#HORIZONTAL">
<font/>
<attributes>
<property name="resizeBorders" type="Vector4f">
<x value="4" />
<y value="4" />
<z value="4" />
<w value="4" />
</property>
</attributes>
<images>
<property name="defaultImg" type="String" value="tonegod/gui/style/def/Button/button_x_u.png" />
<property name="hoverImg" type="String" value="tonegod/gui/style/def/Button/button_x_h.png" />
<property name="pressedImg" type="String" value="tonegod/gui/style/def/Button/button_x_d.png" />
</images>
<effects></effects>
</element>
<element name="SplitPanel#Divider#VERTICAL">
<font/>
<attributes>
<property name="resizeBorders" type="Vector4f">
<x value="4" />
<y value="4" />
<z value="4" />
<w value="4" />
</property>
</attributes>
<images>
<property name="defaultImg" type="String" value="tonegod/gui/style/def/Button/button_x_u.png" />
<property name="hoverImg" type="String" value="tonegod/gui/style/def/Button/button_x_h.png" />
<property name="pressedImg" type="String" value="tonegod/gui/style/def/Button/button_x_d.png" />
</images>
<effects></effects>
</element>
[/java]
Any comments or feature requests?
RR.