Skip to content

GUI and Overlay Rendering

GUI and overlay rendering allows you to create custom user interfaces, HUD elements, and interactive screens for your mods. This guide covers creating custom GUI components, overlays, and interactive elements in Minecraft 26.1.

The GUI system in Minecraft 26.1 uses a component-based architecture:

  • GuiGraphics: Central rendering engine for all UI operations
  • Screen: Base class for all UI screens and menus
  • Gui: Manager for in-game HUD elements
  • AbstractWidget: Base class for interactive UI components
  • Event System: Manages input handling and component interactions

GuiGraphics provides the primary interface for all GUI rendering operations:

// Example: Basic drawing operations with GuiGraphics
public class BasicGuiRendering {
public void drawBasicElements(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
// Draw filled rectangle
graphics.fill(10, 10, 100, 60, 0xFF333333);
// Draw border
graphics.renderOutline(10, 10, 100, 60, 0xFFFFFFFF);
// Draw text
graphics.drawString(Minecraft.getInstance().font,
Component.literal("Custom GUI"), 15, 20, 0xFFFFFF);
// Draw textured element
graphics.blitSprite(ResourceLocation.withDefaultNamespace("widget/button"),
110, 10, 50, 20);
}
public void drawTransformedElement(GuiGraphics graphics, int x, int y, float rotation) {
// Apply transformations
graphics.pose().pushMatrix();
graphics.pose().translate(x + 25, y + 25, 0);
graphics.pose().mulPose(Axis.ZP.rotationDegrees(rotation));
graphics.pose().translate(-25, -25, 0);
// Draw transformed element
graphics.fill(0, 0, 50, 50, 0xFFFF5555);
graphics.pose().popMatrix();
}
}

All custom screens extend the Screen class:

// Example: Custom screen implementation
public class CustomModScreen extends Screen {
private CustomProgressBar progressBar;
private CycleButton<String> modeButton;
private TextFieldWidget textField;
public CustomModScreen() {
super(Component.literal("Custom Mod Interface"));
}
@Override
protected void init() {
// Clear existing widgets
this.clearWidgets();
// Initialize custom components
this.progressBar = new CustomProgressBar(
this.width / 2 - 100,
this.height / 2 - 50,
200,
20,
Component.literal("Processing...")
);
this.modeButton = CycleButton.builder(String.class)
.withValues("Mode A", "Mode B", "Mode C")
.withInitialValue("Mode A")
.create(
this.width / 2 - 50,
this.height / 2,
100,
20,
Component.literal("Mode"),
(button, value) -> onModeChanged(value)
);
this.textField = new TextFieldWidget(
this.font,
this.width / 2 - 75,
this.height / 2 + 40,
150,
20,
Component.literal("Input")
);
this.textField.setMaxLength(50);
// Add widgets to screen
this.addRenderableWidget(progressBar);
this.addRenderableWidget(modeButton);
this.addRenderableWidget(textField);
// Add close button
this.addRenderableWidget(
Button.builder(Component.literal("Close"), button -> this.onClose())
.pos(this.width / 2 - 50, this.height / 2 + 80)
.size(100, 20)
.build()
);
}
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
// Render background
this.renderBackground(graphics, mouseX, mouseY, partialTick);
// Render title
graphics.drawCenteredString(this.font, Component.literal("Custom Mod Interface"),
this.width / 2, this.height / 2 - 80, 0xFFFFFF);
// Render all widgets
super.render(graphics, mouseX, mouseY, partialTick);
// Render additional information
if (this.progressBar.getProgress() > 0.5f) {
graphics.drawString(this.font, Component.literal("Processing..."),
this.width / 2 - 30, this.height / 2 + 120, 0x55FF55);
}
}
private void onModeChanged(String newMode) {
// Handle mode change
System.out.println("Mode changed to: " + newMode);
this.progressBar.setProgress(0.0f); // Reset progress
}
}

All interactive components should extend AbstractWidget:

