Skin System

Overview of Skin, SkinRenderer, and Option

The DynamicHUD library provides a flexible system for rendering context menus using a Skin System (similar to a "theme system"). The Skin class defines the visual style and layout of a context menu, SkinRenderer handles the rendering of specific option types, and Option represents configurable settings within the menu.

Skin

The Skin class is an abstract base class that defines how a context menu is rendered. It manages the overall layout, appearance, and interaction of the menu, delegating the rendering of individual options to SkinRenderer instances.

Key Stuff

  • Rendering the Context Menu: Implements renderContextMenu to draw the menu’s background, borders, and structure.

  • Option Rendering Delegation: Uses a map of SkinRenderer instances to render specific option types.

  • Input Handling: Provides methods like mouseClicked, mouseScrolled, etc., for user interactions.

  • Group Support: Optionally implements GroupableSkin for hierarchical option groups.

Key Methods (more stuff)

  • addRenderer(Class<T>, Supplier<SkinRenderer<?>>): Registers a renderer for a specific option type.

  • getRenderer(Class<T>): Retrieves the renderer for an option type.

  • getOptions(ContextMenu<?>): Returns the menu’s options, flattening groups if supportsGroups() is false.

  • renderContextMenu(DrawContext, ContextMenu<?>, int, int): Abstract method to render the menu.

  • shouldCreateNewScreen(): Determines if the menu opens in a new screen.

  • clone(): A mandatory implementation to create a new instance of the skin with the same values that need to be passed on to Sub-Menu option for same rendering as the parent menu.

Example Usage

Skin skin = new MinecraftSkin(PanelColor.DARK_PANEL);
//By default ContextMenuProperties assigns ClassicSkin.
ContextMenuProperties properties = ContextMenuProperties.createGenericSimplified().skin(skin);
ContextMenu<ContextMenuProperties> menu = new ContextMenu<>(100, 100, properties);

Common Issues

  • Missing Renderer: If getRenderer returns null, ensure addRenderer is called for the option type.

    • addRenderer(BooleanOption.class, ClassicBooleanRenderer::new);
  • Incorrect Scaling: Mouse coordinates may misalign if scaling isn’t handled properly.

    • Fix: Use DrawHelper.customScaledProjection and DrawHelper.scaledProjection to manage scaling. And if you use customScaledProjection then make sure you divide the mouse coordinates with the scale factor

SkinRenderer

The SkinRenderer interface defines how individual options are rendered and interacted with. Each SkinRenderer implementation is specific to an Option subclass, providing custom rendering and input handling.

Key Responsibilities

  • Rendering Options: Implements render to draw the option’s visual representation.

  • Input Handling: Provides default implementations for mouse and keyboard events, delegating to the option if needed.

  • Initialization: Optionally implements init for setup tasks.

Key Methods

  • render(DrawContext, T, int, int, int, int): Renders the option at the specified coordinates.

  • mouseClicked(T, double, double, int): Handles mouse clicks, typically delegating to the option.

  • mouseDragged(T, double, double, int, double, double): Handles mouse dragging for interactive options.

  • init(T): Optional initialization for the renderer.

Example Implementation

public class CustomBooleanRenderer implements SkinRenderer<BooleanOption> {
    @Override
    public void render(DrawContext drawContext, BooleanOption option, int x, int y, int mouseX, int mouseY) {
        option.setPosition(x, y);
        option.setHeight(10);
        option.setWidth(50);
        drawContext.drawText(mc.textRenderer, option.getName(), x, y, option.get() ? 0x00FF00 : 0xFF0000, false);
    }
}

Common Issues

  • Incorrect Dimensions: Failing to set option.setWidth and option.setHeight can cause layout issues.

    • Fix: Always set dimensions in render.

  • Mouse Coordinate Mismatch: Mouse coordinates may be unscaled, causing incorrect hit detection.

    • Fix: Scale mouse coordinates using the menu’s scale factor:

      mouseX = (int) (mouseX / SCALE_FACTOR);

Interaction Between Skin, SkinRenderer, and Option

  1. Initialization:

    • A ContextMenu is created with a Skin via ContextMenuProperties.

    • The Skin registers SkinRenderer instances for each supported Option type.

    • Options are added to the menu, and their renderers are set via updateProperties.

  2. Rendering:

    • The Skin’s renderContextMenu method draws the menu’s structure.

    • For each Option, the Skin retrieves the appropriate SkinRenderer and calls its render method.

    • The SkinRenderer updates the option’s position and dimensions and draws its visual elements.

    • SkinRenderer also should (whenever possible) render the description (a.k.a tooltips) of options.

  3. Input Handling:

    • The Skin handles menu-level input (e.g., closing the menu).

    • Option-specific input is delegated to the SkinRenderer, which may call the Option’s input methods.

    • The Option updates its value via set when interacted with.

Example Flow

// Create menu with ClassicSkin
ContextMenu<ContextMenuProperties> menu = new ContextMenu<>(100, 100,                 ContextMenuProperties.createGenericSimplified().skin(new ModernSkin()));

// Add BooleanOption
menu.addOption(new BooleanOption(Text.of("Toggle"), () -> true, v -> System.out.println(v)));
// Render
menu.render(drawContext, 100, 100, mouseX, mouseY);

Available Skins

  • `ClassicSkin`: Renders a simple, compact menu without a separate screen, ideal for quick settings changes. This is the original context menu.

  • `ModernSkin`: Features a modern UI with tooltips, scrolling, and a separate screen, suitable for complex menus. Includes many satisfying, and smooth animations implemented via AnimationHelpers.

  • `MinecraftSkin`: Mimics Minecraft’s native GUI style with a separate screen and group navigation, best for immersive menus.

Choose ClassicSkin for lightweight menus, ModernSkin for feature-rich interfaces with modern UI designs, and MinecraftSkin for a native Minecraft look.

You have to use ContextMenuProperties.builder().skin(...) to define the skin for the context menu.

Visit the subpages to view images of each skin containing all in-built options

Common Mistakes to Avoid (Personal experience)

  • Not Updating Dimensions: Always set width and height for menus and options.

  • Ignoring Scissor Tests: Properly manage scissor regions to prevent rendering outside bounds.

  • Hardcoding Values: Use dynamic calculations for positions and sizes to support different resolutions.

  • Skipping Renderer Registration: Ensure all option types have registered renderers.

  • After minecraft 1.21.4, you might need to call drawContext.draw() to immediately draw existing buffers. So, if you have any problems with text not rendering as expected or any other un-expected rendering, trying calling the draw() function.

Last updated

Was this helpful?