Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building LibGDX Scene2D Interfaces with NinePatch and UI Widgets

Tech 1

Standard interface elements—such as text labels, interactive buttons, toggle checkboxes, dropdown selectors, images, text inputs, lists, scroll panes, sliders, and split panes—reside within the com.badlogic.gdx.scenes.scene2d.ui package. Because they all inherit from Actor, they integrate seamlessly into a scene graph managed by a Stage. Examining the source code reveals that most of these elements derive from either Widget or Table, both of which serve as excellant base classes for custom UI components.

NinePatch Mechanics

A NinePatch divides a texture into nine distinct regions, allowing the center to scale while preserving the dimensions of corners and edges. This is particularly useful for resizable button backgrounds. Internally, it stores an array of nine TextureRegion objects.

Instantiation typically occurs by specifying the padding margins:

public NinePatch(Texture tex, int leftPad, int rightPad, int topPad, int bottomPad)

public NinePatch(TextureRegion reg, int leftPad, int rightPad, int topPad, int bottomPad)

The slicing mechanism calculates the center dimensions first, then extracts regions from top-to-bottom, left-to-right:

public NinePatch(TextureRegion reg, int leftPad, int rightPad, int topPad, int bottomPad) {
    int centerW = reg.getRegionWidth() - leftPad - rightPad;
    int centerH = reg.getRegionHeight() - topPad - bottomPad;
    this.regions = new TextureRegion[] {
        new TextureRegion(reg, 0, 0, leftPad, topPad),
        new TextureRegion(reg, leftPad, 0, centerW, topPad),
        new TextureRegion(reg, leftPad + centerW, 0, rightPad, topPad),
        new TextureRegion(reg, 0, topPad, leftPad, centerH),
        new TextureRegion(reg, leftPad, topPad, centerW, centerH),
        new TextureRegion(reg, leftPad + centerW, topPad, rightPad, centerH),
        new TextureRegion(reg, 0, topPad + centerH, leftPad, bottomPad),
        new TextureRegion(reg, leftPad, topPad + centerH, centerW, bottomPad),
        new TextureRegion(reg, leftPad + centerW, topPad + centerH, rightPad, bottomPad)
    };
}

During rendering, the engine determines the dynamic boundaries for the columns and rows, drawing the scalable center sections to fit the target dimensions while keeping the corners static.

Text Labels

Labels cache their rendering state. To dynamically update the displayed string, utilize setWrappedText rather than standard setter methods.

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Label;

public class UIDemo implements ApplicationListener {
    private Stage uiStage;
    private Label fpsCounter;

    @Override
    public void create() {
        uiStage = new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true);
        BitmapFont font = new BitmapFont(Gdx.files.internal("font.fnt"), Gdx.files.internal("font.png"), false);
        
        fpsCounter = new Label("fps", font, "fpsLabel");
        fpsCounter.setPosition(10, Gdx.graphics.getHeight() - fpsCounter.getHeight() - 10);
        uiStage.addActor(fpsCounter);
        Gdx.input.setInputProcessor(uiStage);
    }

    @Override
    public void render() {
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        fpsCounter.setWrappedText("FPS: " + Gdx.graphics.getFramesPerSecond(), BitmapFont.HAlignment.CENTER);
        uiStage.act(Gdx.graphics.getDeltaTime());
        uiStage.draw();
    }

    @Override public void dispose() { uiStage.dispose(); }
    @Override public void pause() {}
    @Override public void resume() {}
    @Override public void resize(int width, int height) {}
}

Interactive Buttons

Buton configuration relies on a ButtonStyle, which encapsulates NinePatch drawings for up, down, and hovered states, offset coordinates for pressed effects, alongside a BitmapFont and Color.

Texture btnTex = new Texture(Gdx.files.internal("btn_bg.png"));
NinePatch btnPatch = new NinePatch(btnTex, 8, 8, 10, 10);
BitmapFont uiFont = new BitmapFont(Gdx.files.internal("font.fnt"), Gdx.files.internal("font.png"), false);

Button.ButtonStyle btnStyle = new Button.ButtonStyle(btnPatch, btnPatch, btnPatch, 0f, 0f, 0f, 0f, uiFont, new Color(1, 1, 0, 0.5f));
Button actionBtn = new Button("action", btnStyle, "actionBtn");
actionBtn.setBounds(20, 20, 120, 40);

actionBtn.setClickListener(new ClickListener() {
    @Override
    public void click(Actor actor) {
        Gdx.app.log("UI_EVENT", "Button was clicked");
    }
});
uiStage.addActor(actionBtn);

Checkbox Controls

Checkboxes use a CheckBoxStyle requiring two TextureRegion instances for checked and unchecked visuals, plus a font and color.

Texture uncheckedTex = new Texture(Gdx.files.internal("box_off.png"));
Texture checkedTex = new Texture(Gdx.files.internal("box_on.png"));

CheckBox.CheckBoxStyle chkStyle = new CheckBox.CheckBoxStyle(
    new TextureRegion(uncheckedTex), 
    new TextureRegion(checkedTex), 
    uiFont, 
    new Color(1, 1, 1, 1)
);

CheckBox agreeChk = new CheckBox("agree", chkStyle, "agreeCheck");
agreeChk.setBounds(150, 150, 180, 40);
agreeChk.setText("Accept");

agreeChk.setClickListener(new ClickListener() {
    @Override
    public void click(Actor actor) {
        agreeChk.setText(agreeChk.isChecked ? "Accepted" : "Declined");
    }
});
uiStage.addActor(agreeChk);

Slider Mechanics and Style Configuration

A SliderStyle demands a NinePatch for the track background and a standard Texture for the draggable knob. While styles are often defined programmatically, they can also be loaded via configuration files. For instance, NinePatch regions can be declared using coordinate and dimension mappings:

"customPatch": [ { "height": 13, "width": 9, "x": 761, "y": 78 }, { "height": 13, "width": 1, "x": 770, "y": 78 }, { "height": 13, "width": 9, "x": 771, "y": 78 }, { "height": 1, "width": 9, "x": 761, "y": 91 }, { "height": 1, "width": 1, "x": 770, "y": 91 }, { "height": 1, "width": 9, "x": 771, "y": 91 }, { "height": 13, "width": 9, "x": 761, "y": 92 }, { "hieght": 13, "width": 1, "x": 770, "y": 92 }, { "height": 13, "width": 9, "x": 771, "y": 92 } ]

Always ensure that explicit width and height parameters are set for dimension-dependent widgets to prevent rendering anomalies.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.