1
0
Fork 0

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

This commit is contained in:
BARANGER Emmanuel 2014-06-05 00:34:43 +02:00
commit 1db2300263
63 changed files with 29045 additions and 2210 deletions

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 66 KiB

File diff suppressed because it is too large Load diff

View file

@ -65,5 +65,13 @@ var PropertyElement = {
{
me._node.getNode(key, 1).getBoolValue();
},
# Trigger an update of the element
#
# Elements are automatically updated once a frame, with a delay of one frame.
# If you wan't to get an element updated in the current frame you have to use
# this method.
update: func
{
me.setBool("update", 1);
}
};

View file

@ -161,6 +161,11 @@ var Element = {
return factory(parent_ghost);
},
# Get the canvas this element is placed on
getCanvas: func()
{
wrapCanvas(me._getCanvas());
},
# Check if elements represent same instance
#
# @param el Other Element or element ghost
@ -168,15 +173,6 @@ var Element = {
{
return me._node.equals(el._node_ghost);
},
# Trigger an update of the element
#
# Elements are automatically updated once a frame, with a delay of one frame.
# If you wan't to get an element updated in the current frame you have to use
# this method.
update: func()
{
me.setInt("update", 1);
},
# Hide/Show element
#
# @param visible Whether the element should be visible
@ -416,6 +412,7 @@ var Group = {
# ==============================================================================
# Class for a group element on a canvas with possibly geopgraphic positions
# which automatically get projected according to the specified projection.
# Each map consists of an arbitrary number of layers (canvas groups)
#
var Map = {
df_controller: nil,
@ -436,7 +433,7 @@ var Map = {
me.parents = subvec(me.parents,1);
me.del();
},
setController: func(controller=nil)
setController: func(controller=nil, arg...)
{
if (me.controller != nil) me.controller.del(me);
if (controller == nil)
@ -449,7 +446,7 @@ var Map = {
} else {
if (!isa(controller, Map.Controller))
die("OOP error: controller needs to inherit from Map.Controller");
me.controller = call(func controller.new(me), nil, var err=[]); # try...
me.controller = call(controller.new, [me]~arg, controller, var err=[]); # try...
if (size(err)) {
if (err[0] != "No such member: new") # ... and either catch or rethrow
die(err[0]);
@ -463,25 +460,29 @@ var Map = {
return me;
},
addLayer: func(factory, type_arg=nil, priority=nil, style=nil, options=nil)
addLayer: func(factory, type_arg=nil, priority=nil, style=nil, options=nil, visible=1)
{
if(contains(me.layers, type_arg))
printlog("warn", "addLayer() warning: overwriting existing layer:", type_arg);
# print("addLayer():", type_arg);
# Argument handling
if (type_arg != nil)
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);
else var type = factory;
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;
me.layers[type_arg] = type.new(group:me, map:me, style:style,options:options);
if (priority == nil)
priority = type.df_priority;
if (priority != nil)
me.layers[type_arg].group.setInt("z-index", priority);
layer.group.setInt("z-index", priority);
return me;
return layer; # return new layer to caller() so that we can directly work with it, i.e. to register event handlers (panning/zooming)
},
getLayer: func(type_arg) me.layers[type_arg],
@ -490,6 +491,7 @@ var Map = {
setPos: func(lat, lon, hdg=nil, range=nil, alt=nil)
{
# TODO: also propage setPos events to layers and symbols (e.g. for offset maps)
me.set("ref-lat", lat);
me.set("ref-lon", lon);
if (hdg != nil)
@ -497,7 +499,7 @@ var Map = {
if (range != nil)
me.setRange(range);
if (alt != nil)
me.set("altitude", hdg);
me.set("altitude", alt);
},
getPos: func
{
@ -513,6 +515,10 @@ var Map = {
getAlt: func me.get("altitude"),
getRange: func me.get("range"),
getLatLon: func [me.get("ref-lat"), me.get("ref-lon")],
# N.B.: This always returns the same geo.Coord object,
# so its values can and will change at any time (call
# update() on the coord to ensure it is up-to-date,
# which basically calls this method again).
getPosCoord: func
{
var (lat, lon) = (me.get("ref-lat"),
@ -526,6 +532,8 @@ var Map = {
}
if (!contains(me, "coord")) {
me.coord = geo.Coord.new();
var m = me;
me.coord.update = func m.getPosCoord();
}
me.coord.set_latlon(lat,lon,alt or 0);
return me.coord;
@ -534,12 +542,14 @@ var Map = {
# me.controller.
update: func(predicate=nil)
{
var t = systime();
foreach (var l; keys(me.layers)) {
var layer = me.layers[l];
# Only update if the predicate allows
if (predicate == nil or predicate(layer))
call(layer.update, arg, layer);
layer.update();
}
printlog(_MP_dbg_lvl, "Took "~((systime()-t)*1000)~"ms to update map()");
return me;
},
};
@ -745,6 +755,24 @@ var Path = {
return me;
},
addSegmentGeo: func(cmd, coords...)
{
var coords = _arg2valarray(coords);
var num_coords = me.num_coords[cmd];
if( size(coords) != num_coords )
debug.warn
(
"Invalid number of arguments (expected " ~ num_coords ~ ")"
);
else
{
me.setInt("cmd[" ~ (me._last_cmd += 1) ~ "]", cmd);
for(var i = 0; i < num_coords; i += 1)
me.set("coord-geo[" ~ (me._last_coord += 1) ~ "]", coords[i]);
}
return me;
},
# Remove first segment
pop_front: func me._removeSegment(1),
# Remove last segment
@ -981,8 +1009,22 @@ var Image = {
# @param bottom Rectangle maximum y coordinate
# @param normalized Whether to use normalized ([0,1]) or image
# ([0, image_width]/[0, image_height]) coordinates
setSourceRect: func(left, top, right, bottom, normalized = 1)
setSourceRect: func
{
# Work with both positional arguments and named arguments.
# Support first argument being a vector instead of four separate ones.
if (size(arg) == 1)
arg = arg[0];
elsif (size(arg) and size(arg) < 4 and typeof(arg[0]) == 'vector')
arg = arg[0]~arg[1:];
if (!contains(caller(0)[0], "normalized")) {
if (size(arg) > 4)
var normalized = arg[4];
else var normalized = 1;
}
if (size(arg) >= 3)
var (left,top,right,bottom) = arg;
me._node.getNode("source", 1).setValues({
left: left,
top: top,
@ -1033,7 +1075,7 @@ var Canvas = {
# given every texture of the model will be replaced.
addPlacement: func(vals)
{
var placement = me.texture.addChild("placement", 0, 0);
var placement = me._node.addChild("placement", 0, 0);
placement.setValues(vals);
return placement;
},
@ -1052,31 +1094,32 @@ var Canvas = {
# Set the background color
#
# @param color Vector of 3 or 4 values in [0, 1]
setColorBackground: func () { me.texture.getNode('background', 1).setValue(_getColor(arg)); me; },
getColorBackground: func me.texture.get('background'),
setColorBackground: func me.set('background', _getColor(arg)),
getColorBackground: func me.get('background'),
# Get path of canvas to be used eg. in Image::setFile
getPath: func()
{
return "canvas://by-index/texture[" ~ me.texture.getIndex() ~ "]";
return "canvas://by-index/texture[" ~ me._node.getIndex() ~ "]";
},
# Destructor
#
# releases associated canvas and makes this object unusable
del: func
{
me.texture.remove();
me._node.remove();
me.parents = nil; # ensure all ghosts get destroyed
}
};
var wrapCanvas = func(canvas_ghost)
# @param g Canvas ghost
var wrapCanvas = func(g)
{
var m = {
parents: [PropertyElement, Canvas, canvas_ghost],
texture: props.wrapNode(canvas_ghost._node_ghost)
};
m._node = m.texture;
return m;
if( g != nil and g._impl == nil )
g._impl = {
parents: [PropertyElement, Canvas],
_node: props.wrapNode(g._node_ghost)
};
return g;
}
# Create a new canvas. Pass parameters as hash, eg:
@ -1090,7 +1133,7 @@ var wrapCanvas = func(canvas_ghost)
var new = func(vals)
{
var m = wrapCanvas(_newCanvasGhost());
m.texture.setValues(vals);
m._node.setValues(vals);
return m;
};
@ -1108,11 +1151,7 @@ var get = func(arg)
else
die("canvas.new: Invalid argument.");
var canvas_ghost = _getCanvasGhost(node._g);
if( canvas_ghost == nil )
return nil;
return wrapCanvas(canvas_ghost);
return wrapCanvas(_getCanvasGhost(node._g));
};
var getDesktop = func()

View file

@ -1,23 +0,0 @@
Nothing set in stone yet, we'll document things once the API becomes more stable and once it has been used in several dialogs and instruments
At the moment, this implements the notion of a "LayeredMap", a LayeredMap is a conventional Canvas Map which has support for easily managing "Layers",
which are internally mapped to Canvas Groups. Each Group's "visible" property is managed by the LayeredMap, so that layers can be easily
toggled on/off, i.e. via checkboxes.
Basically, the idea is this, we'll have a MVC (Model/View/Controller) setup, where:
- the Model is mapped to the drawable's meta information (i.e. position)
- the View is mapped to a conventional canvas group
- the Controller is mapped to a bunch of property/timer callbacks to control the Model/View
Model = PositionedSource
View = Canvas
Controller = control properties (zoom, range etc)
LayerElement = callback to create a canvas group
Layer = canvas.Group
Map -> LayeredMap -> GenericMap -> AirportMap

View file

@ -31,13 +31,14 @@ var WindowButton = {
_onStateChange: func
{
var file = style._dir_decoration ~ "/" ~ me._name;
file ~= me._window._focused ? "_focused" : "_unfocused";
var window_focus = me._windowFocus();
file ~= window_focus ? "_focused" : "_unfocused";
if( me._active )
file ~= "_pressed";
else if( me._hover )
file ~= "_prelight";
else if( me._window._focused )
else if( window_focus )
file ~= "_normal";
me._root.set("src", file ~ ".png");
@ -53,6 +54,7 @@ var Window = {
var ghost = _newWindowGhost(id);
var m = {
parents: [Window, PropertyElement, ghost],
_ghost: ghost,
_node: props.wrapNode(ghost._node_ghost),
_focused: 0,
_focused_widget: nil,
@ -80,7 +82,7 @@ var Window = {
if( me["_canvas"] != nil )
{
var placements = me._canvas.texture.getChildren("placement");
var placements = me._canvas._node.getChildren("placement");
# Do not remove canvas if other placements exist
if( size(placements) > 1 )
foreach(var p; placements)
@ -124,7 +126,10 @@ var Window = {
"blend-destination-alpha": "one"
});
me._canvas._focused_widget = nil;
me._canvas.data("focused", me._focused);
me._canvas.addEventListener("mousedown", func me.raise());
return me._canvas;
},
# Set an existing canvas to be used for this Window
@ -135,26 +140,30 @@ var Window = {
canvas_.addPlacement({type: "window", "id": me.get("id")});
me['_canvas'] = canvas_;
canvas_.data("focused", me._focused);
# prevent resizing if canvas is placed from somewhere else
me.onResize = nil;
},
# Get the displayed canvas
getCanvas: func()
getCanvas: func(create = 0)
{
if( me['_canvas'] == nil and create )
me.createCanvas();
return me['_canvas'];
},
getCanvasDecoration: func()
{
return wrapCanvas(me._getCanvasDecoration());
},
addWidget: func(w)
setLayout: func(l)
{
append(me._widgets, w);
w._window = me;
if( size(me._widgets) == 2 )
w.setFocus();
w._onStateChange();
if( me['_canvas'] == nil )
me.createCanvas();
me._canvas.update(); # Ensure placement is applied
me._ghost.setLayout(l);
return me;
},
#
@ -223,6 +232,8 @@ var Window = {
# protected:
_onStateChange: func
{
var event = canvas.CustomEvent.new("wm.focus-" ~ (me._focused ? "in" : "out"));
if( me._getCanvasDecoration() != nil )
{
# Stronger shadow for focused windows
@ -233,10 +244,16 @@ var Window = {
me._title_bar_bg.set("fill", style.getColor("title" ~ suffix));
me._title.set( "fill", style.getColor("title-text" ~ suffix));
me._top_line.set( "stroke", style.getColor("title-highlight" ~ suffix));
me.getCanvasDecoration()
.data("focused", me._focused)
.dispatchEvent(event);
}
foreach(var w; me._widgets)
w._onStateChange();
if( me.getCanvas() != nil )
me.getCanvas()
.data("focused", me._focused)
.dispatchEvent(event);
},
# private:
_propCallback: func(child, mode)
@ -388,7 +405,6 @@ var Window = {
var button_close = WindowButton.new(title_bar, "close")
.move(x, y);
button_close.onClick = func me.del();
me.addWidget(button_close);
# title
me._title = title_bar.createChild("text", "title")
@ -435,6 +451,32 @@ var Dialog = {
}
};
var createLayoutTest = func
{
var dlg = canvas.Window.new([350,250], "dialog")
.set("resize", 1);
dlg.getCanvas(1)
.set("background", style.getColor("bg_color"));
var root = dlg.getCanvas().createGroup();
var vbox = VBoxLayout.new();
dlg.setLayout(vbox);
var b = gui.widgets.Button.new(root, style, {}).setText("Stretch");
b.setMaximumSize([9999, 9999]);
vbox.addItem(b, 1);
var button_box = HBoxLayout.new();
vbox.addItem(button_box);
var b1 = gui.widgets.Button.new(root, style, {}).setText("Ok");
button_box.addItem(b1);
b1.setFocus();
var b2 = gui.widgets.Button.new(root, style, {}).setText("Abort");
button_box.addItem(b2);
}
# Canvas GUI demo
#
# Shows an icon in the top-right corner which upon click opens a simple window
@ -453,11 +495,13 @@ var initDemo = func
{
debug.dump( props.wrapNode(event.target._node_ghost) );
});
my_canvas.addEventListener("click", func
my_canvas.addEventListener("click", func(e)
{
if( e.button == 1 )
return createLayoutTest();
var dlg = canvas.Window.new([400,300], "dialog")
.set("resize", 1);
var my_canvas = dlg.createCanvas()
.set("background", style.getColor("bg_color"));
@ -466,6 +510,7 @@ var initDemo = func
my_canvas.addEventListener("drag", func(e) { printf("drag: screen(%.1f|%.1f) client(%.1f|%.1f) local(%.1f|%.1f) delta(%.1f|%.1f)", e.screenX, e.screenY, e.clientX, e.clientY, e.localX, e.localY, e.deltaX, e.deltaY); });
my_canvas.addEventListener("wheel", func(e) { printf("wheel: screen(%.1f|%.1f) client(%.1f|%.1f) %.1f", e.screenX, e.screenY, e.clientX, e.clientY, e.deltaY); });
var root = my_canvas.createGroup();
root.addEventListener("test", func(e) { printf("test: %s", e.detail.test); });
root.createChild("image")
.set("src", "http://wiki.flightgear.org/skins/common/images/icons-fg-135.png");
var text =
@ -487,27 +532,27 @@ root.createChild("image")
.set("fill", "#ff0000")
.hide();
var visible_count = 0;
text.addEventListener("click", func root.dispatchEvent(canvas.CustomEvent.new("test", {detail: {"test": "some important data.."}})));
text.addEventListener("mouseover", func text_move.show());
text.addEventListener("mouseout", func text_move.hide());
text.addEventListener("mousemove", func(e) { printf("move: screen(%.1f|%.1f) client(%.1f|%.1f) local(%.1f|%.1f) delta(%.1f|%.1f)", e.screenX, e.screenY, e.clientX, e.clientY, e.localX, e.localY, e.deltaX, e.deltaY); });
text.set("fill", style.getColor("text_color"));
dlg.addWidget( gui.widgets.Button.new(root, style, {size: [64, 26]})
.setText("Ok")
.move(20, 250) );
dlg.addWidget( gui.widgets.Button.new(root, style, {size: [64, 26]})
.setText("Apply")
.move(100, 250) );
dlg.addWidget( gui.widgets.Button.new(root, style, {size: [64, 64]})
.setText("Cancel")
.move(180, 200) );
gui.widgets.Button.new(root, style, {size: [64, 26]})
.setText("Ok")
.move(20, 250);
gui.widgets.Button.new(root, style, {size: [64, 26]})
.setText("Apply")
.move(100, 250);
gui.widgets.Button.new(root, style, {size: [64, 64]})
.setText("Cancel")
.move(180, 200);
var scroll = gui.widgets.ScrollArea.new(root, style, {size: [96, 128]})
.move(20, 100);
var txt = scroll.getContent().createChild("text")
.set("text", "01hallo\n02asdasd\n03\n04\n05asdasd06\n07ß\n08\n09asdasd\n10\n11");
scroll.update();
dlg.addWidget(scroll);
txt.addEventListener("mouseover", func txt.set("fill", "red"));
txt.addEventListener("mouseout", func txt.set("fill", "green"));

View file

@ -8,14 +8,14 @@ gui.Widget = {
#
new: func(derived)
{
return {
return canvas.Widget.new({
parents: [derived, gui.Widget],
_focused: 0,
_focus_policy: gui.Widget.NoFocus,
_hover: 0,
_root: nil,
_size: [64, 64]
};
});
},
# Move the widget to the given position (relative to its parent)
move: func(x, y)
@ -35,11 +35,12 @@ gui.Widget = {
if( me._focused )
return me;
if( me._window._focused_widget != nil )
me._window._focused_widget.clearFocus();
var canvas = me.getCanvas();
if( canvas._focused_widget != nil )
canvas._focused_widget.clearFocus();
me._focused = 1;
me._window._focused_widget = me;
canvas._focused_widget = me;
me.onFocusIn();
me._onStateChange();
@ -53,7 +54,7 @@ gui.Widget = {
return me;
me._focused = 0;
me._window._focused_widget = nil;
me.getCanvas()._focused_widget = nil;
me.onFocusOut();
me._onStateChange();
@ -65,10 +66,22 @@ gui.Widget = {
onMouseEnter: func {},
onMouseLeave: func {},
# protected:
_MAX_SIZE: 32768, # size for "no size-limit"
_onStateChange: func {},
_setRoot: func(el)
{
me._root = el;
var canvas = el.getCanvas();
me.setCanvas(canvas);
canvas.addEventListener("wm.focus-in", func {
me._onStateChange();
});
canvas.addEventListener("wm.focus-out", func {
me._onStateChange();
});
el.addEventListener("mouseenter", func {
me._hover = 1;
me.onMouseEnter();
@ -76,15 +89,17 @@ gui.Widget = {
});
el.addEventListener("mousedown", func {
if( bits.test(me._focus_policy, me.ClickFocus / 2) )
{
me.setFocus();
me._window.setFocus();
}
});
el.addEventListener("mouseleave", func {
me._hover = 0;
me.onMouseLeave();
me._onStateChange();
});
},
_windowFocus: func
{
var canvas = me.getCanvas();
return canvas != nil ? canvas.data("focused") : 0;
}
};

View file

@ -28,23 +28,29 @@ DefaultStyle.widgets.button = {
new: func(parent, cfg)
{
me.element = parent.createChild("group", "button");
me.size = cfg.get("size", [26, 26]);
me._bg =
me.element.rect( 3,
3,
me.size[0] - 6,
me.size[1] - 6,
{"border-radius": 5} );
me.element.createChild("path");
me._border =
me.element.createChild("image", "button")
.set("slice", "10 12") #"7")
.setSize(me.size);
.set("slice", "10 12"); #"7")
me._label =
me.element.createChild("text")
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
.set("character-size", 14)
.set("alignment", "center-baseline");
me.setSize( cfg.get("size", [26, 26]) );
},
setSize: func(size)
{
me._bg.reset()
.rect( 3,
3,
size[0] - 6,
size[1] - 6,
{"border-radius": 5} );
me._border.setSize(size);
me.size = size;
},
setText: func(text)
{

View file

@ -13,6 +13,12 @@ gui.widgets.Button = {
m._setRoot(m._button.element);
}
m.setMinimumSize([16, 16]);
m.setSizeHint([32, 32]);
m.setMaximumSize([m._MAX_SIZE, m._MAX_SIZE]);
m.setSetGeometryFunc(m.setGeometry);
return m;
},
setText: func(text)
@ -39,11 +45,18 @@ gui.widgets.Button = {
return me;
},
onClick: func {},
setGeometry: func(geom)
{
me.move(geom[0], geom[1]);
me._button.setSize([geom[2] - geom[0], geom[3] - geom[1]]);
me._onStateChange();
return me;
},
# protected:
_onStateChange: func
{
if( me._button != nil )
me._button.update(me._active, me._focused, me._hover, !me._window._focused);
me._button.update(me._active, me._focused, me._hover, !me._windowFocus());
},
_setRoot: func(el)
{

View file

@ -0,0 +1,46 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'ALT-profile';
var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [MultiSymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
df_options: { # default configuration options
active_node: "/autopilot/route-manager/active",
vnav_node: "/autopilot/route-manager/vnav/",
types: ["tc", "td", "sc", "ed"],
}
});
var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
map: layer.map,
listeners: [],
};
layer.searcher._equals = func(a,b) a.getName() == b.getName();
append(m.listeners, setlistener(layer.options.active_node, func m.layer.update() ));
m.addVisibilityListener();
return m;
};
var del = func() {
foreach (var l; me.listeners)
removelistener(l);
};
var searchCmd = func {
var results = [];
var symNode = props.globals.getNode(me.layer.options.vnav_node);
if (symNode != nil)
foreach (var t; me.layer.options.types) {
var n = symNode.getNode(t);
if (n != nil)
append(results, n);
}
return results;
}

View file

@ -0,0 +1,32 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'ALT-profile';
var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
SymbolLayer.get(name).df_style = {
radius: 13,
};
var element_type = "group";
var init = func {
var name = me.model.getName();
var disptext = chr(string.toupper(name[0]))~"/"~chr(string.toupper(name[1]));
var radius = me.style.radius;
me.element.createChild("path")
.setStrokeLineWidth(5)
.moveTo(-radius, 0)
.arcLargeCW(radius, radius, 0, 2 * radius, 0)
.arcLargeCW(radius, radius, 0, -2 * radius, 0)
.setColor(0.195,0.96,0.097);
me.element.createChild("text")
.setDrawMode( canvas.Text.TEXT )
.setText(disptext)
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
.setFontSize(28)
.setTranslation(25,35)
.setColor(0.195,0.96,0.097);
}

View file

@ -5,23 +5,18 @@ var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
parents: [SingleSymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
df_style: {},
});
# N.B.: if used, this SymbolLayer should be updated every frame
# by the Map Controller, or as often as the position is changed.
var new = func(layer) {
layer.searcher._equals = func(a,b) {
a == b;
}
return {
parents: [__self__],
map: layer.map,
_model: layer.map.getPosCoord(),
};
};
var del = func;
var searchCmd = func {
var c = me.map.getPosCoord();
return [c];
};

View file

@ -1,9 +0,0 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'APS';
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

View file

@ -1,16 +1,19 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'APS';
var parents = [DotSym];
var parents = [SVGSymbol];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
var element_type = "group";
var svg_path = "Nasal/canvas/map/Images/boeingAirplane.svg";
var element_id = "airplane";
var init = func {
canvas.parsesvg(me.element, "Nasal/canvas/map/boeingAirplane.svg");
me.draw();
# Rotate with the main aircraft.
# Will have to be adapted if intended for use with other aircraft
# (but one could simply copy the layer for that).
var draw = func {
var rot = getprop("/orientation/heading-deg");
rot -= me.layer.map.getHdg();
me.element.setRotation(rot*D2R);
};
var draw = func me.element.setRotation(me.layer.map.getHdg());

View file

@ -5,9 +5,18 @@ var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
parents: [MultiSymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
df_style: {
line_width: 3,
scale_factor: 1,
debug: 1,
color_default: [0,0.6,0.85],
label_font_color:[0,0.6,0.85],
label_font_size: 28,
},
});
var a_instance = nil;
var new = func(layer) {
@ -17,7 +26,8 @@ var new = func(layer) {
map: layer.map,
listeners: [],
};
__self__.a_instance = m;
m.addVisibilityListener();
return m;
};
var del = func() {

View file

@ -1,13 +0,0 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'APT';
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

@ -6,47 +6,28 @@ 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 icon_apt = nil;
var text_apt = nil;
# add the draw routine from airports-nd.draw here
var draw = func {
if (me.icon_fix != nil) return;
var icon_apt = me.element.createChild("path", name ~ " icon" )
.moveTo(-17,0)
.arcSmallCW(17,17,0,34,0)
.arcSmallCW(17,17,0,-34,0)
.close()
.setColor(0,0.6,0.85)
.setStrokeLineWidth(3);
var text_apt = me.element.createChild("text", name ~ " label")
.setDrawMode( canvas.Text.TEXT )
.setTranslation(17,35)
.setText(me.model.id)
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
.setColor(0,0.6,0.85)
.setFontSize(28);
#me.element.setGeoPosition(lat, lon)
# .set("z-index",1); # FIXME: this needs to be configurable!!
# disabled:
if(0) {
# the fix symbol
me.icon_fix = me.element.createChild("path")
.moveTo(-15,15)
.lineTo(0,-15)
.lineTo(15,15)
var init = func {
var icon_apt = me.element.createChild("path", name ~ " icon" )
.moveTo(-17,0)
.arcSmallCW(17,17,0,34,0)
.arcSmallCW(17,17,0,-34,0)
.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")
.setColor(me.layer.style.color_default)
.setStrokeLineWidth(me.layer.style.line_width);
var text_apt = me.element.createChild("text", name ~ " label")
.setDrawMode( canvas.Text.TEXT )
.setTranslation(17,35)
.setText(me.model.id)
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
.setFontSize(28)
.setTranslation(5,25);
}
};
.setColor(me.layer.style.label_font_color)
.setFontSize(me.layer.style.label_font_size);
# FIXME: this applies scale to the whole group, better do this separately for each element?
me.element.setScale(me.layer.style.scale_factor);
};
var draw = func;

View file

@ -5,7 +5,7 @@ var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
parents: [NavaidSymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
df_style: {
@ -25,33 +25,16 @@ var new = func(layer) {
listeners: [],
query_type:'dme',
};
##
# default styling parameters - can be overridden via addLayer( style:{key:value, ...} )
if (contains(m.layer,'style')) return m; # we already have a proper style
# otherwise, set up a default style:
m.layer.style={};
m.layer.style.debug = 0; # HACK: setting this enables benchmarking and printlog statements
m.layer.style.animation_test = 0;
# these are used by the draw() routines, see DME.symbol
m.layer.style.scale_factor = 1.0 ; # applied to the whole group for now
m.layer.style.line_width = 3.0;
m.layer.style.color_tuned = [0,1,0];
m.layer.style.color_default = [0,0.6,0.85];
m.addVisibilityListener();
return m;
};
## TODO: also move this to generator helper
var del = func() {
foreach (var l; me.listeners)
removelistener(l);
};
var searchCmd = func {
printlog(_MP_dbg_lvl, "Running query:", me.query_type);
var range = me.map.getRange();
if (range == nil) return;
return positioned.findWithinRange(me.map.getPosCoord(), range, me.query_type);
};
var searchCmd = NavaidSymbolLayer.make(query:'dme');

View file

@ -1,9 +0,0 @@
# See: http://wiki.flightgear.org/MapStructure
# 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, symbol) ; # this controller doesn't need an instance

View file

@ -49,11 +49,6 @@ var del = func {
# var DMEIcon = StyleableElement.new( [{color:IconColor}, ] );
###
# helper to tell if the symbol is already initialized or not
# TODO: encapsulate API-wise (this is a very common thing to do...)
var is_initialized = func me.icon_dme != nil;
###
# FIXME: these should probably be part of MapStructure itself
# TODO: Need to come up with a StyleableElement class for this sort of stuff
@ -64,7 +59,7 @@ var apply_styling = func {
var current_color = me.icon_dme.getColor();
var required_color = nil;
if (typeof(me.layer.map.controller["is_tuned"]) == 'func' and me.layer.map.controller.is_tuned(me.model.frequency/100))
if (typeof(me.map.controller["is_tuned"]) == 'func' and me.map.controller.is_tuned(me.model.frequency/100))
#TODO: once we support caching/instancing, we cannot just change the symbol like this - we need to use a different symbol from the cache instead
# which is why these things need to be done ONCE during init to set up a cache entry for each symbol variation to come up with a corresponding raster image
# TODO: API-wise it would make sense to maintain a vector of required keys, so that the style hash can be validated in the ctor of the layer
@ -90,7 +85,6 @@ var apply_styling = func {
# TODO: also needs to be aware of current range, so that proper LOD handling can be implemented
var apply_scale = func {
# add all symbols here that need scaling
# print("Scaling:", me.layer.style.scale_factor);
me.icon_dme.setScale( me.layer.style.scale_factor );
}
@ -106,52 +100,39 @@ var init_cache = func {
##
# init is done separately to prepare support for caching (instanced symbols)
# NOTE: People should not be "hard-coding" things like color/size here
# these need to be encapsulated via a Hash lookup, so that things can be
# easily customized
#
var init_symbols = func {
# debug.dump(me.layer.style);
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( me.layer.style.line_width ); #TODO: this should be style-able
var init = func {
# debug.dump(me.layer.style);
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( me.layer.style.line_width );
# finally scale the symbol as requested, this is done last so that people can override this when creating the layer
me.apply_scale();
# finally scale the symbol as requested, this is done last so that people can override this when creating the layer
me.apply_scale();
if (me.layer.style.animation_test) {
me.timer = maketimer(0.33, func me.animate() );
me.timer.start();
}
}
var updateRun = func {
# check if the current station is tuned or not - and style the symbol accordingly (color)
me.apply_styling();
me.draw();
}
##
# this method is REQUIRED (basically, the entry point for drawing - most others are just helpers)
# this method is REQUIRED
# check if the current station is tuned or not - and style the symbol accordingly (color)
var draw = func {
# print("DME:draw()");
# Init: will set up the symbol if it isn't already
if ( !me.is_initialized() )
me.init_symbols();
# wrapper for custom styling, based on tuned/default colors (see lookup hash above)
me.updateRun();
me.apply_styling();
};

View file

@ -5,17 +5,9 @@ var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
parents: [NavaidSymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
df_style: {
line_width: 3,
scale_factor: 0.5,
debug: 1,
color_default: [0, 0.6, 0.85],
}
});
var new = func(layer) {
var m = {
@ -25,6 +17,8 @@ var new = func(layer) {
listeners: [],
query_type:'fix',
};
m.addVisibilityListener();
return m;
};
var del = func() {
@ -33,10 +27,5 @@ var del = func() {
removelistener(l);
};
var searchCmd = func {
printlog(_MP_dbg_lvl, "Running query:", me.query_type);
var range = me.map.getRange();
if (range == nil) return;
return positioned.findWithinRange(me.map.getPosCoord(), range, me.query_type);
};
var searchCmd = NavaidSymbolLayer.make('fix');

View file

@ -1,9 +0,0 @@
# See: http://wiki.flightgear.org/MapStructure
# 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

View file

@ -5,45 +5,45 @@ var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
SymbolLayer.get(name).df_style = {
line_width: 3,
scale_factor: 1,
font: "LiberationFonts/LiberationSans-Regular.ttf",
font_color: [0,0,0],
font_size: 28,
color: [0, 0.6, 0.85],
show_labels: 1,
};
var element_type = "group"; # we want a group, becomes "me.element"
var icon_fix = nil;
var text_fix = nil;
##
# used during initialization to populate the symbol cache with a FIX symbol
#
var drawFIX = func(color, width) func(group) {
var symbol = group.createChild("path")
var drawFIX = func(group) {
group.createChild("path")
.moveTo(-15,15)
.lineTo(0,-15)
.lineTo(15,15)
.close()
.setStrokeLineWidth(width)
.setColor(color)
.setScale(0.5,0.5); # FIXME: do proper LOD handling here - we need to scale according to current texture dimensions vs. original/design dimensions
return symbol;
.setStrokeLineWidth(line_width)
.setColor(color);
}
var icon_fix_cached = [
SymbolCache32x32.add(
name: "FIX",
callback: drawFIX( color:[0, 0.6, 0.85], width:3 ), # TODO: use the style hash to encapsulate styling stuff
draw_mode: SymbolCache.DRAW_CENTERED
)
];
var cache = StyleableCacheable.new(
name:name, draw_func: drawFIX,
cache: SymbolCache32x32,
draw_mode: SymbolCache.DRAW_CENTERED,
relevant_keys: ["line_width", "color"],
);
var draw = func {
if (me.icon_fix != nil) return; # fix icon already initialized
# initialize the fix symbol
me.icon_fix = icon_fix_cached[0].render(me.element);
var init = func {
# initialize the cached fix symbol
cache.render(me.element, me.style).setScale(me.style.scale_factor);
# non-cached stuff:
# FIXME: labels need to be LOD-aware (i.e. aware of MapController range, so that we can hide/show them)
me.text_fix = me.element.createChild("text")
.setDrawMode( canvas.Text.TEXT )
.setText(me.model.id)
.setFont("LiberationFonts/LiberationSans-Regular.ttf") # TODO: encapsulate styling stuff
.setFontSize(28) # TODO: encapsulate styling stuff
.setTranslation(5,25);
# non-cached stuff:
if (me.style.show_labels)
me.text_fix = me.newText(me.model.id).setScale(me.style.scale_factor);
}
var draw = func;

View file

@ -0,0 +1,58 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'FLT';
var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [MultiSymbolLayer],
append: func(idx, item, model) {
while (size(me.list) <= idx) {
append(me.list, nil);
} if (me.list[idx] == nil) {
me.list[idx] = me.add_sym(model);
}
append(model, item);
var pos = me.controller.getpos(item);
var cmd = me.list[idx].element._last_cmd == -1 ? canvas.Path.VG_MOVE_TO : canvas.Path.VG_LINE_TO;
me.list[idx].element.addSegmentGeo(cmd, ["N"~pos[0],"E"~pos[1]]);
#props.dump(me.list[idx].element._node);
},
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
add_sym: func(model) {
return Symbol.new(me.type, me.group, me, model);
},
});
var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
listeners: [],
models: [],
active_path: 0,
};
layer.searcher._equals = func(a,b) a == b;
m.addVisibilityListener();
return m;
};
var del = func() {
foreach (var l; me.listeners)
removelistener(l);
};
var searchCmd = func() {
printlog(_MP_dbg_lvl, "Running query: FLT");
var hist = aircraft.history();
var path = hist.pathForHistory(1000);
printlog(_MP_dbg_lvl, "FLT size: "~size(path));
while (size(me.models) <= me.active_path) append(me.models, []);
for (var i=size(me.models[me.active_path]); i<size(path); i+=1)
me.layer.append(me.active_path, path[i], me.models[me.active_path]);
# TODO: filter by in_range()?
#debug.dump(me.models);
return me.models;
};
var getpos = Symbol.Controller.getpos;

View file

@ -0,0 +1,19 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'FLT';
var parents = [LineSymbol];
var __self__ = caller(0)[0];
LineSymbol.makeinstance( name, __self__ );
SymbolLayer.get(name).df_style = { # style to use by default
line_width: 5,
color: [0,0,1]
};
var init = func {
me.element.setColor(me.layer.style.color)
.setStrokeLineWidth(me.layer.style.line_width);
me.needs_update = 0;
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 508 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 474 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 474 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 474 KiB

View file

@ -58,7 +58,7 @@
transform="translate(-364.652,-344.745)">
<g
id="airplane"
transform="translate(364.652,346.745)"
transform="translate(320.73272,291.98516)"
inkscape:label="#g3781">
<path
id="path3783"

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View file

Before

Width:  |  Height:  |  Size: 5.8 MiB

After

Width:  |  Height:  |  Size: 5.8 MiB

View file

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -5,9 +5,10 @@ var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
parents: [NavaidSymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
df_style: {},
});
var new = func(layer) {
var m = {
@ -17,6 +18,8 @@ var new = func(layer) {
listeners: [],
query_type:'ndb',
};
m.addVisibilityListener();
return m;
};
var del = func() {
@ -24,10 +27,6 @@ var del = func() {
removelistener(l);
};
var searchCmd = func {
printlog(_MP_dbg_lvl, "Running query:", me.query_type);
var range = me.map.getRange();
if (range == nil) return;
return positioned.findWithinRange(me.map.getPosCoord(), range, me.query_type);
};
var searchCmd = NavaidSymbolLayer.make('ndb');

View file

@ -1,10 +0,0 @@
# See: http://wiki.flightgear.org/MapStructure
# 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 ];

View file

@ -1,19 +1,7 @@
# See: http://wiki.flightgear.org/MapStructure
# 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;
};
DotSym.makeinstance('NDB', {
parents: [SVGSymbol],
svg_path: "/gui/dialogs/images/ndb_symbol.svg",
#cacheable: 1,
});

View file

@ -0,0 +1,55 @@
# See: http://wiki.flightgear.org/MapStructure
# TODO: this layer doesn't make sense to support for AI/MP traffic, because we don't currently have access to flightplan/routing info
# that also applies to other layers like WPT or even navaid layers that handle station tuning based on local radio settings
#
# Class things:
var name = 'RTE';
var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [MultiSymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
df_options: { # default configuration options
active_node: "/autopilot/route-manager/active",
current_wp_node: "/autopilot/route-manager/current-wp",
}
});
var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
map: layer.map,
listeners: [],
};
layer.searcher._equals = func(l,r) 0; # TODO: create model objects instead?
append(m.listeners, setlistener(layer.options.active_node, func m.layer.update() ));
m.addVisibilityListener();
return m;
};
var del = func() {
foreach (var l; me.listeners)
removelistener(l);
};
var searchCmd = func {
# FIXME: do we return the current route even if it isn't active?
printlog(_MP_dbg_lvl, "Running query: ", name);
var plans = []; # TODO: multiple flightplans?
# http://wiki.flightgear.org/Nasal_Flightplan
var fp = flightplan();
var fpSize = fp.getPlanSize();
var coords = [];
for (var i=0; i<fpSize; i += 1) {
var leg = fp.getWP(i);
foreach (var c; leg.path()) {
append(coords, c);
}
}
append(plans, coords);
return plans;
};

View file

@ -0,0 +1,18 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'RTE';
var parents = [LineSymbol];
var __self__ = caller(0)[0];
LineSymbol.makeinstance( name, __self__ );
SymbolLayer.get(name).df_style = { # style to use by default
line_width: 5,
color: [1,0,1]
};
var init = func {
me.element.setColor(me.style.color)
.setStrokeLineWidth(me.style.line_width);
};

View file

@ -1,14 +1,16 @@
# See: http://wiki.flightgear.org/MapStructure
# FIXME: this needs to be configurable via the ctor to optionally differentiate between MP and AI traffic, and exclude ground/sea traffic
# or at least use a different, non-TCAS symbol for those (looking pretty weird ATM)
#
# Class things:
var name = 'TFC';
var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
parents: [MultiSymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
df_style: { debug: 0 }, # style to use by default
});
var model_root = props.globals.getNode("/ai/models/");
@ -22,6 +24,8 @@ var new = func(layer) {
searchCmd: searchCmd_default,
};
layer.searcher._equals = func(l,r) l.equals(r);
m.addVisibilityListener();
return m;
};
var del = func() {
@ -40,6 +44,7 @@ var TrafficModel = {
id: id,
node: node,
pos: node.getNode("position",1),
type: node.getName(),
};
return m;
},
@ -57,6 +62,13 @@ var TrafficModel = {
get_alt: func() (me.getValue("position/altitude-ft") or 0),
};
var get_alt_diff = func(model) {
# debug.dump( keys(me) );
var model_alt = model.get_alt();
var alt = me.map.getAlt();
if (alt == nil or model_alt == nil) return 0;
return alt-model_alt;
};
##
# dummy/placeholder (will be overridden in ctor and set to the default callback)
@ -66,6 +78,8 @@ var searchCmd_default = func {
# TODO: this would be a good candidate for splitting across frames
printlog(_MP_dbg_lvl, "Doing query: "~name);
if (me.map.getRange() == nil) return;
var result = [];
var models = 0;

View file

@ -1,16 +0,0 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'TFC';
var parents = [Symbol.Controller];
var __self__ = caller(0)[0];
Symbol.Controller.add(name, __self__);
Symbol.registry[name].df_controller = __self__;
var new = func(model, symbol) ; # this controller doesn't need an instance
var get_alt_diff = func(model) {
# debug.dump( keys(me) );
var model_alt = model.get_alt();
var alt = getprop("/position/altitude-ft"); # FIXME: hardcoded - right, we should probably generalize the "NDSourceDriver logic found in navdisplay.mfd and make it part of MapStructure
if (alt == nil or model_alt == nil) return 0;
return alt-model_alt;
};

View file

@ -5,6 +5,17 @@ var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
SymbolLayer.get(name).df_style = { # style to use by default
line_width: 3,
scale_factor: 1,
color_by_lvl: {
3: [1,0,0], # resolution advisory
2: [1,0.5,0], # traffic advisory
1: [1,1,1], # proximate traffic
},
color_default: [1,1,1]
};
var element_type = "group"; # we want a group, becomes "me.element"
var text_tcas = nil;
var icon_tcas = nil;
@ -12,8 +23,12 @@ var arrow_tcas = [nil,nil];
var arrow_type = nil;
var draw_tcas_arrow = nil;
var color = nil;
var threatLvl = 0e-0; # NaN to update even when threatLvl == nil
# TODO: how to integrate both styling and caching?
var draw = func {
# TODO: get rid of draw_tcas_arrow hacks
if (draw_tcas_arrow == nil)
draw_tcas_arrow = [
draw_tcas_arrow_above_500,
@ -23,7 +38,7 @@ var draw = func {
# print("Drawing traffic for:", callsign );
var threatLvl = me.model.get_threat_lvl();
var vspeed = me.model.get_vspd();
var altDiff = me.controller.get_alt_diff(me.model);
var altDiff = me.layer.controller.get_alt_diff(me.model);
# Init
if (me.text_tcas == nil) {
me.text_tcas = me.element.createChild("text")
@ -49,48 +64,54 @@ var draw = func {
me.arrow_tcas[arrow_type] = me.draw_tcas_arrow[arrow_type](me.element);
me.arrow_tcas[arrow_type].show();
}
## TODO: threat level symbols should also be moved to *.draw files
if (threatLvl == 3) {
# resolution advisory
me.icon_tcas.moveTo(-17,-17)
.horiz(34)
.vert(34)
.horiz(-34)
.close()
.setColor(1,0,0)
.setColorFill(1,0,0);
me.text_tcas.setColor(1,0,0);
me.arrow_tcas[me.arrow_type].setColor(1,0,0);
} elsif (threatLvl == 2) {
# traffic advisory
me.icon_tcas.moveTo(-17,0)
.arcSmallCW(17,17,0,34,0)
.arcSmallCW(17,17,0,-34,0)
.setColor(1,0.5,0)
.setColorFill(1,0.5,0);
me.text_tcas.setColor(1,0.5,0);
me.arrow_tcas[me.arrow_type].setColor(1,0.5,0);
} elsif (threatLvl == 1) {
# proximate traffic
me.icon_tcas.moveTo(-10,0)
.lineTo(0,-17)
.lineTo(10,0)
.lineTo(0,17)
.close()
.setColor(1,1,1)
.setColorFill(1,1,1);
me.text_tcas.setColor(1,1,1);
me.arrow_tcas[me.arrow_type].setColor(1,1,1);
} else {
# other traffic
me.icon_tcas.moveTo(-10,0)
.lineTo(0,-17)
.lineTo(10,0)
.lineTo(0,17)
.close()
.setColor(1,1,1);
me.text_tcas.setColor(1,1,1);
me.arrow_tcas[me.arrow_type].setColor(1,1,1);
if (threatLvl != me.threatLvl) {
me.threatLvl = threatLvl;
## TODO: should threat level symbols also be moved to *.draw files?
if (threatLvl == 3) {
# resolution advisory
me.icon_tcas.moveTo(-17,-17)
.horiz(34)
.vert(34)
.horiz(-34)
.close();
} elsif (threatLvl == 2) {
# traffic advisory
me.icon_tcas.moveTo(-17,0)
.arcSmallCW(17,17,0,34,0)
.arcSmallCW(17,17,0,-34,0);
} elsif (threatLvl == 1) {
# proximate traffic
me.icon_tcas.moveTo(-10,0)
.lineTo(0,-17)
.lineTo(10,0)
.lineTo(0,17)
.close();
} else {
# other traffic
me.icon_tcas.moveTo(-10,0)
.lineTo(0,-17)
.lineTo(10,0)
.lineTo(0,17)
.close();
}
}
var color = nil;
if (threatLvl != nil)
if ((var c = me.style.color_by_lvl[threatLvl]) != nil)
var color = canvas._getColor(c);
if (color == nil)
color = canvas._getColor(me.style.color_default);
if (me.color != color) {
me.color = color;
me.icon_tcas.setColor(color);
me.text_tcas.setColor(color);
me.arrow_tcas[me.arrow_type].setColor(color);
if (num(threatLvl) == nil or (threatLvl < 1 or threatLvl > 3)) {
color = [0,0,0,0];
}
me.icon_tcas.setColorFill(color);
}
if (me.style.scale_factor != me.element.getScale())
me.element.setScale(me.style.scale_factor);
};

View file

@ -5,21 +5,11 @@ var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
parents: [NavaidSymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
});
var new = func(layer) {
if(0) {
# TODO: generalize and move to MapStructure.nas
var required_controllers = [ {name: 'query_range',type:'func'}, ];
foreach(var c; required_controllers) {
if (!contains(layer.map.controller, c.name) or typeof(layer.map.controller[c.name]) !=c.type)
die("Required controller is missing/invalid:"~ c.name ~ ' ['~c.type~']' );
}
}
var m = {
parents: [__self__],
layer: layer,
@ -38,6 +28,8 @@ if(0) {
}
#call(debug.dump, keys(layer));
m.changed_freq(update:0);
m.addVisibilityListener();
return m;
};
var del = func() {
@ -59,10 +51,6 @@ var changed_freq = func(update=1) {
me.active_vors[ navN.getIndex() ] = navN.getValue("frequencies/selected-mhz");
if (update) me.layer.update();
};
var searchCmd = func {
printlog(_MP_dbg_lvl, "Running query:", me.query_type);
var range = me.map.getRange();
if (range == nil) return;
return positioned.findWithinRange(me.map.getPosCoord(), range, me.query_type);
};
var searchCmd = NavaidSymbolLayer.make('vor');

View file

@ -1,9 +0,0 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'VOR';
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

View file

@ -5,33 +5,31 @@ var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
SymbolLayer.get(name).df_style = { # style to use by default
line_width: 3,
range_line_width: 3,
radial_line_width: 3,
range_dash_array: [5, 15, 5, 15, 5],
radial_dash_array: [15, 5, 15, 5, 15],
scale_factor: 1,
active_color: [0, 1, 0],
inactive_color: [0, 0.6, 0.85],
};
var element_type = "group"; # we want a group, becomes "me.element"
var icon_vor = nil; # a handle to the cached icon
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 active = -1;
###
# this function returns a new function that renders the symbol
# into a canvas group
# the signature of the first func should contain all customizable
# parameters (such as color/width etc)
#
# you will typically have one such function for each cacheable symbol
# and only call it once during initialization (once for each cached style/variation)
# the signature of the 2nd func remains untouched, it's internally used
##
# Callback for drawing a new VOR based on styling
#
# NOTE: callbacks that create symbols for caching MUST render them using a
# transparent background !!
#
var drawVOR = func(color, width=3) return func(group) {
# init_calls += 1; # TODO: doing this here is too fragile, this should be done by MapStructure ...
# if( init_calls >= 2)
# print("Warning: draw() routine for a cached element has been called more than once, should only happen during reset/reinit");
# print("drawing vor");
var symbol = group.createChild("path")
var drawVOR = func(group) {
group.createChild("path")
.moveTo(-15,0)
.lineTo(-7.5,12.5)
.lineTo(7.5,12.5)
@ -39,35 +37,16 @@ var drawVOR = func(color, width=3) return func(group) {
.lineTo(7.5,-12.5)
.lineTo(-7.5,-12.5)
.close()
.setStrokeLineWidth(width)
.setColor( color );
return symbol; # make this explicit, not everybody knows Nasal internals inside/out ...
.setStrokeLineWidth(line_width)
.setColor(color);
};
##
# a vector with pre-created symbols that are cached during initialization
# this needs to be done for each variation of each symbol, i.e. color/width etc
# note that scaling should not be done here, likewise for positioning via setGeoPosition()
#
# FIXME: We still need to encode styling information as part of the key/name lookup
# so that the cache entry's name contains styling info, or different maps/layers may
# overwrite their symbols
#
# icon_vor_cache[0] - inactive symbol
# icon_vor_cache[1] - active symbol
var icon_vor_cached = [
SymbolCache32x32.add(
name: "VOR-INACTIVE",
callback: drawVOR( color:[0, 0.6, 0.85], width:3 ),
draw_mode: SymbolCache.DRAW_CENTERED
),
SymbolCache32x32.add(
name: "VOR-ACTIVE",
callback: drawVOR( color:[0, 1, 0], width:3 ),
draw_mode: SymbolCache.DRAW_CENTERED
),
];
var cache = StyleableCacheable.new(
name:name, draw_func: drawVOR,
cache: SymbolCache32x32,
draw_mode: SymbolCache.DRAW_CENTERED,
relevant_keys: ["line_width", "color"],
);
##
# TODO: make this a part of the framework, for use by other layers/symbols etc
@ -84,41 +63,39 @@ var controller_check = func(layer, controller, arg...) {
}
var draw = func {
# Init
if (0 and me.model.id == "SAC") # hack to test isActive() around KSMF
setprop("instrumentation/nav[0]/frequencies/selected-mhz", me.model.frequency/100);
var active = controller_check(me.layer, 'isActive', me.model);
#print(me.model.id,"/", me.model.frequency/100, " isActive:", active);
if (active != me.active) {
#print("VOR.symbol: active != me.active: del/render event triggered");
if (me.icon_vor != nil) me.icon_vor.del();
# look up the correct symbol from the cache and render it into the group as a raster image
me.icon_vor = icon_vor_cached[active or 0].render(me.element);
#me.icon_vor = icon_vor_cached[active or 0].render(me.element);
me.style.color = active ? me.style.active_color : me.style.inactive_color;
me.icon_vor = cache.render(me.element, me.style).setScale(me.style.scale_factor);
me.active = active; # update me.active flag
}
# Update (also handles non-cached stuff, such as text labels or animations)
# TODO: we can use a func generator to pre-create the callback/data structure for this
if (active) {
if (me.range_vor == nil) {
if (me.range_vor == nil) {
# initialize me.range and me.radial_vor
var rangeNm = me.layer.map.getRange();
var rangeNm = me.map.getRange();
# 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);
.setStrokeLineWidth(me.style.range_line_width)
.setStrokeDashArray(me.style.range_dash_array)
.setColor(me.style.active_color);
var course = me.layer.map.controller.get_tuned_course(me.model.frequency/100);
var course = me.map.controller.get_tuned_course(me.model.frequency/100);
me.radial_vor = me.element.createChild("path")
.moveTo(0,-radius)
.vert(2*radius)
.setStrokeLineWidth(3)
.setStrokeDashArray([15, 5, 15, 5, 15])
.setColor(0,1,0)
.setStrokeLineWidth(me.style.radial_line_width)
.setStrokeDashArray(me.style.radial_dash_array)
.setColor(me.style.active_color)
.setRotation(course*D2R);
}
me.range_vor.show();

View file

@ -5,9 +5,13 @@ var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
parents: [MultiSymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
df_options: { # default configuration options
active_node: "/autopilot/route-manager/active",
current_wp_node: "/autopilot/route-manager/current-wp",
}
});
var new = func(layer) {
var m = {
@ -16,6 +20,10 @@ var new = func(layer) {
map: layer.map,
listeners: [],
};
layer.searcher._equals = func(l,r) l.equals(r);
append(m.listeners, setlistener(layer.options.active_node, func m.layer.update() ));
m.addVisibilityListener();
return m;
};
var del = func() {
@ -24,15 +32,34 @@ var del = func() {
removelistener(l);
};
var WPT_model = {
new: func(fp, idx) {
var m = { parents:[WPT_model], idx:idx };
var wp = fp.getWP(idx);
m.name = wp.wp_name;
var alt = wp.alt_cstr;
if (alt != 0)
m.name ~= "\n"~alt;
var n = wp.path()[0];
(m.lat,m.lon) = (n.lat,n.lon);
return m;
},
equals: func(other) {
# this is set on symbol init, so use this for equality...
me.name == other.name
},
};
var searchCmd = func {
printlog(_MP_dbg_lvl, "Running query: "~name);
var fp = flightplan();
var fpSize = fp.getPlanSize();
var result = [];
for (var i = 1; i <fpSize; i+=1)
append(result, fp.getWP(i).path()[0] );
for (var i = 1; i < fpSize; i+=1)
append(result, WPT_model.new(fp, i));
return result;
};

View file

@ -5,9 +5,25 @@ 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" );
var last_idx = nil;
var new = func(symbol, model) {
var m = {
parents:[__self__],
listeners: [],
symbol: symbol,
model: model,
};
append(m.listeners, setlistener(symbol.layer.options.current_wp_node, func(n) {
var n = n.getValue();
if (m.last_idx == model.idx or n == model.idx)
symbol.update();
m.last_idx = n;
}, 0, 0));
return m;
}
var del = func() {
foreach (var l; me.listeners)
removelistener(l);
}

View file

@ -5,14 +5,23 @@ 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;
SymbolLayer.get(name).df_style = { # style to use by default
line_width: 3,
scale_factor: 1,
font: "LiberationFonts/LiberationSans-Regular.ttf",
font_size: 28,
font_color: [1,0,1],
active_color: [1,0,1],
inactive_color: [1,1,1],
};
var draw = func {
if (me.base != nil) return;
me.base = me.element.createChild("path")
.setStrokeLineWidth(3)
var element_type = "group"; # we want a group, becomes "me.element"
var active = nil;
var init = func {
# TODO: active vs inactive?
me.path = me.element.createChild("path")
.setStrokeLineWidth(me.style.line_width)
.moveTo(0,-25)
.lineTo(-5,-5)
.lineTo(-25,0)
@ -22,14 +31,23 @@ var draw = func {
.lineTo(25,0)
.lineTo(5,-5)
.setColor(1,1,1)
.close();
.close()
.setScale(me.style.scale_factor);
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);
me.newText(me.model.name, me.style.font_color).setTranslation(25,35)
.setScale(me.style.scale_factor);
me.draw();
};
var draw = func {
var active = (getprop(me.layer.options.current_wp_node) == me.model.idx);
if (active != me.active) {
me.path.setColor(
active ?
me.style.active_color :
me.style.inactive_color
);
me.active = active;
}
};

View file

@ -0,0 +1,76 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'WXR';
var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [MultiSymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
});
var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
map: layer.map,
listeners: [],
searchCmd: searchCmd_default,
};
layer.searcher._equals = func(l,r) l.equals(r);
m.addVisibilityListener();
return m;
};
var del = func() {
#print(name~".lcontroller.del()");
foreach (var l; me.listeners)
removelistener(l);
};
##
# dummy/placeholder (will be overridden in ctor and set to the default callback)
var searchCmd = func;
# FIXME: TFC/WXR seems only to be updated on range change/setPos currently ?
var searchCmd_default = func {
# print("WXR running");
if (me.map.getRange() == nil) return;
printlog(_MP_dbg_lvl, "Doing query: "~name);
var results = [];
foreach (var n; props.globals.getNode("/instrumentation/wxradar",1).getChildren("storm")) {
# Model 3 degree radar beam
var stormLat = n.getNode("latitude-deg").getValue();
var stormLon = n.getNode("longitude-deg").getValue();
print("processing storm at:",stormLat,'/',stormLon);
# FIXME: once ported to MapStructure, these should use the encapsulated "aircraft source"/driver stuff
var acLat = getprop("/position/latitude-deg");
var acLon = getprop("/position/longitude-deg");
var stormGeo = geo.Coord.new();
var acGeo = geo.Coord.new();
stormGeo.set_latlon(stormLat, stormLon);
acGeo.set_latlon(acLat, acLon);
var directDistance = acGeo.direct_distance_to(stormGeo);
var beamH = 0.1719 * directDistance; # M2FT * tan(3deg)
var beamBase = getprop("position/altitude-ft") - beamH;
if (n.getNode("top-altitude-ft").getValue() > beamBase) {
var tmp = geo.Coord.new();
tmp.set_latlon(stormLat, stormLon);
tmp.radiusNm = n.getNode("radius-nm").getValue();
tmp._node = n;
tmp.equals = func(r) me._node.equals(r._node);
append(results, tmp);
}
} # foreach
#print("WXR results:", size(results));
return results;
} # searchCmd_default

View file

@ -0,0 +1,23 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'WXR'; # storms/weather layer for LW/AW (use thunderstom scenario for testing)
var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
var element_type = "group"; # we want a group, becomes "me.element"
# TODO: how to integrate both styling and caching?
var draw = func {
# TODO: once this works properly, consider using caching here
# FIXME: scaling seems way off in comparison with the navdisplay - i.e. hard-coded assumptions about texture size/view port ?
me.element.createChild("image")
.setFile("Nasal/canvas/map/Images/storm.png")
.setSize(128*me.model.radiusNm,128*me.model.radiusNm)
.setTranslation(-64*me.model.radiusNm,-64*me.model.radiusNm)
.setCenter(0,0);
# .setScale(0.3);
# TODO: overlapping storms should probably set their z-index according to altitudes
};

View file

@ -18,12 +18,17 @@ SOURCES["main"] = {
# Layers which get updated every frame
var update_instant = [
"TFC",
"APS",
];
# Update at a slightly lower rate, but still
# unconditionally
var update_quickly = [
"TFC", "FLT",
];
var new = func(map, source='main') {
if (source != 'main')
die ("AI/MP traffic not yet supported (WIP)!");
if (!contains(SOURCES, source))
__die("AI/MP traffic not yet supported (WIP)!");
var m = {
parents: [__self__],
@ -32,42 +37,56 @@ var new = func(map, source='main') {
_pos: nil, _time: nil, _range: nil,
};
m.timer1 = maketimer(0, m, update_pos);
m.timer2 = maketimer(0, m, update_layers);
m.timer1.start();
m.timer2.start();
m.timer2 = maketimer(0.4, m, update_layers);
m.start();
m.update_pos();
return m;
};
var del = func(map) {
if (map != me.map) die();
var start = func() {
me.timer1.start();
me.timer2.start();
};
var stop = func() {
me.timer1.stop();
me.timer2.stop();
};
var del = func(map) {
if (map != me.map) die();
me.stop();
};
# Controller methods
var update_pos = func {
var (lat,lon) = me.source.getPosition();
me.map.setPos(lat:lat, lon:lon,
hdg:getprop("/orientation/heading-deg"),
hdg:me.source.getHeading(),
alt:me.source.getAltitude());
foreach (var t; update_instant)
if ((var l=me.map.getLayer(t)) != nil)
l.update();
};
var update_layers = func {
var do_all = 1;
var pos = me.map.getPosCoord();
var time = systime();
var range = me.map.getRange();
if (me._pos == nil)
me._pos = geo.Coord.new(pos);
# Always update if range changed
# FIXME: FIX doesn't update unless range is changed?
elsif (range == me._range) {
var dist_m = me._pos.direct_distance_to(pos);
# 2 NM until we update again
if (dist_m < 2 * NM2M) return;
if (dist_m < 2 * NM2M) do_all = 0;
# Update at most every 4 seconds to avoid excessive stutter:
elsif (time - me._time < 4) return;
elsif (time - me._time < 4) do_all = 0;
}
if (!do_all) {
foreach (var t; update_quickly)
if ((var l=me.map.getLayer(t)) != nil)
l.update();
return;
} else
printlog(_MP_dbg_lvl, "update aircraft position");
var (x,y,z) = pos.xyz();
me._pos.set_xyz(x,y,z);

View file

@ -1,35 +0,0 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
###
#
#
var draw_dme = func (group, dme, controller=nil, lod=0) {
var lat = dme.lat;
var lon = dme.lon;
var name = dme.id;
var freq = dme.frequency;
var dme_grp = group.createChild("group","dme");
var icon_dme = dme_grp .createChild("path", name)
.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)
.setColor(0,0.6,0.85);
dme_grp.setGeoPosition(lat, lon)
.set("z-index",3);
if( controller != nil and controller['is_tuned'](freq))
icon_dme.setColor(0,1,0);
}

View file

@ -1,10 +0,0 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var DMELayer = {};
DMELayer.new = func(group,name,controller=nil) {
var m=Layer.new(group, name, DMEModel, controller);
m.setDraw (func draw_layer(layer:m, callback: draw_dme, lod:0) );
return m;
}
register_layer("dme", DMELayer);

View file

@ -1,21 +0,0 @@
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var DMEModel = {};
DMEModel.new = func make( LayerModel, DMEModel );
DMEModel.init = func {
me._view.reset();
if(0) {
debug.dump( me._controller );
print(typeof(
me._controller.query_range()
));
}
var results = positioned.findWithinRange(me._controller.query_range() ,"dme");
foreach(result; results) {
me.push(result);
}
me.notifyView();
}

View file

@ -9,11 +9,11 @@ FixModel.init = func {
var numNum = 0;
foreach(result; results) {
# Skip airport fixes
if(string.match(result.id,"*[^0-9]")) {
if(string.match(result.id,"*[^0-9]")) { # this may need to be moved to FIX.lcontroller?
me.push(result);
numNum = numNum + 1;
}
}
me.notifyView();
}
}

View file

@ -3,605 +3,24 @@
# See: http://wiki.flightgear.org/Canvas_ND_Framework
# ==============================================================================
##
# do we really need to keep track of each drawable here ??
var i = 0;
##
# pseudo DSL-ish: use these as placeholders in the config hash below
var ALWAYS = func 1;
var NOTHING = func nil;
##
# so that we only need to update a single line ...
# this file contains a hash that declares features in a generic fashion
# we want to get rid of the bloated update() method sooner than later
# PLEASE DO NOT ADD any code to update() !!
# Instead, help clean up the file and move things over to the navdisplay.styles file
#
# This is the only sane way to keep on generalizing the framework, so that we can
# also support different makes/models of NDs in the future
#
var trigger_update = func(layer) layer._model.init();
##
# TODO: move ND-specific implementation details into this lookup hash
# so that other aircraft and ND types can be more easily supported
# a huge bloated update() method is going to make that basically IMPOSSIBLE
#
# any aircraft-specific ND behavior should be wrapped here,
# to isolate/decouple things in the generic NavDisplay class
#
# TODO: move this to an XML config file
#
var NDStyles = {
##
# this configures the 744 ND to help generalize the NavDisplay class itself
'Boeing': {
font_mapper: func(family, weight) {
if( family == "Liberation Sans" and weight == "normal" )
return "LiberationFonts/LiberationSans-Regular.ttf";
},
# where all the symbols are stored
# TODO: SVG elements should be renamed to use boeing/airbus prefix
# aircraft developers should all be editing the same ND.svg image
# the code can deal with the differences now
svg_filename: "Nasal/canvas/map/boeingND.svg",
##
## this loads and configures existing layers (currently, *.layer files in Nasal/canvas/map)
##
layers: [
{ 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 fix 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:'airplaneSymbol', disabled:1, update_on:['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:'APS', isMapStructure:1, update_on:['toggle_display_mode'],
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_display_mode') == "PLAN";
layer.group.setVisible( visible );
if (visible) {
layer.update();
}
},
},
{ name:'APT', isMapStructure:1, update_on:['toggle_range','toggle_airports','toggle_display_mode'],
predicate: func(nd, layer) {
# toggle visibility here
var visible=nd.get_switch('toggle_airports') and nd.in_mode('toggle_display_mode', ['MAP']);
layer.group.setVisible( visible );
if (visible) {
#print("Updating MapStructure ND layer: APT");
layer.update();
}
}, # end of layer update predicate
}, # end of APT layer
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
{ 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) {
# 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");
layer.update();
}
}, # end of layer update predicate
}, # end of VOR layer
{ name:'DME', isMapStructure:1, update_on:['toggle_range','toggle_stations'],
# FIXME: this is a really ugly place for controller code
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
# toggle visibility here
layer.group.setVisible( visible );
if (visible) {
#print("Updating MapStructure ND layer: DME");
layer.update();
}
}, # end of layer update predicate
}, # end of DME layer
{ name:'TFC', isMapStructure:1, update_on:['toggle_range','toggle_traffic'],
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_traffic');
layer.group.setVisible( visible );
if (visible) {
#print("Updating MapStructure ND layer: TFC");
layer.update();
}
}, # 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_range','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) nd.aircraft_source.get_spd() > 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(),
},
},
{
id: 'tasLbl',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.aircraft_source.get_spd() > 100,
is_true: func(nd) nd.symbols.tasLbl.show(),
is_false: func(nd) nd.symbols.tasLbl.hide(),
},
},
{
id: 'ilsFreq',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']),
is_true: func(nd) {
nd.symbols.ilsFreq.show();
if(getprop("instrumentation/nav/in-range"))
nd.symbols.ilsFreq.setText(getprop("instrumentation/nav/nav-id"));
else
nd.symbols.ilsFreq.setText(getprop("instrumentation/nav/frequencies/selected-mhz-fmt"));
},
is_false: func(nd) nd.symbols.ilsFreq.hide(),
},
},
{
id: 'ilsLbl',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']),
is_true: func(nd) {
nd.symbols.ilsLbl.show();
},
is_false: func(nd) nd.symbols.ilsLbl.hide(),
},
},
{
id: 'wpActiveId',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("/autopilot/route-manager/wp/id") != nil and getprop("autopilot/route-manager/active")
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
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")
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
is_true: func(nd) {
nd.symbols.wpActiveDist.setText(sprintf("%3.01f",getprop("/autopilot/route-manager/wp/dist")));
nd.symbols.wpActiveDist.show();
},
is_false: func(nd) nd.symbols.wpActiveDist.hide(),
},
},
{
id: 'wpActiveDistLbl',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and getprop("autopilot/route-manager/active")
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
is_true: func(nd) {
nd.symbols.wpActiveDistLbl.show();
if(getprop("/autopilot/route-manager/wp/dist") > 1000)
nd.symbols.wpActiveDistLbl.setText(" NM");
},
is_false: func(nd) nd.symbols.wpActiveDistLbl.hide(),
},
},
{
id: 'eta',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("autopilot/route-manager/wp/eta") != nil and getprop("autopilot/route-manager/active")
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
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);
etaSec=etaSec-3600*h;
var m = math.floor(etaSec/60);
etaSec=etaSec-60*m;
var s = etaSec/10;
if (h>24) h=h-24;
nd.symbols.eta.setText(sprintf("%02.0f%02.0f.%01.0fz",h,m,s));
nd.symbols.eta.show();
},
is_false: func(nd) nd.symbols.eta.hide(),
}, # of eta.impl
}, # of eta
{
id: 'gsGroup',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']),
is_true: func(nd) {
if(nd.get_switch('toggle_centered'))
nd.symbols.gsGroup.setTranslation(0,0);
else
nd.symbols.gsGroup.setTranslation(0,150);
nd.symbols.gsGroup.show();
},
is_false: func(nd) nd.symbols.gsGroup.hide(),
},
},
{
id:'hdg',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','MAP','VOR']),
is_true: func(nd) {
var hdgText = "";
if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT")
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
{
if(nd.get_switch('toggle_true_north'))
hdgText = nd.aircraft_source.get_trk_tru();
else
hdgText = nd.aircraft_source.get_trk_mag();
} else {
if(nd.get_switch('toggle_true_north'))
hdgText = nd.aircraft_source.get_hdg_tru();
else
hdgText = nd.aircraft_source.get_hdg_mag();
}
if(hdgText < 0.5) hdgText = 360 + hdgText;
elsif(hdgText >= 360.5) hdgText = hdgText - 360;
nd.symbols.hdg.setText(sprintf("%03.0f", hdgText));
},
is_false: NOTHING,
},
},
{
id:'hdgGroup',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','MAP','VOR']),
is_true: func(nd) {
nd.symbols.hdgGroup.show();
if(nd.get_switch('toggle_centered'))
nd.symbols.hdgGroup.setTranslation(0,100);
else
nd.symbols.hdgGroup.setTranslation(0,0);
},
is_false: func(nd) nd.symbols.hdgGroup.hide(),
},
},
{
id:'gs',
impl: {
init: func(nd,symbol),
common: func(nd) nd.symbols.gs.setText(sprintf("%3.0f",nd.aircraft_source.get_gnd_spd() )),
predicate: func(nd) nd.aircraft_source.get_gnd_spd() >= 30,
is_true: func(nd) {
nd.symbols.gs.setFontSize(36);
},
is_false: func(nd) nd.symbols.gs.setFontSize(52),
},
},
{
id:'rangeArcs',
impl: {
init: func(nd,symbol),
predicate: func(nd) !nd.get_switch('toggle_centered') and nd.get_switch('toggle_rangearc'),
is_true: func(nd) nd.symbols.rangeArcs.show(),
is_false: func(nd) nd.symbols.rangeArcs.hide(),
}, # of rangeArcs.impl
}, # of rangeArcs
{
id:'rangePln1',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
is_true: func(nd) {
nd.symbols.rangePln1.show();
nd.symbols.rangePln1.setText(sprintf("%3.0f",nd.rangeNm()));
},
is_false: func(nd) nd.symbols.rangePln1.hide(),
},
},
{
id:'rangePln2',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
is_true: func(nd) {
nd.symbols.rangePln2.show();
nd.symbols.rangePln2.setText(sprintf("%3.0f",nd.rangeNm()/2));
},
is_false: func(nd) nd.symbols.rangePln2.hide(),
},
},
{
id:'rangePln3',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
is_true: func(nd) {
nd.symbols.rangePln3.show();
nd.symbols.rangePln3.setText(sprintf("%3.0f",nd.rangeNm()/2));
},
is_false: func(nd) nd.symbols.rangePln3.hide(),
},
},
{
id:'rangePln4',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
is_true: func(nd) {
nd.symbols.rangePln4.show();
nd.symbols.rangePln4.setText(sprintf("%3.0f",nd.rangeNm()));
},
is_false: func(nd) nd.symbols.rangePln4.hide(),
},
},
{
id:'crsLbl',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
is_true: func(nd) nd.symbols.crsLbl.show(),
is_false: func(nd) nd.symbols.crsLbl.hide(),
},
},
{
id:'crs',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
is_true: func(nd) {
nd.symbols.crs.show();
if(getprop("instrumentation/nav/radials/selected-deg") != nil)
nd.symbols.crs.setText(sprintf("%03.0f",getprop("instrumentation/nav/radials/selected-deg")));
},
is_false: func(nd) nd.symbols.crs.hide(),
},
},
{
id:'dmeLbl',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
is_true: func(nd) nd.symbols.dmeLbl.show(),
is_false: func(nd) nd.symbols.dmeLbl.hide(),
},
},
{
id:'dme',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
is_true: func(nd) {
nd.symbols.dme.show();
if(getprop("instrumentation/dme/in-range"))
nd.symbols.dme.setText(sprintf("%3.1f",getprop("instrumentation/dme/indicated-distance-nm")));
},
is_false: func(nd) nd.symbols.dme.hide(),
},
},
{
id:'trkInd2',
impl: {
init: func(nd,symbol),
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['MAP','APP','VOR']) and nd.get_switch('toggle_centered')),
is_true: func(nd) {
nd.symbols.trkInd2.show();
},
is_false: func(nd) nd.symbols.trkInd2.hide(),
},
},
{
id:'vorCrsPtr',
impl: {
init: func(nd,symbol),
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and !nd.get_switch('toggle_centered')),
is_true: func(nd) {
nd.symbols.vorCrsPtr.show();
if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT")
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
nd.symbols.vorCrsPtr.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_trk_mag())*D2R);
else
nd.symbols.vorCrsPtr.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_hdg_mag())*D2R);
},
is_false: func(nd) nd.symbols.vorCrsPtr.hide(),
},
},
{
id:'vorCrsPtr2',
impl: {
init: func(nd,symbol),
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and nd.get_switch('toggle_centered')),
is_true: func(nd) {
nd.symbols.vorCrsPtr2.show();
if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT")
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
nd.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_trk_mag())*D2R);
else
nd.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_hdg_mag())*D2R);
},
is_false: func(nd) nd.symbols.vorCrsPtr2.hide(),
},
},
{
id: 'gsDiamond',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']) and getprop("instrumentation/nav/gs-in-range"),
is_true: func(nd) {
var gs_deflection = getprop("instrumentation/nav/gs-needle-deflection-norm");
if(gs_deflection != nil)
nd.symbols.gsDiamond.setTranslation(gs_deflection*150,0);
if(abs(gs_deflection) < 0.99)
nd.symbols.gsDiamond.setColorFill(1,0,1,1);
else
nd.symbols.gsDiamond.setColorFill(0,0,0,1);
},
is_false: func(nd) nd.symbols.gsGroup.hide(),
},
},
{
id:'locPtr',
impl: {
init: func(nd,symbol),
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and !nd.get_switch('toggle_centered') and getprop("instrumentation/nav/in-range")),
is_true: func(nd) {
nd.symbols.locPtr.show();
var deflection = getprop("instrumentation/nav/heading-needle-deflection-norm");
nd.symbols.locPtr.setTranslation(deflection*150,0);
if(abs(deflection) < 0.99)
nd.symbols.locPtr.setColorFill(1,0,1,1);
else
nd.symbols.locPtr.setColorFill(0,0,0,1);
},
is_false: func(nd) nd.symbols.locPtr.hide(),
},
},
{
id:'locPtr2',
impl: {
init: func(nd,symbol),
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and nd.get_switch('toggle_centered') and getprop("instrumentation/nav/in-range")),
is_true: func(nd) {
nd.symbols.locPtr2.show();
var deflection = getprop("instrumentation/nav/heading-needle-deflection-norm");
nd.symbols.locPtr2.setTranslation(deflection*150,0);
if(abs(deflection) < 0.99)
nd.symbols.locPtr2.setColorFill(1,0,1,1);
else
nd.symbols.locPtr2.setColorFill(0,0,0,1);
},
is_false: func(nd) nd.symbols.locPtr2.hide(),
},
},
{
id:'wind',
impl: {
init: func(nd,symbol),
predicate: ALWAYS,
is_true: func(nd) {
var windDir = getprop("environment/wind-from-heading-deg");
if(!nd.get_switch('toggle_true_north'))
windDir = windDir - getprop("environment/magnetic-variation-deg");
if(windDir < 0.5) windDir = 360 + windDir;
elsif(windDir >= 360.5) windDir = windDir - 360;
nd.symbols.wind.setText(sprintf("%03.0f / %02.0f",windDir,getprop("environment/wind-speed-kt")));
},
is_false: NOTHING,
},
},
{
id:'windArrow',
impl: {
init: func(nd,symbol),
predicate: func(nd) (!(nd.in_mode('toggle_display_mode', ['PLAN']) and (nd.get_switch('toggle_display_type') == "LCD")) and nd.aircraft_source.get_spd() > 100),
is_true: func(nd) {
nd.symbols.windArrow.show();
var windArrowRot = getprop("environment/wind-from-heading-deg");
if((nd.in_mode('toggle_display_mode', ['MAP','PLAN']) and nd.get_switch('toggle_display_type') == "CRT")
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
windArrowRot = windArrowRot - nd.aircraft_source.get_trk_mag();
else
windArrowRot = windArrowRot - nd.aircraft_source.get_hdg_mag();
nd.symbols.windArrow.setRotation(windArrowRot*D2R);
},
is_false: func(nd) nd.symbols.windArrow.hide(),
},
},
], # end of vector with features
}, # end of Boeing style
#####
##
## add support for other aircraft/ND types and styles here (Airbus etc)
##
##
}; # end of NDStyles
io.include("Nasal/canvas/map/navdisplay.styles");
##
# 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)
#
# TODO: this predates aircraftpos.controller (MapStructure) should probably be unified to some degree ...
var NDSourceDriver = {};
NDSourceDriver.new = func {
@ -665,18 +84,10 @@ var default_switches = {
'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_true_north': {path: '/mfd/true-north', value:0, type:'BOOL'},
'toggle_rangearc': {path: '/mfd/rangearc', value:0, type:'BOOL'},
'toggle_track_heading': {path: '/trk-selected', value:0, type:'BOOL'},
'toggle_rangearc': {path: '/mfd/rangearc', value:0, type:'BOOL'},
'toggle_track_heading':{path: '/trk-selected', value:0, type:'BOOL'},
};
# Hack to update weather radar once every 10 seconds
var update_weather = func {
if (getprop("/instrumentation/efis/inputs/wxr") != nil)
setprop("/instrumentation/efis/inputs/wxr",getprop("/instrumentation/efis/inputs/wxr"));
settimer(update_weather, 10);
}
update_weather();
##
# TODO:
# - introduce a MFD class (use it also for PFD/EICAS)
@ -690,6 +101,8 @@ var NavDisplay = {
print("Cleaning up NavDisplay");
# shut down all timers and other loops here
me.update_timer.stop();
foreach(var t; me.timers)
t.stop();
foreach(var l; me.listeners)
removelistener(l);
# clean up MapStructure
@ -701,6 +114,11 @@ var NavDisplay = {
NavDisplay.id -= 1;
},
addtimer: func(interval, cb) {
append(me.timers, var job=maketimer(interval, cb));
return job; # so that we can directly work with the timer (start/stop)
},
listen: func(p,c) {
append(me.listeners, setlistener(p,c));
},
@ -741,6 +159,8 @@ var NavDisplay = {
};
}, # of connectAI
setTimerInterval: func(update_time=0.05) me.update_timer.restart(update_time),
# TODO: the ctor should allow customization, for different aircraft
# especially properties and SVG files/handles (747, 757, 777 etc)
new : func(prop1, switches=default_switches, style='Boeing') {
@ -749,6 +169,7 @@ var NavDisplay = {
m.inited = 0;
m.timers=[];
m.listeners=[]; # for cleanup handling
m.aircraft_source = NDSourceDriver.new(); # uses the main aircraft as the driver/source (speeds, position, heading)
@ -799,12 +220,11 @@ var NavDisplay = {
return m;
},
newMFD: func(canvas_group, parent=nil, options=nil)
newMFD: func(canvas_group, parent=nil, options=nil, update_time=0.05)
{
if (me.inited) die("MFD already was added to scene");
me.inited = 1;
#me.listen("/sim/signals/reinit", func(n) me.del() );
me.update_timer = maketimer(0.05, func me.update() ); # TODO: make interval configurable via ctor
me.update_timer = maketimer(update_time, func me.update() );
me.nd = canvas_group;
me.canvas_handle = parent;
@ -814,7 +234,6 @@ var NavDisplay = {
me.symbols = {}; # storage for SVG elements, to avoid namespace pollution (all SVG elements end up here)
foreach(var feature; me.nd_style.features ) {
# print("Setting up SVG feature:", feature.id);
me.symbols[feature.id] = me.nd.getElementById(feature.id).updateCenter();
if(contains(feature.impl,'init')) feature.impl.init(me.nd, feature); # call The element's init code (i.e. updateCenter)
}
@ -839,10 +258,9 @@ var NavDisplay = {
me.map = me.nd.createChild("map","map")
.set("clip", "rect(124, 1024, 1024, 0)")
.set("screen-range", "700");
.set("screen-range", 700);
# this callback will be passed onto the model via the controller hash, and used for the positioned queries, to specify max query range:
var get_range = func me.get_switch('toggle_range');
me.update_sub(); # init some map properties based on switches
# predicate for the draw controller
var is_tuned = func(freq) {
@ -876,7 +294,6 @@ var NavDisplay = {
var controller = {
parents: [canvas.Map.Controller],
_pos: nil, _time: nil,
query_range: func get_range(),
is_tuned:is_tuned,
get_tuned_course:get_course_by_freq,
get_position: get_current_position,
@ -919,18 +336,31 @@ var NavDisplay = {
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
var render_target = (!contains(layer,'not_a_map') or !layer.not_a_map) ? me.map : me.nd;
var the_layer = nil;
if(!layer['isMapStructure']) # set up an old layer
the_layer = me.layers[layer.name] = canvas.MAP_LAYERS[layer.name].new( render_target, layer.name, controller );
if(!layer['isMapStructure']) # set up an old INEFFICIENT and SLOW layer
the_layer = me.layers[layer.name] = canvas.MAP_LAYERS[layer.name].new( me.map, layer.name, controller );
else {
printlog(_MP_dbg_lvl, "Setting up MapStructure-based layer for ND, name:", layer.name);
var opt = options != nil and options[layer.name] != nil ? options[layer.name] :nil;
# print("Options is: ", opt!=nil?"enabled":"disabled");
render_target.addLayer(factory: canvas.SymbolLayer, type_arg: layer.name, options:opt);
the_layer = me.layers[layer.name] = render_target.getLayer(layer.name);
me.map.addLayer(
factory: canvas.SymbolLayer,
type_arg: layer.name,
options:opt,
visible:0,
priority: layer['z-index']
);
the_layer = me.layers[layer.name] = me.map.getLayer(layer.name);
if (1) (func {
var l = layer;
var _predicate = l.predicate;
l.predicate = func {
var t = systime();
call(_predicate, arg, me);
printlog(_MP_dbg_lvl, "Took "~((systime()-t)*1000)~"ms to update layer "~l.name);
}
})();
}
# now register all layer specific notification listeners and their corresponding update predicate/callback
@ -938,6 +368,14 @@ var NavDisplay = {
# 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);
foreach(var event; layer.update_on) {
# this handles timers
if (typeof(event)=='hash' and contains(event, 'rate_hz')) {
#print("FIXME: navdisplay.mfd timer handling is broken ATM");
var job=me.addtimer(1/event.rate_hz, event_handler);
job.start();
}
# and this listeners
else
# print("Setting up subscription:", event, " for ", layer.name, " handler id:", id(event_handler) );
me.listen_switch(event, event_handler);
} # foreach event subscription
@ -950,17 +388,7 @@ var NavDisplay = {
# start the update timer, which makes sure that the update() will be called
me.update_timer.start();
# next, radio & autopilot & listeners
# TODO: move this to .init field in layers hash or to model files
foreach(var n; var radios = [
"instrumentation/nav/frequencies/selected-mhz",
"instrumentation/nav[1]/frequencies/selected-mhz"])
me.listen(n, func() {
# me.drawvor();
# me.drawdme();
});
# TODO: move this to the route.model
# TODO: move this to RTE.lcontroller ?
me.listen("/autopilot/route-manager/current-wp", func(activeWp) {
canvas.updatewp( activeWp.getValue() );
});
@ -972,13 +400,10 @@ var NavDisplay = {
foreach(var m; modes) if(me.get_switch(switch)==m) return 1;
return 0;
},
# each model should keep track of when it last got updated, using current lat/lon
# in update(), we can then check if the aircraft has traveled more than 0.5-1 nm (depending on selected range)
# and update each model accordingly
update: func() # FIXME: This stuff is still too aircraft specific, cannot easily be reused by other aircraft
# Helper function for below (update()) and above (newMFD())
# to ensure position etc. are correct.
update_sub: func()
{
var _time = systime();
# Variables:
var userLat = me.aircraft_source.get_lat();
var userLon = me.aircraft_source.get_lon();
@ -1009,9 +434,9 @@ var NavDisplay = {
if (me.aircraft_source.get_gnd_spd() < 80)
userTrk = userHdg;
if((me.in_mode('toggle_display_mode', ['MAP']) and me.get_switch('toggle_display_type') == "CRT")
or (me.get_switch('toggle_track_heading') and me.get_switch('toggle_display_type') == "LCD"))
{
if((me.in_mode('toggle_display_mode', ['MAP']) and me.get_switch('toggle_display_type') == "CRT")
or (me.get_switch('toggle_track_heading') and me.get_switch('toggle_display_type') == "LCD"))
{
userHdgTrk = userTrk;
userHdgTrkTru = userTrkTru;
me.symbols.hdgTrk.setText("TRK");
@ -1044,12 +469,23 @@ var NavDisplay = {
pos.lon = userLon;
}
call(me.map.setPos, [pos.lat, pos.lon], me.map, pos);
},
# each model should keep track of when it last got updated, using current lat/lon
# in update(), we can then check if the aircraft has traveled more than 0.5-1 nm (depending on selected range)
# and update each model accordingly
# TODO: Hooray is still waiting for a really rainy weekend to clean up all the mess here... so plz don't add to it!
update: func() # FIXME: This stuff is still too aircraft specific, cannot easily be reused by other aircraft
{
var _time = systime();
call(me.update_sub, nil, nil, caller(0)[0]); # call this in the same namespace to "steal" its variables
# MapStructure update!
if (me.map.controller.should_update_all()) {
me.map.update();
} else {
# TODO: ugly list here
# FIXME: use a VOLATILE layer helper here that handles TFC, APS, WXR etc ?
me.map.update(func(layer) (var n=layer.type) == "TFC" or n == "APS");
}
@ -1134,11 +570,11 @@ var NavDisplay = {
if (hdg_bug_active == nil)
hdg_bug_active = 1;
if((me.in_mode('toggle_display_mode', ['MAP']) and me.get_switch('toggle_display_type') == "CRT")
or (me.get_switch('toggle_track_heading') and me.get_switch('toggle_display_type') == "LCD"))
{
if((me.in_mode('toggle_display_mode', ['MAP']) and me.get_switch('toggle_display_type') == "CRT")
or (me.get_switch('toggle_track_heading') and me.get_switch('toggle_display_type') == "LCD"))
{
me.symbols.trkInd.setRotation(0);
me.symbols.trkInd2.setRotation(0);
me.symbols.trkInd2.setRotation(0);
me.symbols.curHdgPtr.setRotation((userHdg-userTrk)*D2R);
me.symbols.curHdgPtr2.setRotation((userHdg-userTrk)*D2R);
}
@ -1174,26 +610,26 @@ var NavDisplay = {
me.symbols.compass.show();
}
var staPtrVis = !me.in_mode('toggle_display_mode', ['PLAN']);
if((me.in_mode('toggle_display_mode', ['MAP']) and me.get_switch('toggle_display_type') == "CRT")
or (me.get_switch('toggle_track_heading') and me.get_switch('toggle_display_type') == "LCD"))
{
var vorheading = userTrkTru;
var adfheading = userTrkMag;
}
else
{
var vorheading = userHdgTru;
var adfheading = userHdgMag;
}
var staPtrVis = !me.in_mode('toggle_display_mode', ['PLAN']);
if((me.in_mode('toggle_display_mode', ['MAP']) and me.get_switch('toggle_display_type') == "CRT")
or (me.get_switch('toggle_track_heading') and me.get_switch('toggle_display_type') == "LCD"))
{
var vorheading = userTrkTru;
var adfheading = userTrkMag;
}
else
{
var vorheading = userHdgTru;
var adfheading = userHdgMag;
}
if(me.in_mode('toggle_display_mode', ['APP','MAP','VOR','PLAN']))
{
if(getprop("instrumentation/nav/heading-deg") != nil)
var nav0hdg=getprop("instrumentation/nav/heading-deg") - vorheading;
var nav0hdg=getprop("instrumentation/nav/heading-deg") - vorheading;
if(getprop("instrumentation/nav[1]/heading-deg") != nil)
var nav1hdg=getprop("instrumentation/nav[1]/heading-deg") - vorheading;
var adf0hdg=getprop("instrumentation/adf/indicated-bearing-deg") - adfheading;
var adf1hdg=getprop("instrumentation/adf[1]/indicated-bearing-deg") - adfheading;
var nav1hdg=getprop("instrumentation/nav[1]/heading-deg") - vorheading;
var adf0hdg=getprop("instrumentation/adf/indicated-bearing-deg") - adfheading;
var adf1hdg=getprop("instrumentation/adf[1]/indicated-bearing-deg") - adfheading;
if(!me.get_switch('toggle_centered'))
{
if(me.in_mode('toggle_display_mode', ['PLAN']))
@ -1345,8 +781,7 @@ var NavDisplay = {
me.symbols['status.arpt'].setVisible( me.get_switch('toggle_airports') and me.in_mode('toggle_display_mode', ['MAP']));
me.symbols['status.sta'].setVisible( me.get_switch('toggle_stations') and me.in_mode('toggle_display_mode', ['MAP']));
# Okay, _how_ do we hook this up with FGPlot?
#print("ND update took "~(systime()-_time)~" seconds");
printlog(_MP_dbg_lvl, "Total ND update took "~((systime()-_time)*100)~"ms");
setprop("/instrumentation/navdisplay["~ NavDisplay.id ~"]/update-ms", systime() - _time);
}
} # of update() method (50% of our file ...seriously?)
};

View file

@ -0,0 +1,619 @@
# ==============================================================================
# Boeing Navigation Display by Gijs de Rooy
# See: http://wiki.flightgear.org/Canvas_ND_Framework
# ==============================================================================
# This file makes use of the MapStructure framework, see: http://wiki.flightgear.org/Canvas_MapStructure
#
# Sooner or later, some parts will be revamped by coming up with a simple animation framework: http://wiki.flightgear.org/NavDisplay#mapping_vs._SVG_animation
##
# pseudo DSL-ish: use these as placeholders in the config hash below
var ALWAYS = func 1;
var NOTHING = func nil;
##
# TODO: move ND-specific implementation details into this lookup hash
# so that other aircraft and ND types can be more easily supported
#
# any aircraft-specific ND behavior should be wrapped here,
# to isolate/decouple things in the generic NavDisplay class
#
# TODO: move this to an XML config file (maybe supporting SGCondition and/or SGStateMachine markup for the logic?)
#
var NDStyles = {
##
# this configures the 744 ND to help generalize the NavDisplay class itself
'Boeing': {
font_mapper: func(family, weight) {
if( family == "Liberation Sans" and weight == "normal" )
return "LiberationFonts/LiberationSans-Regular.ttf";
},
# where all the symbols are stored
# TODO: SVG elements should be renamed to use boeing/airbus prefix
# aircraft developers should all be editing the same ND.svg image
# the code can deal with the differences now
svg_filename: "Nasal/canvas/map/Images/boeingND.svg",
##
## this loads and configures existing layers (currently, *.layer files in Nasal/canvas/map)
##
# TODO: phase out isMapStructure flag once map.nas & *.draw files are killed
layers: [
# TODO: take z-indices from *.draw files -- now handled by MapStructure in the addLayer method.
{ 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 fix 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
'z-index': 3,
}, # end of FIX layer
# Should redraw every 10 seconds TODO: use new MapStructure/WXR here once that works properly (Gijs should check first!)
{ name:'WXR', isMapStructure:1, update_on:[ {rate_hz: 0.1}, 'toggle_range','toggle_weather','toggle_display_mode'],
predicate: func(nd, layer) {
#print("Running storms predicate");
var visible=nd.get_switch('toggle_weather') and nd.get_switch('toggle_display_mode') != "PLAN";
layer.group.setVisible(visible);
if (visible) {
print("storms update requested! (timer issue when closing the dialog?)");
layer.update();
}
}, # end of layer update predicate
}, # end of storms/WXR layer
{ name:'APS', isMapStructure:1, update_on:['toggle_display_mode'],
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_display_mode') == "PLAN";
layer.group.setVisible( visible );
if (visible) {
layer.update();
}
},
},
{ name:'APT', isMapStructure:1, update_on:['toggle_range','toggle_airports','toggle_display_mode'],
predicate: func(nd, layer) {
# toggle visibility here
var visible=nd.get_switch('toggle_airports') and nd.in_mode('toggle_display_mode', ['MAP']);
layer.group.setVisible( visible );
if (visible) {
#print("Updating MapStructure ND layer: APT");
layer.update();
}
}, # end of layer update predicate
'z-index': 1,
}, # end of APT layer
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
{ 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) {
# 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");
layer.update();
}
}, # end of layer update predicate
'z-index': 3,
}, # end of VOR layer
{ name:'DME', isMapStructure:1, update_on:['toggle_range','toggle_stations'],
# FIXME: this is a really ugly place for controller code
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
# toggle visibility here
layer.group.setVisible( visible );
if (visible) {
#print("Updating MapStructure ND layer: DME");
layer.update();
}
}, # end of layer update predicate
'z-index': 3,
}, # end of DME layer
{ name:'TFC', isMapStructure:1, update_on:['toggle_range','toggle_traffic'],
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_traffic');
layer.group.setVisible( visible );
if (visible) {
#print("Updating MapStructure ND layer: TFC");
layer.update();
}
}, # end of layer update predicate
'z-index': 1,
}, # end of traffic layer
{ name:'runway-nd', update_on:['toggle_range','toggle_display_mode'],
predicate: func(nd, layer) {
#print("runway-nd wants to be ported to MapStructure");
var visible = (nd.rangeNm() <= 40) and getprop("autopilot/route-manager/active") and nd.in_mode('toggle_display_mode', ['MAP','PLAN']) ;
if (visible)
layer._model.init(); # clear & redraw
layer._view.setVisible( visible );
}, # end of layer update predicate
}, # end of airports-nd layer
{ name:'RTE', isMapStructure:1, update_on:['toggle_range','toggle_display_mode'],
predicate: func(nd, layer) {
var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN']));
layer.group.setVisible( visible );
if (visible)
layer.update();
}, # end of layer update predicate
'z-index': 2, # apparently route.draw doesn't have a z-index?
}, # end of route layer
{ name:'WPT', isMapStructure:1, update_on:['toggle_range','toggle_display_mode'],
predicate: func(nd, layer) {
var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN']));
layer.group.setVisible( visible );
if (visible)
layer.update();
}, # end of layer update predicate
'z-index': 4,
}, # end of waypoint layer
{ name:'ALT-profile', isMapStructure:1, update_on:['toggle_range','toggle_display_mode'],
predicate: func(nd, layer) {
var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN']));
layer.group.setVisible( visible );
if (visible)
layer.update();
}, # end of layer update predicate
'z-index': 4,
}, # end of altitude profile 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) nd.aircraft_source.get_spd() > 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(),
},
},
{
id: 'tasLbl',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.aircraft_source.get_spd() > 100,
is_true: func(nd) nd.symbols.tasLbl.show(),
is_false: func(nd) nd.symbols.tasLbl.hide(),
},
},
{
id: 'ilsFreq',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']),
is_true: func(nd) {
nd.symbols.ilsFreq.show();
if(getprop("instrumentation/nav/in-range"))
nd.symbols.ilsFreq.setText(getprop("instrumentation/nav/nav-id"));
else
nd.symbols.ilsFreq.setText(getprop("instrumentation/nav/frequencies/selected-mhz-fmt"));
},
is_false: func(nd) nd.symbols.ilsFreq.hide(),
},
},
{
id: 'ilsLbl',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']),
is_true: func(nd) {
nd.symbols.ilsLbl.show();
},
is_false: func(nd) nd.symbols.ilsLbl.hide(),
},
},
{
id: 'wpActiveId',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("/autopilot/route-manager/wp/id") != nil and getprop("autopilot/route-manager/active")
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
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")
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
is_true: func(nd) {
nd.symbols.wpActiveDist.setText(sprintf("%3.01f",getprop("/autopilot/route-manager/wp/dist")));
nd.symbols.wpActiveDist.show();
},
is_false: func(nd) nd.symbols.wpActiveDist.hide(),
},
},
{
id: 'wpActiveDistLbl',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and getprop("autopilot/route-manager/active")
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
is_true: func(nd) {
nd.symbols.wpActiveDistLbl.show();
if(getprop("/autopilot/route-manager/wp/dist") > 1000)
nd.symbols.wpActiveDistLbl.setText(" NM");
},
is_false: func(nd) nd.symbols.wpActiveDistLbl.hide(),
},
},
{
id: 'eta',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("autopilot/route-manager/wp/eta") != nil and getprop("autopilot/route-manager/active")
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
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);
etaSec=etaSec-3600*h;
var m = math.floor(etaSec/60);
etaSec=etaSec-60*m;
var s = etaSec/10;
if (h>24) h=h-24;
nd.symbols.eta.setText(sprintf("%02.0f%02.0f.%01.0fz",h,m,s));
nd.symbols.eta.show();
},
is_false: func(nd) nd.symbols.eta.hide(),
}, # of eta.impl
}, # of eta
{
id: 'gsGroup',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']),
is_true: func(nd) {
if(nd.get_switch('toggle_centered'))
nd.symbols.gsGroup.setTranslation(0,0);
else
nd.symbols.gsGroup.setTranslation(0,150);
nd.symbols.gsGroup.show();
},
is_false: func(nd) nd.symbols.gsGroup.hide(),
},
},
{
id:'hdg',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','MAP','VOR']),
is_true: func(nd) {
var hdgText = "";
if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT")
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
{
if(nd.get_switch('toggle_true_north'))
hdgText = nd.aircraft_source.get_trk_tru();
else
hdgText = nd.aircraft_source.get_trk_mag();
} else {
if(nd.get_switch('toggle_true_north'))
hdgText = nd.aircraft_source.get_hdg_tru();
else
hdgText = nd.aircraft_source.get_hdg_mag();
}
if(hdgText < 0.5) hdgText = 360 + hdgText;
elsif(hdgText >= 360.5) hdgText = hdgText - 360;
nd.symbols.hdg.setText(sprintf("%03.0f", hdgText));
},
is_false: NOTHING,
},
},
{
id:'hdgGroup',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','MAP','VOR']),
is_true: func(nd) {
nd.symbols.hdgGroup.show();
if(nd.get_switch('toggle_centered'))
nd.symbols.hdgGroup.setTranslation(0,100);
else
nd.symbols.hdgGroup.setTranslation(0,0);
},
is_false: func(nd) nd.symbols.hdgGroup.hide(),
},
},
{
id:'gs',
impl: {
init: func(nd,symbol),
common: func(nd) nd.symbols.gs.setText(sprintf("%3.0f",nd.aircraft_source.get_gnd_spd() )),
predicate: func(nd) nd.aircraft_source.get_gnd_spd() >= 30,
is_true: func(nd) {
nd.symbols.gs.setFontSize(36);
},
is_false: func(nd) nd.symbols.gs.setFontSize(52),
},
},
{
id:'rangeArcs',
impl: {
init: func(nd,symbol),
predicate: func(nd) !nd.get_switch('toggle_centered') and nd.get_switch('toggle_rangearc'),
is_true: func(nd) nd.symbols.rangeArcs.show(),
is_false: func(nd) nd.symbols.rangeArcs.hide(),
}, # of rangeArcs.impl
}, # of rangeArcs
{
id:'rangePln1',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
is_true: func(nd) {
nd.symbols.rangePln1.show();
nd.symbols.rangePln1.setText(sprintf("%3.0f",nd.rangeNm()));
},
is_false: func(nd) nd.symbols.rangePln1.hide(),
},
},
{
id:'rangePln2',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
is_true: func(nd) {
nd.symbols.rangePln2.show();
nd.symbols.rangePln2.setText(sprintf("%3.0f",nd.rangeNm()/2));
},
is_false: func(nd) nd.symbols.rangePln2.hide(),
},
},
{
id:'rangePln3',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
is_true: func(nd) {
nd.symbols.rangePln3.show();
nd.symbols.rangePln3.setText(sprintf("%3.0f",nd.rangeNm()/2));
},
is_false: func(nd) nd.symbols.rangePln3.hide(),
},
},
{
id:'rangePln4',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
is_true: func(nd) {
nd.symbols.rangePln4.show();
nd.symbols.rangePln4.setText(sprintf("%3.0f",nd.rangeNm()));
},
is_false: func(nd) nd.symbols.rangePln4.hide(),
},
},
{
id:'crsLbl',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
is_true: func(nd) nd.symbols.crsLbl.show(),
is_false: func(nd) nd.symbols.crsLbl.hide(),
},
},
{
id:'crs',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
is_true: func(nd) {
nd.symbols.crs.show();
if(getprop("instrumentation/nav/radials/selected-deg") != nil)
nd.symbols.crs.setText(sprintf("%03.0f",getprop("instrumentation/nav/radials/selected-deg")));
},
is_false: func(nd) nd.symbols.crs.hide(),
},
},
{
id:'dmeLbl',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
is_true: func(nd) nd.symbols.dmeLbl.show(),
is_false: func(nd) nd.symbols.dmeLbl.hide(),
},
},
{
id:'dme',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
is_true: func(nd) {
nd.symbols.dme.show();
if(getprop("instrumentation/dme/in-range"))
nd.symbols.dme.setText(sprintf("%3.1f",getprop("instrumentation/dme/indicated-distance-nm")));
},
is_false: func(nd) nd.symbols.dme.hide(),
},
},
{
id:'trkInd2',
impl: {
init: func(nd,symbol),
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['MAP','APP','VOR']) and nd.get_switch('toggle_centered')),
is_true: func(nd) {
nd.symbols.trkInd2.show();
},
is_false: func(nd) nd.symbols.trkInd2.hide(),
},
},
{
id:'vorCrsPtr',
impl: {
init: func(nd,symbol),
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and !nd.get_switch('toggle_centered')),
is_true: func(nd) {
nd.symbols.vorCrsPtr.show();
if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT")
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
nd.symbols.vorCrsPtr.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_trk_mag())*D2R);
else
nd.symbols.vorCrsPtr.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_hdg_mag())*D2R);
},
is_false: func(nd) nd.symbols.vorCrsPtr.hide(),
},
},
{
id:'vorCrsPtr2',
impl: {
init: func(nd,symbol),
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and nd.get_switch('toggle_centered')),
is_true: func(nd) {
nd.symbols.vorCrsPtr2.show();
if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT")
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
nd.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_trk_mag())*D2R);
else
nd.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_hdg_mag())*D2R);
},
is_false: func(nd) nd.symbols.vorCrsPtr2.hide(),
},
},
{
id: 'gsDiamond',
impl: {
init: func(nd,symbol),
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']) and getprop("instrumentation/nav/gs-in-range"),
is_true: func(nd) {
var gs_deflection = getprop("instrumentation/nav/gs-needle-deflection-norm");
if(gs_deflection != nil)
nd.symbols.gsDiamond.setTranslation(gs_deflection*150,0);
if(abs(gs_deflection) < 0.99)
nd.symbols.gsDiamond.setColorFill(1,0,1,1);
else
nd.symbols.gsDiamond.setColorFill(0,0,0,1);
},
is_false: func(nd) nd.symbols.gsGroup.hide(),
},
},
{
id:'locPtr',
impl: {
init: func(nd,symbol),
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and !nd.get_switch('toggle_centered') and getprop("instrumentation/nav/in-range")),
is_true: func(nd) {
nd.symbols.locPtr.show();
var deflection = getprop("instrumentation/nav/heading-needle-deflection-norm");
nd.symbols.locPtr.setTranslation(deflection*150,0);
if(abs(deflection) < 0.99)
nd.symbols.locPtr.setColorFill(1,0,1,1);
else
nd.symbols.locPtr.setColorFill(0,0,0,1);
},
is_false: func(nd) nd.symbols.locPtr.hide(),
},
},
{
id:'locPtr2',
impl: {
init: func(nd,symbol),
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and nd.get_switch('toggle_centered') and getprop("instrumentation/nav/in-range")),
is_true: func(nd) {
nd.symbols.locPtr2.show();
var deflection = getprop("instrumentation/nav/heading-needle-deflection-norm");
nd.symbols.locPtr2.setTranslation(deflection*150,0);
if(abs(deflection) < 0.99)
nd.symbols.locPtr2.setColorFill(1,0,1,1);
else
nd.symbols.locPtr2.setColorFill(0,0,0,1);
},
is_false: func(nd) nd.symbols.locPtr2.hide(),
},
},
{
id:'wind',
impl: {
init: func(nd,symbol),
predicate: ALWAYS,
is_true: func(nd) {
var windDir = getprop("environment/wind-from-heading-deg");
if(!nd.get_switch('toggle_true_north'))
windDir = windDir - getprop("environment/magnetic-variation-deg");
if(windDir < 0.5) windDir = 360 + windDir;
elsif(windDir >= 360.5) windDir = windDir - 360;
nd.symbols.wind.setText(sprintf("%03.0f / %02.0f",windDir,getprop("environment/wind-speed-kt")));
},
is_false: NOTHING,
},
},
{
id:'windArrow',
impl: {
init: func(nd,symbol),
predicate: func(nd) (!(nd.in_mode('toggle_display_mode', ['PLAN']) and (nd.get_switch('toggle_display_type') == "LCD")) and nd.aircraft_source.get_spd() > 100),
is_true: func(nd) {
nd.symbols.windArrow.show();
var windArrowRot = getprop("environment/wind-from-heading-deg");
if((nd.in_mode('toggle_display_mode', ['MAP','PLAN']) and nd.get_switch('toggle_display_type') == "CRT")
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
windArrowRot = windArrowRot - nd.aircraft_source.get_trk_mag();
else
windArrowRot = windArrowRot - nd.aircraft_source.get_hdg_mag();
nd.symbols.windArrow.setRotation(windArrowRot*D2R);
},
is_false: func(nd) nd.symbols.windArrow.hide(),
},
},
], # end of vector with features
}, # end of Boeing style
#####
##
## add support for other aircraft/ND types and styles here (Airbus etc)
## or move to other files.
##
## see: http://wiki.flightgear.org/NavDisplay#Custom_ND_Styles
## and: http://wiki.flightgear.org/NavDisplay#Adding_new_features
}; # end of NDStyles

View file

@ -11,7 +11,7 @@ var draw_storm = func (group, storm, controller=nil, lod=0) {
var storm_grp = group.createChild("group","storm"); # one group for each storm
storm_grp.createChild("image")
.setFile("Nasal/canvas/map/storm.png")
.setFile("Nasal/canvas/map/Images/storm.png")
.setSize(128*radiusNm,128*radiusNm)
.setTranslation(-64*radiusNm,-64*radiusNm)
.setCenter(0,0);

View file

@ -8,18 +8,20 @@ StormModel.init = func {
foreach (var n; props.globals.getNode("/instrumentation/wxradar",1).getChildren("storm")) {
# Model 3 degree radar beam
var stormLat = n.getNode("latitude-deg").getValue();
stormLon = n.getNode("longitude-deg").getValue();
acLat = getprop("/position/latitude-deg");
acLon = getprop("/position/longitude-deg");
stormGeo = geo.Coord.new();
acGeo = geo.Coord.new();
var stormLon = n.getNode("longitude-deg").getValue();
# FIXME: once ported to MapStructure, these should use the encapsulated "aircraft source"/driver stuff
var acLat = getprop("/position/latitude-deg");
var acLon = getprop("/position/longitude-deg");
var stormGeo = geo.Coord.new();
var acGeo = geo.Coord.new();
stormGeo.set_latlon(stormLat, stormLon);
acGeo.set_latlon(acLat, acLon);
var directDistance = acGeo.direct_distance_to(stormGeo);
beamH = 0.1719 * directDistance; # M2FT * tan(3deg)
beamBase = getprop("position/altitude-ft") - beamH;
var beamH = 0.1719 * directDistance; # M2FT * tan(3deg)
var beamBase = getprop("position/altitude-ft") - beamH;
if (n.getNode("top-altitude-ft").getValue() > beamBase) {
me.push( { lat: stormLat, lon : stormLon, radiusNm : n.getNode("radius-nm").getValue() } );

View file

@ -53,6 +53,7 @@
<!-- Equipment menu -->
<equipment>Equipment</equipment>
<map>Map</map>
<map-canvas>Map (Canvas)</map-canvas>
<stopwatch>Stopwatch</stopwatch>
<fuel-and-payload>Fuel and Payload</fuel-and-payload>
<radio>Radio Settings</radio>

View file

@ -33,6 +33,13 @@
</binding>
</checkbox>
</group>
<button>
<legend>New Canvas Map</legend>
<binding>
<command>nasal</command>
<script>canvas.MapStructure_selfTest();</script>
</binding>
</button>
<button>
<legend>Close</legend>

498
gui/dialogs/map-canvas.xml Normal file
View file

@ -0,0 +1,498 @@
<?xml version="1.0"?>
<PropertyList>
<name>map-canvas</name>
<layout>vbox</layout>
<resizable>true</resizable>
<color>
<red type="float">0.41</red>
<green type="float">0.4</green>
<blue type="float">0.42</blue>
<alpha type="float">1.0</alpha>
</color>
<nasal>
<open><![CDATA[
var self = cmdarg();
var listeners = [];
var setTransparency = func(updateDialog){
var alpha = (getprop("/sim/gui/dialogs/map-canvas/transparent") or 0);
self.getNode("color/alpha").setValue(1-alpha*0.3);
self.getNode("color/red").setValue(0.41-alpha*0.2);
self.getNode("color/green").setValue(0.4-alpha*0.2);
self.getNode("color/blue").setValue(0.42-alpha*0.2);
var n = props.Node.new({ "dialog-name": "map-canvas" });
if (updateDialog)
{
fgcommand("dialog-close", n);
fgcommand("dialog-show", n);
}
}
setTransparency(0);
]]></open>
<close><![CDATA[
TestMap.del();
foreach (var l; listeners)
removelistener(l);
setsize(listeners, 0);
]]></close>
</nasal>
<group>
<layout>hbox</layout>
<empty><stretch>1</stretch></empty>
<text>
<label>Map (Canvas)</label>
</text>
<empty><stretch>1</stretch></empty>
<button>
<pref-width>16</pref-width>
<pref-height>16</pref-height>
<legend></legend>
<keynum>27</keynum>
<border>2</border>
<binding>
<command>dialog-close</command>
</binding>
</button>
</group>
<hrule/>
<group>
<layout>hbox</layout>
<stretch>true</stretch>
<!-- sidebar -->
<group>
<layout>vbox</layout>
<text>
<label>Display:</label>
</text>
<checkbox>
<label>Airports</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/draw-APT</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
</checkbox>
<checkbox>
<label>Fixes</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/draw-FIX</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
</checkbox>
<checkbox>
<label>VORs</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/draw-VOR</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
</checkbox>
<checkbox>
<label>DMEs</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/draw-DME</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
</checkbox>
<checkbox>
<label>NDBs</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/draw-NDB</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
</checkbox>
<!--
<button>
<legend>Airways</legend>
<pref-width>100</pref-width>
</button>
-->
<checkbox>
<label>Traffic</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/draw-TFC</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
</checkbox>
<!--
<button>
<legend>Obstacles</legend>
<pref-width>100</pref-width>
</button>
-->
<checkbox>
<label>Data</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/draw-data</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
</checkbox>
<checkbox>
<label>Flight History</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/draw-FLT</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
</checkbox>
<checkbox>
<!-- layer only supported if LW/AW is active -->
<enable>
<property>/sim/gui/dialogs/metar/mode/local-weather</property>
<value>1</value>
</enable>
<label>Weather</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/draw-WXR</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
</checkbox>
<!-- layer only supported if tutorial system is active and targets specified-->
<!--
<checkbox>
<enable>
<property>/sim/gui/dialogs/metar/mode/local-weather</property>
<value>1</value>
</enable>
<label>Tutorial Goals</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/draw-TUT</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
</checkbox>
-->
<empty><stretch>true</stretch></empty>
<checkbox>
<label>Magnetic Hdgs</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/magnetic-headings</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
</checkbox>
<checkbox>
<label>Center on Acft</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/centre-on-aircraft</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
</checkbox>
<checkbox>
<label>Aircraft Hdg Up</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/aircraft-heading-up</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
</checkbox>
<checkbox>
<label>Transparent</label>
<pref-width>100</pref-width>
<property>/sim/gui/dialogs/map-canvas/transparent</property>
<live>true</live>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>property-toggle</command>
</binding>
<binding>
<command>nasal</command>
<script>setTransparency(1);</script>
</binding>
</checkbox>
<empty><stretch>true</stretch></empty>
<button>
<name>close</name>
<legend>Close</legend>
<pref-width>100</pref-width>
<default>true</default>
<binding>
<command>dialog-close</command>
</binding>
</button>
</group>
<vrule/>
<group>
<layout>vbox</layout>
<stretch>true</stretch>
<group>
<canvas>
<name>canvas-map</name>
<valign>fill</valign>
<halign>fill</halign>
<stretch>true</stretch>
<pref-width>600</pref-width>
<pref-height>400</pref-height>
<nasal><load><![CDATA[
var myCanvas = canvas.get( cmdarg() );
myCanvas.setColorBackground(0,0,0,0.5); # transparent
var TestMap = myCanvas.createGroup().createChild("map");
# Initialize the controller:
var ctrl_ns = canvas.Map.Controller.get("Aircraft position");
var source = ctrl_ns.SOURCES["map-dialog"];
if (source == nil) {
# TODO: amend
var source = ctrl_ns.SOURCES["map-dialog"] = {
getPosition: func subvec(geo.aircraft_position().latlon(), 0, 2),
getAltitude: func getprop('/position/altitude-ft'),
getHeading: func {
if (me.aircraft_heading)
getprop('/orientation/heading-deg')
else 0
},
aircraft_heading: 1,
};
}
setlistener("/sim/gui/dialogs/map-canvas/aircraft-heading-up", func(n) {
source.aircraft_heading = n.getBoolValue();
}, 1);
# Make it move with our aircraft:
TestMap.setController("Aircraft position", "map-dialog"); # from aircraftpos.controller
# Initialize a range:
TestMap.setRange(20);
var range_step = math.log10(TestMap.getRange());
# TODO: check if this is valid, IIRC DOM manipulation was fragile when done inside canvas/Nasal region (?)
gui.findElementByName(self, "zoomdisplay").setValue("property", TestMap._node.getNode("range").getPath());
# Center the map's origin: FIXME: move to api.nas, i.e. allow maps to have a size/view that differs from the actual canvas ??
TestMap.setTranslation(
myCanvas.get("view[0]")/2,
myCanvas.get("view[1]")/2
);
##
# Styling: This is a bit crude at the moment, i.e. no dedicated APIs yet - but it's
# just there to prototype things for now
var Styles = {};
Styles.get = func(type) return Styles[type];
var Options = {};
Options.get = func(type) return Options[type];
## set up a few keys supported by the DME.symbol file to customize appearance:
Styles.DME = {};
Styles.DME.debug = 1; # HACK for benchmarking/debugging purposes
Styles.DME.animation_test = 0; # for prototyping animated symbols
Styles.DME.scale_factor = 0.4; # 40% (applied to whole group)
Styles.DME.line_width = 3.0;
Styles.DME.color_tuned = [0,1,0]; #rgb
Styles.DME.color_default = [1,1,0]; #rgb
Styles.APT = {};
Styles.APT.scale_factor = 0.4; # 40% (applied to whole group)
Styles.APT.line_width = 3.0;
Styles.APT.color_default = [0,0.6,0.85]; #rgb
Styles.APT.label_font_color = Styles.APT.color_default;
Styles.APT.label_font_size=28;
Styles.TFC = {};
Styles.TFC.scale_factor = 0.4; # 40% (applied to whole group)
Styles.WPT = {};
Styles.WPT.scale_factor = 0.5; # 50% (applied to whole group)
Styles.RTE = {};
Styles.RTE.line_width = 2;
Styles.FLT = {};
Styles.FLT.line_width = 3;
Styles.FIX = {};
Styles.FIX.color = [1,0,0];
Styles.FIX.scale_factor = 0.4; # 40%
Styles.VOR = {};
Styles.VOR.range_line_width = 2;
Styles.VOR.radial_line_width = 1;
Styles.VOR.scale_factor = 0.6; # 60%
Options.FLT = {};
var make_update_wrapper = func(name) {
if (!contains(Options, name)) Options[name] = {};
Options[name].update_wrapper = func(layer, fn) {
var t = systime();
fn();
t = systime() - t;
printlog(canvas._MP_dbg_lvl, name~" took "~(t*1000)~"ms")
}
}
var ToggleLayerVisible = func(name) {
(var l = TestMap.getLayer(name)).setVisible(l.getVisible());
};
var SetLayerVisible = func(name,n=1) {
TestMap.getLayer(name).setVisible(n);
};
Styles.APS = {};
Styles.APS.scale_factor = 0.25;
# TODO: introduce some meta NAV layer that handles both VORs and NDBs, can we instantiate those layers directly ?
var r = func(name,vis=1,zindex=nil) return caller(0)[0];
# TODO: we'll need some z-indexing here, right now it's just random
foreach(var type; [r('TFC',0),r('APT'),r('DME'),r('VOR'),r('NDB'),r('FIX',0),r('RTE'),r('WPT'),r('FLT'),r('WXR',0),r('APS'), ] ) {
if (1 and type.name != 'APS' and type.name != 'FLT') make_update_wrapper(type.name);
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,
visible: type.vis, priority: type.zindex,
style: Styles.get(type.name),
options: Options.get(type.name) );
(func {
# Notify MapStructure about layer visibility changes:
var name = type.name;
props.globals.initNode("/sim/gui/dialogs/map-canvas/draw-"~name, type.vis, "BOOL");
append(listeners,
setlistener("/sim/gui/dialogs/map-canvas/draw-"~name,
func(n) SetLayerVisible(name,n.getValue()))
);
})();
}
]]></load></nasal>
</canvas>
<layout>hbox</layout>
</group>
<hrule/>
<group>
<layout>hbox</layout>
<button>
<name>zoomout</name>
<legend>out</legend>
<pref-width>52</pref-width>
<pref-height>22</pref-height>
<binding>
<command>nasal</command>
<script>
var range = TestMap.getRange();
if (range &lt; 40)
TestMap.setRange(range*range_step);
</script>
</binding>
</button>
<text>
<name>zoomdisplay</name>
<label>MMM</label>
<format>Zoom %0.1f NM</format>
<property>/gui/radar-mapstructure/zoom</property>
<live>true</live>
</text>
<button>
<name>zoomin</name>
<legend>in</legend>
<pref-width>52</pref-width>
<pref-height>22</pref-height>
<binding>
<command>nasal</command>
<script>
var range = TestMap.getRange();
if (range &gt; 1)
TestMap.setRange(range/range_step);
</script>
</binding>
</button>
</group>
</group>
</group>
</PropertyList>

View file

@ -322,13 +322,23 @@
<item>
<name>map</name>
<key>Ctrl-M</key>
<key>Ctrl-M</key>
<binding>
<command>dialog-show</command>
<dialog-name>map</dialog-name>
</binding>
</item>
<item>
<name>map-canvas</name>
<!-- <key>Ctrl-M</key> -->
<binding>
<command>dialog-show</command>
<dialog-name>map-canvas</dialog-name>
</binding>
</item>
<item>
<name>stopwatch</name>
<binding>