An Introduction to Layouts in Quorum

In this tutorial, we will learn how to use Layouts in the Quorum Game Engine. Layouts are an interface tool which allow you to preset how an Item2D, such as a Drawable, appears in the game. For example, Layouts allow for setting a Item2D's size relative to the screen size by using a percentage instead of a fixed number of pixels. To simplify this tutorial, we will solely be using Drawables as our Item2Ds.

For this tutorial, we will demonstrate how Layouts can be used to change the size of a Drawable to a percentage of the screen size, change where on the screen to place a Drawable, change where the origin point of the Drawable is, add a fixed number of pixels offset to a percentage of the game screen, set maximum width and height of a Drawable, and change the Layout based on a maximum game screen width and height. We will also demonstrate how a FlowLayout is used, which can expand in size to meet the size of Drawables added to it. To start, create a new Game Application project.

Basic Layouts

In order to use Layouts, we need to include the Layout library. For this program, we will also need the libraries for Drawable, Color, and DesktopConfiguration, requiring the following statements:
use Libraries.Game.Game
use Libraries.Interface.Layouts.Layout
use Libraries.Game.Graphics.Drawable
use Libraries.Game.Graphics.Color
use Libraries.Game.DesktopConfiguration

Before we worry about how changing the game window size will affect Drawables in our program, we need to make the game window resizable. This needs to be done before calling the StartGame action in the Main action. First we will obtain the Desktop Configuration using the GetDesktopConfiguration action, and then we will set the DesktopConfiguration class's resizeable variable to true. This gives us the following Main action:

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

The remainder of our code will now be in the CreateGame action. For this tutorial, we will be creating five rectangular Drawables, one in the center and one in each corner of the game window, and will be giving different Layouts to each.

Let's begin with the Layout for our top-left rectangle. The first step is to create a Layout, which we will name topLeft, and then we can add effects to it. For this rectangle, we will be setting the height and width to a percentage of the game window, setting the point where the Drawable is placed in terms of a percentage of the width and height of the game window, and setting what point of the Drawable is the origin for placement.

To change the size of the Drawable to match a percentage of the current screen size, we will use the Layout class's SetPercentageHeight and SetPercentageWidth actions. Each of these actions accepts a number, which represents a percentage of the game window's size. For instance, 0.52 is 52%. For our purposes, we want the rectangle to be approximately a third of the screen's height and a third of the screen's width. This gives us the following lines:

Layout topLeft
topLeft:SetPercentageHeight(0.33)
topLeft:SetPercentageWidth(0.33)

Next, we will change where the Drawable is placed on the screen with Layout's SetPercentageX and SetPercentageY actions. Once again, each of these actions accept a number that represents a percentage of the game screen. Since we want the Drawable to appear at the top left of the game window, our percentage for X will be 0, and our percentage for Y will be 1, giving us the following lines:

topLeft:SetPercentageX(0)
topLeft:SetPercentageY(1)

However, recall that the default origin position for Drawables is at the bottom left of the Drawable. Thus, by placing the bottom left at the highest available Y position, the Drawable will extend upwards and will be offscreen. Rather than change the percentage for Y to accommodate for this, we can simply change the origin position for the Layout using the SetPercentageOriginX and SetPercentageOriginY actions. These actions again accept a number that represents a percentage of the game window. If we change the origin percentages to 0 and 1 for X and Y, respectively, then the origin will be at the top left of the Drawable. Thus, the top left of our rectangle will be placed at the top left of the game screen, being completely on screen as the rectangle draws pixels to the right and down. This is done with the following lines:

topLeft:SetPercentageOriginX(0)
topLeft:SetPercentageOriginY(1)

Now that our topLeft Layout is complete, let's create the topRight Layout. This Layout will again be a third of the game window's height and width, but will use the percentages of 1 for both X and Y, placing the rectangle at the top right of the game screen. This gives us the following lines:

