An Introduction to Focuses in Quorum

In this tutorial, we will learn how the Focus works in the Quorum Game Engine. The Focus is an element within the Item class that is used to indicate which Item is currently selected by the user. If an Item does not have the current Focus, it cannot be activated. Typically, the Focus cycles through several Focusable Items. In Quorum, this is done by manually setting the next and previous Focuses for each Item in the cycle.

For this tutorial, we will create several Drawables and allow the user to cycle the Focus through them. When one of the Items receives the Focus, it will change color to green and say which shape it is, and when the Item loses the Focus, it will change color to red and output which shape lost the Focus and which shape gained the Focus. To start, create a new Game Application project.

Creating a Focusable Drawable

Before we can add much to the main, we need to create a new class. We will name this new class "FocusDrawable.quorum." We will need the libraries for Drawable, FocusEvent, Color, and Speech, requiring the following statements:
use Libraries.game.Graphics.Drawable
use Libraries.Interface.Events.FocusEvent
use Libraries.Game.Graphics.Color
use Libraries.Sound.Speech

The FocusDrawable class itself will inherit the Drawable class, and we will override the GainedFocus and LostFocus actions to execute some instructions when the Drawable gains the Focus. We will also need a text class variable for the shape’s description and actions to set and get that description. This gives us the following template:

use Libraries.Game.Graphics.Drawable
use Libraries.Interface.Events.FocusEvent
use Libraries.Game.Graphics.Color
use Libraries.Sound.Speech

class FocusDrawable is Drawable

    text shapeDescription = undefined

    action SetShapeDescription(text newShape)
        shapeDescription = newShape
    end

    action GetShapeDescription returns text
        return shapeDescription
    end

    action GainedFocus(FocusEvent event)
    end

    action LostFocus(FocusEvent event)
    end
end

For the GainedFocus action, recall that this class inherits the Drawable class. Thus, the FocusDrawable item itself will be the shape we want to change the color of. Thus, we need to use "me:SetColor(color:Green())." Additionally, the Speech class will be used to have the program say the shapeDescription variable aloud. This gives us the following action:

action GainedFocus(FocusEvent event)
    Color color
    me:SetColor(color:Green())

    if shapeDescription not= undefined
        Speech speech
        speech:Say(shapeDescription)
    end
end

Now we can move on to LostFocus, the final action. This action will need to set itself to the color Red, and output the shapeDescription that just lost the Focus. This gives us the following code:

action LostFocus(FocusEvent event)
    Color color
    me:SetColor(color:Red())

    output "The " + shapeDescription + " has lost the Focus."
end

The completed FocusDrawable class is as follows:

use Libraries.Game.Graphics.Drawable
use Libraries.Interface.Events.FocusEvent
use Libraries.Game.Graphics.Color
use Libraries.Sound.Speech

class FocusDrawable is Drawable

    text shapeDescription = undefined

    action SetShapeDescription(text newShape)
        shapeDescription = newShape
    end

    action GetShapeDescription returns text
        return shapeDescription
    end

    action GainedFocus(FocusEvent event)
        Color color
        me:SetColor(color:Green())

        if shapeDescription not= undefined
            Speech speech
            speech:Say(shapeDescription)
        end
    end

    action LostFocus(FocusEvent event)
        Color color
        me:SetColor(color:Red())

        output "The " + shapeDescription + " has lost the Focus."
    end
end

Cycles Using Focus

Now that we’ve created the FocusDrawable class, we can return to the main class. This class will need the Color library, so we must add the following statement:

use Libraries.Game.Graphics.Color

Since we want the cycle of shapes to be usable as soon as the game begins, we will write our new code into the CreateGame class. To start, we will create a Color variable and six FocusDrawable objects. These objects will be two rectangles, two circles, and two triangles. The following are our declarations:

Color color
FocusDrawable rectangle1
FocusDrawable circle1
FocusDrawable triangle1
FocusDrawable rectangle2
FocusDrawable circle2
FocusDrawable triangle2

