a9576e8c8d
- get rid of global variables and use instance variables - identified all important drawing routines and move them into *.draw files - changed to dynamic loading of *.draw *.model and *.layer files - implemented poor-man's controller hash to move use-case specific conditionals out of the draw files, and back into the instantiation, i.e. Gijs' EFIS class - started identifying stuff that is not specific to drawing, but to what is to be drawn, i.e. Model stuff - such as positioned queries, moved those out into *.model files - some more work on supporting more than a single ND MFD instance per aircraft - renamed a handful of SVG identifiers to avoid naming conflicts and to simplify usage of SVG IDs as member fields - moved all of the setlistener setup out of the fdm-initialized stub right into the ctor of the Efis class (actually that's controller stuff...) - initial MapStructure framework - aircraft-agnostic NavDisplay class - preparations for deprecating map.nas - additions to canvas.map - preparations for making NDStyles configurable via XML
374 lines
12 KiB
Text
374 lines
12 KiB
Text
var Symbol = {
|
|
# 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 symbol constructor
|
|
# @param group #Canvas.Group to place this on.
|
|
new: func(type, group, arg...) {
|
|
var ret = call((var class = me.get(type)).new, [group]~arg, class);
|
|
ret.element.set("symbol-type", type);
|
|
return ret;
|
|
},
|
|
# Non-static:
|
|
df_controller: nil, # default controller
|
|
# Update the drawing of this object (position and others).
|
|
update: func()
|
|
die("update() not implemented for this symbol type!"),
|
|
draw: func(group, model, lod)
|
|
die("draw() not implemented for this symbol type!"),
|
|
del: func()
|
|
die("del() not implemented for this symbol type!"),
|
|
}; # of Symbol
|
|
|
|
Symbol.Controller = {
|
|
# Static/singleton:
|
|
registry: {},
|
|
add: func(type, class)
|
|
registry[type] = class,
|
|
get: func(type)
|
|
if ((var class = me.registry[type]) == nil)
|
|
die("unknown type '"~type~"'");
|
|
else return class,
|
|
# Calls corresonding symbol constructor
|
|
# @param model Model to place this on.
|
|
new: func(type, model, arg...)
|
|
return call((var class = me.get(type)).new, [model]~arg, class),
|
|
# Non-static:
|
|
# Update anything related to a particular model. Returns whether the object needs updating:
|
|
update: func(model) return 1,
|
|
# Initialize a controller to an object (or initialize the controller itself):
|
|
init: func(model) ,
|
|
# Delete an object from this controller (or delete the controller itself):
|
|
del: func(model) ,
|
|
# Return whether this symbol/object is visible:
|
|
isVisible: func(model) return 1,
|
|
# Get the position of this symbol/object:
|
|
getpos: func(model), # default provided below
|
|
}; # of Symbol.Controller
|
|
|
|
var getpos_fromghost = func(positioned_g)
|
|
return [positioned_g.lat, positioned_g.lon];
|
|
|
|
# Generic getpos: get lat/lon from any object:
|
|
# (geo.Coord and positioned ghost currently)
|
|
Symbol.Controller.getpos = func(obj) {
|
|
if (typeof(obj) == 'ghost')
|
|
if (ghosttype(obj) == 'positioned' or ghosttype(obj) == 'Navaid')
|
|
return getpos_fromghost(obj);
|
|
else
|
|
die("bad ghost of type '"~ghosttype(obj)~"'");
|
|
if (typeof(obj) == 'hash')
|
|
if (isa(obj, geo.Coord))
|
|
return obj.latlon();
|
|
die("no suitable getpos() found! Of type: "~typeof(obj));
|
|
};
|
|
|
|
|
|
var assert_m = func(hash, member)
|
|
if (!contains(hash, member))
|
|
die("required field not found: '"~member~"'");
|
|
var assert_ms = func(hash, members...)
|
|
foreach (var m; members)
|
|
if (m != nil) assert_m(hash, m);
|
|
|
|
|
|
var DotSym = {
|
|
parents: [Symbol],
|
|
element_id: nil,
|
|
# Static/singleton:
|
|
makeinstance: func(name, hash) {
|
|
assert_ms(hash,
|
|
"element_type", # type of Canvas element
|
|
#"element_id", # optional Canvas id
|
|
#"init", # initialize routine
|
|
"draw", # init/update routine
|
|
#getpos", # get position from model in [x_units,y_units] (optional)
|
|
);
|
|
hash.parents = [DotSym];
|
|
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:
|
|
# @param group The Canvas group to add this to.
|
|
# @param model A correct object (e.g. positioned ghost) as
|
|
# expected by the .draw file that represents
|
|
# metadata like position, speed, etc.
|
|
# @param controller Optional controller "glue". Each method
|
|
# is called with the model as the only argument.
|
|
new: func(group, model, controller=nil) {
|
|
var m = {
|
|
parents: [me],
|
|
group: group,
|
|
model: model,
|
|
controller: controller == nil ? me.df_controller : controller,
|
|
element: group.createChild(
|
|
me.element_type, me.element_id
|
|
),
|
|
};
|
|
if (m.controller != nil) {
|
|
#print("Initializing controller");
|
|
m.controller.init(model);
|
|
}
|
|
else die("default controller not found");
|
|
|
|
m.init();
|
|
return m;
|
|
},
|
|
del: func() {
|
|
#print("DotSym.del()");
|
|
me.deinit();
|
|
if (me.controller != nil)
|
|
me.controller.del(me.model);
|
|
call(func me.model.del(), nil, var err=[]); # try...
|
|
if (err[0] != "No such member: del") # ... and either catch or rethrow
|
|
die(err[0]);
|
|
me.element.del();
|
|
},
|
|
# Default wrappers:
|
|
init: func() me.draw(),
|
|
deinit: func(),
|
|
update: func() {
|
|
if (me.controller != nil) {
|
|
if (!me.controller.update(me.model)) return;
|
|
elsif (!me.controller.isVisible(me.model)) {
|
|
me.element.hide();
|
|
return;
|
|
}
|
|
} else
|
|
me.element.show();
|
|
me.draw();
|
|
var pos = me.controller.getpos(me.model);
|
|
if (size(pos) == 2)
|
|
pos~=[nil]; # fall through
|
|
if (size(pos) == 3)
|
|
var (lat,lon,rotation) = pos;
|
|
else die("bad position: "~debug.dump(pos));
|
|
me.element.setGeoPosition(lat,lon);
|
|
if (rotation != nil)
|
|
me.element.setRotation(rotation);
|
|
},
|
|
}; # of DotSym
|
|
|
|
# A layer that manages a list of symbols (using delta positioned handling).
|
|
var SymbolLayer = {
|
|
# 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,
|
|
# Non-static:
|
|
df_controller: nil, # default controller
|
|
df_priority: nil, # default priority for display sorting
|
|
type: nil, # type of #Symbol to add (MANDATORY)
|
|
id: nil, # id of the group #canvas.Element (OPTIONAL)
|
|
# @param group A group to place this on.
|
|
# @param controller A controller object (parents=[SymbolLayer.Controller])
|
|
# or implementation (parents[0].parents=[SymbolLayer.Controller]).
|
|
new: func(group, controller=nil) {
|
|
var m = {
|
|
parents: [me],
|
|
group: group.createChild("group", me.id), # TODO: the id is not properly set, but would be useful for debugging purposes (VOR, FIXES, NDB etc)
|
|
list: [],
|
|
};
|
|
# FIXME: hack to expose type of layer:
|
|
if (caller(1)[1] == Map.addLayer) {
|
|
var this_type = caller(1)[0].type_arg;
|
|
if (this_type != nil)
|
|
m.group.set("symbol-layer-type", this_type);
|
|
}
|
|
if (controller == nil)
|
|
#controller = SymbolLayer.Controller.new(me.type, m);
|
|
controller = me.df_controller;
|
|
assert_m(controller, "parents");
|
|
if (controller.parents[0] == SymbolLayer.Controller)
|
|
controller = controller.new(m);
|
|
assert_m(controller, "parents");
|
|
assert_m(controller.parents[0], "parents");
|
|
if (controller.parents[0].parents[0] != SymbolLayer.Controller)
|
|
die("OOP error");
|
|
m.controller = controller;
|
|
m.searcher = geo.PositionedSearch.new(me.searchCmd, me.onAdded, me.onRemoved, m);
|
|
m.update();
|
|
return m;
|
|
},
|
|
update: func() {
|
|
me.searcher.update();
|
|
foreach (var e; me.list)
|
|
e.update();
|
|
},
|
|
del: func() {
|
|
#print("SymbolLayer.del()");
|
|
me.controller.del();
|
|
foreach (var e; me.list)
|
|
e.del();
|
|
},
|
|
findsym: func(positioned_g, del=0) {
|
|
forindex (var i; me.list) {
|
|
var e = me.list[i];
|
|
if (geo.PositionedSearch._equals(e.model, positioned_g)) {
|
|
if (del) {
|
|
# Remove this element from the list
|
|
var prev = subvec(me.list, 0, i);
|
|
var next = subvec(me.list, i+1);
|
|
me.list = prev~next;
|
|
}
|
|
return e;
|
|
}
|
|
}
|
|
return nil;
|
|
},
|
|
searchCmd: func() me.controller.searchCmd(),
|
|
# Adds a symbol.
|
|
onAdded: func(positioned_g)
|
|
append(me.list, Symbol.new(me.type, me.group, positioned_g)),
|
|
# Removes a symbol
|
|
onRemoved: func(positioned_g)
|
|
me.findsym(positioned_g, 1).del(),
|
|
}; # of SymbolLayer
|
|
|
|
# Class to manage controlling a #SymbolLayer.
|
|
# Currently handles:
|
|
# * Searching for new symbols (positioned ghosts or other objects with unique id's).
|
|
# * Updating the layer (e.g. on an update loop or on a property change).
|
|
SymbolLayer.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 layer The #SymbolLayer this controller is responsible for.
|
|
new: func(type, layer, arg...)
|
|
return call((var class = me.get(type)).new, [layer]~arg, class),
|
|
# Non-static:
|
|
run_update: func() {
|
|
me.layer.update();
|
|
},
|
|
# @return List of positioned objects.
|
|
searchCmd: func()
|
|
die("searchCmd() not implemented for this SymbolLayer.Controller type!"),
|
|
}; # of SymbolLayer.Controller
|
|
|
|
settimer(func {
|
|
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 #######
|
|
#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;
|
|
}
|
|
#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)
|
|
settimer(func {
|
|
if (caller(0)[0] != globals.canvas)
|
|
return call(caller(0)[1], arg, nil, globals.canvas);
|
|
|
|
print("Running MapStructure test code");
|
|
var TestCanvas = canvas.new({
|
|
"name": "Map Test",
|
|
"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);
|
|
else print("MapStructure.nas: Testing code disabled, see $FG_ROOT/gui/dialogs/map-canvas.xml instead");
|
|
}, 0); # end ugly module init timer hack
|
|
|