An Introduction to Layouts in Quorum

What is a Layout?

In this tutorial, we will learn how to use Layouts in the Quorum Game Engine. Layouts are interface tools which tell a container where and how items in the game are shown on the screen.

There are two main layouts that you can use:

What are LayoutProperties?

LayoutProperties are interface tools which allow you to preset how an Item2D, such as a Button or Icon, appears in the game. Controls like Buttons, Menus, and Icons have their own set of Default Properties that tells the game how they should be laid out. To alter how a control is laid out, you can either get the Control's current LayoutProperties and alter them or add your own LayoutProperties object for the Control to use instead. In this tutorial, we will show how to use both methods.

Layout Modes

LayoutProperties also have several Layout Modes that tell the Control how it should calculate its dimensions either horizontally or vertically. There are five modes as follows:

Laying out a Simple UI

In this tutorial, we will make a simple UI with two Buttons and an Icon. We will adjust the LayoutProperties of the three UI elements so that when the screen is resized, the elements will move or resize to adjust for the screen size change. We also make the Buttons change the properties of the Icon so the Icon changes position when the Buttons are clicked. To start, make a New Game Application.

Setting up the Window

For this tutorial, we will use the DesktopConfiguration, ManualLayout, LayoutProperties, Button, Icon and Color Libraries.

Before we create and adjust the Buttons, we will go to our main class, specifically the main action. Just before the StartGame() call, we'll use a DesktopConfiguration object to make our window resizable. This is so we can resize the window while the game is running, allowing us to see our Buttons and Icon change size and position in realtime based on the Screen size.

For a LayoutProperties ojbect to have an effect, the container for the UI elements needs to have a Layout set. In our case, the container will be the game itself, so we simply need to make a ManualLayout object and set the game's layout to be that object inside of the CreateGame action. Before we start adding new UI elements, our main class should look like this:

use Libraries.Game.Game
use Libraries.Game.DesktopConfiguration
use Libraries.Interface.Layouts.ManualLayout
use Libraries.Interface.Layouts.LayoutProperties
use Libraries.Interface.Controls.Button
use Libraries.Interface.Controls.Icon
use Libraries.Game.Graphics.Color

class Main is Game
    action Main
        DesktopConfiguration config = GetDesktopConfiguration()
        config:resizable = true
        StartGame()
    end

    action CreateGame
        ManualLayout man
        SetLayout(man)
    end

    action Update(number seconds)
    end
end

Making the UI Elements

Buttons are one of the most common elements that will appear in a user interface. In Quorum, Buttons have their own Layout and LayoutProperties that they use to position and display their contents. To change the properties of the Button, there are two primary methods, which also apply to all other controls. We can either call the Buttons actions like SetPercentageX() to change its default properties, or we make a LayoutProperties object and set it to the Button's DefaultProperties and change it from our object. The outcomes of both methods are the same, the only difference being that the Button does not have an action to change every aspect of the property, so there some properties you can only change through a LayoutProperties object.

Changing Properties with Public Actions

We will now make our first Button, and we will change its LayoutProperties using public actions. Note that from here on we will be adding code to the CreateGame action in our main class. First, we need to declare a Button and set its name to "Top Button." By default, a Button calculates its width using the FIT_CONTENTS mode and its height with the FIT_FONT mode. In this case we want to define the Size ourselves, so we will change both of these modes to STANDARD. These constants are not defined inside of the Button class, so we need to make a LayoutProperties object and access the constants from there. For this Button, we will change its percentage x and y, as well as its size in pixels. This is done with the following lines of code:

Button top
LayoutProperties prop

top:SetName("Top Button")
top:SetHorizontalLayoutMode(prop:STANDARD)
top:SetVerticalLayoutMode(prop:STANDARD)
top:SetPercentageX(0.35)
top:SetPercentageY(0.6)
top:SetSize(250, 175)

Note the difference between Percentage and Pixel when using Layouts. For example, if a Control has a percentage width of 0.5 then the width of the Control will always be 50% of the width of its container. On the contrary, if you used SetPixelWidth or SetWidth and set the width to 300, then the Control will always be 300 pixels wide, regardless of the width of its container.

Using a LayoutProperites Object

Now we will make the second Button, and for this one we will use a LayoutProperties object to change the default properties of the Button. To get the default properties of the Button, we need to set our new object to the Button's properties using the GetDefaultLayoutProperties action. Then we use actions from the LayoutProperties object to change the Button. This is done with the following lines of code:

Button bottom

bottom:SetName("Bottom Button")
prop = bottom:GetDefaultLayoutProperties()
prop:SetHorizontalLayoutMode(prop:STANDARD)
prop:SetVerticalLayoutMode(prop:STANDARD)
prop:SetPercentageX(0.5)
prop:SetPercentageOriginX(0.5)
prop:SetPercentageY(0.1)
prop:SetPercentageWidth(0.3)
prop:SetPercentageHeight(0.3)

Note in the above code that we used the LayoutProperties object from when we made the Top Button and that we used a new action. The SetPercentageOriginX changes the origin from where a Control calculates its position. This is a property that Buttons do not have a public action to change, so you would need to use this method to change that property.

AddLayoutProperties

