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 in a 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.

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 combined view stack. The combined view stack is made up of two separate view stacks: the main view stack and the overlay view stack. The overlay view stack is conceptually and visually positioned on top of the main view stack. Views opened in the overlay view stack are always considered to be "on top of" any open views in the main view stack. The main view stack contains the open page at the bottom and zero or more popups. Pages cannot be opened in the overlay view stack, so the overlay view stack can only contain popups.

The view at the top of the combined view stack is considered the active view. Typically, all user interactions are routed to the active view, though there are some exceptions.

The combined view stack also maintains a history. Each time a view is pushed to the combined 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.

Switching between control modes switches the active control mode combined 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 main view stack but one main 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 MFD main view stack. Like with other view stacks, non-active display pane main view stacks are retained and ready to be resumed when the user selects their associated pane again.

The MFD control mode still only contains one overlay view stack. Therefore, switching between controllable display panes has no effect on the MFD overlay view stack. Any views open in the overlay view stack before switching the display pane remain open after the switch.

note

Switching the controllable display pane while the MFD control mode is active does not push a new history snapshot onto the MFD control mode's history stack. Instead, the portions of all extant history snapshots that referenced the main view stack of the old controllable display pane are replaced with references to the main view stack of the newly selected controllable display pane.

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. The onInUse() and onOutOfUse() lifecycle methods are called when a view switches between these two states.

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.

warning

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 may be registered with a main view stack (registerView()) or with an overlay view stack (registerOverlayView()). Different views may be registered under the same key to different view stacks. 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 view stack under a key that is already registered to that view stack, then 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.

warning

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.