An Introduction to TabPanes in Quorum
What is a Tab Pane?
In this tutorial, we will learn how to use TabPanes in the Quorum Game Engine. Tab panes are interface tools that allows for different content to share the same space or panel. The user can select which content to view by changing the selected tab. For example, most Internet browsers allow you to open multiple webpages as Tabs in a single window. The TabPane would then be the window where the tabs exist. All the tabs share the pane, but only the selected tab gets drawn while the other tabs are inactive.
What is a Tab?
Tabs are the different panels that exist in the Tab Pane. In our Internet browser example, the Tabs would then be the different webpages. Each Tab in the browser has content that is related to it that is displayed depending on whether it is selected or not. In Quorum, each Tab has a "Related Item" that we can set, and that item is displayed when that Tab is being selected. One important thing to note is that 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, where their Related Items are different shapes, and we will allow the user to add or remove Tabs using Buttons. To start, create a new Game Application project.
Creating a TabPane
Making our Pane
The main class will need the libraries for TabPane, Tab, Icon, Drawable, Color, and Array, requiring the following use statements:
use Libraries.Game.Game use Libraries.Interface.Controls.TabPane use Libraries.Interface.Controls.Tab use Libraries.Interface.Controls.Icon use Libraries.Game.Graphics.Drawable use Libraries.Game.Graphics.Color use Libraries.Containers.Array
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, three Tabs, and a Color. We will be using the Color for our Drawables later. Our declarations are as follows:
TabPane pane Tab tab1 Tab tab2 Tab tab3 Color color
Then we will make the TabPane, set its position, and add the TabPane to the game. This is done with the following lines of code:
pane:SetSize(450, 450) pane:SetPosition(50, 100) Add(pane)
Additionally, we will add an outline for 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)
Making the Tabs
If we were to run the program now, all that would be on the screen is our outline showing where the pane's display area is. This is because we have not added anything to the tabs or added them to the pane yet. Each Tab will show a different shape: a rectangle, a circle, or a triangle. The shapes are created as follows:
Drawable box Drawable circle Drawable triangle box:LoadFilledRectangle(300, 300, color:Red()) circle:LoadFilledCircle(150, color:Blue()) triangle:LoadFilledTriangle(0, 0, 300, 0, 150, 300, color:Green())
The next thing we will do before we can add our Tabs are the icons and they will appear next to the Tab's name. Our icons are created as follows:
Icon icon1 Icon icon2 Icon 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 take our Tabs we created earlier and set their Name, Related Item, and Icon. The name will be the text that is displayed on the tab at the top of our pane along with its icon. The Related Item will be the drawable that is displayed when that tab is selected. This is done as follows:
tab1:SetName("Square") tab1:SetRelatedItem(box) tab1:SetIcon(icon1) tab2:SetName("Circle") tab2:SetRelatedItem(circle) tab2:SetIcon(icon2) tab3:SetName("Triangle") tab3:SetRelatedItem(triangle) tab3:SetIcon(icon3)
Next, we need to create an Array with our Tabs and add it to our pane. This is done through the following lines of code:
Array<Tab> tabs tabs:Add(tab1) tabs:Add(tab2) tabs:Add(tab3) pane:Add(tabs)
Note that these tabs do not need to be in an Array to be added to the pane. Using pane:Add(tab1) for each tab will also work, but we do it this way to show that this method also works.
For accessibility we will be setting the Focus to the TabPane with the following line of code:
When a Tab Pane receives Focus the user can navigate through the Tabs using Ctrl + Tab and Ctrl + Shift + Tab on Windows or Ctrl + [ and Ctrl + ] on Mac.
Now, when we run the program, we can switch Tabs to see our different shapes, and we can close out of them. But, we can still add more to the pane by adding Behaviors for when you change your current selection or close a Tab.
Note that we did not set the position of the shapes but they are drawn inside of our outline. This is because the shapes, now Related Items to the Tabs, are drawn relative to the TabPane.
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 output and say aloud a newly selected Tab.
First, let's create our SelectionListener. To start, create a new Quorum class, named "TabSelectionListener.quorum." This class will need the libraries for SelectionListener, SelectionEvent, and Speech.
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) output 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)
Every time we open or close tabs, a TabChangeEvent occurs. If we want an action to happen when this event occurs, then we need a listener. To do this, create a new Quorum class, "TabPaneListener.quorum," to listen for when a Tab is added or removed from the TabPane. When this listener detects an addition or removal, the program will say and output that a Tab was opened or closed.
This class will use the libraries for TabChangeListener, TabChangeEvent, and 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) output "Tab opened" end action ClosedTab(TabChangeEvent event) Speech speech speech:Say("Tab closed", false) output "Tab closed" end end
Returning to the main, we need to create a TabPaneListener variable and add it to our pane. This is done by adding the following two lines to the CreateGame action:
TabPaneListener listener pane:AddTabChangeListener(listener)
If both listeners are added to your game, then the program will output and say the Tab's name when selecting Tabs, and it will output and say when a Tab is opened or closed.For additional information on how these listeners and events work, check out the tutorial on Events.
Adding Generic Tab
As the program is now, we can close Tabs, but they will stay closed and we can't open more 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, Icon, and Drawable.
The AddTabBehavior class will inherit the Behavior class, and override the Run action. For simplicity, we will design this Behavior to make the same shape for the new Tab every time. To add our new Tab, we will also need an action to get the TabPane from our main class. 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.Interface.Controls.Icon use Libraries.Game.Graphics.Drawable class AddTabBehavior is Behavior TabPane pane = undefined action Run(BehaviorEvent event) end action SetTabPane(TabPane newPane) pane = newPane end end
For the Run action, we need to create a new Tab, making a simple blue rectangle for the icon and Related Item, and add it to the TabPane. We will also protect against program crashes by checking if the TabPane is still undefined. 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.Interface.Controls.Icon use Libraries.Game.Graphics.Drawable class AddTabBehavior is Behavior TabPane pane = undefined action Run(BehaviorEvent event) if pane not= undefined Tab newTab Drawable newSquare Icon newIcon Color color newSquare:LoadFilledRectangle(300, 300, color:Blue()) newIcon:LoadFilledRectangle(50, 50, color:Blue()) newTab:SetName("New Square") newTab:SetRelatedItem(newSquare) newTab:SetIcon(newIcon) pane:Add(newTab) pane:Resize() end end action SetTabPane(TabPane newPane) pane = newPane end end
Returning the main class, we can now make a AddTabBehavior object, set the TabPane, and create a Button to add new Tabs, which adds the following lines to the CreateGame action:
AddTabBehavior tabBehavior tabBehavior:SetTabPane(pane) Button addTab addTab:SetName("Add New Tab") addTab:SetPosition(600, 400) addTab:SetBehavior(tabBehavior) Add(addTab)
Adding Tabs Based on Selection
Now, we will create another new Quorum class for the new Button's Behavior. We will call this class "AddCurrentBehavior.quorum." The template for this class is almost identical to the AddTabBehavior class, but it will also include the TabPaneSelection library.
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 to the current Tab using the TabPaneSelection's GetTab action.
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:
Icon icon 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 a new Tab and Add it to the pane.
Tab newTab newTab:SetName(name) newTab:SetRelatedItem(shape) newTab:SetIcon(icon) pane:Add(newTab)
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.Interface.Controls.Icon 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() Icon icon if current not= undefined Tab newTab text name = current:GetName() Drawable shape = cast(Drawable, current:GetRelatedItem()) if name = "Circle" icon:LoadFilledCircle(25) elseif name = "Triangle" icon:LoadFilledTriangle(0, 0, 50, 0, 25, 50) else icon:LoadFilledRectangle(50, 50) end newTab:SetName(name) newTab:SetRelatedItem(shape) newTab:SetIcon(icon) pane:Add(newTab) 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:SetName("Add Current") addCurrent:SetPosition(600, 350) addCurrent:SetBehavior(currentBehavior) Add(addCurrent)
Now that we have all of the Buttons added we will add our focus cycle so that our application is accessible.
pane:SetNextFocus(addTab) pane:SetPreviousFocus(addCurrent) addTab:SetNextFocus(addCurrent) addTab:SetPreviousFocus(pane) addCurrent:SetNextFocus(pane) addCurrent:SetPreviousFocus(addTab)
Now when we run the program, we can add new Tabs 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.
In the next tutorial, we will discuss Menus, which describes how to use Menus.