1
0
Fork 0

Merge branch 'master' of gitorious.org:fg/fgdata

This commit is contained in:
BARANGER Emmanuel 2014-01-10 14:47:37 +01:00
commit bd96739dce
21 changed files with 1086 additions and 558 deletions

View file

@ -1,3 +1,123 @@
var dump_obj = func(m) {
var h = {};
foreach (var k; keys(m))
if (k != "parents")
h[k] = m[k];
debug.dump(h);
};
##
# must be either of:
# 1) draw* callback, 2) SVG filename, 3) Drawable class (with styling/LOD support)
var SymbolDrawable = {
new: func() {
},
};
## wrapper for each element
## i.e. keeps the canvas and texture map coordinates
var CachedElement = {
new: func(canvas_path, name, source, offset) {
var m = {parents:[CachedElement] };
m.canvas_src = canvas_path;
m.name = name;
m.source = source;
m.offset = offset;
return m;
}, # new()
render: func(group) {
# create a raster image child in the render target/group
return
group.createChild("image", me.name)
.setFile( me.canvas_src )
# TODO: fix .setSourceRect() to accept a single vector for coordinates ...
.setSourceRect(left:me.source[0],top:me.source[1],right:me.source[2],bottom:me.source[3] , normalized:0)
.setTranslation(me.offset); # FIXME: make sure this stays like this and isn't overridden
}, # render()
}; # of CachedElement
var SymbolCache = {
# We can draw symbols either with left/top, centered,
# or right/bottom alignment. Specify two in a vector
# to mix and match, e.g. left/centered would be
# [SymbolCache.DRAW_LEFT_TOP,SymbolCache.DRAW_CENTERED]
DRAW_LEFT_TOP: 0.0,
DRAW_CENTERED: 0.5,
DRAW_RIGHT_BOTTOM: 1.0,
new: func(dim...) {
var m = { parents:[SymbolCache] };
# to keep track of the next free caching spot (in px)
m.next_free = [0, 0];
# to store each type of symbol
m.dict = {};
if (size(dim) == 1 and typeof(dim[0]) == 'vector')
dim = dim[0];
# Two sizes: canvas and symbol
if (size(dim) == 2) {
var canvas_x = var canvas_y = dim[0];
var image_x = var image_y = dim[1];
# Two widths (canvas and symbol) and then height/width ratio
} else if (size(dim) == 3) {
var (canvas_x,image_x,ratio) = dim;
var canvas_y = canvas_x * ratio;
var image_y = image_x * ratio;
# Explicit canvas and symbol widths/heights
} else if (size(dim) == 4) {
var (canvas_x,canvas_y,image_x,image_y) = dim;
}
m.canvas_sz = [canvas_x, canvas_y];
m.image_sz = [image_x, image_y];
# allocate a canvas
m.canvas_texture = canvas.new( {
"name": "SymbolCache"~canvas_x~'x'~canvas_y,
"size": m.canvas_sz,
"view": m.canvas_sz,
"mipmapping": 1
});
# add a placement
m.canvas_texture.addPlacement( {"type": "ref"} );
return m;
},
add: func(name, callback, draw_mode=0) {
if (typeof(draw_mode) == 'scalar')
var draw_mode0 = var draw_mode1 = draw_mode;
else var (draw_mode0,draw_mode1) = draw_mode;
# get canvas texture that we use as cache
# get next free spot in texture (column/row)
# run the draw callback and render into a group
var gr = me.canvas_texture.createGroup();
gr.setTranslation( me.next_free[0] + me.image_sz[0]*draw_mode0,
me.next_free[1] + me.image_sz[1]*draw_mode1);
#settimer(func debug.dump ( gr.getTransformedBounds() ), 0); # XXX: these are only updated when rendered
#debug.dump ( gr.getTransformedBounds() );
gr.update(); # apparently this doesn't result in sane output from .getTransformedBounds() either
#debug.dump ( gr.getTransformedBounds() );
# draw the symbol
callback(gr);
# get the bounding box, i.e. coordinates for texture map, or use the .setTranslation() params
var coords = me.next_free~me.next_free;
foreach (var i; [0,1])
coords[i+2] += me.image_sz[i];
# get the offset we used to position correctly in the bounds of the canvas
var offset = [me.image_sz[0]*draw_mode0, me.image_sz[1]*draw_mode1];
# store texture map coordinates in lookup map using the name as identifier
me.dict[name] = CachedElement.new(me.canvas_texture.getPath(), name, coords, offset );
# update next free position in cache (column/row)
me.next_free[0] += me.image_sz[0];
if (me.next_free[0] >= me.canvas_sz[0])
{ me.next_free[0] = 0; me.next_free[1] += me.image_sz[1] }
if (me.next_free[1] >= me.canvas_sz[1])
die("SymbolCache: ran out of space after adding '"~name~"'");
}, # add()
get: func(name) {
if(!contains(me.dict,name)) die("No SymbolCache entry for key:"~ name);
return me.dict[name];
}, # get()
};
var Symbol = { var Symbol = {
# Static/singleton: # Static/singleton:
registry: {}, registry: {},
@ -29,7 +149,7 @@ Symbol.Controller = {
# Static/singleton: # Static/singleton:
registry: {}, registry: {},
add: func(type, class) add: func(type, class)
registry[type] = class, me.registry[type] = class,
get: func(type) get: func(type)
if ((var class = me.registry[type]) == nil) if ((var class = me.registry[type]) == nil)
die("unknown type '"~type~"'"); die("unknown type '"~type~"'");
@ -58,13 +178,17 @@ var getpos_fromghost = func(positioned_g)
# (geo.Coord and positioned ghost currently) # (geo.Coord and positioned ghost currently)
Symbol.Controller.getpos = func(obj) { Symbol.Controller.getpos = func(obj) {
if (typeof(obj) == 'ghost') if (typeof(obj) == 'ghost')
if (ghosttype(obj) == 'positioned' or ghosttype(obj) == 'Navaid') if (ghosttype(obj) == 'positioned' or ghosttype(obj) == 'Navaid' or ghosttype(obj)=='Fix' or ghosttype(obj)=='flightplan-leg')
return getpos_fromghost(obj); return getpos_fromghost(obj);
else else
die("bad ghost of type '"~ghosttype(obj)~"'"); die("bad ghost of type '"~ghosttype(obj)~"'");
if (typeof(obj) == 'hash') if (typeof(obj) == 'hash')
if (isa(obj, geo.Coord)) if (isa(obj, geo.Coord))
return obj.latlon(); return obj.latlon();
if (contains(obj,'lat') and contains(obj,'lon'))
return [obj.lat, obj.lon];
debug.dump(obj);
die("no suitable getpos() found! Of type: "~typeof(obj)); die("no suitable getpos() found! Of type: "~typeof(obj));
}; };
@ -82,38 +206,17 @@ var DotSym = {
element_id: nil, element_id: nil,
# Static/singleton: # Static/singleton:
makeinstance: func(name, hash) { makeinstance: func(name, hash) {
assert_ms(hash, if (!isa(hash, DotSym))
"element_type", # type of Canvas element die("OOP error");
#"element_id", # optional Canvas id #assert_ms(hash,
#"init", # initialize routine # "element_type", # type of Canvas element
"draw", # init/update routine # #"element_id", # optional Canvas id
#getpos", # get position from model in [x_units,y_units] (optional) # #"init", # initialize routine
); # "draw", # init/update routine
hash.parents = [DotSym]; # #getpos", # get position from model in [x_units,y_units] (optional)
#);
return Symbol.add(name, hash); return Symbol.add(name, hash);
}, },
readinstance: func(file, name=nil) {
#print(file);
if (name == nil)
var name = split("/", file)[-1];
if (substr(name, size(name)-4) == ".draw")
name = substr(name, 0, size(name)-5);
var code = io.readfile(file);
var code = call(compile, [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;
}
call(code, nil, nil, var hash = { parents:[DotSym] });
me.makeinstance(name, hash);
},
# For the instances returned from makeinstance: # For the instances returned from makeinstance:
# @param group The Canvas group to add this to. # @param group The Canvas group to add this to.
# @param model A correct object (e.g. positioned ghost) as # @param model A correct object (e.g. positioned ghost) as
@ -132,6 +235,10 @@ var DotSym = {
), ),
}; };
if (m.controller != nil) { if (m.controller != nil) {
#print("Creating controller");
temp = m.controller.new(m.model,m);
if (temp != nil)
m.controller = temp;
#print("Initializing controller"); #print("Initializing controller");
m.controller.init(model); m.controller.init(model);
} }
@ -281,93 +388,117 @@ SymbolLayer.Controller = {
die("searchCmd() not implemented for this SymbolLayer.Controller type!"), die("searchCmd() not implemented for this SymbolLayer.Controller type!"),
}; # of SymbolLayer.Controller }; # of SymbolLayer.Controller
settimer(func { var AnimatedLayer = {
Map.Controller = {
# Static/singleton:
registry: {},
add: func(type, class)
me.registry[type] = class,
get: func(type)
if ((var class = me.registry[type]) == nil)
die("unknown type '"~type~"'");
else return class,
# Calls corresonding controller constructor
# @param map The #SymbolMap this controller is responsible for.
new: func(type, layer, arg...)
return call((var class = me.get(type)).new, [map]~arg, class),
}; };
####### LOAD FILES ####### var CompassLayer = {
#print("loading files"); };
(func {
var FG_ROOT = getprop("/sim/fg-root"); var AltitudeArcLayer = {
var load = func(file, name) { };
#print(file);
if (name == nil) load_MapStructure = func {
var name = split("/", file)[-1]; Map.Controller = {
if (substr(name, size(name)-4) == ".draw") # Static/singleton:
name = substr(name, 0, size(name)-5); registry: {},
#print("reading file"); add: func(type, class)
var code = io.readfile(file); me.registry[type] = class,
#print("compiling file"); get: func(type)
# This segfaults for some reason: if ((var class = me.registry[type]) == nil)
#var code = call(compile, [code], var err=[]); die("unknown type '"~type~"'");
var code = call(func compile(code, file), [code], var err=[]); else return class,
if (size(err)) { # Calls corresonding controller constructor
#print("handling error"); # @param map The #SymbolMap this controller is responsible for.
if (substr(err[0], 0, 12) == "Parse error:") { # hack around Nasal feature new: func(type, layer, arg...)
var e = split(" at line ", err[0]); return call((var class = me.get(type)).new, [map]~arg, class),
if (size(e) == 2) };
err[0] = string.join("", [e[0], "\n at ", file, ", line ", e[1], "\n "]);
####### LOAD FILES #######
#print("loading files");
(func {
var FG_ROOT = getprop("/sim/fg-root");
var load = func(file, name) {
#print(file);
if (name == nil)
var name = split("/", file)[-1];
if (substr(name, size(name)-4) == ".draw")
name = substr(name, 0, size(name)-5);
#print("reading file");
var code = io.readfile(file);
#print("compiling file");
# This segfaults for some reason:
#var code = call(compile, [code], var err=[]);
var code = call(func compile(code, file), [code], var err=[]);
if (size(err)) {
#print("handling error");
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;
} }
for (var i = 1; (var c = caller(i)) != nil; i += 1) #print("calling code");
err ~= subvec(c, 2, 2); call(code, nil, nil, var hash = {});
debug.printerror(err); #debug.dump(keys(hash));
return; return hash;
};
var load_deps = func(name) {
load(FG_ROOT~"/Nasal/canvas/map/"~name~".lcontroller", name);
load(FG_ROOT~"/Nasal/canvas/map/"~name~".symbol", name);
load(FG_ROOT~"/Nasal/canvas/map/"~name~".scontroller", name);
} }
#print("calling code");
call(code, nil, nil, var hash = {});
#debug.dump(keys(hash));
return hash;
};
load(FG_ROOT~"/Nasal/canvas/map/VOR.lcontroller", "VOR");
DotSym.readinstance(FG_ROOT~"/Nasal/canvas/map/VOR.symbol", "VOR");
load(FG_ROOT~"/Nasal/canvas/map/VOR.scontroller", "VOR");
load(FG_ROOT~"/Nasal/canvas/map/aircraftpos.controller", "VOR");
})();
#print("finished loading files");
####### TEST SYMBOL #######
if (0) foreach( var name; ['VOR','FIX','NDB','DME','WPT'] )
settimer(func { load_deps( name );
if (caller(0)[0] != globals.canvas) load(FG_ROOT~"/Nasal/canvas/map/aircraftpos.controller", name);
return call(caller(0)[1], arg, nil, globals.canvas);
print("Running MapStructure test code"); ###
var TestCanvas = canvas.new({ # set up a cache for 32x32 symbols
"name": "Map Test", var SymbolCache32x32 = SymbolCache.new(1024,32);
"size": [1024, 1024],
"view": [1024, 1024],
"mipmapping": 1
});
var dlg = canvas.Window.new([400, 400], "dialog");
dlg.setCanvas(TestCanvas);
var TestMap = TestCanvas.createGroup().createChild("map"); # we should not directly use a canvas here, but instead a LayeredMap.new()
TestMap.addLayer(factory: SymbolLayer, type_arg: "VOR"); # the ID should be also exposed in the property tree for each group (layer), i.e. better debugging
# Center the map's origin:
TestMap.setTranslation(512,512); # FIXME: don't hardcode these values, but read in canvas texture dimensions, otherwise it will break once someone uses non 1024x1024 textures ...
# Initialize a range (TODO: LayeredMap.Controller):
TestMap.set("range", 100);
# Little cursor of current position:
TestMap.createChild("path").rect(-5,-5,10,10).setColorFill(1,1,1).setColor(0,1,0);
# And make it move with our aircraft:
TestMap.setController("Aircraft position"); # from aircraftpos.controller
dlg.del = func() {
TestMap.del();
# call inherited 'del'
delete(me, "del");
me.del();
};
}, 1);
}, 0); # end ugly module init timer hack
var drawVOR = func(color, width=3) return func(group) {
# print("drawing vor");
var bbox = group.createChild("path")
.moveTo(-15,0)
.lineTo(-7.5,12.5)
.lineTo(7.5,12.5)
.lineTo(15,0)
.lineTo(7.5,-12.5)
.lineTo(-7.5,-12.5)
.close()
.setStrokeLineWidth(width)
.setColor( color );
# debug.dump( bbox.getBoundingBox() );
};
var cachedVOR1 = SymbolCache32x32.add( "VOR-BLUE", drawVOR( color:[0, 0.6, 0.85], width:3), SymbolCache.DRAW_CENTERED );
var cachedVOR2 = SymbolCache32x32.add( "VOR-RED" , drawVOR( color:[1.0, 0, 0], width: 3), SymbolCache.DRAW_CENTERED );
var cachedVOR3 = SymbolCache32x32.add( "VOR-GREEN" , drawVOR( color:[0, 1, 0], width: 3), SymbolCache.DRAW_CENTERED );
var cachedVOR4 = SymbolCache32x32.add( "VOR-WHITE" , drawVOR( color:[1, 1, 1], width: 3), SymbolCache.DRAW_CENTERED );
# STRESS TEST
if (0) {
for(var i=0;i <= 1024/32*4 - 4; i+=1)
SymbolCache32x32.add( "VOR-YELLOW"~i , drawVOR( color:[1, 1, 0], width: 3) );
var dlg = canvas.Window.new([640,320],"dialog");
var my_canvas = dlg.createCanvas().setColorBackground(1,1,1,1);
var root = my_canvas.createGroup();
SymbolCache32x32.get(name:"VOR-BLUE").render( group: root ).setGeoPosition(getprop("/position/latitude-deg"),getprop("/position/longitude-deg"));
}
})();
#print("finished loading files");
####### TEST SYMBOL #######
canvas.load_MapStructure = func;
}; # load_MapStructure
setlistener("/nasal/canvas/loaded", load_MapStructure); # end ugly module init listener hack

View file

@ -462,47 +462,45 @@ var Map = {
addLayer: func(factory, type_arg=nil, priority=nil) addLayer: func(factory, type_arg=nil, priority=nil)
{ {
if (!contains(me, "layers")) if (!contains(me, "layers"))
me.layers = []; me.layers = {};
if(contains(me.layers, type_arg))
print("addLayer() warning: overwriting existing layer:", type_arg);
# print("addLayer():", type_arg);
# Argument handling # Argument handling
if (type_arg != nil) if (type_arg != nil)
var type = factory.get(type_arg); var type = factory.get(type_arg);
else var type = factory; else var type = factory;
me.layers[type_arg]= type.new(me);
if (priority == nil) if (priority == nil)
priority = type.df_priority; priority = type.df_priority;
append(me.layers, [type.new(me), priority]);
if (priority != nil) if (priority != nil)
me._sort_priority(); me.layers[type_arg].setInt("z-index", priority);
return me; return me;
}, },
setPos: func(lat,lon,hdg=nil) getLayer: func(type_arg) me.layers[type_arg],
setPos: func(lat, lon, hdg=nil, range=nil)
{ {
me.set("ref-lat", lat); me.set("ref-lat", lat);
me.set("ref-lon", lon); me.set("ref-lon", lon);
if (hdg != nil) if (hdg != nil)
me.set("hdg", hdg); me.set("hdg", hdg);
if (range != nil)
# me.map.set("range", 100); me.set("range", range);
}, },
# Update each layer on this Map. Called by # Update each layer on this Map. Called by
# me.controller. # me.controller.
update: func update: func
{ {
foreach (var l; me.layers) foreach (var l; keys(me.layers)) {
call(l[0].update, arg, l[0]); var layer = me.layers[l];
call(layer.update, arg, layer);
}
return me; return me;
}, },
# private:
_sort_priority: func()
{
me.layers = sort(me.layers, me._sort_cmp);
forindex (var i; me.layers)
me.layers[i].set("z-index", i);
},
_sort_cmp: func(a,b) {
a[1] != b[1] and a[1] != nil and b[1] != nil and (a[1] < b[1] ? -1 : 1)
},
}; };
# Text # Text

View file

@ -429,10 +429,16 @@ var files_with = func(ext) {
} }
return results; return results;
} }
foreach(var ext; var extensions = ['.draw','.model','.layer'])
load_modules(files_with(ext));
setlistener("/nasal/canvas/loaded", func {
foreach(var ext; var extensions = ['.draw','.model','.layer'])
load_modules(files_with(ext));
if (contains(canvas,"load_MapStructure"))
load_MapStructure();
# canvas.MFD = {EFIS:}; # where we'll be storing all MFDs
# TODO: should be inside a separate subfolder, i.e. canvas/map/mfd
load_modules( files_with('.mfd'), 'canvas' );
});
# canvas.MFD = {EFIS:}; # where we'll be storing all MFDs
# TODO: should be inside a separate subfolder, i.e. canvas/map/mfd
load_modules( files_with('.mfd'), 'canvas' );

