2017-11-21 02:04:19 +00:00
|
|
|
|
# A3XX ND Canvas
|
|
|
|
|
# Joshua Davidson (it0uchpods)
|
|
|
|
|
# Based on work by artix
|
|
|
|
|
|
2018-12-20 01:55:02 +00:00
|
|
|
|
# Copyright (c) 2018 Joshua Davidson (it0uchpods)
|
2017-11-21 02:04:19 +00:00
|
|
|
|
|
2017-06-19 00:14:41 +00:00
|
|
|
|
var assert_m = canvas.assert_m;
|
|
|
|
|
|
|
|
|
|
# --------------------------------
|
|
|
|
|
# From FGDATA/Nasal/canvas/api.nas
|
|
|
|
|
# --------------------------------
|
|
|
|
|
|
|
|
|
|
# Recursively get all children of class specified by first param
|
|
|
|
|
canvas.Group.getChildrenOfType = func(type, array = nil){
|
|
|
|
|
var children = array;
|
|
|
|
|
if(children == nil)
|
|
|
|
|
children = [];
|
|
|
|
|
var my_children = me.getChildren();
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(typeof(type) != "vector")
|
2017-06-19 00:14:41 +00:00
|
|
|
|
type = [type];
|
|
|
|
|
foreach(var c; my_children){
|
|
|
|
|
foreach(var t; type){
|
|
|
|
|
if(isa(c, t)){
|
|
|
|
|
append(children, c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(isa(c, canvas.Group)){
|
|
|
|
|
c.getChildrenOfType(type, children);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return children;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# Set color to children of type Path and Text. It is possible to optionally
|
|
|
|
|
# specify which types of children should be affected by passing a vector as
|
|
|
|
|
# the last agrument, ie. my_group.setColor(1,1,1,[Path]);
|
|
|
|
|
canvas.Group.setColor = func(){
|
|
|
|
|
var color = arg;
|
|
|
|
|
var types = [Path, Text];
|
|
|
|
|
var arg_c = size(color);
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(arg_c > 1 and typeof(color[-1]) == "vector"){
|
2017-06-19 00:14:41 +00:00
|
|
|
|
types = color[-1];
|
|
|
|
|
color = subvec(color, 0, arg_c - 1);
|
|
|
|
|
}
|
|
|
|
|
var children = me.getChildrenOfType(types);
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(typeof(color) == "vector"){
|
2017-06-19 00:14:41 +00:00
|
|
|
|
var first = color[0];
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(typeof(first) == "vector")
|
2017-06-19 00:14:41 +00:00
|
|
|
|
color = first;
|
|
|
|
|
}
|
|
|
|
|
foreach(var c; children)
|
|
|
|
|
c.setColor(color);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
canvas.Map.addLayer = func(factory, type_arg=nil, priority=nil, style=nil, opts=nil, visible=1)
|
|
|
|
|
{
|
|
|
|
|
if(contains(me.layers, type_arg))
|
|
|
|
|
printlog("warn", "addLayer() warning: overwriting existing layer:", type_arg);
|
|
|
|
|
var options = opts;
|
|
|
|
|
# Argument handling
|
|
|
|
|
|
|
|
|
|
if (type_arg != nil) {
|
|
|
|
|
var layer = factory.new(type:type_arg, group:me, map:me, style:style, options:options, visible:visible);
|
|
|
|
|
var type = factory.get(type_arg);
|
|
|
|
|
var key = type_arg;
|
|
|
|
|
} else {
|
|
|
|
|
var layer = factory.new(group:me, map:me, style:style, options:options, visible:visible);
|
|
|
|
|
var type = factory;
|
|
|
|
|
var key = factory.type;
|
|
|
|
|
}
|
|
|
|
|
me.layers[type_arg] = layer;
|
|
|
|
|
|
|
|
|
|
if (priority == nil)
|
|
|
|
|
priority = type.df_priority;
|
|
|
|
|
if (priority != nil)
|
|
|
|
|
layer.group.setInt("z-index", priority);
|
|
|
|
|
|
|
|
|
|
return layer; # return new layer to caller() so that we can directly work with it, i.e. to register event handlers (panning/zooming)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------
|
|
|
|
|
# From FGDATA/Nasal/canvas/MapStructure.nas
|
|
|
|
|
# -----------------------------------------
|
|
|
|
|
|
|
|
|
|
var opt_member = func(h,k) {
|
|
|
|
|
if (contains(h, k)) return h[k];
|
|
|
|
|
if (contains(h, "parents")) {
|
|
|
|
|
var _=h.parents;
|
|
|
|
|
for (var i=0;i<size(_);i+=1){
|
|
|
|
|
var v = opt_member(_[i], k);
|
|
|
|
|
if (v != nil) return v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# Symbol
|
|
|
|
|
|
|
|
|
|
canvas.Symbol.formattedString = func(frmt, model_props){
|
|
|
|
|
if(me.model == nil) return frmt;
|
|
|
|
|
var args = [];
|
|
|
|
|
foreach(var prop; model_props){
|
|
|
|
|
if(contains(me.model, prop)){
|
|
|
|
|
var val = me.model[prop];
|
|
|
|
|
var tp = typeof(val);
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(tp != "scalar"){
|
|
|
|
|
val = "";
|
2017-06-19 00:14:41 +00:00
|
|
|
|
#printlog("warn", "formattedString: invalid type for "~prop~" (" ~ tp ~ ")");
|
|
|
|
|
} else {
|
|
|
|
|
append(args, val);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return call(sprintf, [frmt] ~ args);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
canvas.Symbol.getOption = func(name, default = nil){
|
|
|
|
|
var opt = me.options;
|
|
|
|
|
if(opt == nil)
|
|
|
|
|
opt = me.layer.options;
|
|
|
|
|
if(opt == nil) return default;
|
|
|
|
|
var val = opt_member(opt, name);
|
|
|
|
|
if(val == nil) return default;
|
|
|
|
|
return val;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
canvas.Symbol.getStyle = func(name, default = nil){
|
|
|
|
|
var st = me.style;
|
|
|
|
|
if(st == nil)
|
|
|
|
|
st = me.layer.style;
|
|
|
|
|
if(st == nil) return default;
|
|
|
|
|
var val = opt_member(st, name);
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(typeof(val) == "func"){
|
2017-06-19 00:14:41 +00:00
|
|
|
|
val = (call(val,[],me));
|
|
|
|
|
}
|
|
|
|
|
if(val == nil) return default;
|
|
|
|
|
return val;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
canvas.Symbol.getLabelFromModel = func(default_val = nil){
|
|
|
|
|
if(me.model == nil) return default_val;
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(default_val == nil and contains(me.model, "id"))
|
2017-06-19 00:14:41 +00:00
|
|
|
|
default_val = me.model.id;
|
2017-11-20 22:03:07 +00:00
|
|
|
|
var label_content = me.getOption("label_content");
|
2017-06-19 00:14:41 +00:00
|
|
|
|
if(label_content == nil) return default_val;
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(typeof(label_content) == "scalar")
|
2017-06-19 00:14:41 +00:00
|
|
|
|
label_content = [label_content];
|
2017-11-20 22:03:07 +00:00
|
|
|
|
var format_s = me.getOption("label_format");
|
|
|
|
|
var label = "";
|
2017-06-19 00:14:41 +00:00
|
|
|
|
if(format_s == nil){
|
|
|
|
|
format_s = "%s";
|
|
|
|
|
}
|
|
|
|
|
return me.formattedString(format_s, label_content);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
canvas.Symbol.callback = func(name, args...){
|
2017-11-20 22:03:07 +00:00
|
|
|
|
name = name ~"_callback";
|
2017-06-19 00:14:41 +00:00
|
|
|
|
var f = me.getOption(name);
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(typeof(f) == "func"){
|
2017-06-19 00:14:41 +00:00
|
|
|
|
return call(f, args, me);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# DotSym
|
|
|
|
|
|
|
|
|
|
canvas.DotSym.update = func() {
|
|
|
|
|
if (me.controller != nil) {
|
|
|
|
|
if (!me.controller.update(me, me.model)) return;
|
|
|
|
|
elsif (!me.controller.isVisible(me.model)) {
|
|
|
|
|
me.element.hide();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else
|
|
|
|
|
me.element.show();
|
|
|
|
|
me.draw();
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(me.getOption("disable_position", 0)) return; # << CHECK FOR OPTION "disable_position"
|
2017-06-19 00:14:41 +00:00
|
|
|
|
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("DotSym.update(): bad position: "~debug.dump(pos));
|
|
|
|
|
# print(me.model.id, ": Position lat/lon: ", lat, "/", lon);
|
|
|
|
|
me.element.setGeoPosition(lat,lon);
|
|
|
|
|
if (rotation != nil)
|
|
|
|
|
me.element.setRotation(rotation);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# SVGSymbol
|
|
|
|
|
|
|
|
|
|
canvas.SVGSymbol.init = func() {
|
2017-11-20 22:03:07 +00:00
|
|
|
|
me.callback("init_before");
|
|
|
|
|
var opt_path = me.getStyle("svg_path");
|
2017-06-19 00:14:41 +00:00
|
|
|
|
if(opt_path != nil)
|
|
|
|
|
me.svg_path = opt_path;
|
|
|
|
|
if (!me.cacheable) {
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(me.svg_path != nil and me.svg_path != "")
|
2017-06-19 00:14:41 +00:00
|
|
|
|
canvas.parsesvg(me.element, me.svg_path);
|
|
|
|
|
# hack:
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if (var scale = me.layer.style["scale_factor"])
|
2017-06-19 00:14:41 +00:00
|
|
|
|
me.element.setScale(scale);
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if ((var transl = me.layer.style["translate"]) != nil)
|
2017-06-19 00:14:41 +00:00
|
|
|
|
me.element.setTranslation(transl);
|
|
|
|
|
} else {
|
|
|
|
|
__die("cacheable not implemented yet!");
|
|
|
|
|
}
|
2017-11-20 22:03:07 +00:00
|
|
|
|
me.callback("init_after");
|
2017-06-19 00:14:41 +00:00
|
|
|
|
me.draw();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
canvas.SVGSymbol.draw = func{
|
2017-11-20 22:03:07 +00:00
|
|
|
|
me.callback("draw");
|
2017-06-19 00:14:41 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# SymbolLayer
|
|
|
|
|
|
|
|
|
|
canvas.SymbolLayer._new = func(m, style, controller, options) {
|
|
|
|
|
# print("SymbolLayer setup options:", m.options!=nil);
|
|
|
|
|
m.style = default_hash(style, m.df_style);
|
|
|
|
|
m.options = default_hash(options, m.df_options);
|
|
|
|
|
|
|
|
|
|
if (controller == nil)
|
|
|
|
|
controller = m.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("MultiSymbolLayer: OOP error");
|
|
|
|
|
if(options != nil){ # << CHECK FOR CONFIGURABLE LISTENERS
|
2017-11-20 22:03:07 +00:00
|
|
|
|
var listeners = opt_member(controller, "listeners");
|
|
|
|
|
var listen = opt_member(options, "listen");
|
2017-06-19 00:14:41 +00:00
|
|
|
|
if (listen != nil and listeners != nil){
|
|
|
|
|
var listen_tp = typeof(listen);
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(listen_tp != "vector" and listen_tp != "scalar")
|
2017-06-19 00:14:41 +00:00
|
|
|
|
__die("Options 'listen' cannot be a "~ listen_tp);
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(typeof(listen) == "scalar")
|
2017-06-19 00:14:41 +00:00
|
|
|
|
listen = [listen];
|
|
|
|
|
foreach(var node_name; listen){
|
|
|
|
|
var node = opt_member(options, node_name);
|
|
|
|
|
if(node == nil)
|
|
|
|
|
node = node_name;
|
|
|
|
|
append(controller.listeners,
|
|
|
|
|
setlistener(node, func call(m.update,[],m),0,0));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m.controller = controller;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# LineSymbol
|
|
|
|
|
|
|
|
|
|
canvas.LineSymbol.new = func(group, layer, model, controller=nil) {
|
|
|
|
|
if (me == nil) __die("Need me reference for LineSymbol.new()");
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if (typeof(model) != "vector") {
|
|
|
|
|
if(typeof(model) == "hash"){
|
|
|
|
|
if(!contains(model, "path"))
|
2017-06-19 00:14:41 +00:00
|
|
|
|
canvas.__die("LineSymbol.new(): model hash requires path");
|
|
|
|
|
}
|
|
|
|
|
else canvas.__die("LineSymbol.new(): need a vector of points or a hash");
|
|
|
|
|
}
|
|
|
|
|
var m = {
|
|
|
|
|
parents: [me],
|
|
|
|
|
group: group,
|
|
|
|
|
layer: layer,
|
|
|
|
|
model: model,
|
|
|
|
|
controller: controller == nil ? me.df_controller : controller,
|
|
|
|
|
element: group.createChild(
|
|
|
|
|
"path", me.element_id
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
append(m.parents, m.element);
|
|
|
|
|
canvas.Symbol._new(m);
|
|
|
|
|
|
|
|
|
|
m.init();
|
|
|
|
|
return m;
|
|
|
|
|
};
|
|
|
|
|
# Non-static:
|
|
|
|
|
canvas.LineSymbol.draw = func() {
|
2017-11-20 22:03:07 +00:00
|
|
|
|
me.callback("draw_before");
|
2017-06-19 00:14:41 +00:00
|
|
|
|
if (!me.needs_update) return;
|
|
|
|
|
#printlog(_MP_dbg_lvl, "redrawing a LineSymbol "~me.layer.type);
|
|
|
|
|
me.element.reset();
|
|
|
|
|
var cmds = [];
|
|
|
|
|
var coords = [];
|
|
|
|
|
var cmd = canvas.Path.VG_MOVE_TO;
|
|
|
|
|
var path = me.model;
|
2017-11-20 22:03:07 +00:00
|
|
|
|
if(typeof(path) == "hash"){
|
2017-06-19 00:14:41 +00:00
|
|
|
|
path = me.model.path;
|
|
|
|
|
if(path == nil)
|
|
|
|
|
canvas.__die("LineSymbol model requires a 'path' member (vector)");
|
|
|
|
|
}
|
|
|
|
|
foreach (var m; path) {
|
|
|
|
|
if(size(keys(m)) >= 2){
|
|
|
|
|
var (lat,lon) = me.controller.getpos(m);
|
|
|
|
|
append(coords,"N"~lat);
|
|
|
|
|
append(coords,"E"~lon);
|
|
|
|
|
append(cmds,cmd);
|
|
|
|
|
cmd = canvas.Path.VG_LINE_TO;
|
|
|
|
|
} else {
|
|
|
|
|
cmd = canvas.Path.VG_MOVE_TO;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
me.element.setDataGeo(cmds, coords);
|
2017-11-20 22:03:07 +00:00
|
|
|
|
me.element.update(); # this doesn"t help with flickering, it seems
|
|
|
|
|
me.callback("draw_after");
|
2017-06-19 00:14:41 +00:00
|
|
|
|
};
|
|
|
|
|
|