diff --git a/Nasal/canvas/MapDrivers.nas b/Nasal/canvas/MapDrivers.nas new file mode 100644 index 000000000..a94086ffa --- /dev/null +++ b/Nasal/canvas/MapDrivers.nas @@ -0,0 +1,105 @@ +var RouteDriver = { + new: func(){ + var m = { + parents: [RouteDriver], + }; + m.init(); + return m; + }, + init: func(){ + me.update(); + }, + update: func(){ + me.flightplan = flightplan(); + }, + getNumberOfFlightPlans: func(){1}, + getFlightPlanType: func(fpNum){'current'}, + getFlightPlan: func(fpNum){me.flightplan}, + getPlanSize: func(fpNum){me.flightplan.getPlanSize()}, + getWP: func(fpNum, idx){me.flightplan.getWP(idx)}, + getPlanModeWP: func(plan_wp_idx){me.flightplan.getWP(plan_wp_idx)}, + hasDiscontinuity: func(fpNum, wptID){0}, + getListeners: func(){[]}, + shouldUpdate: func 1 +}; + +var MultiRouteDriver = { + parents: [RouteDriver], + new: func(){ + var m = { + parents: [MultiRouteDriver], + _flightplans: [], + currentFlightPlan: 0 + }; + m.init(); + return m; + }, + addFlightPlan: func(type, plan){ + append(me._flightplans, { + type: type, + flightplan: plan + }); + }, + removeFlightPlanAtIndex: func(idx){ + var sz = size(me._flightplans); + if(idx < sz){ + if(sz == 1) + me._flightplans = []; + elsif(sz == 2 and idx == 0) + me._flightplans = [me._flightplans[1]]; + elsif(sz == 2 and idx == 1) + pop(me._flightplans); + else { + var subv_l = subvec(me._flightplans, 0, idx); + var subv_r = subvec(me._flightplans, idx + 1); + me._flightplans = subv_l ~ subv_r; + } + } + me.triggerSignal('fp-added'); + }, + removeFlightPlanOfType: func(type){ + var new_vec = []; + foreach(var fp; me._flightplans){ + if(fp['type'] != type) + append(new_vec, fp); + } + me._flightplans = new_vec; + me.triggerSignal('fp-removed'); + }, + getNumberOfFlightPlans: func(){ + size(me._flightplans); + }, + getFlightPlanType: func(fpNum){ + if(fpNum >= size(me._flightplans)) return nil; + var fp = me._flightplans[fpNum]; + return fp.type; + }, + getFlightPlan: func(fpNum){ + if(fpNum >= size(me._flightplans)) return nil; + return me._flightplans[fpNum]; + }, + getPlanSize: func(fpNum){ + if(fpNum >= size(me._flightplans)) return 0; + return me._flightplans[fpNum].getPlanSize(); + }, + getWP: func(fpNum, idx){ + if(fpNum >= size(me._flightplans)) return nil; + var fp = me._flightplans[fpNum].getPlanSize(); + return fp.getWP(idx); + }, + getPlanModeWP: func(idx){ + if(me.currentFlightPlan >= size(me._flightplans)) return nil; + var fp = me._flightplans[me.currentFlightPlan]; + return fp.getWP(idx); + }, + triggerSignal: func(signal){ + setprop(me.signalPath(signal)); + }, + signalPath: func(signal){ + 'autopilot/route-manager/signals/rd-'~ signal; + }, + getListeners: func(){[ + me.getSignal('fp-added'), + me.getSignal('fp-removed') + ]} +}; diff --git a/Nasal/canvas/MapStructure.nas b/Nasal/canvas/MapStructure.nas index bc87c2d2c..4ff3b5d82 100644 --- a/Nasal/canvas/MapStructure.nas +++ b/Nasal/canvas/MapStructure.nas @@ -261,6 +261,17 @@ var hashdup = func(_,rkeys=nil) { var h={}; var k=rkeys!=nil?rkeys:members(_); foreach (var k;k) h[tryintern(k)]=member(_,k); h } +var opt_member = func(h,k) { + if (contains(h, k)) return h[k]; + if (contains(h, "parents")) { + var _=h.parents; + for (var i=0;i [1,1,1] + # me.model.tuned = 1; + # var line_color = me.getStyle('line_color'); # --> [0,0,1] + # var txt_color = me.getStyle('text_color', [1,1,1]); # --> [1,1,1] + getStyle: func(name, default = nil){ + var st = me.style; + if(st == nil) + st = me.layer.style; + if(st == nil) return default; + var val = opt_member(st, name); + if(typeof(val) == 'func'){ + val = (call(val,[],me)); + } + if(val == nil) return default; + return val; + }, + getLabelFromModel: func(default_val = nil){ + if(me.model == nil) return default_val; + if(default_val == nil and contains(me.model, 'id')) + default_val = me.model.id; + var label_content = me.getOption('label_content'); + if(label_content == nil) return default_val; + if(typeof(label_content) == 'scalar') + label_content = [label_content]; + var format_s = me.getOption('label_format'); + var label = ''; + if(format_s == nil){ + format_s = "%s"; + } + return me.formattedString(format_s, label_content); + }, + # Executes callback function specified by the first argument with + # variable arguments. The callback is executed within the 'me' context. + # Callbacks must be defined inside the options hash. + # + # EXAMPLE: + # + # me.options = { + # dump_callback: func(){ + # print('Waypoint '~ me.model.id); + # } + # } + # me.callback('dump'); + callback: func(name, args...){ + name = name ~'_callback'; + var f = me.getOption(name); + if(typeof(f) == 'func'){ + return call(f, args, me); + } + } }; # of Symbol @@ -611,6 +734,7 @@ var DotSym = { } else me.element.show(); me.draw(); + if(me.getOption('disable_position', 0)) return; var pos = me.controller.getpos(me.model); if (size(pos) == 2) pos~=[nil]; # fall through @@ -632,17 +756,27 @@ var SVGSymbol = { element_type: "group", cacheable: 0, init: func() { + me.callback('init_before'); + var opt_path = me.getStyle('svg_path'); + if(opt_path != nil) + me.svg_path = opt_path; if (!me.cacheable) { - canvas.parsesvg(me.element, me.svg_path); + if(me.svg_path != nil and me.svg_path != '') + canvas.parsesvg(me.element, me.svg_path); # hack: if (var scale = me.layer.style['scale_factor']) me.element.setScale(scale); + if ((var transl = me.layer.style['translate']) != nil) + me.element.setTranslation(transl); } else { __die("cacheable not implemented yet!"); } + me.callback('init_after'); me.draw(); }, - draw: func, + draw: func{ + me.callback('draw'); + }, }; # of SVGSymbol @@ -685,7 +819,13 @@ var LineSymbol = { # For the instances returned from makeinstance: new: func(group, layer, model, controller=nil) { if (me == nil) __die("Need me reference for LineSymbol.new()"); - if (typeof(model) != 'vector') __die("LineSymbol.new(): need a vector of points"); + if (typeof(model) != 'vector') { + if(typeof(model) == 'hash'){ + if(!contains(model, 'path')) + __die("LineSymbol.new(): model hash requires path"); + } + else __die("LineSymbol.new(): need a vector of points or a hash"); + } var m = { parents: [me], group: group, @@ -705,19 +845,32 @@ var LineSymbol = { # Non-static: draw: func() { if (!me.needs_update) return; + me.callback('draw_before'); printlog(_MP_dbg_lvl, "redrawing a LineSymbol "~me.layer.type); me.element.reset(); var cmds = []; var coords = []; var cmd = canvas.Path.VG_MOVE_TO; - foreach (var m; me.model) { - var (lat,lon) = me.controller.getpos(m); - append(coords,"N"~lat); - append(coords,"E"~lon); - append(cmds,cmd); cmd = canvas.Path.VG_LINE_TO; + var path = me.model; + if(typeof(path) == 'hash'){ + path = me.model.path; + if(path == nil) + __die("LineSymbol model requires a 'path' member (vector)"); + } + foreach (var m; path) { + if(size(keys(m)) >= 2){ + var (lat,lon) = me.controller.getpos(m); + append(coords,"N"~lat); + append(coords,"E"~lon); + append(cmds,cmd); + cmd = canvas.Path.VG_LINE_TO; + } else { + cmd = canvas.Path.VG_MOVE_TO; + } } me.element.setDataGeo(cmds, coords); me.element.update(); # this doesn't help with flickering, it seems + me.callback('draw_after'); }, del: func() { printlog(_MP_dbg_lvl, "LineSymbol.del()"); @@ -791,6 +944,24 @@ var SymbolLayer = { assert_m(controller.parents[0], "parents"); if (controller.parents[0].parents[0] != SymbolLayer.Controller) __die("MultiSymbolLayer: OOP error"); + if(options != nil){ + var listeners = opt_member(controller, 'listeners'); + var listen = opt_member(options, 'listen'); + if (listen != nil and listeners != nil){ + var listen_tp = typeof(listen); + if(listen_tp != 'vector' and listen_tp != 'scalar') + __die("Options 'listen' cannot be a "~ listen_tp); + if(typeof(listen) == 'scalar') + listen = [listen]; + foreach(var node_name; listen){ + var node = opt_member(options, node_name); + if(node == nil) + node = node_name; + append(controller.listeners, + setlistener(node, func call(m.update,[],m),0,0)); + } + } + } m.controller = controller; }, # For instances: @@ -1010,6 +1181,67 @@ var SingleSymbolLayer = { # set up a cache for 32x32 symbols (initialized below in load_MapStructure) var SymbolCache32x32 = nil; +var MapStructure = { + # Generalized load methods used to load various symbols, layer controllers,... + loadFile : func(file, name) { + if (name == nil) + var name = split("/", file)[-1]; + var code = io.readfile(file); + var code = call(func compile(code, file), [code], var err=[]); + if (size(err)) { + if (substr(err[0], 0, 12) == "Parse error:") { # hack around Nasal feature + var e = split(" at line ", err[0]); + if (size(e) == 2) + err[0] = string.join("", [e[0], "\n at ", file, ", line ", e[1], "\n "]); + } + for (var i = 1; (var c = caller(i)) != nil; i += 1) + err ~= subvec(c, 2, 2); + debug.printerror(err); + return; + } + #code=bind( + call(code, nil, nil, var hash = {}); + + # validate + var url = ' http://wiki.flightgear.org/MapStructure#'; + # TODO: these rules should be extended for all main files lcontroller/scontroller and symbol + var checks = [ + { extension:'symbol', symbol:'update', type:'func', error:' update() must not be overridden:', id:300}, + # Sorry, this one doesn't work with the new LineSymbol + # { extension:'symbol', symbol:'draw', type:'func', required:1, error:' symbol files need to export a draw() routine:', id:301}, + # Sorry, this one doesn't work with the new SingleSymbolLayer + # { extension:'lcontroller', symbol:'searchCmd', type:'func', required:1, error:' lcontroller without searchCmd method:', id:100}, + ]; + + + var makeurl = func(scope, id) url ~ scope ~ ':' ~ id; + var bailout = func(file, message, scope, id) __die(file~message~"\n"~makeurl(scope,id) ); + + var current_ext = split('.', file)[-1]; + foreach(var check; checks) { + # check if we have any rules matching the current file extension + if (current_ext == check.extension) { + # check for fields that must not be overridden + if (check['error'] != nil and + hash[check.symbol]!=nil and !check['required'] and + typeof(hash[check.symbol])==check.type ) { + bailout(file,check.error,check.extension,check.id); + } + + # check for required fields + if (check['required'] != nil and + hash[check.symbol]==nil and + typeof( hash[check.symbol]) != check.type) { + bailout(file,check.error,check.extension,check.id); + } + } + } + + return hash; + } +}; + + var load_MapStructure = func { canvas.load_MapStructure = func; # disable any subsequent attempt to load @@ -1063,62 +1295,6 @@ var load_MapStructure = func { ####### LOAD FILES ####### (func { var FG_ROOT = getprop("/sim/fg-root"); - var load = func(file, name) { - if (name == nil) - var name = split("/", file)[-1]; - var code = io.readfile(file); - var code = call(func compile(code, file), [code], var err=[]); - if (size(err)) { - if (substr(err[0], 0, 12) == "Parse error:") { # hack around Nasal feature - var e = split(" at line ", err[0]); - if (size(e) == 2) - err[0] = string.join("", [e[0], "\n at ", file, ", line ", e[1], "\n "]); - } - for (var i = 1; (var c = caller(i)) != nil; i += 1) - err ~= subvec(c, 2, 2); - debug.printerror(err); - return; - } - #code=bind( - call(code, nil, nil, var hash = {}); - - # validate - var url = ' http://wiki.flightgear.org/MapStructure#'; - # TODO: these rules should be extended for all main files lcontroller/scontroller and symbol - var checks = [ - { extension:'symbol', symbol:'update', type:'func', error:' update() must not be overridden:', id:300}, - # Sorry, this one doesn't work with the new LineSymbol -# { extension:'symbol', symbol:'draw', type:'func', required:1, error:' symbol files need to export a draw() routine:', id:301}, - # Sorry, this one doesn't work with the new SingleSymbolLayer -# { extension:'lcontroller', symbol:'searchCmd', type:'func', required:1, error:' lcontroller without searchCmd method:', id:100}, - ]; - - - var makeurl = func(scope, id) url ~ scope ~ ':' ~ id; - var bailout = func(file, message, scope, id) __die(file~message~"\n"~makeurl(scope,id) ); - - var current_ext = split('.', file)[-1]; - foreach(var check; checks) { - # check if we have any rules matching the current file extension - if (current_ext == check.extension) { - # check for fields that must not be overridden - if (check['error'] != nil and - hash[check.symbol]!=nil and !check['required'] and - typeof(hash[check.symbol])==check.type ) { - bailout(file,check.error,check.extension,check.id); - } - - # check for required fields - if (check['required'] != nil and - hash[check.symbol]==nil and - typeof( hash[check.symbol]) != check.type) { - bailout(file,check.error,check.extension,check.id); - } - } - } - - return hash; - }; # sets up a shared symbol cache, which will be used by all MapStructure maps and layers canvas.SymbolCache32x32 = SymbolCache.new(1024,32); @@ -1146,7 +1322,7 @@ var load_MapStructure = func { foreach (var d; dep_names) { foreach (var f; deps[d]) { var name = split(".", f)[0]; - load(contents_dir~f, name); + MapStructure.loadFile(contents_dir~f, name); } } })(); diff --git a/Nasal/canvas/api.nas b/Nasal/canvas/api.nas index 6c778de3e..ea67217fa 100644 --- a/Nasal/canvas/api.nas +++ b/Nasal/canvas/api.nas @@ -360,6 +360,46 @@ var Group = { return children; }, + # Recursively get all children of class specified by first param + getChildrenOfType: func(type, array = nil){ + var children = array; + if(children == nil) + children = []; + var my_children = me.getChildren(); + if(typeof(type) != 'vector') + type = [type]; + foreach(var c; my_children){ + foreach(var t; type){ + if(isa(c, t)){ + append(children, c); + } + } + if(isa(c, canvas.Group)){ + c.getChildrenOfType(type, children); + } + } + return children; + }, + # Set color to children of type Path and Text. It is possible to optionally + # specify which types of children should be affected by passing a vector as + # the last agrument, ie. my_group.setColor(1,1,1,[Path]); + setColor: func(){ + var color = arg; + var types = [Path, Text]; + var arg_c = size(color); + if(arg_c > 1 and typeof(color[-1]) == 'vector'){ + types = color[-1]; + color = subvec(color, 0, arg_c - 1); + } + var children = me.getChildrenOfType(types); + if(typeof(color) == 'vector'){ + var first = color[0]; + if(typeof(first) == 'vector') + color = first; + } + foreach(var c; children) + c.setColor(color); + }, # Get first child with given id (breadth-first search) # # @note Use with care as it can take several miliseconds (for me eg. ~2ms). @@ -460,11 +500,12 @@ var Map = { return me; }, - addLayer: func(factory, type_arg=nil, priority=nil, style=nil, options=nil, visible=1) + addLayer: func(factory, type_arg=nil, priority=nil, style=nil, opts=nil, visible=1) { if(contains(me.layers, type_arg)) printlog("warn", "addLayer() warning: overwriting existing layer:", type_arg); + var options = opts; # Argument handling if (type_arg != nil) { var layer = factory.new(type:type_arg, group:me, map:me, style:style, options:options, visible:visible); diff --git a/Nasal/canvas/map/ALT-profile.lcontroller b/Nasal/canvas/map/ALT-profile.lcontroller index 47446bcae..3cfcd5533 100644 --- a/Nasal/canvas/map/ALT-profile.lcontroller +++ b/Nasal/canvas/map/ALT-profile.lcontroller @@ -9,9 +9,9 @@ SymbolLayer.add(name, { type: name, # Symbol type df_controller: __self__, # controller to use by default -- this one df_options: { # default configuration options - active_node: "/autopilot/route-manager/active", + fplan_active: "/autopilot/route-manager/active", vnav_node: "/autopilot/route-manager/vnav/", - types: ["tc", "td", "sc", "ed"], + types: ["tc", "td", "ec", "ed","sc", "sd"], } }); var new = func(layer) { @@ -22,7 +22,8 @@ var new = func(layer) { listeners: [], }; layer.searcher._equals = func(a,b) a.getName() == b.getName(); - append(m.listeners, setlistener(layer.options.active_node, func m.layer.update() )); + append(m.listeners, setlistener(layer.options.fplan_active, + func m.layer.update() )); m.addVisibilityListener(); return m; diff --git a/Nasal/canvas/map/ALT-profile.symbol b/Nasal/canvas/map/ALT-profile.symbol index f9e64dd96..7308ecb85 100644 --- a/Nasal/canvas/map/ALT-profile.symbol +++ b/Nasal/canvas/map/ALT-profile.symbol @@ -13,20 +13,39 @@ var element_type = "group"; var init = func { var name = me.model.getName(); - var disptext = chr(string.toupper(name[0]))~"/"~chr(string.toupper(name[1])); - var radius = me.style.radius; - me.element.createChild("path") + var svg_path = me.getOption('svg_path'); + if (svg_path != nil) { + var grp = me.element.createChild("group", name); + if(typeof(svg_path) == 'scalar'){ + canvas.parsesvg(me.element, svg_path); + } + else{ #need hash + if(!contains(svg_path, name)) + die('ALT-Profile: no SVG Path for '~ name); + var path = svg_path[name]; + canvas.parsesvg(me.element, path); + } + } else { + var disptext = chr(string.toupper(name[0]))~"/"~chr(string.toupper(name[1])); + var radius = me.style.radius; + me.element.createChild("path") .setStrokeLineWidth(5) .moveTo(-radius, 0) - .arcLargeCW(radius, radius, 0, 2 * radius, 0) - .arcLargeCW(radius, radius, 0, -2 * radius, 0) + .arcLargeCW(radius, radius, 0, 2 * radius, 0) + .arcLargeCW(radius, radius, 0, -2 * radius, 0) .setColor(0.195,0.96,0.097); - me.element.createChild("text") + me.element.createChild("text") .setDrawMode( canvas.Text.TEXT ) .setText(disptext) .setFont("LiberationFonts/LiberationSans-Regular.ttf") .setFontSize(28) .setTranslation(25,35) .setColor(0.195,0.96,0.097); + } + me.callback('init_after'); +} + +var draw = func{ + me.callback('draw'); } diff --git a/Nasal/canvas/map/APS.lcontroller b/Nasal/canvas/map/APS.lcontroller index fb772e77b..a3441567d 100644 --- a/Nasal/canvas/map/APS.lcontroller +++ b/Nasal/canvas/map/APS.lcontroller @@ -13,9 +13,13 @@ SymbolLayer.add(name, { # N.B.: if used, this SymbolLayer should be updated every frame # by the Map Controller, or as often as the position is changed. var new = func(layer) { + var __model = layer.map.getPosCoord(); + #debug.dump(typeof(layer.options)); + if(layer.options != nil and contains(layer.options, 'model')) + __model = layer.options.model; return { parents: [__self__], - _model: layer.map.getPosCoord(), + _model: __model, }; }; var del = func; diff --git a/Nasal/canvas/map/APT.lcontroller b/Nasal/canvas/map/APT.lcontroller index dae7c5d65..2b20829ac 100644 --- a/Nasal/canvas/map/APT.lcontroller +++ b/Nasal/canvas/map/APT.lcontroller @@ -15,6 +15,8 @@ SymbolLayer.add(name, { color_default: [0,0.6,0.85], label_font_color:[0,0.6,0.85], label_font_size: 28, + text_offset: [17,35], + svg_path: nil }, }); diff --git a/Nasal/canvas/map/APT.symbol b/Nasal/canvas/map/APT.symbol index e9c1fb1ff..d6bc6468f 100644 --- a/Nasal/canvas/map/APT.symbol +++ b/Nasal/canvas/map/APT.symbol @@ -11,23 +11,34 @@ var text_apt = nil; # add the draw routine from airports-nd.draw here var init = func { - var icon_apt = me.element.createChild("path", name ~ " icon" ) + var icon_apt = nil; + var style = me.layer.style; + var svg_path = style.svg_path; + if (svg_path != nil) { + canvas.parsesvg(me.element, svg_path); + } else { + icon_apt = me.element.createChild("path", name ~ " icon" ) .moveTo(-17,0) .arcSmallCW(17,17,0,34,0) .arcSmallCW(17,17,0,-34,0) .close() - .setColor(me.layer.style.color_default) - .setStrokeLineWidth(me.layer.style.line_width); + .setColor(style.color_default) + .setStrokeLineWidth(style.line_width); + } + var text = me.getLabelFromModel(); + if(text == nil){ + text = me.model.id; + } var text_apt = me.element.createChild("text", name ~ " label") .setDrawMode( canvas.Text.TEXT ) - .setTranslation(17,35) - .setText(me.model.id) + .setTranslation(style.text_offset) + .setText(text) .setFont("LiberationFonts/LiberationSans-Regular.ttf") - .setColor(me.layer.style.label_font_color) - .setFontSize(me.layer.style.label_font_size); + .setColor(style.label_font_color) + .setFontSize(style.label_font_size); # FIXME: this applies scale to the whole group, better do this separately for each element? - me.element.setScale(me.layer.style.scale_factor); + me.element.setScale(style.scale_factor); }; var draw = func; diff --git a/Nasal/canvas/map/Airbus/Images/airbusAirplane.svg b/Nasal/canvas/map/Airbus/Images/airbusAirplane.svg new file mode 100644 index 000000000..2f2dda8e3 --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbusAirplane.svg @@ -0,0 +1,70 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/Nasal/canvas/map/Airbus/Images/airbusND.svg b/Nasal/canvas/map/Airbus/Images/airbusND.svg new file mode 100644 index 000000000..3c3693e81 --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbusND.svg @@ -0,0 +1,2084 @@ + + + +image/svg+xmlGijs de RooyABCD +999.9 +0 +33 +30 +27 +24 +21 +18 +15 +12 +9 +6 +3 + +1 +2 +4 +5 +7 +8 +10 +11 +13 +14 +16 +17 +19 +20 +22 +23 +25 +26 +28 +29 +31 +32 +34 +35 +999°/ 99 +999 +999 +GS +TAS +999 +HDG +MAG +VOR 1 +NM +99.9 +ABC +VOR 2 +NM +99.9 +ABC +08 34.4z +99 + + + + + +NM +ILS +999.99 +360 +00' 00'' +99 +.1L +.1L +.1L +.1L +N +33 +30 +W +24 +21 +S +15 +12 +E +6 +3 +CRS + +999 +99.9 +ILS APP +N +W +E +S +99 +99 +99 +99 +99 + diff --git a/Nasal/canvas/map/Airbus/Images/airbus_airport.svg b/Nasal/canvas/map/Airbus/Images/airbus_airport.svg new file mode 100644 index 000000000..588ab387a --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbus_airport.svg @@ -0,0 +1,99 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/Nasal/canvas/map/Airbus/Images/airbus_decel.svg b/Nasal/canvas/map/Airbus/Images/airbus_decel.svg new file mode 100644 index 000000000..c1833f099 --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbus_decel.svg @@ -0,0 +1,77 @@ + + + + + + + + + + image/svg+xml + + + + + + + + D + + diff --git a/Nasal/canvas/map/Airbus/Images/airbus_ec.svg b/Nasal/canvas/map/Airbus/Images/airbus_ec.svg new file mode 100644 index 000000000..1040e9a3b --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbus_ec.svg @@ -0,0 +1,72 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/Nasal/canvas/map/Airbus/Images/airbus_ed.svg b/Nasal/canvas/map/Airbus/Images/airbus_ed.svg new file mode 100644 index 000000000..aa7c3b3cd --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbus_ed.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/Nasal/canvas/map/Airbus/Images/airbus_holdL.svg b/Nasal/canvas/map/Airbus/Images/airbus_holdL.svg new file mode 100644 index 000000000..0807b36ff --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbus_holdL.svg @@ -0,0 +1,60 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Nasal/canvas/map/Airbus/Images/airbus_holdR.svg b/Nasal/canvas/map/Airbus/Images/airbus_holdR.svg new file mode 100644 index 000000000..6c51add32 --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbus_holdR.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Nasal/canvas/map/Airbus/Images/airbus_sc.svg b/Nasal/canvas/map/Airbus/Images/airbus_sc.svg new file mode 100644 index 000000000..57f19b549 --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbus_sc.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Nasal/canvas/map/Airbus/Images/airbus_sd.svg b/Nasal/canvas/map/Airbus/Images/airbus_sd.svg new file mode 100644 index 000000000..4708c72b2 --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbus_sd.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Nasal/canvas/map/Airbus/Images/airbus_spd_limit.svg b/Nasal/canvas/map/Airbus/Images/airbus_spd_limit.svg new file mode 100644 index 000000000..077c3677d --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbus_spd_limit.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/Nasal/canvas/map/Airbus/Images/airbus_tc.svg b/Nasal/canvas/map/Airbus/Images/airbus_tc.svg new file mode 100644 index 000000000..f40895434 --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbus_tc.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/Nasal/canvas/map/Airbus/Images/airbus_td.svg b/Nasal/canvas/map/Airbus/Images/airbus_td.svg new file mode 100644 index 000000000..31af5d32f --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbus_td.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Nasal/canvas/map/Airbus/Images/airbus_vor.old.svg b/Nasal/canvas/map/Airbus/Images/airbus_vor.old.svg new file mode 100644 index 000000000..43b3ef980 --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbus_vor.old.svg @@ -0,0 +1,101 @@ + + + + + + image/svg+xml + + + + + + + + + Layer 1 + + + + + + + + + diff --git a/Nasal/canvas/map/Airbus/Images/airbus_vor.svg b/Nasal/canvas/map/Airbus/Images/airbus_vor.svg new file mode 100644 index 000000000..87fbaa44a --- /dev/null +++ b/Nasal/canvas/map/Airbus/Images/airbus_vor.svg @@ -0,0 +1,90 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/Nasal/canvas/map/DECEL.lcontroller b/Nasal/canvas/map/DECEL.lcontroller new file mode 100644 index 000000000..ce3d3a9cd --- /dev/null +++ b/Nasal/canvas/map/DECEL.lcontroller @@ -0,0 +1,37 @@ +# See: http://wiki.flightgear.org/MapStructure +# Class things: +var name = 'DECEL'; +var parents = [canvas.SymbolLayer.Controller]; +var __self__ = caller(0)[0]; + +canvas.SymbolLayer.Controller.add(name, __self__); +canvas.SymbolLayer.add(name, { + parents: [MultiSymbolLayer], + type: name, # Symbol type + df_controller: __self__, # controller to use by default -- this one + df_options : { # default configuration options + decel_node: "/instrumentation/nd/symbols/decel/" + } +}); +var new = func(layer) { + var m = { + parents: [__self__], + layer: layer, + map: layer.map, + listeners: [], + }; + layer.searcher._equals = func(a,b) a.getName() == b.getName(); + return m; +}; +var del = func() { + foreach (var l; me.listeners) + removelistener(l); +}; + +var searchCmd = func { + var results = []; + var symNode = props.globals.getNode(me.layer.options.decel_node); + if (symNode != nil and symNode.getValue('longitude-deg') != nil) + append(results, symNode); + return results; +} diff --git a/Nasal/canvas/map/DECEL.symbol b/Nasal/canvas/map/DECEL.symbol new file mode 100644 index 000000000..f0772e10f --- /dev/null +++ b/Nasal/canvas/map/DECEL.symbol @@ -0,0 +1,32 @@ +# See: http://wiki.flightgear.org/MapStructure +# Class things: +var name = 'DECEL'; +var parents = [canvas.DotSym]; +var __self__ = caller(0)[0]; +canvas.DotSym.makeinstance( name, __self__ ); + +#canvas.SymbolLayer.get(name).df_style = { +# radius: 13, +#}; + +var element_type = "group"; + +var init = func { + var svg_path = me.getStyle('svg_path'); + if(svg_path == nil) + svg_path = 'Nasal/canvas/map/Airbus/Images/airbus_decel.svg'; + me.decel_grp = me.element.createChild("group","decel"); + canvas.parsesvg(me.decel_grp, svg_path); + me.decel_grp.set('z-index', 5); +} + +var draw = func{ + if(me.decel_grp != nil){ + var spd_ctrl = getprop(me.options.spd_ctrl); + var spd_managed = (spd_ctrl == me.options.managed_val); + if(spd_managed) + me.decel_grp.setColor(me.style.managed_color); + else + me.decel_grp.setColor(me.style.selected_color); + } +} diff --git a/Nasal/canvas/map/DME.symbol b/Nasal/canvas/map/DME.symbol index f552b5752..1b9f7eff7 100644 --- a/Nasal/canvas/map/DME.symbol +++ b/Nasal/canvas/map/DME.symbol @@ -103,22 +103,27 @@ var init_cache = func { # var init = func { # debug.dump(me.layer.style); - me.icon_dme = me.element.createChild("path") - .moveTo(-15,0) - .line(-12.5,-7.5) - .line(7.5,-12.5) - .line(12.5,7.5) - .lineTo(7.5,-12.5) - .line(12.5,-7.5) - .line(7.5,12.5) - .line(-12.5,7.5) - .lineTo(15,0) - .lineTo(7.5,12.5) - .vert(14.5) - .horiz(-14.5) - .vert(-14.5) - .close() - .setStrokeLineWidth( me.layer.style.line_width ); + var draw_func = me.getOption('draw_dme'); + if(typeof(draw_func) == 'func'){ + me.icon_dme = draw_func(me); + } else { + me.icon_dme = me.element.createChild("path") + .moveTo(-15,0) + .line(-12.5,-7.5) + .line(7.5,-12.5) + .line(12.5,7.5) + .lineTo(7.5,-12.5) + .line(12.5,-7.5) + .line(7.5,12.5) + .line(-12.5,7.5) + .lineTo(15,0) + .lineTo(7.5,12.5) + .vert(14.5) + .horiz(-14.5) + .vert(-14.5) + .close() + .setStrokeLineWidth( me.layer.style.line_width ); + } # finally scale the symbol as requested, this is done last so that people can override this when creating the layer me.apply_scale(); @@ -126,6 +131,15 @@ var init = func { me.timer = maketimer(0.33, func me.animate() ); me.timer.start(); } + if(me.getOption('draw_text', 0)){ + me.dme_text = me.element.createChild("text") + .setDrawMode( canvas.Text.TEXT ) + .setText(me.model.id) + .setFont("LiberationFonts/LiberationSans-Regular.ttf") + .setFontSize(28) + .setColor(me.getStyle('text_color', [1,1,1])) + .setTranslation(me.getStyle('translation', [45,25])); + } me.draw(); } diff --git a/Nasal/canvas/map/FIX.symbol b/Nasal/canvas/map/FIX.symbol index 988d22ecc..ad4688b3b 100644 --- a/Nasal/canvas/map/FIX.symbol +++ b/Nasal/canvas/map/FIX.symbol @@ -30,20 +30,31 @@ var drawFIX = func(group) { .setStrokeLineWidth(line_width) .setColor(color); } - -var cache = StyleableCacheable.new( - name:name, draw_func: drawFIX, - cache: SymbolCache32x32, - draw_mode: SymbolCache.DRAW_CENTERED, - relevant_keys: ["line_width", "color"], -); +var _name = name; var init = func { # initialize the cached fix symbol + var draw_fn = me.getOption('draw_function'); + if(typeof(draw_fn) != 'func') + draw_fn = drawFIX; + + var cache = StyleableCacheable.new( + name:_name, + draw_func: draw_fn, + cache: SymbolCache32x32, + draw_mode: SymbolCache.DRAW_CENTERED, + relevant_keys: ["line_width", "color"], + ); cache.render(me.element, me.style).setScale(me.style.scale_factor); + var txt_offset = me.getStyle('text_offset', [17, 35]); + var txt_color = me.getStyle('text_color', [0,0.6,0.85]); # non-cached stuff: - if (me.style.show_labels) - me.text_fix = me.newText(me.model.id).setScale(me.style.scale_factor).setTranslation(17,35).setColor(0,0.6,0.85); + if (me.style.show_labels){ + me.text_fix = me.newText(me.model.id). + setScale(me.style.scale_factor). + setTranslation(txt_offset). + setColor(txt_color); + } } -var draw = func; +var draw = func {me.callback('draw');}; diff --git a/Nasal/canvas/map/HOLD.lcontroller b/Nasal/canvas/map/HOLD.lcontroller new file mode 100644 index 000000000..93b204e6a --- /dev/null +++ b/Nasal/canvas/map/HOLD.lcontroller @@ -0,0 +1,100 @@ +# See: http://wiki.flightgear.org/MapStructure +# Class things: +var name = 'HOLD'; +var parents = [canvas.SymbolLayer.Controller]; +var __self__ = caller(0)[0]; + +canvas.SymbolLayer.Controller.add(name, __self__); +canvas.SymbolLayer.add(name, { + parents: [MultiSymbolLayer], + type: name, # Symbol type + df_controller: __self__, # controller to use by default -- this one +}); +var new = func(layer) { + var m = { + parents: [__self__], + layer: layer, + #map: layer.map, + listeners: [], + }; + #debug.dump(layer.parents); + layer.searcher._equals = func(a,b) a.id == b.id; + #append(m.listeners, setlistener(layer.options.fplan_active, func m.layer.update() )); + #m.addVisibilityListener(); + + return m; +}; + +var del = func() { + foreach (var l; me.listeners) + removelistener(l); +}; + +var searchCmd = func { + var results = []; + var node = props.globals.getNode(me.layer.options.hold_node); + var wp_id = node.getValue('wp'); + if(wp_id == nil or wp_id == '' or wp_id == '---'){ + return []; + } + var pointsNode = node.getNode('points'); + var pointNode = nil; + var lat = nil; + var lon = nil; + if(pointsNode != nil) + pointNode = pointsNode.getNode('point', 0); + if (pointNode != nil){ + lat = pointNode.getValue('lat'); + lon = pointNode.getValue('lon'); + } + if (pointNode != nil and lat != nil and lon != nil){ + var r = node.getValue('crs'); + var d = node.getValue('dist'); + var t = node.getValue('turn'); + var model = { + parents: [geo.Coord], + id: wp_id~r~d~t, + pos: pointNode, + type: 'pattern', + latlon: func(){ + return [ + lat, + lon + ]; + }, + radial: r, + dist: d, + turn: t, + equals: func(o){me.id == o.id and me.radial == o.radial} + }; + append(results, model); + } else { + var wp_idx = node.getValue('wp_id'); + var fp = flightplan(); + var wp = fp.getWP(wp_idx); + #print("HOLD AT "~wp.wp_lat~", "~wp.wp_lon); + if(wp == nil or wp.id != wp_id) + return []; + var wp_lat = wp.wp_lat; + var wp_lon = wp.wp_lon; + var model = { + parents: [geo.Coord], + id: wp_id~'-'~wp_idx~'-inactive', + pos: nil, + type: 'hold_symbol', + latlon: func(){ + return [ + wp_lat, + wp_lon + ]; + }, + radial: node.getValue('crs'), + dist: node.getValue('dist'), + turn: node.getValue('turn'), + equals: func(o){me.id == o.id} + }; + append(results, model); + } + + return results; +} diff --git a/Nasal/canvas/map/HOLD.symbol b/Nasal/canvas/map/HOLD.symbol new file mode 100644 index 000000000..22dfad9a2 --- /dev/null +++ b/Nasal/canvas/map/HOLD.symbol @@ -0,0 +1,106 @@ +# See: http://wiki.flightgear.org/MapStructure +# Class things: +var name = 'HOLD'; +var parents = [canvas.DotSym]; +var __self__ = caller(0)[0]; +canvas.DotSym.makeinstance( name, __self__ ); + +#canvas.SymbolLayer.get(name).df_style = { +# radius: 13, +#}; + +var element_type = "group"; + +var draw_pattern = func(dist, ){ + var pattern = me.element.createChild("path", 'hold-pattern'); + pattern.set("z-index",4); + var w = (dist * 2) / math.pi; + var h = dist * 70; + var r = w / 2; + pattern.arcSmallCW(r,r,0,70 * w,0). + line(0,h). + arcSmallCW(r,r,0,-70 * w,0). + line(0,-h). + setStrokeLineWidth(5). + setColor(me.active_color); + return pattern; +}; + +var draw_hold_sym = func(turn){ + var hold_symbol = me.element.createChild("group", 'hold-symbol'); + var svg = "Nasal/canvas/map/Airbus/Images/airbus_hold"~turn~".svg"; + canvas.parsesvg(hold_symbol, svg); + hold_symbol.setTranslation(-32,-64).set('z-index', 4); + return hold_symbol; +} + +var init = func { + var type = me.model.type; + var dist = me.model.dist; + var turn = me.model.turn; + me.active_color = me.getStyle('active_color', + me.getOption('active_route_color', + [0.4,0.7,0.4])); + me.inactive_color = me.getStyle('inactive_color', + me.getOption('inactive_route_color', + [0.95,0.95,0.21])); + if(type == 'pattern'){ + me.hold_pattern = me.draw_pattern(dist); + me.hold_symbol = nil; + } else { + #print('CREATING HOLD SYM'); + me.hold_pattern = nil; + me.hold_symbol = me.draw_hold_sym(turn); + } +} + +var draw = func{ + var type = me.model.type; + var map = me.layer.map; + var pos = map.getPos(); + if(type == 'pattern'){ + var dist = me.model.dist; + if(me.hold_symbol != nil){ + me.hold_symbol.hide(); + } + if(me.hold_pattern == nil) + me.hold_pattern = me.draw_pattern(dist); + if(me.hold_pattern == nil) return; + var radial = me.model.radial; + if(radial != nil){ + + var hdg = pos[2]; + if(hdg == nil) hdg = 0; + radial -= hdg; + if(radial < 0) radial = 360 + radial; + me.element.setRotation(radial*D2R); + } + var rng = pos[3]; + if(rng == nil) rng = 10; + rng = 10 / rng; + me.element.setScale(rng,rng); + var lat_ctrl = getprop(me.getOption('lat_ctrl')); + var lnav = (lat_ctrl == me.getOption('lat_ctrl_managed','fmgc')); + var actv = getprop(me.getOption('fplan_active')); + #print('HOLD ' ~ lnav ~ ',' ~ actv); + if(!lnav or !actv) + me.hold_pattern.setStrokeDashArray([32, 16]); + else + me.hold_pattern.setStrokeDashArray([1,0]); + if(!actv) + me.hold_pattern.setColor(me.inactive_color); + else + me.hold_pattern.setColor(me.active_color); + } else { + var turn = me.model.turn; + if(me.hold_pattern != nil){ + me.hold_pattern.hide(); + } + if(me.hold_symbol == nil) + me.hold_symbol = me.draw_hold_sym(turn); + + me.hold_symbol.show(); + me.element.setScale(1,1); + me.element.setRotation(0); + } +} diff --git a/Nasal/canvas/map/RTE.lcontroller b/Nasal/canvas/map/RTE.lcontroller index 2e3c66e34..914a84e3d 100644 --- a/Nasal/canvas/map/RTE.lcontroller +++ b/Nasal/canvas/map/RTE.lcontroller @@ -15,6 +15,7 @@ SymbolLayer.add(name, { active_node: "/autopilot/route-manager/active", current_wp_node: "/autopilot/route-manager/current-wp", wp_num: "/autopilot/route-manager/route/num", + display_inactive_rte: 0 } }); var new = func(layer) { @@ -25,9 +26,19 @@ var new = func(layer) { listeners: [], }; layer.searcher._equals = func(l,r) 0; # TODO: create model objects instead? - append(m.listeners, setlistener(layer.options.active_node, func m.layer.update() ), setlistener(layer.options.wp_num, func m.layer.update() )); + append(m.listeners, setlistener(layer.options.active_node, func m.layer.update() ), + setlistener(layer.options.wp_num, func m.layer.update() )); + m.addVisibilityListener(); - + var driver = opt_member(m.layer.options, 'route_driver'); + if(driver == nil){ + driver = RouteDriver.new(); + } + var driver_listeners = driver.getListeners(); + foreach(var listener; driver_listeners){ + append(m.listeners, setlistener(listener, func m.layer.update())); + } + m.route_driver = driver; return m; }; var del = func() { @@ -35,21 +46,49 @@ var del = func() { removelistener(l); }; +var last_result = []; + var searchCmd = func { # FIXME: do we return the current route even if it isn't active? printlog(_MP_dbg_lvl, "Running query: ", name); var plans = []; # TODO: multiple flightplans? - + var plans = []; + var driver = me.route_driver; + driver.update(); + if(!driver.shouldUpdate()) return me.last_result; # http://wiki.flightgear.org/Nasal_Flightplan - var fp = flightplan(); - var fpSize = fp.getPlanSize(); - if (!getprop(me.layer.options.active_node)) fpSize = 0; - var coords = []; - for (var i=0; i Append'); + append(results, n); + } + } + return results; +} diff --git a/Nasal/canvas/map/SPD-profile.symbol b/Nasal/canvas/map/SPD-profile.symbol new file mode 100644 index 000000000..a9b6f3833 --- /dev/null +++ b/Nasal/canvas/map/SPD-profile.symbol @@ -0,0 +1,40 @@ +# See: http://wiki.flightgear.org/MapStructure +# Class things: +var name = 'SPD-profile'; +var parents = [canvas.SVGSymbol]; +var __self__ = caller(0)[0]; +canvas.DotSym.makeinstance( name, __self__ ); + +#canvas.SymbolLayer.get(name).df_style = { +# radius: 13, +#}; + +var element_type = "group"; +var svg_path = 'Nasal/canvas/map/Airbus/Images/airbus_spd_limit.svg'; +var spd_sym = nil; + +#var init = func { +# var name = me.model.getName(); +# #var radius = me.style.radius; +# var sym_group = me.element.createChild("group", name); +# sym_group.set("z-index",5); +# var color = me.getStyle('color'); +# var spd_path = sym_group.createChild("path"). +# setStrokeLineWidth(3). +# moveTo(-17,0). +# arcSmallCW(17,17,0,34,0). +# arcSmallCW(17,17,0,-34,0). +# setColor(color). +# setColorFill(color). +# set("z-index",5); +#} + +var draw = func{ + # var name = me.model.getName(); + # var sym_group = me.element.getElementById(name); + # sym_group.set('z-index', 5); + if(me.spd_sym == nil){ + me.spd_sym = me.getElementById("airbus-spd-sym"); + me.spd_sym.setTranslation(-24, -24).set('z-index', 5); + } +} diff --git a/Nasal/canvas/map/VOR-airbus.lcontroller b/Nasal/canvas/map/VOR-airbus.lcontroller new file mode 100644 index 000000000..82fb67bcd --- /dev/null +++ b/Nasal/canvas/map/VOR-airbus.lcontroller @@ -0,0 +1,62 @@ +# See: http://wiki.flightgear.org/MapStructure +# Class things: +var name ='VOR-airbus'; +var parents = [SymbolLayer.Controller]; +var __self__ = caller(0)[0]; +SymbolLayer.Controller.add(name, __self__); +SymbolLayer.add(name, { + parents: [NavaidSymbolLayer], + type: name, # Symbol type + df_controller: __self__, # controller to use by default -- this one + df_options: { + nav1_frq: 'instrumentation/nav/frequencies/selected-mhz', + nav2_frq: 'instrumentation/nav[1]/frequencies/selected-mhz' + }, + df_style: { + scale: 1 + } +}); +var new = func(layer) { + var m = { + parents: [__self__], + layer: layer, + map: layer.map, + active_vors: [], + navNs: props.globals.getNode("instrumentation").getChildren("nav"), + listeners: [], + query_type:'vor', + }; + setsize(m.active_vors, size(m.navNs)); + foreach (var navN; m.navNs) { + append(m.listeners, setlistener( + navN.getNode("frequencies/selected-mhz"), + func m.changed_freq() + )); + } + #call(debug.dump, keys(layer)); + m.changed_freq(update:0); + m.addVisibilityListener(); + + return m; +}; +var del = func() { + printlog(_MP_dbg_lvl, name,".lcontroller.del()"); + foreach (var l; me.listeners) + removelistener(l); +}; + +# Controller methods +var isActive = func(model) { + var my_freq = model.frequency/100; + foreach (var freq; me.active_vors) + if (freq == my_freq) return 1; + return 0; +}; +var changed_freq = func(update=1) { + #debug.dump(me.active_vors); + foreach (var navN; me.navNs) + me.active_vors[ navN.getIndex() ] = navN.getValue("frequencies/selected-mhz"); + if (update) me.layer.update(); +}; + +var searchCmd = NavaidSymbolLayer.make('vor'); \ No newline at end of file diff --git a/Nasal/canvas/map/VOR-airbus.symbol b/Nasal/canvas/map/VOR-airbus.symbol new file mode 100644 index 000000000..428042141 --- /dev/null +++ b/Nasal/canvas/map/VOR-airbus.symbol @@ -0,0 +1,44 @@ +# Class things: +var name = 'VOR-airbus'; +var parents = [SVGSymbol]; +var __self__ = caller(0)[0]; +DotSym.makeinstance( name, __self__ ); + +var element_type = "group"; # we want a group, becomes "me.element" +var text_vor = nil; + +var svg_path = 'Nasal/canvas/map/Airbus/Images/airbus_vor.svg'; +var vor_sym = nil; + +var draw = func{ + if(me.vor_sym == nil) + me.vor_sym = me.element.getElementById("airbus-vor-sym"); + me.vor_sym.setTranslation(-24,-24); + if(me.text_vor == nil){ + var transl = me.getStyle('translation', [25,10]); + var text_color = me.getStyle('text_color', [1,1,1]); + me.text_vor = me.element.createChild("text") + .setDrawMode( canvas.Text.TEXT ) + .setText(me.model.id) + .setFont("LiberationFonts/LiberationSans-Regular.ttf") + .setFontSize(28) + .setColor(text_color) + .setTranslation(transl); + } + + var frq = me.model.frequency; + if(frq != nil){ + var dfcolor = me.getStyle('color', [0.9,0,0.47]); + var tuned_color = me.getStyle('tuned_color', [0,0.62,0.84]); + frq = frq / 100; + var nav1_frq = getprop(me.options.nav1_frq); + var nav2_frq = getprop(me.options.nav2_frq); + if(nav1_frq == frq or nav2_frq == frq){ + me.element.setColor(tuned_color); + } else { + me.element.setColor(dfcolor); + } + } + + me.text_vor.setColor(me.getStyle('text_color', [1,1,1])); +} \ No newline at end of file diff --git a/Nasal/canvas/map/WPT-airbus.lcontroller b/Nasal/canvas/map/WPT-airbus.lcontroller new file mode 100644 index 000000000..a91f8d3fd --- /dev/null +++ b/Nasal/canvas/map/WPT-airbus.lcontroller @@ -0,0 +1,137 @@ +# See: http://wiki.flightgear.org/MapStructure +# Class things: +var name = 'WPT-airbus'; +var parents = [canvas.SymbolLayer.Controller]; +var __self__ = caller(0)[0]; + +canvas.SymbolLayer.Controller.add(name, __self__); +canvas.SymbolLayer.add(name, { + parents: [canvas.MultiSymbolLayer], + type: name, # Symbol type + df_controller: __self__, # controller to use by default -- this one + df_options: { # default configuration options + fix_symbol: func(group){ + group.createChild('path') + .moveTo(-10,0) + .lineTo(0,-17) + .lineTo(10,0) + .lineTo(0,17) + .close() + .setStrokeLineWidth(3) + .setColor(1,1,1) + .setScale(1); + }, + vor_symbol: 'Nasal/canvas/map/Airbus/Images/airbus_vor.svg', + airport_symbol: 'Nasal/canvas/map/Airbus/Images/airbus_airport.svg', + ndb_symbol: func(group){ + group.createChild('path') + .moveTo(-15,15) + .lineTo(0,-15) + .lineTo(15,15) + .close() + .setStrokeLineWidth(3) + #.setColor(0.69,0,0.39) + #.setTranslation(-24, -24), + .setScale(1,1); + } + }, + df_style: { + active_wp_color: [0.4,0.7,0.4], + current_wp_color: [1,1,1], + translation: { + 'airport': [-24,-24], + 'vor': [-24,-24] + } + }, + toggle_cstr: 0 +}); + +var new = func(layer) { + var m = { + parents: [__self__], + layer: layer, + map: layer.map, + listeners: [], + }; + layer.searcher._equals = func(a,b) a.equals(b); + var driver = opt_member(m.layer.options, 'route_driver'); + if(driver == nil){ + driver = RouteDriver.new(); + } + var driver_listeners = driver.getListeners(); + foreach(var listener; driver_listeners){ + append(m.listeners, setlistener(listener, func m.layer.update())); + } + m.route_driver = driver; + return m; +}; + +var del = func() { + foreach (var l; me.listeners) + removelistener(l); +}; + +var WPTModel = { + new: func(fp, idx) { + var m = { parents:[WPTModel] }; + var wp = fp.getWP(idx); + + m.id = wp.id; + m.name = wp.wp_name; + m.alt = wp.alt_cstr; + m.spd = wp.speed_cstr; + m.type = wp.wp_type; + + (m.lat,m.lon) = (wp.wp_lat,wp.wp_lon); + var is_rwy = (m.type == 'runway'); + var id_len = size(m.id); + if(!is_rwy and id_len < 5){ + if(id_len == 4 and airportinfo(m.id) != nil) + m.navtype = 'airport'; + else { + var navaid = nil; + foreach(var t; ['vor', 'ndb']){ + navaid = navinfo(m.lat, m.lon, t, m.id); + if(navaid != nil and size(navaid)){ + m.navtype = t; + break; + } + } + if(navaid == nil or !size(navaid)) m.navtype = 'fix'; + } + } else { + m.navtype = (is_rwy ? 'rwy' : 'fix'); + } + + m.wp = wp; + idx = wp.index; + m.idx = idx; + m.is_departure = (idx == 0 and is_rwy); + m.is_destination = (idx > 0 and is_rwy); + return m; + }, + equals: func(other) { + # this is set on symbol init, so use this for equality... + me.name == other.name and me.alt == other.alt and + me.type == other.type and me.idx == other.idx and + me.navtype == other.navtype + }, +}; + +var searchCmd = func { + var driver = me.route_driver; + if(!driver.shouldUpdate()) return []; + driver.update(); + var result = []; + var planCount = driver.getNumberOfFlightPlans(); + for(var idx = 0; idx < planCount; idx += 1){ + var fp = driver.getFlightPlan(idx); + if(fp == nil) continue; + var fpSize = fp.getPlanSize(idx); + for (var i = 0; i < fpSize; i+=1){ + if(!driver.shouldUpdate()) return[]; + append(result, WPTModel.new(fp, i)); + } + } + return result; +} diff --git a/Nasal/canvas/map/WPT-airbus.symbol b/Nasal/canvas/map/WPT-airbus.symbol new file mode 100644 index 000000000..29f6fe26c --- /dev/null +++ b/Nasal/canvas/map/WPT-airbus.symbol @@ -0,0 +1,137 @@ +# See: http://wiki.flightgear.org/MapStructure +# Class things: +var name = 'WPT-airbus'; +var parents = [canvas.DotSym]; +var __self__ = caller(0)[0]; +canvas.DotSym.makeinstance( name, __self__ ); + +var element_type = "group"; + +var init = func { + var name = me.model.name; + var alt = me.model.alt; + var spd = me.model.spd; + var wp_group = me.element; + me.alt_path = nil; + + var colors = [ + 'wp_color', + 'current_wp_color', + 'constraint_color', + 'active_constraint_color', + 'missed_constraint_color' + ]; + foreach(col; colors){ + me[col] = me.getStyle(col, me.getOption(col)); + } + + var idLen = size(me.model.id); + var draw_sym = nil; + var navtype = me.model.navtype; + if (navtype == nil) navtype = 'fix'; + if(navtype == 'airport') + draw_sym = me.options.airport_symbol; + elsif(navtype == 'vor') + draw_sym = me.options.vor_symbol; + elsif(navtype == 'ndb') + draw_sym = me.options.ndb_symbol; + else + draw_sym = me.options.fix_symbol; + me.wp_sym = me.element.createChild('group', 'wp-'~ me.model.idx); + if(typeof(draw_sym) == 'func') + draw_sym(me.wp_sym); + elsif(typeof(draw_sym) == 'scalar') + canvas.parsesvg(me.wp_sym, draw_sym); + var translation = me.getStyle('translation', {}); + if(contains(translation, navtype)){ + me.wp_sym.setTranslation(translation[navtype]); + } + me.text_wps = wp_group.createChild("text", "wp-text-" ~ me.model.idx) + .setDrawMode( canvas.Text.TEXT ) + .setText(me.model.name) + .setFont("LiberationFonts/LiberationSans-Regular.ttf") + .setFontSize(28) + .setTranslation(25,15) + .setColor(1,1,1); + me.text_alt = nil; + if(alt > 0 or spd > 0){ + var cstr_txt = "\n"; + if(alt > 0){ + if(alt > 10000) + cstr_txt ~= sprintf('FL%3.0f', int(alt / 100)); + else + cstr_txt ~= sprintf('%4.0f', int(alt)); + } + if(spd > 0){ + if(alt > 0) cstr_txt ~= "\n"; + if(spd <= 1) + cstr_txt ~= sprintf('%1.2fM', spd); + else + cstr_txt ~= sprintf('%3.0fKT', int(spd)); + } + me.text_alt = wp_group.createChild("text", "wp-alt-text-" ~ me.model.idx) + .setDrawMode( canvas.Text.TEXT ) + .setText(cstr_txt) + .setFont("LiberationFonts/LiberationSans-Regular.ttf") + .setFontSize(28) + .setTranslation(25,15); + } +} + +var draw = func{ + var wp_group = me.element; + var alt = me.model.alt; + var i = me.model.idx; + var vnav_actv = getprop(me.options.ver_ctrl) == me.options.managed_val; + var curwp = getprop(me.options.current_wp); + if(alt > 0){ + var wp_d = me.model.wp.distance_along_route; + var lvl_off_at = getprop(me.options.level_off_alt); + if(lvl_off_at == nil) lvl_off_at = 0; + if(me.alt_path == nil){ + me.alt_path = wp_group.createChild("path"). + setStrokeLineWidth(4). + moveTo(-22,0). + arcSmallCW(22,22,0,44,0). + arcSmallCW(22,22,0,-44,0); + } + if(vnav_actv){ + if(lvl_off_at and (lvl_off_at - wp_d) > 0.5 and curwp == i) + me.alt_path.setColor(me.missed_constraint_color); + else + me.alt_path.setColor(me.active_constraint_color); + } + else + me.alt_path.setColor(me.constraint_color); + if(me.layer.toggle_cstr) + me.alt_path.show(); + else + me.alt_path.hide(); + } else { + if(me.alt_path != nil) me.alt_path.hide(); + } + wp_group.set("z-index",4); + #var sym = me.element.getElementById('wp-' ~ me.model.idx); + if(alt > 0 and me.text_alt != nil){ + if(vnav_actv) + me.text_alt.setColor(me.active_constraint_color); + else + me.text_alt.setColor(me.constraint_color); + } + if(i == curwp) { + me.wp_sym.setColor(me.current_wp_color); + me.text_wps.setColor(me.current_wp_color); + } else { + me.wp_sym.setColor(me.wp_color); + me.text_wps.setColor(me.wp_color); + } + if(me.model.is_departure or me.model.is_destination){ + var prop = (me.model.is_departure ? 'departure' : 'destination'); + var rwy = getprop("/autopilot/route-manager/"~prop~"/runway"); + if(rwy != nil and size(rwy) > 0){ + me.wp_sym.hide(); + } else { + me.wp_sym.show(); + } + } +} diff --git a/Nasal/canvas/map/WPT.lcontroller b/Nasal/canvas/map/WPT.lcontroller index 2f2fd291c..b4d030fae 100644 --- a/Nasal/canvas/map/WPT.lcontroller +++ b/Nasal/canvas/map/WPT.lcontroller @@ -24,6 +24,15 @@ var new = func(layer) { }; layer.searcher._equals = func(l,r) l.equals(r); append(m.listeners, setlistener(layer.options.active_node, func m.layer.update() ), setlistener(layer.options.wp_num, func m.layer.update() )); + var driver = opt_member(m.layer.options, 'route_driver'); + if(driver == nil){ + driver = RouteDriver.new(); + } + var driver_listeners = driver.getListeners(); + foreach(var listener; driver_listeners){ + append(m.listeners, setlistener(listener, func m.layer.update())); + } + m.route_driver = driver; m.addVisibilityListener(); return m; @@ -59,12 +68,16 @@ var searchCmd = func { printlog(_MP_dbg_lvl, "Running query: "~name); if (!getprop(me.options.active_node)) return []; - - var fp = flightplan(); - var fpSize = fp.getPlanSize(); + var driver = me.route_driver; + driver.update(); var result = []; - for (var i = 0; i < fpSize; i+=1) - append(result, WPT_model.new(fp, i)); + var planCount = driver.getNumberOfFlightPlans(); + for(var idx = 0; idx < planCount; idx += 1){ + var fp = driver.getFlightPlan(idx); + var fpSize = fp.getPlanSize(idx); + for (var i = 0; i < fpSize; i+=1) + append(result, WPT_model.new(fp, i)); + } return result; }; diff --git a/Nasal/canvas/map/WXR_live.lcontroller b/Nasal/canvas/map/WXR_live.lcontroller new file mode 100644 index 000000000..9f9d3f136 --- /dev/null +++ b/Nasal/canvas/map/WXR_live.lcontroller @@ -0,0 +1,69 @@ +# See: http://wiki.flightgear.org/MapStructure +# Class things: +var name = 'WXR_live'; +var parents = [SymbolLayer.Controller]; +var __self__ = caller(0)[0]; +SymbolLayer.Controller.add(name, __self__); +SymbolLayer.add(name, { + parents: [MultiSymbolLayer], + type: name, # Symbol type + df_controller: __self__, # controller to use by default -- this one + df_options: { # default configuration options + viewport_radius: 670 + } +}); + +var wxr_tree = "/instrumentation/wxr"; + +var new = func(layer) { + var m = { + parents: [__self__], + layer: layer, + map: layer.map, + listeners: [], + }; + layer.searcher._equals = func(l,r) l.equals(r); + m.addVisibilityListener(); + #io.read_properties(getprop("/sim/fg-root") ~ "/Nasal/canvas/wxr_api.xml", wxr_tree); + var saved_conf = getprop("/sim/fg-home") ~ "/Export/wxr_api.xml"; + if(io.stat(saved_conf) != nil) + io.read_properties(saved_conf, wxr_tree); + else + io.read_properties(getprop("/sim/fg-root") ~ "/Nasal/canvas/wxr_api.xml", wxr_tree); + return m; +}; +var del = func() { + #print(name~".lcontroller.del()"); + foreach (var l; me.listeners) + removelistener(l); +}; + +var searchCmd = func { + if(me.map.getRange() == nil) return []; + var api_key = getprop(wxr_tree~"/api-key"); + if(!api_key or api_key == '' or api_key == 'YOUR API KEY') + return []; + var lat = getprop(wxr_tree~"/center/latitude-deg"); + var lon = getprop(wxr_tree~"/center/longitude-deg"); + + if(lat == nil or lon == nil) { + var pos = geo.aircraft_position(); + lat = pos.lat(); + lon = pos.lon(); + } + + var result = geo.Coord.new(); + result.set_latlon(lat, lon); + result.rangeNm = me.map.getRange(); + result.key = api_key; + result.res = getprop(wxr_tree~"/resolution"); + result.ltype = getprop(wxr_tree~"/layer-type"); + result.smooth = getprop(wxr_tree~"/smooth"); + result.fetchRad = getprop(wxr_tree~"/fetch-radius"); + result.equals = func(r){ + me.ltype == r.ltype and me.fetchRad == r.fetchRad and + me.lat == r.lat and me.lon == r.lon + }; + + return [result]; +}; diff --git a/Nasal/canvas/map/WXR_live.symbol b/Nasal/canvas/map/WXR_live.symbol new file mode 100644 index 000000000..c5e712e88 --- /dev/null +++ b/Nasal/canvas/map/WXR_live.symbol @@ -0,0 +1,111 @@ +# See: http://wiki.flightgear.org/MapStructure +# Class things: +var name = 'WXR_live'; +var parents = [DotSym]; +var __self__ = caller(0)[0]; +DotSym.makeinstance( name, __self__ ); + +var element_type = "group"; + +var getWXRImageFilePath = func(){ + var home = getprop('sim/fg-home'); + var aircraft = getprop('sim/aircraft'); + var idx = me.layer.getCanvas()._node.getIndex(); + return home ~ '/Export/' ~ aircraft ~ '-wxr-'~me.ltype~'-'~idx~'.png'; +}; + +var getWXRAPIUrl = func(lat, lon){ + var res = me.res; + if(me.ltype == "radar"){ + return "http://api.wunderground.com/api/"~me.key~ + "/radar/image.png?centerlat="~lat~"¢erlon="~lon~ + "&radius="~me.fetchRad~"&width="~res~"&height="~res~ + "&smooth="~me.smooth; + } else { + return nil; + } +}; + +var fetchWXRMap = func(size){ + if(me.fetching) return; + var pos = geo.aircraft_position(); + var lat = pos.lat(); + var lon = pos.lon(); + var url = me.getWXRAPIUrl(lat, lon); + var filePath = me.getWXRImageFilePath(); + if(url == nil) { + print(me.name~': No URL!'); + return; + } + me.fetching = 1; + http.save(url, filePath) + .fail(func(){print(me.name~': Download failed!');}) + .done(func(){ + var sz = size * 2; + var transl = -size; + me.wxlayer.hide(); + me.wxlayer.setFile(filePath) + .setSize(sz, sz) + .setTranslation(transl, transl); + me.wxlayer.show(); + me.last_request = getprop("/sim/time/elapsed-sec"); + }) + .always(func(){ + setprop("/instrumentation/wxr/center/latitude-deg", lat); + setprop("/instrumentation/wxr/center/longitude-deg", lon); + me.fetching = 0; + print("[WXR] Live Layer Request: "~url); # Debugging + }); +}; + +var init = func { + #print('WXR init'); + me.fetching = 0; + me.key = me.model.key; # API Key from Wunderground API Subscription + me.res = me.model.res; # Resolution of image to fetch (default 2048) + me.ltype = me.model.ltype; # Layer Type - radar, satellite + me.smooth = me.model.smooth; # If we should fetch a smoothened image or the original + me.fetchRad = me.model.fetchRad; # Radius of radar layer to fetch + me.range = me.model.rangeNm; # Range of Navigation Display + me.viewport_radius = me.getOption('viewport_radius', 670); + + me.wxlayer = me.element.createChild("image").set("z-index", -100).hide(); + + me.last_request = -210; + me.update_interval = 240; + + var r_scaled = (me.fetchRad*me.viewport_radius)/me.range; + me.fetchWXRMap(r_scaled); +}; + +var draw = func { + var range = me.layer.map.getRange(); # Range of Navigation Display + var update_size = (range != me.range); + me.range = range; + me.fetchRad = me.model.fetchRad; # Radius of radar layer to fetch + #var r_scaled = (me.fetchRad*670)/me.range; + var r_scaled = (me.fetchRad*me.viewport_radius)/me.range; + var hdg = me.layer.map.getHdg(); + var rot = 0 - hdg; + if(rot < 0) rot = 360 + rot; + me.element.setRotation(rot*D2R); + if(update_size){ + print('WXR draw range:'~ me.range); + print('Update size: '~r_scaled~'x2 = '~(r_scaled*2)); + me.wxlayer.hide(); + me.wxlayer.setSize(2*r_scaled, 2*r_scaled) + .setTranslation(-r_scaled, -r_scaled); + me.wxlayer.show(); + } + + if(getprop("/sim/time/elapsed-sec") - me.last_request > me.update_interval) { + #print('Fetching WXR map...'); + me.key = me.model.key; # API Key from Wunderground API Subscription + me.res = me.model.res; # Resolution of image to fetch (default 2048) + me.ltype = me.model.ltype; # Layer Type - radar, satellite + me.smooth = me.model.smooth; # If we should fetch a smoothened image or the original + + me.fetchWXRMap(r_scaled); + } +}; + diff --git a/Nasal/canvas/map/navdisplay.mfd b/Nasal/canvas/map/navdisplay.mfd index d98fb8899..6b46c1cbc 100644 --- a/Nasal/canvas/map/navdisplay.mfd +++ b/Nasal/canvas/map/navdisplay.mfd @@ -22,6 +22,8 @@ io.include("Nasal/canvas/map/navdisplay.styles"); # encapsulate hdg/lat/lon source, so that the ND may also display AI/MP aircraft in a pilot-view at some point (aka stress-testing) # TODO: this predates aircraftpos.controller (MapStructure) should probably be unified to some degree ... +var wxr_live_tree = "/instrumentation/wxr"; + var NDSourceDriver = {}; NDSourceDriver.new = func { var m = {parents:[NDSourceDriver]}; @@ -78,6 +80,10 @@ var default_switches = { 'toggle_true_north': {path: '/mfd/true-north', value:0, type:'BOOL'}, 'toggle_rangearc': {path: '/mfd/rangearc', value:0, type:'BOOL'}, 'toggle_track_heading':{path: '/trk-selected', value:0, type:'BOOL'}, + 'toggle_weather_live': {path: '/mfd/wxr-live-enabled', value: 0, type: 'BOOL'}, + 'toggle_chrono': {path: '/inputs/CHRONO', value: 0, type: 'INT'}, + 'toggle_xtrk_error': {path: '/mfd/xtrk-error', value: 0, type: 'BOOL'}, + 'toggle_trk_line': {path: '/mfd/trk-line', value: 0, type: 'BOOL'}, }; ## @@ -118,6 +124,10 @@ var NavDisplay = { # listeners for cockpit switches listen_switch: func(s,c) { # print("event setup for: ", id(c)); + if (!contains(me.efis_switches, s)) { + print('EFIS Switch not defined: '~ s); + return; + } me.listen( me.get_full_switch_path(s), func { # print("listen_switch triggered:", s, " callback id:", id(c) ); c(); @@ -133,11 +143,22 @@ var NavDisplay = { # helper method for getting configurable cockpit switches (which are usually different in each aircraft) get_switch: func(s) { var switch = me.efis_switches[s]; + if(switch == nil) return nil; var path = me.efis_path ~ switch.path ; #print(s,":Getting switch prop:", path); return getprop( path ); }, + + # helper method for setting configurable cockpit switches (which are usually different in each aircraft) + set_switch: func(s, v) { + var switch = me.efis_switches[s]; + if(switch == nil) return nil; + var path = me.efis_path ~ switch.path ; + #print(s,":Getting switch prop:", path); + + setprop( path, v ); + }, # for creating NDs that are driven by AI traffic instead of the main aircraft (generalization rocks!) connectAI: func(source=nil) { @@ -159,6 +180,12 @@ var NavDisplay = { NavDisplay.id +=1; var m = { parents : [NavDisplay]}; + var df_toggles = keys(default_switches); + foreach(var toggle_name; df_toggles){ + if(!contains(switches, toggle_name)) + switches[toggle_name] = default_switches[toggle_name]; + } + m.inited = 0; m.timers=[]; @@ -166,6 +193,7 @@ var NavDisplay = { m.aircraft_source = NDSourceDriver.new(); # uses the main aircraft as the driver/source (speeds, position, heading) m.nd_style = NDStyles[style]; # look up ND specific stuff (file names etc) + m.style_name = style; m.radio_list=["instrumentation/comm/frequencies","instrumentation/comm[1]/frequencies", "instrumentation/nav/frequencies", "instrumentation/nav[1]/frequencies"]; @@ -197,7 +225,7 @@ var NavDisplay = { m.mk_minimums = props.globals.getNode("instrumentation/mk-viii/inputs/arinc429/decision-height"); # TODO: these are switches, can be unified with switch handling hash above (eventually): - m.nd_plan_wpt = m.efis.initNode("inputs/plan-wpt-index", 0, "INT"); # not yet in switches hash + m.nd_plan_wpt = m.efis.initNode("inputs/plan-wpt-index", -1, "INT"); # not yet in switches hash ### # initialize all switches based on the defaults specified in the switch hash @@ -212,13 +240,28 @@ var NavDisplay = { return m; }, - newMFD: func(canvas_group, parent=nil, options=nil, update_time=0.05) + newMFD: func(canvas_group, parent=nil, nd_options=nil, update_time=0.05) { if (me.inited) die("MFD already was added to scene"); me.inited = 1; + me.range_dependant_layers = []; + me.always_update_layers = {}; me.update_timer = maketimer(update_time, func me.update() ); me.nd = canvas_group; me.canvas_handle = parent; + me.df_options = nil; + if (contains(me.nd_style, 'options')) + me.df_options = me.nd_style.options; + nd_options = default_hash(nd_options, me.df_options); + me.options = nd_options; + me.route_driver = nil; + if (contains(me.options, 'route_driver')) { + me.route_driver = me.options.route_driver; + } + elsif (contains(me.options, 'defaults')) { + if(contains(me.options.defaults, 'route_driver')) + me.route_driver = me.options.defaults.route_driver; + } # load the specified SVG file into the me.nd group and populate all sub groups @@ -245,10 +288,19 @@ var NavDisplay = { "HdgBugCRT2","TrkBugLCD2","HdgBugLCD2","hdgBug2","selHdgLine","selHdgLine2","curHdgPtr2", "staArrowL","staArrowR","staToL","staFromL","staToR","staFromR"] ) me.symbols[element] = me.nd.getElementById(element).updateCenter(); + + var map_rect = [124, 1024, 1024, 0]; + var map_opts = me.options.map; + if (map_opts == nil) map_opts = {}; + if (typeof(map_opts.rect) == 'vector') + map_rect = map_opts.rect; + map_rect = join(', ', map_rect); me.map = me.nd.createChild("map","map") - .set("clip", "rect(124, 1024, 1024, 0)") + .set("clip", map_rect) .set("screen-range", 700); + var z_idx = map_opts['z-index']; + if (z_idx != nil) me.map.set('z-index', z_idx); me.update_sub(); # init some map properties based on switches @@ -323,6 +375,7 @@ var NavDisplay = { me.layers={}; # storage container for all ND specific layers # look up all required layers as specified per the NDStyle hash and do the initial setup for event handling + var default_opts = me.options != nil and contains(me.options, 'defaults') ? me.options.defaults : nil; foreach(var layer; me.nd_style.layers) { if(layer['disabled']) continue; # skip this layer #print("newMFD(): Setting up ND layer:", layer.name); @@ -332,16 +385,33 @@ var NavDisplay = { the_layer = me.layers[layer.name] = canvas.MAP_LAYERS[layer.name].new( me.map, layer.name, controller ); else { printlog(_MP_dbg_lvl, "Setting up MapStructure-based layer for ND, name:", layer.name); - var opt = options != nil and options[layer.name] != nil ? options[layer.name] :nil; - # print("Options is: ", opt!=nil?"enabled":"disabled"); + var opt = me.options != nil and me.options[layer.name] != nil ? me.options[layer.name] : nil; + if (opt == nil and contains(layer, 'options')) + opt = layer.options; + if (opt != nil and default_opts != nil) + opt = default_hash(opt, default_opts); + #elsif(default_opts != nil) + # opt = default_opts; + var style = nil; + if(contains(layer, 'style')) + style = layer.style; + #print("Options is: ", opt!=nil?"enabled":"disabled"); + #debug.dump(opt); me.map.addLayer( factory: canvas.SymbolLayer, type_arg: layer.name, - options:opt, + opts: opt, visible:0, + style: style, priority: layer['z-index'] ); the_layer = me.layers[layer.name] = me.map.getLayer(layer.name); + if(opt != nil and contains(opt, 'range_dependant')){ + if(opt.range_dependant) + append(me.range_dependant_layers, the_layer); + } + if(contains(layer, 'always_update')) + me.always_update_layers[layer.name] = layer.always_update; if (1) (func { var l = layer; var _predicate = l.predicate; @@ -443,15 +513,28 @@ var NavDisplay = { } # First, update the display position of the map + var oldRange = me.map.getRange(); var pos = { lat: nil, lon: nil, alt: nil, hdg: nil, range: nil, }; # reposition the map, change heading & range: - if(me.in_mode('toggle_display_mode', ['PLAN']) and getprop(me.efis_path ~ "/inputs/plan-wpt-index") >= 0) { - pos.lat = getprop("/autopilot/route-manager/route/wp["~getprop(me.efis_path ~ "/inputs/plan-wpt-index")~"]/latitude-deg"); - pos.lon = getprop("/autopilot/route-manager/route/wp["~getprop(me.efis_path ~ "/inputs/plan-wpt-index")~"]/longitude-deg"); + var pln_wpt_idx = getprop(me.efis_path ~ "/inputs/plan-wpt-index"); + if(me.in_mode('toggle_display_mode', ['PLAN']) and pln_wpt_idx >= 0) { + if(me.route_driver != nil){ + var wp = me.route_driver.getPlanModeWP(pln_wpt_idx); + if(wp != nil){ + pos.lat = wp.wp_lat; + pos.lon = wp.wp_lon; + } else { + pos.lat = getprop("/autopilot/route-manager/route/wp["~pln_wpt_idx~"]/latitude-deg"); + pos.lon = getprop("/autopilot/route-manager/route/wp["~pln_wpt_idx~"]/longitude-deg"); + } + } else { + pos.lat = getprop("/autopilot/route-manager/route/wp["~pln_wpt_idx~"]/latitude-deg"); + pos.lon = getprop("/autopilot/route-manager/route/wp["~pln_wpt_idx~"]/longitude-deg"); + } } else { pos.lat = userLat; pos.lon = userLon; @@ -463,7 +546,14 @@ var NavDisplay = { pos.range = me.rangeNm(); # avoid this here, use a listener instead pos.hdg = userHdgTrkTru; } + if(me.options != nil and (var pos_callback = me.options['position_callback']) != nil) + pos_callback(me, pos); call(me.map.setPos, [pos.lat, pos.lon], me.map, pos); + if(pos.range != oldRange){ + foreach(l; me.range_dependant_layers){ + l.update(); + } + } }, # each model should keep track of when it last got updated, using current lat/lon # in update(), we can then check if the aircraft has traveled more than 0.5-1 nm (depending on selected range) @@ -472,7 +562,13 @@ var NavDisplay = { update: func() # FIXME: This stuff is still too aircraft specific, cannot easily be reused by other aircraft { var _time = systime(); - + # Disables WXR Live if it's not enabled. The toggle_weather_live should be common to all + # ND instances. + var wxr_live_enabled = getprop(wxr_live_tree~'/enabled'); + if(wxr_live_enabled == nil or wxr_live_enabled == '') + wxr_live_enabled = 0; + me.set_switch('toggle_weather_live', wxr_live_enabled); + call(me.update_sub, nil, nil, caller(0)[0]); # call this in the same namespace to "steal" its variables # MapStructure update! @@ -481,17 +577,26 @@ var NavDisplay = { } else { # TODO: ugly list here # FIXME: use a VOLATILE layer helper here that handles TFC, APS, WXR etc ? - me.map.update(func(layer) (var n=layer.type) == "TFC" or n == "APS"); + var update_layers = me.always_update_layers; + me.map.update(func(layer) contains(update_layers, layer.type)); } # Other symbol update # TODO: should be refactored! - if(me.in_mode('toggle_display_mode', ['PLAN'])) - me.map.setTranslation(512,512); - elsif(me.get_switch('toggle_centered')) - me.map.setTranslation(512,565); - else - me.map.setTranslation(512,824); + var translation_callback = nil; + if(me.options != nil) + translation_callback = me.options['translation_callback']; + if(typeof(translation_callback) == 'func'){ + var trsl = translation_callback(me); + me.map.setTranslation(trsl.x, trsl.y); + } else { + if(me.in_mode('toggle_display_mode', ['PLAN'])) + me.map.setTranslation(512,512); + elsif(me.get_switch('toggle_centered')) + me.map.setTranslation(512,565); + else + me.map.setTranslation(512,824); + } if(me.get_switch('toggle_rh_vor_adf') == 1) { me.symbols.vorR.setText("VOR R"); diff --git a/Nasal/canvas/map/navdisplay.styles b/Nasal/canvas/map/navdisplay.styles index 3213dce5b..7d4754987 100644 --- a/Nasal/canvas/map/navdisplay.styles +++ b/Nasal/canvas/map/navdisplay.styles @@ -42,6 +42,22 @@ var NDStyles = { # TODO: phase out isMapStructure flag once map.nas & *.draw files are killed layers: [ # TODO: take z-indices from *.draw files -- now handled by MapStructure in the addLayer method. + { + name:'WXR_live', + isMapStructure:1, + always_update: 1, + update_on:[ 'toggle_range','toggle_weather','toggle_display_mode','toggle_weather_live'], + predicate: func(nd, layer) { + var visible = nd.get_switch('toggle_weather') and + nd.get_switch('toggle_weather_live') and + nd.get_switch('toggle_display_mode') != "PLAN"; + layer.group.setVisible(visible); + if (visible) { + layer.update(); + } + }, # end of layer update predicate + 'z-index': -100, + }, { name:'FIX', isMapStructure:1, update_on:['toggle_display_mode','toggle_range','toggle_waypoints'], # FIXME: this is a really ugly place for controller code predicate: func(nd, layer) { @@ -59,20 +75,25 @@ var NDStyles = { }, # end of FIX layer # Should redraw every 10 seconds TODO: use new MapStructure/WXR here once that works properly (Gijs should check first!) - { name:'WXR', isMapStructure:1, update_on:[ {rate_hz: 0.1}, 'toggle_range','toggle_weather','toggle_display_mode'], + { + name:'WXR', + isMapStructure:1, + update_on:[ {rate_hz: 0.1}, 'toggle_range','toggle_weather','toggle_display_mode', 'toggle_weather_live'], predicate: func(nd, layer) { #print("Running storms predicate"); - var visible=nd.get_switch('toggle_weather') and nd.get_switch('toggle_display_mode') != "PLAN"; + var visible=nd.get_switch('toggle_weather') and + !nd.get_switch('toggle_weather_live') and + nd.get_switch('toggle_display_mode') != "PLAN"; layer.group.setVisible(visible); if (visible) { print("storms update requested! (timer issue when closing the dialog?)"); - layer.update(); + layer.update(); } }, # end of layer update predicate 'z-index': -4, }, # end of storms/WXR layer - { name:'APS', isMapStructure:1, update_on:['toggle_display_mode'], + { name:'APS', isMapStructure:1, always_update: 1, update_on:['toggle_display_mode'], predicate: func(nd, layer) { var visible = nd.get_switch('toggle_display_mode') == "PLAN"; layer.group.setVisible( visible ); @@ -124,7 +145,7 @@ var NDStyles = { 'z-index': -2, }, # end of DME layer - { name:'TFC', isMapStructure:1, update_on:['toggle_range','toggle_traffic'], + { name:'TFC', isMapStructure:1, always_update: 1, update_on:['toggle_range','toggle_traffic'], predicate: func(nd, layer) { var visible = nd.get_switch('toggle_traffic'); layer.group.setVisible( visible ); @@ -411,7 +432,7 @@ var NDStyles = { is_true: func(nd) { var hdgText = ""; if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT") - or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD")) + or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD")) { if(nd.get_switch('toggle_true_north')) hdgText = nd.aircraft_source.get_trk_tru(); @@ -537,7 +558,7 @@ var NDStyles = { impl: { init: func(nd,symbol), predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN", - is_true: func(nd) { + is_true: func(nd) { nd.symbols.rangePln1.show(); nd.symbols.rangePln1.setText(sprintf("%3.0f",nd.rangeNm())); }, @@ -549,7 +570,7 @@ var NDStyles = { impl: { init: func(nd,symbol), predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN", - is_true: func(nd) { + is_true: func(nd) { nd.symbols.rangePln2.show(); nd.symbols.rangePln2.setText(sprintf("%3.0f",nd.rangeNm()/2)); }, @@ -561,7 +582,7 @@ var NDStyles = { impl: { init: func(nd,symbol), predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN", - is_true: func(nd) { + is_true: func(nd) { nd.symbols.rangePln3.show(); nd.symbols.rangePln3.setText(sprintf("%3.0f",nd.rangeNm()/2)); }, @@ -573,7 +594,7 @@ var NDStyles = { impl: { init: func(nd,symbol), predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN", - is_true: func(nd) { + is_true: func(nd) { nd.symbols.rangePln4.show(); nd.symbols.rangePln4.setText(sprintf("%3.0f",nd.rangeNm())); }, @@ -585,7 +606,7 @@ var NDStyles = { impl: { init: func(nd,symbol), predicate: func(nd) !nd.get_switch('toggle_centered'), - is_true: func(nd) { + is_true: func(nd) { nd.symbols.range.setText(sprintf("%3.0f",nd.rangeNm()/2)); }, is_false: func(nd) nd.symbols.rangePln4.hide(), @@ -608,7 +629,7 @@ var NDStyles = { impl: { init: func(nd,symbol), predicate: func(nd) (nd.get_switch('toggle_centered') and nd.in_mode('toggle_display_mode', ['APP','MAP','VOR'])), - is_true: func(nd) { + is_true: func(nd) { nd.symbols.rangeCtr2.show(); nd.symbols.rangeCtr2.setText(sprintf("%g",nd.rangeNm()/4)); }, @@ -620,7 +641,7 @@ var NDStyles = { impl: { init: func(nd,symbol), predicate: func(nd) (!nd.get_switch('toggle_centered') and nd.in_mode('toggle_display_mode', ['MAP'])), - is_true: func(nd) { + is_true: func(nd) { var altDiff = (getprop("autopilot/settings/target-altitude-ft") or 0)-(getprop("instrumentation/altimeter/indicated-altitude-ft") or 0); if (abs(altDiff) > 50 and altDiff/nd.aircraft_source.get_vspd() > 0) { var altRangeNm = altDiff/nd.aircraft_source.get_vspd()*nd.aircraft_source.get_gnd_spd()*KT2MPS*M2NM; @@ -642,7 +663,7 @@ var NDStyles = { impl: { init: func(nd,symbol), predicate: func(nd) (nd.get_switch('toggle_centered') and nd.in_mode('toggle_display_mode', ['MAP'])), - is_true: func(nd) { + is_true: func(nd) { var altDiff = (getprop("autopilot/settings/target-altitude-ft") or 0)-(getprop("instrumentation/altimeter/indicated-altitude-ft") or 0); if (abs(altDiff) > 50 and altDiff/nd.aircraft_source.get_vspd() > 0) { var altRangeNm = altDiff/nd.aircraft_source.get_vspd()*nd.aircraft_source.get_gnd_spd()*KT2MPS*M2NM; @@ -708,7 +729,7 @@ var NDStyles = { impl: { init: func(nd,symbol), predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and nd.get_switch('toggle_centered')), - is_true: func(nd) { + is_true: func(nd) { if (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD") nd.symbols.trkInd2.setRotation(0); else @@ -741,7 +762,7 @@ var NDStyles = { is_true: func(nd) { nd.symbols.vorCrsPtr.show(); if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT") - or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD")) + or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD")) nd.symbols.vorCrsPtr.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_trk_mag())*D2R); else nd.symbols.vorCrsPtr.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_hdg_mag())*D2R); @@ -757,7 +778,7 @@ var NDStyles = { is_true: func(nd) { nd.symbols.vorCrsPtr2.show(); if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT") - or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD")) + or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD")) nd.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_trk_mag())*D2R); else nd.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_hdg_mag())*D2R); @@ -954,10 +975,10 @@ var NDStyles = { predicate: func(nd) (nd.in_mode('toggle_display_mode', ['MAP']) and !nd.get_switch('toggle_centered') and nd.efis_switches['toggle_tacan'] != nil), is_true: func(nd) { if (nd.get_switch('toggle_tacan')) { - nd.symbols.TCNArrow.show(); - nd.symbols.TCNArrow.setRotation((getprop("instrumentation/tacan/indicated-bearing-true-deg")-nd.aircraft_source.get_hdg_tru())*D2R); + nd.symbols.TCNArrow.show(); + nd.symbols.TCNArrow.setRotation((getprop("instrumentation/tacan/indicated-bearing-true-deg")-nd.aircraft_source.get_hdg_tru())*D2R); } else { - nd.symbols.TCNArrow.hide(); + nd.symbols.TCNArrow.hide(); } }, is_false: func(nd) nd.symbols.TCNArrow.hide(), @@ -970,10 +991,10 @@ var NDStyles = { predicate: func(nd) (nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_centered') and nd.efis_switches['toggle_tacan'] != nil), is_true: func(nd) { if (nd.get_switch('toggle_tacan')) { - nd.symbols.TCNArrow2.show(); - nd.symbols.TCNArrow2.setRotation((getprop("instrumentation/tacan/indicated-bearing-true-deg")-nd.aircraft_source.get_hdg_tru())*D2R); + nd.symbols.TCNArrow2.show(); + nd.symbols.TCNArrow2.setRotation((getprop("instrumentation/tacan/indicated-bearing-true-deg")-nd.aircraft_source.get_hdg_tru())*D2R); } else { - nd.symbols.TCNArrow2.hide(); + nd.symbols.TCNArrow2.hide(); } }, is_false: func(nd) nd.symbols.TCNArrow2.hide(), @@ -986,10 +1007,10 @@ var NDStyles = { predicate: func(nd) (nd.in_mode('toggle_display_mode', ['MAP']) and nd.efis_switches['toggle_tacan'] != nil), is_true: func(nd) { if (nd.get_switch('toggle_tacan')) { - nd.symbols.TACAN.setText("TACAN"); - nd.symbols.TACAN.show(); + nd.symbols.TACAN.setText("TACAN"); + nd.symbols.TACAN.show(); } else { - nd.symbols.TACAN.hide(); + nd.symbols.TACAN.hide(); } }, is_false: func(nd) nd.symbols.TACAN.hide(), @@ -1002,10 +1023,10 @@ var NDStyles = { predicate: func(nd) (nd.in_mode('toggle_display_mode', ['MAP']) and nd.efis_switches['toggle_tacan'] != nil), is_true: func(nd) { if (nd.get_switch('toggle_tacan') and getprop("instrumentation/tacan/in-range")) { - nd.symbols.TACANId.setText(getprop("instrumentation/tacan/ident")); - nd.symbols.TACANId.show(); + nd.symbols.TACANId.setText(getprop("instrumentation/tacan/ident")); + nd.symbols.TACANId.show(); } else { - nd.symbols.TACANId.hide(); + nd.symbols.TACANId.hide(); } }, is_false: func(nd) nd.symbols.TACANId.hide(), @@ -1018,10 +1039,10 @@ var NDStyles = { predicate: func(nd) (nd.in_mode('toggle_display_mode', ['MAP']) and nd.efis_switches['toggle_tacan'] != nil), is_true: func(nd) { if (nd.get_switch('toggle_tacan') and getprop("instrumentation/tacan/in-range")) { - nd.symbols.TACANdme.setText("DIST"); - nd.symbols.TACANdme.show(); + nd.symbols.TACANdme.setText("DIST"); + nd.symbols.TACANdme.show(); } else { - nd.symbols.TACANdme.hide(); + nd.symbols.TACANdme.hide(); } }, is_false: func(nd) nd.symbols.TACANdme.hide(), @@ -1034,10 +1055,10 @@ var NDStyles = { predicate: func(nd) (nd.in_mode('toggle_display_mode', ['MAP']) and nd.efis_switches['toggle_tacan'] != nil), is_true: func(nd) { if (nd.get_switch('toggle_tacan') and getprop("instrumentation/tacan/in-range")) { - nd.symbols.TACANdmeDist.setText(sprintf("%3.1f",getprop("instrumentation/tacan/indicated-distance-nm"))); - nd.symbols.TACANdmeDist.show(); + nd.symbols.TACANdmeDist.setText(sprintf("%3.1f",getprop("instrumentation/tacan/indicated-distance-nm"))); + nd.symbols.TACANdmeDist.show(); } else { - nd.symbols.TACANdmeDist.hide(); + nd.symbols.TACANdmeDist.hide(); } }, is_false: func(nd) nd.symbols.TACANdmeDist.hide(), @@ -1048,9 +1069,1653 @@ var NDStyles = { }, # end of Boeing style + "Airbus" : { + font_mapper: func(family, weight) { + if( family == "Liberation Sans" and weight == "normal" ) + return "LiberationFonts/LiberationSans-Regular.ttf"; + }, + + # where all the symbols are stored + # TODO: SVG elements should be renamed to use boeing/airbus prefix + # aircraft developers should all be editing the same ND.svg image + # the code can deal with the differences now + svg_filename: "Nasal/canvas/map/Airbus/Images/airbusND.svg", + ## + ## this loads and configures existing layers (currently, *.layer files in Nasal/canvas/map) + ## + options: { + position_callback: func(nd, pos){ + if(nd.get_switch('toggle_centered') or + nd.get_switch('toggle_display_mode') == 'PLAN') + pos.range = (nd.rangeNm() * 1.6156087432822295); + }, + translation_callback: func(nd){ + var t = {x: 512, y: 824}; + if(nd.get_switch('toggle_centered') or + nd.get_switch('toggle_display_mode') == 'PLAN') + t.y = 512; + return t; + }, + defaults: { + # Configurable Global Options: + # - fplan_active: the boolean property used to determine if the flight plan is active + # - lat_ctrl: the property used to determine if lateral flight mode is managed + # Lateral managed mode is similar to Boeing LNAV and indicates that the + # aircraft should follow the current active flight plan. By default + # the lat_ctrl property is checked against 'fmgc' (managed) value. + # You can use a custom value instead on 'fmgc' by overriding 'managed_val' + # - managed_val: the value that the property defined by 'lat_ctrl' should have in case + # that the lateral flight mode is managed. + # You can easily override these options before creating the ND, example: + # canvas.NDStyles['Airbus'].options.defaults.fplan_active = 'my/autpilot/f-plan/active' + fplan_active: 'autopilot/route-manager/active', + lat_ctrl: 'flight-management/control/lat-ctrl', + managed_val: 'fmgc', + ver_ctrl: 'flight-management/control/ver-ctrl', + spd_ctrl: '/flight-management/control/spd-ctrl', + current_wp: '/autopilot/route-manager/current-wp', + ap1: '/flight-management/control/ap1-master', + ap2: '/flight-management/control/ap2-master', + nav1_frq: 'instrumentation/nav/frequencies/selected-mhz', + nav2_frq: 'instrumentation/nav[1]/frequencies/selected-mhz', + adf1_frq: 'instrumentation/adf/frequencies/selected-khz', + adf2_frq: 'instrumentation/adf[1]/frequencies/selected-khz', + dep_rwy: '/autopilot/route-manager/departure/runway', + dest_rwy: '/autopilot/route-manager/destination/runway', + wp_count: 'autopilot/route-manager/route/num', + level_off_alt: '/autopilot/route-manager/vnav/level-off-alt', + athr: '/flight-management/control/a-thrust', + app_mode: 'instrumentation/nd/app-mode', + chrono_node: 'instrumentation/chrono', + active_route_color: [0.4,0.7,0.4], + inactive_route_color: [0.95,0.95,0.21] + }, + map: { + "z-index" : -1 + } + }, + layers: [ + # Should redraw every 10 seconds TODO: use new MapStructure/WXR here once that works properly (Gijs should check first!) + { + name:'WXR_live', + isMapStructure:1, + always_update: 1, + update_on:[ 'toggle_range','toggle_weather','toggle_display_mode','toggle_weather_live'], + predicate: func(nd, layer) { + var visible=nd.get_switch('toggle_weather') and + nd.get_switch('toggle_weather_live') and + nd.get_switch('toggle_display_mode') != "PLAN"; + layer.group.setVisible(visible); + if (visible) { + layer.update(); + } + }, # end of layer update predicate + options: { + viewport_radius: 706 + }, + 'z-index': -100, + }, + { + name:'WXR', + isMapStructure:1, + update_on:[ {rate_hz: 0.1}, 'toggle_range','toggle_weather','toggle_display_mode', 'toggle_weather_live'], + predicate: func(nd, layer) { + #print("Running storms predicate"); + var visible=nd.get_switch('toggle_weather') and + !nd.get_switch('toggle_weather_live') and + nd.get_switch('toggle_display_mode') != "PLAN"; + layer.group.setVisible(visible); + if (visible) { + print("storms update requested! (timer issue when closing the dialog?)"); + layer.update(); + } + }, # end of layer update predicate + 'z-index': -4, + }, # end of storms/WXR layer + { + name:'FIX', + isMapStructure:1, + update_on:['toggle_range','toggle_waypoints', + 'toggle_display_mode'], + predicate: func(nd, layer) { + var visible = nd.get_switch('toggle_waypoints') and + nd.in_mode('toggle_display_mode', ['MAP']) and + (nd.rangeNm() <= 40); + layer.group.setVisible( visible ); + if (visible) + layer.update(); + }, # end of layer update predicate + style: { + color: [0.69, 0, 0.39], + text_offset: [20,10], + text_color: [1,1,1] + }, + options: { + draw_function: func(group){ + group.createChild('path') + .moveTo(-10,0) + .lineTo(0,-17) + .lineTo(10,0) + .lineTo(0,17) + .close() + .setStrokeLineWidth(3) + .setColor(color) + .setScale(1); + } + } + }, # end of FIX layer + { + name: 'ALT-profile', + isMapStructure: 1, + update_on: ['toggle_display_mode','toggle_range',{rate_hz: 2}], + predicate: func(nd, layer) { + var visible = nd.in_mode('toggle_display_mode', ['MAP', 'PLAN']);# and nd.get_switch('toggle_fplan'); + layer.group.setVisible( visible ); + if (visible) { + layer.update(); + } + }, + style: { + default_color: [1,1,1], + armed_color: [0.06,0.55,1], + managed_color: [0.68, 0, 0.38] + }, + options: { + # You can overwrite these with different nodes, before creating the ND + # Example: canvas.NDStyles['Airbus'].layers['ALT-profile'].options.vnav_node = 'my/vnav/node'; + # In order to display ALT-profile on your ND you have to create the various + # nodes corresponding to the different ALT pseudowaypoint in your aircraft code. + # Every node must be set into the 'vnav_node' configured here (you can override it). + # Example: if you want to display the top-of-descent symbol you must create a 'td' + # node into vnav_node. Something like this: + # /autopilot/route-manager/vnav/td/ + # Each node should have the latitude-deg and longitude-deg properties. + # Available nodes are: + # tc (top of climb) + # td (top of descent) + # ec (end of climb) + # ed (end of descent) + # sc (start of climb) + # sd (start of descent) + # If ec and ed are altitude constraints, their node should have the + # boolean 'alt-cstr' property set to 1. + vnav_node: "/autopilot/route-manager/vnav/", + types: ["tc", "td", "ec", "ed","sc","sd"], + svg_path: { + tc: 'Nasal/canvas/map/Airbus/Images/airbus_tc.svg', + td: 'Nasal/canvas/map/Airbus/Images/airbus_td.svg', + ec: 'Nasal/canvas/map/Airbus/Images/airbus_ec.svg', + ed: 'Nasal/canvas/map/Airbus/Images/airbus_ed.svg', + sc: 'Nasal/canvas/map/Airbus/Images/airbus_sc.svg', + sd: 'Nasal/canvas/map/Airbus/Images/airbus_sd.svg' + }, + listen: [ + 'fplan_active', + 'ver_ctrl', + 'ap1', + 'ap2', + 'current_wp' + ], + draw_callback: func(){ + var name = me.model.getName(); + var grp = me.element.getElementById(name~'_symbol'); + if(grp == nil) return; + var dfcolor = me.getStyle('default_color'); + var armed_color = me.getStyle('armed_color'); + var managed_color = me.getStyle('managed_color'); + #print('Draw: -> ' ~ name); + if(name == 'td' or name == 'sd' or name == 'sc'){ + var vnav_armed = me.model.getValue('vnav-armed'); + if(vnav_armed) + grp.setColor(armed_color); + else + grp.setColor(dfcolor); + } + elsif(name == 'ed' or name == 'ec'){ + var is_cstr = me.model.getValue('alt-cstr'); + if(is_cstr) + grp.setColor(managed_color); + else + grp.setColor(armed_color); + } + }, + init_after_callback: func{ + var name = me.model.getName(); + var grp = me.element.getElementById(name~'_symbol'); + if(name != 'td' and name != 'sd' and name != 'sc'){ + grp.setTranslation(-50,0); + } + } + } + }, + { + name:'APT', + isMapStructure:1, + update_on:['toggle_range','toggle_airports', + 'toggle_display_mode'], + predicate: func(nd, layer) { + var visible = nd.get_switch('toggle_airports') and + nd.in_mode('toggle_display_mode', ['MAP']); + layer.group.setVisible( visible ); + if (visible) { + layer.update(); + } + }, # end of layer update predicate + style: { + svg_path: 'Nasal/canvas/map/Airbus/Images/airbus_airport.svg', + text_offset: [45, 35], + label_font_color: [1,1,1], + label_font_size: 28 + } + }, # end of APT layer + { + name:'VOR-airbus', + isMapStructure:1, + update_on:['toggle_range','toggle_vor','toggle_display_mode'], + # FIXME: this is a really ugly place for controller code + predicate: func(nd, layer) { + # print("Running vor layer predicate"); + # toggle visibility here + var visible = nd.get_switch('toggle_vor') and + nd.in_mode('toggle_display_mode', ['MAP']) and + (nd.rangeNm() <= 40); + layer.group.setVisible( visible ); + if (visible) { + layer.update(); + } + }, # end of layer update predicate + # You can override default colors within the style + # Default color: 'color' + # Tuned color: 'tuned_color' + # Example: + # canvas.NDStyles['Airbus'].layers['VOR-airbus'].style.color = [1,1,1]; + # canvas.NDStyles['Airbus'].layers['VOR-airbus'].style.tuned_color = [0,0,1]; + style: {}, + options:{ + listen: [ + 'nav1_frq', + 'nav2_frq' + ], + } + }, # end of VOR layer + { + name:'DME', + isMapStructure:1, + disabled:1, + update_on:['toggle_display_mode','toggle_range','toggle_dme'], + # FIXME: this is a really ugly place for controller code + predicate: func(nd, layer) { + var visible = nd.get_switch('toggle_dme') and + nd.in_mode('toggle_display_mode', ['MAP']) and + (nd.rangeNm() <= 40); + # toggle visibility here + layer.group.setVisible( visible ); + if (visible) { + #print("Updating MapStructure ND layer: DME"); + layer.update(); + } + }, # end of layer update predicate + options: { + draw_dme: func(sym){ + return sym.createChild("path") + .moveTo(-13, 0) + .arcSmallCW(13,13,0,26,0) + .arcSmallCW(13,13,0,-26,0) + .setStrokeLineWidth(2) + .close(); + }, + draw_text: 1 + }, + style: { + color_default: [0.9,0,0.47], + color_tuned: [0,0.62,0.84], + }, + 'z-index': -2, + }, # end of DME layer + { + name:'NDB', + isMapStructure:1, + update_on:['toggle_range','toggle_ndb','toggle_display_mode'], + # FIXME: this is a really ugly place for controller code + predicate: func(nd, layer) { + var visible = nd.get_switch('toggle_ndb') and + nd.in_mode('toggle_display_mode', ['MAP']) and + (nd.rangeNm() <= 40); + # print("Running vor layer predicate"); + # toggle visibility here + layer.group.setVisible( visible ); + if (visible) { + layer.update(); + } + }, # end of layer update predicate + # You can override default colors within the style + # Default color: 'color' + # Tuned color: 'tuned_color' + # Example: + # canvas.NDStyles['Airbus'].layers['VOR-airbus'].style.color = [1,1,1]; + # canvas.NDStyles['Airbus'].layers['VOR-airbus'].style.tuned_color = [0,0,1]; + style: { + #svg_path: 'Nasal/canvas/map/Airbus/Images/airbus_ndb.svg' + svg_path: '' + }, + options: { + listen: [ + 'adf1_frq', + 'adf2_frq' + ], + init_after_callback: func{ + #me.element.removeAllChildren(); + me.text_ndb = me.element.createChild("text") + .setDrawMode( canvas.Text.TEXT ) + .setText(me.model.id) + .setFont("LiberationFonts/LiberationSans-Regular.ttf") + .setFontSize(28) + .setTranslation(25,10); + me.ndb_sym = me.element.createChild('path'); + me.ndb_sym.moveTo(-15,15) + .lineTo(0,-15) + .lineTo(15,15) + .close() + .setStrokeLineWidth(3) + .setColor(0.69,0,0.39) + .setScale(1,1); + }, + draw_callback: func{ + var frq = me.model.frequency; + if(frq != nil){ + var dfcolor = me.getStyle('color', [0.9,0,0.47]); + var tuned_color = me.getStyle('tuned_color', [0,0.62,0.84]); + frq = frq / 100; + var adf1_frq = getprop(me.options.adf1_frq); + var adf2_frq = getprop(me.options.adf2_frq); + if(adf1_frq == frq or adf2_frq == frq){ + me.element.setColor(tuned_color, [Path]); + } else { + me.element.setColor(dfcolor, [Path]); + } + } + }, + } + }, # end of NDB layer + { + name:'TFC', + #disabled:1, + always_update: 1, + isMapStructure:1, + update_on:['toggle_range','toggle_traffic'], + predicate: func(nd, layer) { + var visible = nd.get_switch('toggle_traffic'); + layer.group.setVisible( visible ); + if (visible) { + #print("Updating MapStructure ND layer: TFC"); + layer.update(); + } + }, # end of layer update predicate + }, # end of traffic layer + { + name:'RWY-profile', + isMapStructure:1, + update_on:['toggle_range','toggle_display_mode'], + predicate: func(nd, layer) { + var visible = (nd.rangeNm() <= 40) and + nd.in_mode('toggle_display_mode', ['MAP','PLAN']) ; + layer.group.setVisible( visible ); + if (visible) { + layer.update(); + } + }, # end of layer update predicate + options: { + listen: [ + 'fplan_active', + 'dep_rwy', + 'dest_rwy' + ] + } + }, # end of runway layer + { + name:'HOLD', + isMapStructure: 1, + always_update: 1, + update_on:['toggle_range','toggle_display_mode','toggle_wpt_idx'], + predicate: func(nd, layer) { + var visible= nd.in_mode('toggle_display_mode', ['MAP','PLAN']); + layer.group.setVisible( visible ); + if (visible) { + layer.update(); + } + }, + options: { + hold_node: '/flight-management/hold', + hold_init: 'flight-management/hold/init', + points_node: '/flight-management/hold/points', + first_point_node: '/flight-management/hold/points/point/lat', + hold_wp: '/flight-management/hold/wp', + hold_wp_idx: '/flight-management/hold/wp_id', + range_dependant: 1, + listen: [ + 'first_point_node', + 'fplan_active', + 'lat_ctrl', + 'current_wp', + 'hold_wp', + 'hold_init', + 'hold_wp_idx' + ] + } + # end of layer update predicate + }, # end of HOLD layer + { + name:'RTE', + isMapStructure: 1, + update_on:['toggle_range','toggle_display_mode', 'toggle_cstr', + 'toggle_wpt_idx'], + predicate: func(nd, layer) { + var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN'])); + layer.group.setVisible( visible ); + if (visible) { + layer.update(); + } + }, # end of layer update predicate + options: { + display_inactive_rte: 1, + listen: [ + 'fplan_active', + 'lat_ctrl', + 'current_wp', + 'wp_count' + ], + draw_after_callback: func{ + me.setRouteStyle(); + } + }, + style: { + line_width: 5, + #inactive_color: [0.95,0.95,0.21], + #active_color: [0.4,0.7,0.4], + color: func{ + if(!contains(me, 'inactive_color')){ + me.inactive_color = me.getStyle('inactive_color'); + if(me.inactive_color == nil) + me.inactive_color = me.getOption('inactive_route_color'); + } + if(!contains(me, 'active_color')){ + me.active_color = me.getStyle('active_color'); + if(me.active_color == nil) + me.active_color = me.getOption('active_route_color'); + } + var is_active = getprop(me.options.fplan_active); + (is_active ? me.active_color : me.inactive_color); + }, + color_alternate_current: [0,0.62,0.84], + color_missed: [0,0.62,0.84], + color_temporary: func me.getStyle('inactive_color', me.getOption('inactive_route_color')), + color_secondary: [1,1,1], + color_alternate_secondary: [1,1,1], + line_dash: func{ + var lat_ctrl = getprop(me.options.lat_ctrl); + var is_managed = (lat_ctrl == me.options.managed_val); + var is_active = getprop(me.options.fplan_active); + (is_managed and is_active ? [] : [32, 16]); + }, + line_dash_alternate_current: [32,16], + line_dash_temporary: [32,16] + } + }, # end of route layer + { + name:'WPT-airbus', + isMapStructure: 1, + update_on:['toggle_range','toggle_display_mode', 'toggle_cstr', + 'toggle_wpt_idx'], + style: { + wp_color: [0.4,0.7,0.4], + current_wp_color: [1,1,1], + constraint_color: [1,1,1], + active_constraint_color: [0.69,0,0.39], + missed_constraint_color: [1,0.57,0.14] + }, + predicate: func(nd, layer) { + var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN'])); + layer.group.setVisible( visible ); + if (visible) { + layer.toggle_cstr = nd.get_switch('toggle_cstr'); + layer.update(); + } + }, # end of layer update predicate + options: { + listen: [ + 'fplan_active', + 'lat_ctrl', + 'ver_ctrl', + 'current_wp', + 'wp_count', + 'dep_rwy', + 'dest_rwy', + 'level_off_alt' + ], + } + }, # end of WPT layer + { + name: 'SPD-profile', + isMapStructure: 1, + update_on: ['toggle_display_mode','toggle_range',{rate_hz: 2}], + predicate: func(nd, layer) { + var visible = nd.in_mode('toggle_display_mode', ['MAP', 'PLAN']); + layer.group.setVisible( visible ); + if (visible) { + layer.update(); + } + }, + style: { + color: [0.69,0,0.39] + }, + options: { + spd_node: "/autopilot/route-manager/spd/", + listen: [ + 'fplan_active' + ] + } + }, + { + name: 'DECEL', + isMapStructure: 1, + update_on: ['toggle_display_mode','toggle_range'], + predicate: func(nd, layer) { + var visible = nd.in_mode('toggle_display_mode', ['MAP', 'PLAN']); + layer.group.setVisible( visible ); + if (visible) { + layer.update(); + } + }, + options: { + # Overridable options: + # decel_node: node containing latitude-deg and longitude-deg used to mark the deceleration point + # managed_speed_node: boolean property indicating that the aircraft is flying in managed speed mode + listen: [ + 'fplan_active', + 'spd_ctrl', + 'ver_ctrl', + 'athr' + ] + }, + style: { + # managed_color: decelaration symbol color when the aircraft flies in managed speed mode + # selected_color: decelaration symbol color when the aircraft flies in selected speed mode + managed_color: [0.68, 0, 0.38], + selected_color: [1,1,1] + } + }, + { + name:'APS', + isMapStructure:1, + always_update: 1, + update_on:['toggle_display_mode'], + predicate: func(nd, layer) { + var visible = nd.get_switch('toggle_display_mode') == "PLAN"; + layer.group.setVisible( visible ); + if (visible) { + layer.update(); + } + }, + style: { + svg_path: 'Nasal/canvas/map/Airbus/Images/airbusAirplane.svg', + #translate: [-45,-52] + }, + options: { + model: { + parents: [geo.Coord], + id: 999999, + pos: props.globals.getNode('position'), + type: 'position', + latlon: func(){ + me.pos = props.globals.getNode('position'); + return [ + me.pos.getValue("latitude-deg"), + me.pos.getValue("longitude-deg"), + me.pos.getValue("altitude-ft") + ]; + }, + equals: func(o){me.id == o.id} + }, + init_after_callback: func{ + var aplSymbol = me.element.getElementById("airplane"); + aplSymbol.setTranslation(-45,-52); + } + } + }, + # end of storms/WXR layer + ## add other layers here, layer names must match the registered names as used in *.layer files for now + ## this will all change once we're using Philosopher's MapStructure framework + + ], # end of vector with configured layers + + # This is where SVG elements are configured by providing "behavior" hashes, i.e. for animations + + # to animate each SVG symbol, specify behavior via callbacks (predicate, and true/false implementation) + # SVG identifier, callback etc + # TODO: update_on([]), update_mode (update() vs. timers/listeners) + # TODO: support putting symbols on specific layers + features: [ + { + id: 'compass_mask', + impl: { + init: func(nd, symbol), + predicate: func(nd) !nd.get_switch('toggle_centered'), + is_true: func(nd) nd.symbols.compass_mask.show(), + is_false: func(nd) nd.symbols.compass_mask.hide(), + } + }, + { + id: 'compass_mask_ctr', + impl: { + init: func(nd, symbol), + predicate: func(nd) nd.get_switch('toggle_centered') or nd.in_mode('toggle_display_mode',['PLAN']), + is_true: func(nd) nd.symbols.compass_mask_ctr.show(), + is_false: func(nd) nd.symbols.compass_mask_ctr.hide(), + } + }, + { + # TODO: taOnly doesn't need to use getprop polling in update(), use a listener instead! + id: 'taOnly', # the SVG ID + impl: { # implementation hash + init: func(nd, symbol), # for updateCenter stuff, called during initialization in the ctor + predicate: func(nd) getprop("instrumentation/tcas/inputs/mode") == 2, # the condition + is_true: func(nd) nd.symbols.taOnly.show(), # if true, run this + is_false: func(nd) nd.symbols.taOnly.hide(), # if false, run this + }, # end of taOnly behavior/callbacks + }, # end of taOnly + { + id: 'tas', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.aircraft_source.get_spd() > 100, + is_true: func(nd) { + nd.symbols.tas.setText(sprintf("%3.0f",getprop("/velocities/airspeed-kt") )); + nd.symbols.tas.show(); + }, + is_false: func(nd) nd.symbols.tas.hide(), + }, + }, + { + id: 'tasLbl', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.aircraft_source.get_spd() > 100, + is_true: func(nd) nd.symbols.tasLbl.show(), + is_false: func(nd) nd.symbols.tasLbl.hide(), + }, + }, + { + id: 'ilsFreq', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP', 'VOR']), + is_true: func(nd) { + nd.symbols.ilsFreq.show(); + nd.symbols.ilsFreq.setText(getprop("instrumentation/nav/frequencies/selected-mhz-fmt")); + if(nd.get_switch('toggle_display_mode') == 'APP') + nd.symbols.ilsFreq.setColor(0.69,0,0.39); + else + nd.symbols.ilsFreq.setColor(1,1,1); + }, + is_false: func(nd) nd.symbols.ilsFreq.hide(), + }, + }, + { + id: 'ilsLbl', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP', 'VOR']), + is_true: func(nd) { + nd.symbols.ilsLbl.show(); + if(nd.get_switch('toggle_display_mode') == 'APP') + nd.symbols.ilsLbl.setText('ILS'); + else + nd.symbols.ilsLbl.setText('VOR 1'); + }, + is_false: func(nd) nd.symbols.ilsLbl.hide(), + }, + }, + { + id: 'wpActiveId', + impl: { + init: func(nd,symbol), + predicate: func(nd) getprop("/autopilot/route-manager/wp/id") != nil and + getprop("autopilot/route-manager/active") and + nd.in_mode('toggle_display_mode', ['MAP', 'PLAN']), + is_true: func(nd) { + nd.symbols.wpActiveId.setText(getprop("/autopilot/route-manager/wp/id")); + nd.symbols.wpActiveId.show(); + }, + is_false: func(nd) nd.symbols.wpActiveId.hide(), + }, # of wpActiveId.impl + }, # of wpActiveId + { + id: 'wpActiveCrs', + impl: { + init: func(nd,symbol), + predicate: func(nd) getprop("/autopilot/route-manager/wp/id") != nil and + getprop("autopilot/route-manager/active") and + nd.in_mode('toggle_display_mode', ['MAP', 'PLAN']), + is_true: func(nd) { + #var cur_wp = getprop("autopilot/route-manager/current-wp"); + var deg = int(getprop("/autopilot/route-manager/wp/bearing-deg")); + nd.symbols.wpActiveCrs.setText(''~deg~'°'); + nd.symbols.wpActiveCrs.show(); + }, + is_false: func(nd) nd.symbols.wpActiveCrs.hide(), + }, # of wpActiveId.impl + }, # of wpActiveId + { + id: 'wpActiveDist', + impl: { + init: func(nd,symbol), + predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and + getprop("autopilot/route-manager/active") and + nd.in_mode('toggle_display_mode', ['MAP', 'PLAN']), + is_true: func(nd) { + var dst = getprop("/autopilot/route-manager/wp/dist"); + nd.symbols.wpActiveDist.setText(sprintf("%3.01f",dst)); + nd.symbols.wpActiveDist.show(); + }, + is_false: func(nd) nd.symbols.wpActiveDist.hide(), + }, + }, + { + id: 'wpActiveDistLbl', + impl: { + init: func(nd,symbol), + predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and getprop("autopilot/route-manager/active") and nd.in_mode('toggle_display_mode', ['MAP', 'PLAN']), + is_true: func(nd) { + nd.symbols.wpActiveDistLbl.show(); + if(getprop("/autopilot/route-manager/wp/dist") > 1000) + nd.symbols.wpActiveDistLbl.setText(" NM"); + }, + is_false: func(nd) nd.symbols.wpActiveDistLbl.hide(), + }, + }, + { + id: 'eta', + impl: { + init: func(nd,symbol), + predicate: func(nd) getprop("autopilot/route-manager/wp/eta") != nil and getprop("autopilot/route-manager/active") and nd.in_mode('toggle_display_mode', ['MAP', 'PLAN']), + is_true: func(nd) { + var etaSec = getprop("/sim/time/utc/day-seconds")+ + getprop("autopilot/route-manager/wp/eta-seconds"); + var h = math.floor(etaSec/3600); + etaSec = etaSec-3600*h; + var m = math.floor(etaSec/60); + etaSec = etaSec-60*m; + var s = etaSec / 10; + if (h > 24) h = h - 24; + nd.symbols.eta.setText(sprintf("%02.0f:%02.0f",h,m)); + nd.symbols.eta.show(); + }, + is_false: func(nd) nd.symbols.eta.hide(), + }, # of eta.impl + }, # of eta + { + id: 'gsGroup', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']), + is_true: func(nd) { + if(nd.get_switch('toggle_centered')) + nd.symbols.gsGroup.setTranslation(0,0); + else + nd.symbols.gsGroup.setTranslation(0,150); + nd.symbols.gsGroup.show(); + }, + is_false: func(nd) nd.symbols.gsGroup.hide(), + }, + }, + { + id:'hdg', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','MAP','VOR']), + is_true: func(nd) { + var hdgText = ""; + if(nd.in_mode('toggle_display_mode', ['MAP'])) { + if(nd.get_switch('toggle_true_north')) + hdgText = nd.aircraft_source.get_trk_tru(); + else + hdgText = nd.aircraft_source.get_trk_mag(); + } else { + if(nd.get_switch('toggle_true_north')) + hdgText = nd.aircraft_source.get_hdg_tru(); + else + hdgText = nd.aircraft_source.get_hdg_mag(); + } + nd.symbols.hdg.setText(sprintf("%03.0f", hdgText+0.5)); + }, + is_false: NOTHING, + }, + }, + { + id:'hdgGroup', + impl: { + init: func(nd,symbol), + predicate: func(nd) {return 0},#nd.in_mode('toggle_display_mode', ['APP','MAP','VOR']), + is_true: func(nd) { + nd.symbols.hdgGroup.show(); + if(nd.get_switch('toggle_centered')) + nd.symbols.hdgGroup.setTranslation(0,100); + else + nd.symbols.hdgGroup.setTranslation(0,0); + }, + is_false: func(nd) nd.symbols.hdgGroup.hide(), + }, + }, + { + id:'altArc', + impl: { + init: func(nd,symbol), + predicate: func(nd) {return 0},#nd.in_mode('toggle_display_mode', ['APP','MAP','VOR']), + is_true: func(nd) { + nd.symbols.altArc.show(); + }, + is_false: func(nd) nd.symbols.altArc.hide(), + }, + }, + { + id:'gs', + impl: { + init: func(nd,symbol), + common: func(nd) nd.symbols.gs.setText(sprintf("%3.0f",nd.aircraft_source.get_gnd_spd() )), + predicate: func(nd) nd.aircraft_source.get_gnd_spd() >= 30, + is_true: func(nd) { + #nd.symbols.gs.show(); + nd.symbols.gs.setFontSize(36); + }, + is_false: func(nd) {},#nd.symbols.gs.hide(), + }, + }, + { + id:'compass', + impl: { + init: func(nd,symbol), + predicate: func(nd) (!nd.get_switch('toggle_centered') and nd.get_switch('toggle_display_mode') != "PLAN"), + is_true: func(nd) { + nd.symbols.compass.setRotation(-nd.userHdgTrk*D2R); + nd.symbols.compass.show() + }, + is_false: func(nd) nd.symbols.compass.hide(), + }, # of compass.impl + }, # of compass + { + id:'compassApp', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.get_switch('toggle_centered') and nd.get_switch('toggle_display_mode') != "PLAN"), + is_true: func(nd) { + nd.symbols.compassApp.setRotation(-nd.userHdgTrk*D2R); + nd.symbols.compassApp.show() + }, + is_false: func(nd) nd.symbols.compassApp.hide(), + }, # of compassApp.impl + }, # of compassApp + { + id:'northUp', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN", + is_true: func(nd) nd.symbols.northUp.show(), + is_false: func(nd) nd.symbols.northUp.hide(), + }, # of northUp.impl + }, # of northUp + { + id:'planArcs', + impl: { + init: func(nd,symbol), + predicate: func(nd) ((nd.in_mode('toggle_display_mode', ['APP','VOR','PLAN'])) or ((nd.get_switch('toggle_display_mode') == "MAP") and (nd.get_switch('toggle_centered')))), + is_true: func(nd) nd.symbols.planArcs.show(), + is_false: func(nd) nd.symbols.planArcs.hide(), + }, # of planArcs.impl + }, # of planArcs + { + id:'rangeArcs', + impl: { + init: func(nd,symbol), + predicate: func(nd) ((nd.get_switch('toggle_display_mode') == "MAP") and (!nd.get_switch('toggle_centered'))), + is_true: func(nd) nd.symbols.rangeArcs.show(), + is_false: func(nd) nd.symbols.rangeArcs.hide(), + }, # of rangeArcs.impl + }, # of rangeArcs + { + id:'rangePln1', + impl: { + init: func(nd,symbol), + predicate: func(nd) {return 0}, + is_true: func(nd) { + nd.symbols.rangePln1.show(); + nd.symbols.rangePln1.setText(sprintf("%3.0f",nd.rangeNm())); + }, + is_false: func(nd) nd.symbols.rangePln1.hide(), + }, + }, + { + id:'rangePln2', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.get_switch('toggle_display_mode') == "MAP" and !nd.get_switch('toggle_centered'), + is_true: func(nd) { + nd.symbols.rangePln2.show(); + nd.symbols.rangePln2.setText(sprintf("%3.0f",(nd.rangeNm()/2) + (nd.rangeNm()/4))); + }, + is_false: func(nd) nd.symbols.rangePln2.hide(), + }, + }, + { + id:'rangePln3', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN" or nd.get_switch('toggle_centered'), + is_true: func(nd) { + nd.symbols.rangePln3.show(); + nd.symbols.rangePln3.setText(sprintf("%3.0f",nd.rangeNm()/2)); + }, + is_false: func(nd) nd.symbols.rangePln3.hide(), + }, + }, + { + id:'rangePln4', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN" or nd.get_switch('toggle_centered'), + is_true: func(nd) { + nd.symbols.rangePln4.show(); + nd.symbols.rangePln4.setText(sprintf("%3.0f",nd.rangeNm())); + }, + is_false: func(nd) nd.symbols.rangePln4.hide(), + }, + }, + { + id:'rangePln5', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.get_switch('toggle_display_mode') == "MAP" and !nd.get_switch('toggle_centered'), + is_true: func(nd) { + nd.symbols.rangePln5.show(); + nd.symbols.rangePln5.setText(sprintf("%3.0f",(nd.rangeNm()/2) + (nd.rangeNm()/4))); + }, + is_false: func(nd) nd.symbols.rangePln5.hide(), + }, + }, + { + id:'range', + impl: { + init: func(nd,symbol), + predicate: func(nd) !nd.get_switch('toggle_centered'), + is_true: func(nd) { + nd.symbols.range.show(); + nd.symbols.range.setText(sprintf("%3.0f",nd.rangeNm()/2)); + }, + is_false: func(nd) nd.symbols.range.hide(), + }, + }, + { + id:'range_r', + impl: { + init: func(nd,symbol), + predicate: func(nd) !nd.get_switch('toggle_centered'), + is_true: func(nd) { + nd.symbols.range_r.show(); + nd.symbols.range_r.setText(sprintf("%3.0f",nd.rangeNm()/2)); + }, + is_false: func(nd) nd.symbols.range_r.hide(), + }, + }, + { + id:'aplSymMap', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.get_switch('toggle_display_mode') == "MAP" and !nd.get_switch('toggle_centered')), + is_true: func(nd) { + nd.symbols.aplSymMap.set('z-index', 10); + nd.symbols.aplSymMap.show(); + + }, + is_false: func(nd) nd.symbols.aplSymMap.hide(), + }, + }, + { + id:'aplSymMapCtr', + impl: { + init: func(nd,symbol), + predicate: func(nd) ((nd.get_switch('toggle_display_mode') == "MAP" and nd.get_switch('toggle_centered')) or nd.in_mode('toggle_display_mode', ['APP','VOR'])), + is_true: func(nd) { + nd.symbols.aplSymMapCtr.set('z-index', 10); + nd.symbols.aplSymMapCtr.show(); + + }, + is_false: func(nd) nd.symbols.aplSymMapCtr.hide(), + }, + }, + { + id:'aplSymVor', + impl: { + init: func(nd,symbol), + predicate: func(nd) {return 0;}, + is_true: func(nd) { + nd.symbols.aplSymVor.show(); + }, + is_false: func(nd) nd.symbols.aplSymVor.hide(), + }, + }, + { + id:'crsLbl', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']), + is_true: func(nd) nd.symbols.crsLbl.show(), + is_false: func(nd) nd.symbols.crsLbl.hide(), + }, + }, + { + id:'crs', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']), + is_true: func(nd) { + nd.symbols.crs.show(); + if(getprop("instrumentation/nav/radials/selected-deg") != nil) + nd.symbols.crs.setText(sprintf("%03.0f",getprop("instrumentation/nav/radials/selected-deg"))); + }, + is_false: func(nd) nd.symbols.crs.hide(), + }, + }, + { + id:'dmeLbl', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']), + is_true: func(nd) nd.symbols.dmeLbl.show(), + is_false: func(nd) nd.symbols.dmeLbl.hide(), + }, + }, + { + id:'dme', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']), + is_true: func(nd) { + nd.symbols.dme.show(); + nd.symbols.dme.setText(getprop("instrumentation/nav/nav-id")); + if(nd.get_switch('toggle_display_mode') == 'APP') + nd.symbols.dme.setColor(0.69,0,0.39); + else + nd.symbols.dme.setColor(1,1,1); + }, + is_false: func(nd) nd.symbols.dme.hide(), + }, + }, + { + id:'trkline', + impl: { + init: func(nd,symbol), + predicate: func(nd){ + nd.get_switch('toggle_display_mode') == 'MAP' and + !nd.get_switch('toggle_centered') and + ( + getprop(nd.options.defaults.lat_ctrl) != nd.options.defaults.managed_val or + nd.get_switch('toggle_trk_line') + ) + }, + is_true: func(nd) { + nd.symbols.trkline.show(); + }, + is_false: func(nd) nd.symbols.trkline.hide(), + }, + }, + { + id:'trkInd2', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR','MAP']) and + nd.get_switch('toggle_centered')), + is_true: func(nd) { + nd.symbols.trkInd2.show(); + nd.symbols.trkInd2.setRotation((nd.aircraft_source.get_trk_mag()-nd.aircraft_source.get_hdg_mag())*D2R); + }, + is_false: func(nd) nd.symbols.trkInd2.hide(), + }, + }, + { + id:'trkline2', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.get_switch('toggle_display_mode') == 'MAP' and + nd.get_switch('toggle_centered') and + getprop(nd.options.defaults.lat_ctrl) != nd.options.defaults.managed_val), + is_true: func(nd) { + nd.symbols.trkline2.show(); + }, + is_false: func(nd) nd.symbols.trkline2.hide(), + }, + }, + { + id:'vorCrsPtr', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and !nd.get_switch('toggle_centered')), + is_true: func(nd) { + nd.symbols.vorCrsPtr.show(); + nd.symbols.vorCrsPtr.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_hdg_tru())*D2R); + + }, + is_false: func(nd) nd.symbols.vorCrsPtr.hide(), + }, + }, + { + id:'vorCrsPtr2', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and nd.get_switch('toggle_centered')), + is_true: func(nd) { + nd.symbols.vorCrsPtr2.show(); + nd.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_hdg_tru())*D2R); + var line = nd.symbols.vorCrsPtr2.getElementById('vorCrsPtr2_line'); + if(nd.get_switch('toggle_display_mode') == 'VOR'){ + line.setColor(0,0.62,0.84); + line.setColorFill(0,0.62,0.84); + } else { + line.setColor(0.9,0,0.47); + line.setColorFill(0.9,0,0.47); + } + }, + is_false: func(nd) nd.symbols.vorCrsPtr2.hide(), + }, + }, + { + id: 'gsDiamond', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']), + is_true: func(nd) { + if(getprop("instrumentation/nav/gs-needle-deflection-norm") != nil) + nd.symbols.gsDiamond.setTranslation(getprop("instrumentation/nav/gs-needle-deflection-norm")*150,0); + }, + is_false: func(nd) nd.symbols.gsGroup.hide(), + }, + }, + { + id:'locPtr', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and !nd.get_switch('toggle_centered') and getprop("instrumentation/nav/in-range")), + is_true: func(nd) { + nd.symbols.locPtr.show(); + var deflection = getprop("instrumentation/nav/heading-needle-deflection-norm"); + nd.symbols.locPtr.setTranslation(deflection*150,0); + }, + is_false: func(nd) nd.symbols.locPtr.hide(), + }, + }, + { + id:'locPtr2', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and nd.get_switch('toggle_centered') and getprop("instrumentation/nav/in-range")), + is_true: func(nd) { + var curmode = nd.get_switch('toggle_display_mode'); + var ils_mode = getprop('/flight-management/freq/ils-mode'); + if((ils_mode and curmode == 'VOR') or (!ils_mode and curmode == 'APP')){ + nd.symbols.locPtr2.hide(); + return; + } + nd.symbols.locPtr2.show(); + var deflection = getprop("instrumentation/nav/heading-needle-deflection-norm"); + nd.symbols.locPtr2.setTranslation(deflection*150,0); + var line = nd.symbols.locPtr2.getElementById('locPtr2_line'); + var arr1 = nd.symbols.locPtr2.getElementById('locPtr2_arr1'); + var arr2 = nd.symbols.locPtr2.getElementById('locPtr2_arr2'); + if(curmode == 'VOR'){ + #nd.symbols.vorCrsPtr2.setColor(0,0.62,0.84); + line.setColor(0,0.62,0.84); + line.setColorFill(0,0.62,0.84); + arr1.show(); + arr2.show(); + } else { + line.setColor(0.9,0,0.47); + line.setColorFill(0.9,0,0.47); + arr1.hide(); + arr2.hide(); + } + }, + is_false: func(nd) nd.symbols.locPtr2.hide(), + }, + }, + { + id:'wind', + impl: { + init: func(nd,symbol), + predicate: ALWAYS, + is_true: func(nd) { + var windDir = getprop("environment/wind-from-heading-deg"); + if(!nd.get_switch('toggle_true_north')) + windDir = windDir + getprop("environment/magnetic-variation-deg"); + nd.symbols.wind.setText(sprintf("%03.0f / %02.0f",windDir,getprop("environment/wind-speed-kt"))); + }, + is_false: NOTHING, + }, + }, + { + id:'windArrow', + impl: { + init: func(nd,symbol), + predicate: func(nd) (!(nd.in_mode('toggle_display_mode', ['PLAN']) and (nd.get_switch('toggle_display_type') == "LCD"))), + is_true: func(nd) { + nd.symbols.windArrow.show(); + var windArrowRot = getprop("environment/wind-from-heading-deg"); + if(nd.in_mode('toggle_display_mode', ['MAP','PLAN'])) { + if(nd.get_switch('toggle_true_north')) + windArrowRot = windArrowRot - nd.aircraft_source.get_trk_tru(); + else + windArrowRot = windArrowRot - nd.aircraft_source.get_trk_mag(); + } else { + if(nd.get_switch('toggle_true_north')) + windArrowRot = windArrowRot - nd.aircraft_source.get_hdg_tru(); + else + windArrowRot = windArrowRot - nd.aircraft_source.get_hdg_mag(); + } + nd.symbols.windArrow.setRotation(windArrowRot*D2R); + }, + is_false: func(nd) nd.symbols.windArrow.hide(), + }, + }, + { + id:'staToL2', + impl: { + init: func(nd,symbol), + predicate: func(nd) !(nd.in_mode('toggle_display_mode', ['PLAN'])) and nd.get_switch('toggle_centered') and ((getprop("instrumentation/nav/in-range") and nd.get_switch('toggle_lh_vor_adf') == 1) or (getprop("instrumentation/adf/in-range") and nd.get_switch('toggle_lh_vor_adf') == -1)), + is_true: func(nd) { + if(nd.get_switch('toggle_lh_vor_adf') < 0){ + nd.symbols.staToL2.setColor(0.195,0.96,0.097); + nd.symbols.staFromL2.setColor(0.195,0.96,0.097); + } else { + nd.symbols.staToL2.setColor(1,1,1); + nd.symbols.staFromL2.setColor(1,1,1); + } + nd.symbols.staToL2.show(); + nd.symbols.staFromL2.show(); + }, + is_false: func(nd){ + nd.symbols.staToL2.hide(); + nd.symbols.staFromL2.hide(); + } + } + }, + { + id:'staToR2', + impl: { + init: func(nd,symbol), + predicate: func(nd) !(nd.in_mode('toggle_display_mode', ['PLAN'])) and nd.get_switch('toggle_centered') and ((getprop("instrumentation/nav[1]/in-range") and nd.get_switch('toggle_rh_vor_adf') == 1) or (getprop("instrumentation/adf[1]/in-range") and nd.get_switch('toggle_rh_vor_adf') == -1)), + is_true: func(nd) { + if(nd.get_switch('toggle_rh_vor_adf') < 0){ + nd.symbols.staToR2.setColor(0.195,0.96,0.097); + nd.symbols.staFromR2.setColor(0.195,0.96,0.097); + } else { + nd.symbols.staToR2.setColor(1,1,1); + nd.symbols.staFromR2.setColor(1,1,1); + } + nd.symbols.staToR2.show(); + nd.symbols.staFromR2.show(); + }, + is_false: func(nd){ + nd.symbols.staToR2.hide(); + nd.symbols.staFromR2.hide(); + } + } + }, + { + id:'staToL', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.in_mode('toggle_display_mode', ['MAP']) and !nd.get_switch('toggle_centered') and ((getprop("instrumentation/nav/in-range") and nd.get_switch('toggle_lh_vor_adf') == 1) or (getprop("instrumentation/adf/in-range") and nd.get_switch('toggle_lh_vor_adf') == -1)), + is_true: func(nd) { + if(nd.get_switch('toggle_lh_vor_adf') < 0){ + nd.symbols.staToL.setColor(0.195,0.96,0.097); + nd.symbols.staFromL.setColor(0.195,0.96,0.097); + } else { + nd.symbols.staToL.setColor(1,1,1); + nd.symbols.staFromL.setColor(1,1,1); + } + nd.symbols.staToL.show(); + nd.symbols.staFromL.show(); + }, + is_false: func(nd){ + nd.symbols.staToL.hide(); + nd.symbols.staFromL.hide(); + } + } + }, + { + id:'staToR', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.in_mode('toggle_display_mode', ['MAP']) and !nd.get_switch('toggle_centered') and ((getprop("instrumentation/nav[1]/in-range") and nd.get_switch('toggle_rh_vor_adf') == 1) or (getprop("instrumentation/adf[1]/in-range") and nd.get_switch('toggle_rh_vor_adf') == -1)), + is_true: func(nd) { + if(nd.get_switch('toggle_rh_vor_adf') < 0){ + nd.symbols.staToR.setColor(0.195,0.96,0.097); + nd.symbols.staFromR.setColor(0.195,0.96,0.097); + } else { + nd.symbols.staToR.setColor(1,1,1); + nd.symbols.staFromR.setColor(1,1,1); + } + nd.symbols.staToR.show(); + nd.symbols.staFromR.show(); + }, + is_false: func(nd){ + nd.symbols.staToR.hide(); + nd.symbols.staFromR.hide(); + } + } + }, + { + id:'dmeL', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.get_switch('toggle_lh_vor_adf') != 0), + is_true: func(nd) { + nd.symbols.dmeL.show(); + if(nd.get_switch('toggle_lh_vor_adf') < 0){ + nd.symbols.vorL.setText("ADF 1"); + nd.symbols.vorL.setColor(0.195,0.96,0.097); + nd.symbols.vorLId.setColor(0.195,0.96,0.097); + nd.symbols.dmeLDist.setColor(0.195,0.96,0.097); + } + else{ + nd.symbols.vorL.setText("VOR 1"); + nd.symbols.vorL.setColor(1,1,1); + nd.symbols.vorLId.setColor(1,1,1); + nd.symbols.dmeLDist.setColor(1,1,1); + } + nd.symbols.dmeL.setText('NM'); + nd.symbols.dmeL.setColor(0,0.59,0.8); + }, + is_false: func(nd){ + nd.symbols.dmeL.hide(); + } + } + }, + { + id:'dmeR', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.get_switch('toggle_rh_vor_adf') != 0), + is_true: func(nd) { + nd.symbols.dmeR.show(); + if(nd.get_switch('toggle_rh_vor_adf') < 0){ + nd.symbols.vorR.setText("ADF 2"); + nd.symbols.vorR.setColor(0.195,0.96,0.097); + nd.symbols.vorRId.setColor(0.195,0.96,0.097); + nd.symbols.dmeRDist.setColor(0.195,0.96,0.097); + } else { + nd.symbols.vorR.setText("VOR 2"); + nd.symbols.vorR.setColor(1,1,1); + nd.symbols.vorRId.setColor(1,1,1); + nd.symbols.dmeRDist.setColor(1,1,1); + } + nd.symbols.dmeR.setText('NM'); + nd.symbols.dmeR.setColor(0,0.59,0.8); + }, + is_false: func(nd){ + nd.symbols.dmeR.hide(); + } + } + }, + { + id: 'vorL', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.get_switch('toggle_lh_vor_adf') != 0), + is_true: func(nd) { + nd.symbols.vorL.show(); + nd.symbols.vorLId.show(); + nd.symbols.dmeLDist.show(); + if(nd.get_switch('toggle_rh_vor_adf') < 0){ + var adf = 'instrumentation/adf/'; + var navident = getprop(adf~ "ident"); + var frq = getprop(adf~ "frequencies/selected-khz"); + if(navident != "") + nd.symbols.vorLId.setText(navident); + else + nd.symbols.vorLId.setText(sprintf("%3d", frq)); + nd.symbols.dmeLDist.setText(""); + } else { + var nav = 'instrumentation/nav/'; + var navID = getprop(nav~"nav-id"); + var frq = getprop(nav~"frequencies/selected-mhz-fmt"); + var dme = 'instrumentation/dme/'; + var dst = getprop(dme~ "indicated-distance-nm"); + if(getprop("instrumentation/nav/in-range")) + nd.symbols.vorLId.setText(navID); + else + nd.symbols.vorLId.setText(frq); + if(getprop("instrumentation/dme/in-range")) + nd.symbols.dmeLDist.setText(sprintf("%3.1f",dst)); + else nd.symbols.dmeLDist.setText(" ---"); + } + }, + is_false: func(nd){ + nd.symbols.vorL.hide(); + nd.symbols.vorLId.hide(); + nd.symbols.dmeLDist.hide(); + } + } + }, + { + id:'vorLSym', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.get_switch('toggle_lh_vor_adf') != 0), + is_true: func(nd) { + nd.symbols.vorLSym.show(); + }, + is_false: func(nd){ + nd.symbols.vorLSym.hide(); + } + } + }, + { + id:'vorRSym', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.get_switch('toggle_rh_vor_adf') != 0), + is_true: func(nd) { + nd.symbols.vorRSym.show(); + }, + is_false: func(nd){ + nd.symbols.vorRSym.hide(); + } + } + }, + { + id:'appMode', + impl: { + init: func(nd,symbol), + predicate: func(nd) { + var mode = getprop(nd.options.defaults.app_mode); + return (mode != '' and mode != nil); + }, + is_true: func(nd) { + var mode = getprop(nd.options.defaults.app_mode); + nd.symbols.appMode.show(); + nd.symbols.appMode.setText(mode); + }, + is_false: func(nd){ + nd.symbols.appMode.hide(); + } + } + }, + { + id:'chrono_box', + impl: { + init: func(nd,symbol), + predicate: func(nd) nd.get_switch('toggle_chrono'), + is_true: func(nd) { + var efis_node = props.globals.getNode(nd.efis_path); + var idx = efis_node.getIndex() or 0; + var chronoNode = nd.options.defaults.chrono_node~'['~idx~']'; + chronoNode = props.globals.getNode(chronoNode); + var time = nil; + if(chronoNode != nil){ + time = chronoNode.getValue('text'); + } + nd.symbols.chrono_box.show(); + if(time != nil and time != '') + nd.symbols.chrono_text.setText(time); + }, + is_false: func(nd){ + nd.symbols.chrono_box.hide(); + } + } + }, + { + id:'chrono_text', + impl: { + init: func(nd,symbol), + predicate: func(nd) 1, + is_true: func(nd) nd.symbols.chrono_text.show(), + is_false: func(nd) nd.symbols.chrono_text.hide(), + } + }, + { + id:'degreeArrows', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.get_switch('toggle_display_mode') != 'PLAN' and nd.get_switch('toggle_centered')), + is_true: func(nd) { + nd.symbols.degreeArrows.show(); + }, + is_false: func(nd){ + nd.symbols.degreeArrows.hide(); + } + } + }, + { + id: 'legDistL', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.get_switch('toggle_display_mode') == 'MAP' and !nd.get_switch('toggle_centered')), + is_true: func(nd){ + var active = getprop('autopilot/route-manager/active'); + var lat_ctrl = getprop(nd.options.defaults.lat_ctrl); + var managed_v = nd.options.defaults.managed_val; + var is_managed = (lat_ctrl == managed_v); + var toggle_xtrk_err = nd.get_switch('toggle_xtrk_error'); + if((!active or is_managed) and !toggle_xtrk_err){ + nd.symbols.legDistL.hide(); + } else { + var dist = getprop('instrumentation/gps/wp/wp[1]/course-error-nm'); + if(dist == nil or dist == '' or dist > -0.1){ + nd.symbols.legDistL.hide(); + } else { + dist = sprintf('%.1fL', math.abs(dist)); + nd.symbols.legDistL.setText(dist); + nd.symbols.legDistL.show(); + } + } + }, + is_false: func(nd){ + nd.symbols.legDistL.hide(); + } + } + }, + { + id: 'legDistR', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.get_switch('toggle_display_mode') == 'MAP' and !nd.get_switch('toggle_centered')), + is_true: func(nd){ + var active = getprop('autopilot/route-manager/active'); + var lat_ctrl = getprop(nd.options.defaults.lat_ctrl); + var managed_v = nd.options.defaults.managed_val; + var is_managed = (lat_ctrl == managed_v); + var toggle_xtrk_err = nd.get_switch('toggle_xtrk_error'); + if((!active or is_managed) and !toggle_xtrk_err){ + nd.symbols.legDistR.hide(); + } else { + var dist = getprop('instrumentation/gps/wp/wp[1]/course-error-nm'); + if(dist == nil or dist == '' or dist < 0.1){ + nd.symbols.legDistR.hide(); + } else { + dist = sprintf('%.1fR', math.abs(dist)); + nd.symbols.legDistR.setText(dist); + nd.symbols.legDistR.show(); + } + } + }, + is_false: func(nd){ + nd.symbols.legDistR.hide(); + } + } + }, + { + id: 'legDistCtrL', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.get_switch('toggle_display_mode') == 'MAP' and nd.get_switch('toggle_centered')), + is_true: func(nd){ + var active = getprop('autopilot/route-manager/active'); + var lat_ctrl = getprop(nd.options.defaults.lat_ctrl); + var managed_v = nd.options.defaults.managed_val; + var is_managed = (lat_ctrl == managed_v); + var toggle_xtrk_err = nd.get_switch('toggle_xtrk_error'); + if((!active or is_managed) and !toggle_xtrk_err){ + nd.symbols.legDistCtrL.hide(); + } else { + var dist = getprop('instrumentation/gps/wp/wp[1]/course-error-nm'); + if(dist == nil or dist == '' or dist > -0.1){ + nd.symbols.legDistCtrL.hide(); + } else { + dist = sprintf('%.1fL', math.abs(dist)); + nd.symbols.legDistCtrL.setText(dist); + nd.symbols.legDistCtrL.show(); + } + } + }, + is_false: func(nd){ + nd.symbols.legDistCtrL.hide(); + } + } + }, + { + id: 'legDistCtrR', + impl: { + init: func(nd,symbol), + predicate: func(nd) (nd.get_switch('toggle_display_mode') == 'MAP' and nd.get_switch('toggle_centered')), + is_true: func(nd){ + var active = getprop('autopilot/route-manager/active'); + var lat_ctrl = getprop(nd.options.defaults.lat_ctrl); + var managed_v = nd.options.defaults.managed_val; + var is_managed = (lat_ctrl == managed_v); + var toggle_xtrk_err = nd.get_switch('toggle_xtrk_error'); + if((!active or is_managed) and !toggle_xtrk_err){ + nd.symbols.legDistCtrR.hide(); + } else { + var dist = getprop('instrumentation/gps/wp/wp[1]/course-error-nm'); + if(dist == nil or dist == '' or dist < 0.1){ + nd.symbols.legDistCtrR.hide(); + } else { + dist = sprintf('%.1fR', math.abs(dist)); + nd.symbols.legDistCtrR.setText(dist); + nd.symbols.legDistCtrR.show(); + } + } + }, + is_false: func(nd){ + nd.symbols.legDistCtrR.hide(); + } + } + }, + + ], # end of vector with features + + } ##### ## -## add support for other aircraft/ND types and styles here (Airbus etc) +## add support for other aircraft/ND types and styles here ## or move to other files. ## ## see: http://wiki.flightgear.org/NavDisplay#Custom_ND_Styles