Skip to content

en Basic UI

HoCha113 edited this page Mar 29, 2026 · 10 revisions

中文版本

What is UIHandle?

UIHandle is the base class for InnoVault's general-purpose UI system. Inheriting from UIHandle allows your code to be automatically managed by the UI system, handling both logical updates and drawing updates.

Compared to tModLoader's UIState/UIElement system, UIHandle is more lightweight and flexible:

  • Auto-registration: Just inherit — no need to manually register to interface layers
  • Multi-layer rendering: Supports 10 render layers, automatically inserted at different positions in the vanilla UI pipeline
  • Dual-thread updates: Provides draw-thread Update() and logic-thread LogicUpdate()
  • Built-in key state: Automatically tracks mouse left/right button Pressed/Held/Released states
  • Persistent storage: Supports SaveUIData/LoadUIData for persisting UI state
  • Render priority: Controls draw order of same-layer UIs via RenderPriority
  • Global hooks: Modify all UI element behavior via GlobalUIHandle

Key Properties

Property/Field Type Default Description
Active bool false Whether UI is active, must override to implement activation logic
Texture Texture2D placeholder UI main texture, overridable
LayersMode LayersModeEnum Vanilla_Mouse_Text UI render layer (assigned once at load)
RenderPriority float 1 Same-layer render sort value, higher = on top
DrawPosition Vector2 Draw position (top-left of rectangle)
Size Vector2 UI rectangle size
UIHitBox Rectangle UI collision rectangle
MousePosition Vector2 Mouse screen position
MouseHitBox Rectangle Mouse collision rectangle (1x1 pixel)
hoverInMainPage bool Reserved hover detection field
keyLeftPressState KeyPressState Left button state (auto-follows current update thread)
keyRightPressState KeyPressState Right button state (auto-follows current update thread)
player Player Local player (Main.LocalPlayer shortcut)
ID int Internal ID (read-only)
Mod Mod Owning mod

Render Layers (LayersModeEnum)

Layer Description
Vanilla_Mouse_Text Mouse text layer (default), topmost
Vanilla_Interface_Logic_1 Interface logic layer
Vanilla_MP_Player_Names Multiplayer player names layer
Vanilla_Hide_UI_Toggle UI hide toggle layer
Vanilla_Resource_Bars Resource bars layer (HP/MP bars)
Vanilla_Ingame_Options In-game options layer
Vanilla_Diagnose_Net Network diagnostics layer
Mod_MenuLoad Mod menu layer (available in main menu)
Mod_UIModItem Mod icon interface layer
None No auto-update, requires manual management

Note: LayersMode is only assigned once at load time. Changing it at runtime will have no effect.


Key Press State (KeyPressState)

State Description
None Not pressed
Pressed Just pressed (single frame)
Held Held down continuously
Released Just released (single frame)

Key state automatically switches based on the current update thread (draw thread reads draw state, logic thread reads logic state).

When LayersMode is None, global key states are not updated. In that case, use instance methods CheckLeftKeyState() / CheckRightKeyState() to get real-time state (can only be called once per update cycle).


Quick Example

Below is a usage example from InnoVaultExample

internal class ExampleUI : UIHandle
{
    // Render layer defaults to Vanilla_Mouse_Text, its return value determines
    // which update collection the UI will run in. Don't change this at runtime.
    public override LayersModeEnum LayersMode => base.LayersMode;

    public override Texture2D Texture => Mod.Assets.Request<Texture2D>("Asset/ExampleUIPanel").Value;

    internal bool _active;
    internal float _sengs;

    // Active determines whether the UI will be updated and drawn
    public override bool Active => _active || _sengs > 0;

    public override void Update() {
        // A unified draw origin is important
        DrawPosition = new Vector2(Main.screenWidth / 2, Main.screenHeight / 2) 
            + new Vector2(0, -600 + 600 * _sengs);
        if (_active) {
            if (_sengs < 1) _sengs += 0.02f;
        }
        else {
            if (_sengs > 0) _sengs -= 0.02f;
        }
    }