Now we will load each shape. Since the FocusDrawable class inherits the Drawable class, we can use the Drawable class’s LoadFilledRectangle, LoadFilledCircle, and LoadFilledTriangle actions. This adds the following lines of code:

rectangle1:LoadFilledRectangle(50, 50, color:Red())
circle1:LoadFilledCircle(25, color:Red())
triangle1:LoadFilledTriangle(0, 0, 25, 50, 50, 0, color:Red())
rectangle2:LoadFilledRectangle(50, 50, color:Red())
circle2:LoadFilledCircle(25, color:Red())
triangle2:LoadFilledTriangle(0, 0, 25, 50, 50, 0, color:Red())

To make the shapes match the cycle we will make later, we will arrange them in a circle on the game window. This gives the following lines:

rectangle1:SetPosition(375, 500)
circle1:SetPosition(600, 375)
triangle1:SetPosition(600, 175)
rectangle2:SetPosition(375, 50)
circle2:SetPosition(150, 175)
triangle2:SetPosition(150, 375)

Next, we need to call SetShapeDescription for each FocusDrawable to give it an appropriate description. Our calls are as follows:

rectangle1:SetShapeDescription("First Rectangle")
circle1:SetShapeDescription("First Circle")
triangle1:SetShapeDescription("First Triangle")
rectangle2:SetShapeDescription("Second Rectangle")
circle2:SetShapeDescription("Second Circle")
triangle2:SetShapeDescription("Second Triangle")

Before we add each Item to the Focus cycle, we need to make them Focusable objects. Some Items are Focusable by default, such as TextBoxes and Trees, but Drawables are not, so we must set them manually. This is done by calling the SetFocusable action with a boolean parameter of true. This adds the following lines:

rectangle1:SetFocusable(true)
circle1:SetFocusable(true)
triangle1:SetFocusable(true)
rectangle2:SetFocusable(true)
circle2:SetFocusable(true)
triangle2:SetFocusable(true)

Now that each Item is Focusable, we need to manually set the next and previous Focus for each Item. We will set the next Focus from rectangle1 to circle1, from circle1 to triangle1, then to rectangle2, circle2, triangle2, and finally have triangle2 go to rectangle1, forming a cycle, while the previous Focuses will go in reverse order. This gives us the following code:

rectangle1:SetNextFocus(circle1)
rectangle1:SetPreviousFocus(triangle2)
circle1:SetNextFocus(triangle1)
circle1:SetPreviousFocus(rectangle1)
triangle1:SetNextFocus(rectangle2)
triangle1:SetPreviousFocus(circle1)
rectangle2:SetNextFocus(circle2)
rectangle2:SetPreviousFocus(triangle1)
circle2:SetNextFocus(triangle2)
circle2:SetPreviousFocus(rectangle2)
triangle2:SetNextFocus(rectangle1)
triangle2:SetPreviousFocus(circle2)

With the next and previous Focus set for each Item, now we need to add them to the game. This is done with the following lines:

Add(rectangle1)
Add(circle1)
Add(triangle1)
Add(rectangle2)
Add(circle2)
Add(triangle2)

Finally, we must start the Focus on one of our Items, otherwise we cannot begin the cycle. We will begin with rectangle1. This is done simply by calling the Focus action, giving us the following line:

rectangle1:Focus()

The completed main class is as follows:

use Libraries.Game.Game
use Libraries.Game.Graphics.Color

