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 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
getRenderer
returnsnull
, ensureaddRenderer
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
andDrawHelper.scaledProjection
to manage scaling. And if you usecustomScaledProjection
then 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
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
andoption.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);
Ensure render
updates the option’s position and dimensions to avoid overlapping or misaligned options.
Interaction Between Skin, SkinRenderer, and Option
Initialization:
A
ContextMenu
is created with aSkin
viaContextMenuProperties
.The
Skin
registersSkinRenderer
instances for each supportedOption
type.Options are added to the menu, and their renderers are set via
updateProperties
.
Rendering:
The
Skin
’srenderContextMenu
method draws the menu’s structure.For each
Option
, theSkin
retrieves the appropriateSkinRenderer
and calls itsrender
method.The
SkinRenderer
updates the option’s position and dimensions and draws its visual elements.SkinRenderer
also should (whenever possible) render thedescription
(a.k.a tooltips) of options.
Input Handling:
The
Skin
handles menu-level input (e.g., closing the menu).Option-specific input is delegated to the
SkinRenderer
, which may call theOption
’s input methods.The
Option
updates its value viaset
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, andMinecraftSkin
for a native Minecraft look.
Common Mistakes to Avoid (Personal experience)
Not Updating Dimensions: Always set
width
andheight
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?