An Introduction to TabPanes in Quorum

In this tutorial, we will learn how to use TabPanes in the Quorum Game Engine. TabPanes are an interface tool which separates content on different tabs. For example, most Internet browsers allow you to open multiple webpages as Tabs in a single window. While only one Tab is active at a time, the data stored on inactive Tabs is not lost, and can be displayed simply by switching the active Tab.

For this tutorial, we will create a TabPane with three different Tabs, represented by different shapes, and allow the user to remove or add new Tabs using Buttons. To start, create a new Game Application project.

TabPaneListener

Before we begin working on the main class, we will first create a new Quorum class that will listen for when a Tab is added to the TabPane, or when a Tab is removed from the TabPane. When this listener detects an addition or removal, the program will say that a Tab was opened or closed. This class will use the libraries for TabChangeListener, TabChangeEvent, and Speech, requiring the following use statements:
use Libraries.Interface.Events.TabChangeListener
use Libraries.Interface.Events.TabChangeEvent
use Libraries.Sound.Speech

The class itself will inherit the TabChangeListener class, and will override the OpenedTab and ClosedTab actions. In each of those actions, we will create a Speech object, and call its Say action with "Tab opened" or "Tab closed." The resulting class is as follows:

use Libraries.Interface.Events.TabChangeListener
use Libraries.Interface.Events.TabChangeEvent
use Libraries.Sound.Speech

class TabPaneListener is TabChangeListener

    action OpenedTab(TabChangeEvent event)
        Speech speech
        speech:Say("Tab opened", false)
    end

    action ClosedTab(TabChangeEvent event)
        Speech speech
        speech:Say("Tab closed", false)
    end
end

For additional information on how these listeners and events work, check out the tutorial on Events.

Creating a TabPane

Now we can move on to the main class. The main will use the libraries for TabPane, Tab, Drawable, Color, Array, and Button, requiring the following use statements:

use Libraries.Game.Game
use Libraries.Interface.Controls.TabPane
use Libraries.Interface.Controls.Tab
use Libraries.Game.Graphics.Drawable
use Libraries.Game.Graphics.Color
use Libraries.Containers.Array
use Libraries.Interface.Controls.Button

Since we want the TabPane to be usable as soon as the program begins, we will be writing code in the CreateGame action. To start, create a TabPane, TabPaneListener, and three Tabs. Our declarations are as follows:

TabPane pane
TabPaneListener listener
Tab tab1
Tab tab2
Tab tab3

For our TabPane, each Tab will show a different shape, using Drawables. We will use a rectangle, circle, and triangle. These shapes are created as follows:

Color color

Drawable box
Drawable circle
Drawable triangle

box:LoadFilledRectangle(100, 100, color:Red())
circle:LoadFilledCircle(50, color:Blue())
triangle:LoadFilledTriangle(0, 0, 100, 0, 50, 100, color:Green())

The next thing we need before we can Initialize our Tabs is an icon; this will appear next to the Tab's name. For our example, we will again use Drawables of different shapes, but these will be considerably smaller. These icons are created as follows:

Drawable icon1
Drawable icon2
Drawable icon3

icon1:LoadFilledRectangle(50, 50)
icon2:LoadFilledCircle(25)
icon3:LoadFilledTriangle(0, 0, 50, 0, 25, 50)

Now that our shapes and icons are created, we can Initialize the Tabs we created earlier. We will be Initializing with a text parameter for the name, a Drawable parameter for the associated item within the Tab, a Drawable for each Tab's icon, and a boolean for whether or not the Tab can be closed. Thus, our Initializations are as follows

tab1:Initialize("Square", box, icon1, true)
tab2:Initialize("Circle", circle, icon2, true)
tab3:Initialize("Triangle", triangle, icon3, true)

Next, we need to create an Array with our Tabs, as the Initialize action for the TabPane requires an Array of Tabs parameter. This is done through the following lines of code:

Array tabs
tabs:Add(tab1)
tabs:Add(tab2)
tabs:Add(tab3)

Now that we have our Array of Tabs, we can Initialize the TabPane, set its position, add our TabChangeListener, and finally, add the TabPane to the game. This is done with the following lines of code:

pane:Initialize(450, 450, tabs)
pane:SetPosition(50, 100)
pane:AddTabChangeListener(listener)
Add(pane)

Additionally, we will add an outline to the display area of our Tabs. This is only forappearances, but is easily done by creating a simple Drawable. This adds the following lines:

Drawable outline
outline:LoadRectangle(450, 425, color:Black())
outline:SetPosition(50, 100)
Add(outline)

