X-Plane Widgets

Introduction

This chapter describes the basics of the X-Plane widgets library.

What Is the Widgets Library

The X-Plane Widgets library is a code library that implements a high-level cross-platform UI framework inside X-Plane.  The widgets library makes it easy to create complex user interfaces and provides for most of the common UI components a plugin will need.

A widget is a single user interface element, like a push button, scroll bar, text entry field, etc.  The widgets library provides many kinds of widgets and the code necessary to work with them.

Why Use the Widgets Library

There are many reasons to use the Widgets library:

How Using Widgets are Different from the XPLM UI APIs

The XPLM display APIs are not persistent.  Every time the sim redraws the screen, we are asked to redraw the window.  Widgets are persistent.  We create a widget once and it manages itself, calling we back only when something interesting happens (like the user clicking a button).

The XPLM display APIs provide a very general "window" with no special behaviors.  The Widgets premade window classes have title bars, are draggable, select themselves, and have several preset styles; these are "windows" as users know them.

The XPLM display APIs provide a flat space; a plugin gets a window and fills in the blanks.  Widgets are nestable into widget hierarchies that can represent complex user interfaces.

Concepts

A widget is a single roughly rectangular user interface elements.  Examples of widgets include windows, buttons, text entry fields and scroll bars.

Widgets are refered to via widget ID s. A widget ID is an opaque 32-bit handle to the widget.  A widget ID is similar to an HWND on Windows or a ControlRef on Macintosh.  We never manipulate the widget ID directly; we only pass it back to the widgets library.

Widget Data

Each instance of a widget has a number of properties.  A property is a 32-bit value assigned to a widget.  Each property has a 32-bit property ID that describes it.  Any widget can have any number of properties.  Typically each widget will have properties that are useful to its widget callback (see below).  Examples of properties include the style of a window or the state of a check box (checked or unchecked).

A widget also has a descriptor, a variable-length string attached to the widget.  The widget's descriptor may be interpretted differently depending on the widget, but often is used for the title of a window or the text for a text field/caption.

Widget Callback Functions and Messages

Each widget has a callback function that implements the widget's behavior.  Widgets send each other messages to cause actions to happen.  The widgets library also sends widgets messages, as can the plugin.  Examples of messages include "draw ourselves", "a child was added" and "a push button was clicked".

Widgets can actually have multiple callback functions.  When a widget has multiple callback functions, they are called in order.  This allows we to specialize a widget's behavior.  Adding a callback to a widget is called subclassing a widget.  A widget with multiple callback functions is similar to a class that inherits from another class; behavior not provided by the subclassing callback is handled by the original callback.

We use subclassing to customize widget behavior and to receive messages from the widget system.  For example, we can subclass a push button.  When the push button is clicked, it sends itself an "I was pushed" message.  The callback function for the push button can intercept this message and take action.

Widget Hierarchy, Coordinates and Exposed Areas

Widgets are arranged in a tree known as a widget heirarchy.  If one widget contains another, they are known as parent and child widgets.  All widgets may have zero or more children and zero or one parents.

A widget may be an actual window in the plugin system, or it may not.  If it is, it is said to be a root widget.  If a widget is nested inside a widget that is a root wiget then the widget hierarchy is rooted.  Rooted widget hierarchies may be seen by the user, but rootless ones will not be.  In other words, for the widget to be seen, it must have a parent that is a root widget.

Widgets have a rectangular area described by its top, bottom, left and right, called its geometry.  These coordinates are always in global screen coordinates, e.g. 0,0 is the bottom-left corner of the screen, and 1024, 768 is the top right.  (The size of the screen may vary with user settings and the version of x-plane; use the XPLM Display APIs to determine the upper right corner of the screen.)  The motivation for this coordinate system will be described below.

Only the portion of a widget's area that is contained in all of its parent widget's area can be seen by the user.  This property is recursive; each widget is clipped by all of its parents.  This guarantees that a button will not draw outside of a window, for example.  The widgets libary uses the OpenGL scizzors function to clip widgets to their parents' rectangles.  Just about any OpenGL hardware capable of running X-Plane provides this function in hardware, so the clipping does not have a frame-rate penalty.  The area of a widget that is actually visible is its exposed geometry.

Exposed geometry can be used to implement a scrolling display.  Simply move a large widget that is inside a window up and down in screen space; only the portion of the widget that is visible through the window will be drawn.  For performance we can determine the exposed geometry and only draw that area.

The motivation for using a single global coordinate space is the following:
The global coordinate space is probably one of the least conventional design decisions for the widgets system, but so far it has proven to be significantly simpler to work with than nested coordinate-system frameworks.

Standard Widget Types

The widgets library comes with a number of built in widgets.   They are identified by widget classes.  Each widget class represents a built-in premade widget that implements a useful behavior.  Often several similar widgets will have the same class.  For example, push buttons, radio buttons and check boxes are all one widget class.  A property of the widget identifies which type of button to implement.

The standard widgets are not special; they are simply implementations layered on top of the widgets library.  There is nothing in the standard widgets that a plugin could not implement by hand.

Procedural Vs. Object-Oriented Programming

For programmers that are used to UI frameworks in object oriented languages (such as would be found in Delphi, or PowerPlant or MFC in the C++ world) programming widgets in C will be an adjustment.

A widget callback function essentially implements all of the overridden virtual functions for a subclass of a widget.  Multiple callback functions provide inheritence.  The widget message ID describes which virtual function is being called.  The default widget implementation does nothing, so we do not need to provide implementations for 'empty' virtual functions.