class Main is Game

    action Main
        StartGame()
    end

    action CreateGame
        Color color
        FocusDrawable rectangle1
        FocusDrawable circle1
        FocusDrawable triangle1
        FocusDrawable rectangle2
        FocusDrawable circle2
        FocusDrawable triangle2

        rectangle1:LoadFilledRectangle(50, 50, color:Red())
        circle1:LoadFilledCircle(25, color:Red())
        triangle1:LoadFilledTriangle(0, 0, 25, 50, 50, 0, color:Red())
        rectangle2:LoadFilledRectangle(50, 50, color:Red())
        circle2:LoadFilledCircle(25, color:Red())
        triangle2:LoadFilledTriangle(0, 0, 25, 50, 50, 0, color:Red())

        rectangle1:SetPosition(375, 500)
        circle1:SetPosition(600, 375)
        triangle1:SetPosition(600, 175)
        rectangle2:SetPosition(375, 50)
        circle2:SetPosition(150, 175)
        triangle2:SetPosition(150, 375)

        rectangle1:SetShapeDescription("First Rectangle")
        circle1:SetShapeDescription("First Circle")
        triangle1:SetShapeDescription("First Triangle")
        rectangle2:SetShapeDescription("Second Rectangle")
        circle2:SetShapeDescription("Second Circle")
        triangle2:SetShapeDescription("Second Triangle")

        rectangle1:SetFocusable(true)
        circle1:SetFocusable(true)
        triangle1:SetFocusable(true)
        rectangle2:SetFocusable(true)
        circle2:SetFocusable(true)
        triangle2:SetFocusable(true)

        rectangle1:SetNextFocus(circle1)
        rectangle1:SetPreviousFocus(triangle2)
        circle1:SetNextFocus(triangle1)
        circle1:SetPreviousFocus(rectangle1)
        triangle1:SetNextFocus(rectangle2)
        triangle1:SetPreviousFocus(circle1)
        rectangle2:SetNextFocus(circle2)
        rectangle2:SetPreviousFocus(triangle1)
        circle2:SetNextFocus(triangle2)
        circle2:SetPreviousFocus(rectangle2)
        triangle2:SetNextFocus(rectangle1)
        triangle2:SetPreviousFocus(circle2)

        Add(rectangle1)
        Add(circle1)
        Add(triangle1)
        Add(rectangle2)
        Add(circle2)
        Add(triangle2)

        rectangle1:Focus()
    end

    action Update(number seconds)
    end
end

When we run the program, the shapes are arranged in a circular pattern and can be cycled through, saying aloud the current Focus and turning it Green, and outputting when an Item loses the Focus and turning it back to Red. To move forwards to the next Focus, press Tab, and to move backwards to the previous Focus, press Shift + Tab. Alternatively, the shapes can be clicked on with the mouse to change the Focus.

This image shows the arrangement of Drawables.

GetNextFocus and GetPreviousFocus

There are two additional actions relating to the Focus that are included in the Item class. These are GetNextFocus and GetPreviousFocus, both of which return an Item. To demonstrate these, we will modify the GainedFocus action in the FocusDrawable class from the program we created in the previous section.

To start, delete the line of "speech:Say(shapeDescription)." Rather than have the program say which shape we are on, we will alter it to prompt the user to move to the next or previous shape.

First, we need to create a new FocusDrawable object, nextDrawable, and assign it to the value returned from calling the current Item’s GetNextFocus action. However, this action returns an Item, not a FocusDrawable, so we need to cast it. Then we can use the GetShapeDescription action of the obtained FocusDrawable to get its shapeDescription. This gives us the following lines of code:

FocusDrawable nextDrawable = cast(FocusDrawable, me:GetNextFocus())
text nextDescription = nextDrawable:GetShapeDescription()

Next, we will do the same to get the description of the previous FocusDrawable, only changing the variable names and calling GetPreviousFocus instead of GetNextFocus. This is done with the following lines:

FocusDrawable previousDrawable = cast(FocusDrawable, me:GetPreviousFocus())
text previousDescription = previousDrawable:GetShapeDescription()

Finally, we will use the Speech class to prompt the user aloud. We accomplish this by adding the following line:

speech:Say("Tab to reach the " + nextDescription + ", Shift Tab to reach the " + previousDescription)

Now, when we run the program, it will verbally tell the user the description of both the next and previous Focuses when a new Focus is acquired.

Next Tutorial

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