- 1 About User Interfaces
- 1.1 X-Plane's User Interface
- 1.2 Working With Windows
- 1.3 Working with Menus
- 1.4 Working With Hot Keys
- 1.5 Using Drawing Callbacks
- 1.6 Using Key Sniffers
- 1.7 User Interface Reference
About User Interfaces
The user interface APIs allow the construction of easy-to-use user-interfaces for the X-Plane plugin. We do not have to provide any user interface at all for a plugin; the capabilities described here are strictly optional. The plugin system provides a user interface to disable any and all plugins automatically.
Higher level libraries are available that make user interface creation even easier; the abstractions described here are the lowest level ones available. Make sure to read documentation on other libraries such as the X-Plane Widgets library before designing the plugin.
X-Plane's User Interface
X-Plane features a cross-platform WIMP (Windows, Icons, Menus, Pointer) interface. The simulator normally only shows one window at a time, provides a menu bar, and the user interacts with the simulator using the mouse and keyboard (as well as external hardware devices like joysticks for flight simulation). The X-Plane plug-in system adds floating windows to the equation.
X-Plane's user interface look is custom to the simulator and looks the same on all platforms. The simulator typically runs full screen. <=== OpenGL as a 2d/3d graphics system ===
X-Plane uses OpenGL for its graphics on all platforms. OpenGL is used as a 3-d graphics system to draw the world the plane flies in and the plane itself. OpenGL is used as a 2-d graphics system to draw the cockpit, windows, menu bars, and any other user interface elements. <The graphics chapter covers X-Plane's use of OpenGL in detail. For the purposes of this chapter we only need to know a few basic things about OpenGL.
The X-Plane screen is measured in pixels, with 0,0 being at the lower left corner of the screen and 1024,768 being at the upper right. The X-Plane screen is not necessarily fixed size, although the minimum resolution is 1024x768.
X-Plane fully redraws the screen every simulator frame. It erases the entire screen to the sky, draws in land, draws in buildings, planes, runways, lights, then draws the panel on top of the world, draws the indicators on the panels, and draws any floating windows and the menu bar. This makes the UI world for X-plane very different from conventional UI programming in a few ways:
- We cannot draw at any time; we must wait for the simulator to tell we that it is time to draw the window.
- We must draw anything we want to stick around on the screen over and over since the screen is erased every sim frame. This is acceptable because OpenGL graphics are hardware accelerated and thus very fast. But we cannot afford to do a complex calculation every time we are asked to draw.
- Animation is free. Since we redraw the screen many times a second, animation is created by changing what we draw; no extra redraws are necessary.
- We don't need to 'invalidate' part of the window or track what needs to be redrawn, since the whole window is redrawn every time. To hide a window, we merely don't draw it the next time around.
The X-Plane plugin graphics API and other libraries provide a number of OpenGL helper routines; we may be able to create a user interface using premade library components without writing any OpenGL code at all. But keep the above four points in mind since they affect the architecture of the user interface code.
A 'window' in our context (programming UI in X-Plane) is not the same thing as a window to users. To a user a window has a title bar, can be dragged and resized, etc. From a programming perspective, a window is a much simpler concept. A window is simply a Z-ordered reservation of screen space for a plugin.
To cover that in more detail: creating a window gives we the opportunity to draw on top of part of the screen and capture mouse clicks into that same area. Plugin windows are ordered and their ordering can be changed. A plugin window does not necessarily have to look like an X-Plane window. A plugin can be translucent, have transparent parts, look like a panel, or look like a photograph. If we want a window to look like a user window, we need to program this ourselves (or use a library that provides these standard features).
Windows are described by a rectangular area on the screen. The actual area the window takes up may be less than that rectangle; a window can have translucent or transparent parts, and let clicks go through parts of it to the window behind it.
A window may be hidden, in which case it is not seen and does not receive mouse clicks. If a plugin is disabled, its windows are temporarily hidden.
Windows are the preferred way to draw 2-d graphics to the screen, instruments, panels, and anything other than 3-d elements in the 'world'. If we are using windows for WIMP-style UI, consider using the XP Widgets API instead of using the XPLM Windows API directly; the Widgets API provides a much higher level of abstraction, including the most common widgets premade.
For each window we create, we specify three callbacks: one to handle drawing the window, one to handle mouse clicks and drags in the window, and one to handle key presses when the window has focus (see below).
The window that is receiving keystrokes is said to have keyboard focus. Only one window may have keyboard focus at a time. If a window does not have keyboard focus, X-Plane receives keystrokes.
Windows should take keyboard focus sparingly. Keyboard focus is appropriate for a window that receives text entry. But while a window has keyboard focus, the user cannot use keystrokes to control the simulator. If we only want to receive some keystrokes, use a hotkey or keysniffer (described below).
Plugins can add menu items to the 'plugins' menu in X-Plane. Plugins add items to the plugin menu that contain submenus that are managed by the plugin.
For each menu the plugin provides a callback that is called when a menu item from that menu is picked. For each menu item the plugin can provide a unique pointer to data specific to that menu item.
Menu items can be enabled or disabled. Plugins call routines in the menus API to enable or disable the menu item. Plugins can also cause an LED to be drawn next to the menu that may or may not be lit, indicating whether an item is in effect. Plugins push new state to the menus, including changes in enabling, the LED's state, and changes to the menu item title.
When a plugin is disabled, all of its menu items are automatically disabled.
Use menus to implement configuration, preferences, and other plugin-specific commands that the user would not need to do during flight. (During flight it can be difficult to switch from the joystick to the mouse to choose a menu item.)
If we want to associate a keystroke with a menu item as a shortcut, use a "hot key" (see below).
Hot keys are callbacks that trigger actions inside a plugin. Hot keys are the preferred way to associate key strokes with UI actions.
Plugins create new hot key mappings and provide a callback that is called when the hot key is pressed. Hot keys are specified via a virtual key code, which is a unique code for every key on the keyboard. Virtual key codes distinguish between the numeric key pad but do not distinguish between the shifted and unshifted version of a key. Besides a virtual key code, the hot key is associated with a specific set of modifiers and whether it should activate when the key is pressed, released, or held down. Hot keys can be mapped to any keyboard gesture.
When we create a hot key, we provide a description of what the hot key does and a unique string signature so that the hot key's mapping can be persisted to disk.
Hot keys can be remapped by other plugins. This allows a plugin to provide the user with a way to reconcile conflicting keyboard strokes. If the plugin creates a hot key and it is remapped, the plugin does not notice any change; the new keystroke triggers the same callback.
A hot key management plugin maps keystrokes for all plugins and persists the new mappings to disk. Hot keys are the preferred way to implement key commands because they allow the user to remap all key strokes with one interface and resolve conflicts or fully customize a plugin.
A drawing callback is a way for the plugin to add or replace any part of X-Plane's drawing process. If we need to do 3-d drawing in x-plane's world, a drawing callback allows we to do this. If we need to do very specialized 2-d drawing, a drawing callback lets we do this too, but consider using a window instead.
X-Plane goes through a number of drawing phases each time it refreshes the screen. It plots the ground, the puts buildings on it, runways, airplanes, then goes on to the cockpit, the instrument's moving indicators, and then any windows and overlays that are being drawn. When we create a drawing callback, we specify which drawing phase we would like to be before or after. If we are before a drawing phase, we have the option of repressing X-Plane's drawing action. This allows us to replace whole parts of the X-Plane renderer with our own code.
Warning: the phases of drawing are potentially subject to change with new versions of X-Plane. If we wish to do serious 3-d plugin drawing work, we should coordinate with Laminar Research to determine whether the plugin will be feasable for the long term.
A key sniffer is a callback that receives and may filter all keystrokes. If we need low level access to all keystrokes and the ability to dynamically select which ones to consume, use a key sniffer. But if possible, use a hotkey or a window; they will provide more streamlined interaction with other plugins.
Using User Interface
This section describes how to create and use the user interface facilities in X-Plane.
Working With Windows
We create a window and receive a window ID that identifies it. We then use that window ID to manipulate it.
We may create a window at any time; either at plugin startup or in response to a menu command or other UI action. We may choose to make the windows in advance and show them when the user wants to see them or create them when the user wants to see them.
< Example Code: creating a Window >
Writing the Windows Callbacks
We must provide three callbacks with the window. The first is the drawing callback.
< Example: drawing callback >
The drawing callback will be called perpetually while the window is visible.
We also provide a mouse callback. This callback is called when the user clicks and drags in the window. We receive one message when the user clicks in the window, then one callback for every frame while the mouse is dragged, and finally one message when the mouse is released.
< Example: mouse callback >
When we receive a mouse callback with the mouse down message, we return true or false indicating whether we wish to track the mouse. If we do not track the click, it will be sent to the next window behind the plugins or x-plane. If we do receive the click, we will then receive mouse moved and mouse up callbacks. Note that while we only receive mouse down callbacks if the click is in the window (and no other window on top of it steals the click) once we accept a mouse down we will receive the mouse moved and mouse up messages no matter where the mouse moves. This allows we to implement mouse tracking where the window can be dragged anywhere.
Finally, we need to implement a key press callback. The keypress callback is called when the window has focus and a key is pressed, and also once when focus is taken away from the window.
< Example: keyboard focus >
The window will not take keyboard focus unless we explicitly request it, but may lose keyboard focus if another window takes it. The window will automatically lose keyboard focus when it is hidden.
Manipulating Window State
We can manipulate the window both from regular code and from within window callbacks. For example, we can reposition the window in response to a mouse click.
< Example: manipulating the window. >
Working with Menus
Create the plugin's menu during the initialization phase of the plugin.
< Sample code: creating a menu and installing a menu callback. >
A single callback services all menu items for a single menu. The plugin's menu does not have a callback, so while we can insert submenus in the plugin menu, we cannot have actual menu items in the plugins menu.
Handling Menu Callbacks
In the menu's callback we execute the actual code based on the menu. Use the per-item refcon to determine which item was picked.
< Sample code: the menu callback >
We can change the status or title of a menu item at any time. The plugin manager will not request that we update status, so we need to set up the menus' status as it changes (e.g. as the user executes actions). In the above example, when a menu command is picked, it may change other menus status. Note: if we change the status of a menu item while the menu is open, it will change status while open (e.g. the user will see the status change).
Working With Hot Keys
In the plugin's initialization code, register any hot keys the plugin needs.
< Sample Code: creating a hot key >
Handling Hot Key Callbacks
In the hotkey's callback we execute the actual code associated with the hotkey.
< Sample Code: A Hot Key Callback >
Note that there is no way to disable a hot key. If the hot key is not available, simply ignore it in the callback. The reason the hotkey system is set up this way is so keystrokes will not affect the simulator intermittently depending on whether the hotkey is enabled. When we create a hot key, we make a permanent reservation for that keystroke. (For this reason we should not create a hotkey with a key binding that does not use a modifier key; we will stop the user from typing in text fields with this keystroke!)
Getting Information about Other Hot Keys
We may create a plugin that allows the user to remap the own hot keys or other hot keys. We can enumerate on all hot keys and then remap them.
< Sample Code: Enumerating hot keys, etc. >
Using Drawing Callbacks
Registering a Drawing Callback
To add 3-d graphics to X-Plane, register a drawing callback. Either register to draw before or after X-Plane. The drawing callback will be called whenever that simulator is running or paused and a simulator dialog box is not up.
< Example: registering a 3-d drawing callback. >
If we register the drawing callback before X-Plane we may indicate via the return value whether X-Plane is to draw normally. This allows us to replace X-Plane's drawing routines with our own.
Writing a Drawing Callback
The drawing callback should call OpenGL to render. The coordinate system will either be set up for 2-d or 3-d drawing; see the chapter on graphics for more information on using OpenGL to draw in 3-d.
< Example: a 3-d drawing callback >
Using Key Sniffers
Registering a Key Sniffer
Register the key sniffer to receive keyboard input.
< Example: registering a keyboard sniffer >
We can register a keyboard sniffer to receive keyboards before any other part of the system, or after all plugins but before X-Plane.
Writing a Key Sniffer Callback
The keyboard sniffer callback receives every keystroke, down, up, or held down, with an ASCII key code and a virtual key code. We can either choose to pass on the keystroke (in which case the keystroke is sent to the plugins and/or X-Plane) or consume it. i.e. Do not pass it on.
< Example Code: a keyboard sniffer >