1
0
Fork 0

MapStructure work & (partial) integration

In time for 3.0. The API is still not fully complete, and not fully
cleaned up, but this is good enough for this release cycle (and it
should offer benefit longer term, if not now -- hopefully performance as
well).

Many thanks to Hooray as well, who has helped prepare things while I
could not, and often suggested ideas.
This commit is contained in:
Philosopher 2014-01-09 21:04:36 -06:00
parent 9af485ad93
commit 7ca8482b07
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,9 +388,18 @@ 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:
var CompassLayer = {
};
var AltitudeArcLayer = {
};
load_MapStructure = func {
Map.Controller = {
# Static/singleton:
registry: {}, registry: {},
add: func(type, class) add: func(type, class)
me.registry[type] = class, me.registry[type] = class,
@ -295,11 +411,11 @@ Map.Controller = {
# @param map The #SymbolMap this controller is responsible for. # @param map The #SymbolMap this controller is responsible for.
new: func(type, layer, arg...) new: func(type, layer, arg...)
return call((var class = me.get(type)).new, [map]~arg, class), return call((var class = me.get(type)).new, [map]~arg, class),
}; };
####### LOAD FILES ####### ####### LOAD FILES #######
#print("loading files"); #print("loading files");
(func { (func {
var FG_ROOT = getprop("/sim/fg-root"); var FG_ROOT = getprop("/sim/fg-root");
var load = func(file, name) { var load = func(file, name) {
#print(file); #print(file);
@ -330,44 +446,59 @@ Map.Controller = {
#debug.dump(keys(hash)); #debug.dump(keys(hash));
return 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) var load_deps = func(name) {
settimer(func { load(FG_ROOT~"/Nasal/canvas/map/"~name~".lcontroller", name);
if (caller(0)[0] != globals.canvas) load(FG_ROOT~"/Nasal/canvas/map/"~name~".symbol", name);
return call(caller(0)[1], arg, nil, globals.canvas); load(FG_ROOT~"/Nasal/canvas/map/"~name~".scontroller", name);
}
print("Running MapStructure test code"); foreach( var name; ['VOR','FIX','NDB','DME','WPT'] )
var TestCanvas = canvas.new({ load_deps( name );
"name": "Map Test", load(FG_ROOT~"/Nasal/canvas/map/aircraftpos.controller", name);
"size": [1024, 1024],
"view": [1024, 1024], ###
"mipmapping": 1 # set up a cache for 32x32 symbols
}); var SymbolCache32x32 = SymbolCache.new(1024,32);
var dlg = canvas.Window.new([400, 400], "dialog");
dlg.setCanvas(TestCanvas); var drawVOR = func(color, width=3) return func(group) {
var TestMap = TestCanvas.createGroup().createChild("map"); # we should not directly use a canvas here, but instead a LayeredMap.new() # print("drawing vor");
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 var bbox = group.createChild("path")
# Center the map's origin: .moveTo(-15,0)
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 ... .lineTo(-7.5,12.5)
# Initialize a range (TODO: LayeredMap.Controller): .lineTo(7.5,12.5)
TestMap.set("range", 100); .lineTo(15,0)
# Little cursor of current position: .lineTo(7.5,-12.5)
TestMap.createChild("path").rect(-5,-5,10,10).setColorFill(1,1,1).setColor(0,1,0); .lineTo(-7.5,-12.5)
# And make it move with our aircraft: .close()
TestMap.setController("Aircraft position"); # from aircraftpos.controller .setStrokeLineWidth(width)
dlg.del = func() { .setColor( color );
TestMap.del(); # debug.dump( bbox.getBoundingBox() );
# call inherited 'del'
delete(me, "del");
me.del();
}; };
}, 1);
}, 0); # end ugly module init timer hack
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'])
setlistener("/nasal/canvas/loaded", func {
foreach(var ext; var extensions = ['.draw','.model','.layer'])
load_modules(files_with(ext)); 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,12 +1,28 @@
# 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
if (me.icon_vor == nil) {
me.icon_vor = me.element.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(3)
.setColor(0,0.6,0.85);
}
# Update # Update
if (me.controller.isActive(me.model)) { if (me.controller.isActive(me.model)) {
if (me.range_vor == nil) { if (me.range_vor == nil) {
@ -21,32 +37,21 @@ var draw = func {
.setStrokeDashArray([5, 15, 5, 15, 5]) .setStrokeDashArray([5, 15, 5, 15, 5])
.setColor(0,1,0); .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.range_vor.show();
me.radial_vor.show(); me.radial_vor.show();
} else { } elsif (me.range_vor != nil) {
me.range_vor.hide(); me.range_vor.hide();
me.radial_vor.hide(); me.radial_vor.hide();
} }
} else # Init
me.element.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(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,10 +25,9 @@ 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 Boeing ND to help generalize the NavDisplay class itself # this configures the 744 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" )
@ -40,87 +39,138 @@ var NDStyles =
# 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)
## this loads and configures existing layers (currently, *.layer files in Nasal/canvas/map) ##
##
layers: [ layers: [
{ name:'fixes', update_on:['toggle_range','toggle_waypoints','toggle_display_mode'], predicate: func(nd, layer) { { 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); var visible=nd.get_switch('toggle_waypoints') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if(visible) if (visible) {
# print("fixes update requested!");
trigger_update( layer ); trigger_update( layer );
}
layer._view.setVisible(visible); layer._view.setVisible(visible);
}, # end of layer update predicate }, # end of layer update predicate
}, # end of fixes layer }, # 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 # Should redraw every 10 seconds
{ name:'storms', update_on:['toggle_range','toggle_weather','toggle_display_mode'], predicate: func(nd, layer) { { 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"; var visible=nd.get_switch('toggle_weather') and nd.get_switch('toggle_display_mode') != "PLAN";
if (visible) if (visible) {
#print("storms update requested!");
trigger_update( layer ); trigger_update( layer );
}
layer._view.setVisible(visible); layer._view.setVisible(visible);
}, # end of layer update predicate }, # end of layer update predicate
}, # end of storms layer }, # end of storms layer
{ name:'airplaneSymbol', update_on:['toggle_range','toggle_display_mode'], predicate: func(nd, layer) { { name:'airports-nd', update_on:['toggle_range','toggle_airports','toggle_display_mode'],
var visible=nd.get_switch('toggle_display_mode') == "PLAN"; predicate: func(nd, layer) {
if (visible) # print("Running airports-nd predicate");
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']); var visible = nd.get_switch('toggle_airports') and nd.in_mode('toggle_display_mode', ['MAP']);
if (visible) if (visible) {
trigger_update( layer ); # clear & redraw trigger_update( layer ); # clear & redraw
layer._view.setVisible( visible); }
layer._view.setVisible( visible );
}, # end of layer update predicate }, # end of layer update predicate
}, # end of airports layer }, # end of airports layer
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag. # 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) { { 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); var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if(visible) if(visible) {
trigger_update( layer ); # clear & redraw trigger_update( layer ); # clear & redraw
layer._view.setVisible( visible ); }
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 layer update predicate
}, # end of VOR layer }, # end of VOR layer
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag. # 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) { { 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); var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if(visible) if(visible) {
trigger_update( layer ); # clear & redraw trigger_update( layer ); # clear & redraw
layer._view.setVisible( visible ); }
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 layer update predicate
}, # end of DME layer }, # end of DME layer
{ name:'mp-traffic', update_on:['toggle_range','toggle_traffic'], predicate: func(nd, layer) { { name:'mp-traffic', update_on:['toggle_range','toggle_traffic'],
predicate: func(nd, layer) {
trigger_update( layer ); # clear & redraw trigger_update( layer ); # clear & redraw
layer._view.setVisible( nd.get_switch('toggle_traffic') ); layer._view.setVisible( 1 ); #nd.get_switch('toggle_traffic')
}, # end of layer update predicate }, # end of layer update predicate
}, # end of traffic layer }, # 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']) ; { 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) if (visible)
trigger_update( layer ); # clear & redraw trigger_update( layer ); # clear & redraw
layer._view.setVisible( visible ); layer._view.setVisible( visible );
}, # end of layer update predicate }, # end of layer update predicate
}, # end of airports-nd layer }, # end of airports-nd layer
{ name:'route', update_on:['toggle_display_mode',], predicate: func(nd, layer) { { name:'route', update_on:['toggle_range','toggle_display_mode'],
var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN'])); predicate: func(nd, layer) {
if (visible)
trigger_update( layer ); # clear & redraw trigger_update( layer ); # clear & redraw
layer._view.setVisible( visible ); layer._view.setVisible( 1 ); #nd.get_switch('toggle_traffic')
}, # end of layer update predicate }, # end of layer update predicate
}, # end of route layer }, # end of route layer
## add other layers here, layer names must match the registered names as used in *.layer files for now ## 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 ## this will all change once we're using Philosopher's MapStructure framework
], # end of vector with configured layers ], # end of vector with configured layers
@ -130,7 +180,8 @@ var NDStyles =
# SVG identifier, callback etc # SVG identifier, callback etc
# TODO: update_on([]), update_mode (update() vs. timers/listeners) # TODO: update_on([]), update_mode (update() vs. timers/listeners)
# TODO: support putting symbols on specific layers # TODO: support putting symbols on specific layers
features: [ { features: [
{
# TODO: taOnly doesn't need to use getprop polling in update(), use a listener instead! # TODO: taOnly doesn't need to use getprop polling in update(), use a listener instead!
id: 'taOnly', # the SVG ID id: 'taOnly', # the SVG ID
impl: { # implementation hash impl: { # implementation hash
@ -195,7 +246,8 @@ var NDStyles =
is_false: func(nd) nd.symbols.eta.hide(), is_false: func(nd) nd.symbols.eta.hide(),
}, # of eta.impl }, # of eta.impl
}, # of eta }, # of eta
{ id:'hdg', {
id:'hdg',
impl: { impl: {
init: func(nd,symbol), init: func(nd,symbol),
predicate: ALWAYS, # always true predicate: ALWAYS, # always true
@ -203,8 +255,8 @@ var NDStyles =
is_false: NOTHING, is_false: NOTHING,
}, # of hdg.impl }, # of hdg.impl
}, # of hdg }, # of hdg
{
{ id:'gs', id:'gs',
impl: { impl: {
init: func(nd,symbol), init: func(nd,symbol),
common: func(nd) nd.symbols.gs.setText(sprintf("%3.0f",nd.aircraft_source.get_spd() )), common: func(nd) nd.symbols.gs.setText(sprintf("%3.0f",nd.aircraft_source.get_spd() )),
@ -215,29 +267,35 @@ var NDStyles =
is_false: func(nd) nd.symbols.gs.setFontSize(52), is_false: func(nd) nd.symbols.gs.setFontSize(52),
}, # of gs.impl }, # of gs.impl
}, # of gs }, # of gs
{
{ id:'rangeArcs', id:'rangeArcs',
impl: { impl: {
init: func(nd,symbol), 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'))), 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_true: func(nd) nd.symbols.rangeArcs.show(),
is_false: func(nd) nd.symbols.rangeArcs.hide(), is_false: func(nd) nd.symbols.rangeArcs.hide(),
}, # of rangeArcs.impl }, # of rangeArcs.impl
}, # of rangeArcs }, # of rangeArcs
], # end of vector with features ], # end of vector with features
}, # end of Boeing ND style }, # end of Boeing style
}; #####
##
## add support for other aircraft/ND types and styles here (Airbus etc)
##
##
}; # end of NDStyles
## ##
# 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");
@ -282,7 +340,7 @@ 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'},
@ -290,7 +348,7 @@ var default_switches = {
'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'},
@ -320,25 +378,26 @@ 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)
{ listen: func(p,c) {
append(me.listeners, setlistener(p,c)); append(me.listeners, setlistener(p,c));
}, },
# listeners for cockpit switches # listeners for cockpit switches
listen_switch: func(s,c) listen_switch: func(s,c) {
{ # print("event setup for: ", id(c));
me.listen( me.get_full_switch_path(s), func me.listen( me.get_full_switch_path(s), func {
{ # print("listen_switch triggered:", s, " callback id:", id(c) );
c(); c();
}); });
}, },
@ -350,49 +409,50 @@ var NavDisplay =
}, },
# helper method for getting configurable cockpit switches (which are usually different in each aircraft) # helper method for getting configurable cockpit switches (which are usually different in each aircraft)
get_switch: func(s) 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", m.radio_list=["instrumentation/comm/frequencies","instrumentation/comm[1]/frequencies",
"instrumentation/nav/frequencies","instrumentation/nav[1]/frequencies"]; "instrumentation/nav/frequencies", "instrumentation/nav[1]/frequencies"];
m.mfd_mode_list=["APP","VOR","MAP","PLAN"]; 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.efis = props.globals.initNode(prop1);
m.mfd = m.efis.initNode("mfd"); m.mfd = m.efis.initNode("mfd");
# TODO: unify this with switch handling # TODO: unify this with switch handling
m.mfd_mode_num = m.mfd.initNode("mode-num",2,"INT"); m.mfd_mode_num = m.mfd .initNode("mode-num",2,"INT");
m.std_mode = m.efis.initNode("inputs/setting-std",0,"BOOL"); m.std_mode = m.efis.initNode("inputs/setting-std",0,"BOOL");
m.previous_set = m.efis.initNode("inhg-previous",29.92); m.previous_set = m.efis.initNode("inhg-previous",29.92);
m.kpa_mode = m.efis.initNode("inputs/kpa-mode",0,"BOOL"); m.kpa_mode = m.efis.initNode("inputs/kpa-mode",0,"BOOL");
@ -406,9 +466,21 @@ var NavDisplay =
m.mins_mode_txt = m.efis.initNode("minimums-mode-text","RADIO","STRING"); m.mins_mode_txt = m.efis.initNode("minimums-mode-text","RADIO","STRING");
m.minimums = m.efis.initNode("minimums",250,"INT"); m.minimums = m.efis.initNode("minimums",250,"INT");
m.mk_minimums = props.globals.getNode("instrumentation/mk-viii/inputs/arinc429/decision-height"); m.mk_minimums = props.globals.getNode("instrumentation/mk-viii/inputs/arinc429/decision-height");
# TODO: these are switches, can be unified with switch handling hash above (eventually):
m.nd_plan_wpt = m.efis.initNode("inputs/plan-wpt-index", 0, "INT"); # ditto # 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
###
# 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; return m;
}, },
newMFD: func(canvas_group) newMFD: func(canvas_group)
@ -416,12 +488,14 @@ var NavDisplay =
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 ) {
# print("Setting up SVG feature:", feature.id);
me.symbols[feature.id] = me.nd.getElementById(feature.id); 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) if(contains(feature.impl,'init')) feature.impl.init(me.nd, feature); # call The element's init code (i.e. updateCenter)
} }
@ -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,14 +554,18 @@ 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
# revisit this code once Philosopher's "Smart MVC Symbols/Layers" work is committed and integrated # revisit this code once Philosopher's "Smart MVC Symbols/Layers" work is committed and integrated
@ -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/frequencies/selected-mhz",
"instrumentation/nav[1]/frequencies/selected-mhz"]) "instrumentation/nav[1]/frequencies/selected-mhz"])
me.listen(n, func() 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']));