Skip to content

Custom Wheels

QuickClientApi.openWheel(WheelData) opens the radial wheel screen. It does nothing if another screen is already open or if the local player is null. Call it from client-side code only (inside @Environment(EnvType.CLIENT) methods or equivalent).

java
import com.ofekn.quick.api.client.QuickClientApi;

QuickClientApi.openWheel(myWheelData);

WheelData

WheelData is an interface that drives the wheel:

java
public interface WheelData {
    List<IWheelOption> options(); // entries shown in the wheel
    Status status();              // current lifecycle state
}

Status controls the wheel's lifecycle. The wheel polls status() every tick, so you can drive it externally — for example, close it when a keybind is released:

ValueEffect
SELECTINGNormal operation — wheel stays open
FINISH_SELECTConfirm the current selection and close
CANCELClose without running any action

IWheelOption

Each entry in the wheel implements IWheelOption:

java
public interface IWheelOption {
    void onSelect();
    void extract(GuiGraphicsExtractor graphics);
    void extractTooltip(GuiGraphicsExtractor graphics, int mouseX, int mouseY);
}
  • onSelect — called when the user confirms this option (mouse release or FINISH_SELECT)
  • extract — render the icon centered at (0, 0). The graphics matrix is already translated to the icon center.
  • extractTooltip — render a tooltip at the mouse position. Called only for the currently highlighted option, and only once the open animation has finished.

For item icons and tooltips:

java
@Override
public void extract(GuiGraphicsExtractor graphics) {
    graphics.item(stack, -8, -8);
}

@Override
public void extractTooltip(GuiGraphicsExtractor graphics, int mouseX, int mouseY) {
    graphics.setTooltipForNextFrame(font, stack, mouseX, mouseY);
}

Full example

java
record ItemOption(ItemStack stack, Font font, Runnable action) implements IWheelOption {
    @Override
    public void onSelect() { action.run(); }

    @Override
    public void extract(GuiGraphicsExtractor graphics) {
        graphics.item(stack, -8, -8);
    }

    @Override
    public void extractTooltip(GuiGraphicsExtractor graphics, int mouseX, int mouseY) {
        graphics.setTooltipForNextFrame(font, stack, mouseX, mouseY);
    }
}

// Build the wheel data
List<IWheelOption> options = List.of(
    new ItemOption(new ItemStack(Items.ENDER_PEARL), font, () -> doSomething()),
    new ItemOption(new ItemStack(Items.FIREWORK_ROCKET), font, () -> doSomethingElse())
);

QuickClientApi.openWheel(new WheelData() {
    @Override public List<IWheelOption> options() { return options; }
    @Override public WheelData.Status status() { return WheelData.Status.SELECTING; }
});

To close the wheel from outside — for example when the player releases a keybind — use a mutable status holder:

java
class MyWheelData implements WheelData {
    private final List<IWheelOption> options;
    private Status status = Status.SELECTING;

    MyWheelData(List<IWheelOption> options) { this.options = options; }

    @Override public List<IWheelOption> options() { return options; }
    @Override public Status status() { return status; }

    public void cancel() { status = Status.CANCEL; }
    public void confirm() { status = Status.FINISH_SELECT; }
}

// On keybind release:
myWheelData.confirm();

Wheel layouts (experimental)

The wheel layout controls how the polygonal segments are arranged. Custom layouts can be registered to QuickClientRegistry.WHEEL_LAYOUT. The user can cycle through available layouts by right-clicking while the wheel is open.

WARNING

WheelLayout and WheelPolygon are marked @ApiStatus.Experimental and may change without notice.

java
import com.ofekn.quick.api.client.WheelLayout;
import com.ofekn.quick.api.client.WheelPolygon;

public class MyLayout implements WheelLayout {
    @Override
    public WheelPolygon[] polygons(int numOptions) {
        // Return one WheelPolygon per option.
        // Each WheelPolygon has a Vector2f[] of boundary points and a Vector2f center.
        // All coordinates are relative to the screen center (0, 0).
    }
}
java
// In your ClientModInitializer
Registry.register(
    QuickClientRegistry.WHEEL_LAYOUT,
    Identifier.of("my_mod", "my_layout"),
    new MyLayout()
);
java
// In your client mod constructor
DeferredRegister<WheelLayout> WHEEL_LAYOUTS = DeferredRegister.create(QuickClientRegistryKeys.WHEEL_LAYOUT, "my_mod");
WHEEL_LAYOUTS.register("my_layout", MyLayout::new);
WHEEL_LAYOUTS.register(modEventBus);