Canvas Map API: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
 
(9 intermediate revisions by the same user not shown)
Line 1: Line 1:
{{Affected by Canvas|MapStructure}}
{{Canvas Navigation}}
{{Canvas Navigation}}


Line 9: Line 10:
== Objective ==
== Objective ==
Heavily reduce the amount of specialized Canvas/Nasal code in XML dialogs and instruments related to mapping/charting (airport-selection, map widget, navdisplay etc) and ensure that a maximum degree of code sharing is accomplished, regardless of the canvas placement mode that is used - bottom line being, it really shouldn't matter if a map is shown as part of an airport selection dialog, as part of the moving map dialog, as part of the navdisplay etc.
Heavily reduce the amount of specialized Canvas/Nasal code in XML dialogs and instruments related to mapping/charting (airport-selection, map widget, navdisplay etc) and ensure that a maximum degree of code sharing is accomplished, regardless of the canvas placement mode that is used - bottom line being, it really shouldn't matter if a map is shown as part of an airport selection dialog, as part of the moving map dialog, as part of the navdisplay etc.
== Status ==
{{cquote|I am planning to convert the MapWidget to use this code too, and then the NavDisplay. Support for PNG/texture icons as symbols will be needed for that, and I will also expose the 'route path' which is generated by C++ to Nasal, so a Canvas Path can be created from it.
There's also a long-requested feature to use the replay-code to extract the historical aircraft position, which would be another kind of data to show.
Deciding an API for limiting symbols / databoxes is going to be very important - the NavDisplay and MapWidget already have different solutions for that. In particular there's a notion of symbol priority - basically I sort the available symbols by priority, and the display can be limited to the most important 10/50/100. This is also the approach taken by some real world map displays.
The MapWidget also checks to ensures data-boxes don't overlap where possible - I've no clue how to do that in the current canvas code, however.<ref>{{cite web |url=http://forum.flightgear.org/viewtopic.php?f=71&t=17625#p166894 |title=Using a canvas map in the GUI|author=Zakalawe |date=Wed Sep 19, 2012 12:34 pm}}</ref>|Zakalawe}}
{{cquote|The route-path does need to be exposed - for something like a hold or DME arc, there is only one waypoint in the route, but the route-path contains a series of line segments showing the actual arc / intercept / procedure turn. This also allows for correct rendering of turn-ancticpated waypoints - in the future.
But it's simply a list of lat,lon pairs so exposing it is really trivial - will get it done in the next few days. The Navcache changes are also in now, so papillion and I will look at porting the ground-radar to the canvas, and supporting the v860 pavement / taxiway primitives in the future.
And yes, CanvasImage will be getting some improvements to support 2D panels via the Canvas for sure.<ref>{{cite web |url=http://forum.flightgear.org/viewtopic.php?f=71&t=17625#p166894 |title=Using a canvas map in the GUI|author=Zakalawe |date=Wed Sep 19, 2012 12:34 pm}}</ref>|Zakalawe}}
{{cquote|Supporting multiple airports didn't seem too difficult after all the refactoring that I had done already, and it will also be needed for all the other efforts (mapwidget, navdisplay, route manager) so I gave it a quick try<ref>{{cite web |url=http://forum.flightgear.org/viewtopic.php?f=71&t=17625#p166894 |title=Using a canvas map in the GUI|author=Hooray |date=Wed Sep 19, 2012 12:34 pm}}</ref>|Hooray}}
{{cquote|I did some experimental work on converting the map dialog last night - I need to have some discussions with Tom about symbol instancing, since the vector symbols I'm doing for waypoints, VORs and so on should ideally be shared. I'll let Tom speak about his ideas for that.<ref>{{cite web |url=http://forum.flightgear.org/viewtopic.php?f=71&t=17625#p166894 |title=Using a canvas map in the GUI|author=Zakalawe |date=Wed Sep 19, 2012 12:34 pm}}</ref>|Zakalawe}}
{{cquote|I'm thinking about using a separate canvas for each symbol so that every instance of it is just a textured quad. For efficiency reasons it would be good to draw all symbols to a single canvas/texture and put all quads into a single node. So probably I'll add a new element type for putting quads into a single element which are all rendered at once. Maybe we can even use a geometry shader to just copy the positions to the GPU and generate the full quads with the shader. Ideas and suggestions are always welcome<ref>{{cite web |url=http://forum.flightgear.org/viewtopic.php?f=71&t=17625#p166894 |title=Using a canvas map in the GUI|author=TheTom |date=Wed Sep 19, 2012 12:34 pm}}</ref>|TheTom}}
{{cquote|I will add a new element type to render symbols from a "Cache-Texture" to improve speed of canvasses showing lots of symbols like eg. the navigation display. You will basically be able to set position (maybe rotation) and index of the symbol in the cache-texture and possibly a color for each instance...<ref>{{cite web |url=http://forum.flightgear.org/viewtopic.php?f=71&t=21139&start=60#p193925 |title=How to display Airport Chart?|author=TheTom |date=Tue Nov 12, 2013 9:54 am}}</ref>|TheTom}}
{{cquote| The issue there (at least based on the NavDisplay) is that there's quite high variance in the symbols, e.g. colour changes. For the ND I keep the symbols at greyscale, and colour them based on parameter data (active vs tuned vs inactive for navaids, for example)
Definitely needs some thought, but I think it can be an upgrade - we don't need to get the design right immediately! I do like the idea of the Canvas managing the symbol cache texture, however, since generating it by hand (as the TCAS and ND currently require) is annoying.<ref>{{cite web |url=http://forum.flightgear.org/viewtopic.php?f=71&t=17625#p166894 |title=Using a canvas map in the GUI|author=Zakalawe |date=Wed Sep 19, 2012 12:34 pm}}</ref>|Zakalawe}}
<references/>
== Open Issues ==
* Supporting multi-symbol instancing at the core canvas/C++ level, i.e. using "pointers" to point to another group, instead of replicating it over and over again
* re-implementing object "marking" (runways/parking) in the airports dialog
* Refining the model API
* Using the system for different dialogs & instruments
* generalizing the currently required dialog "prologue" in the Nasal/open block (using caller() and closure())
* supporting/addressing multiple canvases per dialog
* support all major canvas placement modes (CanvasWidget, CanvasWindow, Instrument, Scenery?)
* implementing a real MVC '''controller''' and porting the code to use it
* improving the MVC separation further
* resource cleanup (listeners & timers)
* start using the canvas-generic-map dialog in more dialogs (route manager, map dialog)
* port more dialogs
* support use in instruments
* add a stress test with multiple canvas maps per dialog, all showing different maps/locations, and being active


== Design ==
== Design ==
Line 71: Line 21:


== Supported Layers ==
== Supported Layers ==
As of 10/2012, the following "layers" are supported (not yet using [[MapStructure]]:
As of 10/2012, the following "layers" are supported (not yet using [[MapStructure]]):


* runways
* runways
Line 77: Line 27:
* parking
* parking
* tower
* tower
* navaids
At the moment (11/2013), we are working on additional layers - in order to port the [[Map]] and [[Navigation display]] display to [[Canvas]]. This will probably require support for:
* Routing (waypoints) {{Progressbar|80}} (by Gijs)
* Fixes {{Progressbar|80}} (by Gijs)
* multiplayer traffic {{Progressbar|80}} (by Gijs)
* AI traffic {{Progressbar|80}} (by Gijs)


See the forum for details: http://forum.flightgear.org/viewtopic.php?f=71&t=21139
See the forum for details: http://forum.flightgear.org/viewtopic.php?f=71&t=21139
Line 117: Line 58:
* come up with a framework for MFDs
* come up with a framework for MFDs
* use the MFD framework for creating a stylable ND class
* use the MFD framework for creating a stylable ND class
== Styling ==
Supporting styling is one thing that we missed in the first iteration - but obviously we need a way to allow end-users to customize their ND, by either providing a custom draw callback, by specifying a SVG files, or by implement a "Drawable" interface.
And looking at Gijs 744 code, we also need a way to "inject" custom logic to style/mark elements, depending in controller-specific conditions, such as the range selected in the cockpit, or in the GUI dialog - or the next waypoint.
== Optimization ==
Overall, we can work with the assumption that the aircraft is usually moving, so permanently updating its center/origin coordinates to selectively prune entries that are no longer in range should be more efficient than doing the whole thing in scripting space.
So there are probably some features that can be used to identify missing/useful extensions, not necessarily as dedicated canvas::elements, but rather as extensions to existing elements - once we have a more final version, we can do some profiling to find the low-hanging fruits.
Regarding spatial queries, on the nav-cache side, delta queries would be complex to support. What the C++ NavDisplay does is keep a persistent list which is updated infrequently - only when a setting changes (range, view options config), or when the aircraft moves > 1nm. In C++, computing the delta of two arrays is fast, and that's what I would suggest to avoid the 'remove all children' logic, which is obviously unfriendly to the Canvas. What I'm not sure about, is if Nasal currently has a nice API to compute the difference (added / removed items) between two arrays, but if it's missing I am sure it can be added.
On the canvas side, it would obviously be useful to address elements/groups by ID rather than index, because we could then selectively remove entries that are no longer valid. But maybe Tom has a better idea ?
== Full Example: Creating dialogs with embedded Canvas Maps ==
At the moment, the system (and its design) is still evolving - so nothing is set in stone, and most things written here should be considered a "draft". This is also why the system makes currently some assumptions and requires certain variables/functions to be specified in the dialog XML.
(For the latest information, you'll want to refer to [[$FG_ROOT]]/gui/dialogs/airports.xml as the "de facto" example of how to use the system)
Add this to the "Nasal/open" tag of your XML dialog and customize it for your dialog:
<syntaxhighlight lang="nasal">
## "prologue" currently required by the canvas-generic-map
      var dialog_name ="airports"; #TODO: use substr() and cmdarg() to get this dynamically
      var dialog_property = func(p) return "/sim/gui/dialogs/airports/"~p; #TODO: generalize using cmdarg     
      var DIALOG_CANVAS = gui.findElementByName(cmdarg(), "airport-selection");
      canvas.GenericMap.setupGUI(DIALOG_CANVAS, "canvas-control"); #TODO: this is not a method!
      ## end of canvas-generic-map prologue
</syntaxhighlight>
Note the string "airport-selection" which should match the name of of the subsequent canvas section.
Add this to a group in the dialog where you want the Canvas to appear
<syntaxhighlight lang="xml">
  <!-- Instantiate a generic canvas map and parametrize it via inclusion -->
      <!-- TODO: use params and aliasing -->
      <canvas include="/Nasal/canvas/generic-canvas-map.xml">
<name>airport-selection</name>
        <valign>fill</valign>
        <halign>fill</halign>
        <stretch>true</stretch>
        <pref-width>600</pref-width>
        <pref-height>400</pref-height>
        <view n="0">600</view>
        <view n="1">400</view>
      <features>
<!-- TODO: use params and aliases to make this shorter -->
<!-- TODO: support styling, i.e. image sets/fonts and colors to be used -->
<!-- this will set up individual "layers" and map them to boolean "toggle" properties -->
<!-- providing an optional "description" tag here allows us to create all checkboxes procedurally -->
<dialog-root>/sim/gui/dialogs/airports</dialog-root>
<range-property>zoom</range-property>
<!-- These are the ranges available for the map:      var ranges = [0.1, 0.25, 0.5, 1, 2.5, 5] -->
<ranges>
<range>0.1</range>
<range>0.25</range>
<range>0.5</range>
<range>1</range>
<range>2.5</range>
<range>5</range>
</ranges>
<!-- available layers and their toggle property (appended to dialog-root specified above) -->
<layer>
                <name>airport_test</name>
                <init-property>selected-airport/id</init-property>      <!-- the init/input property that re-inits the layer -->
                <property>display-test</property>                    <!-- property switch to toggle the layer on/off -->
                <description>Show TestLayer</description>              <!-- GUI label for the checkbox -->
                <default>disabled</default>                              <!-- default checkbox/layer state -->
                <hide-checkbox>true</hide-checkbox>                    <!-- for default layers so that the checkbox is hidden -->
        </layer>
<layer>
<name>runways</name>
<init-property>selected-airport/id</init-property>
<property>display-runways</property>
<description>Show Runways</description>
<default>enabled</default>
<hide-checkbox>true</hide-checkbox> 
</layer>
<layer>
                <name>taxiways</name>
<init-property>selected-airport/id</init-property>
                <property>display-taxiways</property>
<description>Show Taxiways</description>
<default>disabled</default>
        </layer>
<layer>
                <name>parkings</name>
<init-property>selected-airport/id</init-property>
                <property>display-parking</property>
<description>Show Parking</description>
<default>disabled</default>
        </layer>
<layer>
                <name>towers</name>
<init-property>selected-airport/id</init-property>
                <property>display-tower</property>
<description>Show Tower</description>
<default>enabled</default>
        </layer>
<!--
<layer>
                <name>navaid_test</name>
<init-property>selected-airport/id</init-property>
                <property>display-navaids</property>
<default>disabled</default>
        </layer>
-->
      </features>
      </canvas>
</syntaxhighlight>
Add this to the "Nasal/close" tag to clean up all resources automatically:
<syntaxhighlight lang="nasal">
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>
== How the whole thing works ==
This is targeted at people who are interested in helping improve the whole thing, so that they get a better understanding of how the whole thing hangs together at the moment.
For the time being, the implementation is specific to GUI dialogs - in that it will largely simplify (and even automate) the creation of GUI dialogs with an embedded canvas "map". This is accomplished using a handful of fixed assumptions, most of which will need to be addressed in order to support additional dialogs, and eventually, also non-GUI, i.e. instrument-use. Namely, that means:
* that people can simply instantiate a "GenericMap" by including an existing XML file
* dialog-specific settings can be overridden and customized (see above, or [[$FG_ROOT]]/gui/dialogs/airports.xml) - i.e. just copied/pasted
* the GenericMap is a LayeredMap where layers are created for different features and linked to toggle properties
* there are a handful of variables/functions that need to be declared in the body of the dialog's Nasal/open block
* in addition, 1-2 function calls need to be made during initialization, in the Nasal/OPEN section
* these are later on used by the system to instantiate all requested layers dynamically
* first of all, this is the "setupGUI" function, which will create the requested layers, and add GUI checkboxes and zoom buttons to the dialog automatically


== Also see ==
== Also see ==
* http://forum.flightgear.org/viewtopic.php?f=71&t=21139
* http://forum.flightgear.org/viewtopic.php?f=71&t=21139

Latest revision as of 14:33, 29 June 2014

IMPORTANT: Some, and possibly most, of the features/ideas discussed here are likely to be affected, and possibly even deprecated, by the ongoing work on providing a property tree-based 2D drawing API accessible from Nasal using the new Canvas system available since FlightGear 2.80 (08/2012). Please see: MapStructure for further information

You are advised not to start working on anything directly related to this without first discussing/coordinating your ideas with other FlightGear contributors using the FlightGear developers mailing list or the Canvas subforum This is a link to the FlightGear forum.. Anything related to Canvas Core Development should be discussed first of all with TheTom and Zakalawe. Nasal-space frameworks are being maintained by Philosopher and Hooray currently. talk page.

Canvasready.png


Background

Update 11/2013: please see MapStructure

As of FlightGear 2.9, the base package contains additional Canvas helpers to help with the creation of navigational displays (map dialogs or map instruments). These are currently under development and will change rapidly during the next weeks and months, so that we can come up with a generic and re-usable design for different dialogs/instruments.

Objective

Heavily reduce the amount of specialized Canvas/Nasal code in XML dialogs and instruments related to mapping/charting (airport-selection, map widget, navdisplay etc) and ensure that a maximum degree of code sharing is accomplished, regardless of the canvas placement mode that is used - bottom line being, it really shouldn't matter if a map is shown as part of an airport selection dialog, as part of the moving map dialog, as part of the navdisplay etc.

Design

The design follows the basic MVC (Model/View/Controller) principle, i.e.:

  • the Model contains the data to be shown
  • the View contains the Layer (Canvas group)
  • the Controller contains the interface to update the model and the view (using timers and/or listeners)


The basic idea is such that each layer is linked to a "control" property to easily toggle its visibility (this can be a GUI property for a checkbox or a cockpit hotspot), drawables (symbols) can be put in different layers and easily toggled on/off.

Supported Layers

As of 10/2012, the following "layers" are supported (not yet using MapStructure):

  • runways
  • taxiways
  • parking
  • tower

See the forum for details: http://forum.flightgear.org/viewtopic.php?f=71&t=21139

ToDo

Originally, the idea was that we want to avoid the ongoing degree of copy/paste programming that could be seen in the airport selection dialog - next, the 744 ND duplicates lots of code used in the airport dialog, but also in the underlying framework - all without using inheritance or delegates/predicates to customize things. So over time, we would end up with tons of custom code that is highly specific to each aircraft/display and GUI dialog.

From a low-level point of view, it shouldn't matter to the draw routines if they're called by an aircraft instrument or by a GUI dialog. However, once they contain use-case specific logic, such as getprop() calls and other conditionals, things do get messy pretty quickly. Thus, what I have been doing is simply copying things to separate *.draw files for different scenarios, i.e. I now ended up with airport.draw and airports-nd.draw for example - that isn't all that elegant, but at least it solves the problem of having to implement full models and controllers for each scenario (well for now), because all the embedded logic can stay, and only needs to be refactored later on.

Thus, the idea is basically this:

  • .draw files contain the low-level logic to draw arbitrary symbols (runway, waypoint, taxiways, airports, traffic) - without knowing anything about the frontend/user - so that such details needs to be separately encapsulated. The *.model files merely determine what is to be drawn, data-wise, as in NasalPositioned queries and their results - all stored in a single Nasal vector. The layer files turn everything into a dedicated canvas group that can be separately toggled/redrawn/updated - the currently missing bits are controllers that tie everything together, so that each frontend (instrument, MFD, GUI dialog, widget) can separately specify and customize behavior by registering their own callbacks.

Most of this is already working here (with very minor regressions so far that Gijs won't be too happy about ...), but otherwise I managed to turn Gijs code into layers/models that can be directly used by the old framework, i.e. I can now add a handful of new ND layers (fixes, vor, MP or AI traffic, route etc) to the airport selection dialog (or any other dialog actually), and things do show up properly.

What's still missing is the controller part, and styling for different aircraft/GUI purposes - also, Gijs' ND routines are currently used directly, without any LOD applied - but I think that can be easily solved by using the canvas's scale() method.

Just to clarify: stuff like getprop("instrumentation/nav[0]/frequencies/selected-mhz") doesn't belong into the visualization part of the code (i.e. the draw/view stuff) - simply because that's the sort of stuff that would "break" once we're using a different use-case, e.g. the map dialog - which is such conditions would need to be either specified in some form of condition hash, as part of the draw* signature, or via a dedicated Drawable class that's able to evaluate such conditions.

To be honest, that was the whole point of moving stuff to different *.layer and *.model files - so that these *.draw files can be shared, without requiring any modifications - your current example would still require using differrent *.draw files for each purpose.

  • Implementing proper LOD support for *.draw callbacks (i.e. using canvas/scaling)
  • Implementing support for different styles
  • Supporting multiple NDs per aircraft
  • Supporting multiple maps per dialog
  • Stress Test: render a dialog with 10 NDs, for 10 different MP/AI aircraft
  • clean up map.nas, get rid of stubs and old code
  • get rid of the PUI helpers in map.nas, and use real canvas dialogs instead
  • Optimize (Nasal/Canvas and C++ space)
  • come up with a framework for MFDs
  • use the MFD framework for creating a stylable ND class

Also see