That last Control we will add is an Icon and we will change its LayoutProperties differently from how we did the Buttons. Controls have multiple LayoutProperties and they will use them either based on the size of their container or which one was used most recently. For the Icon, we will make a LayoutProperties object, change its properties, and then add it to the Icon using the AddLayoutProperties action. We will also give the Icon a filled rectangle for its graphic. This is done in the following lines of code:

Icon selectBox
Color color
LayoutProperties boxProp

selectBox:LoadFilledRectangle(10, 10, color:Blue())
boxProp:SetPercentageX(0.25)
boxProp:SetPercentageY(0.55)
boxProp:SetPercentageWidth(0.5)
boxProp:SetPercentageHeight(0.4)

selectBox:AddLayoutProperties(boxProp)

Now we'll add all three of the UI elements to the game so they appear when we run it. Note that we will add the Icon first, because we want it to be drawn under the Buttons.

Add(selectBox)
Add(top)
Add(bottom)

When you now run the game, there will be two Buttons and an Icon under one of them. But, if you were to resize the window, then the Bottom Button and the Icon will get larger or smaller depending on the size of the window. All three items will stay in the same relative position because we set their position to be based on a percentage. The Top Button will also stay the same size no matter the size of the window because we gave its dimensions in pixels.

Changing Properties with a Behavior

Now we have the Controls sized and positioned, but a good UI will respond when you interact with it, so we will make a Behavior for our two Buttons that will move the Icon depending on the position of the Button that pressed it.

We will have the Buttons take an Icon, which we will pass from our main class, get its current layout properties so it can change its y position, and then simply say and output that the Button was clicked. Previously we used the GetDefaultLayoutProperties, but since the object might not be using its default properties, we will use the GetCurrentLayoutProperties action to return the properties that the previous LayoutProperties was using to lay itself out.

The Behavior will be its own Quorum class known as ButtonBehavior and is as follows:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Sound.Speech
use Libraries.Interface.Controls.Icon
use Libraries.Interface.Controls.Button
use Libraries.Interface.Layouts.LayoutProperties

class ButtonBehavior is Behavior
    Icon shape
    LayoutProperties prop

    action setShape(Icon icon)
        shape = icon
    end

    action Run(BehaviorEvent event)
        Button item = cast(Button, event:GetItem())
        number pos = item:GetPercentageY()
        prop = shape:GetCurrentLayoutProperties()
        prop:SetPercentageY(pos - 0.05)
        Speech speech
        speech:Say(item:GetName() + " clicked", false)
        output item:GetName() + " clicked"
    end
end

Now, with the Behavior complete, we can go back to our main class and set the shape for the Behavior and set the Behavior for the Buttons. This is done with the following lines of code:

ButtonBehavior behavior
behavior:setShape(selectBox)
top:SetBehavior(behavior)
bottom:SetBehavior(behavior)

Now, when you run the program, the position and size of the items will still change with when the game is resized, but now clicking a Button will update the Layout of the Icon and change its y position.

FlowLayout

In the above example, we used a ManualLayout to lay out the items in the UI, but the other main Layout you can use is the FlowLayout. As described previously, the Flow Layout allows for items to determine their own size, but the container and the layout determines their positions. The Flow Layout will force the children of the container to position themselves as far to the left as they can without overlapping other children, and if the width cannot contain them, then the items will try to form a new line under the full line and will start flowing to the right from there.

To demonstrate how a FlowLayout works, we will go back to our previous example and change the ManualLayout to a FlowLayout, which also means we need to include the FlowLayout library using the following line of code:

use Libraries.Interface.Layouts.FlowLayout

We will first set a FlowLayout to the game window by adding these next two lines of code to the top of the CreateGame action:

FlowLayout flow
SetLayout(flow)

Now, we will change how the Top Button calculates its width to demonstrate how the FlowLayout repositions items. We will simply delete the SetSize call and give the Top Button the same percentage size as the Bottom Button. This is done with the following lines of code:

top:SetPercentageHeight(0.3)
top:SetPercentageWidth(0.3)

If you were to add up the percentage widths of the three items, you would notice that they add up to 1.1, which is greater than the width of the screen (1.0 means the entire width of the container). In this situation, the FlowLayout will move the item that will not fit in the line down to start its own line. In our example, that means that Bottom Button will be forced to its own line under the Icon.

Making a Container

In our example's current state, you'll notice that our action calls to change the position no longer work because of the FlowLayout. But, what if we want a ManualLayout for other UI elements? We can easily solve this by creating a container object to hold those items, and internally the container will have a FlowLayout but will exist in a ManualLayout. We can accomplish this with the following lines of code:

ManualLayout manual
SetLayout(manual)

Control container
FlowLayout containFlow
container:SetLayout(containFlow)
container:SetPercentageWidth(1.0)
container:SetPercentageHeight(0.4)
container:SetPosition(50, 300)

container:Add(selectBox)
container:Add(top)
container:Add(bottom)
Add(container)

The above code will replace the add actions we had at the bottom of the CreateGame action.

Now, if you run the program, you will see that the Buttons and Icon are smaller since they are now part of a smaller container but they are in a FlowLayout while everything else added to the game is in a ManualLayout. This can be especially useful when adding things to your game like Menus, which are covered in a later tutorial, because they require a FlowLayout.

Next Tutorial

In the next tutorial, we will discuss Checkboxes, which describes how to use Checkboxes.