Custom Mod UI
Overriding the Method
Sometimes mod actions and settings aren’t enough, because you need more control over the UI.
To make the UI yourself you can override the BuildPanel method and return an empty Container:
public class MyMod : BaseMod
{
public override string Name => "My Mod";
public override string Description => "My new test mod";
public override ModsWindow ModsWindow => Plugin.MyModsWindow;
public override Container BuildPanel(string id)
{
return new Container(id);
}
} Creating a Simple Field
Let’s say you have a field that you want to make custom UI for:
public static float MySetting = 1f; Optionally we can turn this into a mod setting to get macro support:
[ModSetting(ShowInUI = false)]
public static float MySetting = 1f; Then we can create a DragFloat in the BuildPanel method:
public override Container BuildPanel(string id)
{
return new Container(id,
new DragFloat("My Setting")
);
} Linking an Element to a Field
This will appear in the UI but do nothing, to make it actually link to the field, we can turn the field into a Ref<float>:
[ModSetting(ShowInUI = false)]
public static Ref<float> MySetting = new(1f); and then link it to the UI element:
public override Container BuildPanel(string id)
{
return new Container(id,
new DragFloat("My Setting").WithValue(MySetting)
);
} you can alternatively use the onValueChanged parameter:
public override Container BuildPanel(string id)
{
return new Container(id,
new DragFloat("My Setting", onValueChanged: num => MySetting = num)
);
} but this will not update the DragFloat’s value, which is why Refs are recommended.
Setting the field like this also skips the mod registry, so anything listening for that setting to change won’t hear about it.
Making the Element Change Something
To now make this setting do something, we can use the ref to set the value of the input and use the onValueChanged event to do something with the updated value.
public class MyMod : BaseMod
{
public override string Name => "My Mod";
public override string Description => "My new test mod";
public override ModsWindow ModsWindow => Plugin.MyModsWindow;
[ModSetting(ShowInUI = false)]
public static Ref<float> MySetting = new(1f);
public override Container BuildPanel(string id)
{
return new Container(id,
new DragFloat("My Setting", onValueChanged: num => Player.Speed = num).WithValue(MySetting)
);
}
public override void RefreshUI()
{
MySetting.Value = Player.Speed;
}
} Alternatively you could hook into the Ref<T>.Changed event to apply the new value.
You can do this in OnStaticInit for a static field, or in Awake for instance fields
(override Awake instead of writing a constructor, that way failures get reported properly):
public class MyMod : BaseMod
{
public override string Name => "My Mod";
public override string Description => "My new test mod";
public override ModsWindow ModsWindow => Plugin.MyModsWindow;
[ModSetting(ShowInUI = false)]
public static Ref<float> MySetting = new(1f);
protected override void OnStaticInit()
{
MySetting.Changed += num => Player.Speed = num;
}
public override Container BuildPanel(string id)
{
return new Container(id,
new DragFloat("My Setting").WithValue(MySetting)
);
}
public override void RefreshUI()
{
MySetting.Value = Player.Speed;
}
} OnStaticInit gets called when the first mod instance gets created.
If we now want to add an apply button we can do it like this:
public class MyMod : BaseMod
{
public override string Name => "My Mod";
public override string Description => "My new test mod";
public override ModsWindow ModsWindow => Plugin.MyModsWindow;
[ModSetting(ShowInUI = false)]
public static Ref<float> MySetting = new(1f);
public override Container BuildPanel(string id)
{
return new Container(id,
new DragFloat("My Setting").WithValue(MySetting),
new Button("Apply", () => Player.Speed = MySetting.Value).WithContentWidth()
);
}
public override void RefreshUI()
{
MySetting.Value = Player.Speed;
}
} This works by moving the onValueChanged callback to the a button’s onPressed callback.
The WithContentWidth is optional and only added to make the button the same width as the DragFloat.
Using both Auto UI and Custom UI
If you still want to have the automatically built UI and just append your custom UI at the end or start, you can add it like this:
public override Container BuildPanel(string id)
{
return new Container(id,
base.BuildPanel(id), // Build the automatic UI and add it to your Container
new DragFloat("My Setting").WithValue(MySetting),
);
} This can technically also be expressed like this:
public override Container BuildPanel(string id)
{
return new Container(id,
AutoUIBuilder.Build(
this, // The mod
id // Unique Id for the UI
),
new DragFloat("My Setting").WithValue(MySetting),
);
} Macro Context Menu
ModActions and ModSettings automatically have a context menu when right clicked to quickly add them to a macro.
You can also manually add this to your UI when building a custom UI for it:
public class MyMod : BaseMod
{
public override string Name => "My Mod";
public override string Description => "My new test mod";
public override ModsWindow ModsWindow => Plugin.MyModsWindow;
[ModSetting(ShowInUI = false)]
public static Ref<float> MySetting = new(1f);
[ModAction(ShowInUI = false)]
public static void MyAction()
{
Plugin.Logger.LogInfo("Action Triggered");
}
public override Container BuildPanel(string id)
{
return new Container(id,
SettingMenu( // Method on BaseMod that builds the context menu
new DragFloat("My Setting", onValueChanged: num => Player.Speed = num).WithValue(MySetting), // UI element to add it to
nameof(MySetting) // Name of the setting field
),
ActionMenu(new Button("My Action", MyAction), nameof(MyAction))
);
}
public override void RefreshUI()
{
MySetting.Value = Player.Speed;
}
}