Skip to content

Creating custom widgets

Juuz edited this page Jul 23, 2024 · 12 revisions

Sometimes you need a widget that isn't available out of the box. You can use custom widget types to create customized versions of stock widgets or completely new ones.

If you want to create a completely new widget, you can extend the WWidget class. If, however you only need to change a specific method, you should extend the widget that comes closest to what you want and override what's necessary.

Extending WWidget

Below is a basic widget class skeleton.

public class WMyWidget extends WWidget {
    @Override
    public boolean canResize() {
        return true; // set to false if you want a static size
    }

    @Environment(EnvType.CLIENT)
    @Override
    public void paint(DrawContext context, int x, int y, int mouseX, int mouseY) {
        // This is the most important method of a custom widget, where you decide how it'll look.
    }
}

To that skeleton, you can add painting code and input handlers.

Painting your widget

The main utility class is ScreenDrawing, which can be used to draw various shapes and images on the screen.

Textures

public class WMyWidget extends WWidget {
    private static final Identifier TEXTURE = Identifier.of("mymod", "textures/gui/my_widget.png");
    // ...
    @Environment(EnvType.CLIENT)
    @Override
    public void paint(DrawContext context, int x, int y, int mouseX, int mouseY) {
        // If you want the whole texture
        ScreenDrawing.texturedRect(context, x, y, width, height, TEXTURE, 0xFFFFFFFF);

        // or for partial textures:
        ScreenDrawing.texturedRect(context, x, y, width, height, TEXTURE, u1, v1, u2, v2, 0xFFFFFFFF);
        // u1, v1, u2, v2 are fractions of the texture dimensions, so for example for the top half (u: 0-1, v: 0-0.5):
        ScreenDrawing.texturedRect(context, x, y, width, height, TEXTURE, 0f, 0f, 1f, 0.5f, 0xFFFFFFFF);

        // the 0xFFFFFFFF is the color in ARGB format, which in this case is white
    }
}

Custom input handling

There are several event methods in the WWidget class that can be overwritten to change functionality. We will go over a few of them.

Note that they all return InputResult. This describes whether the mouse input was PROCESSED or IGNORED. It's important to return the correct value since it affects the passthrough of mouse events - by returning IGNORED, you're allowing the ignored mouse event to pass through to lower widgets.

Mouse clicked

The obvious one. When a user clicks on WMyWidget then onClick will execute.

public class WMyWidget extends WWidget {
    @Environment(EnvType.CLIENT)
    @Override
    public InputResult onClick(int x, int y, int button) {
        // x & y are the coordinates of the mouse when the event was triggered
        // int button is which button was pressed
        return InputResult.PROCESSED;
    }

}

The button codes are:

  • 0: Left mouse button
  • 1: Right mouse button
  • 2: Scrollwheel mouse button

Mouse scrolling

This event will trigger when the user scrolls in the widget.

public class WMyWidget extends WWidget {
    @Environment(EnvType.CLIENT)
    @Override
    public InputResult onMouseScroll(int x, int y, double amount) {
        super.onMouseScroll(x, y, amount);
        // x & y are the coordinates of the mouse when the event was triggered
        return InputResult.PROCESSED;
    }
}

amount will be positive when you scroll up and negative when you scroll down.

Focused events

Some events will only trigger when a widget has been focused (like focusing an input field). To do this we need to override the canFocus so it returns true. After that we need to call requestFocus to actually focus it.

public class WMyWidget extends WWidget {
    @Environment(EnvType.CLIENT)
    @Override
    public InputResult onClick(int x, int y, int button) {
        requestFocus(); // To focus a widget
        return InputResult.PROCESSED;
    }

    @Override
    public boolean canFocus() {
        return true; // To make a widget able to become focused
    }
}

When a widget has gained focus, the event onFocusGained method will trigger. When it loses focus again the onFocusLost method will trigger.

On key pressed

Keyboard events will only trigger when your widget is focused.

There are two different keyboard events, onKeyPressed and onCharTyped.

onCharTyped will only pass what character was typed

onKeyPressed will pass 3 parameters:

  • ch: The keyboard key that was pressed or released
  • key: The system-specific scancode of the key
  • modifiers: bitfield describing which modifiers keys were held down
public class WMyWidget extends WWidget {
    @Environment(EnvType.CLIENT)
    @Override
    public InputResult onClick(int x, int y, int button) {
        requestFocus();
        return InputResult.PROCESSED;
    }

    @Override
    public boolean canFocus() {
        return true;
    }

    @Environment(EnvType.CLIENT)
    @Override
    public void onCharTyped(char ch) {
        System.out.println("Character typed: " + ch);
    }

    @Environment(EnvType.CLIENT)
    @Override
    public void onKeyPressed(int ch, int key, int modifiers) {
        System.out.println("Key pressed: " + ch);
    }
}

In the code example we focus the widget by clicking on it. After we have done that the onCharTyped and onKeyPressed methods will be triggered.

List of all possible events

  • onMouseDown(int x, int y, int button)
  • onMouseUp(int x, int y, int button)
  • onMouseDrag(int x, int y, int button, double deltaX, double deltaY)
  • onClick(int x, int y, int button)
  • onMouseScroll(int x, int y, double amount)
  • onMouseMove(int x, int y)
  • onCharTyped(char ch)
  • onKeyPressed(int ch, int key, int modifiers)
  • onKeyReleased(int ch, int key, int modifiers)
  • onFocusGained()
  • onFocusLost()