View file

@ -0,0 +1,32 @@
# Class things:
var name = 'DME';
var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
});
var a_instance = nil;
var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
listeners: [],
query_range_nm: 25,
query_type:'dme',
};
__self__.a_instance = m;
return m;
};
var del = func() {
foreach (var l; me.listeners)
removelistener(l);
};
var searchCmd = func {
#print("Running query:", me.query_type);
return positioned.findWithinRange(me.query_range_nm, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch
};

View file

@ -0,0 +1,12 @@
# Class things:
var name = 'DME';
var parents = [Symbol.Controller];
var __self__ = caller(0)[0];
Symbol.Controller.add(name, __self__);
Symbol.registry[ name ].df_controller = __self__;
var new = func(model) ; # this controller doesn't need an instance
var LayerController = SymbolLayer.Controller.registry[ name ];
var isActive = func(model) LayerController.a_instance.isActive(model);
var is_tuned = func()
die( name~".scontroller.is_tuned /MUST/ be provided by implementation" );

View file

@ -0,0 +1,35 @@
# Class things:
var name = 'DME';
var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
var element_type = "group"; # we want a group, becomes "me.element"
var icon_dme = nil;
var draw = func {
# Init
if (me.icon_dme == nil) {
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(3);
}
if (me.controller != nil and me.controller.is_tuned(me.model.frequency/100))
me.icon_dme.setColor(0,1,0);
else
me.icon_dme.setColor(0,0.6,0.85);
};

View file

@ -0,0 +1,33 @@
# Class things:
var name = 'FIX';
var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
});
var a_instance = nil;
var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
listeners: [],
query_range_nm: 25,
query_type:'fix',
};
__self__.a_instance = m;
return m;
};
var del = func() {
#print("VOR.lcontroller.del()");
foreach (var l; me.listeners)
removelistener(l);
};
var searchCmd = func {
#print("Running query:", me.query_type);
return positioned.findWithinRange(me.query_range_nm, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch
};

View file

@ -0,0 +1,12 @@
# Class things:
var name = 'FIX';
var parents = [Symbol.Controller];
var __self__ = caller(0)[0];
Symbol.Controller.add(name, __self__);
Symbol.registry[ name ].df_controller = __self__;
var new = func(model) ; # this controller doesn't need an instance
var LayerController = SymbolLayer.Controller.registry[ name ];
var isActive = func(model) LayerController.a_instance.isActive(model);
var query_range = func()
die( name~".scontroller.query_range /MUST/ be provided by implementation" );

View file

@ -0,0 +1,30 @@
# Class things:
var name = 'FIX';
var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
var element_type = "group"; # we want a group, becomes "me.element"
var icon_fix = nil;
var text_fix = nil;
var draw = func {
if (me.icon_fix != nil) return;
# the fix symbol
me.icon_fix = me.element.createChild("path")
.moveTo(-15,15)
.lineTo(0,-15)
.lineTo(15,15)
.close()
.setStrokeLineWidth(3)
.setColor(0,0.6,0.85)
.setScale(0.5,0.5); # FIXME: do proper LOD handling here - we need to scale according to current texture dimensions vs. original/design dimensions
# the fix label
me.text_fix = me.element.createChild("text")
.setDrawMode( canvas.Text.TEXT )
.setText(me.model.id)
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
.setFontSize(28)
.setTranslation(5,25);
};

View file

@ -0,0 +1,30 @@
# Class things:
var name = 'NDB';
var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
});
var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
listeners: [],
query_range_nm: 25,
query_type:'ndb',
};
return m;
};
var del = func() {
foreach (var l; me.listeners)
removelistener(l);
};
var searchCmd = func {
#print("Running query:", me.query_type);
return positioned.findWithinRange(me.query_range_nm, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch
};

View file

@ -0,0 +1,12 @@
# Class things:
var name = 'NDB';
var parents = [Symbol.Controller];
var __self__ = caller(0)[0];
Symbol.Controller.add(name, __self__);
Symbol.registry[ name ].df_controller = __self__;
var new = func(model) ; # this controller doesn't need an instance
var LayerController = SymbolLayer.Controller.registry[ name ];
var isActive = func(model) LayerController.a_instance.isActive(model);
var query_range = func()
die( name~".scontroller.query_range /MUST/ be provided by implementation" );

View file

@ -0,0 +1,18 @@
# Class things:
var name = 'NDB';
var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
var element_type = "group"; # we want a group, becomes "me.element", which we parse a SVG onto
var svg_path = "/gui/dialogs/images/ndb_symbol.svg"; # speaking of path, this is our path to use
var local_svg_path = nil; # track changes in the SVG's path
var draw = func {
if (me.svg_path == me.local_svg_path) return;
me.element.removeAllChildren();
me.local_svg_path = me.svg_path;
canvas.parsesvg(me.element, me.svg_path);
me.inited = 1;
};

View file

@ -7,6 +7,7 @@ SymbolLayer.add("VOR", {
type: "VOR", # Symbol type type: "VOR", # Symbol type
df_controller: __self__, # controller to use by default -- this one df_controller: __self__, # controller to use by default -- this one
}); });
var a_instance = nil;
var new = func(layer) { var new = func(layer) {
var m = { var m = {
parents: [__self__], parents: [__self__],
@ -14,6 +15,7 @@ var new = func(layer) {
active_vors: [], active_vors: [],
navNs: props.globals.getNode("instrumentation").getChildren("nav"), navNs: props.globals.getNode("instrumentation").getChildren("nav"),
listeners: [], listeners: [],
query_type:'vor',
}; };
setsize(m.active_vors, size(m.navNs)); setsize(m.active_vors, size(m.navNs));
foreach (var navN; m.navNs) { foreach (var navN; m.navNs) {
@ -24,6 +26,7 @@ var new = func(layer) {
} }
#call(debug.dump, keys(layer)); #call(debug.dump, keys(layer));
m.changed_freq(update:0); m.changed_freq(update:0);
__self__.a_instance = m;
return m; return m;
}; };
var del = func() { var del = func() {
@ -46,7 +49,7 @@ var changed_freq = func(update=1) {
if (update) me.layer.update(); if (update) me.layer.update();
}; };
var searchCmd = func { var searchCmd = func {
#print("Run query"); #print("Running query:", me.query_type);
return positioned.findWithinRange(100, 'vor'); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch return positioned.findWithinRange(100, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch
}; };

View file

@ -1,10 +1,11 @@
# Class things: # Class things:
var parents = [Symbol.Controller]; var parents = [Symbol.Controller];
var __self__ = caller(0)[0]; var __self__ = caller(0)[0];
Symbol.Controller.add("VOR", __self__);
Symbol.registry["VOR"].df_controller = __self__; Symbol.registry["VOR"].df_controller = __self__;
var new = func(model) ; # this controller doesn't need an instance var new = func(model) ; # this controller doesn't need an instance
var LayerController = SymbolLayer.registry["VOR"]; var LayerController = SymbolLayer.Controller.registry["VOR"];
var isActive = func(model) LayerController.isActive(model); var isActive = func(model) LayerController.a_instance.isActive(model);
var query_range = func() var query_range = func()
die("VOR.scontroller.query_range /MUST/ be provided by implementation"); die("VOR.scontroller.query_range /MUST/ be provided by implementation");

View file

@ -1,52 +1,57 @@
# Read by the DotSym.readinstance; each variable becomes a derived class's member/method # Class things:
var name = 'VOR';
var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
var element_type = "group"; # we want a group, becomes "me.element" var element_type = "group"; # we want a group, becomes "me.element"
var inited = 0; # this allows us to track whether draw() is an init() or an update() var icon_vor = nil;
var range_vor = nil; # two elements that get drawn when needed var range_vor = nil; # two elements that get drawn when needed
var radial_vor = nil; # if one is nil, the other has to be nil var radial_vor = nil; # if one is nil, the other has to be nil
var draw = func { var draw = func {
if (me.inited) { # Init
# Update if (me.icon_vor == nil) {
if (me.controller.isActive(me.model)) { me.icon_vor = me.element.createChild("path")
if (me.range_vor == nil) { .moveTo(-15,0)
var rangeNm = me.controller.query_range(); .lineTo(-7.5,12.5)
# print("VOR is tuned:", me.model.id); .lineTo(7.5,12.5)
var radius = (me.model.range_nm/rangeNm)*345; .lineTo(15,0)
me.range_vor = me.element.createChild("path") .lineTo(7.5,-12.5)
.moveTo(-radius,0) .lineTo(-7.5,-12.5)
.arcSmallCW(radius,radius,0,2*radius,0) .close()
.arcSmallCW(radius,radius,0,-2*radius,0) .setStrokeLineWidth(3)
.setStrokeLineWidth(3) .setColor(0,0.6,0.85);
.setStrokeDashArray([5, 15, 5, 15, 5]) }
.setColor(0,1,0); # Update
if (me.controller.isActive(me.model)) {
if (me.range_vor == nil) {
var rangeNm = me.controller.query_range();
# print("VOR is tuned:", me.model.id);
var radius = (me.model.range_nm/rangeNm)*345;
me.range_vor = me.element.createChild("path")
.moveTo(-radius,0)
.arcSmallCW(radius,radius,0,2*radius,0)
.arcSmallCW(radius,radius,0,-2*radius,0)
.setStrokeLineWidth(3)
.setStrokeDashArray([5, 15, 5, 15, 5])
.setColor(0,1,0);
var course = controller.get_tuned_course(me.model.frequency/100); var course = me.controller.get_tuned_course(me.model.frequency/100);
vor_grp.createChild("path") me.radial_vor = me.element.createChild("path")
.moveTo(0,-radius) .moveTo(0,-radius)
.vert(2*radius) .vert(2*radius)
.setStrokeLineWidth(3) .setStrokeLineWidth(3)
.setStrokeDashArray([15, 5, 15, 5, 15]) .setStrokeDashArray([15, 5, 15, 5, 15])
.setColor(0,1,0) .setColor(0,1,0)
.setRotation(course*D2R); .setRotation(course*D2R);
icon_vor.setColor(0,1,0); me.icon_vor.setColor(0,1,0);
}
me.range_vor.show();
me.radial_vor.show();
} else {
me.range_vor.hide();
me.radial_vor.hide();
} }
} else # Init me.range_vor.show();
me.element.createChild("path") me.radial_vor.show();
.moveTo(-15,0) } elsif (me.range_vor != nil) {
.lineTo(-7.5,12.5) me.range_vor.hide();
.lineTo(7.5,12.5) me.radial_vor.hide();
.lineTo(15,0) }
.lineTo(7.5,-12.5)
.lineTo(-7.5,-12.5)
.close()
.setStrokeLineWidth(3)
.setColor(0,0.6,0.85);
}; };

View file

@ -0,0 +1,39 @@
# Class things:
var name = 'WPT'; # for waypoints
var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
});
var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
listeners: [],
query_range_nm: 25,
query_type:'vor',
};
return m;
};
var del = func() {
#print("VOR.lcontroller.del()");
foreach (var l; me.listeners)
removelistener(l);
};
var searchCmd = func {
#print("Running query: WPT");
var fp = flightplan();
var fpSize = fp.getPlanSize();
var result = [];
for (var i = 1; i <fpSize; i+=1)
append(result, fp.getWP(i).path()[0] );
return result;
# return positioned.findWithinRange(me.query_range_nm, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch
};

