|
|
| Line 196: |
Line 196: |
| <syntaxhighlight lang="nasal"> | | <syntaxhighlight lang="nasal"> |
| map.cleanup_listeners(); | | map.cleanup_listeners(); |
| </syntaxhighlight>
| |
|
| |
| == Adding support for new Layer Types ==
| |
|
| |
| You only need to read this section if you are interested in understanding the underlying design, in order to extend existing layer types or create new ones from scratch.
| |
|
| |
| Required:
| |
| * callback to draw a single layer element (aircraft, waypoint, navaid, runway, route, fixes etc)
| |
| * A new class (Nasal hash) that derives from the "Layer" class and implements its interface
| |
| * A "data source" (provider) hash that provides the data to be rendered (i.e. populates the model, which is just a Nasal vector of drawables)
| |
|
| |
|
| |
| The callbacks to draw a particular layer element are to be found in [[$FG_ROOT]]/Nasal/canvas/map, at the moment, we have these modules (listed in ascending complexity):
| |
| * Nasal/canvas/map/tower.draw
| |
| * Nasal/canvas/map/dme.draw
| |
| * Nasal/canvas/map/navaid.draw
| |
| * Nasal/canvas/map/vor.draw (this demonstrates how to customize/style layers)
| |
| * Nasal/canvas/map/parking.draw
| |
| * Nasal/canvas/map/runways.draw
| |
| * Nasal/canvas/map/taxiways.draw
| |
| * Nasal/canvas/map/route.draw
| |
| * Nasal/canvas/map/traffic.draw
| |
|
| |
| Each "*.draw" file contains a single Nasal function, named "draw_FOO" (by convention, not by requirement)- where FOO is simply chosen based on what is drawn, so you can make up your own name, like "draw_route" for example.
| |
|
| |
| When implementing support for new routines, it is recommended to take an existing file, such as the tower.draw or navaid.draw files and just copy/paste and customize things as needed.
| |
|
| |
| This is what the tower.draw file looks like:
| |
| <syntaxhighlight lang="nasal">
| |
| var draw_tower = func (group, apt,lod) {
| |
| var group = group.createChild("group", "tower");
| |
| var icon_tower =
| |
| group.createChild("path", "tower")
| |
| .setStrokeLineWidth(1)
| |
| .setScale(1.5)
| |
| .setColor(0.2,0.2,1.0)
| |
| .moveTo(-3, 0)
| |
| .vert(-10)
| |
| .line(-3, -10)
| |
| .horiz(12)
| |
| .line(-3, 10)
| |
| .vert(10);
| |
|
| |
| icon_tower.setGeoPosition(apt.lat, apt.lon);
| |
| }
| |
| </syntaxhighlight>
| |
|
| |
| As you can see, the draw* callback takes three arguments:
| |
| * the canvas group/layer to be used as the rendering target
| |
| * the layer-specific "model" information (airport/apt in this case, could also be a navaid, fix or waypoint)
| |
| * an LOD argument (currently not yet used).
| |
|
| |
| The airport.draw file demonstrates how to create OpenVG paths procedurally. But you can just as well load the vector image from an SVG file. For an example, please refer to navaid.draw, which is shown below:
| |
|
| |
| <syntaxhighlight lang="nasal">
| |
| var draw_navaid = func (group, navaid, lod) {
| |
| var symbols = {
| |
| NDB:"/gui/dialogs/images/ndb_symbol.svg"
| |
| }; # TODO: add more navaid symbols here
| |
| if (symbols[navaid.type] == nil) return print("Missing svg image for navaid:", navaid.type);
| |
|
| |
| var symbol_navaid = group.createChild("group", "navaid");
| |
| canvas.parsesvg(symbol_navaid, symbols[navaid.type]);
| |
| symbol_navaid.setGeoPosition(navaid.lat, navaid.lon);
| |
| }
| |
| </syntaxhighlight>
| |
|
| |
| Once you have created your own "draw" implementation, you still need to have a data source (i.e. a "provider") that determines what data the callback needs to be invoked for.
| |
| This is currently still a little "hackish" and still work in progress. So we have just a very simple MVC model at the moment, whose interface needs to be implemented.
| |
|
| |
| For example, the various airport-specific "layers" all use the same "AirportModel" to be found in airport.model. In the future, other models can be found in *.model files. For a simple example of how to populate the model, just refer to the file "navaid.model", which is shown here:
| |
|
| |
| <syntaxhighlight lang="nasal">
| |
| var NavaidModel = {};
| |
| NavaidModel.new = func make(LayerModel, NavaidModel);
| |
| NavaidModel.init = func {
| |
| me._view_handle.reset(); # empty the elements vector and remove all canvas groups from the view layer
| |
| var navaids = findNavaidsWithinRange(50);
| |
| foreach(var n; navaids)
| |
| me.push(n);
| |
| me.notifyView();
| |
| }
| |
| </syntaxhighlight>
| |
|
| |
| As can be seen, each "Model" needs to derive from the "LayerModel" class. At the moment, the only method that needs to be implemented is the "init" method, which is invoked once the Layer is re-initialized.
| |
|
| |
| In this case, the init() method merely clears the internal vector using the "me.clear()" call, runs findNavaidsWithinRange(50); and then populates the model by appending each navaid to the MVC model in the top-level "LayerModel". Afterwards, the "notifyView" method is invoked to update the view (this will probably change pretty soon, once a real MVC controller abstraction is added).
| |
|
| |
| Note that the model merely stores the data - only the draw* callback will try to access it, i.e. the lat/lon/id fields or anything else that is model specific.
| |
|
| |
| Also, the LayerModel class is currently just an extremely simple wrapper, all of its fields/methods are directly available to you:
| |
|
| |
| <syntaxhighlight lang="nasal">
| |
| ##
| |
| # A layer model is just a wrapper for a vector with elements
| |
| # either updated via a timer or via a listener
| |
|
| |
| var LayerModel = {_elements:[] };
| |
| LayerModel.new = func make(LayerModel);
| |
| LayerModel.clear = func me._elements = [];
| |
| LayerModel.push = func (e) append(me._elements, e);
| |
| LayerModel.get = func me._elements;
| |
| LayerModel.update = func;
| |
| LayerModel.hasData = func size(me. _elements);
| |
| </syntaxhighlight>
| |
|
| |
| That is, you have the following methods available:
| |
| * new() - to create a new LayerModel instance
| |
| * clear() - to clear the internal _elements vector
| |
| * push() - to append new data to the internal _elements vector
| |
| * get() - to get a handle to the internal _elements vector
| |
| * update() - empty placeholder for the time being
| |
| * hasData() - to get the size of the internal _elements vector (beginning at 0)
| |
|
| |
| In addition, a bunch of additional fields are currently exposed to the model, because the design is currently not a full MVC implementation, so that we need to work around the lack for a proper MVC separation, and a real controller interface, by making handles to the enclosing layer and map available to the model, these are:
| |
|
| |
| * _view_handle - a handle to the layer (canvas group)
| |
| * _map_handle - a handle to the layer's map (map group)
| |
|
| |
| These are properly initialized during construction of the layer, but will probably be phased out, once the design has improved a little and becomes more stable - so please just consider them "helpers" for now, because we don't have all of the API in place yet.
| |
|
| |
| Now, to actually make a new layer known to the system, we need to add another file that registers the layer. For an example of how to do this, please see navaids.layer:
| |
|
| |
| <syntaxhighlight lang="nasal">
| |
| var NavLayer = {};
| |
| NavLayer.new = func(group,name) {
| |
| var m=Layer.new(group, name, NavaidModel);
| |
| m.setDraw (func draw_layer(layer:m, callback: draw_navaid, lod:0) );
| |
| return m;
| |
| }
| |
|
| |
| register_layer("navaids", NavLayer);
| |
| </syntaxhighlight>
| |
|
| |
| Currently, it is also necessary to edit $FG_ROOT/Nasal/canvas/map.nas in order to add your new *.model, *.draw and *.layer files to a vector of files that are explicitly loaded at the end of the file, search for three global variables named DRAWABLES, MODELS and LAYERS:
| |
|
| |
| <syntaxhighlight lang="nasal">
| |
| var DRAWABLES = ["navaid.draw", "parking.draw", "runways.draw", "taxiways.draw", "tower.draw"];
| |
| load_modules(DRAWABLES);
| |
|
| |
| var MODELS = ["airports.model", "navaids.model",];
| |
| load_modules(MODELS);
| |
|
| |
| var LAYERS = ["runways.layer", "taxiways.layer", "parking.layer", "tower.layer", "navaids.layer","test.layer",];
| |
| load_modules(LAYERS);
| |
|
| |
| </syntaxhighlight>
| |
|
| |
| A new layer hash is created by returning a new Layer object via "Layer.new" which derives from the corresponding model (NavaidModel in this case), and setting the draw callback to the draw routine that we created earlier, in this case using the MAP_LAYERS hash - which, currently, needs to be extended in map.nas (but which will soon be changed such that it automatically loads all *.layer files).
| |
|
| |
| The layer is registered at the end of the file using the "register_layer" call and passing a symbolic/lookup name, and the name of the hash that implements the layer.
| |
|
| |
| Finally, to actually load the new layer, you'll want to edit your XML dialog file and add it to your XML file. For example, this is how the navaids layer is enabled:
| |
|
| |
| <syntaxhighlight lang="xml">
| |
| <layer>
| |
| <name>navaids</name>
| |
| <init-property>selected-airport/id</init-property>
| |
| <property>display-navaids</property>
| |
| <description>Show Navaids</description>
| |
| <default>enabled</default>
| |
| </layer>
| |
|
| |
| </syntaxhighlight> | | </syntaxhighlight> |
|
| |
|