Skip to content

Custom Widgets

raeleus edited this page Apr 17, 2024 · 14 revisions

Custom Widgets

libGDX's collection of widgets is rather limited when compared to the likes of Swing and JavaFX. However, Scene2D.UI is incredibly extensible to the point of the only limit being what you know how to draw. The process of creating customized widgets needs to be explained, however.

Extending an existing Widget

The simplest way to create a custom widget is to extend a high level widget like Table or Button. Considering that you already know table layout, you can avoid learning Layout, WidgetGroup and all the trivialities of what makes Scene2D work in the background by adding onto one of these well-known widgets.

Table

I often extend Table so that I can organize my UI into different class files. This differs from a single, monolithic class that sets the layout for everything in one go.

This makes rearranging large chunks of the interface easy.

In fact, you can use this technique to create a panel like design that the user can customize by pressing a few buttons.

You can go beyond just organizing your personal project and generalize your class so that it will be useful to you throughout your project and in other projects. The constructor for this example class takes the title as a parameter. Now you can consider this a custom widget.

Button

The above example is good if you're just organizing information. What if you want to make it clickable? It's pretty simple to add a ClickListener to make it do something when the player clicks it.

However, the table doesn't look like it's being pressed. You can change the background to have an up state drawable and a down state drawable and toggle between the two. But wait! This strangely sounds like something we already have. In fact, it's just a button.

Since every button is a table, you can add whatever you want to it just like a table. You can extend it like the previous example too. It will fire a ChangeEvent whenever this widget is clicked.

Why stop there? You can now bake in some animation by adding an input listener. Enjoy having clean, reactive buttons.

Since all these widgets are just actors being drawn on a stage, you can have a little fun with it by playing with its coordinates.

Looking closely, you'll see that the majority of widgets in Scene2D are derivative of Button or table. Take these as examples to learn how to do new things like creating pop ups that follows the player or the mouse. I created my general utility widget, PopTable, in this way by examining how Window and Dialog work.

Style

You can get away with using the built in style classes for Button and the like to allow your custom widget to be customizable by users. But usually you want more than a simple ButtonStyle to describe all the different facets of your widget. A style is a simple class that describes what skin assets you need to build the widget. These are commonly drawables, bitmap fonts, other styles, and sometimes primitive values like booleans and floats. Define this as a static inner class of your widget with optional fields denoted as @Null for the convenience of your users.

You can even extend an existing style class if you only need to add a few fields.

The user is expected to create an instance of the style and fill the fields.

If you want your styles to be loaded from a skin JSON file, you'll have to register your classes with a skin loader

Creating a unique Widget

Sometimes your widget cannot be a derivative of some other class. Perhaps it's something unique that has never been attempted before or it's too radical to define in Table layout. Let's dig a little deeper

Widget and Layout

As no surprise to anybody, you can extend the Widget class directly to make your own widget. If you do this, you will no longer be insulated by the niceties of the Table class. There are no cells, no pre-existing style. You're responsible for drawing the different aspects of your widget yourself. Everything is drawn in a coordinate system with (0, 0) localized to the actor's position. This is more cumbersome, but you have full control now.

A widget is simply an actor that implements Layout. Layout is an interface used to inform Scene2D.UI about the widget's minimum, preferred, and maximum dimensions. Remember, the parent of a widget sets the widget's size, but you can influence this size with these values. You have to override the following methods if you want to control these aspects:

Preferred width and height define what is the ideal size of this widget. It could be made bigger or smaller, but preferred is the natural way to size this widget. Take text for example. The preferred size could be the width and height of the text at the default scale. You can make text smaller, perhaps using word wrap or ellipses, but preferably it would be at full size given adequate space.

For an image, the preferred size is typically the size of the Texture or TextureRegion. This important for nine-patches because they can scale up or down gracefully, but they have need to have a default size when not under constraint.

When you're combining multiple elements, you'll take the largest dimensions when the widget is fully composed to set your preferred size. This all should be determined programmatically so that the preferred width/height will be representative of whatever content you include.

Min width and height, on the other hand, indicate that this is the smallest that this widget can be squished.

Of course, the user of your widget may try to force your widget to be smaller than this value regardless of what you specify. When they do this, they are knowingly breaking the contract with your class, so they shouldn't be surprised when it doesn't behave well in these circumstances. Table, for instance, just lets the contents spill out of their cells, overlapping other widgets next to it.

The user is expected to implement the widget inside of another one that allows for a smaller min size. This is commonly a ScrollPane so that the players can drag the bars to see the rest of it.

If you expect your widget to behave well at any size, return a min width and height at 0.

Max width and height are not as commonly used, but it can still be important for the specific thing you're doing. You might not want your images to appear too stretched for example. This only really comes into play when users call grow() on the cell that they add your widget to.

Another property of widget is Touchable. The widget is defaulted to Touchable.enabled, meaning that it will be able to respond to user input events like clicks and drags. Set it to disabled if this widget does not take input. This is important because you don't want it to capture inputs and block them from reaching other actors it's overlapping like in a stack.

Use the layout method to arrange the components of your widget if necessary. This is an opportune time to adjust the scale and position of sprites, update your font layout, or anything else that has state based on the width and height of your widget. Layout is only called when there have been changes to the UI that could affect the size. This is much more important when you deal with WidgetGroup, not Widget however.

The draw method is what allows you to render your widget. This usually involves calling the draw methods of your assets collected in the widget style based on the dimensions provided.

Layout, Invalidate, Validate and Pack methods

stage, null parent indicates that it's not on the stage.

Widget Group

Layout

Invalidate/Validate

Adding/removing children calls invalidate.

Transform

Clipping

Custom Drawables

Examples

Create an analog clock widget.

Create a circular widget that rotates.

Creating a custom drawable logo.

Further Steps

Continue with 15 - Skin JSON or return to the table of contents.

Clone this wiki locally