View file

@ -0,0 +1,12 @@
# Class things:
var name = 'WPT';
var parents = [Symbol.Controller];
var __self__ = caller(0)[0];
Symbol.Controller.add(name, __self__);
Symbol.registry[ name ].df_controller = __self__;
var new = func(model) ; # this controller doesn't need an instance
var LayerController = SymbolLayer.Controller.registry[ name ];
var isActive = func(model) LayerController.a_instance.isActive(model);
var query_range = func()
die( name~".scontroller.query_range /MUST/ be provided by implementation" );

View file

@ -0,0 +1,34 @@
# Class things:
var name = 'WPT';
var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
var element_type = "group"; # we want a group, becomes "me.element"
var base = nil;
var text_wps = nil;
var draw = func {
if (me.base != nil) return;
me.base = me.element.createChild("path")
.setStrokeLineWidth(3)
.moveTo(0,-25)
.lineTo(-5,-5)
.lineTo(-25,0)
.lineTo(-5,5)
.lineTo(0,25)
.lineTo(5,5)
.lineTo(25,0)
.lineTo(5,-5)
.setColor(1,1,1)
.close();
me.text_wps = wpt_grp.createChild("text")
.setDrawMode( canvas.Text.TEXT )
.setText(name)
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
.setFontSize(28)
.setTranslation(25,35)
.setColor(1,0,1);
};