Layout topRight
topRight:SetPercentageHeight(0.33)
topRight:SetPercentageWidth(0.33)
topRight:SetPercentageX(1)
topRight:SetPercentageY(1)
topRight:SetPercentageOriginX(1)
topRight:SetPercentageOriginY(1)

Unlike the topLeft Layout, however, we will add another effect to the topRight Layout. We will give it a height offset using Layout's SetHeightOffset action. This action accepts a number as an offset. This offset is a fixed number of pixels, where the Layout will add the offset pixels onto the Drawable once it has reached the end of the percentage specified. So, in this case, by giving an offset of 100 pixels, the rectangle will extend to a third of the game window, then add another 100 pixels. For example, if the game window is 150 pixels high, the Layout will make the rectangle 50 pixels plus 100 pixels for a total of 150 pixels. At the same time, if the game window is 1500 pixels high, the Layout will make the rectangle 500 pixels plus 100 pixels for a total of 600 pixels. So, no matter how much the size of the game window changes, the offset remains the same at 100 pixels. This is done by adding the following line of code:

topRight:SetHeightOffset(100)

Next, we will create the bottomLeft Layout. Once again, the Layout will be a third of the game windoW's height and width, but using the percentages of 0 for both X and Y. Since the default percentages for these are already 0, these actions are unnecessary, but we included them in the tutorial for consistency. This gives us the following lines:

Layout bottomLeft
bottomLeft:SetPercentageHeight(0.33)
bottomLeft:SetPercentageWidth(0.33)
bottomLeft:SetPercentageX(0)
bottomLeft:SetPercentageY(0)
bottomLeft:SetPercentageOriginX(0)
bottomLeft:SetPercentageOriginY(0)

Now we will add a width offset using the SetWidthOffset action, similar to the height offset we used in the topLeft Layout. This time, however, we will provide an offset of -100. As a negative offset, rather than add 100 pixels, this will remove 100 pixels after the percentage is applied. This adds the following line of code:

bottomLeft:SetWidthOffset(-100)

With the bottomLeft Layout completed, we will now move on to the bottomRight layout. As with the previous Layouts, this one will be a third of the game window’s height and width, but using the percentages of 1 and 0 for X and Y, respectively, to place it at the bottom right corner. This gives us the following lines of code:

Layout bottomRight
bottomRight:SetPercentageHeight(0.33)
bottomRight:SetPercentageWidth(0.33)
bottomRight:SetPercentageX(1)
bottomRight:SetPercentageY(0)
bottomRight:SetPercentageOriginX(1)
bottomRight:SetPercentageOriginY(0)

For the extra effect of this Layout, we will set a maximum width and height using Layout’s SetMaximumWidth and SetMaximumHeight actions. The number provided to each of these actions once again represents a fixed number of pixels. When the width or height of the Drawable exceeds this maximum number of pixels, it will no longer scale according to the Layout in that direction. For our purposes, we will set both the maximum width and height to 300, so the maximum size the rectangle can be is 300 x 300 pixels. This is done with the following lines of code:

bottomRight:SetMaximumWidth(300)
bottomRight:SetMaximumHeight(300)

Now that the corner Layouts are complete, we will create the Layout for the center rectangle. This rectangle will be used mainly as reference to see the effects on the four corner Layouts, as it will match up to the default positions of each rectangle being a third of the width and height of the game window. This Layout will be a third again, but will be approximated to 0.34 instead of 0.33 for the percentage so that it touches the other rectangles at the corners. It will also use 0.5 for the percentages of both X and Y, placing the center of the rectangle at the center of the game window. This gives us the following lines of code:

Layout center
center:SetPercentageHeight(0.34)
center:SetPercentageWidth(0.34)
center:SetPercentageX(0.5)
center:SetPercentageY(0.5)
center:SetPercentageOriginX(0.5)
center:SetPercentageOriginY(0.5)

Now that our Layouts have been created, we need to create the rectangle Drawables and add our Layouts to them. Since our Drawables are going to be part of a Layout, the initial dimensions for width and height do not need to be accurate; the Layout will fix them to the percentage we specified earlier. However, Layouts are only called by a resize of the game screen, or by calling the Resize action on the Drawable, so we need to do that after we add the Layout using the Drawable's AddLayout action. This gives us the following lines of code:

Color color
Drawable tLeft
tLeft:LoadFilledRectangle(1, 1, color:Red())
tLeft:AddLayout(topLeft)
Add(tLeft)
tLeft:Resize()

We will do the same process for the remaining four Drawables, just giving the appropriate Layout and changing the color to differentiate them from one another. This gives us the following lines of code:

Drawable tRight
tRight:LoadFilledRectangle(1, 1, color:Yellow())
tRight:AddLayout(topRight)
Add(tRight)
tRight:Resize()

Drawable bLeft
bLeft:LoadFilledRectangle(1, 1, color:Blue())
bLeft:AddLayout(bottomLeft)
Add(bLeft)
bLeft:Resize()

Drawable bRight
bRight:LoadFilledRectangle(1, 1, color:Green())
bRight:AddLayout(bottomRight)
Add(bRight)
bRight:Resize()

Drawable centerBox
centerBox:LoadFilledRectangle(1, 1, color:Black())
centerBox:AddLayout(center)
Add(centerBox)
centerBox:Resize()

The complete Main class is as follows:

use Libraries.Game.Game
use Libraries.Interface.Layouts.Layout
use Libraries.Game.Graphics.Drawable
use Libraries.Game.Graphics.Color
use Libraries.Game.DesktopConfiguration
use Libraries.Interface.Layouts.FlowLayout

class Main is Game

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

    action CreateGame

 //We create the topLeft Layout, which is a third of the window's width and height in the top left corner of the window
        Layout topLeft
        topLeft:SetPercentageHeight(0.33)
        topLeft:SetPercentageWidth(0.33)
        topLeft:SetPercentageX(0)
        topLeft:SetPercentageY(1)
        topLeft:SetPercentageOriginX(0)
        topLeft:SetPercentageOriginY(1)

 //We create the topRight Layout, which is a third of the window's width and height in the top right corner of the window, and also has a vertical offset of 100 pixels
        Layout topRight
        topRight:SetPercentageHeight(0.33)
        topRight:SetPercentageWidth(0.33)
        topRight:SetPercentageX(1)
        topRight:SetPercentageY(1)
        topRight:SetPercentageOriginX(1)
        topRight:SetPercentageOriginY(1)
        topRight:SetHeightOffset(100)

 //We create the bottomLeft Layout, which is a third of the window's width and height in the bottom left corner of the window, and also has a horizontal offset of -100 pixels
        Layout bottomLeft
        bottomLeft:SetPercentageHeight(0.33)
        bottomLeft:SetPercentageWidth(0.33)
        bottomLeft:SetPercentageX(0)
        bottomLeft:SetPercentageY(0)
        bottomLeft:SetPercentageOriginX(0)
        bottomLeft:SetPercentageOriginY(0)
        bottomLeft:SetWidthOffset(-100)

 //We create the bottomRight Layout, which is a third of the window's width and height in the bottom right corner of the window, and also has a maximum width and height of 300 pixels each
        Layout bottomRight
        bottomRight:SetPercentageHeight(0.33)
        bottomRight:SetPercentageWidth(0.33)
        bottomRight:SetPercentageX(1)
        bottomRight:SetPercentageY(0)
        bottomRight:SetPercentageOriginX(1)
        bottomRight:SetPercentageOriginY(0)
        bottomRight:SetMaximumWidth(300)
        bottomRight:SetMaximumHeight(300)

 //We create the center Layout, which is a third of the widnow's width and height in the middle of the window
        Layout center
        center:SetPercentageHeight(0.34)
        center:SetPercentageWidth(0.34)
        center:SetPercentageX(0.5)
        center:SetPercentageY(0.5)
        center:SetPercentageOriginX(0.5)
        center:SetPercentageOriginY(0.5)

 //We create Drawables for each of our Layouts

        Color color
        Drawable tLeft
        tLeft:LoadFilledRectangle(1, 1, color:Red())
        tLeft:AddLayout(topLeft)
        Add(tLeft)
        tLeft:Resize()

        Drawable tRight
        tRight:LoadFilledRectangle(1, 1, color:Yellow())
        tRight:AddLayout(topRight)
        Add(tRight)
        tRight:Resize()

        Drawable bLeft
        bLeft:LoadFilledRectangle(1, 1, color:Blue())
        bLeft:AddLayout(bottomLeft)
        Add(bLeft)
        bLeft:Resize()

        Drawable bRight
        bRight:LoadFilledRectangle(1, 1, color:Green())
        bRight:AddLayout(bottomRight)
        Add(bRight)
        bRight:Resize()

        Drawable centerBox
        centerBox:LoadFilledRectangle(1, 1, color:Black())
        centerBox:AddLayout(center)
        Add(centerBox)
        centerBox:Resize()
    end

    action Update(number seconds)
    end
