Essential Mod

repository·main·Indexed 43 days ago

https://github.com/sparkuniverse/essential-mod

A 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.

Tokens
11.5K
Snippets
31
Records
47
Agent score
55%

What's inside Essential

  1. What is slf4j-to-log4j and when to use it

    main

    The slf4j-to-log4j module 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 official log4j-slf4j-impl to 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 the org.apache.logging package which is excluded by default on that platform.

    Limitations: To maintain simplicity and compatibility, this implementation does not support:

    • Class loader shenanigans
    • LoggerContext
    • Markers
  2. Use Modifiers to configure component properties

    main

    Modifiers 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: BasicXModifier allows setting constraints directly, though it is generally discouraged in favor of higher-level modifiers.
  3. Configure components with Modifiers

    main

    A Modifier is a composable object used to configure component properties like size, position, color, and callbacks. Modifiers are built using a mini-DSL starting from the Modifier companion object.

    Composition

    • Chaining: Use extension methods to chain modifications: Modifier.width(100).height(20).
    • Combining: Use the then method or the then infix operator to combine two existing modifiers: modifierA.then(modifierB) or modifierA then modifierB.

    Sizing Modifiers

    • Fixed Size: width(pixels) and height(pixels) set a constant size.
    • Aspect Ratio: widthAspect(multiple) and heightAspect(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), or alignBoth(Alignment) sets position. Common alignments are Start, Center, and End.
    • Constraints: BasicXModifier (where X is a constraint type) acts as an escape hatch to set raw constraints.
  4. Use Containers (box, row, column) for layouts

    main

    Elementa 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 to Alignment.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. Use Arrangement to 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. Use Arrangement to control surplus vertical space.
    • flowContainer(minSeparation, verticalSeparation): Similar to a row, 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*")
            }
        }
    }
  5. How the LWJGL3 isolation architecture works

    main

    Because 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:

    1. 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.impl package.

    2. 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.api package; instead, it should use the gg.essential.util.lwjgl3.impl package.

    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.api interfaces.

    Naming Convention for Implementations: When creating implementation classes, they must follow these rules:

    • They must be located in the impl package (not the api package).
    • They must be named after the interface they implement, followed by an Impl suffix (e.g., MyInterfaceImpl).
  6. Handle hovering with whenHovered and hoverScope

    main

    To change a component's appearance when hovered, use the whenHovered modifier 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 hoverScope modifier.

    • 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))
  7. Understanding State in Elementa GUIs

    main

    While the Layout DSL handles structure, dynamic behavior is driven by the State API.

    Key concepts for managing dynamic UI:

    • MutableState: Most high-level GUI state is stored in multiple MutableState instances, typically one per piece of data (factum).
    • Deriving State: You can use map or zip to derive new State objects 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 State objects. When an underlying MutableState changes, the components using that state update automatically.
    • Collections in State: There is support for Lists and Sets within states.
    • stateBy: A utility used when map or zip are insufficient for deriving state.

    Note: The documentation for the specific State API implementation (V1 vs V2) is found in the source code, as the DSL relies heavily on these primitives for reactivity.

  8. Understand the Layout DSL motivation

    main

    The 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 childOf calls 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 Modifier objects that express intent (e.g., fillParent, alignBoth).
    • Sensible Defaults: box components automatically center their children and use ChildBasedSizeConstraint instead of defaulting to 0.pixels size.
    • High-level Layouts: Provides row and column abstractions to manage primary and secondary axis alignment easily.
  9. Build the Essential Mod

    main

    You 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 build

    Note: 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:build

    Output 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:build
  10. Build the Essential Container

    main

    The 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>:build

    Replace <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:build
  11. Use LayoutScope to build component trees

    main

    The Layout DSL allows you to define the hierarchy of an Elementa UI by calling the layout extension function on a UIComponent. Inside the layout block, you have access to a LayoutScope which provides methods to add children.

    To add a child, you can use the invoke operator (written as ()) on the child component. You can also pass a Modifier to the layout call to configure the parent, or pass a Modifier and a trailing lambda to a child to configure the child and open a new LayoutScope for its own children.

    Common patterns:

    • myComponent.layout { myChild() } (Adds myChild to myComponent)
    • myComponent.layout { myChild(Modifier.fillParent()) { myInnerChild() } } (Adds myChild with a modifier, and myInnerChild as a child of myChild)
    • UIContainer()() (Inline constructor followed by the invoke operator 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()()
    }