// Example: Custom progress bar widget
public class CustomProgressBar extends AbstractWidget {
private float progress = 0.0f;
private int barColor = 0xFF55FF55;
private int backgroundColor = 0xFF333333;
private boolean showPercentage = true;
public CustomProgressBar(int x, int y, int width, int height, Component message) {
super(x, y, width, height, message);
}
@Override
protected void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
// Draw background
graphics.fill(this.getX(), this.getY(),
this.getX() + this.width, this.getY() + this.height,
this.backgroundColor);
// Draw border
graphics.renderOutline(this.getX(), this.getY(),
this.getX() + this.width, this.getY() + this.height,
0xFFFFFFFF);
// Draw progress fill
int progressWidth = (int)(this.width * this.progress);
graphics.fill(this.getX(), this.getY(),
this.getX() + progressWidth, this.getY() + this.height,
this.barColor);
// Draw text/percentage
if (this.showPercentage) {
Component progressText = Component.literal(String.format("%.1f%%", this.progress * 100.0f));
int textWidth = this.font.width(progressText);
int textX = this.getX() + (this.width - textWidth) / 2;
int textY = this.getY() + (this.height - this.font.lineHeight) / 2;
graphics.drawString(this.font, progressText, textX, textY, 0xFFFFFF);
}
}
@Override
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
if (this.isMouseOver(event.x(), event.y())) {
if (event.button() == 0) { // Left click
// Reset progress on click
this.setProgress(0.0f);
return true;
} else if (event.button() == 1) { // Right click
// Set to full on right click
this.setProgress(1.0f);
return true;
}
}
return super.mouseClicked(event, doubleClick);
}
@Override
protected void onDrag(double mouseX, double mouseY, double deltaX, double deltaY) {
if (this.isFocused() && this.isMouseOver(mouseX, mouseY)) {
// Update progress based on drag
float relativeX = (float)(mouseX - this.getX()) / this.width;
this.setProgress(Mth.clamp(relativeX, 0.0f, 1.0f));
}
}
// Utility methods
public void setProgress(float progress) {
this.progress = Mth.clamp(progress, 0.0f, 1.0f);
}
public float getProgress() {
return this.progress;
}
public void setBarColor(int color) {
this.barColor = color;
}
}
// Example: Custom draggable panel widget
public class DraggablePanel extends AbstractWidget {
private boolean isDragging = false;
private double dragOffsetX = 0;
private double dragOffsetY = 0;
private final List<AbstractWidget> children = new ArrayList<>();
private final int headerColor = 0xFF444466;
private final int borderColor = 0xFF666666;
public DraggablePanel(int x, int y, int width, int height, Component title) {
super(x, y, width, height, title);
}
@Override
protected void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
// Draw panel background
graphics.fill(this.getX(), this.getY(),
this.getX() + this.width, this.getY() + this.height,
0xAA000000);
// Draw header
graphics.fill(this.getX(), this.getY(),
this.getX() + this.width, this.getY() + 25,
this.headerColor);
// Draw borders
graphics.renderOutline(this.getX(), this.getY(),
this.getX() + this.width, this.getY() + this.height,
this.borderColor);
// Draw title
graphics.drawString(this.font, this.getMessage(),
this.getX() + 5, this.getY() + 5, 0xFFFFFF);
// Draw resize handle
graphics.fill(this.getX() + this.width - 10, this.getY() + this.height - 10,
this.getX() + this.width, this.getY() + this.height,
0xFF888888);
// Render child widgets
graphics.pose().pushMatrix();
graphics.pose().translate(this.getX(), this.getY(), 0);
int childMouseX = mouseX - this.getX();
int childMouseY = mouseY - this.getY();
for (AbstractWidget child : children) {
child.render(graphics, childMouseX, childMouseY, partialTick);
}
graphics.pose().popMatrix();
}
@Override
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
if (this.isMouseOver(event.x(), event.y())) {
// Check if clicking on header for dragging
if (event.y() - this.getY() < 25) {
this.isDragging = true;
this.dragOffsetX = event.x() - this.getX();
this.dragOffsetY = event.y() - this.getY();
return true;
}
// Check if clicking on resize handle
if (event.x() > this.getX() + this.width - 10 &&
event.y() > this.getY() + this.height - 10) {
// Handle resizing (simplified example)
return true;
}
// Pass click to children
for (AbstractWidget child : children) {
if (child.mouseClicked(event, doubleClick)) {
return true;
}
}
}
return super.mouseClicked(event, doubleClick);
}
@Override
protected void onDrag(double mouseX, double mouseY, double deltaX, double deltaY) {
if (this.isDragging) {
this.setX((int)(mouseX - this.dragOffsetX));
this.setY((int)(mouseY - this.dragOffsetY));
}
}
@Override
public boolean mouseReleased(MouseButtonEvent event) {
this.isDragging = false;
return super.mouseReleased(event);
}
public void addChild(AbstractWidget child) {
this.children.add(child);
}
public void removeChild(AbstractWidget child) {
this.children.remove(child);
}
}
// Example: Custom HUD overlay implementation
public class CustomHudOverlay {
private final Minecraft minecraft = Minecraft.getInstance();
private boolean showHud = true;
private int hudX = 10;
private int hudY = 50;
public void render(GuiGraphics graphics, DeltaTracker deltaTracker) {
if (!this.showHud || this.minecraft.options.hideGui) {
return;
}
Player player = this.minecraft.player;
if (player == null) {
return;
}
// Draw custom HUD elements
drawPlayerStats(graphics, player);
drawCustomCompass(graphics);
drawMiniMap(graphics, player);
drawCustomEffects(graphics, player);
}
private void drawPlayerStats(GuiGraphics graphics, Player player) {
int y = this.hudY;
// Player health with custom styling
Component healthText = Component.literal("Health: ")
.append(Component.literal(String.valueOf((int)player.getHealth()))
.withStyle(Style.EMPTY.withColor(ChatFormatting.RED)));
graphics.drawString(this.minecraft.font, healthText, this.hudX, y, 0xFFFFFF);
y += 15;
// Player experience
Component expText = Component.literal("EXP: ")
.append(Component.literal(String.valueOf(player.experienceProgress * 100))
.withStyle(Style.EMPTY.withColor(ChatFormatting.GREEN)));
graphics.drawString(this.minecraft.font, expText, this.hudX, y, 0xFFFFFF);
y += 15;
// Current biome
ResourceLocation biomeId = this.minecraft.level.getBiome(player.blockPosition())
.value().getRegistryName();
if (biomeId != null) {
Component biomeText = Component.literal("Biome: ")
.append(Component.literal(biomeId.getPath())
.withStyle(Style.EMPTY.withColor(ChatFormatting.YELLOW)));
graphics.drawString(this.minecraft.font, biomeText, this.hudX, y, 0xFFFFFF);
}
}
private void drawCustomCompass(GuiGraphics graphics) {
int compassX = graphics.guiWidth() - 80;
int compassY = 50;
Player player = this.minecraft.player;
if (player == null) return;
float yaw = player.getYRot();
// Draw compass background
graphics.fill(compassX, compassY, compassX + 60, compassY + 60, 0x66000000);
graphics.renderOutline(compassX, compassY, compassX + 60, compassY + 60, 0xFFFFFFFF);
// Draw compass direction
graphics.pose().pushMatrix();
graphics.pose().translate(compassX + 30, compassY + 30, 0);
graphics.pose().mulPose(Axis.ZP.rotationDegrees(yaw));
// Draw north indicator
graphics.fill(-2, -25, 2, -20, 0xFFFF0000);
graphics.pose().popMatrix();
// Draw N/E/S/W labels
graphics.drawString(this.minecraft.font, "N", compassX + 27, compassY + 2, 0xFF0000);
graphics.drawString(this.minecraft.font, "S", compassX + 27, compassY + 45, 0xFFFFFF);
graphics.drawString(this.minecraft.font, "E", compassX + 45, compassY + 24, 0xFFFFFF);
graphics.drawString(this.minecraft.font, "W", compassX + 9, compassY + 24, 0xFFFFFF);
}
private void drawMiniMap(GuiGraphics graphics, Player player) {
int mapSize = 64;
int mapX = graphics.guiWidth() - mapSize - 10;
int mapY = graphics.guiHeight() - mapSize - 10;
// Draw map background
graphics.fill(mapX, mapY, mapX + mapSize, mapY + mapSize, 0x66000000);
graphics.renderOutline(mapX, mapY, mapX + mapSize, mapY + mapSize, 0xFFFFFFFF);
// Draw player position on minimap
int playerMapX = mapX + mapSize / 2;
int playerMapY = mapY + mapSize / 2;
graphics.fill(playerMapX - 2, playerMapY - 2,
playerMapX + 2, playerMapY + 2, 0xFFFF0000);
// Draw nearby terrain (simplified)
drawMiniMapTerrain(graphics, player, mapX, mapY, mapSize);
}
private void drawMiniMapTerrain(GuiGraphics graphics, Player player,
int mapX, int mapY, int mapSize) {
Level level = this.minecraft.level;
BlockPos centerPos = player.blockPosition();
int radius = mapSize / 2 / 16; // blocks from center
for (int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) {
BlockPos pos = centerPos.offset(x, 0, z);
BlockState state = level.getBlockState(pos);
// Simple terrain representation
int color = getTerrainColor(state);
int screenX = mapX + mapSize / 2 + x * 16;
int screenY = mapY + mapSize / 2 + z * 16;
if (screenX >= mapX && screenX < mapX + mapSize &&
screenY >= mapY && screenY < mapY + mapSize) {
graphics.fill(screenX, screenY, screenX + 16, screenY + 16, color);
}
}
}
}
private int getTerrainColor(BlockState state) {
if (state.isAir()) {
return 0x00000000; // Transparent for air
} else if (state.is(Blocks.GRASS_BLOCK)) {
return 0xFF228B22; // Green for grass
} else if (state.is(Blocks.STONE)) {
return FF808080; // Gray for stone
} else if (state.is(Blocks.WATER)) {
return 0xFF0066CC; // Blue for water
}
return 0xFF666666; // Default gray
}
private void drawCustomEffects(GuiGraphics graphics, Player player) {
int effectsY = this.hudY + 80;
// Draw custom effect indicators
if (player.hasEffect(MobEffects.NIGHT_VISION)) {
graphics.drawString(this.minecraft.font,
Component.literal("Night Vision Active").withStyle(ChatFormatting.AQUA),
this.hudX, effectsY, 0xFFFFFF);
effectsY += 12;
}
if (player.isInWater()) {
graphics.drawString(this.minecraft.font,
Component.literal("Underwater").withStyle(ChatFormatting.BLUE),
this.hudX, effectsY, 0xFFFFFF);
effectsY += 12;
}
// Draw custom power indicators
for (int i = 0; i < player.getInventory().getContainerSize(); i++) {
ItemStack stack = player.getInventory().getItem(i);
if (isPowerItem(stack)) {
Component powerText = Component.literal("Power: ")
.append(Component.literal(getPowerLevel(stack))
.withStyle(ChatFormatting.YELLOW));
graphics.drawString(this.minecraft.font, powerText,
this.hudX, effectsY, 0xFFFFFF);
effectsY += 12;
break; // Only show first power item
}
}
}
private boolean isPowerItem(ItemStack stack) {
return stack.getItem() == Items.DIAMOND ||
stack.getItem() == Items.EMERALD ||
stack.getItem() == Items.GOLD_INGOT;
}
private String getPowerLevel(ItemStack stack) {
if (stack.getItem() == Items.DIAMOND) {
return "High";
} else if (stack.getItem() == Items.EMERALD) {
return "Medium";
} else if (stack.getItem() == Items.GOLD_INGOT) {
return "Low";
}
return "Unknown";
}
// HUD control methods
public void toggleHud() {
this.showHud = !this.showHud;
}
public void setHudPosition(int x, int y) {
this.hudX = x;
this.hudY = y;
}
}
// Example: Using render events for HUD integration
@EventBusSubscriber(modid = CustomHudMod.MOD_ID, bus = EventBusSubscriber.Bus.FORGE)
public class CustomHudEventHandler {
private static final CustomHudOverlay customHud = new CustomHudOverlay();
@SubscribeEvent
public static void onRenderGuiOverlay(RenderGuiLayerEvent.Post event) {
if (event.getName() == RenderGuiLayerLayerElementType.ALL) {
customHud.render(event.getGuiGraphics(), event.getDeltaTracker());
}
}
@SubscribeEvent
public static void onKeyPress(KeyEvent event) {
// Toggle HUD with F6 key
if (event.getKey() == InputConstants.KEY_F6) {
customHud.toggleHud();
event.setCanceled(true); // Prevent other handlers
}
}
}
// Example: Animated widget with smooth transitions
public class AnimatedWidget extends AbstractWidget {
private float currentProgress = 0.0f;
private float targetProgress = 0.0f;
private long lastUpdateTime = 0;
private static final float ANIMATION_SPEED = 0.1f;
public AnimatedWidget(int x, int y, int width, int height, Component message) {
super(x, y, width, height, message);
}
@Override
protected void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
updateAnimation();
// Draw animated progress bar
int barWidth = (int)(this.width * this.currentProgress);
// Smooth color transition based on progress
int barColor = interpolateColor(0xFF5555, 0xFF55FF55, this.currentProgress);
graphics.fill(this.getX(), this.getY(),
this.getX() + barWidth, this.getY() + this.height,
barColor);
// Draw animated text
float scale = 1.0f + (float)Math.sin(this.currentProgress * Math.PI * 2) * 0.1f;
graphics.pose().pushMatrix();
graphics.pose().translate(this.getX() + this.width / 2,
this.getY() + this.height / 2, 0);
graphics.pose().scale(scale, scale, 1.0f);
Component text = Component.literal(String.format("%.1f%%", this.currentProgress * 100));
graphics.drawString(this.font, text,
-this.font.width(text) / 2,
-this.font.lineHeight / 2, 0xFFFFFF);
graphics.pose().popMatrix();
}
private void updateAnimation() {
long currentTime = System.currentTimeMillis();
long deltaTime = currentTime - this.lastUpdateTime;
if (deltaTime > 16) { // Limit to ~60 FPS
// Smooth animation with easing
float deltaProgress = this.targetProgress - this.currentProgress;
this.currentProgress += deltaProgress * ANIMATION_SPEED;
this.lastUpdateTime = currentTime;
}
}
private int interpolateColor(int color1, int color2, float t) {
t = Mth.clamp(t, 0.0f, 1.0f);
int r1 = (color1 >> 16) & 0xFF;
int g1 = (color1 >> 8) & 0xFF;
int b1 = color1 & 0xFF;
int r2 = (color2 >> 16) & 0xFF;
int g2 = (color2 >> 8) & 0xFF;
int b2 = color2 & 0xFF;
int r = (int)(r1 + (r2 - r1) * t);
int g = (int)(g1 + (g2 - g1) * t);
int b = (int)(b1 + (b2 - b1) * t);
return (r << 16) | (g << 8) | b;
}
public void setTargetProgress(float progress) {
this.targetProgress = Mth.clamp(progress, 0.0f, 1.0f);
}
}
// Example: Responsive UI that adapts to screen size
public class ResponsiveScreen extends Screen {
private final Map<String, AbstractWidget> widgets = new HashMap<>();
private ScreenSize currentSize;
public ResponsiveScreen() {
super(Component.literal("Responsive Interface"));
}
@Override
protected void init() {
this.clearWidgets();
this.widgets.clear();
// Determine screen size category
this.currentSize = categorizeScreenSize();
// Create layout based on screen size
switch (this.currentSize) {
case SMALL -> createSmallLayout();
case MEDIUM -> createMediumLayout();
case LARGE -> createLargeLayout();
}
}
@Override
public void resize(Minecraft minecraft, int width, int height) {
super.resize(minecraft, width, height);
this.init(); // Reinitialize layout
}
private ScreenSize categorizeScreenSize() {
if (this.width < 600 || this.height < 400) {
return ScreenSize.SMALL;
} else if (this.width < 1000 || this.height < 600) {
return ScreenSize.MEDIUM;
} else {
return ScreenSize.LARGE;
}
}
private void createSmallLayout() {
// Compact vertical layout for small screens
this.widgets.put("mainButton",
Button.builder(Component.literal("Main Action"), this::onMainAction)
.pos(this.width / 2 - 50, this.height / 2 - 60)
.size(100, 20)
.build()
);
this.widgets.put("settingsButton",
Button.builder(Component.literal("Settings"), this::onSettings)
.pos(this.width / 2 - 50, this.height / 2 - 20)
.size(100, 20)
.build()
);
this.widgets.put("exitButton",
Button.builder(Component.literal("Exit"), this::onClose)
.pos(this.width / 2 - 50, this.height / 2 + 20)
.size(100, 20)
.build()
);
}
private void createMediumLayout() {
// Horizontal layout for medium screens
this.widgets.put("mainButton",
Button.builder(Component.literal("Main Action"), this::onMainAction)
.pos(this.width / 2 - 160, this.height / 2 - 20)
.size(100, 20)
.build()
);
this.widgets.put("settingsButton",
Button.builder(Component.literal("Settings"), this::onSettings)
.pos(this.width / 2 - 50, this.height / 2 - 20)
.size(100, 20)
.build()
);
this.widgets.put("helpButton",
Button.builder(Component.literal("Help"), this::onHelp)
.pos(this.width / 2 + 60, this.height / 2 - 20)
.size(100, 20)
.build()
);
this.widgets.put("exitButton",
Button.builder(Component.literal("Exit"), this::onClose)
.pos(this.width / 2 - 50, this.height / 2 + 20)
.size(100, 20)
.build()
);
}
private void createLargeLayout() {
// Grid layout for large screens
int buttonWidth = 120;
int buttonHeight = 30;
int spacing = 20;
int totalWidth = 3 * buttonWidth + 2 * spacing;
int totalHeight = 3 * buttonHeight + 2 * spacing;
int startX = (this.width - totalWidth) / 2;
int startY = (this.height - totalHeight) / 2;
this.widgets.put("mainButton",
Button.builder(Component.literal("Main Action"), this::onMainAction)
.pos(startX, startY)
.size(buttonWidth, buttonHeight)
.build()
);
this.widgets.put("settingsButton",
Button.builder(Component.literal("Settings"), this::onSettings)
.pos(startX + buttonWidth + spacing, startY)
.size(buttonWidth, buttonHeight)
.build()
);
this.widgets.put("helpButton",
Button.builder(Component.literal("Help"), this::onHelp)
.pos(startX + 2 * (buttonWidth + spacing), startY)
.size(buttonWidth, buttonHeight)
.build()
);
this.widgets.put("toolsButton",
Button.builder(Component.literal("Tools"), this::onTools)
.pos(startX, startY + buttonHeight + spacing)
.size(buttonWidth, buttonHeight)
.build()
);
this.widgets.put("statsButton",
Button.builder(Component.literal("Statistics"), this::onStats)
.pos(startX + buttonWidth + spacing, startY + buttonHeight + spacing)
.size(buttonWidth, buttonHeight)
.build()
);
this.widgets.put("exitButton",
Button.builder(Component.literal("Exit"), this::onClose)
.pos(startX + 2 * (buttonWidth + spacing), startY + buttonHeight + spacing)
.size(buttonWidth, buttonHeight)
.build()
);
}
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
this.renderBackground(graphics, mouseX, mouseY, partialTick);
// Draw screen info
Component sizeText = Component.literal("Screen Size: ")
.append(Component.literal(this.currentSize.toString())
.withStyle(Style.EMPTY.withColor(ChatFormatting.YELLOW)));
graphics.drawString(this.font, sizeText, 10, 10, 0xFFFFFF);
// Render all widgets
for (AbstractWidget widget : this.widgets.values()) {
this.addRenderableWidget(widget);
}
super.render(graphics, mouseX, mouseY, partialTick);
}
private void onMainAction(Button button) {
System.out.println("Main action triggered");
}
private void onSettings(Button button) {
System.out.println("Settings triggered");
}
private void onHelp(Button button) {
System.out.println("Help triggered");
}
private void onTools(Button button) {
System.out.println("Tools triggered");
}
private void onStats(Button button) {
System.out.println("Statistics triggered");
}
private enum ScreenSize {
SMALL, MEDIUM, LARGE
}
}
// Example: Complex input handling with keyboard shortcuts
public class AdvancedInputHandler {
private final Map<Integer, Runnable> keyBindings = new HashMap<>();
private boolean shiftPressed = false;
private boolean ctrlPressed = false;
private boolean altPressed = false;
public AdvancedInputHandler() {
setupKeyBindings();
}
private void setupKeyBindings() {
// Register keyboard shortcuts
this.keyBindings.put(InputConstants.KEY_S, this::saveAction);
this.keyBindings.put(InputConstants.KEY_L, this::loadAction);
this.keyBindings.put(InputConstants.KEY_O, this::openAction);
this.keyBindings.put(InputConstants.KEY_N, this::newAction);
this.keyBindings.put(InputConstants.KEY_DELETE, this::deleteAction);
this.keyBindings.put(InputConstants.KEY_F, this::findAction);
this.keyBindings.put(InputConstants.KEY_R, this::replaceAction);
}
@SubscribeEvent
public void onKeyPress(KeyEvent event) {
updateModifierKeys(event);
// Check for modifier combinations
if (this.ctrlPressed && this.shiftPressed) {
if (event.getKey() == InputConstants.KEY_S) {
saveAsAction();
event.setCanceled(true);
return;
}
}
// Check for single key bindings
Runnable action = this.keyBindings.get(event.getKey());
if (action != null && shouldHandleKey(event)) {
action.run();
event.setCanceled(true);
}
}
@SubscribeEvent
public void onKeyRelease(KeyEvent event) {
updateModifierKeys(event);
}
private void updateModifierKeys(KeyEvent event) {
this.shiftPressed = event.getModifiers() == InputConstants.Modifiers.SHIFT;
this.ctrlPressed = event.getModifiers() == InputConstants.Modifiers.CTRL;
this.altPressed = event.getModifiers() == InputConstants.Modifiers.ALT;
}
private boolean shouldHandleKey(KeyEvent event) {
// Don't handle key presses when in chat or text fields
Minecraft mc = Minecraft.getInstance();
if (mc.screen instanceof ChatScreen ||
(mc.screen != null && mc.screen.getFocused() instanceof TextFieldWidget)) {
return false;
}
return true;
}
// Action methods
private void saveAction() {
System.out.println("Save action triggered");
}
private void loadAction() {
System.out.println("Load action triggered");
}
private void openAction() {
System.out.println("Open action triggered");
}
private void newAction() {
System.out.println("New action triggered");
}
private void deleteAction() {
System.out.println("Delete action triggered");
}
private void findAction() {
System.out.println("Find action triggered");
}
private void replaceAction() {
System.out.println("Replace action triggered");
}
private void saveAsAction() {
System.out.println("Save As action triggered");
}
}
// Example: Performance-optimized widget
public class EfficientWidget extends AbstractWidget {
private final Font font;
private String cachedText = "";
private int cachedColor = 0;
private Component cachedComponent = null;
private long lastUpdate = 0;
private static final long CACHE_DURATION = 100; // ms
public EfficientWidget(int x, int y, int width, int height, Component message) {
super(x, y, width, height, message);
this.font = Minecraft.getInstance().font;
}
@Override
protected void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
// Avoid unnecessary updates
long currentTime = System.currentTimeMillis();
if (currentTime - lastUpdate > CACHE_DURATION) {
updateCachedValues();
lastUpdate = currentTime;
}
// Use cached values for rendering
if (cachedComponent != null) {
graphics.drawString(this.font, cachedComponent, this.getX(), this.getY(), cachedColor);
}
}
private void updateCachedValues() {
String newText = this.getMessage().getString();
int newColor = calculateDynamicColor();
// Only update if values changed
if (!newText.equals(cachedText) || newColor != cachedColor) {
cachedText = newText;
cachedColor = newColor;
cachedComponent = Component.literal(cachedText)
.withStyle(Style.EMPTY.withColor(TextColor.fromRgb(
(cachedColor >> 16) & 0xFF,
(cachedColor >> 8) & 0xFF,
cachedColor & 0xFF
)));
}
}
private int calculateDynamicColor() {
// Example: Color changes based on time
long time = System.currentTimeMillis() / 1000;
float hue = (time % 10) / 10.0f;
return Color.HSBtoRGB(hue, 0.7f, 0.9f);
}
}

Problem: Custom widgets don’t receive input Solution: Ensure proper event handling chain and focus management

Problem: Elements render in wrong order Solution: Use strata system and proper widget hierarchy

Problem: Custom GUI causes lag Solution: Implement efficient caching and avoid per-frame object creation

Problem: GUI operations cause crashes Solution: Ensure all GUI operations happen on main thread

  1. Use efficient caching for expensive operations
  2. Implement proper event handling with correct propagation
  3. Design responsive layouts that adapt to different screen sizes
  4. Provide visual feedback for user interactions
  5. Handle accessibility concerns with proper contrast and sizing
  6. Test across different resolutions and UI scales