end

The following images demonstrate a sample of the differences you can expect to see; however, due to shrinking the images, it may be difficult to understand the changes between the two, so the reader is advised to experiment with different sizes on their own. For reference, the first image is taken at the default window size of 800x600, and the second image is taken at maximized size on a 2560x1440 monitor.

The changes between the two are explained as follows:

- The topLeft Layout makes the red rectangle always match to a third of the window's width and height.

- The topRight Layout makes the yellow rectangle match to a third of the window's width and height, but then adds another 100 pixels downwards. Since the second image is taken at a much higher resolution, the 100 pixel offset is much less of the overall rectangle size, as the 33% of screen size is much higher than it was in the first image.

- The bottomLeft Layout makes the blue rectangle match to a third of the window's width and height, but then takes away 100 pixels from the right side. Since the second image is taken at a much higher resolution, the -100 pixel offset is much less of the rectangle's overall size, as the 33% of screen size is much higher than it was in the first image.

- The bottomRight Layout makes the green rectangle match to a third of the window's width and height, but only until the rectangle reaches the dimensions of 300x300. In the first image, it has not reached this limit yet, so it looks identical in size to the red rectangle. However, the second image is far past that limit, so the green rectangle's scaling is stopped at 300x300, making it much smaller in comparison to the other rectangles.

- The center Layout makes the black rectangle match to a third of the window's width and height, though it is slightly larger than the red rectangle at 34% of screen size to account for our rounding a third to 33% for the other rectangles. This rectangle is particularly useful as reference to where the other rectangles would be without the extra effects we added; as you can see with the red rectangle, the corners of the red and black rectangle touch since it has no extra effects.

This image shows the Layout in effect at the default window size.This image shows the Layout in effect at a larger window size.

Multiple Layouts

In the last section, we gave just one Layout to each rectangle; however, Quorum also allows giving multiple Layouts to a single Drawable. By setting constraints on the maximum window size allowed for a Layout, we can switch between which Layout is currently used depending on the current window size. For example, consider we have three Layouts, Layout1 with a maximum window height of 200 pixels, Layout2 with a maximum window height of 400 pixels, and Layout3 with no constraints on window height. At any given point, the most specific Layout which is still allowed is used. So, if the current window height is 100 pixels, Layout1 will be used. If the current window height is 300 pixels, Layout2 will be used, since Layout1 is not allowed and Layout3 is less specific. Once the current window height gets to 401 pixels, however, the only option will be Layout3. If no Layout is allowed at some window size, no Layout will be applied, and whatever dimensions any Drawable were left with from the previously applied Layout will remain.

For this tutorial, we'll only make one extra Layout for when the width and height fall below 400 and 300 pixels, respectively. We will call this smallLayout, and it will use the SetMaximumContainerWidth and SetMaximumContainerHeight actions to set the window size that this Layout will be used for. When this Layout is in effect, it will set the rectangle's dimensions to 0 percent of the game window's width and height, rendering them unviewable. This gives us the following lines of code:

