Custom Controllers (and Components) - LiruJ/GuiCookie GitHub Wiki
Controllers allow logic for specific elements to be encapsulated into separate classes, rather than all through the root. This is incredibly useful when making games as it means the layout of the UI can reflect the layout of the game's data, allowing a better flow of data and less time spent maintaining the UI.
This guide picks up from where the custom templates guide left off, and assumes you have completed all prior guides.
Create a new folder in the project (NOT in the content folder), in this example it will be called "Controllers". Add a new class to this folder, in this example it will be called "CounterPanel". Ensure this class is within the Controllers namespace.
The controller class should inherit the GuiCookie Element class, which gives it access to a variety of functions and properties, as well as allowing it to exist as an element in a layout.
Cut the four variables defined in the root, and paste them into the CounterPanel class. They are not needed in the root anymore, so make sure to delete them.
From TestRoot.cs
private int counter;
private TextBox display;
private Button subtractButton;
private Button addButton;Into CounterPanel.cs
public class CounterPanel : Element
{
private int counter;
private TextBox display;
private Button subtractButton;
private Button addButton;
}Next; override the OnFullSetup function, this is called when the element is created and is used somewhat like a constructor. The Element class has multiple functions for accessing the element's children, here the GetChildByName function will be used as a replacement for the GetElementByTag function used in the Root. Cut the contents from the TestRoot's PostInitialise function and paste them into the OnFullSetup function of the CounterPanel class and replace the "GetElementFromTag" calls to "GetChildByName" calls like previously mentioned.
From TestRoot.cs
protected override void PostInitialise()
{
display = GetElementFromTag<TextBox>("Display");
subtractButton = GetElementFromTag<Button>("SubtractButton");
addButton = GetElementFromTag<Button>("AddButton");
display.Text = $"Value: {0}";
subtractButton.ConnectLeftClick(() => { counter--; display.Text = $"Value: {counter}"; });
addButton.ConnectLeftClick(() => { counter++; display.Text = $"Value: {counter}"; });
}Into CounterPanel.cs
public override void OnFullSetup()
{
display = GetChildByName<TextBox>("Display");
subtractButton = GetChildByName<Button>("SubtractButton");
addButton = GetChildByName<Button>("AddButton");
display.Text = $"Value: {0}";
subtractButton.ConnectLeftClick(() => { counter--; display.Text = $"Value: {counter}"; });
addButton.ConnectLeftClick(() => { counter++; display.Text = $"Value: {counter}"; });
}Now the TestRoot class should be empty.
Open the Layout.xml file and delete the children of the CounterPanel node, as elements are now being referred to by name rather than tag. This means that the CounterPanel element can be a single line in the layout sheet.
<CounterPanel/>Open the Templates.xml file and add the "Controller" attribute to the CounterPanel template node, the value is the name of the controller class, in this case "CounterPanel".
<CounterPanel Controller="CounterPanel" Position="50%" Pivot="50%" Size="128, 64">This tells GuiCookie that any CounterPanel element must have an attached CounterPanel controller. Note that this "Controller" attribute can be placed on any element node, even in the layout. Doing so overrides the controller defined in the template, which can be useful for controllers that do not require a specific set of children or components.
The final step before the controller can be used is to tell GuiCookie where the custom controller classes are. The UIManager class has a function that does exactly this.
Open the Game1 class (or whatever you have renamed it to) and find the LoadContent function where you created the UIManager previously. Add a call to the RegisterElementNamespace function, using the executing assembly and the name of the namespace. For example; if your project's name is "MyGame", then the element's namespace would be "MyGame.Controllers".
uiManager.RegisterElementNamespace(Assembly.GetExecutingAssembly(), "MyGame.Controllers");Now if you run the project, you should see the exact same thing as before, which is a good thing. But using a controller rather than the root opens up many possibilities.
In the Layout.xml file, add another CounterPanel node. Add a "Position" attribute with something like "60%, 50%", just so it is out of the way of the first one. If it is still in the way, then change the position and try again.
<CounterPanel/>
<CounterPanel Position="60%, 50%"/>Now when you run the project, you should see two counter panels, and clicking on the buttons should only change the counter for that specific panel.
Now that you know how to make custom controllers, you should know that custom components are almost exactly the same. The UIManager class has a function called RegisterComponentNamespace which works the same way, and the "Components" tag can be added to any element node (even in the layout sheet) to add components to elements.
<MyElement Controller="MyElementController" Components="Frame, MyComponent, MouseHandler"/>Custom components are more useful for behaviours that are small in nature and need to be reused a lot across many elements, whereas controllers are better at co-ordinating multiple child elements and components to fulfil a specific purpose.
Templates and controllers (and components) build the backbone of GuiCookie and being comfortable with them will allow you to make almost any element you could imagine. The next guide will show how you can create elements while the game is running, which is incredibly useful for almost any game.