1 # ==============================================================================
2 # Boeing Navigation Display by Gijs de Rooy
3 # ==============================================================================
6 # do we really need to keep track of each drawable here ??
10 # pseudo DSL-ish: use these as placeholders in the config hash below
12 var NOTHING = func nil;
15 # so that we only need to update a single line ...
17 var trigger_update = func(layer) layer._model.init();
20 # TODO: move ND-specific implementation details into this lookup hash
21 # so that other aircraft and ND types can be more easily supported
23 # any aircraft-specific ND behavior should be wrapped here,
24 # to isolate/decouple things in the generic NavDisplay class
26 # TODO: move this to an XML config file
30 # this configures the 744 ND to help generalize the NavDisplay class itself
32 font_mapper: func(family, weight) {
33 if( family == "Liberation Sans" and weight == "normal" )
34 return "LiberationFonts/LiberationSans-Regular.ttf";
37 # where all the symbols are stored
38 # TODO: SVG elements should be renamed to use boeing/airbus prefix
39 # aircraft developers should all be editing the same ND.svg image
40 # the code can deal with the differences now
41 svg_filename: "Nasal/canvas/map/boeingND.svg",
43 ## this loads and configures existing layers (currently, *.layer files in Nasal/canvas/map)
47 { name:'fixes', disabled:1, update_on:['toggle_range','toggle_waypoints'],
48 predicate: func(nd, layer) {
49 # print("Running fixes predicate");
50 var visible=nd.get_switch('toggle_waypoints') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
52 # print("fixes update requested!");
53 trigger_update( layer );
55 layer._view.setVisible(visible);
56 }, # end of layer update predicate
57 }, # end of fixes layer
58 { name:'FIX', isMapStructure:1, update_on:['toggle_range','toggle_waypoints'],
59 # FIXME: this is a really ugly place for controller code
60 predicate: func(nd, layer) {
61 # print("Running vor layer predicate");
62 # toggle visibility here
63 var visible=nd.get_switch('toggle_waypoints') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
64 layer.group.setVisible( nd.get_switch('toggle_waypoints') );
66 #print("Updating MapStructure ND layer: FIX");
67 # (Hopefully) smart update
70 }, # end of layer update predicate
72 # Should redraw every 10 seconds
73 { name:'storms', update_on:['toggle_range','toggle_weather','toggle_display_mode'],
74 predicate: func(nd, layer) {
75 # print("Running fixes predicate");
76 var visible=nd.get_switch('toggle_weather') and nd.get_switch('toggle_display_mode') != "PLAN";
78 #print("storms update requested!");
79 trigger_update( layer );
81 layer._view.setVisible(visible);
82 }, # end of layer update predicate
83 }, # end of storms layer
85 { name:'airports-nd', update_on:['toggle_range','toggle_airports','toggle_display_mode'],
86 predicate: func(nd, layer) {
87 # print("Running airports-nd predicate");
88 var visible = nd.get_switch('toggle_airports') and nd.in_mode('toggle_display_mode', ['MAP']);
90 trigger_update( layer ); # clear & redraw
92 layer._view.setVisible( visible );
93 }, # end of layer update predicate
94 }, # end of airports layer
96 # Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
97 { name:'vor', disabled:1, update_on:['toggle_range','toggle_stations','toggle_display_mode'],
98 predicate: func(nd, layer) {
99 # print("Running vor layer predicate");
100 var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
102 trigger_update( layer ); # clear & redraw
104 layer._view.setVisible( nd.get_switch('toggle_stations') );
105 }, # end of layer update predicate
106 }, # end of VOR layer
107 { name:'VOR', isMapStructure:1, update_on:['toggle_range','toggle_stations','toggle_display_mode'],
108 # FIXME: this is a really ugly place for controller code
109 predicate: func(nd, layer) {
110 # print("Running vor layer predicate");
111 # toggle visibility here
112 var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
113 layer.group.setVisible( visible );
115 #print("Updating MapStructure ND layer: VOR");
116 # (Hopefully) smart update
119 }, # end of layer update predicate
120 }, # end of VOR layer
122 # Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
123 { name:'dme', disabled:1, update_on:['toggle_range','toggle_stations'],
124 predicate: func(nd, layer) {
125 var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
127 trigger_update( layer ); # clear & redraw
129 layer._view.setVisible( nd.get_switch('toggle_stations') );
130 }, # end of layer update predicate
131 }, # end of DME layers
132 { name:'DME', isMapStructure:1, update_on:['toggle_range','toggle_stations'],
133 # FIXME: this is a really ugly place for controller code
134 predicate: func(nd, layer) {
135 var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
136 # print("Running vor layer predicate");
137 # toggle visibility here
138 layer.group.setVisible( visible );
140 #print("Updating MapStructure ND layer: DME");
141 # (Hopefully) smart update
144 }, # end of layer update predicate
145 }, # end of DME layer
147 { name:'mp-traffic', disabled:1, update_on:['toggle_range','toggle_traffic'],
148 predicate: func(nd, layer) {
149 var visible = nd.get_switch('toggle_traffic');
150 layer._view.setVisible( visible );
152 trigger_update( layer ); # clear & redraw
154 }, # end of layer update predicate
155 }, # end of traffic layer
156 { name:'TFC', isMapStructure:1, update_on:['toggle_range','toggle_traffic'],
157 predicate: func(nd, layer) {
158 var visible = nd.get_switch('toggle_traffic');
159 layer.group.setVisible( visible );
161 #print("Updating MapStructure ND layer: TFC");
164 }, # end of layer update predicate
165 }, # end of traffic layer
167 { name:'runway-nd', update_on:['toggle_range','toggle_display_mode'],
168 predicate: func(nd, layer) {
169 var visible = (nd.rangeNm() <= 40) and getprop("autopilot/route-manager/active") and nd.in_mode('toggle_display_mode', ['MAP','PLAN']) ;
171 trigger_update( layer ); # clear & redraw
172 layer._view.setVisible( visible );
173 }, # end of layer update predicate
174 }, # end of airports-nd layer
176 { name:'route', update_on:['toggle_range','toggle_display_mode'],
177 predicate: func(nd, layer) {
178 var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN']));
180 trigger_update( layer ); # clear & redraw
181 layer._view.setVisible( visible );
182 }, # end of layer update predicate
183 }, # end of route layer
185 ## add other layers here, layer names must match the registered names as used in *.layer files for now
186 ## this will all change once we're using Philosopher's MapStructure framework
188 ], # end of vector with configured layers
190 # This is where SVG elements are configured by providing "behavior" hashes, i.e. for animations
192 # to animate each SVG symbol, specify behavior via callbacks (predicate, and true/false implementation)
193 # SVG identifier, callback etc
194 # TODO: update_on([]), update_mode (update() vs. timers/listeners)
195 # TODO: support putting symbols on specific layers
198 # TODO: taOnly doesn't need to use getprop polling in update(), use a listener instead!
199 id: 'taOnly', # the SVG ID
200 impl: { # implementation hash
201 init: func(nd, symbol), # for updateCenter stuff, called during initialization in the ctor
202 predicate: func(nd) getprop("instrumentation/tcas/inputs/mode") == 2, # the condition
203 is_true: func(nd) nd.symbols.taOnly.show(), # if true, run this
204 is_false: func(nd) nd.symbols.taOnly.hide(), # if false, run this
205 }, # end of taOnly behavior/callbacks
210 init: func(nd,symbol),
211 predicate: func(nd) nd.aircraft_source.get_spd() > 100,
213 nd.symbols.tas.setText(sprintf("%3.0f",getprop("/velocities/airspeed-kt") ));
214 nd.symbols.tas.show();
216 is_false: func(nd) nd.symbols.tas.hide(),
222 init: func(nd,symbol),
223 predicate: func(nd) nd.aircraft_source.get_spd() > 100,
224 is_true: func(nd) nd.symbols.tasLbl.show(),
225 is_false: func(nd) nd.symbols.tasLbl.hide(),
231 init: func(nd,symbol),
232 predicate: func(nd) getprop("/autopilot/route-manager/wp/id") != nil and getprop("autopilot/route-manager/active"),
234 nd.symbols.wpActiveId.setText(getprop("/autopilot/route-manager/wp/id"));
235 nd.symbols.wpActiveId.show();
237 is_false: func(nd) nd.symbols.wpActiveId.hide(),
238 }, # of wpActiveId.impl
243 init: func(nd,symbol),
244 predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and getprop("autopilot/route-manager/active"),
246 nd.symbols.wpActiveDist.setText(sprintf("%3.01f",getprop("/autopilot/route-manager/wp/dist")));
247 nd.symbols.wpActiveDist.show();
249 is_false: func(nd) nd.symbols.wpActiveDist.hide(),
253 id: 'wpActiveDistLbl',
255 init: func(nd,symbol),
256 predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and getprop("autopilot/route-manager/active"),
257 is_true: func(nd) nd.symbols.wpActiveDistLbl.show(),
258 is_false: func(nd) nd.symbols.wpActiveDistLbl.hide(),
264 init: func(nd,symbol),
265 predicate: func(nd) getprop("autopilot/route-manager/wp/eta") != nil and getprop("autopilot/route-manager/active"),
267 var etaSec = getprop("/sim/time/utc/day-seconds")+getprop("autopilot/route-manager/wp/eta-seconds");
268 var h = math.floor(etaSec/3600);
270 etaSec=etaSec-3600*h;
271 var m = math.floor(etaSec/60);
274 nd.symbols.eta.setText(sprintf("%02.0f%02.0f.%01.0fz",h,m,s));
275 nd.symbols.eta.show();
277 is_false: func(nd) nd.symbols.eta.hide(),
283 init: func(nd,symbol),
284 predicate: ALWAYS, # always true
285 is_true: func(nd) nd.symbols.hdg.setText(sprintf("%03.0f", nd.aircraft_source.get_hdg_mag() )),
292 init: func(nd,symbol),
293 common: func(nd) nd.symbols.gs.setText(sprintf("%3.0f",nd.aircraft_source.get_gnd_spd() )),
294 predicate: func(nd) nd.aircraft_source.get_gnd_spd() >= 30,
296 nd.symbols.gs.setFontSize(36);
298 is_false: func(nd) nd.symbols.gs.setFontSize(52),
304 init: func(nd,symbol),
305 predicate: func(nd) ((((nd.get_switch('toggle_display_mode') == "APP" or nd.get_switch('toggle_display_mode') == "VOR") and nd.get_switch('toggle_weather')) or nd.get_switch('toggle_display_mode') == "MAP") and (!nd.get_switch('toggle_centered'))),
306 is_true: func(nd) nd.symbols.rangeArcs.show(),
307 is_false: func(nd) nd.symbols.rangeArcs.hide(),
308 }, # of rangeArcs.impl
313 init: func(nd,symbol),
314 predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
316 nd.symbols.rangePln1.show();
317 nd.symbols.rangePln1.setText(sprintf("%3.0f",nd.rangeNm()));
319 is_false: func(nd) nd.symbols.rangePln1.hide(),
325 init: func(nd,symbol),
326 predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
328 nd.symbols.rangePln2.show();
329 nd.symbols.rangePln2.setText(sprintf("%3.0f",nd.rangeNm()/2));
331 is_false: func(nd) nd.symbols.rangePln2.hide(),
337 init: func(nd,symbol),
338 predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
340 nd.symbols.rangePln3.show();
341 nd.symbols.rangePln3.setText(sprintf("%3.0f",nd.rangeNm()/2));
343 is_false: func(nd) nd.symbols.rangePln3.hide(),
349 init: func(nd,symbol),
350 predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
352 nd.symbols.rangePln4.show();
353 nd.symbols.rangePln4.setText(sprintf("%3.0f",nd.rangeNm()));
355 is_false: func(nd) nd.symbols.rangePln4.hide(),
361 init: func(nd,symbol),
362 predicate: func(nd) (!(nd.in_mode('toggle_display_mode', ['PLAN']) and (nd.get_switch('toggle_display_type') == "LCD")) and nd.aircraft_source.get_spd() > 100),
364 nd.symbols.windArrow.show();
365 var windArrowRot = (nd.aircraft_source.get_hdg_tru()-nd.aircraft_source.get_trk_tru())*D2R;
366 if(nd.in_mode('toggle_display_mode', ['MAP','PLAN']))
367 windArrowRot = 2*windArrowRot;
368 nd.symbols.windArrow.setRotation(windArrowRot);
370 is_false: func(nd) nd.symbols.windArrow.hide(),
374 ], # end of vector with features
377 }, # end of Boeing style
380 ## add support for other aircraft/ND types and styles here (Airbus etc)
387 # 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)
390 var NDSourceDriver = {};
391 NDSourceDriver.new = func {
392 var m = {parents:[NDSourceDriver]};
393 m.get_hdg_mag= func getprop("/orientation/heading-magnetic-deg");
394 m.get_hdg_tru= func getprop("/orientation/heading-deg");
395 m.get_hgg = func getprop("instrumentation/afds/settings/heading");
398 if(getprop("/velocities/groundspeed-kt") > 80)
400 getprop("/orientation/track-magnetic-deg");
404 getprop("/orientation/heading-magnetic-deg");
409 if(getprop("/velocities/groundspeed-kt") > 80)
411 getprop("/orientation/track-deg");
415 getprop("/orientation/heading-deg");
418 m.get_lat= func getprop("/position/latitude-deg");
419 m.get_lon= func getprop("/position/longitude-deg");
420 m.get_spd= func getprop("/velocities/airspeed-kt");
421 m.get_gnd_spd= func getprop("/velocities/groundspeed-kt");
422 m.get_vspd= func getprop("/velocities/vertical-speed-fps");
427 # configure aircraft specific cockpit switches here
428 # these are some defaults, can be overridden when calling NavDisplay.new() -
429 # see the 744 ND.nas file the backend code should never deal directly with
430 # aircraft specific properties using getprop.
431 # To get started implementing your own ND, just copy the switches hash to your
432 # ND.nas file and map the keys to your cockpit properties - and things will just work.
434 # TODO: switches are ND specific, so move to the NDStyle hash!
436 var default_switches = {
437 'toggle_range': {path: '/inputs/range-nm', value:40, type:'INT'},
438 'toggle_weather': {path: '/inputs/wxr', value:0, type:'BOOL'},
439 'toggle_airports': {path: '/inputs/arpt', value:0, type:'BOOL'},
440 'toggle_stations': {path: '/inputs/sta', value:0, type:'BOOL'},
441 'toggle_waypoints': {path: '/inputs/wpt', value:0, type:'BOOL'},
442 'toggle_position': {path: '/inputs/pos', value:0, type:'BOOL'},
443 'toggle_data': {path: '/inputs/data',value:0, type:'BOOL'},
444 'toggle_terrain': {path: '/inputs/terr',value:0, type:'BOOL'},
445 'toggle_traffic': {path: '/inputs/tfc',value:0, type:'BOOL'},
446 'toggle_centered': {path: '/inputs/nd-centered',value:0, type:'BOOL'},
447 'toggle_lh_vor_adf': {path: '/inputs/lh-vor-adf',value:0, type:'INT'},
448 'toggle_rh_vor_adf': {path: '/inputs/rh-vor-adf',value:0, type:'INT'},
449 'toggle_display_mode': {path: '/mfd/display-mode', value:'MAP', type:'STRING'}, # valid values are: APP, MAP, PLAN or VOR
450 'toggle_display_type': {path: '/mfd/display-type', value:'CRT', type:'STRING'}, # valid values are: CRT or LCD
451 'toggle_true_north': {path: '/mfd/true-north', value:0, type:'BOOL'},
454 # Hack to update weather radar once every 10 seconds
455 var update_weather = func {
456 if (getprop("/instrumentation/efis/inputs/wxr") != nil)
457 setprop("/instrumentation/efis/inputs/wxr",getprop("/instrumentation/efis/inputs/wxr"));
458 settimer(update_weather, 10);
462 # Hack to update airplane symbol location on PLAN mode every 5 seconds
463 var update_apl_sym = func {
464 if (getprop("/instrumentation/efis/mfd/display-mode") == "PLAN")
465 setprop("/instrumentation/efis/mfd/display-mode","PLAN");
466 settimer(update_apl_sym, 5);
472 # - introduce a MFD class (use it also for PFD/EICAS)
473 # - introduce a SGSubsystem class and use it here
474 # - introduce a Boeing NavDisplay class
479 print("Cleaning up NavDisplay");
480 # shut down all timers and other loops here
481 me.update_timer.stop();
482 foreach(var l; me.listeners)
484 # clean up MapStructure
487 if (me.canvas_handle != nil)
488 me.canvas_handle.del();
493 append(me.listeners, setlistener(p,c));
496 # listeners for cockpit switches
497 listen_switch: func(s,c) {
498 # print("event setup for: ", id(c));
499 me.listen( me.get_full_switch_path(s), func {
500 # print("listen_switch triggered:", s, " callback id:", id(c) );
505 # get the full property path for a given switch
506 get_full_switch_path: func (s) {
507 # debug.dump( me.efis_switches[s] );
508 return me.efis_path ~ me.efis_switches[s].path; # FIXME: should be using props.nas instead of ~
511 # helper method for getting configurable cockpit switches (which are usually different in each aircraft)
512 get_switch: func(s) {
513 var switch = me.efis_switches[s];
514 var path = me.efis_path ~ switch.path ;
515 #print(s,":Getting switch prop:", path);
517 return getprop( path );
520 # for creating NDs that are driven by AI traffic instead of the main aircraft (generalization rocks!)
521 connectAI: func(source=nil) {
522 me.aircraft_source = {
523 get_hdg_mag: func source.getNode('orientation/heading-magnetic-deg').getValue(),
524 get_trk_mag: func source.getNode('orientation/track-magnetic-deg').getValue(),
525 get_lat: func source.getNode('position/latitude-deg').getValue(),
526 get_lon: func source.getNode('position/longitude-deg').getValue(),
527 get_spd: func source.getNode('velocities/true-airspeed-kt').getValue(),
528 get_gnd_spd: func source.getNode('velocities/groundspeed-kt').getValue(),
532 # TODO: the ctor should allow customization, for different aircraft
533 # especially properties and SVG files/handles (747, 757, 777 etc)
534 new : func(prop1, switches=default_switches, style='Boeing') {
535 var m = { parents : [NavDisplay]};
539 m.listeners=[]; # for cleanup handling
540 m.aircraft_source = NDSourceDriver.new(); # uses the main aircraft as the driver/source (speeds, position, heading)
542 m.nd_style = NDStyles[style]; # look up ND specific stuff (file names etc)
544 m.radio_list=["instrumentation/comm/frequencies","instrumentation/comm[1]/frequencies",
545 "instrumentation/nav/frequencies", "instrumentation/nav[1]/frequencies"];
546 m.mfd_mode_list=["APP","VOR","MAP","PLAN"];
549 m.efis_switches = switches;
551 # just an alias, to avoid having to rewrite the old code for now
552 m.rangeNm = func m.get_switch('toggle_range');
554 m.efis = props.globals.initNode(prop1);
555 m.mfd = m.efis.initNode("mfd");
557 # TODO: unify this with switch handling
558 m.mfd_mode_num = m.mfd .initNode("mode-num",2,"INT");
559 m.std_mode = m.efis.initNode("inputs/setting-std",0,"BOOL");
560 m.previous_set = m.efis.initNode("inhg-previous",29.92);
561 m.kpa_mode = m.efis.initNode("inputs/kpa-mode",0,"BOOL");
562 m.kpa_output = m.efis.initNode("inhg-kpa",29.92);
563 m.kpa_prevoutput = m.efis.initNode("inhg-kpa-previous",29.92);
564 m.temp = m.efis.initNode("fixed-temp",0);
565 m.alt_meters = m.efis.initNode("inputs/alt-meters",0,"BOOL");
566 m.fpv = m.efis.initNode("inputs/fpv",0,"BOOL");
568 m.mins_mode = m.efis.initNode("inputs/minimums-mode",0,"BOOL");
569 m.mins_mode_txt = m.efis.initNode("minimums-mode-text","RADIO","STRING");
570 m.minimums = m.efis.initNode("minimums",250,"INT");
571 m.mk_minimums = props.globals.getNode("instrumentation/mk-viii/inputs/arinc429/decision-height");
573 # TODO: these are switches, can be unified with switch handling hash above (eventually):
574 m.nd_plan_wpt = m.efis.initNode("inputs/plan-wpt-index", 0, "INT"); # not yet in switches hash
577 # initialize all switches based on the defaults specified in the switch hash
579 foreach(var switch; keys( m.efis_switches ) )
580 props.globals.initNode
581 ( m.get_full_switch_path (switch),
582 m.efis_switches[switch].value,
583 m.efis_switches[switch].type
589 newMFD: func(canvas_group, parent=nil)
591 if (me.inited) die("MFD already was added to scene");
593 #me.listen("/sim/signals/reinit", func(n) me.del() );
594 me.update_timer = maketimer(0.05, func me.update() ); # TODO: make interval configurable via ctor
595 me.nd = canvas_group;
596 me.canvas_handle = parent;
598 # load the specified SVG file into the me.nd group and populate all sub groups
600 canvas.parsesvg(me.nd, me.nd_style.svg_filename, {'font-mapper': me.nd_style.font_mapper});
601 me.symbols = {}; # storage for SVG elements, to avoid namespace pollution (all SVG elements end up here)
603 foreach(var feature; me.nd_style.features ) {
604 # print("Setting up SVG feature:", feature.id);
605 me.symbols[feature.id] = me.nd.getElementById(feature.id).updateCenter();
606 if(contains(feature.impl,'init')) feature.impl.init(me.nd, feature); # call The element's init code (i.e. updateCenter)
609 ### this is the "old" method that's less flexible, we want to use the style hash instead (see above)
610 # because things are much better configurable that way
611 # now look up all required SVG elements and initialize member fields using the same name to have a convenient handle
612 foreach(var element; ["wind","dmeLDist","dmeRDist","dmeL","dmeR","vorL","vorR","vorLId","vorRId",
613 "range","status.wxr","status.wpt","hdgGroup","status.sta","status.arpt"])
614 me.symbols[element] = me.nd.getElementById(element);
616 # load elements from vector image, and create instance variables using identical names, and call updateCenter() on each
617 # anything that needs updatecenter called, should be added to the vector here
619 foreach(var element; ["compassApp","northUp","aplSymMap","aplSymMapCtr","aplSymVor",
620 "staFromL2","staToL2","staFromR2","staToR2",
621 "locPtr","hdgTrk","truMag","altArc","planArcs",
622 "trkInd","compass","HdgBugCRT","TrkBugLCD","HdgBugLCD","selHdgLine","curHdgPtr",
623 "staFromL","staToL","staFromR","staToR"] )
624 me.symbols[element] = me.nd.getElementById(element).updateCenter();
626 foreach(var element; ["HdgBugCRT2","TrkBugLCD2","HdgBugLCD2","selHdgLine2","curHdgPtr2","vorCrsPtr2"] )
627 me.symbols[element] = me.nd.getElementById(element).setCenter(512,565);
629 # this should probably be using Philosopher's new SymbolLayer ?
630 me.map = me.nd.createChild("map","map")
631 .set("clip", "rect(124, 1024, 1024, 0)")
632 .set("screen-range", "700");
634 # this callback will be passed onto the model via the controller hash, and used for the positioned queries, to specify max query range:
635 var get_range = func me.get_switch('toggle_range');
637 # predicate for the draw controller
638 var is_tuned = func(freq) {
639 var nav1=getprop("instrumentation/nav[0]/frequencies/selected-mhz");
640 var nav2=getprop("instrumentation/nav[1]/frequencies/selected-mhz");
641 if (freq == nav1 or freq == nav2) return 1;
645 # another predicate for the draw controller
646 var get_course_by_freq = func(freq) {
647 if (freq == getprop("instrumentation/nav[0]/frequencies/selected-mhz"))
648 return getprop("instrumentation/nav[0]/radials/selected-deg");
650 return getprop("instrumentation/nav[1]/radials/selected-deg");
653 var get_current_position = func {
654 delete(caller(0)[0], "me"); # remove local me, inherit outer one
656 me.aircraft_source.get_lat(), me.aircraft_source.get_lon()
660 # a hash with controller callbacks, will be passed onto draw routines to customize behavior/appearance
661 # the point being that draw routines don't know anything about their frontends (instrument or GUI dialog)
662 # so we need some simple way to communicate between frontend<->backend until we have real controllers
663 # for now, a single controller hash is shared by most layers - unsupported callbacks are simply ignored by the draw files
666 query_range: func get_range(),
668 get_tuned_course:get_course_by_freq,
669 get_position: get_current_position,
672 # FIXME: MapStructure: big hack
673 canvas.Symbol.Controller.get("VOR").query_range = controller.query_range;
674 canvas.Symbol.Controller.get("VOR").get_tuned_course = controller.get_tuned_course;
675 canvas.Symbol.Controller.get("DME").is_tuned = controller.is_tuned;
676 canvas.SymbolLayer.Controller.get("TFC").query_range = controller.query_range;
677 canvas.SymbolLayer.Controller.get("TFC").get_position = controller.get_position;
680 # set up various layers, controlled via callbacks in the controller hash
681 # revisit this code once Philosopher's "Smart MVC Symbols/Layers" work is committed and integrated
683 # helper / closure generator
684 var make_event_handler = func(predicate, layer) func predicate(me, layer);
686 me.layers={}; # storage container for all ND specific layers
687 # look up all required layers as specified per the NDStyle hash and do the initial setup for event handling
688 foreach(var layer; me.nd_style.layers) {
689 if(layer['disabled']) continue; # skip this layer
690 #print("newMFD(): Setting up ND layer:", layer.name);
691 # huge hack for the alt-arc, which is not rendered as a map group, but directly as part of the toplevel ND group
692 var render_target = (!contains(layer,'not_a_map') or !layer.not_a_map) ? me.map : me.nd;
695 if(!layer['isMapStructure'])
696 the_layer = me.layers[layer.name] = canvas.MAP_LAYERS[layer.name].new( render_target, layer.name, controller );
698 printlog(_MP_dbg_lvl, "Setting up MapStructure-based layer for ND, name:", layer.name);
699 render_target.addLayer(factory: canvas.SymbolLayer, type_arg: layer.name);
700 the_layer = me.layers[layer.name] = render_target.getLayer(layer.name);
703 # now register all layer specific notification listeners and their corresponding update predicate/callback
704 # pass the ND instance and the layer handle to the predicate when it is called
705 # so that it can directly access the ND instance and its own layer (without having to know the layer's name)
706 var event_handler = make_event_handler(layer.predicate, the_layer);
707 foreach(var event; layer.update_on) {
708 # print("Setting up subscription:", event, " for ", layer.name, " handler id:", id(event_handler) );
709 me.listen_switch(event, event_handler);
710 } # foreach event subscription
711 # and now update/init each layer once by calling its update predicate for initialization
715 #print("navdisplay.mfd:ND layer setup completed");
717 # start the update timer, which makes sure that the update() will be called
718 me.update_timer.start();
721 # next, radio & autopilot & listeners
722 # TODO: move this to .init field in layers hash or to model files
723 foreach(var n; var radios = [
724 "instrumentation/nav/frequencies/selected-mhz",
725 "instrumentation/nav[1]/frequencies/selected-mhz"])
726 me.listen(n, func() {
730 # TODO: move this to the route.model
731 me.listen("/autopilot/route-manager/current-wp", func(activeWp) {
732 canvas.updatewp( activeWp.getValue() );
737 in_mode:func(switch, modes)
739 foreach(var m; modes) if(me.get_switch(switch)==m) return 1;
742 # each model should keep track of when it last got updated, using current lat/lon
743 # in update(), we can then check if the aircraft has traveled more than 0.5-1 nm (depending on selected range)
744 # and update each model accordingly
745 update: func() # FIXME: This stuff is still too aircraft specific, cannot easily be reused by other aircraft
748 # important constants
759 # fgcommand('profiler-start');
761 var userHdgMag = me.aircraft_source.get_hdg_mag();
762 var userHdgTru = me.aircraft_source.get_hdg_tru();
763 var userTrkMag = me.aircraft_source.get_trk_mag();
764 var userTrkTru = me.aircraft_source.get_trk_tru();
765 if(me.get_switch('toggle_true_north')) {
766 me.symbols.truMag.setText("TRU");
767 var userHdg=userHdgTru;
768 var userTrk=userTrkTru;
770 me.symbols.truMag.setText("MAG");
771 var userHdg=userHdgMag;
772 var userTrk=userTrkMag;
774 if (me.aircraft_source.get_gnd_spd() < 80)
776 var userLat = me.aircraft_source.get_lat();
777 var userLon = me.aircraft_source.get_lon();
778 var userGndSpd = me.aircraft_source.get_gnd_spd();
779 var userVSpd = me.aircraft_source.get_vspd();
780 var dispLCD = me.get_switch('toggle_display_type') == "LCD";
782 # this should only ever happen when testing the experimental AI/MP ND driver hash (not critical)
783 if (!userHdg or !userTrk or !userLat or !userLon) {
784 print("aircraft source invalid, returning !");
788 if(me.in_mode('toggle_display_mode', ['PLAN']))
789 me.map.setTranslation(512,512);
790 elsif(me.get_switch('toggle_centered'))
791 me.map.setTranslation(512,565);
793 me.map.setTranslation(512,824);
794 # Calculate length in NM of one degree at current location TODO: expose as methods, for external callbacks
795 var userLatR = userLat*D2R;
796 var userLonR = userLon*D2R;
797 var latlen = m1 + (m2 * math.cos(2 * userLatR)) + (m3 * math.cos(4 * userLatR)) + (m4 * math.cos(6 * userLatR));
798 var lonlen = (p1 * math.cos(userLatR)) + (p2 * math.cos(3 * userLatR)) + (p3 * math.cos(5 * userLatR));
799 latNm = latlen*M2NM; #60 at equator
800 lonNm = lonlen*M2NM; #60 at equator
802 me.symbols.wind.setText(sprintf("%3.0f / %2.0f",getprop("/environment/wind-from-heading-deg"),getprop("/environment/wind-speed-kt")));
804 if(me.get_switch('toggle_lh_vor_adf') == 1)
806 me.symbols.vorL.setText("VOR L");
807 me.symbols.vorL.setColor(0.195,0.96,0.097);
808 me.symbols.dmeL.setText("DME");
809 me.symbols.dmeL.setColor(0.195,0.96,0.097);
810 if(getprop("instrumentation/nav/in-range"))
811 me.symbols.vorLId.setText(getprop("instrumentation/nav/nav-id"));
813 me.symbols.vorLId.setText(getprop("instrumentation/nav/frequencies/selected-mhz-fmt"));
814 me.symbols.vorLId.setColor(0.195,0.96,0.097);
815 if(getprop("instrumentation/nav/dme-in-range"))
816 me.symbols.dmeLDist.setText(sprintf("%3.1f",getprop("instrumentation/nav/nav-distance")*0.000539));
817 else me.symbols.dmeLDist.setText(" ---");
818 me.symbols.dmeLDist.setColor(0.195,0.96,0.097);
819 } elsif(me.get_switch('toggle_lh_vor_adf') == -1) {
820 me.symbols.vorL.setText("ADF L");
821 me.symbols.vorL.setColor(0,0.6,0.85);
822 me.symbols.dmeL.setText("");
823 me.symbols.dmeL.setColor(0,0.6,0.85);
824 if((var navident=getprop("instrumentation/adf/ident")) != "")
825 me.symbols.vorLId.setText(navident);
826 else me.symbols.vorLId.setText(sprintf("%3d",getprop("instrumentation/adf/frequencies/selected-khz")));
827 me.symbols.vorLId.setColor(0,0.6,0.85);
828 me.symbols.dmeLDist.setText("");
829 me.symbols.dmeLDist.setColor(0,0.6,0.85);
831 me.symbols.vorL.setText("");
832 me.symbols.dmeL.setText("");
833 me.symbols.vorLId.setText("");
834 me.symbols.dmeLDist.setText("");
836 if(me.get_switch('toggle_rh_vor_adf') == 1) {
837 me.symbols.vorR.setText("VOR R");
838 me.symbols.vorR.setColor(0.195,0.96,0.097);
839 me.symbols.dmeR.setText("DME");
840 me.symbols.dmeR.setColor(0.195,0.96,0.097);
841 if(getprop("instrumentation/nav[1]/in-range"))
842 me.symbols.vorRId.setText(getprop("instrumentation/nav[1]/nav-id"));
844 me.symbols.vorRId.setText(getprop("instrumentation/nav[1]/frequencies/selected-mhz-fmt"));
845 me.symbols.vorRId.setColor(0.195,0.96,0.097);
846 if(getprop("instrumentation/nav[1]/dme-in-range"))
847 me.symbols.dmeRDist.setText(sprintf("%3.1f",getprop("instrumentation/nav[1]/nav-distance")*0.000539));
848 else me.symbols.dmeRDist.setText(" ---");
849 me.symbols.dmeRDist.setColor(0.195,0.96,0.097);
850 } elsif(me.get_switch('toggle_rh_vor_adf') == -1) {
851 me.symbols.vorR.setText("ADF R");
852 me.symbols.vorR.setColor(0,0.6,0.85);
853 me.symbols.dmeR.setText("");
854 me.symbols.dmeR.setColor(0,0.6,0.85);
855 if((var navident=getprop("instrumentation/adf[1]/ident")) != "")
856 me.symbols.vorRId.setText(navident);
857 else me.symbols.vorRId.setText(sprintf("%3d",getprop("instrumentation/adf[1]/frequencies/selected-khz")));
858 me.symbols.vorRId.setColor(0,0.6,0.85);
859 me.symbols.dmeRDist.setText("");
860 me.symbols.dmeRDist.setColor(0,0.6,0.85);
862 me.symbols.vorR.setText("");
863 me.symbols.dmeR.setText("");
864 me.symbols.vorRId.setText("");
865 me.symbols.dmeRDist.setText("");
868 me.symbols.range.setText(sprintf("%3.0f",me.rangeNm()/2));
870 # reposition the map, change heading & range:
871 if(me.in_mode('toggle_display_mode', ['PLAN'])) {
872 me.map._node.getNode("hdg",1).setDoubleValue(0);
873 if (getprop(me.efis_path ~ "/inputs/plan-wpt-index") >= 0) {
874 me.map._node.getNode("ref-lat",1).setDoubleValue(getprop("/autopilot/route-manager/route/wp["~getprop(me.efis_path ~ "/inputs/plan-wpt-index")~"]/latitude-deg"));
875 me.map._node.getNode("ref-lon",1).setDoubleValue(getprop("/autopilot/route-manager/route/wp["~getprop(me.efis_path ~ "/inputs/plan-wpt-index")~"]/longitude-deg"));
878 me.map._node.getNode("ref-lat",1).setDoubleValue(userLat);
879 me.map._node.getNode("ref-lon",1).setDoubleValue(userLon);
881 # The set range of the map does not correspond to what we see in-sim!!
882 me.map._node.getNode("range",1).setDoubleValue(me.rangeNm()); # avoid this here, use a listener instead
884 # Hide heading bug 10 secs after change
885 var vhdg_bug = getprop("autopilot/settings/heading-bug-deg");
886 var hdg_bug_active = getprop("autopilot/settings/heading-bug-active");
887 if (hdg_bug_active == nil)
890 if(me.in_mode('toggle_display_mode', ['MAP'])) {
891 me.symbols.HdgBugCRT.setRotation((vhdg_bug-userTrk)*D2R);
892 me.symbols.HdgBugLCD.setRotation((vhdg_bug-userTrk)*D2R);
893 me.symbols.TrkBugLCD.setRotation((vhdg_bug-userTrk)*D2R);
894 me.symbols.selHdgLine.setRotation((vhdg_bug-userTrk)*D2R);
895 me.symbols.HdgBugCRT2.setRotation((vhdg_bug-userTrk)*D2R);
896 me.symbols.TrkBugLCD2.setRotation((vhdg_bug-userTrk)*D2R);
897 me.symbols.selHdgLine2.setRotation((vhdg_bug-userTrk)*D2R);
898 me.symbols.trkInd.setRotation(0);
899 me.symbols.curHdgPtr.setRotation((userHdg-userTrk)*D2R);
900 me.symbols.curHdgPtr2.setRotation((userHdg-userTrk)*D2R);
901 me.map._node.getNode("hdg",1).setDoubleValue(userTrkTru);
902 me.symbols.compass.setRotation(-userTrk*D2R);
903 me.symbols.compassApp.setRotation(-userTrk*D2R);
904 me.symbols.hdgTrk.setText("TRK");
906 if(me.in_mode('toggle_display_mode', ['APP','VOR'])) {
907 me.symbols.HdgBugCRT.setRotation((vhdg_bug-userHdg)*D2R);
908 me.symbols.HdgBugLCD.setRotation((vhdg_bug-userHdg)*D2R);
909 me.symbols.selHdgLine.setRotation((vhdg_bug-userHdg)*D2R);
910 me.symbols.HdgBugCRT2.setRotation((vhdg_bug-userHdg)*D2R);
911 me.symbols.HdgBugLCD2.setRotation((vhdg_bug-userHdg)*D2R);
912 me.symbols.selHdgLine2.setRotation((vhdg_bug-userHdg)*D2R);
913 me.symbols.trkInd.setRotation((userTrk-userHdg)*D2R);
914 me.symbols.curHdgPtr.setRotation(0);
915 me.symbols.curHdgPtr2.setRotation(0);
916 me.map._node.getNode("hdg",1).setDoubleValue(userHdgTru);
917 me.symbols.compass.setRotation(-userHdg*D2R);
918 me.symbols.compassApp.setRotation(-userHdg*D2R);
919 me.symbols.hdgTrk.setText("HDG");
921 if(me.get_switch('toggle_centered')) {
922 if (me.in_mode('toggle_display_mode', ['APP','VOR'])) {
923 me.symbols.vorCrsPtr2.show();
924 me.symbols.compassApp.show();
925 if(getprop("instrumentation/nav/in-range")) {
926 var deflection = getprop("instrumentation/nav/heading-needle-deflection-norm");
927 me.symbols.locPtr.show();
928 me.symbols.locPtr.setTranslation(deflection*150,0);
929 if(abs(deflection < 0.99))
930 me.symbols.locPtr.setColorFill(1,0,1,1);
932 me.symbols.locPtr.setColorFill(1,0,1,0);
934 me.symbols.locPtr.hide();
936 me.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-userHdg)*D2R);
937 me.symbols.hdgGroup.setTranslation(0,100);
939 me.symbols.vorCrsPtr2.hide();
940 me.symbols.hdgGroup.setTranslation(0,100*me.in_mode('toggle_display_mode', ['MAP']));
941 me.symbols.compassApp.setVisible(me.in_mode('toggle_display_mode', ['MAP']));
944 me.symbols.vorCrsPtr2.hide();
945 me.symbols.hdgGroup.setTranslation(0,0);
946 me.symbols.compassApp.hide();
949 if ((me.get_switch('toggle_centered') and !me.in_mode('toggle_display_mode', ['PLAN'])) or me.in_mode('toggle_display_mode', ['PLAN'])) {
950 me.symbols.compass.hide();
952 me.symbols.compass.show();
955 var staPtrVis = !me.in_mode('toggle_display_mode', ['APP','PLAN']);
956 var magVar = getprop("environment/magnetic-variation-deg");
957 if(me.in_mode('toggle_display_mode', ['APP','MAP','VOR','PLAN']))
959 if(getprop("instrumentation/nav/heading-deg") != nil)
960 var nav0hdg=getprop("instrumentation/nav/heading-deg") - userHdg - magVar;
961 if(getprop("instrumentation/nav[1]/heading-deg") != nil)
962 var nav1hdg=getprop("instrumentation/nav[1]/heading-deg") - userHdg - magVar;
963 var adf0hdg=getprop("instrumentation/adf/indicated-bearing-deg");
964 var adf1hdg=getprop("instrumentation/adf[1]/indicated-bearing-deg");
965 if(!me.get_switch('toggle_centered'))
967 if(me.in_mode('toggle_display_mode', ['PLAN']))
968 me.symbols.trkInd.hide();
970 me.symbols.trkInd.show();
971 if((getprop("instrumentation/nav/in-range") and me.get_switch('toggle_lh_vor_adf') == 1)) {
972 me.symbols.staFromL.setVisible(staPtrVis);
973 me.symbols.staToL.setVisible(staPtrVis);
974 me.symbols.staFromL.setColor(0.195,0.96,0.097);
975 me.symbols.staToL.setColor(0.195,0.96,0.097);
976 me.symbols.staFromL.setRotation((nav0hdg+180)*D2R);
977 me.symbols.staToL.setRotation(nav0hdg*D2R);
979 elsif(getprop("instrumentation/adf/in-range") and (me.get_switch('toggle_lh_vor_adf') == -1)) {
980 me.symbols.staFromL.setVisible(staPtrVis);
981 me.symbols.staToL.setVisible(staPtrVis);
982 me.symbols.staFromL.setColor(0,0.6,0.85);
983 me.symbols.staToL.setColor(0,0.6,0.85);
984 me.symbols.staFromL.setRotation((adf0hdg+180)*D2R);
985 me.symbols.staToL.setRotation(adf0hdg*D2R);
987 me.symbols.staFromL.hide();
988 me.symbols.staToL.hide();
990 if((getprop("instrumentation/nav[1]/in-range") and me.get_switch('toggle_rh_vor_adf') == 1)) {
991 me.symbols.staFromR.setVisible(staPtrVis);
992 me.symbols.staToR.setVisible(staPtrVis);
993 me.symbols.staFromR.setColor(0.195,0.96,0.097);
994 me.symbols.staToR.setColor(0.195,0.96,0.097);
995 me.symbols.staFromR.setRotation((nav1hdg+180)*D2R);
996 me.symbols.staToR.setRotation(nav1hdg*D2R);
997 } elsif(getprop("instrumentation/adf[1]/in-range") and (me.get_switch('toggle_rh_vor_adf') == -1)) {
998 me.symbols.staFromR.setVisible(staPtrVis);
999 me.symbols.staToR.setVisible(staPtrVis);
1000 me.symbols.staFromR.setColor(0,0.6,0.85);
1001 me.symbols.staToR.setColor(0,0.6,0.85);
1002 me.symbols.staFromR.setRotation((adf1hdg+180)*D2R);
1003 me.symbols.staToR.setRotation(adf1hdg*D2R);
1005 me.symbols.staFromR.hide();
1006 me.symbols.staToR.hide();
1008 me.symbols.staFromL2.hide();
1009 me.symbols.staToL2.hide();
1010 me.symbols.staFromR2.hide();
1011 me.symbols.staToR2.hide();
1012 me.symbols.curHdgPtr2.hide();
1013 me.symbols.HdgBugCRT2.hide();
1014 me.symbols.TrkBugLCD2.hide();
1015 me.symbols.HdgBugLCD2.hide();
1016 me.symbols.selHdgLine2.hide();
1017 me.symbols.curHdgPtr.setVisible(staPtrVis);
1018 me.symbols.TrkBugLCD.hide();
1019 me.symbols.HdgBugCRT.setVisible(staPtrVis and !dispLCD);
1020 me.symbols.HdgBugLCD.setVisible(staPtrVis and dispLCD);
1021 me.symbols.selHdgLine.setVisible(staPtrVis and hdg_bug_active);
1023 me.symbols.trkInd.hide();
1024 if((getprop("instrumentation/nav/in-range") and me.get_switch('toggle_lh_vor_adf') == 1)) {
1025 me.symbols.staFromL2.setVisible(staPtrVis);
1026 me.symbols.staToL2.setVisible(staPtrVis);
1027 me.symbols.staFromL2.setColor(0.195,0.96,0.097);
1028 me.symbols.staToL2.setColor(0.195,0.96,0.097);
1029 me.symbols.staFromL2.setRotation((nav0hdg+180)*D2R);
1030 me.symbols.staToL2.setRotation(nav0hdg*D2R);
1031 } elsif(getprop("instrumentation/adf/in-range") and (me.get_switch('toggle_lh_vor_adf') == -1)) {
1032 me.symbols.staFromL2.setVisible(staPtrVis);
1033 me.symbols.staToL2.setVisible(staPtrVis);
1034 me.symbols.staFromL2.setColor(0,0.6,0.85);
1035 me.symbols.staToL2.setColor(0,0.6,0.85);
1036 me.symbols.staFromL2.setRotation((adf0hdg+180)*D2R);
1037 me.symbols.staToL2.setRotation(adf0hdg*D2R);
1039 me.symbols.staFromL2.hide();
1040 me.symbols.staToL2.hide();
1042 if((getprop("instrumentation/nav[1]/in-range") and me.get_switch('toggle_rh_vor_adf') == 1)) {
1043 me.symbols.staFromR2.setVisible(staPtrVis);
1044 me.symbols.staToR2.setVisible(staPtrVis);
1045 me.symbols.staFromR2.setColor(0.195,0.96,0.097);
1046 me.symbols.staToR2.setColor(0.195,0.96,0.097);
1047 me.symbols.staFromR2.setRotation((nav1hdg+180)*D2R);
1048 me.symbols.staToR2.setRotation(nav1hdg*D2R);
1049 } elsif(getprop("instrumentation/adf[1]/in-range") and (me.get_switch('toggle_rh_vor_adf') == -1)) {
1050 me.symbols.staFromR2.setVisible(staPtrVis);
1051 me.symbols.staToR2.setVisible(staPtrVis);
1052 me.symbols.staFromR2.setColor(0,0.6,0.85);
1053 me.symbols.staToR2.setColor(0,0.6,0.85);
1054 me.symbols.staFromR2.setRotation((adf1hdg+180)*D2R);
1055 me.symbols.staToR2.setRotation(adf1hdg*D2R);
1057 me.symbols.staFromR2.hide();
1058 me.symbols.staToR2.hide();
1060 me.symbols.staFromL.hide();
1061 me.symbols.staToL.hide();
1062 me.symbols.staFromR.hide();
1063 me.symbols.staToR.hide();
1064 me.symbols.curHdgPtr.hide();
1065 me.symbols.HdgBugCRT.hide();
1066 me.symbols.TrkBugLCD.hide();
1067 me.symbols.HdgBugLCD.hide();
1068 me.symbols.selHdgLine.hide();
1069 me.symbols.curHdgPtr2.setVisible(staPtrVis);
1070 me.symbols.TrkBugLCD2.hide();
1071 me.symbols.HdgBugCRT2.setVisible(staPtrVis and !dispLCD);
1072 me.symbols.HdgBugLCD2.setVisible(staPtrVis and dispLCD);
1073 me.symbols.selHdgLine2.setVisible(staPtrVis and hdg_bug_active);
1077 me.symbols.hdgGroup.setVisible(!me.in_mode('toggle_display_mode', ['PLAN']));
1078 me.symbols.northUp.setVisible(me.in_mode('toggle_display_mode', ['PLAN']));
1079 me.symbols.aplSymMap.setVisible(me.in_mode('toggle_display_mode', ['APP','MAP','VOR']) and !me.get_switch('toggle_centered'));
1080 me.symbols.aplSymMapCtr.setVisible(me.in_mode('toggle_display_mode', ['MAP']) and me.get_switch('toggle_centered'));
1081 me.symbols.aplSymVor.setVisible(me.in_mode('toggle_display_mode', ['APP','VOR']) and me.get_switch('toggle_centered'));
1082 me.symbols.planArcs.setVisible(me.in_mode('toggle_display_mode', ['PLAN']));
1084 if (abs(userVSpd) > 5) {
1085 var altDiff = (getprop("autopilot/settings/target-altitude-ft") or 0)-(getprop("instrumentation/altimeter/indicated-altitude-ft") or 0);
1086 if (abs(altDiff) > 50 and altDiff/userVSpd > 0) {
1087 var altRangeNm = altDiff/userVSpd*userGndSpd*KT2MPS*M2NM;
1088 if(altRangeNm > 1) {
1089 var altRangePx = (350/me.rangeNm())*altRangeNm;
1090 if (altRangePx > 700)
1092 me.symbols.altArc.setTranslation(0,-altRangePx);
1094 me.symbols.altArc.show();
1096 me.symbols.altArc.hide();
1098 me.symbols.altArc.hide();
1101 ## run all predicates in the NDStyle hash and evaluate their true/false behavior callbacks
1102 ## this is in line with the original design, but normally we don't need to getprop/poll here,
1103 ## using listeners or timers would be more canvas-friendly whenever possible
1104 ## because running setprop() on any group/canvas element at framerate means that the canvas
1105 ## will be updated at frame rate too - wasteful ... (check the performance monitor!)
1107 foreach(var feature; me.nd_style.features ) {
1108 # for stuff that always needs to be updated
1109 if (contains(feature.impl, 'common')) feature.impl.common(me);
1111 if(!contains(feature.impl, 'predicate')) continue; # no conditional stuff
1112 if ( var result=feature.impl.predicate(me) )
1113 feature.impl.is_true(me, result); # pass the result to the predicate
1115 feature.impl.is_false( me, result ); # pass the result to the predicate
1118 ## update the status flags shown on the ND (wxr, wpt, arpt, sta)
1119 # this could/should be using listeners instead ...
1120 me.symbols['status.wxr'].setVisible( me.get_switch('toggle_weather') and me.in_mode('toggle_display_mode', ['MAP']));
1121 me.symbols['status.wpt'].setVisible( me.get_switch('toggle_waypoints') and me.in_mode('toggle_display_mode', ['MAP']));
1122 me.symbols['status.arpt'].setVisible( me.get_switch('toggle_airports') and me.in_mode('toggle_display_mode', ['MAP']));
1123 me.symbols['status.sta'].setVisible( me.get_switch('toggle_stations') and me.in_mode('toggle_display_mode', ['MAP']));