Layout smallLayout
smallLayout:SetPercentageWidth(0)
smallLayout:SetPercentageHeight(0)
smallLayout:SetMaximumContainerWidth(400)
smallLayout:SetMaximumContainerHeight(300)

Then we simply need to use the Drawable's AddLayout action again for each of our Drawables. We'll omit giving this Layout to the centerBox, giving us the following lines of code:

tLeft:AddLayout(smallLayout)
tRight:AddLayout(smallLayout)
bLeft:AddLayout(smallLayout)
bRight:AddLayout(smallLayout)

Now, when we run the program, if the window size is reduced down to within that 400x300 range, then the corner rectangles will disappear, giving a small window with only the black rectangle remaining, since we didn't give smallLayout to that Drawable.

This image shows the smallLayout in effect.

Note that this only occurs when both the conditions for width and height are met; in other words, even if only the width exceeds the maximum window size, the Layout will not take effect. This can be alleviated simply by creating more Layouts, so we will move on for the purposes of this tutorial.

FlowLayouts

FlowLayouts are type of Layout with a few more features. In particular, the Resize action for FlowLayouts automatically increases the size of the FlowLayout to properly fit the Drawables added to them. For this section of the tutorial, we used the same class as before, but commented out or deleted the code in the CreateGame action. Alternatively, a new class can be created, but remember to copy the use statements and Main action from the previous section. In addition, we will need one more use statement for this section, adding the following line:

use Libraries.Interface.Layouts.FlowLayout

In the CreateGame action, we will first create a FlowLayout variable and several Drawables. For the Drawables, one will be the parent Drawable, in this case parentBox1, and the remaining Drawables will be children, where the children Drawables will be added to the parent Drawable. Our declarations are as follows:

Color color

FlowLayout flow1
Drawable parentBox1
Drawable box1Child1
Drawable box1Child2
Drawable box1Child3

Next, we will need to load rectangles for the Drawables. To differentiate between them, each will be a different color. This adds the following lines of code:

parentBox1:LoadFilledRectangle(100, 100, color:Black())
box1Child1:LoadFilledRectangle(100, 100, color:Orange())
box1Child2:LoadFilledRectangle(100, 100, color:Purple())
box1Child3:LoadFilledRectangle(100, 100, color:Green())

Now we need to add the FlowLayout to our parent Drawable. Then we need to set the position for the parent, and add it to the game. This adds the following lines:

parentBox1:AddLayout(flow1)
parentBox1:SetPosition(50, 400)
Add(parentBox1)

Next, we need to add the children Drawables to the parent Drawable, and then Resize the parent. Since the children are added relative to the parent, setting their position is not necessary. This gives us the following lines of code:

parentBox1:Add(box1Child1)
parentBox1:Add(box1Child2)
parentBox1:Add(box1Child3)
parentBox1:Resize()

We will now make a similar FlowLayout, with only differing variable names and the feature of padding. Padding is extra space in between two Drawables added to the FlowLayout, and is, by default, added to the end of each Drawable. This extra space is the extended parent, meaning that the parent will be partially visible in this FlowLayout, whereas the previous FlowLayout covered it with the children Drawables. This padding is set through the SetPadding action, which accepts a number that represents the number of pixels to pad in between each Drawable. We will call SetPadding immediately after declaring our FlowLayout. This gives us the following lines for our second FlowLayout:

FlowLayout flow2
flow2:SetPadding(50)
Drawable parentBox2
Drawable box2Child1
Drawable box2Child2
Drawable box2Child3

parentBox2:LoadFilledRectangle(100, 100, color:Black())
box2Child1:LoadFilledRectangle(100, 100, color:Orange())
box2Child2:LoadFilledRectangle(100, 100, color:Purple())
box2Child3:LoadFilledRectangle(100, 100, color:Green())

parentBox2:AddLayout(flow2)
parentBox2:SetPosition(50, 250)
Add(parentBox2)

parentBox2:Add(box2Child1)
parentBox2:Add(box2Child2)
parentBox2:Add(box2Child3)
parentBox2:Resize()

