Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/main/java/com/cleanroommc/modularui/ClientProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.cleanroommc.modularui.factory.inventory.InventoryTypes;
import com.cleanroommc.modularui.holoui.HoloScreenEntity;
import com.cleanroommc.modularui.holoui.ScreenEntityRender;
import com.cleanroommc.modularui.hud.HudManager;
import com.cleanroommc.modularui.network.ModularNetwork;
import com.cleanroommc.modularui.screen.ClientScreenHandler;
import com.cleanroommc.modularui.test.TestItem;
Expand Down Expand Up @@ -66,6 +67,8 @@ void preInit(FMLPreInitializationEvent event) {
// registered to both buses since handled events are not bound to a single bus
FMLCommonHandler.instance().bus().register(clientScreenHandler);
MinecraftForge.EVENT_BUS.register(clientScreenHandler);
// register the HUD manager on the Forge event bus
HudManager.init();
AnimatorManager.init();

if (ModularUIConfig.enableTestGuis) {
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/com/cleanroommc/modularui/hud/HudContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.cleanroommc.modularui.hud;

import com.cleanroommc.modularui.screen.ModularScreen;
import com.cleanroommc.modularui.screen.viewport.ModularGuiContext;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;

import org.jetbrains.annotations.ApiStatus;

/**
* A read-only context for HUD elements: disables the hover/focus/drag subsystem, since HUD
* elements are display-only.
*/
@ApiStatus.Internal
@SideOnly(Side.CLIENT)
public class HudContext extends ModularGuiContext {

public HudContext(ModularScreen screen) {
super(screen);
}

@Override
public void onFrameUpdate() {
// no-op: display-only, no hover/focus/drag
}
}
161 changes: 161 additions & 0 deletions src/main/java/com/cleanroommc/modularui/hud/HudElement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package com.cleanroommc.modularui.hud;

import com.cleanroommc.modularui.screen.ModularPanel;
import com.cleanroommc.modularui.screen.ModularScreen;
import com.cleanroommc.modularui.screen.viewport.ModularGuiContext;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;

import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Objects;
import java.util.function.Function;

/**
* A single HUD element rendered on top of the game world.
* <p>
* A HUD element is <b>display-only</b>: it cannot capture mouse or keyboard input, has no
* hover events, no focus, and no drag support. The context's mouse/keyboard state is still
* updated each frame so widgets can read absolute mouse position in their {@code draw()} methods.
*
* <h2>Example</h2>
* <pre>{@code
* HudElement hud = new HudElement("mymod", ctx -> {
* ModularPanel panel = new ModularPanel("status");
* panel.size(100, 20).pos(5, 5);
* panel.child(new TextWidget()
* .text(IKey.dynamic(() -> "Current time: " + System.currentTimeMillis())));
* return panel;
* });
* HudManager.register(hud);
* }</pre>
*/
@SideOnly(Side.CLIENT)
public class HudElement {

@Getter private final ModularScreen screen;
private final HudWrapper wrapper;

@Getter private boolean enabled = true;
private boolean visibleInWorld = true;
private boolean visibleInGui = true;
private boolean overGui = false;
@Getter private int renderPriority = 0;

/**
* Creates a new HUD element with the given owner and main panel.
*
* @param owner owner of this element (usually a mod id). Used for theme lookup.
* @param mainPanel main panel of this element.
*/
public HudElement(@NotNull String owner, @NotNull ModularPanel mainPanel) {
this(owner, ctx -> mainPanel);
}

/**
* Creates a new HUD element with the given owner and panel creator.
*
* @param owner owner of this element (usually a mod id). Used for theme lookup.
* @param panelCreator function which creates the main panel. Receives the (read-only)
* {@link ModularGuiContext} of this element.
*/
public HudElement(@NotNull String owner, @NotNull Function<ModularGuiContext, ModularPanel> panelCreator) {
Objects.requireNonNull(owner, "The owner must not be null!");
Objects.requireNonNull(panelCreator, "The panel creator must not be null!");
this.screen = new HudScreen(owner, panelCreator);
this.wrapper = new HudWrapper(this.screen);
}

/**
* Sets whether this element is rendered when no screen is open (in-game).
*
* @param visibleInWorld true to render in-world
* @return this
*/
public HudElement visibleInWorld(boolean visibleInWorld) {
this.visibleInWorld = visibleInWorld;
return this;
}

/**
* Sets whether and where this element is drawn while a {@code GuiScreen} (inventory, etc.) is
* open. Visible underneath open screens by default, not over them: most HUD elements (persistent
* overlays, ambient status) should not cover an open screen's own content.
*
* @param visibleInGui true to render at all while a screen is open
* @param isOverGui if visible, true to draw on top of the screen, false to draw underneath it
* @return this
*/
public HudElement visibleInGui(boolean visibleInGui, boolean isOverGui) {
this.visibleInGui = visibleInGui;
this.overGui = isOverGui;
return this;
}

/**
* Sets the render priority. Higher values are drawn on top of lower values.
*
* @param renderPriority priority value
* @return this
*/
public HudElement renderPriority(int renderPriority) {
this.renderPriority = renderPriority;
return this;
}

/**
* Enables or disables this element. Disabled elements are not rendered or ticked.
*
* @param enabled true to enable
* @return this
*/
public HudElement enabled(boolean enabled) {
this.enabled = enabled;
return this;
}

public ModularPanel getPanel() {
return screen.getMainPanel();
}

/**
* @return true if this element should be rendered in-world (no screen open)
*/
public boolean isVisibleInWorld() {
return enabled && visibleInWorld;
}

/**
* @return true if this element should be rendered on top of an open {@code GuiScreen}
*/
public boolean isVisibleOverGui() {
return enabled && visibleInGui && overGui;
}

/**
* @return true if this element should be rendered underneath an open {@code GuiScreen}
*/
public boolean isVisibleUnderGui() {
return enabled && visibleInGui && !overGui;
}

/**
* @return true if this element can be ticked (enabled)
*/
boolean canDraw() {
return enabled;
}

@Nullable
public String getOwner() {
return screen.getOwner();
}

@Override
public String toString() {
return "HudElement[" + screen.getOwner() + ":" + screen.getName() + "]";
}
}
175 changes: 175 additions & 0 deletions src/main/java/com/cleanroommc/modularui/hud/HudManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package com.cleanroommc.modularui.hud;

import com.cleanroommc.modularui.utils.GlStateManager;

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType;
import cpw.mods.fml.common.eventhandler.EventPriority;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;

import org.jetbrains.annotations.ApiStatus;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;

/**
* Manages all registered {@link HudElement HUD elements}.
* <p>
* Subscribes to Forge render/tick events and draws each visible element. HUD elements are
* display-only (no input capture). See {@link HudElement} for details.
*/
@ApiStatus.Internal
@SideOnly(Side.CLIENT)
public class HudManager {

private static final List<HudElement> elements = new ArrayList<>();
private static int lastWidth = -1;
private static int lastHeight = -1;
private static boolean initialized = false;
private static final HudManager INSTANCE = new HudManager();

private HudManager() {}

/**
* Registers the HUD manager on the Forge event bus. Must be called once during client init,
* typically from {@code ClientProxy.preInit()}.
*/
public static void init() {
if (initialized) return;
initialized = true;
// must be an instance method - Forge's ASMEventHandler crashes on a static @SubscribeEvent
net.minecraftforge.common.MinecraftForge.EVENT_BUS.register(INSTANCE);
}

/**
* Registers a HUD element so it is rendered and ticked.
*
* @param element the element to register
*/
public static void register(HudElement element) {
if (element == null) throw new NullPointerException("Element must not be null!");
if (!elements.contains(element)) {
elements.add(element);
}
}

/**
* Unregisters a HUD element.
*
* @param element the element to unregister
* @return true if the element was registered and is now removed
*/
public static boolean unregister(HudElement element) {
return elements.remove(element);
}

/**
* Removes all registered HUD elements.
*/
public static void clear() {
elements.clear();
}

/**
* Renders HUD elements over the game world (no screen open), after the vanilla HUD.
*/
@SubscribeEvent(priority = EventPriority.LOW)
public void onRenderGameOverlay(RenderGameOverlayEvent.Post event) {
if (event.type != ElementType.ALL) return;
Minecraft mc = Minecraft.getMinecraft();
if (mc.currentScreen != null) return;
drawVisible(event.mouseX, event.mouseY, event.partialTicks, HudElement::isVisibleInWorld);
}

/**
* Renders HUD elements underneath an open {@link GuiScreen} (before the screen draws).
*/
@SubscribeEvent(priority = EventPriority.LOW)
public void onGuiDrawPre(GuiScreenEvent.DrawScreenEvent.Pre event) {
drawVisible(event.mouseX, event.mouseY, event.renderPartialTicks, HudElement::isVisibleUnderGui);
}

/**
* Renders HUD elements over an open {@link GuiScreen} (after the screen draws).
*/
@SubscribeEvent(priority = EventPriority.HIGH)
public void onGuiDrawPost(GuiScreenEvent.DrawScreenEvent.Post event) {
drawVisible(event.mouseX, event.mouseY, event.renderPartialTicks, HudElement::isVisibleOverGui);
}

private static void drawVisible(int mouseX, int mouseY, float partialTicks, Predicate<HudElement> visibility) {
if (elements.isEmpty()) return;
checkResize();
boolean anyVisible = false;
for (HudElement e : elements) {
if (visibility.test(e)) {
anyVisible = true;
e.getScreen()
.getContext()
.updateState(mouseX, mouseY, partialTicks);
}
}
if (!anyVisible) return;

// Render from lowest to highest priority so higher priority draws on top.
elements.stream()
.filter(visibility)
.sorted(Comparator.comparingInt(HudElement::getRenderPriority))
.forEach(e -> {
GlStateManager.enableBlend();
GlStateManager.color(1f, 1f, 1f, 1f);
e.getScreen()
.drawScreen();
e.getScreen()
.drawForeground();
});
}

/**
* Ticks all enabled, drawable HUD elements at 20 Hz (client tick).
*/
@SubscribeEvent
public void onClientTick(TickEvent.ClientTickEvent event) {
if (event.phase != TickEvent.Phase.END) return;
if (elements.isEmpty()) return;
for (HudElement e : elements) {
if (e.canDraw()) {
e.getScreen()
.onUpdate();
}
}
}

private static void checkResize() {
Minecraft mc = Minecraft.getMinecraft();
int scaledWidth;
int scaledHeight;
if (mc.currentScreen != null) {
scaledWidth = mc.currentScreen.width;
scaledHeight = mc.currentScreen.height;
} else {
net.minecraft.client.gui.ScaledResolution res = new net.minecraft.client.gui.ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
scaledWidth = res.getScaledWidth();
scaledHeight = res.getScaledHeight();
}

boolean resized = scaledWidth != lastWidth || scaledHeight != lastHeight;
if (resized) {
lastWidth = scaledWidth;
lastHeight = scaledHeight;
}
for (HudElement e : elements) {
if (resized || !e.getScreen().getPanelManager().isOpen()) {
e.getScreen().onResize(scaledWidth, scaledHeight);
}
}
}
}
Loading