Modding:GUIs

From Vintage Story Wiki
Revision as of 16:45, 12 April 2019 by Tyron (talk | contribs) (→‎TBD)

Creating good graphical user interfaces is a challenging task - sometimes even the largest of companies mess it up.

In Vintage Story you currently need to formulate your GUIs in code, which requires acute spacial thinking. Hopefully someday we'll have a point&click GUI designer that takes off a lot of work. Until then, here are some of the essentials.

Starting out

You should encapsulate every GUI Dialog into its own class, albeit a dialog may consist of multiple windows - the vanilla character dialog for example has two - one for the players wearables and one for the player stats. There's a couple of base classes to make your live a bit easier.

Block Entity GUI

To create a GUI that's bound to a block entity, inherit from GuiDialogBlockEntity. In your block entity code you can then create and open that gui e.g. upon player interaction (example: Quern Block Entity, Quern Block Dialog)

HUD

To create a GUI element which is not interactable, inherit from HudElement.

General Purpose GUI

For any other uses, inerhit from the general purpose class GuiDialog, from which HudElement and GuiDialogBlockEntity also inherit from. You can override `ToggleKeyCombinationCode` to something like "yourAweseomeHotkeyCode" and use capi.Input.RegisterHotKey + capi.Input.SetHotKeyHandler to have your own keyboard key mapped to opening/closing your GUI (example: World Map)

GUI Basics

In general, you can pretty much build your own GUI system if you want to, by just overriding OnRenderGUI and render whatever you like. There's a multitude of overridable methods to handle mouse and keyboard inputs as well, see also the GuiDialog class on Github

If you want to use the vanilla GUI system, it's concept is merely a flat or hierarchical list of GUI elements placed at certain positions. The position is determined by an instance of ElementBounds. Let's have a closer look at it's properties:

ElementBounds

  • FixedX/FixedY: The absolute position where the element should be placed
  • FixedWidth/FixedHeight: The absolute width and height of the element
  • FixedPaddingX/FixedPaddingY: The absolute inner padding of the element
  • FixedMarginX/FixedMarginY: The absolute outer padding of the element
  • ParentBounds: The parent boundaries, in which this element resides in
  • ChildBounds: The child boundaries this element contains
  • Alignment: The alignment of the element. If set to None the FixedX/Y Position is used. For any other value the FixedX/Y values are ignored. For example when you used RightTop the element will always be at the right top most corner of its parent bounds. If you use RightFixed the element will be right aligned, but its Y-Position is determined by FixedY

GuiComposer

This is the component that builds and manages your GUI elements for you. You can create a composer via the client api: capi.Gui.CreateCompo(dialogName, bounds). You have to supply it with a unique identifier and the overall dialog bounds. Once you have a GUIComposer instance you can chain-add elements. Here's a small example:

// Auto-sized dialog at the center of the screen
ElementBounds dialogBounds = ElementStdBounds.AutosizedMainDialog.WithAlignment(EnumDialogArea.CenterMiddle);

// Just a simple 300x300 pixel box
ElementBounds textBounds = ElementBounds.Fixed(0,0,300,300)

SingleComposer = capi.Gui.CreateCompo("myAwesomeDialog", dialogBounds) 
  .AddStaticText("This is a piece of text at the center of your screen - Enjoy!", CairoFont.WhiteDetailText(), textBounds)
  .Compose()
;

Some explanations:

  • ElementStdBounds: Contains a bunch of often used element bounds configurations. See also [[1]] on github.
  • ElementBounds.Fixed(0, 0, 300, 300): Create a new bounds instance with fixedX/Y at 0/0 and a fixed widt/height of 300/300 pixels.
  • CairoFont.WhiteDetailText(): Create a new font configuration based on a often used size and color, in this case white with font size 14
  • SingleComposer: This property is defined in the GuiDialog class. Its a getter/setter to the Composers dictionary. It contains all the "windows" of this dialog which are then rendered / handled by this GuiDialog instance

Examples

A simple standard dialog, triggered by the keyboard key 'U'

public class GuiDialogAnnoyingText : GuiDialog
{
    public override string ToggleKeyCombinationCode => "annoyingtextgui";

    public GuiDialogAnnoyingText(ICoreClientAPI capi) : base(capi)
    {
        SetupDialog();
    }

    private void SetupDialog()
    {
        // Auto-sized dialog at the center of the screen
        ElementBounds dialogBounds = ElementStdBounds.AutosizedMainDialog.WithAlignment(EnumDialogArea.CenterMiddle);

        // Just a simple 300x300 pixel box
        ElementBounds textBounds = ElementBounds.Fixed(0, 40, 300, 100);

        // Background boundaries. Again, just make it fit it's child elements, then add the text as a child element
        ElementBounds bgBounds = ElementBounds.Fill.WithFixedPadding(GuiStyle.ElementToDialogPadding);
        bgBounds.BothSizing = ElementSizing.FitToChildren;
        bgBounds.WithChildren(textBounds);

        // Lastly, create the dialog
        SingleComposer = capi.Gui.CreateCompo("myAwesomeDialog", dialogBounds)
            .AddShadedDialogBG(bgBounds)
            .AddDialogTitleBar("Heck yeah!", OnTitleBarCloseClicked)
            .AddStaticText("This is a piece of text at the center of your screen - Enjoy!", CairoFont.WhiteDetailText(), textBounds)
            .Compose()
        ;
    }

    private void OnTitleBarCloseClicked()
    {
        TryClose();
    }
}

public class AnnoyingTextSystem : ModSystem
{
    ICoreClientAPI capi;
    GuiDialog dialog;

    public override bool ShouldLoad(EnumAppSide forSide)
    {
        return forSide == EnumAppSide.Client;
    }
        
    public override void StartClientSide(ICoreClientAPI api)
    {
        base.StartClientSide(api);

        dialog = new GuiDialogAnnoyingText(api);

        capi = api;
        capi.Input.RegisterHotKey("annoyingtextgui", "Annoys you with annoyingly centered text", GlKeys.U, HotkeyType.GUIOrOtherControls);
        capi.Input.SetHotKeyHandler("annoyingtextgui", ToggleGui);
    }

    private bool ToggleGui(KeyCombination comb)
    {
        if (dialog.IsOpened()) dialog.TryClose();
        else dialog.TryOpen();

        return true;
    }
}


TBD

More to come here in the future.