Lastly, we will make one final FlowLayout, again adding only one extra line of code and changing variable names from our second FlowLayout. This FlowLayout will once again have padding, but will include Front Padding, which simply adds the same size of padding to the very beginning of the FlowLayout, and then has normal adding after every Drawable as usual. This is done with the SetFrontPadding action, which we will supply with true, giving us the following lines for our third FlowLayout:

FlowLayout flow3
flow3:SetFrontPadding(true)
flow3:SetPadding(50)
Drawable parentBox3
Drawable box3Child1
Drawable box3Child2
Drawable box3Child3

parentBox3:LoadFilledRectangle(100, 100, color:Black())
box3Child1:LoadFilledRectangle(100, 100, color:Orange())
box3Child2:LoadFilledRectangle(100, 100, color:Purple())
box3Child3:LoadFilledRectangle(100, 100, color:Green())

parentBox3:AddLayout(flow3)
parentBox3:SetPosition(50, 100)
Add(parentBox3)

parentBox3:Add(box3Child1)
parentBox3:Add(box3Child2)
parentBox3:Add(box3Child3)
parentBox3:Resize()

This gives the completed main class as follows:

use Libraries.Game.Game
use Libraries.Interface.Layouts.Layout
use Libraries.Game.Graphics.Drawable
use Libraries.Game.Graphics.Color
use Libraries.Game.DesktopConfiguration
use Libraries.Interface.Layouts.FlowLayout

class Main is Game

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

    action CreateGame
        Color color

        FlowLayout flow1
        Drawable parentBox1
        Drawable box1Child1
        Drawable box1Child2
        Drawable box1Child3

        parentBox1:LoadFilledRectangle(100, 100, color:Black())
        box1Child1:LoadFilledRectangle(100, 100, color:Orange())
        box1Child2:LoadFilledRectangle(100, 100, color:Purple())
        box1Child3:LoadFilledRectangle(100, 100, color:Green())

        parentBox1:AddLayout(flow1)
        parentBox1:SetPosition(50, 400)
        Add(parentBox1)

        parentBox1:Add(box1Child1)
        parentBox1:Add(box1Child2)
        parentBox1:Add(box1Child3)
        parentBox1:Resize()

        FlowLayout flow2
        flow2:SetPadding(50)
        Drawable parentBox2
        Drawable box2Child1
        Drawable box2Child2
        Drawable box2Child3

        parentBox2:LoadFilledRectangle(100, 100, color:Black())
        box2Child1:LoadFilledRectangle(100, 100, color:Orange())
        box2Child2:LoadFilledRectangle(100, 100, color:Purple())
        box2Child3:LoadFilledRectangle(100, 100, color:Green())

        parentBox2:AddLayout(flow2)
        parentBox2:SetPosition(50, 250)
        Add(parentBox2)

        parentBox2:Add(box2Child1)
        parentBox2:Add(box2Child2)
        parentBox2:Add(box2Child3)
        parentBox2:Resize()

        FlowLayout flow3
        flow3:SetFrontPadding(true)
        flow3:SetPadding(50)
        Drawable parentBox3
        Drawable box3Child1
        Drawable box3Child2
        Drawable box3Child3

        parentBox3:LoadFilledRectangle(100, 100, color:Black())
        box3Child1:LoadFilledRectangle(100, 100, color:Orange())
        box3Child2:LoadFilledRectangle(100, 100, color:Purple())
        box3Child3:LoadFilledRectangle(100, 100, color:Green())

        parentBox3:AddLayout(flow3)
        parentBox3:SetPosition(50, 100)
        Add(parentBox3)

        parentBox3:Add(box3Child1)
        parentBox3:Add(box3Child2)
        parentBox3:Add(box3Child3)
        parentBox3:Resize()
    end

    action Update(number seconds)
    end
end

When the program is run now, the three FlowLayouts will appear, with flow1 at the top, flow2 in the middle, and flow3 on the bottom, showing how the different features appear.

This image shows the FlowLayout.