An Introduction to ScrollPanes in Quorum

In this tutorial, we will learn how to use ScrollPanes in the Quorum Game Engine. ScrollPanes are interface tools which use a scrollbar to display only a portion of large region. For example, unless you are zoomed out, the webpage for this tutorial is a ScrollPane with a vertical scrollbar, and can be scrolled up or down to display different parts of the page.

For this tutorial, we will create a ScrollPane that only displays a small portion of a much larger region, with Buttons located at the center and each corner of the region. Thus, the user must navigate the region using the vertical and horizontal scrollbars to find each Button. To start, create a new Game Application project.

Creating a ScrollPane

Before we begin with the main class, we will create a Behavior class for our Buttons, which we will call "ButtonBehavior.quorum." This class will use the libraries for Behavior, BehaviorEvent, and Speech, requiring the following statements:
use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Sound.Speech

The ButtonBehavior class itself will inherit the Behavior class, and will use a class variable to keep track of the Button's location, which is set by a SetLocation action. This gives us the following template for the class:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Sound.Speech

class ButtonBehavior is Behavior

    text location = undefined

    action SetLocation(text currentLocation)
        location = currentLocation
    end

    action Run(BehaviorEvent event)
    end
end

In the Run action, we need to have the program output the location and say it aloud. This is done with the following:

action Run(BehaviorEvent event)
    if location not= undefined
        Speech speech
        speech:Say(location)
        output location
    end
end

This gives us the completed ButtonBehavior class as follows:

use Libraries.Interface.Behaviors.Behavior
use Libraries.Interface.Events.BehaviorEvent
use Libraries.Sound.Speech

class ButtonBehavior is Behavior

    text location = undefined

    action SetLocation(text currentLocation)
        location = currentLocation
    end

    action Run(BehaviorEvent event)
        if location not= undefined
            Speech speech
            speech:Say(location)
            output location
        end
    end
end

Now we can return to our main class. In order to use a ScrollPane, we must include the ScrollPane library. In additional, we will need the libraries for Color, Drawable, and Button, requiring the following statements:

use Libraries.Game.Game
use Libraries.interface.Controls.ScrollPane
use Libraries.Game.Graphics.Color
use Libraries.Game.Graphics.Drawable
use Libraries.Interface.Controls.Button

Since we want the ScrollPane to be usable as soon as the game begins, we'll add our code to the CreateGame class. To start, we'll simply declare the variables we'll be using. We will need a ScrollPane, a Color variable, and five Buttons, giving us the following declarations:

ScrollPane scrollpane
Color color
Button topLeft
Button topRight
Button center
Button bottomLeft
Button bottomRight

Before we add our Buttons to the ScrollPane, we'll Initialize them and give them Behaviors. This is done for the topLeft Button as follows:

topLeft:Initialize(100, 30, "Top Left")
ButtonBehavior topLeftBehavior
topLeftBehavior:SetLocation("Top Left")
topLeft:SetBehavior(topLeftBehavior)

Next, we need to do the same thing for each remaining Button, changing the variable names and text to match the situation. This gives us the following:

topRight:Initialize(100, 30, "Top Right")
ButtonBehavior topRightBehavior
topRightBehavior:SetLocation("Top Right")
topRight:SetBehavior(topRightBehavior)

center:Initialize(100, 30, "Center")
ButtonBehavior centerBehavior
center:SetBehavior(centerBehavior)

bottomLeft:Initialize(100, 30, "Bottom Left")
ButtonBehavior bottomLeftBehavior
bottomLeftBehavior:SetLocation("Bottom Left")
bottomLeft:SetBehavior(bottomLeftBehavior)

bottomRight:Initialize(100, 30, "Bottom Right")
ButtonBehavior bottomRightBehavior
bottomRightBehavior:SetLocation("Bottom Right")
bottomRight:SetBehavior(bottomRightBehavior)

Now that our Buttons are created, we can add them to the ScrollPane with the following code:

scrollpane:Add(topLeft)
scrollpane:Add(topRight)
scrollpane:Add(center)
scrollpane:Add(bottomLeft)
scrollpane:Add(bottomRight)

Next, we need to Initialize our ScrollPane and set its position. The Initialize action call for ScrollPanes requires a number for width, a number for height, and a Color for the background. This done with the following lines:

scrollpane:Initialize(600, 400, color:Gray())
scrollpane:SetPosition(100, 100)

Finally, we need to set the position of each Button within the ScrollPane. Because we added each Button to the ScrollPane, that these positions are relative to the ScrollPane, not the game window. For example, since we set the ScrollPane's position to the (x, y) coordinate of (100, 100), calling "bottomLeft:SetPosition(0, 0)" will place the Button at the relative position of (0, 0) on the ScrollPane, which is at the absolute position of (100, 100) on the game window. Our Button positions are as follows:

topLeft:SetPosition(0, 2000 - 30)
topRight:SetPosition(2000 - 100, 2000 - 30)
center:SetPosition((2000 / 2) - 50, (2000 / 2) - 15)
bottomLeft:SetPosition(0, 0)
bottomRight:SetPosition(2000 - 100, 0)

Next, we will call the FitRegionToContents action to automatically fit the region to match what we added to it. For example, we used "topRight:SetPosition(2000 - 100, 2000 - 30)" to place our topRight Button. Since the Button has a width of 100 and a height of 30, this makes the Button extend to the (x, y) coordinate of (2000, 2000). So, FitRegionToContents will make the region for the ScrollPane extend to (2000, 2000). The following block of code calls FitRegionToContents and adds the ScrollPane to the game:

scrollpane:FitRegionToContents()
Add(scrollpane)

The full main class is as follows:

use Libraries.Game.Game
use Libraries.Interface.Controls.ScrollPane
use Libraries.Game.Graphics.Color
use Libraries.Game.Graphics.Drawable
use Libraries.Interface.Controls.Button

class Main is Game

    action Main
        StartGame()
    end

    action CreateGame
        ScrollPane scrollpane
        Color color
        Button topLeft
        Button topRight
        Button center
        Button bottomLeft
        Button bottomRight

        topLeft:Initialize(100, 30, "Top Left")
        ButtonBehavior topLeftBehavior
        topLeftBehavior:SetLocation("Top Left")
        topLeft:SetBehavior(topLeftBehavior)

        topRight:Initialize(100, 30, "Top Right")
        ButtonBehavior topRightBehavior
        topRightBehavior:SetLocation("Top Right")
        topRight:SetBehavior(topRightBehavior)

        center:Initialize(100, 30, "Center")
        ButtonBehavior centerBehavior
        center:SetBehavior(centerBehavior)

        bottomLeft:Initialize(100, 30, "Bottom Left")
        ButtonBehavior bottomLeftBehavior
        bottomLeftBehavior:SetLocation("Bottom Left")
        bottomLeft:SetBehavior(bottomLeftBehavior)

        bottomRight:Initialize(100, 30, "Bottom Right")
        ButtonBehavior bottomRightBehavior
        bottomRightBehavior:SetLocation("Bottom Right")
        bottomRight:SetBehavior(bottomRightBehavior)

        scrollpane:Add(topLeft)
        scrollpane:Add(topRight)
        scrollpane:Add(center)
        scrollpane:Add(bottomLeft)
        scrollpane:Add(bottomRight)

        scrollpane:Initialize(600, 400, color:Gray())
        scrollpane:SetPosition(100, 100)

        topLeft:SetPosition(0, 2000 - 30)
        topRight:SetPosition(2000 - 100, 2000 - 30)
        center:SetPosition((2000 / 2) - 50, (2000 / 2) - 15)
        bottomLeft:SetPosition(0, 0)
        bottomRight:SetPosition(2000 - 100, 0)
        scrollpane:FitRegionToContents()
        Add(scrollpane)
    end

    action Update(number seconds)
    end
end

When the program is run, it has a ScrollPane showing only a 600 x 400 portion of the 2000 x 2000 region. Thus, the scrollbars can be used to display other parts of the 2000 x 2000 region. For example, below is an image of the ScrollPane at the top left of the region and near the center of the region. When a Button is activated, the program will output and say its location aloud.

This image shows the starting point of the ScrollPane. With the This image shows the ScrollPane with the Center Button visible in the middle of the screen.

Manually Setting the Region Size

Suppose we wanted to move the Buttons closer together now. We can move the Buttons again by simply calling the SetPosition action again, using the following lines:

topLeft:SetPosition(0, 500 - 30)
topRight:SetPosition(500 - 100, 500 - 30)
center:SetPosition((500 / 2) - 50, (500 / 2) - 15)
bottomLeft:SetPosition(0, 0)
bottomRight:SetPosition(500 - 100, 0)

By using 500 in our calculations instead of 2000, we've shrunk down the area containing all five buttons by a factor of 4. If we were to run the program now, the vast majority of the region for our ScrollPane would be unnecessary. As such, it is reasonable to shrink down the region itself to match our Buttons. Fortunately, Quorum allows you to manually set the region size, using the ScrollPane's SetRegionSize action with a width and height. Thus, we will add the following line:

scrollpane:SetRegionSize(500, 500)

However, recall that when we Initialized our ScrollPane, we gave it the view area of 600 x 400. We cannot view more area than exists for our ScrollPane, so we need to change the view area as well. Fortunately, this is done by simply calling the Initialize action again, giving us the following line:

scrollpane:Initialize(400, 300, color:Gray())

Lastly, to fit the ScrollPane to the middle of our game window, we will add the following line of code:

scrollpane:SetPosition(200, 150)

Now, when we run the program, the area is much smaller, as shown in the following image:

This image shows a smaller version of the ScrollPane with the TopLeft and Center buttons visable.

Indicies

In Quorum, ScrollPanes have an additional tracking feature: indices. An index is simply an integer, starting from 0, associated with an object added to the ScrollPane. In the previous section, we added our Buttons to the ScrollPane without indicies, which simply gives an index in the order that objects were added. For example, since topLeft was the first thing we added, it has the index value of 0. Similarly, bottomLeft was the fourth thing we added, so it has the index value of 3. We could have also used the Add action with an index specified, in the form of "scrollpane:Add(0, topLeft)." For example, the following Add actions would have given us the same indicies as the section above:

scrollpane:Add(0, topLeft)
scrollpane:Add(1, topRight)
scrollpane:Add(2, center)
scrollpane:Add(3, bottomLeft)
scrollpane:Add(4, bottomRight)

However, note that using the Add action this way must either use the very next index value, or a previous value, otherwise it will crash the program. For instance, starting with "scrollpane:Add(1, topLeft)" instead would crash, as would finishing with "scrollpane:Add(5, bottomRight)."

As for specifying a previously assigned index, the new object will push the preexisting object, and everything following it, up by 1. For example, consider the following Add actions:

scrollpane:Add(0, topLeft)
scrollpane:Add(0, topRight)
scrollpane:Add(1, center)
scrollpane:Add(0, bottomLeft)
scrollpane:Add(2, bottomRight)

In this case, the order of events is as follows:

  1. topLeft is assigned to index 0.
  2. topRight is assigned to index 0, and topLeft is pushed from index 0 to index 1.
  3. center is assigned to index 1, and topLeft is pushed from index 1 to index 2.
  4. bottomLeft is assigned to index 0, topRight is pushed from index 0 to index 1, center is pushed from index 1 to index 2, and topLeft is pushed from index 2 to index 3.
  5. bottomRight is assigned to index 2, center is pushed from index 2 to index 3, and topLeft is pushed from index 3 to index 4.

Final Indicies are:

In particular, indicies are used to remove objects from ScrollPanes. For example, if we wanted to Remove the Center Button using the previous example, we could either use "scrollpane:Remove(center)" or "scrollpane:Remove(3)." While we just call the Remove action like this, note that the Remove action with an Item parameter will return a boolean, True if the item was in the ScrollPane and successfully removed or False if the item was not in the ScrollPane. The Remove action with an integer parameter will return the Item that was previously contained at that index.

The action of removing an object also has an impact on the indicies of other objects, though in this case, it will change the following indicies by 1 to fill in the space left by the removed object. Using the previous example again, consider the following Removes:

scrollpane:Remove(3)
scrollpane:Remove(0)

In this case, the order of events is as follows:

  1. center (index 3) is removed, and topLeft is pulled down from index 4 to index 3.
  2. bottomLeft (index 0) is removed, topRight is pulled down from index 1 to index 0, bottomRight is pulled down from index 2 to index 1, and topLeft is pulled down from index 3 to index 2.

Final Indicies are:

Next Tutorial

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