Now, when we run the program, we can switch Tabs to see our different shapes, and we can close out of them

This image shows the expected initial output.

Adding Tabs

Now, let's give our program the ability to add new Tabs, which we will do with Buttons. To start, we will need to create a Behavior. We will name this new class "AddTabBehavior," and it will use the libraries for Behavior, BehaviorEvent, Tab, TabPane, and Drawable, requiring the following use statements:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Interface.Controls.Tab
use Libraries.Interface.Controls.TabPane
use Libraries.Game.Graphics.Drawable

The AddTabBehavior class itself will inherit the Behavior class, and will override the Run action. To start, we will need to create new actions to pass in the desired arguments from the main class. These will be the TabPane itself, the name of the new Tab, the shape displayed by the new Tab, and the icon for the new Tab, and each of these will need to be created as class variables. This gives us the following template for our class:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Interface.Controls.Tab
use Libraries.Interface.Controls.TabPane
use Libraries.Game.Graphics.Drawable

class AddTabBehavior is Behavior

    TabPane pane = undefined
    text name = undefined
    Drawable shape = undefined
    Drawable icon = undefined

    action Run(BehaviorEvent event)
    end

    action SetTabPane(TabPane newPane)
        pane = newPane
    end

    action SetTabType(text newName, Drawable newShape, Drawable newIcon)
        name = newName
        shape = newShape
        icon = newIcon
    end
end

For the Run action, we need to create a new Tab, Initialize it with the class variables, and add it to the TabPane. We will also protect against program crashes by checking if any of the class variables remain undefined. This gives us the following code:

action Run(BehaviorEvent event)
    if pane not= undefined and name not= undefined and shape not= undefined and icon not= undefined
        Tab newTab
        newTab:Initialize(name, shape, icon, true)
        pane:Add(newTab)
    end
end

Adding this to our template, the complete AddTabBehavior class is as follows:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Interface.Controls.Tab
use Libraries.Interface.Controls.TabPane
use Libraries.Game.Graphics.Drawable

class AddTabBehavior is Behavior

    TabPane pane = undefined
    text name = undefined
    Drawable shape = undefined
    Drawable icon = undefined

    action Run(BehaviorEvent event)
        if pane not= undefined and name not= undefined and shape not= undefined and icon not= undefined
            Tab newTab
            newTab:Initialize(name, shape, icon, true)
            pane:Add(newTab)
        end
    end

    action SetTabPane(TabPane newPane)
        pane = newPane
    end

    action SetTabType(text newName, Drawable newShape, Drawable newIcon)
        name = newName
        shape = newShape
        icon = newIcon
    end
end

Returning the main class, we can now create a few Buttons to add new Tabs. We will create one for each of the shapes we used earlier, which adds the following lines:

Button addSquare
Button addCircle
Button addTriangle

addSquare:Initialize(150, 75, "Add Square")
addSquare:SetPosition(600, 400)
addCircle:Initialize(150, 75, "Add Circle")
addCircle:SetPosition(600, 300)
addTriangle:Initialize(150, 75, "Add Triangle")
addTriangle:SetPosition(600, 200)

Add(addSquare)
Add(addCircle)
Add(addTriangle)

Now we must create an AddTabBehavior object for each Button as pass in the appropriate variables with our SetTabPane and SetTabType actions. Finally, we'll add the new Behaviors to our Buttons through the SetBehavior action. This is done with the following code:

AddTabBehavior squareBehavior
AddTabBehavior circleBehavior
AddTabBehavior triangleBehavior

squareBehavior:SetTabPane(pane)
squareBehavior:SetTabType("Square", box, icon1)

circleBehavior:SetTabPane(pane)
circleBehavior:SetTabType("Circle", circle, icon2)

triangleBehavior:SetTabPane(pane)
triangleBehavior:SetTabType("Triangle", triangle, icon3)

addSquare:SetBehavior(squareBehavior)
addCircle:SetBehavior(circleBehavior)
addTriangle:SetBehavior(triangleBehavior)

The TabPaneSelection Class

Like Trees and TextBoxes, TabPanes have their own specific type of Selection, which keeps track of the index of the current Tab. For this tutorial, we will create a SelectionListener to say aloud a newly selected Tab and a Button which will duplicate the currently selected Tab.

First, let's create our SelectionListener. To start, create a new Quorum class. We named ours "TabSelectionListener.quorum." This class will need the libraries for SelectionListener, SelectionEvent, and Speech, requiring the following use statements:

use Libraries.Interface.Events.SelectionListener
use Libraries.Interface.Events.SelectionEvent
use Libraries.Sound.Speech