Widget properties represent member data on a widget.  Users can set and get these properties arbitrarily, and extra properties may be present; widgets do not have as strong encapsulation of data as some object oriented languages.

Using the Widgets Library

This section describes how to creaet user interfaces using the Widgets library.

Creating Widget Hierarchies

Widget hierarchies can be created in any order, but usually they are created from the top down.  The basic step is to create the widgets and set their properties.

The function XPCreateWidget is used to create a widget and takes a number of arguments: the location of the widget on screen (in screen coordinates; the bottom left is 0, 0), whether the widget is initially visible, whether it is a root widget, its descriptor string, any widget containing it, and its class.

< Code Example: using XPCreateWidget to create a few widgets.>

The top widget in the hierarchy must be a root widget.  This means that it will be visible on the screen.  All other widgets should not be root widgets, but should have a parent widget.  Pass 0 or NULL for the parent widget of the root widget to indicate that it has no parent.

Given a widget or widget hierarchy, we can rearrange the hierarchy by calling XPPlaceWidgetWithin.  XPPlaceWidgetWithin will remove the moving widget from its parent (if it has one) and embed it in a new parent.  The most recently added child of a parent will be visually "on top" of its sibblings but clipped to its parent.

We can use XPUCreateWidgets to easily create a number of widgets in a hierarchy.  We pass in an array of structures describing each widget and the widgets are created and nested for we.  Often when dealing with a complex widget hierarchy it may be easiest to work with the widgets in an array; this function makes building this array from a table of geometry straightforward.

< Code Example: a dialog box using XPUCreateWidgets. >

Once we have created the widgets, we use XPSetWidgetProperty to configure them by setting properties.

< Code Example: setting XPSetWidgetProperty to set up the dialog box. >

Most widgets will initialize their properties to reasonable values when created; set widget properties to access customized behavior (to change a window's style, for example).

Manipulating Widgets

Sometimes we may have to change the appearance of the widgets.  The most common task is to show or hide a widget.  This can be done using the XPShowWidget and XPHideWidget routines.  Being visible is recursive; if we hide a widget, all of its children are hidden too.

We can position a widget or widget hierarchy on screen using the XPSetWidgetGeometry function.  The movement of the children of the widget we position is a function of the wiget class.  Most root widgets will move their  children with them to keep the relative position of the window's elements constant.

We can change widgets' appearance by using XPSetWidgetProperty or calling XPSetWidgetDescriptor to change the string associated with a widget.  This is often used to change a dialog box's strings.

<Code Example: setting up a widgets dialog box.>

Handling Messages from Widgets

Once we have created the widget hierarchy, it takes care of itself until the user wants to do something.  The widget hiearchy will draw itself, buttons will be clickable, the usere will be able to type.  When we need to respond to an action (for example, the enter or escape keys being pressed or an 'OK' button being clicked) we write a widget function and subclass one of the widgets to receive messages we are interested in.

Widgets are constantly sending messages to each other to accomplish the basic user interface management tasks.  When we attach the own widget function to a widget, we will receive all of those messages before the widget's normal handler.  We return either 0 if we did not handle the function or 1 if we did.  The widget function can then respond to a few widget functions uniquely.

Sometimes widget messages are sent up the widget hiearchy for convenience.  For example, when the push button widget is clicked, it sends an "I was pushed" message to itself.  If the push button does not handle this message (which by default it does not), the message is sent to every widget in the hierarchy up the root until someone handles it.  One of the parameters is the ID of the widget that first originated the click.

This pattern of sending widget messages up the hierarchy is done for most messages that a plugin would be interested in overriding.  This allows us to provide one widget message function for an entire widget hierarchy rather than one per widget.

< Example code: writing a widget message function >

We use the function XPAddWidgetCallback to add a new widget function to an existing widget, changing its behavior.  Typically we install the widget function when we initialize the widgdet.

Cleaning Up Widgets

When we are done with the widgets, we must dispose them or else we will leak memory!  To dispose widgets, use the XPDestroyWidget function.  If we destroy widgets we can recursively destroy their children too (which will clean up the whole hierarchy), or just destroy the root widget, leaving all children parentless and rootless.

<Example Code: Cleaning up Widgets>

Introduction to the Standard Widgets

The widgets library comes with a number of standard widgets that provide many user interface features.  This document will not get into the details of the standard widgets; the standard widgets web page in the reference section of the SDK describes each widget in detail.  This section describes some of the design ideas behind the standard widgets.

The standard widgets are divided intowiwdget classes.  Each class handles a similar set of widget behaviors.  For example, one widget class handles push buttons and check box buttons.  We set a property on a widget to customize its behavior and appearance.

< TODO: include a general table of the widget class and its overall purpose. >

The standard widgets send messages to themselves that then go up the hierarchy to notify we of user interactions.  Each standard widget defines unique message IDs for widget events and property IDs and enumerations to control widget behavihors.

Creating Our Own Widgets

We can create our own widgets.  Simply provide a widget function that will handle the entire widget's set of behaviors.  We then use XPCreateCustomWidget to create the widget, passing in the widget function.

Widget Message Dispatching

Widget messages can be dispatched based on a number of modes:
Use the routine XPSendMessageToWidget from the plugin or within the wigets to send messages to the widgets.

Key Widget Messages

The widgets library defines some basic messages that are sent to the custom widget.  
Remember that multiple widgets might use the widget function.  Attach widget-specific information to widgets using properties.  Do not use global variables for per-widget data!

We can define the own widget properties and widget messages to provide an API to the widget's unique functionality.

Reference

See the reference section for the specific functional areas: