Skip to main content

G3000 GTC Framework

GTC Views and the View Stack

The G3000's GTC framework is centered around the concept of views and the view stack.

Views render content to be displayed on the GTC and handle user interactions with the touchscreen, knobs, and softkeys. All views are FS components that extend the abstract class GtcView. There are two types of views: pages and popups. Only one page can be open at a time. Meanwhile, multiple popups can be open simultaneously.

info

Views on horizontal GTCs (580 model) are 990px in width and 768px in height. Views on vertical GTCs (570/575 model) are 480px in width and 413 px in height. These dimensions include the area used to display the view title.

Open views are stored on the view stack. The open page is located at the bottom of the stack, and popups are pushed onto the stack in the order in which they were opened. The view at the top of the stack is considered the active view. Typically, all user interactions are routed to the active view, though there are some exceptions.

The view stack also maintains a history. Each time a view is pushed to the view stack, a new history snapshot of the view stack state is created and pushed onto the history stack. This allows previous view stack states to be restored via a 'Back' operation.

Control Modes

GTCs operate in one of three control modes:

  • PFD
  • MFD
  • NAV/COM

A particular GTC does not necessarily have access to all three control modes. Horizontal GTCs (580 model) can be configured to support all three modes or only the PFD and NAV/COM modes. Vertical GTCs (570/575 model) can only support either the PFD or MFD modes, and cannot freely switch between them.

Each control mode maintains its own view stack. Switching between control modes switches the active view stack and therefore which view(s) are visible. The view stacks of non-active control modes are retained, ready to be resumed when the user switches back to those modes.

The MFD control mode is unique in that it maintains not just one view stack but one view stack for each controllable display pane. When the MFD control mode is active, the user can select any non-hidden controllable display pane to control, and that pane's view stack becomes the active view stack. Like with other view stacks, non-active display pane view stacks are retained and ready to be resumed when the user selects their associated pane again.

View Lifecycle

GTC views have a complicated lifecycle, since they can move between many different states. Each view stack has its own instance of any particular view. Therefore, views of the same type in different stacks do not implicitly share state.

A view is considered in-use when it appears in any historical version of its view stack (including the current version) and out-of-use otherwise.

A view is considered open when it appears in the current (most recent) version of its view stack and closed otherwise. The onOpen() and onClose() lifecycle methods are called when a view switches between these two states.

A view is considered awake when it appears in the current (most recent) version of the active view stack, and asleep otherwise.

Finally, a view is considered active or resumed when it appears at the top of the current (most recent) version of the active view stack, and inactive or paused otherwise. The onResume() and onPause() lifecycle methods are called when a view switches between these two states. Each GTC only has one active view at a time, and it is with this view that the user primarily interacts.

Views are always created initially in an out of use state. Views can also have different lifespans, determined by their lifecycle policy. The different possible lifecycle policies are included as members of the GtcViewLifecyclePolicy enum:

  • Static: The view is created immediately when it is registered (see below) and is never destroyed. Should only be used by views that have persistent state and need to be created when the GTC instrument is first initialized.
  • Persistent: The view is created immediately before the first time it transitions to an in-use state and is never destroyed. Should be used by views that have persistent state.
  • Transient: The view is created immediately before the first time it transitions to an in-use state and is destroyed when it transitions back to an out-of-use state. Should be used by views that do not have persistent state.
tip

Views have a non-negligible memory footprint when created. Therefore, it is recommended that the transient lifecycle policy be used whenever possible to reduce memory load and increase stability in memory-limited scenarios oftentimes seen in low-spec PCs and the XBox consoles.

caution

Ensure that transient views properly release all resources they use when they are destroyed to avoid memory leaks. Cleanup code should be included in the view's destroy() method.

It is also good practice to ensure all views contain cleanup code, regardless of their lifecycle policy. This is because a mod or plugin can theoretically replace any registered view with another one (see Registering Views).

The GTC Service

Each GTC instrument contains one instance of GtcService, which is the class that controls all view-related state and logic. Switching control modes, selecting controllable display panes, opening and closing views - all of these actions and more are handled by GtcService. All views are passed a reference to their parent GtcService instance as a prop.

Registering Views

Views must be registered with GtcService before they can be used. Each view is registered under a unique string key, which is then used to open and retrieve the view from GtcService. Views are registered on a per-control mode basis; different views may be registered under the same key to different control modes. Finally, views are registered as factories - functions that create and return the registered view as a VNode - instead of directly as instances of the view. This is to allow GtcService to handle the process of creating views with various lifecycle policies, and in the case of MFD control mode views, to allow the creation of one instance of the registered view for each controllable display pane view stack.

If a view is registered to a control mode under a key that is already registered to that control mode, the new view will replace the existing view registered under that key. If an instance of the existing view has already been created, it will be destroyed when it is replaced.

info

The GtcViewKeys enum contains all GTC view keys defined and used by the base G3000.

The Button Bar and Label Bar

In addition to content rendered by views, the GTC also displays a button bar and label bar on the edge of the screen (the right edge on horizontal GTCs and the bottom edge on vertical GTCs). Collectively, these are known as the sidebar, and they contain context-dependent buttons and labels.

Views can control what appears in the sidebar using the _sidebarState property defined by GtcView. The state defined by _sidebarState is automatically applied to the sidebar when the view becomes the active view. Correspondingly, when a view is no longer the active view, it automatically passes control of the sidebar state to the new active view.

The GtcSidebar class contains utility methods for working with the sidebar.

Interaction Events

The user interacts with the GTC in two ways: (1) through "touch" (mouse) events, and (2) through manipulating the GTC's "physical" knobs, joysticks, and bezel keys. Touch events are handled by various components rendered on the GTC screen that simulate touchscreen buttons, sliders, touchpads, etc. Meanwhile, the "physical" events, along with certain touch events that are not specific to any GTC view, are handled using the GtcInteractionEvent API.

When a user manipulates a GTC knob, joystick, or bezel key, or presses a button on the button bar, the interaction generates a GtcInteractionEvent. The event is then routed to a series of handlers implementing the GtcInteractionHandler interface. As each handler is notified of the event, it can either choose to handle the event or do nothing. If the event is handled, then no further handlers will be notified of the event. If the event is not handled, then the next handler is notified and the process repeats.

Event routing always follows the same path:

  1. The active GTC view.
  2. All registered GTC plugins, in the reverse order in which they were loaded.
  3. The default interaction handler defined in the GTC service.

A common pattern encountered when an event is routed to a GTC view is that the responsibility for handling it falls to a single component in the view. To facilitate implementing this pattern, the base GtcView class defines a property called _activeComponent, which is a Subject that takes either a GtcInteractionHandler value or null. When the value of _activeComponent is not null, all interaction events routed to the view will automatically be passed to the handler that is the value of _activeComponent. When the value of _activeComponent is null, all interaction events routed to the view will be ignored. Subclasses of GtcView can change this default behavior by overriding the onGtcInteractionEvent() method.

Knobs and Control State

Each GTC has a set of "physical" hardware knobs which the user can manipulate to perform context-dependent actions. Horizontal GTCs have two knobs: the upper dual concentric knob and the lower "map" knob. Vertical GTCs have three knobs: the left "map" knob (actually a joystick, but we refer to it as a knob for consistency with the others), the center knob, and the right dual concentric knob. The label bar has a section for each knob which is used to display text that describes the current function of its corresponding knob.

Each knob has an associated control state which helps to define the knob's current function and what appears in the label bar for the knob. Control states can be any string value. The control states of all knobs can be accessed via the gtcKnobStates property of GtcService. The base G3000 package automatically computes a control state for each knob, chosen from a set of pre-defined states (members of the GtcDualKnobState, GtcCenterKnobState, and GtcMapKnobState enums), depending on the current context. The default interaction handler uses these states to determine how to handle knob interaction events, and the label bar uses them to determine what to display. For example, when the map knob state is equal to GtcMapKnobState.MapNoPointer, the map knob can be used to change the range of the selected map and the label bar will display "-Range+" for the map knob.

The default interaction handling and label bar rendering behavior defined by control states can be overridden in several different ways.

GTC views can choose to handle knob interaction events through the use of _activeComponent or onGtcInteractionEvent() instead of deferring to the default interaction handler. Views can also override what appears in the label bar through _sidebarState.

To implement custom behavior that is not view-specific, GTC plugins can use the getKnobStateOverrides(), getLabelBarHandlers(), and onGtcInteractionEvent() methods. The getKnobStateOverrides() method allows a plugin to inject control states for any knob that will replace the control state generated by the base G3000 package. Plugins can even inject control states that are not pre-defined by the base G3000 package. The getLabelBarHandlers() method allows a plugin to override what appears on the label bar for any given knob control state. Finally, onGtcInteractionEvent() can be used to handle any knob interaction event before it is routed to the default interaction handler.

info

Plugin knob state overrides, label bar handlers, and interaction event handling are all superceded by GTC view-level overrides. In other words, the active GTC view always gets first priority to decide how to handle interaction events and what appears on the label bar.

caution

Plugins that inject custom knob control states that are not defined in the base G3000 package must also define label bar handlers that generate appropriate labels for those states. If the label bar cannot find a label to render for a control state, it will throw a runtime error.