An Introduction to Menus in Quorum

In this tutorial, we will learn how to use Menus in the Quorum Game Engine. Menus are a GUI tool very similar to Trees, but the MenuItems are arranged on a MenuBar differently TreeItems on a Tree. Another key difference is that only one submenu can be open at a time, whereas multiple subtrees can be expanded at a time. Typically, MenuBars are located at the top of an application and have headers like File, Edit, View, and so on.

This image shows an example of a Menu in NetBeans. The File menu is chosen, with Import Project chosen under it and expanded. From Zip is the final choice highlighted.

For this tutorial, we will create a simple Menu with several operations for editing a Drawable. Specifically, our Menu will allow us to scale, rotate, reflect, color, and reset a triangle. To start, create a new Game Application project.

Creating Headers with Arrays

Like subtrees, submenus are created by adding one or more MenuItems to another MenuItem. In Quorum, this can be done by Initializing a MenuItem with an Array of other MenuItems, or by adding each child MenuItem to the parent MenuItem individually. For our first header, which is "Size," we will use the Array method.

For our main class, we will need libraries for MenuBar, MenuItem, Array, Dawable, and Color, adding the following use statements:

use Libraries.Game.Game
use Libraries.Interface.Controls.MenuBar
use Libraries.Interface.Controls.MenuItem
use Libraries.Containers.Array
use Libraries.Game.Graphics.Drawable
use Libraries.Game.Graphics.Color

Additionally, since we want our Menu and Drawable to be usable as soon as the game is created, we will write the remaining code for the main class in the CreateGame action. To start, we will create our triangle Drawable with the following lines of code:

Drawable triangle
triangle:LoadFilledTriangle(0, 0, 150, 80, 200, 25)
triangle:SetPosition(100, 100)
Add(triangle)

To demonstrate the proper changes for all operations, it is important that the Drawable is not symmetrical across either axis. As such, we suggest using the parameters we have chosen above.

Next, we can create our MenuItems for our first header. We will also create three Arrays of MenuItems, which will be used to create our submenus. Our declarations are as follows:

MenuItem header1        //size
MenuItem header1_1          //increase size
MenuItem header1_1_1            //horizontal
MenuItem header1_1_2            //vertical
MenuItem header1_1_3            //both
MenuItem header1_2          //decrease size
MenuItem header1_2_1            //horizontal
MenuItem header1_2_2            //vertical
MenuItem header1_2_3            //both

Array  head1
Array  head1_1
Array  head1_2

Note that the comments indicate the organization of our Menu, where submenus have an extra indentation level than their parent menu. For example, this means that header1_1_3 is one of the three submenus under header1_1, and header1_1 itself is a submenu under header1.

Before we can Initialize our MenuItems, we need to set up our Arrays. The first Array, head1, will have MenuItems header1_1 and header1_2. The second Array, head1_1, will have the MenuItems header1_1_1, header1_1_2, and header1_1_3. In the same manner, the head1_2 Array will have the MenuItems header1_2_1, header1_2_2, and header1_2_3. This adds the following lines:

head1:Add(header1_1)
head1:Add(header1_2)
head1_1:Add(header1_1_1)
head1_1:Add(header1_1_2)
head1_1:Add(header1_1_3)
head1_2:Add(header1_2_1)
head1_2:Add(header1_2_2)
head1_2:Add(header1_2_3)

With our Arrays created, we can now Initialize our MenuItems. The MenuItems header1, header1_1, and header1_2 will use the Initialize action with a text parameter for the name and an Array of MenuItems, while the remaining MenuItems will use the Initialize action with only a text parameter for the name, since they don’t have additional submenus at the lowest level. This is done with the following lines:

header1:Initialize("Size", head1)
header1_1:Initialize("Increase Size", head1_1)
header1_2:Initialize("Decrease Size", head1_2)

header1_1_1:Initialize("Horizontal Scale")
header1_1_2:Initialize("Vertical Scale")
header1_1_3:Initialize("Full Scale")

header1_2_1:Initialize("Horizontal Shrink")
header1_2_2:Initialize("Vertical Shrink")
header1_2_3:Initialize("Full Shrink")

Now we simply need to add Behaviors to our lowest-level MenuItems, but we need to first create that Behavior class. Typically, if a MenuItem has any submenus under it, it will not have a Behavior, as its purpose is to open and show the related submenus, not to run code.

