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
renderContextMenuto draw the menu’s background, borders, and structure.Option Rendering Delegation: Uses a map of
SkinRendererinstances to render specific option types.Input Handling: Provides methods like
mouseClicked,mouseScrolled, etc., for user interactions.Group Support: Optionally implements
GroupableSkinfor 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 ifsupportsGroups()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
getRendererreturnsnull, ensureaddRendereris 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.customScaledProjectionandDrawHelper.scaledProjectionto manage scaling. And if you usecustomScaledProjectionthen make sure you divide the mouse coordinates with the scale factor
Always register renderers for all supported option types to avoid runtime exceptions.
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
renderto 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
initfor 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.setWidthandoption.setHeightcan 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);
Ensure render updates the option’s position and dimensions to avoid overlapping or misaligned options.
Interaction Between Skin, SkinRenderer, and Option
Initialization:
A
ContextMenuis created with aSkinviaContextMenuProperties.The
SkinregistersSkinRendererinstances for each supportedOptiontype.Options are added to the menu, and their renderers are set via
updateProperties.
Rendering:
The
Skin’srenderContextMenumethod draws the menu’s structure.For each
Option, theSkinretrieves the appropriateSkinRendererand calls itsrendermethod.The
SkinRendererupdates the option’s position and dimensions and draws its visual elements.SkinRendereralso should (whenever possible) render thedescription(a.k.a tooltips) of options.
Input Handling:
The
Skinhandles menu-level input (e.g., closing the menu).Option-specific input is delegated to the
SkinRenderer, which may call theOption’s input methods.The
Optionupdates its value viasetwhen 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
ClassicSkinfor lightweight menus,ModernSkinfor feature-rich interfaces with modern UI designs, andMinecraftSkinfor a native Minecraft look.
Common Mistakes to Avoid (Personal experience)
Not Updating Dimensions: Always set
widthandheightfor 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?