Modding:GUIs: Difference between revisions
Line 16: | Line 16: | ||
===General Purpose GUI=== | ===General Purpose GUI=== | ||
For any other uses, inerhit from the general purpose class <code>GuiDialog</code>, from which HudElement and GuiDialogBlockEntity also inherit from. You can override | For any other uses, inerhit from the general purpose class <code>GuiDialog</code>, from which HudElement and GuiDialogBlockEntity also inherit from. You can override <code>ToggleKeyCombinationCode</code> to something like "yourAweseomeHotkeyCode" and use <code>capi.Input.RegisterHotKey</code> + <code>capi.Input.SetHotKeyHandler</code> to have your own keyboard key mapped to opening/closing your GUI (example: [https://github.com/anegostudios/vsessentialsmod/blob/master/Systems/WorldMap/WorldMapManager.cs#L105 World Map]) | ||
== GUI Basics == | == GUI Basics == |
Revision as of 17:14, 12 April 2019
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 placedFixedWidth
/FixedHeight
: The absolute width and height of the elementFixedPaddingX
/FixedPaddingY
: The absolute inner padding of the elementFixedMarginX
/FixedMarginY
: The absolute outer padding of the elementParentBounds
: The parent boundaries, in which this element resides inChildBounds
: The child boundaries this element containsAlignment
: The alignment of the element. If set toNone
the FixedX/Y Position is used. For any other value the FixedX/Y values are ignored. For example when you usedRightTop
the element will always be at the right top most corner of its parent bounds. If you useRightFixed
the element will be right aligned, but its Y-Position is determined byFixedY
HorizontalSizing
/VerticalSizing
: The sizing method to be used, can be eitherFixed
(default),Percentual
orFitToChildren
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:
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, 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 14SingleComposer
: This property is defined in theGuiDialog
class. Its a getter/setter to theComposers
dictionary. It contains all the "windows" of this dialog which are then rendered / handled by this GuiDialog instance
Fancying it up
This is of course the absolute minimum example that will only show some text. Let's add a title bar and a dialog background:
private void SetupDialog()
{
// Auto-sized dialog at the center of the screen
ElementBounds dialogBounds = ElementStdBounds.AutosizedMainDialog.WithAlignment(EnumDialogArea.CenterMiddle);
// Just a simple 300x100 pixel box with 40 pixels top spacing for the title bar
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);
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();
}
This covers some of the most basic parts. There is a grand amount of various pre-built UI elements that each come with their own argument list that are in dire need of Documentation. Here's an overview of some of the more commonly used ones
Dialog / Graphics
.AddShadedDialogBG
: Draws a pretty background and dialog border.AddDialogTitleBar
: Draws a title bar with a close button and a button to move around the dialog.AddInset
: Adds a darkened section with a inset border around it
Text
.AddStaticText
: Add a static snippet of text.AddDynamicText
: Add a static snippet of text that can be set to other texts without the need to redraw the whole dialog.AddRichtext
: Same as.AddDynamicText
but allows use of VTML - a minimalist version of HTML code.AddHoverText
: When the mouse cursor moves over the element boundaries, will show supplied text as a tooltip
UI Control/Input
.AddButton
: Adds a clickable button.AddDropDown
: Adds a drop down element.AddHorizontalTabs
: Adds horizontally aligned tabs, like the ingame chat window has.AddVerticalScrollbar
: Adds a vertical scrollbar.AddTextInput
: Adds an single line editable text field.AddNumberInput
: Adds an editable text field built for entering numbers.AddTextArea
: Adds multiple line editable text field
Other
.AddIf
/.EndIf
: Can be used to conditionally add certain elements (but you can also just split up your creation code for more fine grained control).BeginClip
/.EndClip
: Used in combination with a scrollbar to cut away oversized content, such as in the creative inventory.AddStaticCustomDraw
: Lets you define your own drawing code to be added to the GUI
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.