Additionally, the TabSelectionListener class itself will inherit SelectionListener and override the SelectionChanged action. To read the current Tab's name aloud, we simply need to obtain the name with the SelectionEvent's GetDisplayName action, and then use the Speech class's Say action. This gives us the following complete TabSelectionListener class:

use Libraries.Interface.Events.SelectionListener
use Libraries.Interface.Events.SelectionEvent
use Libraries.Sound.Speech

class TabSelectionListener is SelectionListener
    action SelectionChanged(SelectionEvent selection)
        text currentTab = selection:GetDisplayName()
        Speech speech
        speech:Say(currentTab)
    end
end

Returning to the main, we need to create a TabSelectionListener variable and add it as a SelectionListener to the game. This is done by adding the following two lines to the CreateGame action:

TabSelectionListener selectionListener
AddSelectionListener(selectionListener)

Now, we will create another new Quorum class for the new Button's Behavior. We will call this class "AddCurrentBehavior.quorum." This class will need the libraries for Behavior, BehaviorEvent, TabPane, TabPaneSelection, Tab, and Drawable, requiring the following use statements:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Interface.Controls.TabPane
use Libraries.Interface.Selections.TabPaneSelection
use Libraries.Interface.Controls.Tab
use Libraries.Game.Graphics.Drawable

Additionally, the AddCurrentBehavior class itself will inherit the Behavior class, will override the Run action, and have a TabPane class variable that will be set with a SetTabPane action. This gives us the following template:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Interface.Controls.TabPane
use Libraries.Interface.Selections.TabPaneSelection
use Libraries.Interface.Controls.Tab
use Libraries.Game.Graphics.Drawable

class AddCurrentBehavior is Behavior

    TabPane pane = undefined

    action Run(BehaviorEvent behavior)
    end

    action SetTabPane(TabPane newPane)
        pane = newPane
    end
end

The Run action will first check if the pane variable was defined by the SetTabPane action. If so, it will create a TabPaneSelection variable and set it using the TabPane's GetSelection action. Once this selection has been obtained, we can create a Tab object and set it using the TabPaneSelection's GetTab action. This gives us the following lines of code:

if pane not= undefined
    TabPaneSelection selection = pane:GetSelection()
    Tab current = selection:GetTab()
end

Still within this conditional if statement, we now need to check if the Tab variable "current" is undefined before calling any actions from it. If it is defined, we can obtain the Tab's name and the contents of the Tab using the Tab class's GetName and GetRelatedItem actions, respectively. Note that the GetRelatedItem action returns an Item2D, not a Drawable, so we will have to cast it before assigning it to a Drawable variable. This gives us the following code so far:

if pane not= undefined
    TabPaneSelection selection = pane:GetSelection()
    Tab current = selection:GetTab()

    if current not= undefined
        text name = current:GetName()
        Drawable shape = cast(Drawable, current:GetRelatedItem())
    end
end

We now have the Tab's name and shape, but are still missing the icon. While there is not an action to get the Tab's icon, we can simply use the obtained name to determine which shape to make. Using conditionals, we can accomplish this with the following lines of code, which are still within the previous conditionals:

if name = "Square"
    icon:LoadFilledRectangle(50, 50)
elseif name = "Circle"
    icon:LoadFilledCircle(25)
elseif name = "Triangle"
    icon:LoadFilledTriangle(0, 0, 50, 0, 25, 50)
end

With our name, shape, and icon variables obtained from the current Tab, we can now create an AddTabBehavior variable and call its Run action using our behavior parameter using the following code:

AddTabBehavior addTab
addTab:SetTabPane(pane)
addTab:SetTabType(name, shape, icon)
addTab:Run(behavior)

This gives the completed AddCurrentBehavior class as follows:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Interface.Controls.TabPane
use Libraries.Interface.Selections.TabPaneSelection
use Libraries.Interface.Controls.Tab
use Libraries.Game.Graphics.Drawable

class AddCurrentBehavior is Behavior

    TabPane pane = undefined

    action Run(BehaviorEvent behavior)
        if pane not= undefined
            TabPaneSelection selection = pane:GetSelection()
            Tab current = selection:GetTab()

            if current not= undefined
                text name = current:GetName()
                Drawable shape = cast(Drawable, current:GetRelatedItem())

                if name = "Square"
                    icon:LoadFilledRectangle(50, 50)
                elseif name = "Circle"
                    icon:LoadFilledCircle(25)
                elseif name = "Triangle"
                    icon:LoadFilledTriangle(0, 0, 50, 0, 25, 50)
                end

                AddTabBehavior addTab
                addTab:SetTabPane(pane)
                addTab:SetTabType(name, shape, icon)
                addTab:Run(behavior)
            end
        end
    end

    action SetTabPane(TabPane newPane)
        pane = newPane
    end
