Essential Mod
repository·main·Indexed 43 days ago
https://github.com/sparkuniverse/essential-modA quality-of-life mod for Minecraft Java Edition. Includes documentation for Elementa's Layout DSL, a high-level domain-specific language for declaring GUI structures using a visual hierarchy, Modifiers for component configuration, and a reactive State API for dynamic UI updates.
What's inside Essential
- The Layout DSL is a high-level domain-specific language used to declare the structure and layout of Elementa components or entire GUI screens. It allows developers to define how UI elements are organized and positioned within a hierarchy.
What is slf4j-to-log4j and when to use it
mainThe
slf4j-to-log4jmodule is a lightweight implementation of the SLF4J API that redirects logging calls to Log4j. It is designed as a 'poor man's version' of the officiallog4j-slf4j-implto avoid dependency on specific Log4j2 implementation details.Use this module when:
- You are operating in an environment where the Log4j version is unpredictable (e.g., MultiMC or archloom may bump or change versions).
- You need to avoid class loading issues on
LaunchWrapper, as this implementation avoids theorg.apache.loggingpackage which is excluded by default on that platform.
Limitations: To maintain simplicity and compatibility, this implementation does not support:
- Class loader shenanigans
LoggerContext- Markers
Use Modifiers to configure component properties
mainModifiers are used to express configurations like position, size, color, effects, and callbacks. They can be chained together using the
.then()method to create a single composite modifier.Common modifier patterns:
- Chaining:
Modifier.alignHorizontal(Alignment.Start).then(halfWidth) - Higher-order modifiers: Can apply modifications conditionally (e.g., only while hovered).
- Escape hatch:
BasicXModifierallows setting constraints directly, though it is generally discouraged in favor of higher-level modifiers.
- Chaining:
Configure components with Modifiers
mainA
Modifieris a composable object used to configure component properties like size, position, color, and callbacks. Modifiers are built using a mini-DSL starting from theModifiercompanion object.Composition
- Chaining: Use extension methods to chain modifications:
Modifier.width(100).height(20). - Combining: Use the
thenmethod or thetheninfix operator to combine two existing modifiers:modifierA.then(modifierB)ormodifierA then modifierB.
Sizing Modifiers
- Fixed Size:
width(pixels)andheight(pixels)set a constant size. - Aspect Ratio:
widthAspect(multiple)andheightAspect(multiple)set size relative to the other axis. - Top-down (Parent to Child):
fillParent(fraction, padding): Grows to a fraction of the parent's size minus padding.fillWidth()/fillHeight(): Grows to fill the parent's axis.fillRemainingWidth()/fillRemainingHeight(): Takes up all remaining space (only one child can use this).
- Bottom-up (Child to Parent):
childBasedWidth(padding)/childBasedHeight(padding): Sizes to match the sum of children's sizes.childBasedMaxWidth(padding)/childBasedMaxHeight(padding): Sizes to match the largest child.
Alignment and Constraints
- Alignment:
alignHorizontal(Alignment),alignVertical(Alignment), oralignBoth(Alignment)sets position. Common alignments areStart,Center, andEnd. - Constraints:
BasicXModifier(where X is a constraint type) acts as an escape hatch to set raw constraints.
- Chaining: Use extension methods to chain modifications:
Use Containers (box, row, column) for layouts
mainElementa provides specialized containers to simplify common layout patterns:
box(modifier): A plain container. By default, it matches the size of its children and aligns them toAlignment.Center. Useful for adding background colors or padding.row(arrangement, alignment): Arranges children horizontally. By default, it matches the height of the tallest child and the width of all children combined. UseArrangementto control surplus horizontal space.column(arrangement, alignment): Arranges children vertically. By default, it matches the width of the widest child and the height of all children combined. UseArrangementto control surplus vertical space.flowContainer(minSeparation, verticalSeparation): Similar to arow, but wraps children to new lines when the width limit is reached.scrollable(modifier, vertical): A container that allows vertical or horizontal scrolling. Content outside the bounds is not rendered.lazyBox(modifier): Lazily initializes its inner scope only when the box is rendered. Use this for large, poorly optimized component lists as a last resort.
Example Layout
window.layout { box(Modifier.width(500).height(500)) { column { row { text("top left") text("top right") } text("*second row*") } } }How the LWJGL3 isolation architecture works
mainBecause Minecraft includes its own version of LWJGL, Essential uses an isolated class loader to provide an up-to-date LWJGL3 environment without dependency conflicts. This architecture splits the functionality into two distinct parts:
The API/Loader (Regular Class Loader): This part is loaded in the standard Minecraft class loader. It is responsible for creating the isolated loader and defining the shared interface definitions. It must not declare classes in the
gg.essential.util.lwjgl3.implpackage.The Implementation (Isolated Class Loader): This part is loaded within the isolated environment and has full access to the shipped LWJGL3 libraries. It contains the actual logic for the interfaces. It must not declare classes in the
gg.essential.util.lwjgl3.apipackage; instead, it should use thegg.essential.util.lwjgl3.implpackage.
Communication Pattern: Communication between the regular and isolated environments must be explicit. Implementation classes are accessed via reflection. To simplify this, the loader provides a factory that returns instances of classes implementing the
gg.essential.util.lwjgl3.apiinterfaces.Naming Convention for Implementations: When creating implementation classes, they must follow these rules:
- They must be located in the
implpackage (not theapipackage). - They must be named after the interface they implement, followed by an
Implsuffix (e.g.,MyInterfaceImpl).
Handle hovering with whenHovered and hoverScope
mainTo change a component's appearance when hovered, use the
whenHoveredmodifier or its shorthand variants (e.g.,hoverColor).Hover Scopes
By default, hover states are independent. To make a group of components (like all children of a button) react to a single hover event, use the
hoverScopemodifier.hoverScope(state): Declares a new hover scope. If no state is provided, it tracks the hover state of the component itself.inheritHoverScope: Used on a child component to make it follow the hover state of its parent's hover scope.
Example: Hovering
// A black box that turns red when hovered box(Modifier.color(Color.Black).hoverColor(Color.RED).then(size))Understanding State in Elementa GUIs
mainWhile the Layout DSL handles structure, dynamic behavior is driven by the
StateAPI.Key concepts for managing dynamic UI:
- MutableState: Most high-level GUI state is stored in multiple
MutableStateinstances, typically one per piece of data (factum). - Deriving State: You can use
maporzipto derive newStateobjects from existing ones (e.g., deriving the text for a label from a raw data state). - Automatic Updates: Most GUI components and dynamic parts of the Layout DSL accept
Stateobjects. When an underlyingMutableStatechanges, the components using that state update automatically. - Collections in State: There is support for
Lists andSets within states. - stateBy: A utility used when
maporzipare insufficient for deriving state.
Note: The documentation for the specific
StateAPI implementation (V1 vs V2) is found in the source code, as the DSL relies heavily on these primitives for reactivity.- MutableState: Most high-level GUI state is stored in multiple
Understand the Layout DSL motivation
mainThe Layout DSL is designed to solve the complexity of traditional Elementa layouts. In standard Elementa, components are often declared as separate fields, making it difficult to visualize the hierarchy and understand how
childOfcalls and constraints relate to one another.Key improvements provided by the Layout DSL include:
- Visual Hierarchy: Uses nested blocks to clearly show parent-child relationships.
- Declarative Modifiers: Replaces complex constraint blocks with
Modifierobjects that express intent (e.g.,fillParent,alignBoth). - Sensible Defaults:
boxcomponents automatically center their children and useChildBasedSizeConstraintinstead of defaulting to0.pixelssize. - High-level Layouts: Provides
rowandcolumnabstractions to manage primary and secondary axis alignment easily.
Build the Essential Mod
mainYou can build the Essential mod for all Minecraft versions or target a specific version and loader.
Build all versions
Run the following command to build all Essential versions for all supported Minecraft versions:
./gradlew buildNote: The first build may take 10 to 60 minutes.
Build a specific version
To build for a specific Minecraft version and loader, use the syntax
./gradlew :<version>-<loader>:build. For example, to build for Minecraft 1.12.2 using Forge:./gradlew :1.12.2-forge:buildOutput Location
Once the build is complete, the resulting JAR files are located in:
versions/<MC-Version>/build/libs/Jar Types:
pinned_*.jar: The version made available via Modrinth/CurseForge.- The other JAR file: Used by the in-game update functionality, third-party mods embedding the Essential Loader, thin container mods, and the Essential Installer.
./gradlew :1.12.2-forge:buildBuild the Essential Container
mainThe Essential Container is a thin mod that downloads the latest version of Essential on its first launch. It is used by the Essential Installer and for downloads on
essential.gg/download.Available Platforms
There are four specific platform versions available:
fabric: For all Fabric environments.launchwrapper: For Forge on Minecraft 1.8.9 and 1.12.2.modlauncher8: For Forge on Minecraft 1.16.5.modlauncher9: For Forge on Minecraft 1.17 and above.
Build Command
To build a specific container platform, run:
./gradlew :loader:container:<platform>:buildReplace
<platform>with one of the four platforms listed above.Output Location
The resulting JAR file is located in:
loader/container/<platform>/build/libs/./gradlew :loader:container:fabric:buildUse LayoutScope to build component trees
mainThe Layout DSL allows you to define the hierarchy of an Elementa UI by calling the
layoutextension function on aUIComponent. Inside thelayoutblock, you have access to aLayoutScopewhich provides methods to add children.To add a child, you can use the
invokeoperator (written as()) on the child component. You can also pass aModifierto thelayoutcall to configure the parent, or pass aModifierand a trailing lambda to a child to configure the child and open a newLayoutScopefor its own children.Common patterns:
myComponent.layout { myChild() }(AddsmyChildtomyComponent)myComponent.layout { myChild(Modifier.fillParent()) { myInnerChild() } }(AddsmyChildwith a modifier, andmyInnerChildas a child ofmyChild)UIContainer()()(Inline constructor followed by theinvokeoperator to add it to the current scope)
val myComponent = UIContainer() val myChild = UIContainer() val myInnerChild = UIContainer() myComponent.layout(Modifier.width(100).height(20)) { myChild() myChild(Modifier.fillParent()) { myInnerChild() } UIContainer()() }