OK, thanks for the clarification.
But there is one thing that still doesn’t make sense to me.
In Element.addChild and Screen.addChild you have code that inverts the y position (typically as provided by the constructor) when a child is “initialized.” But any other time calling setY doesn’t invert the y axis. Why the inconstancy? Am I missing something?
Anyway, I was able to come up with (IMO) a simple yet powerful pattern for doing dynamic layout. In use it looks something like the following. Note that the layout is actually done when the state is enabled and can be refreshed at any time. The calls in initialize just create a LayoutHelper control and attach it to the element.
No changes to tonegodGUI are needed to use this pattern.
[java]
private Vector2f dpiScale;
private Panel panel;
private Button button;
@Override
public final void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
panel = new Panel(
screen,
LayoutHelper.autoPosition(), // initialize with new Vector2f(NaN, NaN)
LayoutHelper.autoSize() // initialize with new Vector2f(NaN, NaN)
);
dpiScale = new Vector2f(); // will be filled before layout is applied
// setup layout that will happen later
ScaleLayout.dimensions(panel, dpiScale, 0.75f, 0.75f);
ScaleLayout.left(panel, dpiScale, 0.05f);
PixelLayout.centerVertical(panel);
button = new ButtonAdapter(
screen,
LayoutHelper.autoPosition(),
LayoutHelper.autoSize()
);
// again, setup layout for later
PercentLayout.fill(button, 0.05f);
panel.addChild(button);
}
public void setEnabled(boolean enabled) {
if(enabled) {
screen.addElement(panel);
// fill in the dpi scale that is used in layout
// deviceState is what my app uses to encapsulate Android dependencies
deviceState.getDpi(dpiScale);
// apply the layout
LayoutHelper.layoutForScreen(panel);
} else {
screen.removeElement(panel);
}
}
[/java]
LayoutHelper looks like this:
[java]
import java.util.logging.Level;
import java.util.logging.Logger;
import tonegod.gui.core.Element;
import com.jme3.math.Vector2f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
public abstract class LayoutHelper extends AbstractControl {
private final Logger log = Logger.getLogger(this.getClass().getName());
private final static Level LOG_LEVEL = Level.FINE;
private Element element;
protected void log(String msg, Object ... params) {
if(log.isLoggable(LOG_LEVEL)) {
log.log(LOG_LEVEL, msg + " -> position: " + element.getPosition() + ", dimensions: " + element.getDimensions() + " on " + element, params);
}
}
@Override
public void setSpatial(Spatial spatial) {
if(!(spatial instanceof Element)) {
throw new RuntimeException("Wrong type of spatial");
}
this.element = (Element)spatial;
super.setSpatial(spatial);
}
/**
* Override to update element's layout before the parent's layout has been updated.
* Use this when the parent's layout depends on the layout of the child (e.g. size
* panel to fit children).
*/
protected void beforeParent() {};
/**
* Override to update element's layout to reflect changes in child layout but before
* the parent's layout is updated.
*/
protected void afterChildren() {}
/**
* Override to update element's layout after the parent's layout has been updated.
* Use this when the child's layout depends on the layout of the parent (e.g. size
* children to fit in panel).
*/
protected void afterParent() {};
/**
* Width of parent element or screen.
*/
public float getContainerWidth() {
Element parent = element.getElementParent();
if(parent != null) {
return parent.getWidth();
} else {
return element.getScreen().getWidth();
}
}
/**
* Height of parent element or screen.
*/
public float getContainerHeight() {
Element parent = element.getElementParent();
if(parent != null) {
return parent.getHeight();
} else {
return element.getScreen().getHeight();
}
}
@Override
protected void controlUpdate(float tpf) {
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
public static void layoutForScreen(Element element) {
if(element.getElementParent() != null) {
throw new RuntimeException("Not a root element");
}
// call afterParent (the parent is the screen) for current element
for(int i = 0; i < element.getNumControls(); ++i) {
Control control = element.getControl(i);
if(control instanceof LayoutHelper) {
((LayoutHelper)control).afterParent();
}
}
// call beforeParent and afterChildren for child elements recursively
for(Spatial spatial : element.getChildren()) {
if(spatial instanceof Element) {
visitBeforeParent((Element)spatial);
}
}
// call afterChildren for current element
for(int i = 0; i < element.getNumControls(); ++i) {
Control control = element.getControl(i);
if(control instanceof LayoutHelper) {
((LayoutHelper)control).afterChildren();
}
}
// call afterParent for child elements recursively
for(Spatial spatial : element.getChildren()) {
if(spatial instanceof Element) {
visitAfterParent((Element)spatial);
}
}
}
private static void visitBeforeParent(Element element) {
// beforeParent for current element
for(int i = 0; i < element.getNumControls(); ++i) {
Control control = element.getControl(i);
if(control instanceof LayoutHelper) {
((LayoutHelper)control).beforeParent();
}
}
// call beforeParent and afterChildren for child elements recursively
for(Spatial spatial : element.getChildren()) {
if(spatial instanceof Element) {
visitBeforeParent((Element)spatial);
}
}
// call afterChildren for current element
for(int i = 0; i < element.getNumControls(); ++i) {
Control control = element.getControl(i);
if(control instanceof LayoutHelper) {
((LayoutHelper)control).afterChildren();
}
}
}
private static void visitAfterParent(Element element) {
// afterParent for current element
for(int i = 0; i < element.getNumControls(); ++i) {
Control control = element.getControl(i);
if(control instanceof LayoutHelper) {
((LayoutHelper)control).afterParent();
}
}
// afterParent for child elements recursively
for(Spatial spatial : element.getChildren()) {
if(spatial instanceof Element) {
visitAfterParent((Element)spatial);
}
}
}
public static Vector2f autoPosition() {
return new Vector2f(Float.NaN, Float.NaN);
}
public static Vector2f autoSize() {
return new Vector2f(Float.NaN, Float.NaN);
}
}
[/java]
The PixelLayout, ScaleLayout, and PercentLayout classes are:
[java]
import tonegod.gui.core.Element;
public class PixelLayout {
public static void position(final Element element, final float left, final float bottom) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(left);
element.setY(bottom);
log("position - left: {0}, bottom: {1}", left, bottom);
}
}
);
}
public static void left(final Element element, final float left) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(left);
log("left: {0}", left);
}
}
);
}
public static void bottom(final Element element, final float bottom) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setY(bottom);
log("bottom: {0}", bottom);
}
}
);
}
public static void dimensions(final Element element, final float width, final float height) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setWidth(width);
element.setHeight(height);
log("dimensions - width: {0}, height: {1}", width, height);
}
}
);
}
public static void width(final Element element, final float width) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setWidth(width);
log("width: {0}", width);
}
}
);
}
public static void height(final Element element, final float height) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setHeight(height);
log("height: {0}", height);
}
}
);
}
public static void fillWidth(final Element element, final float leftMargin, final float rightMargin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(leftMargin);
element.setWidth(getContainerWidth() - (leftMargin + rightMargin));
log("fillWidth - leftMargin: {0}, rightMargin: {1}", leftMargin, rightMargin);
}
}
);
}
public static void fillWidth(final Element element, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(margin);
element.setWidth(getContainerWidth() - (2 * margin));
log("fillWidth - margin: {0}", margin);
}
}
);
}
public static void fillHeight(final Element element, final float bottomMargin, final float topMargin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setY(bottomMargin);
element.setHeight(getContainerHeight() - (bottomMargin + topMargin));
log("fillHeight - bottomMargin: {0}, topMaring: {1}", bottomMargin, topMargin);
}
}
);
}
public static void fillHeight(final Element element, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setY(margin);
element.setHeight(getContainerHeight() - (2 * margin));
log("fillHeight - margin: {0}", margin);
}
}
);
}
public static void fill(final Element element, final float leftMargin, final float bottomMargin, final float rightMargin, final float topMargin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(leftMargin);
element.setY(bottomMargin);
element.setWidth(getContainerWidth() - (leftMargin + rightMargin));
element.setHeight(getContainerHeight() - (bottomMargin + topMargin));
log("fill - leftMargin: {0}, bottomMargin: {1}, rightMargin: {2}, topMargin: {3}", leftMargin, bottomMargin, rightMargin, topMargin);
}
}
);
}
public static void fill(final Element element, final float horizontalMargin, final float verticalMargin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(horizontalMargin);
element.setY(verticalMargin);
element.setWidth(getContainerWidth() - (2 * horizontalMargin));
element.setHeight(getContainerHeight() - (2 * verticalMargin));
log("fill - horizontalMargin: {0}, verticalMargin: {1}", horizontalMargin, verticalMargin);
}
}
);
}
public static void fill(final Element element, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(margin);
element.setY(margin);
element.setWidth(getContainerWidth() - (2 * margin));
element.setHeight(getContainerHeight() - (2 * margin));
log("fill - margin: {0}", margin);
}
}
);
}
public static void extendUp(final Element element, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setHeight(getContainerHeight() - (element.getY() + margin));
log("extendUp - margin: {0}", margin);
}
}
);
}
public static void extendUpTo(final Element element, final float margin, final Element target) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setHeight(target.getY() - element.getY() - margin);
log("extendUpTo - margin: {0}, target: {1}", margin, target);
}
}
);
}
public static void extendRight(final Element element, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setWidth(getContainerWidth() - (element.getX() + margin));
log("extendRight - margin: {0}", margin);
}
}
);
}
public static void extendRightTo(final Element element, final float margin, final Element target) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setHeight(target.getX() - element.getX() - margin);
log("extendRightTo - margin: {0}, target: {1}", margin, target);
}
}
);
}
public static void centerVertical(final Element element) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setY((getContainerHeight() / 2) - (element.getHeight() / 2));
log("centerVertical");
}
}
);
}
public static void centerHorizontal(final Element element) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX((getContainerWidth() / 2) - (element.getWidth() / 2));
log("centerVertical");
}
}
);
}
}
[/java]
[java]
import com.jme3.math.Vector2f;
import tonegod.gui.core.Element;
public class ScaleLayout {
public static void position(final Element element, final Vector2f scale, final float left, final float bottom) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(left * scale.x);
element.setY(bottom * scale.y);
log("position - scale: {0}, left: {1}, bottom: {2}", left, bottom);
}
}
);
}
public static void left(final Element element, final Vector2f scale, final float left) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(left * scale.x);
log("left - scale: {0}, left: {1}", scale, left);
}
}
);
}
public static void bottom(final Element element, final Vector2f scale, final float bottom) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setY(bottom * scale.y);
log("bottom - scale: {0}, bottom: {1}", scale, bottom);
}
}
);
}
public static void dimensions(final Element element, final Vector2f scale, final float width, final float height) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setWidth(width * scale.x);
element.setHeight(height * scale.y);
log("dimensions - scale: {0}, width: {1}, height: {2}", scale, width, height);
}
}
);
}
public static void width(final Element element, final Vector2f scale, final float width) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setWidth(width * scale.x);
log("width - scale: {0}, width: {1}", scale, width);
}
}
);
}
public static void height(final Element element, final Vector2f scale, final float height) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setHeight(height * scale.y);
log("height - scale: {0}, height: {1}", scale, height);
}
}
);
}
public static void fillWidth(final Element element, final Vector2f scale, final float leftMargin, final float rightMargin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(leftMargin * scale.x);
element.setWidth(getContainerWidth() - ((leftMargin + rightMargin) * scale.x));
log("fillWidth - scale: {0}, leftMargin: {1}, rightMargin: {2}", scale, leftMargin, rightMargin);
}
}
);
}
public static void fillWidth(final Element element, final Vector2f scale, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(margin * scale.x);
element.setWidth(getContainerWidth() - (2 * margin * scale.x));
log("fillWidth - scale: {0}, margin: {1}", scale, margin);
}
}
);
}
public static void fillHeight(final Element element, final Vector2f scale, final float bottomMargin, final float topMargin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setY(bottomMargin * scale.y);
element.setHeight(getContainerHeight() - ((bottomMargin + topMargin) * scale.y));
log("fillHeight - scale: {0}, bottomMargin: {1}, topMaring: {2}", scale, bottomMargin, topMargin);
}
}
);
}
public static void fillHeight(final Element element, final Vector2f scale, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setY(margin * scale.y);
element.setHeight(getContainerHeight() - (2 * margin * scale.y));
log("fillHeight - scale: {0}, margin: {1}", scale, margin);
}
}
);
}
public static void fill(final Element element, final Vector2f scale, final float leftMargin, final float bottomMargin, final float rightMargin, final float topMargin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(leftMargin * scale.x);
element.setY(bottomMargin * scale.y);
element.setWidth(getContainerWidth() - (leftMargin + rightMargin) * scale.x);
element.setHeight(getContainerHeight() - (bottomMargin + topMargin) * scale.y);
log("fill - scale: {0}, leftMargin: {1}, bottomMargin: {2}, rightMargin: {3}, topMargin: {4}", scale, leftMargin, bottomMargin, rightMargin, topMargin);
}
}
);
}
public static void fill(final Element element, final Vector2f scale, final float horizontalMargin, final float verticalMargin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(horizontalMargin * scale.x);
element.setY(verticalMargin * scale.y);
element.setWidth(getContainerWidth() - (2 * horizontalMargin * scale.x));
element.setHeight(getContainerHeight() - (2 * verticalMargin * scale.y));
log("fill - scale: {0}, horizontalMargin: {1}, verticalMargin: {2}", scale, horizontalMargin, verticalMargin);
}
}
);
}
public static void fill(final Element element, final Vector2f scale, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(margin * scale.x);
element.setY(margin * scale.y);
element.setWidth(getContainerWidth() - (2 * margin * scale.x));
element.setHeight(getContainerHeight() - (2 * margin * scale.y));
log("fill - scale: {0}, margin: {1}", scale, margin);
}
}
);
}
public static void extendUp(final Element element, final Vector2f scale, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setHeight(getContainerHeight() - (element.getY() + margin * scale.y));
log("extendUp - scale: {0}, margin: {1}", scale, margin);
}
}
);
}
public static void extendUpTo(final Element element, final Vector2f scale, final float margin, final Element target) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setHeight(target.getY() - element.getY() - margin * scale.y);
log("extendUpTo - scale: {0}, margin: {1}, target: {2}", scale, margin, target);
}
}
);
}
public static void extendRight(final Element element, final Vector2f scale, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setWidth(getContainerWidth() - (element.getX() + margin * scale.x));
log("extendRight - scale: {0}, margin: {1}", scale, margin);
}
}
);
}
public static void extendRightTo(final Element element, final Vector2f scale, final float margin, final Element target) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setHeight(target.getX() - element.getX() - margin * scale.x);
log("extendRightTo - scale: {0}, margin: {1}, target: {2}", scale, margin, target);
}
}
);
}
}
[/java]
[java]
import tonegod.gui.core.Element;
public class PercentLayout {
public static void position(final Element element, final float left, final float bottom) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(left * getContainerWidth());
element.setY(bottom * getContainerHeight());
log("position - left: {0}, bottom: {1}", left, bottom);
}
}
);
}
public static void left(final Element element, final float left) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(left * getContainerWidth());
log("left: {0}", left);
}
}
);
}
public static void bottom(final Element element, final float bottom) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setY(bottom * getContainerHeight());
log("bottom: {0}", bottom);
}
}
);
}
public static void dimensions(final Element element, final float width, final float height) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setWidth(width * getContainerWidth());
element.setHeight(height * getContainerHeight());
log("dimensions - width: {0}, height: {1}", width, height);
}
}
);
}
public static void width(final Element element, final float width) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setWidth(width * getContainerWidth());
log("width: {0}", width);
}
}
);
}
public static void height(final Element element, final float height) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setHeight(height * getContainerHeight());
log("height: {0}", height);
}
}
);
}
public static void fillWidth(final Element element, final float leftMargin, final float rightMargin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(leftMargin * getContainerWidth());
element.setWidth(getContainerWidth() - (leftMargin + rightMargin) * getContainerWidth());
log("fillWidth - leftMargin: {0}, rightMargin: {1}", leftMargin, rightMargin);
}
}
);
}
public static void fillWidth(final Element element, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(margin * getContainerWidth());
element.setWidth(getContainerWidth() - (2 * margin * getContainerWidth()));
log("fillWidth - margin: {0}", margin);
}
}
);
}
public static void fillHeight(final Element element, final float bottomMargin, final float topMargin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setY(bottomMargin * getContainerHeight());
element.setHeight(getContainerHeight() - (bottomMargin + topMargin) * getContainerHeight());
log("fillHeight - bottomMargin: {0}, topMaring: {1}", bottomMargin, topMargin);
}
}
);
}
public static void fillHeight(final Element element, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setY(margin * getContainerHeight());
element.setHeight(getContainerHeight() - (2 * margin * getContainerHeight()));
log("fillHeight - margin: {0}", margin);
}
}
);
}
public static void fill(final Element element, final float leftMargin, final float bottomMargin, final float rightMargin, final float topMargin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(leftMargin * getContainerWidth());
element.setY(bottomMargin * getContainerHeight());
element.setWidth(getContainerWidth() - (leftMargin + rightMargin) * getContainerWidth());
element.setHeight(getContainerHeight() - (bottomMargin + topMargin) * getContainerHeight());
log("fill - leftMargin: {0}, bottomMargin: {1}, rightMargin: {2}, topMargin: {3}", leftMargin, bottomMargin, rightMargin, topMargin);
}
}
);
}
public static void fill(final Element element, final float horizontalMargin, final float verticalMargin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(horizontalMargin * getContainerWidth());
element.setY(verticalMargin * getContainerHeight());
element.setWidth(getContainerWidth() - (2 * horizontalMargin * getContainerWidth()));
element.setHeight(getContainerHeight() - (2 * verticalMargin * getContainerHeight()));
log("fill - horizontalMargin: {0}, verticalMargin: {1}", horizontalMargin, verticalMargin);
}
}
);
}
public static void fill(final Element element, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setX(margin * getContainerWidth());
element.setY(margin * getContainerHeight());
element.setWidth(getContainerWidth() - (2 * margin * getContainerWidth()));
element.setHeight(getContainerHeight() - (2 * margin * getContainerHeight()));
log("fill - margin: {0}", margin);
}
}
);
}
public static void extendUp(final Element element, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setHeight(getContainerHeight() - (element.getY() + margin * getContainerHeight()));
log("extendUp - margin: {0}", margin);
}
}
);
}
public static void extendUpTo(final Element element, final float margin, final Element target) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setHeight(target.getY() - element.getY() - margin * getContainerHeight());
log("extendUpTo - margin: {0}, target: {1}", margin, target);
}
}
);
}
public static void extendRight(final Element element, final float margin) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setWidth(getContainerWidth() - (element.getX() + margin * getContainerWidth()));
log("extendRight - margin: {0}", margin);
}
}
);
}
public static void extendRightTo(final Element element, final float margin, final Element target) {
element.addControl(
new LayoutHelper() {
@Override
public void afterParent() {
element.setHeight(target.getX() - element.getX() - margin * getContainerWidth());
log("extendRightTo - margin: {0}, target: {1}", margin, target);
}
}
);
}
}
[/java]