Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
2208c84
optimise adaptable drawing
brachy84 Nov 28, 2025
3b50bd1
fix sync value update listener only being called on sync
brachy84 Nov 29, 2025
5da978f
minor sync value improvement
brachy84 Nov 29, 2025
dabc652
call change listener after set source
brachy84 Nov 29, 2025
2e2a968
more readable panel sync handler syncing
brachy84 Nov 29, 2025
33c004a
disallow registering panel sync handlers late
brachy84 Nov 29, 2025
366d13d
Rework sync handler and value validation (#181)
brachy84 Dec 4, 2025
fc64fa3
fix #182
brachy84 Dec 6, 2025
46b0840
rename WidgetTree.stream() to WidgetTree.flatStream()
brachy84 Dec 6, 2025
47a00f6
fix #179
brachy84 Dec 6, 2025
8d54505
fix cycle button widget crash when no tooltip
brachy84 Dec 6, 2025
3ac3f46
implicit state count of cycle buttons
brachy84 Dec 6, 2025
5f19c18
sync handler hyper visor
brachy84 Dec 6, 2025
cae5224
update readme
brachy84 Dec 7, 2025
1d7a461
adjust post the log anim & other
brachy84 Dec 7, 2025
88fc32c
helper class to read image sizes fast
brachy84 Dec 7, 2025
fcd11b1
hollow rectangles and fluent rectangle setter names
brachy84 Dec 7, 2025
566b879
experimental graph plotting
brachy84 Dec 8, 2025
7eb933d
ItemDisplayWidget implements RecipeViewerIngredientProvider
brachy84 Dec 8, 2025
3b054f6
Add overload of callSyncedAction with no packet arg for actions that …
Zorbatron Dec 8, 2025
987ef92
improve graph geometry, cache graph vertices, support multiple plots …
brachy84 Dec 9, 2025
4dcf5e8
graph aspect ratio
brachy84 Dec 9, 2025
9f977f3
aspectRatio for Icon
brachy84 Dec 9, 2025
857a546
warn when aspect ratio cant be applied
brachy84 Dec 9, 2025
60173fb
multiple default colors & apply stencil to plot
brachy84 Dec 9, 2025
6229105
float array math
brachy84 Dec 9, 2025
865261b
fallback to sync hypervisor
brachy84 Dec 10, 2025
90eee69
use double for plot data points & more math
brachy84 Dec 10, 2025
8be3ca0
Add shortcut method to toggle panel open or closed (#184)
Zorbatron Dec 11, 2025
03b6e61
fix re registering sync handlers when already registered to another psm
brachy84 Dec 14, 2025
ba58728
ability to register custom IMuiScreen to UISettings
brachy84 Dec 14, 2025
1f97eb3
null safe DrawableStack
brachy84 Dec 14, 2025
b9c55a6
port and refactor FluidDisplayWidget, refactor FluidSlot
brachy84 Dec 14, 2025
8e2e6cb
fix
brachy84 Dec 14, 2025
7e8ce68
recipe viewer ingredient provider for fluid display widget
brachy84 Dec 14, 2025
47779c3
remove useless alpha masking
brachy84 Dec 14, 2025
4461dca
dont just close container on gui stack pop
brachy84 Dec 27, 2025
2e0654b
remove some useless childIf overloads
brachy84 Jan 1, 2026
31a6ecc
remove empty file
brachy84 Jan 13, 2026
1afc6b7
fix IDrawable.drawAtZero
brachy84 Jan 1, 2026
37801b7
ability to override color on UITexture without theme
brachy84 Jan 2, 2026
cd3e5e4
make adding bogo sort buttons much easier
brachy84 Jan 2, 2026
061d707
rework syncing so inactive screens can still receive packets
brachy84 Jan 2, 2026
ea627e5
experimental container stack
brachy84 Jan 2, 2026
5d2c9dc
ability to reopen containers
brachy84 Jan 3, 2026
94d4aba
Begone mXparser (#188)
brachy84 Jan 4, 2026
d2bb7ab
fix missing setter in FluidDisplayWidget
brachy84 Jan 7, 2026
8106b19
fix UITexture.parseFromJson()
brachy84 Jan 10, 2026
217eb10
fix
brachy84 Jan 13, 2026
f768e4a
method to remove current tooltips
brachy84 Jan 13, 2026
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
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@ With ModularUI you simply call `.child(new FluidSlot().syncHandler(new FluidTank
- panel system similar to windows
- widgets are placed in a tree like structure
- widget rendering and interactions are automatically handled
- no need to create GUI texture sheets, each widget is rendered dynamically
- easy and dynamic widget sizing and positioning
- syncing widget values
- build in APIs for various UI things like color, stencil (fancy scissor) and animations
- easy syncing between client and server without
- good for client only GUIs and client-server synced GUIs
- GUI themes are loaded via JSON and can be added and modified by resourcepacks
- NEI compat for things like exclusion zones
- NEI compat for things like exclusion zones and ghost ingredients

## Planned Features
- Create NEI recipe handlers with MUI
- Improved text rendering

### History
- First appearance of ModularUI in GTCE by Archengius
Expand Down
8 changes: 6 additions & 2 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,17 @@
dependencies {
api("com.github.GTNewHorizons:GTNHLib:0.7.0:dev")

shadowImplementation('org.mariuszgromada.math:MathParser.org-mXparser:6.1.0')
// used in evalex
compileOnly("org.projectlombok:lombok:1.18.42")
annotationProcessor("org.projectlombok:lombok:1.18.42")
testCompileOnly("org.projectlombok:lombok:1.18.42")
testAnnotationProcessor("org.projectlombok:lombok:1.18.42")

implementation("com.github.GTNewHorizons:NotEnoughItems:2.8.9-GTNH:dev")
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.2:dev") { transitive = false }
compileOnly("com.github.GTNewHorizons:GT5-Unofficial:5.09.52.26:dev") { transitive = false }
compileOnly("com.github.GTNewHorizons:ModularUI:1.3.0:dev") { transitive = false }
implementation("com.github.GTNewHorizons:Baubles-Expanded:2.2.0-GTNH:dev") { transitive = false }
implementation("com.github.GTNewHorizons:Baubles-Expanded:2.2.0-GTNH:dev") { transitive = false }
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
//compileOnly rfg.deobf("curse.maven:neverenoughanimation-1062347:6552319")
compileOnly files("libs/neverenoughanimations-v1.0.6-1-7-10.8+91c551807b-dirty-dev.jar")
Expand Down
5 changes: 2 additions & 3 deletions src/main/java/com/cleanroommc/modularui/ClientProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,8 @@ void preInit(FMLPreInitializationEvent event) {
} catch (IOException | LWJGLException e) {
throw new RuntimeException(e);
} catch (Throwable e) {
ModularUI.LOGGER.info("Custom Cursors failed to load. This is likely because an incompatible LWJGL version was used like with CleanroomLoader.");
// TODO: proper lwjgl 3 support
// currently it seems this is not even triggered and the cursors are created correctly, but when the cursors are set nothing happens
ModularUI.LOGGER.info("Custom Cursors failed to load.");
// lwjgl3: currently it seems this is not even triggered and the cursors are created correctly, but when the cursors are set nothing happens
}
}

Expand Down
8 changes: 1 addition & 7 deletions src/main/java/com/cleanroommc/modularui/ModularUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.mariuszgromada.math.mxparser.License;

import java.util.function.Predicate;

Expand Down Expand Up @@ -44,11 +43,6 @@ public class ModularUI {
public static final boolean isTestEnv = Launch.blackboard == null;
public static final boolean isDevEnv = isTestEnv || (boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment");

static {
// confirm mXparser license
License.iConfirmNonCommercialUse("GTNewHorizons");
}

@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
proxy.preInit(event);
Expand All @@ -69,7 +63,7 @@ public enum Mods {
BAUBLES(ModIds.BAUBLES),
BOGOSORTER(ModIds.BOGOSORTER),
GT5U(ModIds.GT5U, mod -> !Loader.isModLoaded(ModIds.GT6)),
HODGEPODGE(ModIds.BOGOSORTER),
HODGEPODGE(ModIds.HODGEPODGE),
NEI(ModIds.NEI),
NEA(ModIds.NEA);

Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/cleanroommc/modularui/api/IPanelHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ static IPanelHandler simple(ModularPanel parent, SecondaryPanel.IPanelBuilder pr
@ApiStatus.OverrideOnly
void closePanelInternal();

/**
* Toggles this panel open or closed. Delegates to {@link #openPanel()} and {@link #closePanel()}.
*
* @return {@code true} if the panel was opened, {@code false} if it was closed
*/
default boolean togglePanel() {
if (isPanelOpen()) {
closePanel();
return false;
} else {
openPanel();
return true;
}
}

/**
* Deletes the current cached panel. Should not be used frequently.
* This only works on panels which don't have {@link ItemSlotSH} sync handlers.
Expand Down
30 changes: 14 additions & 16 deletions src/main/java/com/cleanroommc/modularui/api/MCHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import com.cleanroommc.modularui.ModularUI;
import com.cleanroommc.modularui.api.widget.Interactable;
import com.cleanroommc.modularui.ModularUIConfig;
import com.cleanroommc.modularui.api.drawable.IKey;
import com.cleanroommc.modularui.network.ModularNetwork;
import com.cleanroommc.modularui.network.NetworkUtils;

import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityClientPlayerMP;
Expand Down Expand Up @@ -32,38 +36,38 @@
public class MCHelper {

public static boolean hasMc() {
return getMc() != null;
return NetworkUtils.isDedicatedClient() && getMc() != null;
}

@SideOnly(Side.CLIENT)
public static @Nullable Minecraft getMc() {
return Minecraft.getMinecraft();
}

@SideOnly(Side.CLIENT)
public static @Nullable EntityPlayer getPlayer() {
if (hasMc()) {
return getMc().thePlayer;
}
return null;
}

@SideOnly(Side.CLIENT)
public static boolean closeScreen() {
if (!hasMc()) return false;
EntityPlayer player = getPlayer();
if (player != null) {
player.closeScreen();
return true;
}
Minecraft.getMinecraft().displayGuiScreen(null);
return false;
}

@SideOnly(Side.CLIENT)
public static void popScreen(boolean openParentOnClose, GuiScreen parent) {
EntityPlayer player = MCHelper.getPlayer();
if (player != null) {
// TODO: should we really close container here?
prepareCloseContainer(player);
// container should not just be closed here
// instead they are kept in a stack until all screens are closed
if (openParentOnClose) {
Minecraft.getMinecraft().displayGuiScreen(parent);
ModularNetwork.CLIENT.reopenSyncerOf(parent);
} else {
Minecraft.getMinecraft().displayGuiScreen(null);
}
Expand All @@ -74,14 +78,6 @@ public static void popScreen(boolean openParentOnClose, GuiScreen parent) {
}

@SideOnly(Side.CLIENT)
private static void prepareCloseContainer(EntityPlayer entityPlayer) {
if (entityPlayer instanceof EntityClientPlayerMP clientPlayerMP) {
clientPlayerMP.sendQueue.addToSendQueue(new C0DPacketCloseWindow(clientPlayerMP.openContainer.windowId));
}
entityPlayer.openContainer = entityPlayer.inventoryContainer;
entityPlayer.inventory.setItemStack(null);
}

public static boolean displayScreen(GuiScreen screen) {
Minecraft mc = getMc();
if (mc != null) {
Expand All @@ -91,11 +87,13 @@ public static boolean displayScreen(GuiScreen screen) {
return false;
}

@SideOnly(Side.CLIENT)
public static GuiScreen getCurrentScreen() {
Minecraft mc = getMc();
return mc != null ? mc.currentScreen : null;
}

@SideOnly(Side.CLIENT)
public static FontRenderer getFontRenderer() {
if (hasMc()) return getMc().fontRenderer;
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ default void drawPadded(GuiContext context, Area area, WidgetTheme widgetTheme)
*/
@SideOnly(Side.CLIENT)
default void drawAtZero(GuiContext context, Area area, WidgetTheme widgetTheme) {
draw(context, 0, 0, area.paddedWidth(), area.paddedHeight(), widgetTheme);
draw(context, 0, 0, area.width, area.height, widgetTheme);
}

/**
Expand All @@ -111,7 +111,6 @@ default void drawAtZeroPadded(GuiContext context, Area area, WidgetTheme widgetT
draw(context, area.getPadding().getLeft(), area.getPadding().getTop(), area.paddedWidth(), area.paddedHeight(), widgetTheme);
}


/**
* @return if theme color can be applied on this drawable
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.cleanroommc.modularui.api.drawable;

import com.cleanroommc.modularui.drawable.text.RichText;
import com.cleanroommc.modularui.drawable.text.Spacer;
import com.cleanroommc.modularui.utils.Alignment;

Expand All @@ -13,6 +12,16 @@ public interface IRichTextBuilder<T extends IRichTextBuilder<T>> {

IRichTextBuilder<?> getRichText();

/**
* Removes all text and style.
*
* @return this
*/
default T reset() {
getRichText().reset();
return getThis();
}

/**
* Adds a string to the current line
*
Expand Down
120 changes: 120 additions & 0 deletions src/main/java/com/cleanroommc/modularui/api/value/ISyncOrValue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.cleanroommc.modularui.api.value;

import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* An interface that is implemented on {@link IValue} and {@link com.cleanroommc.modularui.value.sync.SyncHandler SyncHandler} for easier
* validation and setters.
*/
@ApiStatus.NonExtendable
public interface ISyncOrValue {

/**
* A sync handler or value representing null.
*/
ISyncOrValue EMPTY = new ISyncOrValue() {
@Override
public <T> @Nullable T castNullable(Class<T> type) {
return null;
}

@Override
public boolean isTypeOrEmpty(Class<?> type) {
return true;
}
};

/**
* Returns the given sync handler or value or {@link #EMPTY} if null.
*
* @param syncOrValue sync handler or value
* @return a non-null representation of the given sync handler or value
*/
@NotNull
static ISyncOrValue orEmpty(@Nullable ISyncOrValue syncOrValue) {
return syncOrValue != null ? syncOrValue : EMPTY;
}

/**
* Returns if this sync handler or value is an instance of the given type or if this represents null. This is useful, when the value or
* sync handler can be null in the widget.
*
* @param type type to check for
* @return if this sync handler or value is an instance of the type or empty
*/
default boolean isTypeOrEmpty(Class<?> type) {
return type.isAssignableFrom(getClass());
}

/**
* Casts this sync handler or value to the given type or null if this isn't a subtype of the given type.
*
* @param type type to cast this sync handle or value to
* @param <T> type to cast to
* @return this cast sync handler or value
*/
@Nullable
@SuppressWarnings("unchecked")
default <T> T castNullable(Class<T> type) {
return type.isAssignableFrom(getClass()) ? (T) this : null;
}

/**
* Casts this sync handler or value to a {@link IValue IValue&lt;V&gt;} if it is a value handler and the containing value is of type
* {@link V} else null.
*
* @param valueType expected type of the containing value
* @param <V> expected type of the containing value
* @return a {@link IValue IValue&lt;V&gt;} if types match or null
*/
@Nullable
default <V> IValue<V> castValueNullable(Class<V> valueType) {
return null;
}

/**
* Casts this sync handler or value to the given type or throws an exception if this isn't a subtype of the given type.
*
* @param type type to cast this sync handle or value to
* @param <T> type to cast to
* @return this cast sync handler or value
* @throws IllegalStateException if this is not a subtype of the given type
*/
default <T> T castOrThrow(Class<T> type) {
T t = castNullable(type);
if (t == null) {
if (!isSyncHandler() && !isValueHandler()) {
throw new IllegalStateException("Empty sync handler or value can't be used for anything.");
}
String self = isSyncHandler() ? "sync handler" : "value";
throw new IllegalStateException("Can't cast " + self + " of type '" + getClass().getSimpleName() + "' to type '" + type.getSimpleName() + "'.");
}
return t;
}

/**
* Returns if the containing value of this is of the given type. If this is not a value it will always return false.
*
* @param type expected value type
* @return if the containing value of this is of the given type
*/
default boolean isValueOfType(Class<?> type) {
return false;
}

/**
* @return if this is a sync handler (false if this represents null)
*/
default boolean isSyncHandler() {
return false;
}

/**
* @return if this is a value handler (false if this represents null)
*/
default boolean isValueHandler() {
return false;
}
}
19 changes: 18 additions & 1 deletion src/main/java/com/cleanroommc/modularui/api/value/IValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*
* @param <T> value type
*/
public interface IValue<T> {
public interface IValue<T> extends ISyncOrValue {

/**
* Gets the current value.
Expand All @@ -20,4 +20,21 @@ public interface IValue<T> {
* @param value new value
*/
void setValue(T value);

Class<T> getValueType();

default boolean isValueOfType(Class<?> type) {
return type.isAssignableFrom(getValueType());
}

@SuppressWarnings("unchecked")
@Override
default <V> IValue<V> castValueNullable(Class<V> valueType) {
return isValueOfType(valueType) ? (IValue<V>) this : null;
}

@Override
default boolean isValueHandler() {
return true;
}
}
Loading