Canvas Draw

From FlightGear wiki
Jump to navigation Jump to search
Graph examples.png
Started in 12/2018
Description 2D drawing helpers for creating avionics
Maintainer(s) jsb [1]
Contributor(s) rleibner, Thorsten (via shuttle)
Status discussion/stubs as of 11/2018
Topic branches:
This article is a stub. You can help the wiki by expanding it.


1rightarrow.png See Canvas draw library for the main article about this subject.

By now, Thorsten created a library of procedural commands which allows you to e.g. draw a HUD with a minimal set of commands and re-draw it with a different design if you find it doesn't match reality runtime - something which SVG can just not do.

All it needs is to streamline it and document it, and then the situation your describe may (or not) change, but in Thorsten's opinion it beats SVG in designer-friendliness by a large margin - simply because you can change spacings etc. runtime in FG. Whereas re-drawing a pitch ladder with different spacing in SVG is a piece of work and there's no guarantee you get it right runtime.

within the Shuttle code, Thorsten seems to have developed a considerably superior version of the Canvas API, i.e. would it not make sense to move that into the FGData Canvas files, and standardise it?

It sounds as if Dirk and probably many other people want exactly that kind of functionality.[2]


a library of functions to draw more complex things on a canvas? While some people prefer Inkscape/SVG and others prefer nasal scripting, this page is intended to document a library for procedural drawing.

Specifically, to help implement elements commonly used for the PFD and parts of MFD.

While people can certainly find lots of code in all the different aircraft for drawing things like speed tape, compass, engine instruments etc. but as far as I see there is nothing like a canvas.draw library as of 12/2018.

It seems, it would be good to start such thing and the first elements would be a compass rose and a linear tape (e.g. for speed and altitude), stylable of course. So the library should also contain a structure to organize styles.

Existing work

TODO: add links

  • shuttle 2D drawing helpers (Thorsten)
  • rleibner's 2D drawing API
  • extra500 helpers
  • IFSD

(anything else ?)


Ideally, the new 2D drawing helpers would be agnostic to the concrete use-case, so that they can be used for arbitrary purposes. This can be accomplished by following a few simple design principles:

  • all drawing callbacks should accept a mandatory group/element node to operate on
  • all drawing callbacks should accept/support an optional options hash
  • all drawing callback should return the corresponding Canvas element

This would be in line with the existing Canvas/MapStructure APIs, and it would also make it possible to easily use delegates to customize internal behavior

var myElement = func(root, options, style) {
return element;


We are proposing a layered design, with simple shapes being used to implement more complex elements (think altitude/speed tapes, artificial horizon etc). Internal default heuristics should be customizable via delegates (Nasal callbacks).


To ensure rapid prototyping, the idea is to structure the whole thing as an addon. This would make it possible to easily copy existing code into a new module and adapt it as needed.

For testing purposes, it would make sense to extend svg.nas as we go, i.e. to add support for native shapes to the svg parser.

In addition, it might be worthwhile to also write a custom Nasal parser to parse our existing HUD XML markup (README.hud). This would seem like a goo idea, because we've been wanting to get rid of the hard-coded legacy HUD system anyway, but were still lacking a corresponding 2D API for things like altitude/speed tapes.

Structuring the whole thing as an addon, would also mean that people can get more easily involved in testing the new module, but also in developing it further.

Once the canvas.draw module is sufficiently mature it would be reviewed and get committed to the base package.

Animation Handling

See also: Canvas animations (original discussion)

At some point, anything involving symbol/display animations (i.e. Nasal callbacks handling updates via timers and listeners) should ideally be wrapped using a corresponding set of helper classes, so that if/when more appropriate native helpers become available, such "animations" can be easily mapped to corresponding native code, instead of relying on hundreds of Nasal timers and listeners, which are infamous for being the most likely culprits when it comes to resource leaks.

The background being, from a profiling standpoint, we really need to be using more OSG-level data structures and map those to "canvas properties", to do away with things that Nasal code is currently handling, especially those doing context switches between Nasal and native code using timers and listeners.

The most prominent use-case being Canvas "animations" built on top of timers and listeners - OSG has native data structures for doing this sort of thing, and we only really need to introduce new elements (or even just attributes) to make use of those, which would also mean doing away with tons of Nasal-induced overhead due to timers and listeners animating a Canvas element currently.

For instance, the real issue arises once people want to interact with a Canvas MFD (think PFD, ND, FG1000) using out-of-process tools (FGQCanvas, Phi, tablet PCs, Android phones, Rasberry Pi etc), at which point all crucial internal state that merely resides in Nasal/scripting space is hugely problematic - whereas everything that is part of the Canvas/Element-specific property tree hierarchy can be trivially supported without people having to know anything about Nasal.

We should have introduced dedicated elements for dealing with animations without going through Nasal space using timers and listeners long ago. In the meantime, it makes sense to make sure that all such uses are encapsulated by using a corresponding set of helper classes that can be easily updated once better APIs become available.

HUD Parser

James mentioned that he would like to see Canvas-based reimplementations of the currently hard-coded HUD/2D panels systems, including hooks to trigger such Canvas/Nasal replacements instead of the legacy code:

The aim would be to replace the C++ code with a Nasal HUD layer, likely in FG_DATA/Canvas/Hud - with one Canvas HUD item per each of the current C++ HUD items (ladder, tape, dial etc). Each one should be fairly easy to build with some path, text and group commands.

Then we need to write an XML loader in Nasal, possibly one line of C++ to trigger loading, and we can remove the C++ HUD code in favour of the Canvas implementation. [...] map the old 2D panel code which uses raw OpenGL calls, to instead build up Canvas items (textures, transforms, text) and update them. Some things are easier here by using C++ - we can keep the same XML parser and the texture loading is much simpler.

In both cases the aim is to remove legacy OpenGL 1.x code, since these are some of the awkward remaining pieces.[3]

HUD files are PropertyList-encoded XML files, typically in the form of:

HUDs consist usually of several building blocks, separately stored under $FG_ROOT/Huds/Instruments:

As can be seen, the building blocks needed for a HUD would directly map to 2D drawing API for creating avionics.

HUD Widgets

Since the 2D drawing library is intended to help with implementing PFD/ND and other MFD-style avionics, using the existing HUD system for testing purposes seems like a good idea to grow a library of useful elements (see $FG_ROOT/Docs/README.hud ).

  • <ladder>
  • <tape>
  • <turn-bank-indicator>
  • <runway>
  • <aiming-reticle>
  • <gauge>
  • <dial>

We can protoype a simple Nasal parser that maps these primitives to the corresponding 2D API calls to show the corresponding HUD in a dedicated Canvas GUI dialog for testing purposes.

For instance, to dump the XML markup to the console, we can use io.read_properties() like this (via the Nasal Console):

var filename = "Huds/minimal.xml";
var path = getprop("/sim/fg-root") ~ "/" ~ filename;
var xmlNode = io.read_properties(path);
var xmlHash = xmlNode.getValues();

debug.dump( xmlHash );