To start, create a new Quorum class, which we will name "SizeBehavior.quorum." This class will need the libraries for Behavior, BehaviorEvent, Drawable, and Speech, requiring the following use statements:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Sound.Speech

Additionally, the SizeBehavior class itself will inherit the Behavior class. It will also override the Run action, and will have SetDrawable and SetScale actions that set class variables, giving us the following template:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Sound.Speech

class SizeBehavior is Behavior

    Drawable triangle = undefined
    number scaleX = 1.0
    number scaleY = 1.0

    action Run(BehaviorEvent behavior)
    end

    action SetDrawable(Drawable newTriangle)
        triangle = newTriangle
    end

    action SetScale(number newScaleX, number newScaleY)
        scaleX = newScaleX
        scaleY = newScaleY
    end
end

For the Run action, we will need to check if the Drawable was properly defined, and use the Drawable’s SetScale action. This action takes two number parameters, one for scaling the X-values and one for scaling the Y-values. These numbers represent percentages, so 1.25 means the shape will be scaled to 125% of the previous size, while 0.75 means the shape will shrink to 75% of the previous size. Then we will use the Speech class to say the X and Y scale factors. This gives us the following Run action:

action Run(BehaviorEvent behavior)
    if triangle not= undefined
        triangle:SetScale(scaleX, scaleY)

        Speech speech
        speech:Say("Scaled by a factor of " + scaleX + " horizontally, and a factor of " + scaleY + "vertically.")
    end
end

Thus, our SizeBehavior class is as follows:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Sound.Speech

class SizeBehavior is Behavior

    Drawable triangle = undefined
    number scaleX = 1.0
    number scaleY = 1.0

    action Run(BehaviorEvent behavior)
        if triangle not= undefined
            triangle:SetScale(scaleX, scaleY)

            Speech speech
            speech:Say("Scaled by a factor of " + scaleX + " horizontally, and a factor of " + scaleY + "vertically.")
        end
    end

    action SetDrawable(Drawable newTriangle)
        triangle = newTriangle
    end

    action SetScale(number newScaleX, number newScaleY)
        scaleX = newScaleX
        scaleY = newScaleY
    end
end

In addition to the Arrays method from the last section, we can also use the AddMenuItem action to create submenus. This allows us to use just one Initialize action, specifically the one with only a text parameter. We will be creating our second header and its submenus using this method. Below are our declarations of the MenuItems:

SizeBehavior horizontalScale
SizeBehavior verticalScale
SizeBehavior fullScale
SizeBehavior horizontalShrink
SizeBehavior verticalShrink
SizeBehavior fullShrink

horizontalScale:SetDrawable(triangle)
verticalScale:SetDrawable(triangle)
fullScale:SetDrawable(triangle)
horizontalShrink:SetDrawable(triangle)
verticalShrink:SetDrawable(triangle)
fullShrink:SetDrawable(triangle)

horizontalScale:SetScale(1.25, 1.00)
verticalScale:SetScale(1.00, 1.25)
fullScale:SetScale(1.25, 1.25)
horizontalShrink:SetScale(0.75, 1.00)
verticalShrink:SetScale(1.00, 0.75)
fullShrink:SetScale(0.75, 0.75)

header1_1_1:SetBehavior(horizontalScale)
header1_1_2:SetBehavior(verticalScale)
header1_1_3:SetBehavior(fullScale)
header1_2_1:SetBehavior(horizontalShrink)
header1_2_2:SetBehavior(verticalShrink)
header1_2_3:SetBehavior(fullShrink)

Creating Headers with AddMenuItem

In addition to the Arrays method from the last section, we can also use the AddMenuItem action to create submenus. This allows us to use just one Initialize action, specifically the one with only a text parameter. We will be creating our second header and its submenus using this method. Below are our declarations of the MenuItems:

MenuItem header2        //rotation
MenuItem header2_1          //clockwise rotation
MenuItem header2_1_1            //30 degrees
MenuItem header2_1_2            //45 degrees
MenuItem header2_1_3            //90 degrees
MenuItem header2_2          //counter-clockwise rotation
MenuItem header2_2_1            //30 degrees
MenuItem header2_2_2            //45 degrees
MenuItem header2_2_3            //90 degrees

We only need to supply the name when initializing our MenuItems this time, since we’ll be adding them to one another afterwards. This gives us the following Initialize calls:

header2:Initialize("Rotation")
header2_1:Initialize("Clockwise")
header2_1_1:Initialize("30 Degrees")
header2_1_2:Initialize("45 Degrees")
header2_1_3:Initialize("90 Degrees")
header2_2:Initialize("Counter-Clockwise")
header2_2_1:Initialize("30 Degrees")
header2_2_2:Initialize("45 Degrees")
header2_2_3:Initialize("90 Degrees")

Next, to add the MenuItems to one another, we will use the AddMenuItem action as follows:

header2:AddMenuItem(header2_1)
header2:AddMenuItem(header2_2)

header2_1:AddMenuItem(header2_1_1)
header2_1:AddMenuItem(header2_1_2)
header2_1:AddMenuItem(header2_1_3)

header2_2:AddMenuItem(header2_2_1)
header2_2:AddMenuItem(header2_2_2)
header2_2:AddMenuItem(header2_2_3)

Now, we need another Behavior for this header’s rotation operations. To start, create a new Quorum class. We will name this class "RotationBehavior.quorum." We will need the libraries for Behavior, BehaviorEvent, Drawable, and Speech, requiring the following use statements:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Sound.Speech

In addition, the RotationBehavior class itself will inherit the Behavior class. We will need SetDrawable and SetDegrees actions to set class variables, and will override the Run action, giving us the following template:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Sound.Speech

class RotationBehavior is Behavior

    Drawable triangle = undefined
    number degrees = 0.0

    action Run(BehaviorEvent behavior)
    end

    action SetDrawable(Drawable newTriangle)
        triangle = newTriangle
    end

    action SetDegrees(number newDegrees)
        degrees = newDegrees
    end
end

In the Run action, we will again check if our Drawable is undefined, and rotate it appropriately if it is defined using the Rotate action. Then we will use the Speech class to say which rotation was done, giving us the following Run action:

action Run(BehaviorEvent behavior)
    if triangle not= undefined
        triangle:Rotate(degrees)

        Speech speech
        speech:Say("Rotated " + degrees + " degrees.")
    end
end

This gives the full RotationBehavior class as follows:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Sound.Speech

class RotationBehavior is Behavior

    Drawable triangle = undefined
    number degrees = 0.0

    action Run(BehaviorEvent behavior)
        if triangle not= undefined
            triangle:Rotate(degrees)

            Speech speech
            speech:Say("Rotated " + degrees + " degrees.")
        end
    end

    action SetDrawable(Drawable newTriangle)
        triangle = newTriangle
    end

    action SetDegrees(number newDegrees)
        degrees = newDegrees
    end
end

Returning to the main class, now we need to create a RotationBehavior variable for each of our final submenus, call the SetDrawable and SetDegrees actions, and set the Behaviors to our MenuItems. This is done with the following lines:

RotationBehavior clockwise30
RotationBehavior clockwise45
RotationBehavior clockwise90
RotationBehavior counter30
RotationBehavior counter45
RotationBehavior counter90

clockwise30:SetDrawable(triangle)
clockwise45:SetDrawable(triangle)
clockwise90:SetDrawable(triangle)
counter30:SetDrawable(triangle)
counter45:SetDrawable(triangle)
counter90:SetDrawable(triangle)

clockwise30:SetDegrees(30)
clockwise45:SetDegrees(45)
clockwise90:SetDegrees(90)
counter30:SetDegrees(-30)
counter45:SetDegrees(-45)
counter90:SetDegrees(-90)

header2_1_1:SetBehavior(clockwise30)
header2_1_2:SetBehavior(clockwise45)
header2_1_3:SetBehavior(clockwise90)
header2_2_1:SetBehavior(counter30)
header2_2_2:SetBehavior(counter45)
header2_2_3:SetBehavior(counter90)

Our third header will have the reflection operations, both horizontal and vertical. To start, create a new Quorum class for the Behavior, which we will name "ReflectionBehavior.quorum." This class itself will inherit the Behavior class. It will also override the Run action, will have actions to set the Drawable and axis of reflection, and will use the libraries for Behavior, BehaviorEvent, Drawable, and Speech, giving the following template:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Sound.Speech

class ReflectionBehavior is Behavior

    Drawable triangle = undefined
    text axis = undefined

    action Run(BehaviorEvent behavior)
    end

    action SetDrawable(Drawable newTriangle)
        triangle = newTriangle
    end

    action SetReflection(text newAxis)
        axis = newAxis
    end
end

In the Run action, we will check that the Drawable is defined, and use a conditional if statement to check whether the SetReflection action set the axis variable to "X" or "Y," calling the Drawable class’s FlipX or FlipY action, as appropriate. We will also use the Speech class to say the operation aloud. This gives us the following Run action:

action Run(BehaviorEvent behavior)
    if triangle not= undefined
        Speech speech

        if axis = "X"
            triangle:FlipX()
            speech:Say("Horizontal reflection.")
        elseif axis = "Y"
            triangle:FlipY()
             speech:Say("Vertical reflection.")
        end
    end
end

Thus, the ReflectionBehavior class is as follows:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Sound.Speech

class ReflectionBehavior is Behavior

    Drawable triangle = undefined
    text axis = undefined

    action Run(BehaviorEvent behavior)
        if triangle not= undefined
            Speech speech

            if axis = "X"
                triangle:FlipX()
                speech:Say("Horizontal reflection.")
            elseif axis = "Y"
                triangle:FlipY()
                speech:Say("Vertical reflection.")
            end
        end
    end    action SetDrawable(Drawable newTriangle)
        triangle = newTriangle
    end

    action SetReflection(text newAxis)
        axis = newAxis
    end
end

Returning to the main class, we can now create our MenuItems for the third header, Initialize them, and give them the ReflectionBehavior. This adds the following code:

MenuItem header3        //reflection
MenuItem header3_1          //horizontal rotation
MenuItem header3_2          //vertical rotation

header3:Initialize("Reflection")
header3_1:Initialize("Horizontal Reflection")
header3_2:Initialize("Vertical Reflection")

header3:AddMenuItem(header3_1)
header3:AddMenuItem(header3_2)

ReflectionBehavior horizontalReflection
ReflectionBehavior verticalReflection

horizontalReflection:SetDrawable(triangle)
verticalReflection:SetDrawable(triangle)

horizontalReflection:SetReflection("X")
verticalReflection:SetReflection("Y")

header3_1:SetBehavior(horizontalReflection)
header3_2:SetBehavior(verticalReflection)

Since we have a few headers now, let’s go ahead and create our MenuBar. The MenuBar can be Initialized with an Array of MenuItems, so we’ll use this method. Then we need to add our MenuBar to the game and call the MenuBar’s Resize action to fit the Menu properly at the top of the game window. This is done with the following lines of code:

MenuBar menu

Array  headers
headers:Add(header1)
headers:Add(header2)
headers:Add(header3)

menu:Initialize(headers)
Add(menu)
menu:Resize()

Now, when we run the program, our three headers appear on a MenuBar at the top of the game window, and each affects the Drawable in the intended way.

Next, let’s add the last two headers, one for changing the Color and one to reset the triangle. To start, create a new Quorum class for the Color changing Behavior, which we will call "ColorBehavior.quorum." We will use the Behavior, BehaviorEvent, Drawable, Color, and Speech libraries, and the class itself will inherit the Behavior class, override the Run action, and add actions to set the Drawable and Color. This gives us the following template:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Game.Graphics.Color
use Libraries.Sound.Speech

class ColorBehavior is Behavior

    Drawable triangle = undefined
    Color color = undefined
    text colorName = undefined

    action Run(BehaviorEvent behavior)
    end

    action SetDrawable(Drawable newTriangle)
        triangle = newTriangle
    end

    action SetColor(Color newColor, text newName)
        color = newColor
        colorName = newName
    end
end

The Run action will check that the triangle, color, and colorName class variables are all defined, and then call the Drawable’s SetColor action and use the Speech class to say the color’s name, giving us the following code:

action Run(BehaviorEvent behavior)
    if triangle not= undefined and color not= undefined and colorName not= undefined
        triangle:SetColor(color)

        Speech speech
        speech:Say("Color changed to " + colorName + ".")
    end
end

Thus, our ColorBehavior class is as follows:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Game.Graphics.Color
use Libraries.Sound.Speech

class ColorBehavior is Behavior

    Drawable triangle = undefined
    Color color = undefined
    text colorName = undefined

    action Run(BehaviorEvent behavior)
        if triangle not= undefined and color not= undefined and colorName not= undefined
            triangle:SetColor(color)

            Speech speech
            speech:Say("Color changed to " + colorName + ".")
        end
    end

    action SetDrawable(Drawable newTriangle)
        triangle = newTriangle
    end

    action SetColor(Color newColor, text newName)
        color = newColor
        colorName = newName
    end
