High-res, Scaleable Buttons/Windows at a Low Render Cost & APK Size

Thought I would share how I am rendering buttons and windows for Android dev so they look high-res at any screen resolution.

The technique incorporates both tiling and stretched layering.

First things first… the atlas (well… peices of it used for the button example here)

In this image is a section that we’ll want to use for tiling the button background, so we’ll save a 32 x 32 pixel image with the tile in it.

And the pressed version:

Now, we alter the Button.gui.xml file that we copied from the atlasdef package within the library, and change the images to non-atlas query strings (path to images)

We replace:


<property name="defaultImg" type="String" value="x=153|y=1|w=16|h=16" />
<property name="hoverImg" type="String" value="x=153|y=19|w=16|h=16" />
<property name="pressedImg" type="String" value="x=153|y=37|w=16|h=16" />

With:


<property name="defaultImg" type="String" value="Textures/UI/button_tile.png" />
<property name="hoverImg" type="String" value="Textures/UI/button_tile.png" />
<property name="pressedImg" type="String" value="Textures/UI/button_tile.png" />

The first property is really the only important one… this flag’s the Element as using a non-atlased image.

Next on the list is to extend ButtonAdapter and make a few changes (actually a few additions!)


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.utils.UIDUtil;

/**
 *
 * @author t0neg0d
 */
public class LayeredButton extends ButtonAdapter {
	public Element dsOverlay, gradOverlay, buttonBorder;
	
	public LayeredButton(ElementManager screen, Vector2f position, Vector2f dimensions) {
		super(screen, position, dimensions);
		
		// First we set the Element to tile it's background
		this.setTileImage(true);
		
		// Next scale the font size and text padding
		this.setTextPadding(screen.scaleVector4f(this.getTextPaddingVec()));
		this.setFontSize(screen.scaleFontSize(fontSize));
		
		// Change the hover and pressed images
		this.setButtonHoverInfo("Textures/UI/button_tile.png", hoverFontColor);
		this.setButtonPressedInfo("Textures/UI/button_tile_d.png", pressedFontColor);
		
		this.setScaleEW(false);
		this.setScaleNS(false);
		
		// Now we are going to an Element that containes a semi-transparent gradient fill
		// to give the background some depth.  This Element is set to scale so the gradient bg
		// will always fill the entire button
		gradOverlay = new Element(
			screen,
			UIDUtil.getUID(),
			Vector2f.ZERO,
			dimensions,
			Vector4f.ZERO,
			"x=35|y=103|w=32|h=32"
		);
		gradOverlay.setIgnoreMouse(true);
		gradOverlay.setScaleEW(false);
		gradOverlay.setScaleNS(false);
		gradOverlay.setDocking(Docking.SW);
		
		// Next we add another layer will an inner drop shadow to define
		// the rounded edges of our button
		dsOverlay = new Element(
			screen,
			UIDUtil.getUID(),
			new Vector2f(4,4),
			dimensions.subtract(8,8),
			new Vector4f(14,14,14,14),
			"x=103|y=69|w=32|h=32"
		);
		dsOverlay.setIgnoreMouse(true);
		dsOverlay.setScaleEW(false);
		dsOverlay.setScaleNS(false);
		dsOverlay.setDocking(Docking.SW);
		
		// And lastly, we add a cool looking border to reshape the button
		buttonBorder = new Element(
			screen,
			UIDUtil.getUID(),
			new Vector2f(-4,-4),
			dimensions.add(new Vector2f(8,8)),
			new Vector4f(14,14,14,14),
			"x=137|y=1|w=64|h=64"
		);
		buttonBorder.setIgnoreMouse(true);
		buttonBorder.setScaleEW(true);
		buttonBorder.setScaleNS(true);
		buttonBorder.setDocking(Docking.SW);
		
		addChild(gradOverlay);
		addChild(dsOverlay);
		addChild(buttonBorder);
	}
	
	@Override
	public void onMouseLeftPressed(MouseButtonEvent evt) {
		// Call Button's left pressed method then flip the gradient fill
		// to make the button appear to sink in
		super.onMouseLeftPressed(evt);
		gradOverlay.setColorMap("x=1|y=103|w=32|h=32");
	}
	
	@Override
	public void onMouseLeftReleased(MouseButtonEvent evt) {
		// Call Button's left release method then flip the gradient fill
		// back to the original to make the button appear to pop back
		// out
		super.onMouseLeftReleased(evt);
		gradOverlay.setColorMap("x=35|y=103|w=32|h=32");
	}
}

Here is what the buttons look like with each layer added one at a time.

Just the tiled background:

Now, add the gradient fill layer:

Then add the inner dropshadow layer:

Finally add the outer border:

Anyways, this was a quick throw-together that I thought I would share, as it looks awesome on different dpi’s.

2 Likes