    public override void Draw(SpriteBatch spriteBatch) {
        spriteBatch.Draw(Texture, DrawPosition, null, Color.White * _sengs
            , 0, Vector2.Zero, 2 * _sengs, SpriteEffects.None, 0);
    }
}

This example is best viewed alongside ExampleButtonUI — together they demonstrate how different UI elements should interact with each other.


Lifecycle Callbacks

Method When Called Description
Update() Every frame (draw thread) Called before Draw(), handles UI state updates
LogicUpdate() Every frame (logic thread) Game main loop logic update, not called in main menu
MenuLogicUpdate() Main menu at fixed 60 ticks Main menu logic update, frame-rate independent
Draw(SpriteBatch) Every frame (draw thread) Render UI
OnEnterWorld() When entering world UI initialization
UnLoad() When game unloads Resource cleanup
SaveUIData(TagCompound) On save Saved using FullName as key
LoadUIData(TagCompound) On load Not called if no saved data exists

About Dual-Thread Updates

UIHandle provides two independent update hooks:

  • Update(): Runs on the draw thread, frame-rate dependent. Suitable for animations, position interpolation, and other visual logic
  • LogicUpdate(): Runs on the game logic thread (60 ticks/sec), frame-rate independent. Suitable for data logic

Use the static property UIHandle.IsLogicUpdate to determine which thread you're currently on.


Global Key Events

UIHandleLoader provides global key events that can be subscribed to from anywhere:

UIHandleLoader.LeftPressedEvent += () => { /* Left button pressed */ };
UIHandleLoader.LeftHeldEvent += () => { /* Left button held */ };
UIHandleLoader.LeftReleasedEvent += () => { /* Left button released */ };
UIHandleLoader.RightPressedEvent += () => { /* Right button pressed */ };
UIHandleLoader.RightHeldEvent += () => { /* Right button held */ };
UIHandleLoader.RightReleasedEvent += () => { /* Right button released */ };

GlobalUIHandle (Global UI Hooks)

Inherit GlobalUIHandle to modify the behavior of all UI elements:

public class MyGlobalUI : GlobalUIHandle
{
    // When key states update
    public override void UpdateKeyState() { }
    
    // Before a UI element updates, return false to prevent that element from updating
    public override bool PreUIHanderElementUpdate(UIHandle handle) => true;
    
    // After a UI element updates
    public override void PostUIHanderElementUpdate(UIHandle handle) { }
    
    // After all UI updates complete
    public override void PostUpdataInUIEverything() { }
}

UIHandleLoader Common Methods

Method Description
UIHandleLoader.GetUIHandleInstance<T>() Get UI instance of specified type
UIHandleLoader.GetUIHandleInstance(string name) Get UI instance by name
UIHandleLoader.GetUIHandleOfType<T>() Return type-safe UI instance
UIHandleLoader.GetUIHandleID<T>() Get UI's internal ID
UIHandleLoader.UIHandles List of all registered UI instances

Finding UI Instances via VaultContent

UIHandle ui = Mod.FindUI<ExampleUI>();
UIHandle ui2 = Mod.FindUI("ExampleUI");

Practical Tips

UI Interaction

For interaction between different UI elements, it's recommended to get references via UIHandleLoader.GetUIHandleOfType<T>():

internal class ExampleButtonUI : UIHandle
{
    public override void Update() {
        if (keyLeftPressState == KeyPressState.Pressed && hoverInMainPage) {
            // Get another UI instance by type and modify its state
            var mainUI = UIHandleLoader.GetUIHandleOfType<ExampleUI>();
            mainUI._active = !mainUI._active;
        }
    }
}

About DrawPosition

It's recommended to consistently use DrawPosition as your draw origin — this makes it easy to implement overall UI movement, animations, and similar effects.

Clone this wiki locally