end

Returning again to the main class, we will create MenuItems for the fourth header, Initialize them, and assign them Behaviors, in the same manner as before, giving the following code:

MenuItem header4        //color
MenuItem header4_1          //red
MenuItem header4_2          //orange
MenuItem header4_3          //yellow
MenuItem header4_4          //green
MenuItem header4_5          //blue
MenuItem header4_6          //purple

header4:Initialize("Color")
header4_1:Initialize("Red")
header4_2:Initialize("Orange)
header4_3:Initialize("Yellow")
header4_4:Initialize("Green")
header4_5:Initialize("Blue")
header4_6:Initialize(Purple)

header4:AddMenuItem(header4_1)
header4:AddMenuItem(header4_2)
header4:AddMenuItem(header4_3)
header4:AddMenuItem(header4_4)
header4:AddMenuItem(header4_5)
header4:AddMenuItem(header4_6)

ColorBehavior redBehavior
ColorBehavior orangeBehavior
ColorBehavior yellowBehavior
ColorBehavior greenBehavior
ColorBehavior blueBehavior
ColorBehavior purpleBehavior

redBehavior:SetDrawable(triangle)
orangeBehavior:SetDrawable(triangle)
yellowBehavior:SetDrawable(triangle)
greenBehavior:SetDrawable(triangle)
blueBehavior:SetDrawable(triangle)
purpleBehavior:SetDrawable(triangle)

Color color

redBehavior:SetColor(color:Red(), "Red")
orangeBehavior:SetColor(color:Orange(), "Orange")
yellowBehavior:SetColor(color:Yellow(), "Yellow")
greenBehavior:SetColor(color:Green(), "Green")
blueBehavior:SetColor(color:Blue(), "Blue")
purpleBehavior:SetColor(color:Purple(), "Purple")

header4_1:SetBehavior(redBehavior)
header4_2:SetBehavior(orangeBehavior)
header4_3:SetBehavior(yellowBehavior)
header4_4:SetBehavior(greenBehavior)
header4_5:SetBehavior(blueBehavior)
header4_6:SetBehavior(purpleBehavior)

This time, however, our MenuBar has already been Initialized. Thus, we must instead call the MenuBar’s AddMenuItem action to add the fourth header, and then call the Resize action again, giving us the following lines:

menu:AddMenuItem(header4)
menu:Resize()

Now we’ll add the final header, which will reset the size, rotation, reflection, and color of the triangle. To start, make another new Quorum class, which we have will call "ResetBehavior.quorum." We will use the libraries for Behavior, BehaviorEvent, Drawable, and Speech, and the class itself will inherit the Behavior class, overriding the Run action and having an action to set the Drawable. This gives the following template:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Sound.Speech

class ResetBehavior is Behavior

    Drawable triangle = undefined
    text axis = undefined

    action Run(BehaviorEvent behavior)
    end

    action SetDrawable(Drawable newTriangle)
        triangle = newTriangle
    end
end

The Run action will check that triangle has been defined by the SetDrawable action, as usual, and will then reset it to the original properties. This is most simply done by calling the same LoadFilledTriangle action we used in the main class, and then we will use the Speech class to announce the reset. This gives the following Run action:

action Run(BehaviorEvent behavior)
    if triangle not= undefined
        triangle:LoadFilledTriangle(0, 0, 150, 80, 200, 25)

        Speech speech
        speech:Say("Reset")
    end
end

Thus, our ResetBehavior class is as follows:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Sound.Speech

class ResetBehavior is Behavior

    Drawable triangle = undefined
    text axis = undefined

    action Run(BehaviorEvent behavior)
        if triangle not= undefined
            triangle:LoadFilledTriangle(0, 0, 150, 80, 200, 25)

            Speech speech
            speech:Say("Reset")
        end
    end

    action SetDrawable(Drawable newTriangle)
        triangle = newTriangle
    end
end

Returning to the main for the last time, we need to create MenuItems for the fifth header and add the ResetBehavior. Then we just need to add the header to the MenuBar and call the MenuBar’s Resize action again, giving the following code:

MenuItem header5        //reset
MenuItem header5_1          //reset all

header5:Initialize("Reset")
header5_1:Initialize("Reset All")

header5:AddMenuItem(header5_1)

ResetBehavior resetBehavior
resetBehavior:SetDrawable(triangle)
header5:SetBehavior(resetBehavior)

menu:AddMenuItem(header5)
menu:Resize()

The complete main class is as follows:

use Libraries.Game.Game
use Libraries.Interface.Controls.MenuBar
use Libraries.Interface.Controls.MenuItem
use Libraries.Containers.Array
use Libraries.Game.Graphics.Drawable
use Libraries.Game.Graphics.Color

class Main is Game

    action Main
        StartGame()
    end

    action CreateGame
        Drawable triangle
        triangle:LoadFilledTriangle(0, 0, 150, 80, 200, 25)
        triangle:SetPosition(100, 100)
        Add(triangle)

        //We declare the MenuItems for header1
        MenuItem header1        //size
        MenuItem header1_1          //increase size
        MenuItem header1_1_1            //horizontal
        MenuItem header1_1_2            //vertical
        MenuItem header1_1_3            //both
        MenuItem header1_2          //decrease size
        MenuItem header1_2_1            //horizontal
        MenuItem header1_2_2            //vertical
        MenuItem header1_2_3            //both

        Array  head1
        Array  head1_1
        Array  head1_2

        //We add the header1 MenuItems to the correct Array
        head1:Add(header1_1)
        head1:Add(header1_2)
        head1_1:Add(header1_1_1)
        head1_1:Add(header1_1_2)
        head1_1:Add(header1_1_3)
        head1_2:Add(header1_2_1)
        head1_2:Add(header1_2_2)
        head1_2:Add(header1_2_3)

        //We Initialize our MenuItems, creating submenus with our Arrays
        header1:Initialize("Size", head1)
        header1_1:Initialize("Increase Size", head1_1)
        header1_2:Initialize("Decrease Size", head1_2)

        header1_1_1:Initialize("Horizontal Scale")
        header1_1_2:Initialize("Vertical Scale")
        header1_1_3:Initialize("Full Scale")

        header1_2_1:Initialize("Horizontal Shrink")
        header1_2_2:Initialize("Vertical Shrink")
        header1_2_3:Initialize("Full Shrink")

        //We set our Behaviors for each lowest-level MenuItem
        SizeBehavior horizontalScale
        SizeBehavior verticalScale
        SizeBehavior fullScale
        SizeBehavior horizontalShrink
        SizeBehavior verticalShrink
        SizeBehavior fullShrink

        horizontalScale:SetDrawable(triangle)
        verticalScale:SetDrawable(triangle)
        fullScale:SetDrawable(triangle)
        horizontalShrink:SetDrawable(triangle)
        verticalShrink:SetDrawable(triangle)
        fullShrink:SetDrawable(triangle)

        horizontalScale:SetScale(1.25, 1.00)
        verticalScale:SetScale(1.00, 1.25)
        fullScale:SetScale(1.25, 1.25)
        horizontalShrink:SetScale(0.75, 1.00)
        verticalShrink:SetScale(1.00, 0.75)
        fullShrink:SetScale(0.75, 0.75)

        header1_1_1:SetBehavior(horizontalScale)
        header1_1_2:SetBehavior(verticalScale)
        header1_1_3:SetBehavior(fullScale)
        header1_2_1:SetBehavior(horizontalShrink)
        header1_2_2:SetBehavior(verticalShrink)
        header1_2_3:SetBehavior(fullShrink)

        //We move on to header2 now

        //We declare the MenuItems for header2
        MenuItem header2        //rotation
        MenuItem header2_1          //clockwise rotation
        MenuItem header2_1_1            //30 degrees
        MenuItem header2_1_2            //45 degrees
        MenuItem header2_1_3            //90 degrees
        MenuItem header2_2          //counter-clockwise rotation
        MenuItem header2_2_1            //30 degrees
        MenuItem header2_2_2            //45 degrees
        MenuItem header2_2_3            //90 degrees

        //We Initialize the MenuItems for header2 with names
        header2:Initialize("Rotation")
        header2_1:Initialize("Clockwise")
        header2_1_1:Initialize("30 Degrees")
        header2_1_2:Initialize("45 Degrees")
        header2_1_3:Initialize("90 Degrees")
        header2_2:Initialize("Counter-Clockwise")
        header2_2_1:Initialize("30 Degrees")
        header2_2_2:Initialize("45 Degrees")
        header2_2_3:Initialize("90 Degrees")

        //We create the submenus for header2 using AddMenuItem
        header2:AddMenuItem(header2_1)
        header2:AddMenuItem(header2_2)

        header2_1:AddMenuItem(header2_1_1)
        header2_1:AddMenuItem(header2_1_2)
        header2_1:AddMenuItem(header2_1_3)

        header2_2:AddMenuItem(header2_2_1)
        header2_2:AddMenuItem(header2_2_2)
        header2_2:AddMenuItem(header2_2_3)

        //We add our Behaviors to each lowest-level MenuItem of header2
        RotationBehavior clockwise30
        RotationBehavior clockwise45
        RotationBehavior clockwise90
        RotationBehavior counter30
        RotationBehavior counter45
        RotationBehavior counter90

        clockwise30:SetDrawable(triangle)
        clockwise45:SetDrawable(triangle)
        clockwise90:SetDrawable(triangle)
        counter30:SetDrawable(triangle)
        counter45:SetDrawable(triangle)
        counter90:SetDrawable(triangle)

        clockwise30:SetDegrees(30)
        clockwise45:SetDegrees(45)
        clockwise90:SetDegrees(90)
        counter30:SetDegrees(-30)
        counter45:SetDegrees(-45)
        counter90:SetDegrees(-90)

        header2_1_1:SetBehavior(clockwise30)
        header2_1_2:SetBehavior(clockwise45)
        header2_1_3:SetBehavior(clockwise90)
        header2_2_1:SetBehavior(counter30)
        header2_2_2:SetBehavior(counter45)
        header2_2_3:SetBehavior(counter90)

        //We move on to header3 now

        //We declare the MenuItems for header3
        MenuItem header3        //reflection
        MenuItem header3_1          //horizontal rotation
        MenuItem header3_2          //vertical rotation

        //We Initialize each MenuItem for header3 with names
        header3:Initialize("Reflection")
        header3_1:Initialize("Horizontal Reflection")
        header3_2:Initialize("Vertical Reflection")

        //We create submenus for header3 using AddMenuItem
        header3:AddMenuItem(header3_1)
        header3:AddMenuItem(header3_2)

        //We add our Behaviors to each lowest-level MenuItem for header3
        ReflectionBehavior horizontalReflection
        ReflectionBehavior verticalReflection

        horizontalReflection:SetDrawable(triangle)
        verticalReflection:SetDrawable(triangle)

        horizontalReflection:SetReflection("X")
        verticalReflection:SetReflection("Y")

        header3_1:SetBehavior(horizontalReflection)
        header3_2:SetBehavior(verticalReflection)

        //We Initialize each MenuItem for header3 with names
        MenuBar menu

        Array  headers
        headers:Add(header1)
        headers:Add(header2)
        headers:Add(header3)

        menu:Initialize(headers)
        Add(menu)
        menu:Resize()

        //We move on to header4 now

        //We declare the MenuItems for header4
        MenuItem header4        //color
        MenuItem header4_1          //red
        MenuItem header4_2          //orange
        MenuItem header4_3          //yellow
        MenuItem header4_4          //green
        MenuItem header4_5          //blue
        MenuItem header4_6          //purple

        //We Initialize each MenuItem with a name
        header4:Initialize("Color")
        header4_1:Initialize("Red")
        header4_2:Initialize("Orange)
        header4_3:Initialize("Yellow")
        header4_4:Initialize("Green")
        header4_5:Initialize("Blue")
        header4_6:Initialize(Purple)

        //We create submenus for header4 by using AddMenuItem
        header4:AddMenuItem(header4_1)
        header4:AddMenuItem(header4_2)
        header4:AddMenuItem(header4_3)
        header4:AddMenuItem(header4_4)
        header4:AddMenuItem(header4_5)
        header4:AddMenuItem(header4_6)

        ColorBehavior redBehavior
        ColorBehavior orangeBehavior
        ColorBehavior yellowBehavior
        ColorBehavior greenBehavior
        ColorBehavior blueBehavior
        ColorBehavior purpleBehavior

        redBehavior:SetDrawable(triangle)
        orangeBehavior:SetDrawable(triangle)
        yellowBehavior:SetDrawable(triangle)
        greenBehavior:SetDrawable(triangle)
        blueBehavior:SetDrawable(triangle)
        purpleBehavior:SetDrawable(triangle)

        Color color

        redBehavior:SetColor(color:Red(), "Red")
        orangeBehavior:SetColor(color:Orange(), "Orange")
        yellowBehavior:SetColor(color:Yellow(), "Yellow")
        greenBehavior:SetColor(color:Green(), "Green")
        blueBehavior:SetColor(color:Blue(), "Blue")
        purpleBehavior:SetColor(color:Purple(), "Purple")

        header4_1:SetBehavior(redBehavior)
        header4_2:SetBehavior(orangeBehavior)
        header4_3:SetBehavior(yellowBehavior)
        header4_4:SetBehavior(greenBehavior)
        header4_5:SetBehavior(blueBehavior)
        header4_6:SetBehavior(purpleBehavior)

        //We add header4 to our MenuBar
        menu:AddMenuItem(header4)
        menu:Resize()

        //We move on to header5 now

        //We declare the MenuItems for header5
        MenuItem header5        //reset
        MenuItem header5_1          //reset all

        //We Initialize each MenuItem with a name
        header5:Initialize("Reset")
        header5_1:Initialize("Reset All")

        //We create submenus for header5 using AddMenuItem
        header5:AddMenuItem(header5_1)

        //We add Behaviors to each lowest-level MenuItem for header5
        ResetBehavior resetBehavior
        resetBehavior:SetDrawable(triangle)
        header5:SetBehavior(resetBehavior)

        //We add header5 to our MenuBar
        menu:AddMenuItem(header5)
        menu:Resize()
    end

    action Update(number seconds)
    end
end

MenuSelections

In Quorum, Menus use their own selection class, MenuSelection, to keep track of information regarding the currently selected MenuItem. In particular, the MenuSelection keeps track of the unique and specific path to the current selection, causing another similarity between Trees and Menus. This path is stored in the form of an Array of MenuItems. To demonstrate this, we will add a small amount of code to each of our Behavior classes we created in the tutorial thus far.

To start, we will need the libraries for MenuItem, MenuBar, MenuSelection, and Array, adding the following use statements:

use Libraries.Interface.Controls.MenuItem
use Libraries.Interface.Controls.MenuBar
use Libraries.Interface.Selections.MenuSelection
use Libraries.Containers.Array

Next, we will add to the end of the Run action, outside of the conditional if statement. First, we need to obtain the currently selected MenuItem. This can be done with BehaviorEvent’s GetItem action, but this returns an Item, not a MenuItem, so we will need to cast it. This is done with the following line:

MenuItem tempItem = cast(MenuItem, behavior:GetItem())

With the current MenuItem, we can obtain the MenuBar it belongs to with the GetMenuBar action. Next, we can then obtain the MenuSelection with the MenuBar’s GetSelection action. Finally, after obtaining the MenuSelection, we can use its GetPath action to get the Array of MenuItems. This gives the following lines:

MenuBar bar = tempItem:GetMenuBar()
MenuSelection selection = bar:GetSelection()

Array items = selection:GetPath()

Now that we have the path to the currently selected MenuItem, we can traverse the Array from the beginning to the end to obtain the full path. We will simply output this as text, so we will use each MenuItem’s GetName action as we go through the Array in a repeat while loop. This is gives the following code:

integer counter = 0
integer size = items:GetSize()
text path = ""
repeat while counter < size
    path = path + items:Get(counter):GetName() + "\"
    counter = counter + 1
end

output path

As an example, the updated SizeBehavior class is as follows:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Sound.Speech

class SizeBehavior is Behavior

    Drawable triangle = undefined
    number scaleX = 1.0
    number scaleY = 1.0

    action Run(BehaviorEvent behavior)
        if triangle not= undefined
            triangle:SetScale(scaleX, scaleY)

            Speech speech
            speech:Say("Scaled by a factor of " + scaleX + " horizontally, and a factor of " + scaleY + "vertically.")
        end

        MenuBar bar = tempItem:GetMenuBar()
        MenuSelection selection = bar:GetSelection()

        Array items = selection:GetPath()

        integer counter = 0
        integer size = items:GetSize()
        text path = ""
        repeat while counter < size
            path = path + items:Get(counter):GetName() + "\"
            counter = counter + 1
        end

        output path
    end

    action SetDrawable(Drawable newTriangle)
        triangle = newTriangle
    end

    action SetScale(number newScaleX, number newScaleY)
        scaleX = newScaleX
        scaleY = newScaleY
    end
end

Now, when the SizeBehavior is run, it will output the path to the currently selected MenuItem. This change is done the same way for all five Behavior classes we made, so the use statements and addition to the Run action can simply be copied into the other classes.

Next Tutorial

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