A universal graphics API for your Minecraft plugin.
A Paper plugin paired with a Fabric client mod. Draw shapes, text and images on a player's screen from your own plugin.
int id = GraphicsAPI.drawRectangle(player, x, y, w, h,
true, 0xBF2A2A2A, false, true, 8,
GraphicsAPI.NO_GROUP, screenId);
Quick start
Installed once on the server as its own plugin. Your plugin depends on it, it doesn't bundle a copy.
1. Add the dependency
<repositories>
<repository>
<id>spoofies-private-repo</id>
<url>https://repo.spoofies.dev/repo</url>
</repository>
</repositories>
<dependency>
<groupId>the.spoofies</groupId>
<artifactId>graphicapi</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
2. Declare it in plugin.yml
depend: [GraphicsAPI]
3. Draw something
int id = GraphicsAPI.drawRectangle(player, 40, 30, 160, 80, true);
A filled rectangle at (40, 30), 160×80, on the HUD.
Elements
Every shape you draw is an element with an id, returned from the drawX(...) call. Reuse the id to update or remove it:
int id = GraphicsAPI.drawRectangle(player, 40, 30, 160, 80, true);
GraphicsAPI.updateRectangle(player, id, 50, 30, 160, 80,
true, 0xBF333232, false, false, 0, GraphicsAPI.NO_GROUP);
GraphicsAPI.remove(player, id);
Elements persist until removed or the player disconnects. Redraw only when something changes.
Groups
Tag elements with a shared groupId. They move together when the group's anchor is dragged:
int square = GraphicsAPI.drawRectangle(player, x, y, w, h, true,
color, false, true, 6, GraphicsAPI.NO_GROUP, screenId);
GraphicsAPI.drawText(player, x + 10, y - 12, color, false, 1.0f, 0,
"Ready", false, square, screenId);
Screens
Elements sit on the HUD by default: visible, no cursor. Open a screen to make things clickable and draggable:
GraphicsAPI.openScreen(player, MY_SCREEN_ID);
A real Minecraft Screen. Cursor appears, Esc closes it. Elements tagged with MY_SCREEN_ID (or unscoped, NO_SCREEN) render inside it. Everything else stays hidden.
Buttons and controls inside a screen are elements with interactive = true and an onClick handler.
Move / Scale / Grid
Move
draggable = true at creation. Dragging the body moves it.
GraphicsAPI.drawRectangle(player, x, y, w, h, true, color,
false, true, 8, GraphicsAPI.NO_GROUP, screenId);
Scale
Toggled after creation. Corner handles appear on the element. Drag one to resize from that corner.
GraphicsAPI.setScalable(player, elementId, true);
setDraggable(player, elementId, boolean) works the same way for drag.
Grid snap
GraphicsAPI.setGridSnap(player, true);
Shows a grid overlay. Move and resize snap to it while dragging. Wire it to your own button or command.
Shapes
Five element types, each with matching draw / update / animate methods.
Rectangle
GraphicsAPI.drawRectangle(player, x, y, width, height, filled,
argbColor, interactive, draggable, cornerRadius, groupId, screenId);
| Parameter | Type | Description |
|---|---|---|
| player | Player | Who sees this. |
| x, y | int | Top-left corner, pixels. |
| width, height | int | Size, pixels. |
| filled | boolean | Solid fill vs outline only. |
| argbColor | int | 0xAARRGGBB. |
| interactive | boolean | Clickable while a screen is open. |
| draggable | boolean | Body can be dragged. |
| cornerRadius | int | 0 for sharp corners, higher rounds them. |
| groupId | int | Another element's id, or NO_GROUP. |
| screenId | int | Which screen, or NO_SCREEN for unscoped. |
Text
GraphicsAPI.drawText(player, x, y, argbColor, shadow, scale,
wrapWidth, text, interactive, groupId, screenId);
| Parameter | Type | Description |
|---|---|---|
| player | Player | Who sees this. |
| x, y | int | Top-left of the text. |
| argbColor | int | 0xAARRGGBB. |
| shadow | boolean | Standard drop-shadow. |
| scale | float | 1.0 is default size. |
| wrapWidth | int | 0 for single line, else word-wrap width in pixels. |
| text | String | Content. |
| interactive | boolean | Clickable while a screen is open. |
| groupId | int | Another element's id, or NO_GROUP. |
| screenId | int | Which screen, or NO_SCREEN. |
Line
GraphicsAPI.drawLine(player, x1, y1, x2, y2, thickness, argbColor, groupId, screenId);
| Parameter | Type | Description |
|---|---|---|
| player | Player | Who sees this. |
| x1, y1 | int | Start point. |
| x2, y2 | int | End point. |
| thickness | int | Width, pixels. |
| argbColor | int | 0xAARRGGBB. |
| groupId | int | Another element's id, or NO_GROUP. |
| screenId | int | Which screen, or NO_SCREEN. |
No interactive or draggable options. Lines don't support either.
Circle
GraphicsAPI.drawCircle(player, centerX, centerY, radius, filled,
argbColor, interactive, groupId, screenId);
| Parameter | Type | Description |
|---|---|---|
| player | Player | Who sees this. |
| centerX, centerY | int | Center point. |
| radius | int | Pixels. |
| filled | boolean | Solid disc vs ring outline. |
| argbColor | int | 0xAARRGGBB. |
| interactive | boolean | Clickable while a screen is open. |
| groupId | int | Another element's id, or NO_GROUP. |
| screenId | int | Which screen, or NO_SCREEN. |
Image
GraphicsAPI.sendImageFile(player, "banner.png");
GraphicsAPI.drawImage(player, x, y, width, height, imageId, interactive, groupId, screenId);
| Parameter | Type | Description |
|---|---|---|
| player | Player | Who sees this. |
| x, y | int | Top-left corner. |
| width, height | int | Draw size. |
| imageId | String | Must match a name already sent via sendImageFile. |
| interactive | boolean | Clickable while a screen is open. |
| groupId | int | Another element's id, or NO_GROUP. |
| screenId | int | Which screen, or NO_SCREEN. |
See Images for how the transfer works.
Interactivity
Set interactive = true and register a handler. Fires only while a screen is open:
int button = GraphicsAPI.drawRectangle(player, x, y, w, h, true,
color, true, false, 4, groupId, screenId);
GraphicsAPI.onClick(button, (p, click) -> {
p.sendMessage("Clicked at " + click.mouseX() + "," + click.mouseY());
});
Find out where a drag or resize ended up:
GraphicsAPI.onPanelMoved(anchorId, (p, newX, newY) -> { /* ... */ });
GraphicsAPI.onElementResized(anchorId, (p, newWidth, newHeight) -> { /* ... */ });
Screen & Hud builder
For screen-scoped controls or a persistent HUD group, build declaratively instead of hand-tracking ids and cleanup.
Screen
A method that builds and returns one:
private Screen buildEditScreen(MyState state) {
Screen screen = GraphicsAPI.screen(MY_SCREEN_ID);
state.buttonRef = screen.rectangle(x, y, w, h, true, color)
.interactive()
.onClick((p, click) -> onButtonClick(p));
state.labelRef = screen.text(x + 8, y + 5, "Label");
return screen;
}
GraphicsAPI.openScreen(player, buildEditScreen(state));
state.buttonId = state.buttonRef.id(); // valid only after openScreen runs
Everything drawn through it is removed automatically when the player closes the screen.
Hud
Same shape, persistent. Multiple different huds can be enabled at once:
private Hud buildMyHud(Player player, MyState state) {
Hud hud = GraphicsAPI.hud(MY_HUD_ID);
ElementRef box = hud.rectangle(x, y, 40, 40, true, color).draggable();
hud.text(x, y - 12, "Label").groupWith(box);
return hud;
}
GraphicsAPI.enableHud(player, buildMyHud(player, state));
GraphicsAPI.disableHud(player, MY_HUD_ID);
What it's not for
Content that changes every tick, like a countdown. Build once with the builder, keep the id, use updateText/updateRectangle for the parts that change.
Animation
Every shape has a matching animateX(...), interpolated client-side every render frame.
Rectangle
GraphicsAPI.animateRectangle(player, id, toX, toY, toWidth, toHeight,
filled, toArgbColor, interactive, draggable, cornerRadius, durationMs, easing);
Text
GraphicsAPI.animateText(player, id, toX, toY, toArgbColor, shadow,
toScale, wrapWidth, text, interactive, durationMs, easing);
Line
GraphicsAPI.animateLine(player, id, toX1, toY1, toX2, toY2,
thickness, toArgbColor, durationMs, easing);
Circle
GraphicsAPI.animateCircle(player, id, toCenterX, toCenterY, toRadius,
filled, toArgbColor, interactive, durationMs, easing);
Image
GraphicsAPI.animateImage(player, id, toX, toY, toWidth, toHeight,
imageId, interactive, durationMs, easing);
Every one takes the same trailing pair: durationMs (int) and easing. Values: Easing.LINEAR, Easing.EASE_OUT_CUBIC, Easing.EASE_IN_OUT_CUBIC.
Images
GraphicsAPI.sendImageFile(player, "banner.png");
GraphicsAPI.drawImage(player, x, y, width, height, "banner");
PNGs are chunked and streamed to the client, then decoded into a texture. drawImage can be called right after sending. Shows a placeholder until the transfer finishes, then swaps in automatically. Re-sending the same name replaces it.