end

Returning to the main class, we need to make another Button for the new Behavior. This gives us the following code added to the end of the CreateGame action:

AddCurrentBehavior currentBehavior
currentBehavior:SetTabPane(pane)

Button addCurrent
addCurrent:Initialize(150, 75, "Add Current")
addCurrent:SetPosition(600, 100)
addCurrent:SetBehavior(currentBehavior)
Add(addCurrent)

With that, our completed main class is as follows:

use Libraries.Game.Game
use Libraries.Interface.Controls.TabPane
use Libraries.Interface.Controls.Tab
use Libraries.Game.Graphics.Drawable
use Libraries.Game.Graphics.Color
use Libraries.Containers.Array
use Libraries.Interface.Controls.Button

class Main is Game

    action Main
        StartGame()
    end

    action CreateGame
        //We declare our TabPane and our Tabs
        TabPane pane
        TabPaneListener listener
        Tab tab1
        Tab tab2
        Tab tab3

        //We create the Drawables to be contained in each Tab
        Color color

        Drawable box
        Drawable circle
        Drawable triangle

        box:LoadFilledRectangle(100, 100, color:Red())
        circle:LoadFilledCircle(50, color:Blue())
        triangle:LoadFilledTriangle(0, 0, 100, 0, 50, 100, color:Green())

        //We create the Drawables to be each Tab's icon
        Drawable icon1
        Drawable icon2
        Drawable icon3

        icon1:LoadFilledRectangle(50, 50)
        icon2:LoadFilledCircle(25)
        icon3:LoadFilledTriangle(0, 0, 50, 0, 25, 50)

        //We Initialize each of our Tabs
        tab1:Initialize("Square", box, icon1, true)
        tab2:Initialize("Circle", circle, icon2, true)
        tab3:Initialize("Triangle", triangle, icon3, true)

        //We add each Tab to an Array
        Array tabs
        tabs:Add(tab1)
        tabs:Add(tab2)
        tabs:Add(tab3)

        //We initialize the TabPane with our Array of Tabs
        pane:Initialize(450, 450, tabs)
        pane:SetPosition(50, 100)
        pane:AddTabChangeListener(listener)
        Add(pane)

        Drawable outline
        outline:LoadRectangle(450, 425, color:Black())
        outline:SetPosition(50, 100)
        Add(outline)

        //We create our Buttons for creating new Tabs
        Button addSquare
        Button addCircle
        Button addTriangle

        addSquare:Initialize(150, 75, "Add Square")
        addSquare:SetPosition(600, 400)
        addCircle:Initialize(150, 75, "Add Circle")
        addCircle:SetPosition(600, 300)
        addTriangle:Initialize(150, 75, "Add Triangle")
        addTriangle:SetPosition(600, 200)

        Add(addSquare)
        Add(addCircle)
        Add(addTriangle)

        //We create Behaviors for each Button to add new Tabs
        AddTabBehavior squareBehavior
        AddTabBehavior circleBehavior
        AddTabBehavior triangleBehavior

        squareBehavior:SetTabPane(pane)
        squareBehavior:SetTabType("Square", box, icon1)

        circleBehavior:SetTabPane(pane)
        circleBehavior:SetTabType("Circle", circle, icon2)

        triangleBehavior:SetTabPane(pane)
        triangleBehavior:SetTabType("Triangle", triangle, icon3)

        addSquare:SetBehavior(squareBehavior)
        addCircle:SetBehavior(circleBehavior)
        addTriangle:SetBehavior(triangleBehavior)

        //We add our SelectionListener to the game to detect when Tabs are opened or close
        TabSelectionListener selectionListener
        AddSelectionListener(selectionListener)

        //We create and add the Button to duplicate the currently selected Tab
        AddCurrentBehavior currentBehavior
        currentBehavior:SetTabPane(pane)

        Button addCurrent
        addCurrent:Initialize(150, 75, "Add Current")
        addCurrent:SetPosition(600, 100)
        addCurrent:SetBehavior(currentBehavior)
        Add(addCurrent)

When we run the program, we can add new Tabs of each shape or duplicate the current Tab by clicking the appropriate Button. Then we can select between which Tab to display, reading aloud the Tab title, and can actively open or close more Tabs, the program saying aloud either "Tab opened" or "Tab closed" when we do so.

This image shows the final expected game window.

Next Tutorial

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