From: Flightgear-commitlogs <ma...@hy...> - 2012-09-29 21:14:16
|
The branch, master has been updated - Log ----------------------------------------------------------------- commit 21d55d57afa443c5171383d261a625de41825cef Author: Stuart Buchanan Date: Sat Sep 29 21:24:12 2012 +0100 Fix bug where last axis of the joystick was written out with the original bindings of all buttons. commit 3f3dc9fef25b6bf94666c0fb8d536a8be6cbcb26 Author: Hooray Date: Fri Sep 21 01:49:17 2012 +0200 Canvas Scripting Layer (Mapping): - first stab at refactoring the map.nas module, and trying to let the API evolve according to our requirements - split up the module into separate files (some of them will disappear soon) - split up the "drawing" loops into separate functions so that they can be individually called - move actual "drawing" to map_layers.nas - introduce some OOP helpers to prepare a pure Layer-based design - prepare helpers: LayeredMap, GenericMap, AirportMap (TODO: use a real "Layer" class) - move airport features (taxiways, runways, parking, tower) to separate layers (i.e. canvas groups) - avoid using a single update callback and use different layer-specific callbacks to update individual layers more efficiently - add some boilerplate hashes to prepare the MVC design - allow lazy updating of layers, where canvas groups are only populated on demand, to save some time during instantiation, i.e. loading an airport without "parking" selected, will only populate the layer once the checkbox is checked - extend the original code such that it supports showing multiple airports at once - add some proof of concept "navaid" layer using SVG files for navaid symbols (added only NDB symbol from wikimedia commons) regressions: - runway highlighting needs to be re-implemented - parking highlighting will be done differently - enforcing a specific drawing order for layers is currently not explicitly supported, so that taxiways may be rendered on top of runways Also: - integrated with the latest changes in git/master (HEAD) -i.e. metar support - further generalized map.nas - partially moved instantiation from Nasal space to XML space (WIP) - create "toggle layer" checkboxes procedurally in Nasal space - prepared the code to be better reusable in other dialogs (e.g. route manager, map dialog etc) - completely removed the "highlighting" (runway/parking) feature for now, because we talked about re-implementing it anyhow - Summary ------------------------------------------------------------- Nasal/canvas/PropertyElement.nas | 7 +- Nasal/canvas/api.nas | 3 + Nasal/canvas/design.txt | 23 + Nasal/canvas/generic-canvas-map.xml | 105 +++ Nasal/canvas/map.nas | 735 +++++++++++++------ .../EADI.xml => Nasal/canvas/map/README.txt | 0 Nasal/canvas/map/airports.model | 27 + .../EADI.xml => Nasal/canvas/map/fixes.model | 0 Nasal/canvas/map/navaid.draw | 15 + Nasal/canvas/map/navaids.layer | 9 + Nasal/canvas/map/navaids.model | 11 + Nasal/canvas/map/parking.draw | 15 + Nasal/canvas/map/parking.layer | 9 + Nasal/canvas/map/runways.draw | 113 +++ Nasal/canvas/map/runways.layer | 10 + Nasal/canvas/map/taxiways.draw | 40 ++ Nasal/canvas/map/taxiways.layer | 11 + Nasal/canvas/map/test.layer | 11 + Nasal/canvas/map/tower.draw | 19 + Nasal/canvas/map/tower.layer | 8 + .../EADI.xml => Nasal/canvas/map/waypoints.model | 0 Nasal/joystick.nas | 2 +- gui/dialogs/airports.xml | 285 +++----- gui/dialogs/images/ndb_symbol.svg | 744 ++++++++++++++++++++ 24 files changed, 1798 insertions(+), 404 deletions(-) create mode 100644 Nasal/canvas/design.txt create mode 100644 Nasal/canvas/generic-canvas-map.xml copy Aircraft/767-300/Models/2Dpanel/Instruments/EADI.xml => Nasal/canvas/map/README.txt (100%) create mode 100644 Nasal/canvas/map/airports.model copy Aircraft/767-300/Models/2Dpanel/Instruments/EADI.xml => Nasal/canvas/map/fixes.model (100%) create mode 100644 Nasal/canvas/map/navaid.draw create mode 100644 Nasal/canvas/map/navaids.layer create mode 100644 Nasal/canvas/map/navaids.model create mode 100644 Nasal/canvas/map/parking.draw create mode 100644 Nasal/canvas/map/parking.layer create mode 100644 Nasal/canvas/map/runways.draw create mode 100644 Nasal/canvas/map/runways.layer create mode 100644 Nasal/canvas/map/taxiways.draw create mode 100644 Nasal/canvas/map/taxiways.layer create mode 100644 Nasal/canvas/map/test.layer create mode 100644 Nasal/canvas/map/tower.draw create mode 100644 Nasal/canvas/map/tower.layer copy Aircraft/767-300/Models/2Dpanel/Instruments/EADI.xml => Nasal/canvas/map/waypoints.model (100%) create mode 100644 gui/dialogs/images/ndb_symbol.svg - Diff ---------------------------------------------------------------- diff --git a/Nasal/canvas/PropertyElement.nas b/Nasal/canvas/PropertyElement.nas index 613df1a..3b8f2c2 100644 --- a/Nasal/canvas/PropertyElement.nas +++ b/Nasal/canvas/PropertyElement.nas @@ -60,5 +60,10 @@ var PropertyElement = { return node.getValue(); else return default; - } + }, + getBool: func(key) + { + me._node.getNode(key, 1).getBoolValue(); + }, + }; diff --git a/Nasal/canvas/api.nas b/Nasal/canvas/api.nas index 643efab..4d3ef04 100644 --- a/Nasal/canvas/api.nas +++ b/Nasal/canvas/api.nas @@ -218,10 +218,13 @@ var Element = { { me.setBool("visible", visible); }, + getVisible: func me.getBool("visible"), # Hide element (Shortcut for setVisible(0)) hide: func me.setVisible(0), # Show element (Shortcut for setVisible(1)) show: func me.setVisible(1), + # Toggle element visibility + toggleVisibility: func me.setVisible( !me.getVisible() ), # setGeoPosition: func(lat, lon) { diff --git a/Nasal/canvas/design.txt b/Nasal/canvas/design.txt new file mode 100644 index 0000000..7db8f49 --- /dev/null +++ b/Nasal/canvas/design.txt @@ -0,0 +1,23 @@ +Nothing set in stone yet, we'll document things once the API becomes more stable and once it has been used in several dialogs and instruments + +At the moment, this implements the notion of a "LayeredMap", a LayeredMap is a conventional Canvas Map which has support for easily managing "Layers", +which are internally mapped to Canvas Groups. Each Group's "visible" property is managed by the LayeredMap, so that layers can be easily +toggled on/off, i.e. via checkboxes. + +Basically, the idea is this, we'll have a MVC (Model/View/Controller) setup, where: + - the Model is mapped to the drawable's meta information (i.e. position) + - the View is mapped to a conventional canvas group + - the Controller is mapped to a bunch of property/timer callbacks to control the Model/View + + + + +Model = PositionedSource +View = Canvas +Controller = control properties (zoom, range etc) + +LayerElement = callback to create a canvas group +Layer = canvas.Group + +Map -> LayeredMap -> GenericMap -> AirportMap + diff --git a/Nasal/canvas/generic-canvas-map.xml b/Nasal/canvas/generic-canvas-map.xml new file mode 100644 index 0000000..01a6151 --- /dev/null +++ b/Nasal/canvas/generic-canvas-map.xml @@ -0,0 +1,105 @@ +<?xml version="1.0"?> +<!-- + generic-canvas.map XML: + - to be used by dialogs and instruments to add a generic map (navaids, fixes, airports etc) + - with each feature put on a separate layer (canvas group) + - each layer being controllable via a boolean property + +NOTE: This is still work in progress, and will be significantly refactored in the time to come + +Current requirements: (these are subject to change) + +Dialogs wanting to use this, MUST: + + - set DIALOG_CANVAS in open block + - provide a helper function dialog_property(p) to return a property appended to the dialog root in /sim/gui/dialogs/FOO/ + - to set up layer-checkboxes automatically, use canvas.GenericMap.setupGUICheckboxes(DIALOG_CANVAS, gui_group) + +For example, add this to your dialogs Nasal/open block in "foo.xml": + var dialog_name = "foo"; + var dialog_property = func(p) return "/sim/gui/dialogs/foo/"~p; + var DIALOG_CANVAS = gui.findElementByName(cmdarg(), "airport-selection"); + canvas.GenericMap.setupGUICheckboxes(DIALOG_CANVAS, "canvas-control"); + +TODO: use a single "InitCanvasMapSupport();" helper + +In the close block, you'll want to call "map.cleanup_listeners()" at the moment + +--> +<PropertyList> + <!--FIXME: move somewhere else, this is GUI specific and not useful for canvas maps shown as instruments! --> + <checkbox-toggle-template> + <name></name> + <label></label> + <property></property> + <binding> + <command>dialog-apply</command> + <object-name></object-name> + </binding> + </checkbox-toggle-template> + + <!-- will be procedurally added to the dialog --> + <zoom-template> + <button> + <name>zoomout</name> + <legend>-</legend> + <pref-width>22</pref-width> + <pref-height>22</pref-height> + + <binding> + <command>property-adjust</command> + <property></property> + <min>0</min> + <step>-1</step> + </binding> + </button> + + <text> + <label>MMMM</label> + <halign>center</halign> + <format>Zoom %d</format> + <property></property> + <live>true</live> + </text> + + <button> + <name>zoomin</name> + <legend>+</legend> + <pref-width>22</pref-width> + <pref-height>22</pref-height> + + <binding> + <command>property-adjust</command> + <property></property> + <step>1</step> + <max></max> <!-- FIXME: compute dynamically via Nasal size() or just a property--> + </binding> + </button> + + <empty><stretch>true</stretch></empty> + + </zoom-template> + + <nasal> + <load><![CDATA[ + var my_canvas = canvas.get(cmdarg()); + my_canvas.setColorBackground(0.2, 0.5, 0.2, 0.5); #TODO: support customization in XML + + var root = my_canvas.createGroup(); + # the top level AirportMap element uses a "GenericMap" now: + + #TODO: features should be procedurally enabled via params (WIP) + #TODO: use generic Map and instantiate via XML + var map = canvas.GenericMap.new(parent:root, name:dialog_name) # FIXME: We shouldn't be using AirportMap here: + # we need a high level wrapper that can instantiate + # all sorts of maps, not just AirportMaps + .setTranslation(300, 200) # TODO: move to Map class ctor! + .setupZoom( dialog:DIALOG_CANVAS ) # TODO: make zooming configurable for non GUI use + .pickupFeatures (DIALOG_CANVAS); # set up the features specified in the XML file + + # FIXME: resource cleanup (listeners!) + update_info(); + ]]> + </load> + </nasal> +</PropertyList> diff --git a/Nasal/canvas/map.nas b/Nasal/canvas/map.nas index 8aaeab8..768e663 100644 --- a/Nasal/canvas/map.nas +++ b/Nasal/canvas/map.nas @@ -1,5 +1,56 @@ +### +# map.nas - provide a high level method to create typical maps in FlightGear (airports, navaids, fixes and waypoints) for both, the GUI and instruments +# implements the notion of a "layer" by using canvas groups and adding geo-referenced elements to a layer +# layered maps are linked to boolean properties so that visibility can be easily toggled (GUI checkboxes or cockpit hotspots) +# without having to redraw other layers +# +# GOALS: have a single Nasal/Canvas wrapper for all sort of maps in FlightGear, that can be easily shared and reused for different purposes +# +# DESIGN: ... is slowly evolving, but still very much beta for the time being +# +# API: not yet documented, but see eventually design.txt (will need to add doxygen-strings then) +# +# PERFORMANCE: will be improved, probabaly by moving some features to C++ space and optimizing things there +# +# +# ISSUES: just look for the FIXME and TODO strings - currently, the priority is to create an OOP/MVC design with less specialized code in XML files +# +# +# ROADMAP: Generalize this further, so that: +# +# - it can be easily reused +# - use a MVC approach, where layer-specific data is provided by a Model object +# - other dialogs can use this without tons of custom code (airports.xml, route-manager.xml, map-canvas.xml) +# - generalize this further so that it can be used by instruments +# - implement additional layers (tcas, wxradar, agradar) - especially expose the required data to Nasal +# - implement better GUI support (events) so that zooming/panning via mouse can be supported +# - make the whole thing styleable +# +# - keep track of things getting added here and decide if they should better move to the core canvas module or the C++ code +# +# +# C++ RFEs: +# - overload findNavaidsWithinRange() to support an optional position argument, so that arbitrary navaids can be looked up +# - add Nasal extension function to get scenery vector data (landclass) +# - +# - +# + + + +var DEBUG=0; +if (DEBUG) { + var benchmark = debug.benchmark; + + } + +else { + var benchmark = func(label, code) code(); # NOP + } -# Mapping from surface codes to +var assert = func(label, expr) expr and die(label); + +# Mapping from surface codes to #TODO: make this XML-configurable var SURFACECOLORS = { 1 : { type: "asphalt", r:0.2, g:0.2, b:0.2 }, 2 : { type: "concrete", r:0.3, g:0.3, b:0.3 }, @@ -14,6 +65,25 @@ var SURFACECOLORS = { 0 : { type: "gravel", r:0.35, g:0.3, b:0.3 }, }; + +### +# ALL LayeredMap "draws" go through this wrapper, which makes it easy to check what's going on: +var draw_layer = func(layer, callback, lod) { + var name= layer._view.get("id"); + # print("Canvas:Draw op triggered"); # just to make sure that we are not adding unnecessary data when checking/unchecking a checkbox + if (DEBUG and name=="taxiways") fgcommand("profiler-start"); #without my patch, this is a no op, so no need to disable + #print("Work items:", size(layer._model._elements)); + benchmark("Drawing Layer:"~layer._view.get("id"), func + foreach(var element; layer._model._elements) { + #print(typeof(layer._view)); + #debug.dump(layer._view); + callback(layer._view, element, lod); # ISSUE here + }); + if (! layer._model.hasData() ) print("Layer was EMPTY:", name); + if (DEBUG and name=="taxiways") fgcommand("profiler-stop"); + layer._drawn=1; #TODO: this should be encapsulated +} + # Runway # var Runway = { @@ -45,209 +115,466 @@ var Runway = { } }; -# AirportMap +var make = func return {parents:arg}; + +## +# TODO: Create a cache to reuse layers and layer data (i.e. runways) + + +## +# Todo: wrap parsesvg and return a function that memoizes the created canvas group, so that svg files only need to be parsed once # -var AirportMap = { - # Create AirportMap from hash - # - # @param apt Hash containing airport data as returned from airportinfo() - # @param rwy Whether to display runways (default=1) - # @param taxi Whether to display taxiways (default=1) - # @param park Whether to display parking positions (default = 1) - # @param twr Whether to display tower positions (default = 1) - new: func(apt, rwy=1, taxi=1, park=1, twr=1) - { - return { - parents: [AirportMap], - _apt: apt, - _display_runways: rwy, - _display_taxiways: taxi, - _display_parking: park, - _display_tower: twr, - }; - }, - # Build the graphical representation of the represented airport - # - # @param layer_runways canvas.Group to attach airport map to - build: func(layer_runways) - { - var rws_done = {}; - - me.grp_apt = layer_runways.createChild("group", "apt-" ~ me._apt.id); - - # Taxiways drawn first so the runways and parking positions end up on top. - if (me._display_taxiways) - { - foreach(var taxi; me._apt.taxiways) - { - var clr = SURFACECOLORS[taxi.surface]; - if (clr == nil) { clr = SURFACECOLORS[0]}; - - var icon_taxi = - me.grp_apt.createChild("path", "taxi") - .setStrokeLineWidth(0) - .setColor(clr.r, clr.g, clr.b) - .setColorFill(clr.r, clr.g, clr.b); - - var txi = Runway.new(taxi); - var beg1 = txi.pointOffCenterline(0, 0.5 * taxi.width); - var beg2 = txi.pointOffCenterline(0, -0.5 * taxi.width); - var end1 = txi.pointOffCenterline(taxi.length, 0.5 * taxi.width); - var end2 = txi.pointOffCenterline(taxi.length, -0.5 * taxi.width); - - icon_taxi.setDataGeo - ( - [ canvas.Path.VG_MOVE_TO, - canvas.Path.VG_LINE_TO, - canvas.Path.VG_LINE_TO, - canvas.Path.VG_LINE_TO, - canvas.Path.VG_CLOSE_PATH ], - [ beg1[0], beg1[1], - beg2[0], beg2[1], - end2[0], end2[1], - end1[0], end1[1] ] - ); - } - } - - if (me._display_runways) - { - foreach(var rw; keys(me._apt.runways)) - { - var is_heli = substr(rw, 0, 1) == "H"; - var rw_dir = is_heli ? nil : int(substr(rw, 0, 2)); - - var rw_rec = ""; - var thresh_rec = 0; - if( rw_dir != nil ) - { - rw_rec = sprintf("%02d", math.mod(rw_dir - 18, 36)); - if( size(rw) == 3 ) - { - var map_rec = { - "R": "L", - "L": "R", - "C": "C" - }; - rw_rec ~= map_rec[substr(rw, 2)]; - } - - if( rws_done[rw_rec] != nil ) - continue; - - var rw_rec = me._apt.runways[rw_rec]; - if( rw_rec != nil ) - thresh_rec = rw_rec.threshold; - } - - rws_done[rw] = 1; - - rw = me._apt.runways[rw]; - - var clr = SURFACECOLORS[rw.surface]; - if (clr == nil) { clr = SURFACECOLORS[0]}; + +## +# TODO: Implement a real MVC design for "LayeredMaps" that have: +# - a "DataProvider" (i.e. Positioned objects) +# - a View (i.e. a Canvas) +# - a controller (i.e. input/output properties) +# +var MapModel = {}; # navaids, waypoints, fixes etc + MapModel.new = func make(MapModel); + +var MapView = {}; # the canvas view, including a layer for each feature + MapView.new = func make(MapView); + +var MapController = {}; # the property tree interface to manipulate the model/view via properties + MapController.new = func make(MapController); + +var LazyView = {}; # Gets drawables on demand from the model - via property toggle + +var DataProvider = {}; + DataProvider.new = func make(DataProvider); + +### +# for airports, navaids, fixes, waypoints etc +var PositionedProvider = {}; + PositionedProvider.new = func make(DataProvider, PositionedProvider); + +## +# Drawable +# + +## LayerElement (UNUSED ATM): +# for runways, navaids, fixes, waypoints etc +# TODO: we should differentiate between "fairly static" vs. "dynamic" layers - i.e. navaids vs. traffic +var LayerElement = {_drawable:nil}; + LayerElement.new = func(drawable) { + var temp = make(LayerElement); + temp._drawable=drawable; + return temp; +} + # a drawable is either a Nasal callback or a scalar, i.e. a path to an SVG file + LayerElement.draw = func(group) { + (typeof(me._drawable)=='func') and drawable(group) or canvas.parsesvg(group,_drawable); + } + +# For static targets like Navaids, Fixes - i.e. geographic position doesn't change +var StaticLayerElement = {}; + +# For moving targets such as aircraft, multiplayer, ai traffic etc +var DynamicLayerElement = {}; + +var AnimatedLayerElement = {}; + +# for elements whose appearance may change depending on selected range (i.e. LOD) +var RangeAwareLayerElement = {}; + + +## +# A layer model is just a wrapper for a vector with elements +# either updated via a timer or via a listener + +var LayerModel = {_elements:[], _view:, _controller: }; + 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); + LayerModel.setView = func(v) me._view=v; + LayerModel.setController = func(c) me._controller=c; + + +var LayerController = {}; + LayerController.new = func make(LayerController); + +## +# use timers to update the model/view (canvas) +var TimeBasedLayerController = {}; + LayerController.new = func make(TimeBasedLayerController); + +## +# use listeners to update the model/view (canvas) +# +var ListenerBasedLayerController = {}; + ListenerBasedLayerController.new = func make(ListenerBasedLayerController); + + +## +# Uses, both, listeners and timers to update the model/view (canvas) +# + +var HybridLayerController = {}; + HybridLayerController.new = func make(HybridLayerController); + +var ModelEvents = {INIT:, RESET:, UPDATE:}; +var ViewEvents = {INIT:, RESET:, UPDATE:}; +var ControllerEvents = {INIT:, RESET: , UPDATE:, ZOOM:, PAN:, }; + + +## +# A layer is mapped to a canvas group +# Layers are linked to a single boolean property to toggle them on/off +var Layer = { _model: , + _view: , + _controller: , + _drawn:0, + }; + + Layer.new = func(group, name, model) { + #print("Setting up new Layer:", name); + var m = make(Layer); + m._model = model.new(); + #print("Model name is:", m._model.name); + m._view = group.createChild("group",name); + m.name = name; #FIXME: not needed, there's already _view.get("id") + return m; +} + + Layer.hide = func me._view.setVisible(0); + Layer.show = func me._view.setVisible(1); + #TODO: Unify toggle and update methods - and support lazy drawing (make it optional!) + Layer.toggle = func { + # print("Toggling layer"); + var checkbox = getprop(me.display_layer); + if(checkbox and !me._drawn) { + # print("Lazy drawing"); + me.draw(); + } + + #var state= me._view.getBool("visible"); + #print("Toggle layer visibility ",me.display_layer," checkbox is", checkbox); + #print("Layer id is:", me._view.get("id")); + #print("Drawn is:", me._drawn); + checkbox?me._view.setVisible(1) : me._view.setVisible(0); + } + Layer.reset = func { + me._view.removeAllChildren(); # clear the "real" canvas drawables + me._model.clear(); # the vector is used for lazy rendering + assert("Model not emptied during layer reset!", me._model.hasData() ); + me._drawn = 0; + } + #TODO: Unify toggle and update + Layer.update = func { + # print("Layer update: Check if layer is visible, if so, draw"); + if (! getprop(me.display_layer)) return; # checkbox for layer not set + if (!me._model.hasData() ) return; # no data available + # print("Trying to draw"); + me.draw(); +} + +Layer.setDraw = func(callback) me.draw = callback; +Layer.setController = func(c) me._controller=c; # TODO: implement +Layer.setModel = func(m) nil; # TODO: implement + + +##TODO: differentiate between layers with a single object (i.e. aircraft) and multiple objects (airports) + +## +# We may need to display some stuff that isn't strictly a geopgraphic feature, but just a chart feature +# +var CartographicLayer = {}; + +#TODO: +var InteractiveLayer = {}; + +### +# PositionedLayer +# +# layer of positioned objects (i.e. have lat,lon,alt) +# +var PositionedLayer = {}; + PositionedLayer.new = func() { + make( Layer.new() , PositionedLayer ); + } + + +### +# CachedLayer +# +# when re-centering on an airport already loaded, we don't want to reload it +# but change the reference point and load missing airports + +var CachedLayer = {}; + +## +# +var AirportProvider = {}; + AirportProvider.new = func make(AirportProvider); + AirportProvider.get = func { + return airportinfo("ksfo"); +} + +### Data Providers (preparation for MVC version): +# TODO: should use the LayerModel class +# + +## +# Manage a bunch of layers +# + +var LayerManager = {}; + +# WXR ? + +# TODO: Stub +var MapBehavior = {}; + MapBehavior.new = make(MapBehavior); + MapBehavior.zoom = func; + MapBehavior.center = func; + +## +# A layered map consists of several layers +# TODO: Support nested LayeredMaps, where a LayeredMap may contain other LayeredMaps +# TODO: use MapBehavior here and move the zoom/refpos methods there, so that map behavior can be easily customized +var LayeredMap = { ranges:[], + zoom_property:nil, listeners:[], + update_property:nil, layers:[], + }; + LayeredMap.new = func(parent, name) + return make(LayeredMap, parent.createChild("map",name) ); + + LayeredMap.listen = func(p,c) { #FIXME: listening should be managed by each m/v/c separately + # print("Setting up LayeredMap-managed listener:", p); + append(me.listeners, setlistener(p, c)); + } + + LayeredMap.initializeLayers = func { + # print("initializing all layers and updating"); + foreach(var l; me.layers) + l.update(); + } + + LayeredMap.setRefPos = func(lat, lon) { + # print("RefPos set"); + me._node.getNode("ref-lat", 1).setDoubleValue(lat); + me._node.getNode("ref-lon", 1).setDoubleValue(lon); + me; # chainable + } + LayeredMap.setHdg = func(hdg) { + me._node.getNode("hdg",1).setDoubleValue(hdg); + me; # chainable + } + + LayeredMap.updateZoom = func { + var z = getprop(me.zoom_property) or 0; + var zoom = me.ranges[ size(me.ranges)-1 -z]; + # print("Setting zoom range to:", zoom); + benchmark("Zooming map:"~zoom, func + me._node.getNode("range", 1).setDoubleValue(zoom) + ); + me; #chainable + } + + # this is a huge hack at the moment, we need to encapsulate the setRefPos/setHdg methods, so that they are exposed to XML space + # +LayeredMap.updateState = func { + # center map on airport TODO: should be moved to a method and wrapped with a controller so that behavior can be customizeda + #var apt = me.layers[0]._model._elements[0]; + # FIXME: + #me.setRefPos(lat:me._refpos.lat, lon:me._refpos.lon); + + me.setHdg(0.0); + me.updateZoom(); +} + +# +# TODO: this is currently GUI specific and not re-usable for instruments + LayeredMap.setupZoom = func(dialog) { + var dlgroot = dialog.getNode("features/dialog-root").getValue();#FIXME: GUI specific - needs to be re-implemented for instruments + var zoom_property = dlgroot ~"/"~dialog.getNode("features/range-property").getValue(); #FIXME: this doesn't belong here, need to be in ctor instead !!! + ranges=dialog.getNode("features/ranges").getChildren("range"); + foreach(var r; ranges) + append(me.ranges, r.getValue() ); + + # print("Setting up Zoom Ranges:", size(ranges)-1); + me.zoom_property=zoom_property; + me.listen(zoom_property, func me.updateZoom() ); + me.updateZoom(); + me; #chainable + } + LayeredMap.setZoom = func {} #TODO + + LayeredMap.resetLayers = func { + + benchmark("Resetting LayeredMap", func + foreach(var l; me.layers) { #TODO: hide all layers, hide map + l.reset(); + } + ); + + +} + + #FIXME: listener management should be done at the MVC level, for each component - not as part of the LayeredMap! + LayeredMap.cleanup_listeners = func { + print("Cleaning up listeners"); + foreach(var l; me.listeners) + removelistener(l); - var icon_rw = - me.grp_apt.createChild("path", "runway-" ~ rw.id) - .setStrokeLineWidth(0.5) - .setColor(1.0,1.0,1.0) - .setColorFill(clr.r, clr.g, clr.b); - - var rwy = Runway.new(rw); - var beg_thr = rwy.pointOffCenterline(rw.threshold); - var beg_thr1 = rwy.pointOffCenterline(rw.threshold, 0.5 * rw.width); - var beg_thr2 = rwy.pointOffCenterline(rw.threshold, -0.5 * rw.width); - var beg1 = rwy.pointOffCenterline(0, 0.5 * rw.width); - var beg2 = rwy.pointOffCenterline(0, -0.5 * rw.width); - - var end_thr = rwy.pointOffCenterline(rw.length - thresh_rec); - var end_thr1 = rwy.pointOffCenterline(rw.length - thresh_rec, 0.5 * rw.width); - var end_thr2 = rwy.pointOffCenterline(rw.length - thresh_rec, -0.5 * rw.width); - var end1 = rwy.pointOffCenterline(rw.length, 0.5 * rw.width); - var end2 = rwy.pointOffCenterline(rw.length, -0.5 * rw.width); - - icon_rw.setDataGeo - ( - [ canvas.Path.VG_MOVE_TO, - canvas.Path.VG_LINE_TO, - canvas.Path.VG_LINE_TO, - canvas.Path.VG_LINE_TO, - canvas.Path.VG_CLOSE_PATH ], - [ beg1[0], beg1[1], - beg2[0], beg2[1], - end2[0], end2[1], - end1[0], end1[1] ] - ); - - if( rw.length / rw.width > 3 and !is_heli ) - { - # only runways which are much longer than wide are - # real runways, otherwise it's probably a heliport. - var icon_cl = - me.grp_apt.createChild("path", "centerline") - .setStrokeLineWidth(0.5) - .setColor(1,1,1) - .setStrokeDashArray([15, 10]); - - icon_cl.setDataGeo - ( - [ canvas.Path.VG_MOVE_TO, - canvas.Path.VG_LINE_TO ], - [ beg_thr[0], beg_thr[1], - end_thr[0], end_thr[1] ] - ); - - var icon_thr = - me.grp_apt.createChild("path", "threshold") - .setStrokeLineWidth(1.5) - .setColor(1,1,1); - - icon_thr.setDataGeo - ( - [ canvas.Path.VG_MOVE_TO, - canvas.Path.VG_LINE_TO, - canvas.Path.VG_MOVE_TO, - canvas.Path.VG_LINE_TO ], - [ beg_thr1[0], beg_thr1[1], - beg_thr2[0], beg_thr2[1], - end_thr1[0], end_thr1[1], - end_thr2[0], end_thr2[1] ] - ); - } - } - } - - if (me._display_parking) - { - foreach(var park; me._apt.parking()) - { - var icon_park = - me.grp_apt.createChild("text", "parking-" ~ park.name) - .setDrawMode( canvas.Text.ALIGNMENT - + canvas.Text.TEXT ) - .setText(park.name) - .setFont("LiberationFonts/LiberationMono-Bold.ttf") - .setGeoPosition(park.lat, park.lon) - .setFontSize(15, 1.3); - } - } - - if (me._display_tower) - { - var icon_tower = - me.grp_apt.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); - - var pos = me._apt.tower(); - icon_tower.setGeoPosition(pos.lat, pos.lon); - } - } + } + +### +# GenericMap: A generic map is a layered map that puts all supported features on a different layer (canvas group) so that +# they can be individually toggled on/off so that unnecessary updates are avoided, there are methods to link layers to boolean properties +# so that they can be easily associated with GUI properties (checkboxes) or cockpit hotspots +# TODO: generalize the XML-parametrization and move it to a helper class + +var GenericMap = { }; + GenericMap.new = func(parent, name) make(LayeredMap.new(parent:parent, name:name), GenericMap); + + GenericMap.setupLayer = func(layer, property) { + var l = MAP_LAYERS[layer].new(me, layer); # Layer.new(me, layer); + l.display_layer = property; #FIXME: use controller object instead here and this overlaps with update_property + #print("Set up layer with toggle property=", property); + l._view.setVisible( getprop(property) ) ; + append(me.layers, l); + return l; + } + + # features are layers - so this will do layer setup and then register listeners for each layer + GenericMap.setupFeature = func(layer, property, init ) { + var l=me.setupLayer( layer, property ); + me.listen(property, func l.toggle() ); #TODO: should use the controller object here ! + + l._model._update_property=property; #TODO: move somewhere else - this is the property that is mapped to the CHECKBOX + l._model._view_handle = l; #FIXME: very crude, set a handle to the view(group), so that the model can notify it (for updates) + l._model._map_handle = me; #FIXME: added here so that layers can send update requests to the parent map + #print("Setting up layer init for property:", init); + + l._model._input_property = init; # FIXME: init property = input property - needs to be improved! + me.listen(init, func l._model.init() ); #TODO: makes sure that the layer's init method for the MODEL is invoked + me; #chainable }; + + # This will read in the config and procedurally instantiate all requested layers and link them to toggle properties + # FIXME: this is currently GUI specific and doesn't yet support instrument use, i.e. needs to be generalized further + GenericMap.pickupFeatures = func(DIALOG_CANVAS) { + var dlgroot = DIALOG_CANVAS.getNode("features/dialog-root").getValue(); + # print("Picking up features for:", DIALOG_CANVAS.getPath() ); + var layers=DIALOG_CANVAS.getNode("features").getChildren("layer"); + foreach(var n; layers) { + var name = n.getNode("name").getValue(); + var toggle = n.getNode("property").getValue(); + var init = n.getNode("init-property").getValue(); + init = dlgroot ~"/"~init; + var property = dlgroot ~"/"~toggle; + # print("Adding layer:",n.getNode("name").getValue() ); + me.setupFeature(name, property, init); + } + me; +} + + # NOT a method, cmdarg() is no longer meaningful when the canvas nasal block is executed + # so this needs to be called in the dialog's OPEN block instead - TODO: generalize + #FIXME: move somewhere else, this is a GUI helper and should probably be generalized and moved to gui.nas +GenericMap.setupGUI = func (dialog, group) { + var group = gui.findElementByName(cmdarg() , group); + + var layers=dialog.getNode("features").getChildren("layer"); + var template = dialog.getNode("checkbox-toggle-template"); + var dlgroot = dialog.getNode("features/dialog-root").getValue(); + var zoom = dlgroot ~"/"~ dialog.getNode("features/range-property").getValue(); + var i=0; + foreach(var n; layers) { + var name = n.getNode("name").getValue(); + var toggle = dlgroot ~ "/" ~ n.getNode("property").getValue(); + var label = n.getNode("description",1).getValue() or name; + + var default = n.getNode("default",1).getValue(); + default = (default=="enabled")?1:0; + #print("Layer default for", name ," is:", default); + setprop(toggle, default); # set the checkbox to its default setting + + var hide_checkbox = n.getNode("hide-checkbox",1).getValue(); + hide_checkbox = (hide_checkbox=="true")?1:0; + + var checkbox = group.getChild("checkbox",i, 1); #FIXME: compute proper offset dynamically, will currently overwrite other existing checkboxes! + + props.copy(template, checkbox); + checkbox.getNode("name").setValue("display-"~name); + checkbox.getNode("label").setValue(label); + checkbox.getNode("property").setValue(toggle); + checkbox.getNode("binding/object-name").setValue("display-"~name); + checkbox.getNode("enabled",1).setValue(!hide_checkbox); + i+=1; + } + + #add zoom buttons procedurally: + var template = dialog.getNode("zoom-template"); + template.getNode("button[0]/binding[0]/property[0]").setValue(zoom); + template.getNode("text[0]/property[0]").setValue(zoom); + template.getNode("button[1]/binding[0]/property[0]").setValue(zoom); + template.getNode("button[1]/binding[0]/max[0]").setValue( i ); + props.copy(template, group); + } + +### +# TODO: StylableGenericMap (colors, fonts, symbols) +# + +var AirportMap = {}; + AirportMap.new = func(parent,name) make(GenericMap.new(parent,name), AirportMap); + #TODO: Use real MVC (DataProvider/PositionedProvider) here + + +# this is currently "directly" invoked via a listener, needs to be changed +# to use the controller object instead +# TODO: adopt real MVC here +# FIXME: this must currently be explicitly called by the model, we need to use a wrapper to call it automatically instead! +LayerModel.notifyView = func () { + # print("View notified"); + me._view_handle.update(); # update the layer/group + me._map_handle.updateState(); # update the map +} + +# ID +var SingleAirportProvider = {}; + +# inputs: position, range +var MultiAirportProvider = {}; + +#TODO: remove and unify with update() +AirportMap.init = func { + me.resetLayers(); + me.updateState(); +} + +# MultiObjectLayer: +# - Airports +# - Traffic (MP/AI) +# - Navaids +# + +# TODO: a "MapLayer" is a full MVC implementation that is owned by a "LayeredMap" + +var MAP_LAYERS = {}; +var register_layer = func(name, layer) MAP_LAYERS[name]=layer; + +var MVC_FOLDER = getprop("/sim/fg-root") ~ "/Nasal/canvas/map/"; +var load_modules = func(vec) foreach(var file; vec) io.load_nasal(MVC_FOLDER~file, "canvas"); + +# TODO: read in the file names dynamically: *.draw, *.model, *.layer + +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); + +#TODO: Implement! +var CONTROLLERS = []; +load_modules(CONTROLLERS); diff --git a/Aircraft/767-300/Models/2Dpanel/Instruments/EADI.xml b/Nasal/canvas/map/README.txt similarity index 100% copy from Aircraft/767-300/Models/2Dpanel/Instruments/EADI.xml copy to Nasal/canvas/map/README.txt diff --git a/Nasal/canvas/map/airports.model b/Nasal/canvas/map/airports.model new file mode 100644 index 0000000..5dc35e4 --- /dev/null +++ b/Nasal/canvas/map/airports.model @@ -0,0 +1,27 @@ +var AirportModel = {}; + AirportModel.new = func make(AirportModel, LayerModel); + +# FIXME: Just testing for now: This really shouldn't be part of the core LayerModel, needs to go to "AirportModel" instead +# FIXME: This will get called ONCE for EACH layer that uses the AirportModel, so VERY inefficient ATM! => should be shared among layers +AirportModel.init = func { + # print("AirportModel initialized!"); + # me._map_handle.resetLayers(); + me._view_handle.reset(); + var id = getprop(me._input_property); # HACK: this needs to be handled via the controller - introduce "input_property" + #print("ID is:", id); + (id == "") and return; + var apt=airportinfo(id); # FIXME: replace with controller call to update the model + #var airports = findAirportsWithinRange(apt.lat, apt.lon, 10); # HACK: expose the range !! + foreach(var a; [ apt ]) #FIXME: move to separate method: "populate" + # print("storing:", a.id) and + me.push(a); + #print("Work items in Model:", me.hasData() ); + #print("Model updated!!"); + + # set RefPos and hdg to apt !! + me._map_handle.setRefPos(apt.lat, apt.lon); + + #TODO: Notify view on update - use proper NOTIFICATIONS (INIT; UPDATE etc) + me.notifyView(); +} + diff --git a/Aircraft/767-300/Models/2Dpanel/Instruments/EADI.xml b/Nasal/canvas/map/fixes.model similarity index 100% copy from Aircraft/767-300/Models/2Dpanel/Instruments/EADI.xml copy to Nasal/canvas/map/fixes.model diff --git a/Nasal/canvas/map/navaid.draw b/Nasal/canvas/map/navaid.draw new file mode 100644 index 0000000..c053e76 --- /dev/null +++ b/Nasal/canvas/map/navaid.draw @@ -0,0 +1,15 @@ +## +# FIXME: until we have better instancing support for symbols, it would be better to return a functor here +# so that symbols are only parsed once +var NAVAID_CACHE = {}; +var draw_navaid = func (group, navaid, lod) { + #var group = group.createChild("group", "navaid"); + DEBUG and print("Drawing navaid:", navaid.id); + 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); +} + diff --git a/Nasal/canvas/map/navaids.layer b/Nasal/canvas/map/navaids.layer new file mode 100644 index 0000000..8bc1761 --- /dev/null +++ b/Nasal/canvas/map/navaids.layer @@ -0,0 +1,9 @@ +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); + diff --git a/Nasal/canvas/map/navaids.model b/Nasal/canvas/map/navaids.model new file mode 100644 index 0000000..2f56335 --- /dev/null +++ b/Nasal/canvas/map/navaids.model @@ -0,0 +1,11 @@ +var NavaidModel = {}; + NavaidModel.new = func make(LayerModel, NavaidModel); + NavaidModel.init = func { + me._view_handle.reset(); + var navaids = findNavaidsWithinRange(15); + foreach(var n; navaids) + me.push(n); + me.notifyView(); +} + + diff --git a/Nasal/canvas/map/parking.draw b/Nasal/canvas/map/parking.draw new file mode 100644 index 0000000..6f3cc6f --- /dev/null +++ b/Nasal/canvas/map/parking.draw @@ -0,0 +1,15 @@ +var draw_parking = func(group, apt, lod) { + var group = group.createChild("group", "apt-"~apt.id); + foreach(var park; apt.parking()) + { + var icon_park = + group.createChild("text", "parking-" ~ park.name) + .setDrawMode( canvas.Text.ALIGNMENT + + canvas.Text.TEXT ) + .setText(park.name) + .setFont("LiberationFonts/LiberationMono-Bold.ttf") + .setGeoPosition(park.lat, park.lon) + .setFontSize(15, 1.3); + } +} + diff --git a/Nasal/canvas/map/parking.layer b/Nasal/canvas/map/parking.layer new file mode 100644 index 0000000..1b6ef1f --- /dev/null +++ b/Nasal/canvas/map/parking.layer @@ -0,0 +1,9 @@ +#TODO: use custom Model/DataProvider +var ParkingLayer = {}; # make(Layer); + ParkingLayer.new = func(group, name) { + var m=Layer.new(group, name, AirportModel ); #FIXME: AirportModel can be shared by Taxiways, Runways etc!! + m.setDraw( func draw_layer(layer: m, callback: draw_parking, lod:0 ) ); + return m; +} + +register_layer("parkings", ParkingLayer); diff --git a/Nasal/canvas/map/runways.draw b/Nasal/canvas/map/runways.draw new file mode 100644 index 0000000..51ce72f --- /dev/null +++ b/Nasal/canvas/map/runways.draw @@ -0,0 +1,113 @@ + +#TODO: split: draw_single_runway(pos) +var draw_runways = func(group, apt,lod) { + DEBUG and print("Drawing runways for:", apt.id); + # var group = group.createChild("group", "apt-"~apt.id); + # group = group.createChild("group", "runways"); + var rws_done = {}; + foreach(var rw; keys(apt.runways)) + { + var is_heli = substr(rw, 0, 1) == "H"; + var rw_dir = is_heli ? nil : int(substr(rw, 0, 2)); + + var rw_rec = ""; + var thresh_rec = 0; + if( rw_dir != nil ) + { + rw_rec = sprintf("%02d", math.mod(rw_dir - 18, 36)); + if( size(rw) == 3 ) + { + var map_rec = { + "R": "L", + "L": "R", + "C": "C" + }; + rw_rec ~= map_rec[substr(rw, 2)]; + } + + if( rws_done[rw_rec] != nil ) + continue; + + var rw_rec = apt.runways[rw_rec]; + if( rw_rec != nil ) + thresh_rec = rw_rec.threshold; + } + + rws_done[rw] = 1; + + rw = apt.runways[rw]; + + var clr = SURFACECOLORS[rw.surface]; + if (clr == nil) { clr = SURFACECOLORS[0]}; + + var icon_rw = + group.createChild("path", "runway-" ~ rw.id) + .setStrokeLineWidth(0.5) + .setColor(1.0,1.0,1.0) + .setColorFill(clr.r, clr.g, clr.b); + + var rwy = Runway.new(rw); + var beg_thr = rwy.pointOffCenterline(rw.threshold); + var beg_thr1 = rwy.pointOffCenterline(rw.threshold, 0.5 * rw.width); + var beg_thr2 = rwy.pointOffCenterline(rw.threshold, -0.5 * rw.width); + var beg1 = rwy.pointOffCenterline(0, 0.5 * rw.width); + var beg2 = rwy.pointOffCenterline(0, -0.5 * rw.width); + + var end_thr = rwy.pointOffCenterline(rw.length - thresh_rec); + var end_thr1 = rwy.pointOffCenterline(rw.length - thresh_rec, 0.5 * rw.width); + var end_thr2 = rwy.pointOffCenterline(rw.length - thresh_rec, -0.5 * rw.width); + var end1 = rwy.pointOffCenterline(rw.length, 0.5 * rw.width); + var end2 = rwy.pointOffCenterline(rw.length, -0.5 * rw.width); + + icon_rw.setDataGeo + ( + [ canvas.Path.VG_MOVE_TO, + canvas.Path.VG_LINE_TO, + canvas.Path.VG_LINE_TO, + canvas.Path.VG_LINE_TO, + canvas.Path.VG_CLOSE_PATH ], + [ beg1[0], beg1[1], + beg2[0], beg2[1], + end2[0], end2[1], + end1[0], end1[1] ] + ); + + if( rw.length / rw.width > 3 and !is_heli ) + { + # only runways which are much longer than wide are + # real runways, otherwise it's probably a heliport. + var icon_cl = + group.createChild("path", "centerline") + .setStrokeLineWidth(0.5) + .setColor(1,1,1) + .setStrokeDashArray([15, 10]); + + icon_cl.setDataGeo + ( + [ canvas.Path.VG_MOVE_TO, + canvas.Path.VG_LINE_TO ], + [ beg_thr[0], beg_thr[1], + end_thr[0], end_thr[1] ] + ); + + var icon_thr = + group.createChild("path", "threshold") + .setStrokeLineWidth(1.5) + .setColor(1,1,1); + + icon_thr.setDataGeo + ( + [ canvas.Path.VG_MOVE_TO, + canvas.Path.VG_LINE_TO, + canvas.Path.VG_MOVE_TO, + canvas.Path.VG_LINE_TO ], + [ beg_thr1[0], beg_thr1[1], + beg_thr2[0], beg_thr2[1], + end_thr1[0], end_thr1[1], + end_thr2[0], end_thr2[1] ] + ); + } + } +} + + diff --git a/Nasal/canvas/map/runways.layer b/Nasal/canvas/map/runways.layer new file mode 100644 index 0000000..10b990f --- /dev/null +++ b/Nasal/canvas/map/runways.layer @@ -0,0 +1,10 @@ +#TODO: use custom Model/DataProvider +var RunwayLayer = {}; # make(Layer); + RunwayLayer.new = func(group, name) { + # print("Setting up new TestLayer"); + var m=Layer.new(group, name, AirportModel ); #FIXME: AirportModel can be shared by Taxiways, Runways etc!! + m.setDraw( func draw_layer(layer: m, callback: draw_runways, lod:0 ) ); + return m; +} +register_layer("runways", RunwayLayer); + diff --git a/Nasal/canvas/map/taxiways.draw b/Nasal/canvas/map/taxiways.draw new file mode 100644 index 0000000..0980c06 --- /dev/null +++ b/Nasal/canvas/map/taxiways.draw @@ -0,0 +1,40 @@ +var draw_taxiways = func(group, apt, lod) { # TODO: the LOD arg isn't stricly needed here, + # the layer is a conventional canvas group, so it can access its map + # parent and just read the "range" property to do LOD handling + group.set("z-index",-100); # HACK: we need to encapsulate this + # var group = group.createChild("group", "apt-"~apt.id); #FIXME: we don't need to use two nested groups for each taxiway - performance? + # group = group.createChild("group", "taxiways"); + # print("drawing taxiways for:", apt.id); + # Taxiways drawn first so the runways and parking positions end up on top. + foreach(var taxi; apt.taxiways) + { + var clr = SURFACECOLORS[taxi.surface]; + if (clr == nil) { clr = SURFACECOLORS[0]}; + + var icon_taxi = + group.createChild("path", "taxi") + .setStrokeLineWidth(0) + .setColor(clr.r, clr.g, clr.b) + .setColorFill(clr.r, clr.g, clr.b); + + var txi = Runway.new(taxi); + var beg1 = txi.pointOffCenterline(0, 0.5 * taxi.width); + var beg2 = txi.pointOffCenterline(0, -0.5 * taxi.width); + var end1 = txi.pointOffCenterline(taxi.length, 0.5 * taxi.width); + var end2 = txi.pointOffCenterline(taxi.length, -0.5 * taxi.width); + + icon_taxi.setDataGeo + ( + [ canvas.Path.VG_MOVE_TO, + canvas.Path.VG_LINE_TO, + canvas.Path.VG_LINE_TO, + canvas.Path.VG_LINE_TO, + canvas.Path.VG_CLOSE_PATH ], + [ beg1[0], beg1[1], + beg2[0], beg2[1], + end2[0], end2[1], + end1[0], end1[1] ] + ); + } +} + diff --git a/Nasal/canvas/map/taxiways.layer b/Nasal/canvas/map/taxiways.layer new file mode 100644 index 0000000..40b853c --- /dev/null +++ b/Nasal/canvas/map/taxiways.layer @@ -0,0 +1,11 @@ +#TODO: use custom Model/DataProvider +var TaxiwayLayer = {}; # make(Layer); + TaxiwayLayer.new = func(group, name) { + # print("Setting up new TestLayer"); + var m=Layer.new(group, name, AirportModel ); #FIXME: AirportModel can be shared by Taxiways, Runways etc!! + m.setDraw( func draw_layer(layer: m, callback: draw_taxiways, lod:0 ) ); + return m; +} + +register_layer("taxiways", TaxiwayLayer); + diff --git a/Nasal/canvas/map/test.layer b/Nasal/canvas/map/test.layer new file mode 100644 index 0000000..069d1ee --- /dev/null +++ b/Nasal/canvas/map/test.layer @@ -0,0 +1,11 @@ + +#TODO: use custom Model/DataProvider +var TestLayer = {}; # make(Layer); + TestLayer.new = func(group, name) { + # print("Setting up new TestLayer"); + var m=Layer.new(group, name, AirportModel ); #FIXME: AirportModel can be shared by Taxiways, Runways etc!! + m.setDraw( func draw_layer(layer: m, callback: MAP_LAYERS["runways"], lod:0 ) ); + return m; +} + +register_layer("airport_test", TestLayer); diff --git a/Nasal/canvas/map/tower.draw b/Nasal/canvas/map/tower.draw new file mode 100644 index 0000000..29a01a0 --- /dev/null +++ b/Nasal/canvas/map/tower.draw @@ -0,0 +1,19 @@ +var draw_tower = func (group, apt,lod) { + var group = group.createChild("group", "tower"); + # TODO: move to map_elements.nas (tower, runway, parking etc) + # i.e.: set_element(group, "tower", "style"); + 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); +} + diff --git a/Nasal/canvas/map/tower.layer b/Nasal/canvas/map/tower.layer new file mode 100644 index 0000000..10704d0 --- /dev/null +++ b/Nasal/canvas/map/tower.layer @@ -0,0 +1,8 @@ +var TowerLayer = {}; + TowerLayer.new = func(group, name) { + var m=Layer.new(group, name, AirportModel ); #FIXME: AirportModel can be shared by Taxiways, Runways etc!! + m.setDraw( func draw_layer(layer: m, callback: draw_tower, lod:0 ) ); + return m; +} +register_layer("towers", TowerLayer); + diff --git a/Aircraft/767-300/Models/2Dpanel/Instruments/EADI.xml b/Nasal/canvas/map/waypoints.model similarity index 100% copy from Aircraft/767-300/Models/2Dpanel/Instruments/EADI.xml copy to Nasal/canvas/map/waypoints.model diff --git a/Nasal/joystick.nas b/Nasal/joystick.nas index 6596add..c5a69d9 100644 --- a/Nasal/joystick.nas +++ b/Nasal/joystick.nas @@ -611,7 +611,7 @@ var readConfig = func(dialog_root="/sim/gui/dialogs/joystick-config") { if (a != nil) { # Read properties from bindings - props.copy(a, p.getNode("original_binding", 1)); + props.copy(a, btn.getNode("original_binding", 1)); var binding = nil; foreach (var b; joystick.buttonBindings) { if ((binding == nil) and (a != nil) and b.match(a)) { diff --git a/gui/dialogs/airports.xml b/gui/dialogs/airports.xml index d743c3b..ba94540 100644 --- a/gui/dialogs/airports.xml +++ b/gui/dialogs/airports.xml @@ -24,13 +24,26 @@ <binding> <command>dialog-close</command> </binding> + <binding> + <command>property-toggle</command> + <property>/sim/gui/dialogs/airports/signals/dialog-close</property> + </binding> </button> </group> <hrule/> <nasal> + <!-- Generalize all this, turn into helpers and load defaults via XML --> <open><![CDATA[ + ## "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 + + setprop("/sim/gui/dialogs/airports/selected-airport/rwy", ""); setprop("/sim/gui/dialogs/airports/selected-airport/parkpos", ""); setprop("/sim/gui/dialogs/airports/mode", "search"); @@ -176,12 +189,15 @@ setprop("/sim/presets/parkpos", getprop("/sim/gui/dialogs/airports/selected-airport/parkpos")); } } + + update_info(); ]]> </open> <close> - fgcommand("clear-metar", var n = props.Node.new({ "path": "/sim/gui/dialogs/airports/selected-airport/metar", - "station": airport_id})); + fgcommand("clear-metar", var n = props.Node.new({ "path": "/sim/gui/dialogs/airports/selected-airport/metar", + "station": airport_id})); + # map.cleanup_listeners(); #TODO: We should be setting a signal when closing the dialog, so that cleanup code can be invoked automatically </close> </nasal> @@ -575,8 +591,11 @@ <group> <layout>vbox</layout> - <canvas> - <name>map-dialog</name> + <!-- 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> @@ -584,207 +603,77 @@ <pref-height>400</pref-height> <view n="0">600</view> <view n="1">400</view> - - <nasal> - - - <load><![CDATA[ - var my_canvas = canvas.get(cmdarg()); - my_canvas.setColorBackground(0.2, 0.5, 0.2, 0.5); - var root = my_canvas.createGroup(); - - var map = root.createChild("map", "map-test") - .setTranslation(300, 200); - - var layer_runways = map.createChild("group", "runways"); - - var updateMap = func() { - var id = getprop("/sim/gui/dialogs/airports/selected-airport/id"); - - if (id != "") { - var apt = airportinfo(id); - - map.removeAllChildren(); - layer_runways = map.createChild("group", "runways"); - - var display_taxiways = getprop("/sim/gui/dialogs/airports/display-taxiways"); - var display_parking = getprop("/sim/gui/dialogs/airports/display-parking"); - var display_tower = getprop("/sim/gui/dialogs/airports/display-tower"); - - var airport = canvas.AirportMap.new(apt, 1, display_taxiways, display_parking, display_tower); - airport.build(layer_runways); - - map._node.getNode("ref-lat", 1).setDoubleValue(apt.lat); - map._node.getNode("ref-lon", 1).setDoubleValue(apt.lon); - map._node.getNode("hdg", 1).setDoubleValue(0.0); - - updateZoom(); - } - } - - var ranges = [0.1, 0.25, 0.5, 1, 2.5, 5]; - - var updateZoom = func() - { - var z = getprop("/sim/gui/dialogs/airports/zoom"); - if( z == nil ) - z = 0; - var zoom = ranges[4 - z]; - map._node.getNode("range", 1).setDoubleValue(zoom); - - }; - - var updateRunwayHighlight = func() - { - var selected_rwy = getprop("/sim/gui/dialogs/airports/selected-airport/rwy"); - var selected_apt = getprop("/sim/gui/dialogs/airports/selected-airport/id"); - - var is_heli = substr(selected_rwy, 0, 1) == "H"; - var rw_dir = is_heli ? nil : int(substr(selected_rwy, 0, 2)); - - var rw_rec = ""; - if( rw_dir != nil ) { - rw_rec = sprintf("%02d", math.mod(rw_dir - 18, 36)); - if( size(selected_rwy) == 3 ) { - var map_rec = { - "R": "L", - "L": "R", - "C": "C" - }; - rw_rec ~= map_rec[substr(selected_rwy, 2)]; - } - } - - foreach (var apt; layer_runways.getChildren()) { - if (apt.get("id") == "apt-" ~ selected_apt) { - foreach (var rwy; apt.getChildren()) { - if ((rwy.get("id") == "runway-" ~ selected_rwy) or - (rwy.get("id") == "runway-" ~ rw_rec) ) - { - rwy.setColor(1.0,0.0,0.0); - } else { - rwy.setColor(1.0,1.0,1.0); - } - } - } - } - } - var updateParkingHighlight = func() - { - var selected_parkpos = getprop("/sim/gui/dialogs/airports/selected-airport/parkpos"); - var selected_apt = getprop("/sim/gui/dialogs/airports/selected-airport/id"); - - foreach (var apt; layer_runways.getChildren()) { - if (apt.get("id") == "apt-" ~ selected_apt) { - foreach (var rwy; apt.getChildren()) { - if (rwy.get("id") == "parking-" ~ selected_parkpos) { - rwy.setColor(1.0,0.0,0.0); - } else { - rwy.setColor(1.0,1.0,1.0); - } - } - } - } - } - - var listeners = []; - - append(listeners, setlistener("/sim/gui/dialogs/airports/selected-airport/id", updateMap)); - append(listeners, setlistener("/sim/gui/dialogs/airports/selected-airport/rwy", updateRunwayHighlight)); - append(listeners, setlistener("/sim/gui/dialogs/airports/selected-airport/parkpos", updateParkingHighlight)); - append(listeners, setlistener("/sim/gui/dialogs/airports/display-taxiways", updateMap)); - append(listeners, setlistener("/sim/gui/dialogs/airports/display-parking", updateMap)); - append(listeners, setlistener("/sim/gui/dialogs/airports/display-tower", updateMap)); - append(listeners, setlistener("/sim/gui/dialogs/airports/zoom", updateZoom)); - - update_info(); - ... [truncated message content] |