View file

@ -25,219 +25,277 @@ var trigger_update = func(layer) layer._model.init();
# #
# TODO: move this to an XML config file # TODO: move this to an XML config file
# #
var NDStyles = var NDStyles = {
{ ##
## # this configures the 744 ND to help generalize the NavDisplay class itself
# this configures the Boeing ND to help generalize the NavDisplay class itself 'Boeing': {
'Boeing': { font_mapper: func(family, weight) {
font_mapper: func(family, weight) { if( family == "Liberation Sans" and weight == "normal" )
if( family == "Liberation Sans" and weight == "normal" ) return "LiberationFonts/LiberationSans-Regular.ttf";
return "LiberationFonts/LiberationSans-Regular.ttf"; },
},
# where all the symbols are stored # where all the symbols are stored
# TODO: SVG elements should be renamed to use boeing/airbus prefix # TODO: SVG elements should be renamed to use boeing/airbus prefix
# aircraft developers should all be editing the same ND.svg image # aircraft developers should all be editing the same ND.svg image
# the code can deal with the differences now # the code can deal with the differences now
svg_filename: "Nasal/canvas/map/boeingND.svg", svg_filename: "Nasal/canvas/map/boeingND.svg",
##
## this loads and configures existing layers (currently, *.layer files in Nasal/canvas/map)
##
layers: [
{ name:'fixes', disabled:1, update_on:['toggle_range','toggle_waypoints'],
predicate: func(nd, layer) {
# print("Running fixes predicate");
var visible=nd.get_switch('toggle_waypoints') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if (visible) {
# print("fixes update requested!");
trigger_update( layer );
}
layer._view.setVisible(visible);
}, # end of layer update predicate
}, # end of fixes layer
{ name:'FIX', isMapStructure:1, update_on:['toggle_range','toggle_waypoints'],
# 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_waypoints') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
layer.group.setVisible( nd.get_switch('toggle_waypoints') );
if (visible) {
#print("Updating MapStructure ND layer: FIX");
# (Hopefully) smart update
layer.update();
}
}, # end of layer update predicate
}, # end of FIX layer
# Should redraw every 10 seconds
{ name:'storms', update_on:['toggle_range','toggle_weather','toggle_display_mode'],
predicate: func(nd, layer) {
# print("Running fixes predicate");
var visible=nd.get_switch('toggle_weather') and nd.get_switch('toggle_display_mode') != "PLAN";
if (visible) {
#print("storms update requested!");
trigger_update( layer );
}
layer._view.setVisible(visible);
}, # end of layer update predicate
}, # end of storms layer
{ name:'airports-nd', update_on:['toggle_range','toggle_airports','toggle_display_mode'],
predicate: func(nd, layer) {
# print("Running airports-nd predicate");
var visible = nd.get_switch('toggle_airports') and nd.in_mode('toggle_display_mode', ['MAP']);
if (visible) {
trigger_update( layer ); # clear & redraw
}
layer._view.setVisible( visible );
}, # end of layer update predicate
}, # end of airports layer
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
{ name:'vor', disabled:1, update_on:['toggle_range','toggle_stations','toggle_display_mode'],
predicate: func(nd, layer) {
# print("Running vor layer predicate");
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if(visible) {
trigger_update( layer ); # clear & redraw
}
layer._view.setVisible( nd.get_switch('toggle_stations') );
}, # end of layer update predicate
}, # end of VOR layer
{ name:'VOR', isMapStructure:1, update_on:['toggle_range','toggle_stations','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_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
layer.group.setVisible( visible );
if (visible) {
#print("Updating MapStructure ND layer: VOR");
# (Hopefully) smart update
layer.update();
}
}, # end of layer update predicate
}, # end of VOR layer
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
{ name:'dme', disabled:1, update_on:['toggle_range','toggle_stations'],
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if(visible) {
trigger_update( layer ); # clear & redraw
}
layer._view.setVisible( nd.get_switch('toggle_stations') );
}, # end of layer update predicate
}, # end of DME layers
{ name:'DME', isMapStructure:1, update_on:['toggle_range','toggle_stations'],
# FIXME: this is a really ugly place for controller code
predicate: func(nd, layer) {
# print("Running vor layer predicate");
# toggle visibility here
layer.group.setVisible( nd.get_switch('toggle_stations') );
if (nd.rangeNm() <= 40 and
nd.get_switch('toggle_stations') and
nd.get_switch('toggle_display_mode') == "MAP") {
#print("Updating MapStructure ND layer: DME");
# (Hopefully) smart update
layer.update();
}
}, # end of layer update predicate
}, # end of DME layer
{ name:'mp-traffic', update_on:['toggle_range','toggle_traffic'],
predicate: func(nd, layer) {
trigger_update( layer ); # clear & redraw
layer._view.setVisible( 1 ); #nd.get_switch('toggle_traffic')
}, # end of layer update predicate
}, # end of traffic layer
{ name:'runway-nd', update_on:['toggle_range','toggle_display_mode'],
predicate: func(nd, layer) {
var visible = (nd.rangeNm() <= 40 and getprop("autopilot/route-manager/active") ) ;
if (visible)
trigger_update( layer ); # clear & redraw
layer._view.setVisible( visible );
}, # end of layer update predicate
}, # end of airports-nd layer
{ name:'route', update_on:['toggle_range','toggle_display_mode'],
predicate: func(nd, layer) {
trigger_update( layer ); # clear & redraw
layer._view.setVisible( 1 ); #nd.get_switch('toggle_traffic')
}, # end of layer update predicate
}, # end of route 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: [
{
# 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) getprop("/velocities/airspeed-kt") > 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(),
}, # end of tas behavior callbacks
}, # end of tas hash
{
id: 'wpActiveId',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("/autopilot/route-manager/wp/id") != nil and getprop("autopilot/route-manager/active"),
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: 'wpActiveDist',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and getprop("autopilot/route-manager/active"),
is_true: func(nd) {
nd.symbols.wpActiveDist.setText(sprintf("%3.01fNM",getprop("/autopilot/route-manager/wp/dist")));
nd.symbols.wpActiveDist.show();
},
is_false: func(nd) nd.symbols.wpActiveDist.hide(),
}, # of wpActiveDist.impl
}, # of wpActiveDist
{
id: 'eta',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("autopilot/route-manager/wp/eta") != nil and getprop("autopilot/route-manager/active"),
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);
if (h>24) h=h-24;
etaSec=etaSec-3600*h;
var m = math.floor(etaSec/60);
etaSec=etaSec-60*m;
var s = etaSec;
nd.symbols.eta.setText(sprintf("%02.0f%02.0f.%02.0fz",h,m,s));
nd.symbols.eta.show();
},
is_false: func(nd) nd.symbols.eta.hide(),
}, # of eta.impl
}, # of eta
{
id:'hdg',
impl: {
init: func(nd,symbol),
predicate: ALWAYS, # always true
is_true: func(nd) nd.symbols.hdg.setText(sprintf("%03.0f", nd.aircraft_source.get_hdg_mag() )),
is_false: NOTHING,
}, # of hdg.impl
}, # of hdg
{
id:'gs',
impl: {
init: func(nd,symbol),
common: func(nd) nd.symbols.gs.setText(sprintf("%3.0f",nd.aircraft_source.get_spd() )),
predicate: func(nd) nd.aircraft_source.get_spd() >= 30,
is_true: func(nd) {
nd.symbols.gs.setFontSize(36);
},
is_false: func(nd) nd.symbols.gs.setFontSize(52),
}, # of gs.impl
}, # of gs
{
id:'rangeArcs',
impl: {
init: func(nd,symbol),
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"),
is_true: func(nd) nd.symbols.rangeArcs.show(),
is_false: func(nd) nd.symbols.rangeArcs.hide(),
}, # of rangeArcs.impl
}, # of rangeArcs
], # end of vector with features
}, # end of Boeing style
#####
##
## add support for other aircraft/ND types and styles here (Airbus etc)
## ##
## this loads and configures existing layers (currently, *.layer files in Nasal/canvas/map)
## ##
layers: [ }; # end of NDStyles
{ name:'fixes', 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);
if(visible)
trigger_update( layer );
layer._view.setVisible(visible);
}, # end of layer update predicate
}, # end of fixes layer
# Should redraw every 10 seconds
{ name:'storms', update_on:['toggle_range','toggle_weather','toggle_display_mode'], predicate: func(nd, layer) {
var visible=nd.get_switch('toggle_weather') and nd.get_switch('toggle_display_mode') != "PLAN";
if (visible)
trigger_update( layer );
layer._view.setVisible(visible);
}, # end of layer update predicate
}, # end of storms layer
{ name:'airplaneSymbol', update_on:['toggle_range','toggle_display_mode'], predicate: func(nd, layer) {
var visible=nd.get_switch('toggle_display_mode') == "PLAN";
if (visible)
trigger_update( layer );
layer._view.setVisible(visible);
},
},
{ name:'airports-nd', 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']);
if (visible)
trigger_update( layer ); # clear & redraw
layer._view.setVisible( visible);
}, # end of layer update predicate
}, # end of airports layer
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
{ name:'vor', update_on:['toggle_range','toggle_stations','toggle_display_mode'], predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if(visible)
trigger_update( layer ); # clear & redraw
layer._view.setVisible( visible );
}, # end of layer update predicate
}, # end of VOR layer
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
{ name:'dme', update_on:['toggle_range','toggle_stations','toggle_display_mode'], predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if(visible)
trigger_update( layer ); # clear & redraw
layer._view.setVisible( visible );
}, # end of layer update predicate
}, # end of DME layer
{ name:'mp-traffic', update_on:['toggle_range','toggle_traffic'], predicate: func(nd, layer) {
trigger_update( layer ); # clear & redraw
layer._view.setVisible( nd.get_switch('toggle_traffic') );
}, # end of layer update predicate
}, # end of traffic layer
{ name:'runway-nd', update_on:['toggle_range','toggle_display_mode'], predicate: func(nd, layer) {
var visible = (nd.rangeNm() <= 40) and getprop("autopilot/route-manager/active") and nd.in_mode('toggle_display_mode', ['MAP','PLAN']) ;
if (visible)
trigger_update( layer ); # clear & redraw
layer._view.setVisible( visible );
}, # end of layer update predicate
}, # end of airports-nd layer
{ name:'route', update_on:['toggle_display_mode',], predicate: func(nd, layer) {
var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN']));
if (visible)
trigger_update( layer ); # clear & redraw
layer._view.setVisible( visible );
}, # end of layer update predicate
}, # end of route 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: [ {
# 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) getprop("/velocities/airspeed-kt") > 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(),
}, # end of tas behavior callbacks
}, # end of tas hash
{
id: 'wpActiveId',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("/autopilot/route-manager/wp/id") != nil and getprop("autopilot/route-manager/active"),
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: 'wpActiveDist',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and getprop("autopilot/route-manager/active"),
is_true: func(nd) {
nd.symbols.wpActiveDist.setText(sprintf("%3.01fNM",getprop("/autopilot/route-manager/wp/dist")));
nd.symbols.wpActiveDist.show();
},
is_false: func(nd) nd.symbols.wpActiveDist.hide(),
}, # of wpActiveDist.impl
}, # of wpActiveDist
{
id: 'eta',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("autopilot/route-manager/wp/eta") != nil and getprop("autopilot/route-manager/active"),
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);
if (h>24) h=h-24;
etaSec=etaSec-3600*h;
var m = math.floor(etaSec/60);
etaSec=etaSec-60*m;
var s = etaSec;
nd.symbols.eta.setText(sprintf("%02.0f%02.0f.%02.0fz",h,m,s));
nd.symbols.eta.show();
},
is_false: func(nd) nd.symbols.eta.hide(),
}, # of eta.impl
}, # of eta
{ id:'hdg',
impl: {
init: func(nd,symbol),
predicate: ALWAYS, # always true
is_true: func(nd) nd.symbols.hdg.setText(sprintf("%03.0f", nd.aircraft_source.get_hdg_mag() )),
is_false: NOTHING,
}, # of hdg.impl
}, # of hdg
{ id:'gs',
impl: {
init: func(nd,symbol),
common: func(nd) nd.symbols.gs.setText(sprintf("%3.0f",nd.aircraft_source.get_spd() )),
predicate: func(nd) nd.aircraft_source.get_spd() >= 30,
is_true: func(nd) {
nd.symbols.gs.setFontSize(36);
},
is_false: func(nd) nd.symbols.gs.setFontSize(52),
}, # of gs.impl
}, # of gs
{ id:'rangeArcs',
impl: {
init: func(nd,symbol),
predicate: func(nd) ((nd.in_mode('toggle_display_mode', ['APP','VOR']) and nd.get_switch('toggle_weather')) or (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
], # end of vector with features
}, # end of Boeing ND style
};
## ##
# 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) # 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)
# #
var NDSourceDriver = {}; var NDSourceDriver = {};
NDSourceDriver.new = func NDSourceDriver.new = func {
{
var m = {parents:[NDSourceDriver]}; var m = {parents:[NDSourceDriver]};
m.get_hdg_mag= func getprop("/orientation/heading-magnetic-deg"); m.get_hdg_mag= func getprop("/orientation/heading-magnetic-deg");
m.get_hdg_tru= func getprop("/orientation/heading-deg"); m.get_hdg_tru= func getprop("/orientation/heading-deg");
@ -246,7 +304,7 @@ NDSourceDriver.new = func
{ {
if(getprop("/velocities/groundspeed-kt") > 80) if(getprop("/velocities/groundspeed-kt") > 80)
{ {
getprop("/orientation/track-magnetic-deg"); getprop("/orientation/track-magnetic-deg");
} }
else else
{ {
@ -282,21 +340,21 @@ NDSourceDriver.new = func
# TODO: switches are ND specific, so move to the NDStyle hash! # TODO: switches are ND specific, so move to the NDStyle hash!
var default_switches = { var default_switches = {
'toggle_range': {path: '/inputs/range-nm', value:10, type:'INT'}, 'toggle_range': {path: '/inputs/range-nm', value:40, type:'INT'},
'toggle_weather': {path: '/inputs/wxr', value:0, type:'BOOL'}, 'toggle_weather': {path: '/inputs/wxr', value:0, type:'BOOL'},
'toggle_airports': {path: '/inputs/arpt', value:0, type:'BOOL'}, 'toggle_airports': {path: '/inputs/arpt', value:0, type:'BOOL'},
'toggle_stations': {path: '/inputs/sta', value:0, type:'BOOL'}, 'toggle_stations': {path: '/inputs/sta', value:0, type:'BOOL'},
'toggle_waypoints': {path: '/inputs/wpt', value:0, type:'BOOL'}, 'toggle_waypoints': {path: '/inputs/wpt', value:0, type:'BOOL'},
'toggle_position': {path: '/inputs/pos', value:0, type:'BOOL'}, 'toggle_position': {path: '/inputs/pos', value:0, type:'BOOL'},
'toggle_data': {path: '/inputs/data',value:0, type:'BOOL'}, 'toggle_data': {path: '/inputs/data',value:0, type:'BOOL'},
'toggle_terrain': {path: '/inputs/terr',value:0, type:'BOOL'}, 'toggle_terrain': {path: '/inputs/terr',value:0, type:'BOOL'},
'toggle_traffic': {path: '/inputs/tfc',value:0, type:'BOOL'}, 'toggle_traffic': {path: '/inputs/tcas',value:0, type:'BOOL'},
'toggle_centered': {path: '/inputs/nd-centered',value:0, type:'BOOL'}, 'toggle_centered': {path: '/inputs/nd-centered',value:0, type:'BOOL'},
'toggle_lh_vor_adf': {path: '/inputs/lh-vor-adf',value:0, type:'INT'}, 'toggle_lh_vor_adf': {path: '/inputs/lh-vor-adf',value:0, type:'INT'},
'toggle_rh_vor_adf': {path: '/inputs/rh-vor-adf',value:0, type:'INT'}, 'toggle_rh_vor_adf': {path: '/inputs/rh-vor-adf',value:0, type:'INT'},
'toggle_display_mode': {path: '/mfd/display-mode', value:'MAP', type:'STRING'}, # valid values are: APP, MAP, PLAN or VOR 'toggle_display_mode': {path: '/mfd/display-mode', value:'MAP', type:'STRING'}, # valid values are: APP, MAP, PLAN or VOR
'toggle_display_type': {path: '/mfd/display-type', value:'CRT', type:'STRING'}, # valid values are: CRT or LCD 'toggle_display_type': {path: '/mfd/display-type', value:'CRT', type:'STRING'}, # valid values are: CRT or LCD
'toggle_true_north': {path: '/mfd/true-north', value:0, type:'BOOL'}, 'toggle_true_north': {path: '/mfd/true-north', value:0, type:'BOOL'},
}; };
# Hack to update weather radar once every 10 seconds # Hack to update weather radar once every 10 seconds
@ -320,131 +378,147 @@ update_apl_sym();
# - introduce a MFD class (use it also for PFD/EICAS) # - introduce a MFD class (use it also for PFD/EICAS)
# - introduce a SGSubsystem class and use it here # - introduce a SGSubsystem class and use it here
# - introduce a Boeing NavDisplay class # - introduce a Boeing NavDisplay class
var NavDisplay = var NavDisplay = {
{
# reset handler # reset handler
handle_reinit: func handle_reinit: func {
{ print("Cleaning up NavDisplay listeners");
# shut down all timers and other loops here # shut down all timers and other loops here
me.update_timer.stop(); me.update_timer.stop();
foreach(var l; me.listeners) foreach(var l; me.listeners)
removelistener(l); removelistener(l);
},
listen: func(p,c)
{
append(me.listeners, setlistener(p,c));
},
# listeners for cockpit switches
listen_switch: func(s,c)
{
me.listen( me.get_full_switch_path(s), func
{
c();
});
}, },
# get the full property path for a given switch listen: func(p,c) {
get_full_switch_path: func (s) { append(me.listeners, setlistener(p,c));
# debug.dump( me.efis_switches[s] ); },
return me.efis_path ~ me.efis_switches[s].path; # FIXME: should be using props.nas instead of ~
},
# helper method for getting configurable cockpit switches (which are usually different in each aircraft) # listeners for cockpit switches
get_switch: func(s) listen_switch: func(s,c) {
{ # print("event setup for: ", id(c));
me.listen( me.get_full_switch_path(s), func {
# print("listen_switch triggered:", s, " callback id:", id(c) );
c();
});
},
# get the full property path for a given switch
get_full_switch_path: func (s) {
# debug.dump( me.efis_switches[s] );
return me.efis_path ~ me.efis_switches[s].path; # FIXME: should be using props.nas instead of ~
},
# helper method for getting configurable cockpit switches (which are usually different in each aircraft)
get_switch: func(s) {
var switch = me.efis_switches[s]; var switch = me.efis_switches[s];
var path = me.efis_path ~ switch.path ; var path = me.efis_path ~ switch.path ;
#print(s,":Getting switch prop:", path);
return getprop( path ); return getprop( path );
}, },
# for creating NDs that are driven by AI traffic instead of the main aircraft (generalization rocks!) # for creating NDs that are driven by AI traffic instead of the main aircraft (generalization rocks!)
connectAI: func(source=nil) connectAI: func(source=nil) {
{ me.aircraft_source = {
me.aircraft_source =
{
get_hdg_mag: func source.getNode('orientation/heading-magnetic-deg').getValue(), get_hdg_mag: func source.getNode('orientation/heading-magnetic-deg').getValue(),
get_hdg_tru: func source.getNode('orientation/heading-deg').getValue(),
get_trk_mag: func source.getNode('orientation/track-magnetic-deg').getValue(), get_trk_mag: func source.getNode('orientation/track-magnetic-deg').getValue(),
get_trk_tru: func source.getNode('orientation/track-deg').getValue(),
get_lat: func source.getNode('position/latitude-deg').getValue(), get_lat: func source.getNode('position/latitude-deg').getValue(),
get_lon: func source.getNode('position/longitude-deg').getValue(), get_lon: func source.getNode('position/longitude-deg').getValue(),
get_spd: func source.getNode('velocities/true-airspeed-kt').getValue(), get_spd: func source.getNode('velocities/true-airspeed-kt').getValue(),
get_vspd: func source.getNode('velocities/vertical-speed-fps').getValue(),
}; };
}, # of connectAI }, # of connectAI
# TODO: the ctor should allow customization, for different aircraft # TODO: the ctor should allow customization, for different aircraft
# especially properties and SVG files/handles (747, 757, 777 etc) # especially properties and SVG files/handles (747, 757, 777 etc)
new : func(prop1, switches=default_switches, style='Boeing') new : func(prop1, switches=default_switches, style='Boeing') {
{
var m = { parents : [NavDisplay]}; var m = { parents : [NavDisplay]};
m.listeners=[]; # for cleanup handling m.listeners=[]; # for cleanup handling
m.aircraft_source = NDSourceDriver.new(); # uses the main aircraft as the driver/source (speeds, position, heading) 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.nd_style = NDStyles[style]; # look up ND specific stuff (file names etc)
m.radio_list=["instrumentation/comm/frequencies","instrumentation/comm[1]/frequencies",
"instrumentation/nav/frequencies","instrumentation/nav[1]/frequencies"]; m.radio_list=["instrumentation/comm/frequencies","instrumentation/comm[1]/frequencies",
m.mfd_mode_list=["APP","VOR","MAP","PLAN"]; "instrumentation/nav/frequencies", "instrumentation/nav[1]/frequencies"];
m.mfd_mode_list=["APP","VOR","MAP","PLAN"];
m.efis_path = prop1; m.efis_path = prop1;
m.efis_switches = switches; m.efis_switches = switches;
# just an alias, to avoid having to rewrite the old code for now
m.rangeNm = func m.get_switch('toggle_range'); m.rangeNm = func m.get_switch('toggle_range');
m.efis = props.globals.initNode(prop1);
m.mfd = m.efis.initNode("mfd");
# TODO: unify this with switch handling
m.mfd_mode_num = m.mfd.initNode("mode-num",2,"INT");
m.std_mode = m.efis.initNode("inputs/setting-std",0,"BOOL");
m.previous_set = m.efis.initNode("inhg-previous",29.92);
m.kpa_mode = m.efis.initNode("inputs/kpa-mode",0,"BOOL");
m.kpa_output = m.efis.initNode("inhg-kpa",29.92);
m.kpa_prevoutput = m.efis.initNode("inhg-kpa-previous",29.92);
m.temp = m.efis.initNode("fixed-temp",0);
m.alt_meters = m.efis.initNode("inputs/alt-meters",0,"BOOL");
m.fpv = m.efis.initNode("inputs/fpv",0,"BOOL");
m.mins_mode = m.efis.initNode("inputs/minimums-mode",0,"BOOL"); m.efis = props.globals.initNode(prop1);
m.mins_mode_txt = m.efis.initNode("minimums-mode-text","RADIO","STRING"); m.mfd = m.efis.initNode("mfd");
m.minimums = m.efis.initNode("minimums",250,"INT");
m.mk_minimums = props.globals.getNode("instrumentation/mk-viii/inputs/arinc429/decision-height"); # TODO: unify this with switch handling
m.mfd_mode_num = m.mfd .initNode("mode-num",2,"INT");
m.std_mode = m.efis.initNode("inputs/setting-std",0,"BOOL");
m.previous_set = m.efis.initNode("inhg-previous",29.92);
m.kpa_mode = m.efis.initNode("inputs/kpa-mode",0,"BOOL");
m.kpa_output = m.efis.initNode("inhg-kpa",29.92);
m.kpa_prevoutput = m.efis.initNode("inhg-kpa-previous",29.92);
m.temp = m.efis.initNode("fixed-temp",0);
m.alt_meters = m.efis.initNode("inputs/alt-meters",0,"BOOL");
m.fpv = m.efis.initNode("inputs/fpv",0,"BOOL");
m.mins_mode = m.efis.initNode("inputs/minimums-mode",0,"BOOL");
m.mins_mode_txt = m.efis.initNode("minimums-mode-text","RADIO","STRING");
m.minimums = m.efis.initNode("minimums",250,"INT");
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): # 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", 0, "INT"); # ditto ###
return m; # initialize all switches based on the defaults specified in the switch hash
#
foreach(var switch; keys( m.efis_switches ) )
props.globals.initNode
( m.get_full_switch_path (switch),
m.efis_switches[switch].value,
m.efis_switches[switch].type
);
return m;
}, },
newMFD: func(canvas_group) newMFD: func(canvas_group)
{ {
me.listen("/sim/signals/reinit", func me.handle_reinit() ); me.listen("/sim/signals/reinit", func me.handle_reinit() );
me.update_timer = maketimer(0.05, func me.update() ); # TODO: make interval configurable via ctor me.update_timer = maketimer(0.05, func me.update() ); # TODO: make interval configurable via ctor
me.nd = canvas_group; me.nd = canvas_group;
# load the specified SVG file into the me.nd group and populate all sub groups # load the specified SVG file into the me.nd group and populate all sub groups
canvas.parsesvg(me.nd, me.nd_style.svg_filename, {'font-mapper': me.nd_style.font_mapper}); canvas.parsesvg(me.nd, me.nd_style.svg_filename, {'font-mapper': me.nd_style.font_mapper});
me.symbols = {}; # storage for SVG elements, to avoid namespace pollution (all SVG elements end up here) me.symbols = {}; # storage for SVG elements, to avoid namespace pollution (all SVG elements end up here)
foreach(var feature; me.nd_style.features ) { foreach(var feature; me.nd_style.features ) {
me.symbols[feature.id] = me.nd.getElementById(feature.id); # print("Setting up SVG feature:", feature.id);
if(contains(feature.impl,'init')) feature.impl.init(me.nd, feature); # call The element's init code (i.e. updateCenter) me.symbols[feature.id] = me.nd.getElementById(feature.id);
if(contains(feature.impl,'init')) feature.impl.init(me.nd, feature); # call The element's init code (i.e. updateCenter)
} }
### this is the "old" method that's less flexible, we want to use the style hash instead (see above) ### this is the "old" method that's less flexible, we want to use the style hash instead (see above)
# because things are much better configurable that way # because things are much better configurable that way
# now look up all required SVG elements and initialize member fields using the same name to have a convenient handle # now look up all required SVG elements and initialize member fields using the same name to have a convenient handle
foreach(var element; ["wind","dmeLDist","dmeRDist","dmeL","dmeR","vorL","vorR","vorLId","vorRId", foreach(var element; ["wind","dmeLDist","dmeRDist","dmeL","dmeR","vorL","vorR","vorLId","vorRId",
"range","status.wxr","status.wpt","hdgGroup","status.sta","status.arpt"]) "range","status.wxr","status.wpt","hdgGroup","status.sta","status.arpt"])
me.symbols[element] = me.nd.getElementById(element); me.symbols[element] = me.nd.getElementById(element);
# load elements from vector image, and create instance variables using identical names, and call updateCenter() on each # load elements from vector image, and create instance variables using identical names, and call updateCenter() on each
# anything that needs updatecenter called, should be added to the vector here # anything that needs updatecenter called, should be added to the vector here
# #
foreach(var element; ["windArrow","compassApp","northUp","aplSymMap","aplSymMapCtr","aplSymVor", foreach(var element; ["windArrow","compassApp","northUp","aplSymMap","aplSymMapCtr","aplSymVor",
"staFromL2","staToL2","staFromR2","staToR2", "staFromL2","staToL2","staFromR2","staToR2",
"locPtr","hdgTrk","truMag","altArc","planArcs", "locPtr","hdgTrk","truMag","altArc","planArcs",
"trkInd","compass","HdgBugCRT","TrkBugLCD","HdgBugLCD","selHdgLine","curHdgPtr", "trkInd","compass","HdgBugCRT","TrkBugLCD","HdgBugLCD","selHdgLine","curHdgPtr",
"staFromL","staToL","staFromR","staToR"] ) "staFromL","staToL","staFromR","staToR"] )
me.symbols[element] = me.nd.getElementById(element).updateCenter(); me.symbols[element] = me.nd.getElementById(element).updateCenter();
foreach(var element; ["HdgBugCRT2","TrkBugLCD2","HdgBugLCD2","selHdgLine2","curHdgPtr2","vorCrsPtr2"] ) foreach(var element; ["HdgBugCRT2","TrkBugLCD2","HdgBugLCD2","selHdgLine2","curHdgPtr2","vorCrsPtr2"] )
me.symbols[element] = me.nd.getElementById(element).setCenter(512,565); me.symbols[element] = me.nd.getElementById(element).setCenter(512,565);
# this should probably be using Philosopher's new SymbolLayer ? # this should probably be using Philosopher's new SymbolLayer ?
me.map = me.nd.createChild("map","map") me.map = me.nd.createChild("map","map")
@ -454,26 +528,25 @@ var NavDisplay =
var get_range = func me.get_switch('toggle_range'); var get_range = func me.get_switch('toggle_range');
# predicate for the draw controller # predicate for the draw controller
var is_tuned = func(freq) var is_tuned = func(freq) {
{
var nav1=getprop("instrumentation/nav[0]/frequencies/selected-mhz"); var nav1=getprop("instrumentation/nav[0]/frequencies/selected-mhz");
var nav2=getprop("instrumentation/nav[1]/frequencies/selected-mhz"); var nav2=getprop("instrumentation/nav[1]/frequencies/selected-mhz");
if(freq == nav1 or freq == nav2) return 1; if (freq == nav1 or freq == nav2) return 1;
return 0; return 0;
} }
# another predicate for the draw controller # another predicate for the draw controller
var get_course_by_freq = func(freq) var get_course_by_freq = func(freq) {
{ if (freq == getprop("instrumentation/nav[0]/frequencies/selected-mhz"))
if(freq == getprop("instrumentation/nav[0]/frequencies/selected-mhz"))
return getprop("instrumentation/nav[0]/radials/selected-deg"); return getprop("instrumentation/nav[0]/radials/selected-deg");
else else
return getprop("instrumentation/nav[1]/radials/selected-deg"); return getprop("instrumentation/nav[1]/radials/selected-deg");
} }
var get_current_position = func var get_current_position = func {
{ return [
return [me.aircraft_source.get_lat(), me.aircraft_source.get_lon()]; me.aircraft_source.get_lat(), me.aircraft_source.get_lon()
];
} }
# a hash with controller callbacks, will be passed onto draw routines to customize behavior/appearance # a hash with controller callbacks, will be passed onto draw routines to customize behavior/appearance
@ -481,13 +554,17 @@ var NavDisplay =
# so we need some simple way to communicate between frontend<->backend until we have real controllers # so we need some simple way to communicate between frontend<->backend until we have real controllers
# for now, a single controller hash is shared by most layers - unsupported callbacks are simply ignored by the draw files # for now, a single controller hash is shared by most layers - unsupported callbacks are simply ignored by the draw files
# #
var controller = var controller = {
{
query_range: func get_range(), query_range: func get_range(),
is_tuned:is_tuned, is_tuned:is_tuned,
get_tuned_course:get_course_by_freq, get_tuned_course:get_course_by_freq,
get_position: get_current_position, get_position: get_current_position,
}; };
# FIXME: MapStructure: big hack
canvas.Symbol.Controller.get("VOR").query_range = controller.query_range;
canvas.Symbol.Controller.get("VOR").get_tuned_course = controller.get_tuned_course;
canvas.Symbol.Controller.get("DME").is_tuned = controller.is_tuned;
### ###
# set up various layers, controlled via callbacks in the controller hash # set up various layers, controlled via callbacks in the controller hash
@ -498,56 +575,66 @@ var NavDisplay =
me.layers={}; # storage container for all ND specific layers 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 # look up all required layers as specified per the NDStyle hash and do the initial setup for event handling
foreach(var layer; me.nd_style.layers) {
foreach(var layer; me.nd_style.layers) if(layer['disabled']) continue; # skip this layer
{ #print("newMFD(): Setting up ND layer:", layer.name);
# huge hack for the alt-arc, which is not rendered as a map group, but directly as part of the toplevel ND group # huge hack for the alt-arc, which is not rendered as a map group, but directly as part of the toplevel ND group
var render_target = (!contains(layer,'not_a_map') or !layer.not_a_map) ? me.map : me.nd; var render_target = (!contains(layer,'not_a_map') or !layer.not_a_map) ? me.map : me.nd;
var the_layer = me.layers[layer.name] = canvas.MAP_LAYERS[layer.name].new( render_target, layer.name, controller );
var the_layer = nil;
if(!layer['isMapStructure'])
the_layer = me.layers[layer.name] = canvas.MAP_LAYERS[layer.name].new( render_target, layer.name, controller );
else {
#print("Setting up MapStructure-based layer for ND, name:", layer.name);
render_target.addLayer(factory: canvas.SymbolLayer, type_arg: layer.name);
the_layer = me.layers[layer.name] = render_target.getLayer(layer.name);
}
# now register all layer specific notification listeners and their corresponding update predicate/callback # now register all layer specific notification listeners and their corresponding update predicate/callback
# pass the ND instance and the layer handle to the predicate when it is called # pass the ND instance and the layer handle to the predicate when it is called
# so that it can directly access the ND instance and its own layer (without having to know the layer's name) # so that it can directly access the ND instance and its own layer (without having to know the layer's name)
var event_handler = make_event_handler(layer.predicate, the_layer); var event_handler = make_event_handler(layer.predicate, the_layer);
foreach(var event; layer.update_on) foreach(var event; layer.update_on) {
{ # print("Setting up subscription:", event, " for ", layer.name, " handler id:", id(event_handler) );
me.listen_switch(event, event_handler ) ; me.listen_switch(event, event_handler);
} # foreach event subscription } # foreach event subscription
# and now update/init each layer once by calling its update predicate for initialization # and now update/init each layer once by calling its update predicate for initialization
event_handler(); event_handler();
} # foreach layer } # foreach layer
#print("navdisplay.mfd:ND layer setup completed");
# start the update timer, which makes sure that the update() will be called # start the update timer, which makes sure that the update() will be called
me.update_timer.start(); me.update_timer.start();
# next, radio & autopilot & listeners # next, radio & autopilot & listeners
# TODO: move this to .init field in layers hash or to model files # TODO: move this to .init field in layers hash or to model files
foreach(var n; var radios = [ "instrumentation/nav/frequencies/selected-mhz", foreach(var n; var radios = [
"instrumentation/nav[1]/frequencies/selected-mhz"]) "instrumentation/nav/frequencies/selected-mhz",
me.listen(n, func() "instrumentation/nav[1]/frequencies/selected-mhz"])
{ me.listen(n, func() {
# me.drawvor(); # me.drawvor();
# me.drawdme(); # me.drawdme();
}); });
# TODO: move this to the route.model # TODO: move this to the route.model
# Hack to draw the route on rm activation # Hack to draw the route on rm activation
me.listen("/autopilot/route-manager/active", func(active) me.listen("/autopilot/route-manager/active", func(active) {
{ if(active.getValue()) {
if(active.getValue()) me.drawroute();
{ me.drawrunways();
setprop(me.get_full_switch_path('toggle_display_mode'),getprop(me.get_full_switch_path('toggle_display_mode'))); } else {
} #print("TODO: navdisplay.mfd: implement route-manager/layer clearing!");
else
{
#me.route_group.removeAllChildren(); # HACK! #me.route_group.removeAllChildren(); # HACK!
} }
}); });
me.listen("/autopilot/route-manager/current-wp", func(activeWp) me.listen("/autopilot/route-manager/current-wp", func(activeWp) {
{
canvas.updatewp( activeWp.getValue() ); canvas.updatewp( activeWp.getValue() );
}); });
}, },
drawroute: func print("drawroute no longer used!"),
drawrunways: func print("drawrunways no longer used!"),
in_mode:func(switch, modes) in_mode:func(switch, modes)
{ {
@ -572,7 +659,6 @@ var NavDisplay =
var lonNm = 60; var lonNm = 60;
# fgcommand('profiler-start'); # fgcommand('profiler-start');
# Heading update # Heading update
var userHdgMag = me.aircraft_source.get_hdg_mag(); var userHdgMag = me.aircraft_source.get_hdg_mag();
var userHdgTru = me.aircraft_source.get_hdg_tru(); var userHdgTru = me.aircraft_source.get_hdg_tru();
@ -931,7 +1017,6 @@ var NavDisplay =
## update the status flags shown on the ND (wxr, wpt, arpt, sta) ## update the status flags shown on the ND (wxr, wpt, arpt, sta)
# this could/should be using listeners instead ... # this could/should be using listeners instead ...
me.symbols['status.wxr'].setVisible( me.get_switch('toggle_weather') and me.in_mode('toggle_display_mode', ['MAP'])); me.symbols['status.wxr'].setVisible( me.get_switch('toggle_weather') and me.in_mode('toggle_display_mode', ['MAP']));
me.symbols['status.wpt'].setVisible( me.get_switch('toggle_waypoints') and me.in_mode('toggle_display_mode', ['MAP'])); me.symbols['status.wpt'].setVisible( me.get_switch('toggle_waypoints') and me.in_mode('toggle_display_mode', ['MAP']));
me.symbols['status.arpt'].setVisible( me.get_switch('toggle_airports') and me.in_mode('toggle_display_mode', ['MAP'])); me.symbols['status.arpt'].setVisible( me.get_switch('toggle_airports') and me.in_mode('toggle_display_mode', ['MAP']));

View file

@ -5,7 +5,7 @@ RouteModel.new = func make(LayerModel, RouteModel);
RouteModel.init = func { RouteModel.init = func {
me._view.reset(); me._view.reset();
if (!getprop("/autopilot/route-manager/active")) if (!getprop("/autopilot/route-manager/active"))
return; return;
## TODO: all the model stuff is still inside the draw file for now, this just ensures that it will be called once ## TODO: all the model stuff is still inside the draw file for now, this just ensures that it will be called once
foreach(var t; [nil] ) foreach(var t; [nil] )