The Anatomy of a Tab Group
A tabbed panel in a window is basically split into two sections. The first section contains the tabs and their functionality. The second section is a clientarea that contains all of the content panels for each tab. Both of these sections are in turn contained by a transparent panel called a "TabbedParent". While the visual layout of a tab group's sections is irrelevant, the child/parent relationships must either be maintained or overridden for the purposes of automatic setup of your tabs. Each tab should have a corresponding content panel in the clientarea, and when the controls are built, an Association is formed between these two controls. If the clientarea is placed somewhere besides as a child of the TabbedParent, the tab will not be able to find its content panel to create this association. It is theoretically possible to do all of this setup manually, but it's not recommended, shouldn't be necessary in most cases, and isn't covered here.
You may find it helpful to refer to the diagram to the right, which visualizes the most common layout of a tab group.
The first half of a tab group is all housed within a single transparent control of type "tabs", which is typically dockMode=TOP and is a direct child of the TabbedParent. Contained within this tabs control are three controls, a "tab container", a "tab arrows", and a scrollable panel containing horizontal rules (also known as lines). The tab container is a transparent panel that holds the actual tabs themselves. The tab arrows control starts out life as a hidden control, and it will automatically unhide itself whenever your tabs exceed their horizontal space, allowing you to scroll through them. Generally speaking, you should not need to adjust any of these controls, and in fact may cause trouble by doing so. However, should it prove necessary, details for replacing the arrows with your own art and prototypes show up later in this document.
Within the tab container itself, you place the tabs. There is an included _tab prototype for tabs (and all other controls mentioned here), using unspectacular art, so you will most likely need custom appearances for your tabs. This is explained further on. For now, just understand that your tabs need to have the same name as their content panel, and that you shouldn't need to mess with lining them up properly, as this is handled automatically. However, the order in which they are placed into the tab container is the initial left-to-right order in which they will appear visually.
The other necessary child of the TabbedParent control is a "clientarea" control, which in its default state is a transparent panel with dockMode=FILL. Its visual state and docking actually mean very little, and you can fill it in with a solid color or a texture if this pleases you. The clientarea contains all of the content panels for the tabs. While it is possible to add tab/panel pairs after the TabbedParent has been built, if a tab is included at build time, it must also have a content panel included, or a script error will result. Tabs identify their content panels by virtue of an identical name. Neither the name of the tab nor the content panel have any actual bearing on their functionality outside of this, so feel free to name them whatever you like, so long as they come in matched pairs.
Content panels are most commonly given a dockMode of FILL, so as to maximize their size within the clientarea and ensure that they have a relatively consistent appearance. There is a _GUITabContentPanel class and a _tabContentPanel prototype available, but the use of this specialized panel as a tab's content area is not strictly necessary. The _GUITabClassMethods script (or its relevant child class methods) automatically handles the visualization of content panels, hiding all panels except the one belonging to the active tab.
The actual contents of content panels is completely arbitrary. They can scroll or not, they can have textures and colors and forms and virtual stages and even a tab group of their own. The order in which they are placed into the clientarea has no bearing, as well, and in fact, the orders of both those and the tabs themselves will probably be changed from whatever order you place them.
Making Your Own Tab Prototype
You will want to make a standard tab prototype to be used for your interface, and use this whenever possible for consistency's sake. This should be instantiated from a class of your own making, which should be of the guicontrol archetype and inherit from the _GUITab class or one of its child classes.
Custom Tab Artwork
The main reason to create a new tab prototype is custom artwork. Clearly you want your tabs to match the rest of your game's interface, and the most consistent method of achieving this is to create a new prototype which uses this art. This may require creating a new class for your tabs as well. There are three methods that you may need to implement in your new class methods script, based on the art:
method HE_activeTabVerticalOffset() as float
This sets the position.y of whichever tab is currently considered the active tab, based on the height of the tab's art and the vertical size of the tab container. This allows you to choose whether your active tab appears slightly higher, lower, or equal to the rest of the tabs.
For example, if your tabs control is 30px high, and your tabs themselves are 24px high, you will want the active tab to be placed at a position.y of about 6 or 7. If your active tab should sit slightly higher than the inactive ones, you would return 6 with this method, and have the HE_inactiveTabVerticalOffset() (see below) method return a value of 7 or 8.
method HE_inactiveTabVerticalOffset() as float
Similarly, this determines the height of all tabs except the active one. Return a value of 1 or 2 greater than the active tab's vertical offset to place your inactive tabs slightly lower, or a value of 1 or 2 higher to have your inactive tabs higher than the active tab. It is recommended that the bottom of the highest tab group (active vs. inactive) be flush with the bottom of their container, or perhaps even 1px beyond it.
method HE_tabHorizontalOffset() as float
This determines how much spacing should be between each tab. Frequently, a set of tabs will overlap one another, with the active one in front. Negative return values are fine for this to create that effect, or you can use positive values to space the tabs out more.
Custom Tab Functionality
The second reason for creating your own tab prototype is that of specialized functionality. If your tabs need to do something that normal tabs do not, you will want to create a new prototype out of the simple necessity of attaching a new class to them to override some or all of the existing GUITab class methods.
As mentioned, your new class should 1.) be based on the guicontrol archetype, as opposed to the data archetype, and 2.) inherit from the existing _GUITab class. Doing so will allow you to only override the parts of the default tab behavior that you desire to change. Bear in mind that in most cases, implementing an override function will completely replace the default behavior, so if you want only slight modifications, you will need to copy the existing behavior from the relevant function in _GUITabClassMethods.
Adding Tabs Dynamically
To add a new tab to your TabbedParent, you first need to build the content panel associated with it, so you can attach them to the TabbedParent together. You will probably want to create an override class and prototype for the default _TabbedParent prototype as well, as this control's class methods script handles the creation and addition of new tabs for itself via the following two override methods:
method HE_newTab() as NodeRef of Class _GUITab
This method does little more than create a tab control from your game's default prototype and return it for further adjustment, such as labeling. It is used primarily by the next method.
method HE_addTab(title as String, name as String, panel references NodeRef of Class GUIControl) as NodeRef of Class _GUITab
This method takes the given input, creates a tab based on your default tab prototype, labels it according to the title you pass in (and resizes it for that text if necessary), creates the association between the tab and its content panel, and adds them both to their respective locations in the TabbedParent. If your game's tabs use icons instead of a text title, or some combination of both, you may wish to use the title parameter of this method to pass in a texture FQN or case match of some sort.
Note that the HeroEngine implementation of this method will allow you to pass in a null node for the panel, and will subsequently create one for you based on the default tab content panel prototype, as defined by the TabbedParent method HE_newTabContentPanel(). Because the panel parameter references the variable, whatever script originally made the call to add the tab will then have the new panel available for content creation.
For special case tabs even within the context of a game specific interface:
method HE_addTabFromPrototype(prototype as String, title as String, name as String, panel references NodeRef of Class GUIControl) as NodeRef of Class _GUITab
This method is typically identical to the HE_addTab(title, name, panel) method in every way, except that it uses the specified prototype to create the tab, instead of your game's default tab.