diff --git a/Aircraft/Instruments-3d/FG1000/Models/EIS_c172s.svg b/Aircraft/Instruments-3d/FG1000/Models/EIS_c172s.svg
new file mode 100644
index 000000000..e1af03ee8
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Models/EIS_c172s.svg
@@ -0,0 +1,841 @@
+
+
+
+
diff --git a/Aircraft/Instruments-3d/FG1000/Models/MFD.svg b/Aircraft/Instruments-3d/FG1000/Models/MFD.svg
new file mode 100644
index 000000000..b2660a6ae
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Models/MFD.svg
@@ -0,0 +1,1744 @@
+
+
+
+
diff --git a/Aircraft/Instruments-3d/FG1000/Models/MFD2.svg b/Aircraft/Instruments-3d/FG1000/Models/MFD2.svg
new file mode 100644
index 000000000..c9ab3aa49
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Models/MFD2.svg
@@ -0,0 +1,1745 @@
+
+
+
+
diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/Drivers/EISDriver.nas b/Aircraft/Instruments-3d/FG1000/Nasal/Drivers/EISDriver.nas
new file mode 100644
index 000000000..5d4803d45
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Nasal/Drivers/EISDriver.nas
@@ -0,0 +1,68 @@
+# FG1000 Engine Information Display Default Driver
+#
+# Driver values in the EIS.
+
+var PropMap =
+{
+ new : func(name, property)
+ {
+ var obj = { parents : [ PropMap ] };
+ obj._name = name;
+ obj._val = 0;
+ obj._prop = globals.props.getNode(property, 1);
+ obj._val = obj._prop.getValue();
+ return obj;
+ },
+
+ update : func()
+ {
+ me._val = me._prop.getValue();
+ },
+
+ getValue : func()
+ {
+ return me._val;
+ }
+
+};
+
+var EISDriver =
+{
+ new : func()
+ {
+ var obj = { parents : [ EISDriver ] };
+
+ # Hack to handle most aircraft not having proper engine hours
+ setprop("/engines/engine[0]/hours", 157.0);
+
+ obj._propmap = {
+ "RPM" : PropMap.new("RPM", "/engines/engine[0]/rpm"),
+ "MBusVolts" : PropMap.new("MBusVolts", "/systems/electrical/volts"),
+ "EngineHours" : PropMap.new("EngineHours", "/engines/engine[0]/hours"),
+ "FuelFlowGPH" : PropMap.new("FuelFlowGPH", "/engines/engine[0]/fuel-flow-gph"),
+ "OilPressurePSI" : PropMap.new("OilPressurePSI", "/engines/engine[0]/oil-pressure-psi"),
+ "OilTemperatureF" : PropMap.new("OilTemperatureF", "/engines/engine[0]/oil-temperature-degf"),
+ "EGTNorm" : PropMap.new("EGTNorm", "/engines/engine[0]/egt-norm"),
+ "VacuumSuctionInHG" : PropMap.new("VacuumSuctionInHG", "/systems/vacuum/suction-inhg"),
+
+ "LeftFuelUSGal" : PropMap.new("LeftFuelUSGal", "/consumables/fuel/tank[0]/indicated-level-gal_us"),
+ "RightFuelUSGal" : PropMap.new("RightFuelUSGal", "/consumables/fuel/tank[1]/indicated-level-gal_us"),
+
+
+ };
+ return obj;
+ },
+
+ getValue : func(name)
+ {
+ if (me._propmap[name] == nil) print("Unknown Driver Key in EISDriver.nas " ~ name);
+ return me._propmap[name].getValue();
+ },
+
+ update: func () {
+
+ foreach (var tp; keys(me._propmap)) {
+ me._propmap[tp].update();
+ }
+ }
+};
diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/EIS.nas b/Aircraft/Instruments-3d/FG1000/Nasal/EIS.nas
new file mode 100644
index 000000000..cd385fc27
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Nasal/EIS.nas
@@ -0,0 +1,146 @@
+# FG1000 Engine Information Display
+
+# A mapping from names of elements to specific elements of the SVG.
+# These names mapped to values by the provided EISDriver.
+var DynamicTextElementMap =
+{
+ new : func(svg, name, element_id, format)
+ {
+ var obj = { parents : [ DynamicTextElementMap ] };
+ obj._name = name;
+ obj._element_id = element_id;
+ obj._element = svg.getElementById(element_id);
+ obj._format = format;
+ return obj;
+ },
+
+ update : func(driver)
+ {
+ me._element.setText(sprintf(me._format, driver.getValue(me._name)));
+ },
+};
+
+var DynamicSliderElementMap =
+{
+ new : func(svg, name, element_id, min=0.0, max=1.0)
+ {
+ var obj = { parents : [ DynamicSliderElementMap ] };
+ obj._name = name;
+ obj._element_id = element_id;
+ obj._element = svg.getElementById(element_id);
+ obj._baseTranslation = obj._element.getTranslation();
+ obj._min = min;
+ obj._max = max;
+ return obj;
+ },
+
+ update : func(driver)
+ {
+ var value = driver.getValue(me._name);
+ if (value == nil) value = 0.0;
+
+ # Bound value to the minimum and maximum values.
+ value = math.max(me._min, value);
+ value = math.min(me._max, value);
+
+ # Convert to normalized value
+ value = (value - me._min) / (me._max - me._min);
+
+ # Simply shift the slider along. All sliders have a 0 value
+ # at x=3.5, and a maximum value at x=138.0.
+ me._element.setTranslation([
+ me._baseTranslation[0] + (value * (138.0 - 3.5)),
+ me._baseTranslation[1]
+ ]);
+ },
+};
+
+
+var DynamicRotatingElementMap =
+{
+ new : func(svg, name, element_id, min=0.0, max=1.0, range=360.0)
+ {
+ var obj = { parents : [ DynamicRotatingElementMap ] };
+ obj._name = name;
+ obj._element_id = element_id;
+ obj._element = svg.getElementById(element_id);
+ obj._baseTranslation = obj._element.getTranslation();
+ obj._min = min;
+ obj._max = max;
+ obj._range = range;
+ return obj;
+ },
+
+ update : func(driver)
+ {
+ var value = driver.getValue(me._name);
+ if (value == nil) value = 0.0;
+
+ # Bound value to the minimum and maximum values.
+ value = math.max(me._min, value);
+ value = math.min(me._max, value);
+
+ # Convert to normalized value
+ value = (value - me._min) / (me._max - me._min);
+ me._element.setRotation(value * me._range * D2R, [100.0, 100.0]);
+ },
+};
+
+var EIS =
+{
+ new : func (myCanvas, EISDriver)
+ {
+ var obj = { parents : [ EIS ] };
+ obj._canvas = myCanvas;
+ obj._EISDriver = EISDriver;
+
+ obj._svg= obj._canvas.createGroup("EIS");
+ canvas.parsesvg(obj._svg, '/Aircraft/Instruments-3d/FG1000/Models/EIS_c172s.svg');
+
+ # Objects to display as straight text
+ obj._textElements = [
+ DynamicTextElementMap.new(obj._svg, "RPM", "RPMDisplay", "%i"),
+ DynamicTextElementMap.new(obj._svg, "MBusVolts" , "MBusVolts", "%.01f"),
+ DynamicTextElementMap.new(obj._svg, "MBusVolts" , "EBusVolts", "%.01f"),
+ DynamicTextElementMap.new(obj._svg, "EngineHours" , "EngineHours", "%.01f"),
+ ];
+
+ obj._sliderElements = [
+ DynamicSliderElementMap.new(obj._svg, "FuelFlowGPH", "FuelFlowPointer", 0.0, 20.0),
+ DynamicSliderElementMap.new(obj._svg, "OilPressurePSI", "OilPressurePointer", 0.0, 115.0),
+ DynamicSliderElementMap.new(obj._svg, "OilTemperatureF", "OilTempPointer", 0.0, 245.0),
+ DynamicSliderElementMap.new(obj._svg, "EGTNorm", "EGTPointer", 0.0, 1.0),
+ DynamicSliderElementMap.new(obj._svg, "EGTNorm", "EGTCylinder", 0.0, 1.0),
+ DynamicSliderElementMap.new(obj._svg, "VacuumSuctionInHG", "VacPointer", 3.0, 7.0),
+
+ DynamicSliderElementMap.new(obj._svg, "LeftFuelUSGal", "LeftFuelPointer", 0.0, 30.0),
+ DynamicSliderElementMap.new(obj._svg, "RightFuelUSGal", "RightFuelPointer", 0.0, 30.0),
+ ];
+
+ obj._rotationElements = [
+ DynamicRotatingElementMap.new(obj._svg, "RPM", "RPMPointer", 0.0, 3000.0, 260.0),
+ ];
+
+ obj._svg.getElementById("RPMPointer").set("center-offset-x", 150.0);
+ obj._svg.getElementById("RPMPointer").set("center-offset-y", 100.0);
+
+ return obj;
+ },
+
+ update : func()
+ {
+ foreach(var te; me._textElements) {
+ te.update(me._EISDriver);
+ }
+
+ foreach(var se; me._sliderElements) {
+ se.update(me._EISDriver);
+ }
+
+ foreach(var se; me._rotationElements) {
+ se.update(me._EISDriver);
+ }
+
+ }
+
+};
diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFD.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFD.nas
new file mode 100644
index 000000000..3d7141927
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFD.nas
@@ -0,0 +1,85 @@
+# FG1000 MFD
+
+var nasal_dir = getprop("/sim/fg-root") ~ "/Aircraft/Instruments-3d/FG1000/Nasal/";
+io.load_nasal(nasal_dir ~ 'NavMap/NavMap.nas', "fg1000");
+io.load_nasal(nasal_dir ~ 'NavMap/NavMapStyles.nas', "fg1000");
+io.load_nasal(nasal_dir ~ 'NavMap/NavMapOptions.nas', "fg1000");
+io.load_nasal(nasal_dir ~ 'NavMap/NavMapController.nas', "fg1000");
+
+io.load_nasal(nasal_dir ~ 'TrafficMap/TrafficMap.nas', "fg1000");
+io.load_nasal(nasal_dir ~ 'TrafficMap/TrafficMapStyles.nas', "fg1000");
+io.load_nasal(nasal_dir ~ 'TrafficMap/TrafficMapOptions.nas', "fg1000");
+io.load_nasal(nasal_dir ~ 'TrafficMap/TrafficMapController.nas', "fg1000");
+
+io.load_nasal(nasal_dir ~ 'EIS.nas', "fg1000");
+io.load_nasal(nasal_dir ~ 'Drivers/EISDriver.nas', "fg1000");
+io.load_nasal(nasal_dir ~ 'PageGroupController.nas', "fg1000");
+
+
+var MFD =
+{
+ # Center of any maps
+ MAP_CENTER : {
+ X: (1024/2 + 60),
+ Y: (768/2 + 20)
+ },
+
+ # Constants for the hard-buttons on the fascia
+ FASCIA : {
+ FMS_OUTER : 0,
+ FMS_INNER : 1,
+ RANGE : 2,
+ },
+
+
+
+ new : func (myCanvas)
+ {
+ var obj = { parents : [ MFD ] };
+
+ obj._svg = myCanvas.createGroup("softkeys");
+ canvas.parsesvg(obj._svg, '/Aircraft/Instruments-3d/FG1000/Models/MFD.svg');
+
+ obj._MFDDevice = canvas.PFD_Device.new(obj._svg, 12, "SoftKey", myCanvas, "MFD");
+ obj._MFDDevice.RegisterWithEmesary();
+
+ # Engine Information System
+ obj._eisDriver = fg1000.EISDriver.new();
+ obj._eis = fg1000.EIS.new(myCanvas, obj._eisDriver);
+
+ # Controller for the display on the bottom left which allows selection
+ # of page groups and individual pages using the FMS controller.
+ obj._pageGroupController = fg1000.PageGroupController.new(myCanvas, obj._svg, obj._MFDDevice);
+ obj._pageGroupController.RegisterWithEmesary();
+
+ obj._pageGroupController.addPage("NavigationMap", fg1000.NavMap.new(myCanvas, obj._MFDDevice, obj._svg));
+ obj._pageGroupController.addPage("TrafficMap", fg1000.TrafficMap.new(myCanvas, obj._MFDDevice, obj._svg));
+
+ # Display the NavMap and the appropriate top level on startup.
+ obj._MFDDevice.selectPage(obj._pageGroupController.getPage("NavigationMap"));
+
+ # Add a wheel controller., which we will attach to the zoom.
+ myCanvas.addEventListener("wheel", func(e)
+ {
+ if (e.deltaY >0) {
+ obj._MFDDevice.current_page.controller.zoomIn();
+ } else {
+ obj._MFDDevice.current_page.controller.zoomOut();
+ }
+ });
+
+ var updateTimer = func() {
+ obj._eisDriver.update();
+ obj._eis.update();
+ settimer(updateTimer, 0.1);
+ };
+
+ updateTimer();
+
+ return obj;
+ },
+ del: func()
+ {
+ me._pageGroupController.DeRegisterWithEmesary();
+ }
+};
diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/NavMap/NavMap.nas b/Aircraft/Instruments-3d/FG1000/Nasal/NavMap/NavMap.nas
new file mode 100644
index 000000000..20e6a9512
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Nasal/NavMap/NavMap.nas
@@ -0,0 +1,211 @@
+# Navigation Map
+var NavMap =
+{
+ new : func (myCanvas, device, svg)
+ {
+ var obj = {
+ _group : myCanvas.createGroup("NavigationMapLayer"),
+ parents : [ NavMap, device.addPage("NavigationMap", "NavigationMapGroup") ]
+ };
+
+ obj.Styles = fg1000.NavMapStyles.new();
+ obj.Options = fg1000.NavMapOptions.new();
+ obj.MFDMap = obj._group.createChild("map");
+
+ # Need to display this underneath the softkeys, EIS, header.
+ obj._group.set("z-index", -10.0);
+ obj._group.setVisible(0);
+
+ obj.device = device;
+
+ # Initialize the controller:
+ var ctrl_ns = canvas.Map.Controller.get("Aircraft position");
+ var source = ctrl_ns.SOURCES["current-pos"];
+ if (source == nil) {
+ # TODO: amend
+ var source = ctrl_ns.SOURCES["current-pos"] = {
+ 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:
+ obj.MFDMap.setController("Aircraft position", "current-pos"); # from aircraftpos.controller
+
+ # Center the map's origin, modified to take into account the surround.
+ obj.MFDMap.setTranslation(
+ fg1000.MFD.MAP_CENTER.X,
+ fg1000.MFD.MAP_CENTER.Y
+ );
+
+ 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('GRID'),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')] ) {
+ obj.MFDMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,
+ visible: type.vis, priority: 4,
+ style: obj.Styles.getStyle(type.name),
+ options: obj.Options.getOption(type.name) );
+ }
+
+ foreach(var type; [ r('STAMEN_terrain'),r('STAMEN'), r('OpenAIP') ]) {
+ obj.MFDMap.addLayer(factory: canvas.OverlayLayer, type_arg: type.name,
+ visible: 0, priority: 1,
+ style: obj.Styles.getStyle(type.name),
+ options: obj.Options.getOption(type.name) );
+ }
+
+ obj.controller = fg1000.NavMapController.new(obj, svg, "RangeDisplay", 8);
+
+ var topMenu = func(device, pg, menuitem) {
+ pg.clearMenu();
+ resetMenuColors(device);
+ pg.addMenuItem(0, "ENGINE", pg, engineMenu);
+ pg.addMenuItem(2, "MAP", pg, mapMenu);
+ pg.addMenuItem(8, "DCLTR", pg, func(dev, pg, mi) { obj.controller.incrDCLTR(dev, mi); } );
+ #pg.addMenuItem(9, "SHW CHRT", pg); # Optional
+ #pg.addMenuItem(10, "CHKLIST", pg); # Optional
+ device.updateMenus();
+ };
+
+ var engineMenu = func(device, pg, menuitem) {
+ pg.clearMenu();
+ resetMenuColors(device);
+ pg.addMenuItem(0, "ENGINE", pg, engineMenu);
+ pg.addMenuItem(1, "LEAN", pg, leanMenu);
+ pg.addMenuItem(2, "SYSTEM", pg, systemMenu);
+ pg.addMenuItem(8, "BACK", pg, topMenu);
+ device.updateMenus();
+ };
+
+
+ # Display map toggle softkeys which change color depending
+ # on whether a particular layer is enabled or not.
+ var display_toggle = func(device, svg, mi, layer) {
+ var bg_name = sprintf("SoftKey%d-bg",mi.menu_id);
+ if (obj.controller.isEnabled(layer)) {
+ device.svg.getElementById(bg_name).setColorFill(0.5,0.5,0.5);
+ svg.setColor(0.0,0.0,0.0);
+ } else {
+ device.svg.getElementById(bg_name).setColorFill(0.0,0.0,0.0);
+ svg.setColor(1.0,1.0,1.0);
+ }
+ svg.setText(mi.title);
+ svg.setVisible(1); # display function
+ };
+
+ # Function to undo any colors set by display_toggle when loading a new menu
+ var resetMenuColors = func(device) {
+ for(var i = 0; i < 12; i +=1) {
+ var name = sprintf("SoftKey%d",i);
+ device.svg.getElementById(name ~ "-bg").setColorFill(0.0,0.0,0.0);
+ device.svg.getElementById(name).setColor(1.0,1.0,1.0);
+ }
+ }
+
+ var mapMenu = func(device, pg, menuitem) {
+ pg.clearMenu();
+ resetMenuColors(device);
+ pg.addMenuItem(0, "TRAFFIC", pg,
+ func(dev, pg, mi) { obj.controller.toggleLayer("TFC"); device.updateMenus(); }, # callback
+ func(svg, mi) { display_toggle(device, svg, mi, "TFC"); }
+ );
+
+ pg.addMenuItem(1, "PROFILE", pg);
+ pg.addMenuItem(2, "TOPO", pg,
+ func(dev, pg, mi) { obj.controller.toggleLayer("STAMEN"); device.updateMenus(); }, # callback
+ func(svg, mi) { display_toggle(device, svg, mi, "STAMEN"); }
+ );
+
+ pg.addMenuItem(3, "TERRAIN", pg,
+ func(dev, pg, mi) { obj.controller.toggleLayer("STAMEN_terrain"); device.updateMenus(); }, # callback
+ func(svg, mi) { display_toggle(device, svg, mi, "STAMEN_terrain"); }
+ );
+
+ pg.addMenuItem(4, "AIRWAYS", pg, func(dev, pg, mi) { obj.controller.incrAIRWAYS(dev, mi); } );
+ #pg.addMenuItem(5, "STRMSCP", pg); Optional
+ #pg.addMenuItem(6, "PRECIP", pg); Optional, or NEXRAD
+ #pg.addMenuItem(7, "XM LTNG", pg); Optional, or DL LTNG
+ #pg.addMenuItem(8, "METAR", pg);
+ #pg.addMenuItem(9, "LEGEND", pg); Optional - only available with NEXRAD/XM LTNG/METAR/PROFILE selected
+ pg.addMenuItem(10, "BACK", pg, topMenu); # Or should this just be the next button?
+ device.updateMenus();
+ };
+
+ var leanMenu = func(device, pg, menuitem) {
+ pg.clearMenu();
+ resetMenuColors(device);
+ pg.addMenuItem(0, "ENGINE", pg, engineMenu);
+ pg.addMenuItem(1, "LEAN", pg, leanMenu);
+ pg.addMenuItem(2, "SYSTEM", pg, systemMenu);
+ pg.addMenuItem(3, "CYL SELECT", pg);
+ pg.addMenuItem(4, "ASSIST", pg);
+ pg.addMenuItem(9, "BACK", pg, engineMenu);
+ device.updateMenus();
+ };
+
+ var systemMenu = func(device, pg, menuitem) {
+ pg.clearMenu();
+ resetMenuColors(device);
+ pg.addMenuItem(0, "ENGINE", pg, engineMenu);
+ pg.addMenuItem(1, "LEAN", pg, leanMenu);
+ pg.addMenuItem(2, "SYSTEM", pg, systemMenu);
+ pg.addMenuItem(3, "RST FUEL", pg);
+ pg.addMenuItem(4, "GAL REM", pg, galRemMenu);
+ pg.addMenuItem(5, "BACK", pg, engineMenu);
+ device.updateMenus();
+ };
+
+ var galRemMenu = func(device, pg, menuitem) {
+ pg.clearMenu();
+ resetMenuColors(device);
+ pg.addMenuItem(0, "ENGINE", pg, engineMenu);
+ pg.addMenuItem(1, "LEAN", pg, leanMenu);
+ pg.addMenuItem(2, "SYSTEM", pg, systemMenu);
+ pg.addMenuItem(3, "-10 GAL", pg);
+ pg.addMenuItem(4, "-1 GAL", pg);
+ pg.addMenuItem(5, "+1 GAL", pg);
+ pg.addMenuItem(6, "+10 GAL", pg);
+ pg.addMenuItem(7, "44 GAL", pg);
+ pg.addMenuItem(8, "BACK", pg, engineMenu);
+ device.updateMenus();
+ };
+
+ topMenu(device, obj, nil);
+
+ return obj;
+ },
+ toggleLayerVisible : func(name) {
+ (var l = me.MFDMap.getLayer(name)).setVisible(l.getVisible());
+ },
+ setLayerVisible : func(name,n=1) {
+ me.MFDMap.getLayer(name).setVisible(n);
+ },
+ setRange : func(range) {
+ me.MFDMap.setRange(range);
+ },
+ setScreenRange : func(range) {
+ me.MFDMap.setScreenRange(range);
+ },
+ offdisplay : func() {
+ me._group.setVisible(0);
+
+ # Reset the menu colours. Shouldn't have to do this here, but
+ # there's not currently an obvious other location to do so.
+ for(var i = 0; i < 12; i +=1) {
+ var name = sprintf("SoftKey%d",i);
+ me.device.svg.getElementById(name ~ "-bg").setColorFill(0.0,0.0,0.0);
+ me.device.svg.getElementById(name).setColor(1.0,1.0,1.0);
+ }
+ },
+ ondisplay : func() {
+ me._group.setVisible(1);
+ },
+};
diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/NavMap/NavMapController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/NavMap/NavMapController.nas
new file mode 100644
index 000000000..6a339a974
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Nasal/NavMap/NavMapController.nas
@@ -0,0 +1,152 @@
+# Navigation Map Controller
+var NavMapController =
+{
+ # Vertical ranges, and labels.
+ # 28 ranges from 500ft to 2000nm, measuring the vertical map distance.
+ # Vertical size of the map (once the nav box and softkey area is removed) is 689px.
+ # 2000nm = 12,152,000ft.
+ RANGES : [{range: 500/6076.12, label: "500ft"},
+ {range: 750/6076.12, label: "750ft"},
+ {range: 1000/6076.12, label: "1000ft"},
+ {range: 1500/6076.12, label: "1500ft"},
+ {range: 2000/6076.12, label: "2000ft"},
+ {range: 0.5, label: "0.5nm"},
+ {range: 0.75, label: "0.75nm"},
+ {range: 1, label: "1nm"},
+ {range: 2, label: "2nm"},
+ {range: 3, label: "3nm"},
+ {range: 4, label: "4nm"},
+ {range: 6, label: "6nm"},
+ {range: 8, label: "8nm"},
+ {range: 10, label: "10nm"},
+ {range: 12, label: "12nm"},
+ {range: 15, label: "15nm"},
+ {range: 20, label: "20nm"},
+ {range: 25, label: "25nm"},
+ {range: 30, label: "30nm"},
+ {range: 40, label: "40nm"},
+ {range: 50, label: "50nm"},
+ {range: 75, label: "75nm"},
+ {range: 100, label: "100nm"},
+ {range: 200, label: "200nm"},
+ {range: 500, label: "500nm"},
+ {range: 1000, label: "1000nm"},
+ {range: 1500, label: "1500nm"},
+ {range: 2000, label: "2000nm"}, ],
+
+ # Layer display configuration:
+ # enabled - whether this layer has been enabled by the user
+ # declutter - the maximum declutter level (0-3) that this layer is visible in
+ # range - the maximum range this layer is visible (configured by user)
+ # max_range - the maximum range value that a user can configure for this layer.
+ LAYER_RANGES : {
+ GRID : { enabled: 0, declutter: 1, range: 20, max_range: 2000 },
+ DME : { enabled: 1, declutter: 1, range: 150, max_range: 300 },
+ VOR : { enabled: 1, declutter: 1, range: 150, max_range: 300 },
+ NDB : { enabled: 1, declutter: 1, range: 15, max_range: 30 },
+ FIX : { enabled: 1, declutter: 1, range: 15, max_range: 30 },
+ RTE : { enabled: 1, declutter: 3, range: 2000, max_range: 2000 },
+ WPT : { enabled: 1, declutter: 3, range: 2000, max_range: 2000 },
+
+ APS : { enabled: 1, declutter: 3, range: 2000, max_range: 2000 },
+ FLT : { enabled: 1, declutter: 3, range: 2000, max_range: 2000 },
+
+ WXR : { enabled: 1, declutter: 2, range: 2000, max_range: 2000 },
+
+ APT : { enabled: 1, declutter: 2, range: 150, max_range: 300 },
+
+ TFC : { enabled: 0, declutter: 3, range: 150, max_range: 2000},
+
+ OpenAIP : { enabled: 1, declutter: 1, range: 150, max_range: 300 },
+ STAMEN : { enabled: 1, declutter: 3, range: 500, max_range: 2000 },
+ STAMEN_terrain : { enabled: 1, declutter: 3, range: 500, max_range: 2000 },
+ },
+
+ # TODO: Add STAMEN topo layer, which is visible on all levels as opposed to
+ # roads, railways, boundaries, cities which are only visible on declutter 0.
+
+ # Declutter levels.
+ DCLTR : [ "DCLTR", "DCLTR-1", "DCLTR-2", "DCLTR-3"],
+
+ # Airways levels.
+ AIRWAYS : [ "AIRWAYS", "AIRWY ON", "AIRWY LO", "AIRWY HI"],
+
+ new : func (navmap, svg, zoom_label, current_zoom)
+ {
+ var obj = { parents : [ NavMapController ] };
+ obj.current_zoom = current_zoom;
+ obj.declutter = 0;
+ obj.airways = 0;
+ obj.navmap = navmap;
+ obj.navmap.setScreenRange(689/2.0);
+ obj.label = svg.getElementById(zoom_label);
+ obj.setZoom(obj.current_zoom);
+ return obj;
+ },
+ zoomIn : func() {
+ me.setZoom(me.current_zoom -1);
+ },
+ zoomOut : func() {
+ me.setZoom(me.current_zoom +1);
+ },
+ zoom : func(val)
+ {
+ var incr_or_decr = (val > 0) ? 1 : -1;
+ me.setZoom(me.current_zoom + incr_or_decr);
+ },
+ setZoom : func(zoom) {
+ if ((zoom < 0) or (zoom > (size(me.RANGES) - 1))) return;
+ me.current_zoom = zoom;
+ # Ranges above represent vertical ranges, but the display is a rectangle, so
+ # we need to use the diagonal range of the 1024 x 689, which is 617px.
+ # 617px is 1.8 x 689/2, so we need to increase the range values by x1.8
+ me.navmap.setRange(me.RANGES[zoom].range);
+ me.label.setText(me.RANGES[zoom].label);
+ me.updateVisibility();
+ },
+ updateVisibility : func() {
+ # Determine which layers should be visible.
+ foreach (var layer_name; keys(me.LAYER_RANGES)) {
+ var layer = me.LAYER_RANGES[layer_name];
+ if (layer.enabled and
+ (me.RANGES[me.current_zoom].range <= layer.range) and
+ (me.declutter <= layer.declutter) )
+ {
+ me.navmap.MFDMap.getLayer(layer_name).setVisible(1);
+ } else {
+ me.navmap.MFDMap.getLayer(layer_name).setVisible(0);
+ }
+ }
+ },
+ configureLayer : func(layer, enabled, range) {
+ me.LAYER_RANGES[layer].enabled = enabled;
+ me.LAYER_RANGES[layer].range = math.min(range, me.LAYER_RANGES[layer].max_range);
+ },
+ isEnabled : func(layer) {
+ return me.LAYER_RANGES[layer].enabled;
+ },
+ toggleLayer : func(layer) {
+ me.LAYER_RANGES[layer].enabled = ! me.LAYER_RANGES[layer].enabled;
+ me.updateVisibility();
+ },
+
+ # Increment through the de-clutter levels, which impact what layers are
+ # displayed. We also need to update the declutter menu item.
+ incrDCLTR : func(device, menuItem) {
+ me.declutter = math.mod(me.declutter +1, 4);
+ menuItem.title = me.DCLTR[me.declutter];
+ device.updateMenus();
+ me.updateVisibility();
+ },
+
+ # Increment through the AIRWAYS levels. At present this doesn't do anything
+ # except change the label. It should enable/disable different airways
+ # information.
+ incrAIRWAYS : func(device, menuItem) {
+ me.airways = math.mod(me.airways +1, 4);
+ menuItem.title = me.AIRWAYS[me.airways];
+ device.updateMenus();
+ me.updateVisibility();
+ },
+
+};
diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/NavMap/NavMapOptions.nas b/Aircraft/Instruments-3d/FG1000/Nasal/NavMap/NavMapOptions.nas
new file mode 100644
index 000000000..f7f1d4188
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Nasal/NavMap/NavMapOptions.nas
@@ -0,0 +1,28 @@
+# Navigation Map Options
+var NavMapOptions =
+{
+ new : func() {
+ var obj = { parents : [NavMapOptions] };
+ obj.Options= {};
+ obj.loadOptions();
+ return obj;
+ },
+
+ getOption : func(type) {
+ return me.Options[type];
+ },
+
+ setOption : func(type, name, value) {
+ me.Options[type][name] = value;
+ },
+
+ loadOptions : func() {
+ me.clearOptions();
+ me.Options.APS = {};
+ },
+
+ clearOptions : func() {
+ me.Options = {};
+ },
+
+};
diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/NavMap/NavMapStyles.nas b/Aircraft/Instruments-3d/FG1000/Nasal/NavMap/NavMapStyles.nas
new file mode 100644
index 000000000..aa775ee58
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Nasal/NavMap/NavMapStyles.nas
@@ -0,0 +1,66 @@
+# Navigation Map Styles
+var NavMapStyles =
+{
+ new : func() {
+ var obj = { parents : [ NavMapStyles ]};
+ obj.Styles = {};
+ obj.loadStyles();
+ return obj;
+ },
+
+ getStyle : func(type) {
+ return me.Styles[type];
+ },
+
+ setStyle : func(type, name, value) {
+ me.Styles[type][name] = value;
+ },
+
+ loadStyles : func() {
+ me. clearStyles();
+ me.Styles.DME = {};
+ me.Styles.DME.debug = 1; # HACK for benchmarking/debugging purposes
+ me.Styles.DME.animation_test = 0; # for prototyping animated symbols
+
+ me.Styles.DME.scale_factor = 0.4; # 40% (applied to whole group)
+ me.Styles.DME.line_width = 3.0;
+ me.Styles.DME.color_tuned = [0,1,0]; #rgb
+ me.Styles.DME.color_default = [1,1,0]; #rgb
+
+ me.Styles.APT = {};
+ me.Styles.APT.scale_factor = 0.4; # 40% (applied to whole group)
+ me.Styles.APT.line_width = 3.0;
+ me.Styles.APT.color_default = [0,0.6,0.85]; #rgb
+ me.Styles.APT.label_font_color = me.Styles.APT.color_default;
+ me.Styles.APT.label_font_size=28;
+
+ me.Styles.TFC = {};
+ me.Styles.TFC.scale_factor = 0.4; # 40% (applied to whole group)
+
+ me.Styles.WPT = {};
+ me.Styles.WPT.scale_factor = 0.5; # 50% (applied to whole group)
+
+ me.Styles.RTE = {};
+ me.Styles.RTE.line_width = 2;
+
+ me.Styles.FLT = {};
+ me.Styles.FLT.line_width = 3;
+
+ me.Styles.FIX = {};
+ me.Styles.FIX.color = [1,0,0];
+ me.Styles.FIX.scale_factor = 0.4; # 40%
+
+ me.Styles.VOR = {};
+ me.Styles.VOR.range_line_width = 2;
+ me.Styles.VOR.radial_line_width = 1;
+ me.Styles.VOR.scale_factor = 0.6; # 60%
+
+ me.Styles.APS = {};
+ me.Styles.APS.scale_factor = 0.25;
+ },
+
+ clearStyles : func() {
+ me.Styles = {};
+ },
+
+};
diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/PageGroupController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/PageGroupController.nas
new file mode 100644
index 000000000..5a25ea714
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Nasal/PageGroupController.nas
@@ -0,0 +1,195 @@
+# Controller for the PageGroup navigation, displayed in the bottom right of the
+# FMS, and controlled by the FMS knob
+
+# Set of pages, references by SVG ID
+var PAGE_GROUPS = [
+
+ { label: "MapPageGroupLabel",
+ group: "MapPageGroup",
+ pages: [ "NavigationMap", "TrafficMap", "Stormscope", "WeatherDataLink", "TAWSB"],
+ },
+ { label: "WPTGroupLabel",
+ group: "WPTPageGroup",
+ pages: [ "AirportInformation", "IntersectionInformation", "NDBInformation", "VORInformation", "UserWPTInformation"],
+ },
+
+ { label: "AuxGroupLabel",
+ group: "AuxPageGroup",
+ pages: [ "TripPlanning", "Utility", "GPSStatus", "XMRadio", "SystemStatus"],
+ },
+
+ { label: "FPLGroupLabel",
+ group: "FPLPageGroup",
+ pages: [ "ActiveFlightPlan", "FlightPlanCatalog", "StoredFlightPlan"],
+ },
+
+ { label: "LstGroupLabel",
+ group: "LstPageGroup",
+ pages: [ "Checklist1", "Checklist2", "Checklist3", "Checklist4", "Checklist5"],
+ },
+
+ { label: "NrstGroupLabel",
+ group: "NrstPageGroup",
+ pages: [ "NearestAirports", "NearestIntersections", "NearestNDB", "NearestVOR", "NearestUserWaypoints", "NearestFrequencies", "NearestAirspaces"],
+ }
+];
+
+var PageGroupController =
+{
+ new : func (myCanvas, svg, device)
+ {
+ var obj = { parents : [ PageGroupController ] };
+ obj._canvas = myCanvas;
+ obj._svg = svg;
+ obj._device = device;
+ obj._menuVisible = 0;
+ obj._selectedPageGroup = 0;
+ obj._selectedPage = 0;
+
+ # List of pages to be controllers. Keys are the pages in PAGE_GROUPS;
+ obj._pageList = {};
+
+ # Timers to controll when to hide the menu after inactivity, and when to load
+ # a new page.
+ obj._hideMenuTimer = maketimer(5, obj, obj.hideMenu);
+ obj._hideMenuTimer.singleShot = 1;
+
+ obj._loadPageTimer = maketimer(0.5, obj, obj.loadPage);
+ obj._loadPageTimer.singleShot = 1;
+
+ # Emesary
+ obj._recipient = nil;
+
+ obj.hideMenu();
+ return obj;
+ },
+
+ addPage : func(name, page)
+ {
+ me._pageList[name] = page;
+ },
+
+ getPage : func(name)
+ {
+ return me._pageList[name];
+ },
+
+ hideMenu : func()
+ {
+ foreach(var pageGroup; PAGE_GROUPS)
+ {
+ me._svg.getElementById(pageGroup.group).setVisible(0);
+ me._svg.getElementById(pageGroup.label).setVisible(0);
+ }
+ me._menuVisible = 0;
+ },
+
+ # Function to change a page based on the selection
+ loadPage : func()
+ {
+ var pageToLoad = PAGE_GROUPS[me._selectedPageGroup].pages[me._selectedPage];
+ var page = me._pageList[pageToLoad];
+
+ if (page != nil) {
+ me._device.selectPage(page);
+ } else {
+ printf("Unable to find page " ~ pageToLoad);
+ }
+ },
+
+ showMenu : func()
+ {
+ foreach(var pageGroup; PAGE_GROUPS)
+ {
+ if (PAGE_GROUPS[me._selectedPageGroup].label == pageGroup.label)
+ {
+ # Display the page group and highlight the label
+ me._svg.getElementById(pageGroup.group).setVisible(1);
+ me._svg.getElementById(pageGroup.label).setVisible(1);
+ me._svg.getElementById(pageGroup.label).setColor(0.7,0.7,1.0);
+
+ foreach (var page; pageGroup.pages)
+ {
+ # Highlight the current page.
+ if (pageGroup.pages[me._selectedPage] == page) {
+ me._svg.getElementById(page).setColor(0.7,0.7,1.0);
+ } else {
+ me._svg.getElementById(page).setColor(0.7,0.7,0.7);
+ }
+ }
+ }
+ else
+ {
+ # Hide the pagegroup and unhighlight the label on the bottom
+ me._svg.getElementById(pageGroup.group).setVisible(0);
+ me._svg.getElementById(pageGroup.label).setVisible(1);
+ me._svg.getElementById(pageGroup.label).setColor(0.7,0.7,0.7);
+ }
+ }
+ me._menuVisible = 1;
+ me._hideMenuTimer.stop();
+ me._hideMenuTimer.restart(5);
+ me._loadPageTimer.stop();
+ me._loadPageTimer.restart(0.5);
+
+ },
+
+ FMSOuter : func(val)
+ {
+ if (me._menuVisible == 1) {
+ # Change page group
+ var incr_or_decr = (val > 0) ? 1 : -1;
+ me._selectedPageGroup = math.mod(me._selectedPageGroup + incr_or_decr, size(PAGE_GROUPS));
+ me._selectedPage = 0;
+ }
+ me.showMenu();
+ },
+
+ FMSInner : func(val)
+ {
+ if (me._menuVisible == 1) {
+ # Change page group
+ var incr_or_decr = (val > 0) ? 1 : -1;
+ me._selectedPage = math.mod(me._selectedPage + incr_or_decr, size(PAGE_GROUPS[me._selectedPageGroup].pages));
+ }
+ me.showMenu();
+ },
+ RegisterWithEmesary : func(transmitter = nil){
+ if (transmitter == nil)
+ transmitter = emesary.GlobalTransmitter;
+
+ if (me._recipient == nil){
+ me._recipient = emesary.Recipient.new("PageController_" ~ me._device.designation);
+ var pfd_obj = me._device;
+ var controller = me;
+ me._recipient.Receive = func(notification)
+ {
+ if (notification.Device_Id == pfd_obj.device_id
+ and notification.NotificationType == notifications.PFDEventNotification.DefaultType) {
+ if (notification.Event_Id == notifications.PFDEventNotification.HardKeyPushed
+ and notification.EventParameter != nil)
+ {
+ var id = notification.EventParameter.Id;
+ var value = notification.EventParameter.Value;
+ #printf("Button pressed " ~ id ~ " " ~ value);
+ if (id == fg1000.MFD.FASCIA.FMS_OUTER) controller.FMSOuter(value);
+ if (id == fg1000.MFD.FASCIA.FMS_INNER) controller.FMSInner(value);
+ if (id == fg1000.MFD.FASCIA.RANGE) { if (pfd_obj.current_page.controller.zoom != nil) pfd_obj.current_page.controller.zoom(value); }
+ }
+
+ return emesary.Transmitter.ReceiptStatus_OK;
+ }
+ return emesary.Transmitter.ReceiptStatus_NotProcessed;
+ };
+ transmitter.Register(me._recipient);
+ me.transmitter = transmitter;
+ }
+ },
+ DeRegisterWithEmesary : func(transmitter = nil){
+ # remove registration from transmitter; but keep the recipient once it is created.
+ if (me.transmitter != nil)
+ me.transmitter.DeRegister(me._recipient);
+ me.transmitter = nil;
+ },
+
+};
diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/TrafficMap/TrafficMap.nas b/Aircraft/Instruments-3d/FG1000/Nasal/TrafficMap/TrafficMap.nas
new file mode 100644
index 000000000..3d9a07df5
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Nasal/TrafficMap/TrafficMap.nas
@@ -0,0 +1,172 @@
+# Traffic Map
+#
+# Functionally similar to the Garmin GTS 800 Unit
+#
+var TrafficMap =
+{
+ new : func (myCanvas, device, svg)
+ {
+ var obj = {
+ _group : myCanvas.createGroup("TrafficMapLayer"),
+ parents : [ TrafficMap, device.addPage("TrafficMap", "TrafficMapGroup") ]
+ };
+
+ obj.Styles = fg1000.TrafficMapStyles.new();
+ obj.Options = fg1000.TrafficMapOptions.new();
+ obj.mapgroup = obj._group.createChild("map");
+ obj.device = device;
+
+ # Dynamic text elements
+ obj.op_label = svg.getElementById("TrafficMapOpMode");
+ obj.alt_label = svg.getElementById("TrafficMapAltMode");
+ obj.outer_label = svg.getElementById("TrafficMapOuterRange");
+ obj.inner_label = svg.getElementById("TrafficMapInnerRange");
+
+ # Need to display this underneath the softkeys, EIS, header.
+ obj._group.set("z-index", -10.0);
+ obj._group.setVisible(0);
+
+ # Initialize the controller:
+ var ctrl_ns = canvas.Map.Controller.get("Aircraft position");
+ var source = ctrl_ns.SOURCES["current-pos"];
+ if (source == nil) {
+ # TODO: amend
+ var source = ctrl_ns.SOURCES["current-pos"] = {
+ 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:
+ obj.mapgroup.setController("Aircraft position", "current-pos"); # from aircraftpos.controller
+
+ # Center the map's origin, modified to take into account the surround.
+ obj.mapgroup.setTranslation(
+ fg1000.MFD.MAP_CENTER.X,
+ fg1000.MFD.MAP_CENTER.Y
+ );
+
+ var r = func(name,vis=1,zindex=nil) return caller(0)[0];
+ foreach(var type; [r('TFC',0),r('APS')] ) {
+ obj.mapgroup.addLayer(canvas.SymbolLayer,
+ type.name,
+ 4,
+ obj.Styles.getStyle(type.name),
+ obj.Options.getOption(type.name),
+ type.vis );
+ }
+
+ obj.controller = fg1000.TrafficMapController.new(obj, svg);
+
+ var topMenu = func(device, pg, menuitem) {
+ pg.clearMenu();
+ resetMenuColors(device);
+ pg.addMenuItem(4, "STANDBY", pg,
+ func(dev, pg, mi) { pg.controller.setOperate(0); device.updateMenus(); }, # callback
+ func(svg, mi) { display_toggle(device, svg, mi, "STANDBY"); }
+ );
+
+ pg.addMenuItem(5, "OPERATE", pg,
+ func(dev, pg, mi) { pg.controller.setOperate(1); device.updateMenus(); }, # callback
+ func(svg, mi) { display_toggle(device, svg, mi, "OPERATE"); }
+ );
+
+ pg.addMenuItem(6, "TEST", pg, func(dev, pg, mi) { printf("Traffic Map TEST mode not implemented yet."); }, nil);
+ pg.addMenuItem(7, "FLT ID", pg,
+ func(dev, pg, mi) { pg.controller.toggleFlightID(); device.updateMenus(); }, # callback
+ func(svg, mi) { display_toggle(device, svg, mi, "FLT ID"); }
+ );
+
+ pg.addMenuItem(8, "ALT MODE", pg, altMenu);
+ device.updateMenus();
+ };
+
+ var altMenu = func(device, pg, menuitem) {
+ pg.clearMenu();
+ resetMenuColors(device);
+ pg.addMenuItem(4, "ABOVE", pg,
+ func(dev, pg, mi) { pg.controller.setAlt("ABOVE"); device.updateMenus(); }, # callback
+ func(svg, mi) { display_toggle(device, svg, mi, "ABOVE"); }
+ );
+ pg.addMenuItem(5, "NORMAL", pg,
+ func(dev, pg, mi) { pg.controller.setAlt("NORMAL"); device.updateMenus(); }, # callback
+ func(svg, mi) { display_toggle(device, svg, mi, "NORMAL"); }
+ );
+
+ pg.addMenuItem(6, "BELOW", pg,
+ func(dev, pg, mi) { pg.controller.setAlt("BELOW"); device.updateMenus(); }, # callback
+ func(svg, mi) { display_toggle(device, svg, mi, "BELOW"); }
+ );
+
+ pg.addMenuItem(7, "UNREST", pg,
+ func(dev, pg, mi) { pg.controller.setAlt("UNREST"); device.updateMenus(); }, # callback
+ func(svg, mi) { display_toggle(device, svg, mi, "UNREST"); }
+ );
+
+ pg.addMenuItem(8, "BACK", pg, topMenu);
+
+ device.updateMenus();
+ };
+
+ # Display map toggle softkeys which change color depending
+ # on whether a particular layer is enabled or not.
+ var display_toggle = func(device, svg, mi, layer) {
+ var bg_name = sprintf("SoftKey%d-bg",mi.menu_id);
+ if (obj.controller.isEnabled(layer)) {
+ device.svg.getElementById(bg_name).setColorFill(0.5,0.5,0.5);
+ svg.setColor(0.0,0.0,0.0);
+ } else {
+ device.svg.getElementById(bg_name).setColorFill(0.0,0.0,0.0);
+ svg.setColor(1.0,1.0,1.0);
+ }
+ svg.setText(mi.title);
+ svg.setVisible(1); # display function
+ };
+
+ # Function to undo any colors set by display_toggle when loading a new menu
+ var resetMenuColors = func(device) {
+ for(var i = 0; i < 12; i +=1) {
+ var name = sprintf("SoftKey%d",i);
+ device.svg.getElementById(name ~ "-bg").setColorFill(0.0,0.0,0.0);
+ device.svg.getElementById(name).setColor(1.0,1.0,1.0);
+ }
+ }
+
+ topMenu(device, obj, nil);
+
+ return obj;
+ },
+ setLayerVisible : func(name,n=1) {
+ me.mapgroup.getLayer(name).setVisible(n);
+ },
+ setRange : func(range, inner_label, outer_label) {
+ me.mapgroup.setRange(range);
+ me.inner_label.setText(inner_label);
+ me.outer_label.setText(outer_label);
+ },
+ setScreenRange : func(range) {
+ me.mapgroup.setScreenRange(range);
+ },
+ offdisplay : func() {
+ me._group.setVisible(0);
+
+ # Reset the menu colours. Shouldn't have to do this here, but
+ # there's not currently an obvious other location to do so.
+ for(var i = 0; i < 12; i +=1) {
+ var name = sprintf("SoftKey%d",i);
+ me.device.svg.getElementById(name ~ "-bg").setColorFill(0.0,0.0,0.0);
+ me.device.svg.getElementById(name).setColor(1.0,1.0,1.0);
+ }
+ },
+ ondisplay : func() {
+ me._group.setVisible(1);
+ },
+};
diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/TrafficMap/TrafficMapController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/TrafficMap/TrafficMapController.nas
new file mode 100644
index 000000000..c652cc81d
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Nasal/TrafficMap/TrafficMapController.nas
@@ -0,0 +1,91 @@
+# Traffic Map Controller
+var TrafficMapController =
+{
+ # Altitude levels levels.
+ ALTS : { ABOVE : { label: "ABOVE", ceiling_ft: 9000, floor_ft: 2700 },
+ NORMAL : { label: "NORMAL", ceiling_ft: 2700, floor_ft: 2700 },
+ BELOW : { label: "BELOW", ceiling_ft: 2700, floor_ft: 9000 },
+ UNREST : { label: "UNRESTRICTED", ceiling_ft: 9000, floor_ft: 9000 }},
+
+
+ # Three ranges available
+ # 2nm
+ # 2nm / 6nm
+ # 6nm / 12nm
+ #
+ # TODO: Currently we simply use the outer range, and display the inner
+ # range as 1/3 of the outer. Doing this properly, we should display
+ # different inner rings.
+ RANGES : [ {range: 2, inner_label: nil, outer_label: "2nm"},
+ {range: 6, inner_label: "2nm", outer_label: "6nm"},
+ {range: 12, inner_label: "4nm", outer_label: "12nm"} ],
+
+
+ new : func (trafficmap, svg)
+ {
+ var obj = { parents : [ TrafficMapController ] };
+ obj.range = 1;
+ obj.alt = "NORMAL";
+ obj.operating = 0;
+ obj.flight_id = 0;
+ obj.trafficmap = trafficmap;
+ obj.trafficmap.setScreenRange(689/2.0);
+
+ obj.setZoom(obj.range);
+ return obj;
+ },
+ zoomIn : func() {
+ me.setZoom(me.current_zoom -1);
+ },
+ zoomOut : func() {
+ me.setZoom(me.current_zoom +1);
+ },
+ zoom : func(val)
+ {
+ var incr_or_decr = (val > 0) ? 1 : -1;
+ me.setZoom(me.current_zoom + incr_or_decr);
+ },
+ setZoom : func(zoom) {
+ if ((zoom < 0) or (zoom > (size(me.RANGES) - 1))) return;
+ me.current_zoom = zoom;
+ me.trafficmap.setRange(
+ me.RANGES[zoom].range,
+ me.RANGES[zoom].inner_label,
+ me.RANGES[zoom].outer_label);
+ },
+ setAlt : func(alt) {
+ if (me.ALTS[alt] == nil) return;
+ me.trafficmap.alt_label.setText(me.ALTS[alt].label);
+ me.alt = alt;
+ # Update the TFC controller to filter out the correct targets
+ me.trafficmap.mapgroup.getLayer("TFC").options.ceiling_ft = me.ALTS[alt].ceiling_ft;
+ me.trafficmap.mapgroup.getLayer("TFC").options.floor_ft = me.ALTS[alt].floor_ft;
+ },
+ setOperate : func(enabled) {
+ if (enabled) {
+ me.trafficmap.op_label.setText("OPERATING");
+ me.trafficmap.setLayerVisible("TFC", 1);
+ me.operating = 1;
+ } else {
+ me.trafficmap.op_label.setText("STANDBY");
+ me.trafficmap.setLayerVisible("TFC", 0);
+ me.operating = 0;
+ }
+ },
+ setFlightID : func(enabled) {
+ me.flight_id = enabled;
+ me.trafficmap.Options.setOption("TFC", "display_id", enabled);
+ },
+ toggleFlightID : func() {
+ me.setFlightID(! me.flight_id);
+ },
+ isEnabled : func(label) {
+ # Cheeky little function that returns whether the alt or operation mode
+ # matches the label. Used to highlight current settings in softkeys
+ if (label == me.alt) return 1;
+ if (me.operating and label == "OPERATE") return 1;
+ if (me.operating == 0 and label == "STANDBY") return 1;
+ if (me.flight_id == 1 and label == "FLT ID") return 1;
+ return 0;
+ },
+};
diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/TrafficMap/TrafficMapOptions.nas b/Aircraft/Instruments-3d/FG1000/Nasal/TrafficMap/TrafficMapOptions.nas
new file mode 100644
index 000000000..e8aa2d148
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Nasal/TrafficMap/TrafficMapOptions.nas
@@ -0,0 +1,34 @@
+# Traffic Map Options
+var TrafficMapOptions =
+{
+ new : func() {
+ var obj = { parents : [TrafficMapOptions] };
+ obj.Options= {};
+ obj.loadOptions();
+ return obj;
+ },
+
+ getOption : func(type) {
+ return me.Options[type];
+ },
+
+ setOption : func(type, name, value) {
+ me.Options[type][name] = value;
+ },
+
+ loadOptions : func() {
+ me.clearOptions();
+ me.Options.APS = {};
+
+ me.Options.TFC = {
+ ceiling_ft : 2700, # Display targets up to this height above the aircraft
+ floor_ft : 2700, # Display target from this height below the aircraft
+ display_id: 0,
+ };
+ },
+
+ clearOptions : func() {
+ me.Options = {};
+ },
+
+};
diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/TrafficMap/TrafficMapStyles.nas b/Aircraft/Instruments-3d/FG1000/Nasal/TrafficMap/TrafficMapStyles.nas
new file mode 100644
index 000000000..c243d2941
--- /dev/null
+++ b/Aircraft/Instruments-3d/FG1000/Nasal/TrafficMap/TrafficMapStyles.nas
@@ -0,0 +1,33 @@
+# Traffic Map Styles
+var TrafficMapStyles =
+{
+ new : func() {
+ var obj = { parents : [ TrafficMapStyles ]};
+ obj.Styles = {};
+ obj.loadStyles();
+ return obj;
+ },
+
+ getStyle : func(type) {
+ return me.Styles[type];
+ },
+
+ setStyle : func(type, name, value) {
+ me.Styles[type][name] = value;
+ },
+
+ loadStyles : func() {
+ me. clearStyles();
+
+ me.Styles.TFC = {};
+ me.Styles.TFC.scale_factor = 1.0; # 40% (applied to whole group)
+
+ me.Styles.APS = {};
+ me.Styles.APS.scale_factor = 0.25;
+ },
+
+ clearStyles : func() {
+ me.Styles = {};
+ },
+
+};
diff --git a/Nasal/canvas/MFD_Generic.nas b/Nasal/canvas/MFD_Generic.nas
index c8b27e764..7940fcb78 100644
--- a/Nasal/canvas/MFD_Generic.nas
+++ b/Nasal/canvas/MFD_Generic.nas
@@ -17,17 +17,22 @@
#
# Menu Item. There is a list of these for each page changing button per display page
# Parameters:
-# menu_id : page change event id for this menu item. e.g. button number
-# title : Title Text (for display on the device)
-# page : Instance of page usually returned from PFD.addPage
+# menu_id : page change event id for this menu item. e.g. button number
+# title : Title Text (for display on the device)
+# page : Instance of page usually returned from PFD.addPage
+# callbackfn : Function to call when menu item is selected
+# displayfn : Function to call when the menu item is displayed. Used to enable
+# highlighting of menu items, for example.
var PFD_MenuItem =
{
- new : func (menu_id, title, page)
+ new : func (menu_id, title, page, callbackfn=nil, displayfn=nil)
{
var obj = {parents : [PFD_MenuItem] };
obj.page = page;
obj.menu_id = menu_id;
+ obj.callbackfn = callbackfn;
+ obj.displayfn = displayfn;
obj.title = title;
return obj;
},
@@ -63,7 +68,7 @@ var PFD_Page =
# make a page that is currenty visible not visible before making a new page visible,
# however more than one page could be visible - but only one set of menu buttons can be active
# so if two pages are visible (e.g. an overlay) then when the overlay removed it would be necessary
- # to call setVisible on the base page to ensure that the menus are seutp
+ # to call setVisible on the base page to ensure that the menus are setup
setVisible : func(vis)
{
if(me.svg != nil)
@@ -75,6 +80,21 @@ var PFD_Page =
me.offdisplay();
},
+ # Standard callback for buttons, causing the appropriate page to be displayed
+ std_callbackfn : func (device, me, mi)
+ {
+ device.selectPage(mi.page);
+ },
+
+ # Standard display function for buttons, displaying the text and making visible
+ std_displayfn : func(svg_element, menuitem)
+ {
+ svg_element.setText(menuitem.title);
+ svg_element.setVisible(1);
+ #me.buttons[mi.menu_id].setText(mi.title);
+ #me.buttons[mi.menu_id].setVisible(1);
+ },
+
#
# Perform action when button is pushed
notifyButton : func(button_id)
@@ -82,7 +102,7 @@ var PFD_Page =
{
if (mi.menu_id == button_id)
{
- me.device.selectPage(mi.page);
+ if (mi.callbackfn != nil) mi.callbackfn(me.device, me, mi);
break;
}
}
@@ -96,14 +116,25 @@ var PFD_Page =
# page that will be selected when pressed
#
# The corresponding menu for the selected page will automatically be loaded
- addMenuItem : func(menu_id, title, page)
+ addMenuItem : func(menu_id, title, page, callbackfn=nil, displayfn=nil)
{
- var nm = PFD_MenuItem.new(menu_id, title, page);
+ if (callbackfn == nil) callbackfn = me.std_callbackfn;
+ if (displayfn == nil) displayfn = me.std_displayfn;
+ var nm = PFD_MenuItem.new(menu_id, title, page, callbackfn, displayfn);
append(me.menus, nm);
return nm;
},
- # base method for update; this can be overriden per page instance to provide update of the
+ #
+ # Clear all items from the menu. Use-case is where they may be a hierarchy
+ # of menus within the same page.
+ #
+ clearMenu : func()
+ {
+ me.menus = [];
+ },
+
+ # base method for update; this can be overridden per page instance to provide update of the
# elements on display (e.g. to display updated properties)
update : func(notification=nil)
{
@@ -133,6 +164,7 @@ var PFD_Device =
# - num_menu_buttons is the Number of menu buttons; starting from the bottom left then right, then top, then left.
# - button prefix (e.g MI_) is the prefix of the labels in the SVG for the menu boxes.
# - _canvas is the canvas group.
+# - designation (optional) is used for Emesary designation
#NOTE:
# This does not actually create the canvas elements, or parse the SVG, that would typically be done in
# a higher level class that contains an instance of this class.
@@ -161,7 +193,7 @@ var PFD_Device =
else
{
obj.buttons[idx] = msvg;
- obj.buttons[idx].setText(sprintf("M",idx));
+ obj.buttons[idx].setText(sprintf("M%d",idx));
}
}
obj.Recipient = nil;
@@ -187,17 +219,19 @@ var PFD_Device =
var pfd_obj = me;
me.Recipient.Receive = func(notification)
{
- if (notification.Device_id == pfd_obj.device_id
+ if (notification.Device_Id == pfd_obj.device_id
and notification.NotificationType == notifications.PFDEventNotification.DefaultType) {
if (notification.Event_Id == notifications.PFDEventNotification.SoftKeyPushed
and notification.EventParameter != nil)
{
+ #printf("Button pressed " ~ notification.EventParameter);
pfd_obj.notifyButton(notification.EventParameter);
}
else if (notification.Event_Id == notifications.PFDEventNotification.ChangeMenuText
and notification.EventParameter != nil)
{
foreach(var eventMenu; notification.EventParameter) {
+ #printf("Menu Text changed : " ~ eventMenu.Text);
foreach (var mi ; pfd_obj.current_page.menus) {
if (pfd_obj.buttons[eventMenu.Id] != nil) {
pfd_obj.buttons[eventMenu.Id].setText(eventMenu.Text);
@@ -253,6 +287,17 @@ var PFD_Device =
return np;
},
#
+ # Get a named page
+ #
+ getPage : func(title)
+ {
+ foreach(var p; me.pages) {
+ if (p.title == title) return p;
+ }
+
+ return nil;
+ },
+ #
# manage the update of the currently selected page
update : func(notification=nil)
{
@@ -264,6 +309,8 @@ var PFD_Device =
# - the page object method controls the visibility
selectPage : func(p)
{
+ if (me.current_page == p) return;
+
if (me.current_page != nil)
me.current_page.setVisible(0);
if (me.buttons != nil)
@@ -276,8 +323,7 @@ var PFD_Device =
{
if (me.buttons[mi.menu_id] != nil)
{
- me.buttons[mi.menu_id].setText(mi.title);
- me.buttons[mi.menu_id].setVisible(1);
+ mi.displayfn(me.buttons[mi.menu_id], mi);
}
else
printf("PFD_device: Menu for button not found. Menu ID '%s'",mi.menu_id);
@@ -290,12 +336,17 @@ var PFD_Device =
# ensure that the menus are display correctly for the current page.
updateMenus : func
{
+ foreach(var mb ; me.buttons)
+ if (mb != nil)
+ mb.setVisible(0);
+
+ if (me.current_page == nil) return;
+
foreach(var mi ; me.current_page.menus)
{
if (me.buttons[mi.menu_id] != nil)
{
- me.buttons[mi.menu_id].setText(mi.title);
- me.buttons[mi.menu_id].setVisible(1);
+ mi.displayfn(me.buttons[mi.menu_id], mi);
}
else
printf("No corresponding item '%s'",mi.menu_id);
diff --git a/Nasal/canvas/map/STAMEN_terrain.lcontroller b/Nasal/canvas/map/STAMEN_terrain.lcontroller
new file mode 100644
index 000000000..061d76f8c
--- /dev/null
+++ b/Nasal/canvas/map/STAMEN_terrain.lcontroller
@@ -0,0 +1,40 @@
+# See: http://wiki.flightgear.org/MapStructure
+# Class things:
+var name = 'STAMEN_terrain';
+var parents = [OverlayLayer.Controller];
+var __self__ = caller(0)[0];
+OverlayLayer.Controller.add(name, __self__);
+TileLayer.add(name, {
+ parents: [TileLayer],
+ type: name, # Layer 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: [],
+ };
+
+ layer.makeURL = string.compileTemplate('http://b.tile.stamen.com/terrain-background/{z}/{x}/{y}.png');
+ layer.makePath = string.compileTemplate(layer.maps_base ~ '/stamen-terrain-background/{z}/{x}/{y}.png');
+
+ #layer.makeURL = string.compileTemplate('http://b.tile.stamen.com/toner/{z}/{x}/{y}.png');
+ #layer.makePath = string.compileTemplate(layer.maps_base ~ '/stamen-toner/{z}/{x}/{y}.png');
+
+
+ m.addVisibilityListener();
+ m.addRangeListener();
+ m.addScreenRangeListener();
+ return m;
+};
+
+var updateLayer = func() {
+}
+
+var del = func() {
+ foreach (var l; me.listeners)
+ removelistener(l);
+};
diff --git a/Nasal/canvas/map/TFC.lcontroller b/Nasal/canvas/map/TFC.lcontroller
index 5f96a1263..7666c24e4 100644
--- a/Nasal/canvas/map/TFC.lcontroller
+++ b/Nasal/canvas/map/TFC.lcontroller
@@ -1,7 +1,7 @@
# 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];
@@ -11,6 +11,13 @@ SymbolLayer.add(name, {
parents: [MultiSymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
+ df_style: {
+ },
+ df_options: { # default configuration options
+ floor_ft: 120000, # Display target from this height below the aircraft
+ ceiling_ft: 120000, # Display targets up to this height above the aircraft
+ display_id: 1, # Display aircraft ID
+ },
});
var model_root = props.globals.getNode("/ai/models/");
@@ -67,7 +74,7 @@ var get_alt_diff = func(model) {
var model_alt = model.get_alt();
var alt = me.map.getAlt();
if (alt == nil or model_alt == nil) return 0;
- return alt-model_alt;
+ return alt - model_alt;
};
##
@@ -83,6 +90,12 @@ var searchCmd_default = func {
var result = [];
var models = 0;
+ var alt = me.map.getAlt();
+ if (alt == nil) alt = 0;
+
+ var min_alt = alt - me.layer.options.floor_ft;
+ var max_alt = alt + me.layer.options.ceiling_ft;
+
# AI and Multiplayer traffic
foreach (var t; model_root.getChildren()) {
if (!t.getValue("valid")) continue;
@@ -95,11 +108,16 @@ var searchCmd_default = func {
printlog("alert", "lat/lon was nil for AI node "~t.getPath());
continue;
}
- if (me.map.controller.in_range(lat, lon))
- append(result, TrafficModel.new(t, nil, me.layer));
+
+ var tm = TrafficModel.new(t, nil, me.layer);
+
+ if ((min_alt < tm.get_alt()) and
+ (tm.get_alt() < max_alt) and
+ me.map.controller.in_range(lat, lon) ) {
+ append(result, tm);
+ }
}
#print("Found "~size(result)~" TrafficModel's in range out of "~models~" total.");
return result;
};
-
diff --git a/Nasal/notifications.nas b/Nasal/notifications.nas
index 8f56c7a59..813532f0d 100644
--- a/Nasal/notifications.nas
+++ b/Nasal/notifications.nas
@@ -13,7 +13,7 @@
#
# Version : 4.8
#
- # Copyright © 2016 Richard Harrison Released under GPL V2
+ # Copyright � 2016 Richard Harrison Released under GPL V2
#
#---------------------------------------------------------------------------*/
@@ -35,7 +35,7 @@ var PFDEventNotification_Id = 20;
#
# Usage example - this can all go into one Nasal module somewhere.
#-----------
-# var PropertySyncNotification =
+# var PropertySyncNotification =
# {
# new: func(_ident="none", _name="", _kind=0, _secondary_kind=0)
# {
@@ -65,13 +65,13 @@ var PFDEventNotification_Id = 20;
# compared to over 1100 bytes with the traditional method.
#
# The other advantage with this method of transferring data is that the model is in full control of what is
-# sent, and also when it is sent. This works on a per notification basis so less important properties could be
+# sent, and also when it is sent. This works on a per notification basis so less important properties could be
# transmitted on a less frequent schedule; however this will require an instance of the notification for each one.
#
# PropertySyncNotificationBase is a shortcut notification; as it doesn't need to received and all
# of the properties are simply set when the notification is unpacked over MP.
# So although the notification will be transmitted
-var PropertySyncNotificationBase =
+var PropertySyncNotificationBase =
{
new: func(_ident="none", _name="", _kind=0, _secondary_kind=0)
{
@@ -87,29 +87,29 @@ var PropertySyncNotificationBase =
new_class.addIntProperty = func(variable, property, length)
{
me[variable] = nil;
- append(me._bridgeProperties,
+ append(me._bridgeProperties,
{
getValue:func{return emesary.TransferInt.encode(getprop(property) or 0,length);},
- setValue:func(v,bridge,pos){var dv=emesary.TransferInt.decode(v,length,pos);me[variable]=dv.value;setprop(bridge.PropertyRoot~property, me[variable]);return dv;},
+ setValue:func(v,bridge,pos){var dv=emesary.TransferInt.decode(v,length,pos);me[variable]=dv.value;setprop(bridge.PropertyRoot~property, me[variable]);return dv;},
});
}
new_class.addNormProperty = func(variable, property, length)
{
me[variable] = nil;
- append(me._bridgeProperties,
+ append(me._bridgeProperties,
{
getValue:func{return emesary.TransferNorm.encode(getprop(property) or 0,length);},
- setValue:func(v,bridge,pos){var dv=emesary.TransferNorm.decode(v,length,pos);me[variable] = dv.value;setprop(bridge.PropertyRoot~property, me[variable]);return dv;},
+ setValue:func(v,bridge,pos){var dv=emesary.TransferNorm.decode(v,length,pos);me[variable] = dv.value;setprop(bridge.PropertyRoot~property, me[variable]);return dv;},
});
}
new_class.addStringProperty = func(variable, property)
{
me[variable] = nil;
- append(me._bridgeProperties,
+ append(me._bridgeProperties,
{
getValue:func{return emesary.TransferString.encode(getprop(property) or 0);},
- setValue:func(v,bridge,pos){var dv=emesary.TransferString.decode(v,pos);me[variable] = dv.value;setprop(bridge.PropertyRoot~property, me[variable]);return dv;},
+ setValue:func(v,bridge,pos){var dv=emesary.TransferString.decode(v,pos);me[variable] = dv.value;setprop(bridge.PropertyRoot~property, me[variable]);return dv;},
});
}
@@ -121,9 +121,9 @@ var PropertySyncNotificationBase =
}
};
#
-# Transmit a generic control event.
+# Transmit a generic control event.
# two parameters - the event Id and the event value which is a 4 byte length (+/- 1,891371.000)
-var AircraftControlNotification =
+var AircraftControlNotification =
{
new: func(_ident="none")
{
@@ -136,15 +136,15 @@ var AircraftControlNotification =
new_class.bridgeProperties = func
{
- return
- [
+ return
+ [
{
getValue:func{return emesary.TransferInt.encode(new_class.EventType,2);},
- setValue:func(v,bridge,pos){var dv=emesary.TransferInt.decode(v,2,pos);new_class.EventType=dv.value;return dv;},
+ setValue:func(v,bridge,pos){var dv=emesary.TransferInt.decode(v,2,pos);new_class.EventType=dv.value;return dv;},
},
{
getValue:func{return emesary.TransferFixedDouble.encode(new_class.EventValue,4,1000);},
- setValue:func(v,bridge,pos){var dv=emesary.TransferFixedDouble.decode(v,4,1000,pos);new_class.EventValue=dv.value;print("dec ",dv.value);return dv;},
+ setValue:func(v,bridge,pos){var dv=emesary.TransferFixedDouble.decode(v,4,1000,pos);new_class.EventValue=dv.value;print("dec ",dv.value);return dv;},
},
];
};
@@ -154,9 +154,9 @@ var AircraftControlNotification =
#
#
-# Use to transmit events that happen at a specific place; can be used to make
+# Use to transmit events that happen at a specific place; can be used to make
# models that are simulated locally (e.g. tankers) appear on other player's MP sessions.
-var GeoEventNotification =
+var GeoEventNotification =
{
# new:
# _ident - the identifier for the notification. not bridged.
@@ -188,43 +188,43 @@ var GeoEventNotification =
};
new_class.bridgeProperties = func
{
- return
- [
+ return
+ [
{
getValue:func{return emesary.TransferCoord.encode(new_class.Position);},
- setValue:func(v,root,pos){var dv=emesary.TransferCoord.decode(v, pos);new_class.Position=dv.value;return dv},
+ setValue:func(v,root,pos){var dv=emesary.TransferCoord.decode(v, pos);new_class.Position=dv.value;return dv},
},
{
getValue:func{return emesary.TransferString.encode(new_class.Name);},
- setValue:func(v,root,pos){var dv=emesary.TransferString.decode(v,pos);new_class.Name=dv.value;return dv},
+ setValue:func(v,root,pos){var dv=emesary.TransferString.decode(v,pos);new_class.Name=dv.value;return dv},
},
{
getValue:func{return emesary.TransferByte.encode(new_class.Kind);},
- setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.Kind=dv.value;return dv},
+ setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.Kind=dv.value;return dv},
},
{
getValue:func{return emesary.TransferByte.encode(new_class.SecondaryKind);},
- setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.SecondaryKind=dv.value;return dv},
+ setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.SecondaryKind=dv.value;return dv},
},
{
getValue:func{return emesary.TransferFixedDouble.encode(new_class.u_fps,2,10);},
- setValue:func(v,root,pos){var dv=emesary.TransferFixedDouble.decode(v,2,10,pos);new_class.u_fps=dv.value;return dv},
+ setValue:func(v,root,pos){var dv=emesary.TransferFixedDouble.decode(v,2,10,pos);new_class.u_fps=dv.value;return dv},
},
{
getValue:func{return emesary.TransferFixedDouble.encode(new_class.v_fps,2,10);},
- setValue:func(v,root,pos){var dv=emesary.TransferFixedDouble.decode(v,2,10,pos);new_class.v_fps=dv.value;return dv},
+ setValue:func(v,root,pos){var dv=emesary.TransferFixedDouble.decode(v,2,10,pos);new_class.v_fps=dv.value;return dv},
},
{
getValue:func{return emesary.TransferFixedDouble.encode(new_class.w_fps,2,10);},
- setValue:func(v,root,pos){var dv=emesary.TransferFixedDouble.decode(v,2,10,pos);new_class.w_fps=dv.value;return dv},
+ setValue:func(v,root,pos){var dv=emesary.TransferFixedDouble.decode(v,2,10,pos);new_class.w_fps=dv.value;return dv},
},
{
getValue:func{return emesary.TransferString.encode(new_class.RemoteCallsign);},
- setValue:func(v,root,pos){var dv=emesary.TransferString.decode(v,pos);new_class.RemoteCallsign=dv.value;return dv},
+ setValue:func(v,root,pos){var dv=emesary.TransferString.decode(v,pos);new_class.RemoteCallsign=dv.value;return dv},
},
{
getValue:func{return emesary.TransferByte.encode(new_class.Flags);},
- setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.Flags=dv.value;return dv},
+ setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.Flags=dv.value;return dv},
},
];
};
@@ -232,7 +232,7 @@ var GeoEventNotification =
},
};
#
-# Defined kinds:
+# Defined kinds:
# 1 - Created
# 2 - Moved
# 3 - Deleted
@@ -242,8 +242,8 @@ var GeoEventNotification =
# using the first 4 bits as the classification and the second 4 bits as the sub-classification
#-----------
# Type 0000 : Cargo
-# 0 0000 0000 - Vehicle
-# 1 0000 0001 - Person
+# 0 0000 0000 - Vehicle
+# 1 0000 0001 - Person
# 2 0000 0010 - 10 kg Item
# 3 0000 0011 - 20 kg Item
# 4 0000 0100 - 30 kg Item
@@ -256,26 +256,26 @@ var GeoEventNotification =
# 11 0000 1011 - Chaff
# 12 0000 1100 - Flares
# 13 0000 1101 - Water (fire fighting)
-# 14 0000 1110 -
+# 14 0000 1110 -
# 15 0000 1111 - Morris Marina
#--------
# Type 0001 : Self propelled
# 16 0001 0000 - X-2
# 17 0001 0001 - X-15
# 18 0001 0010 - X-24
-# 19 0001 0011 -
-# 20 0001 0100 -
-# 21 0001 0101 -
-# 22 0001 0110 -
-# 23 0001 0111 -
-# 24 0001 1000 -
-# 25 0001 1001 -
-# 26 0001 1010 -
-# 27 0001 1011 -
-# 28 0001 1100 -
-# 29 0001 1101 -
-# 30 0001 1110 -
-# 31 0001 1111 -
+# 19 0001 0011 -
+# 20 0001 0100 -
+# 21 0001 0101 -
+# 22 0001 0110 -
+# 23 0001 0111 -
+# 24 0001 1000 -
+# 25 0001 1001 -
+# 26 0001 1010 -
+# 27 0001 1011 -
+# 28 0001 1100 -
+# 29 0001 1101 -
+# 30 0001 1110 -
+# 31 0001 1111 -
#--------
# Type 0010 : Aircraft Damage (e.g space shuttle re-entry or during launch)
# 32 0010 0000 - Engine 1
@@ -300,36 +300,36 @@ var GeoEventNotification =
# 49 0011 0001 - Drop Tank 2
# 50 0011 0010 - Drop Tank 3
# 51 0011 0011 - Drop Tank 4
-# 52 0011 0100 -
-# 53 0011 0101 -
-# 54 0011 0110 -
-# 55 0011 0111 -
-# 56 0011 1000 -
-# 57 0011 1001 -
-# 58 0011 1010 -
-# 59 0011 1011 -
-# 60 0011 1100 -
-# 61 0011 1101 -
-# 62 0011 1110 -
-# 63 0011 1111 -
+# 52 0011 0100 -
+# 53 0011 0101 -
+# 54 0011 0110 -
+# 55 0011 0111 -
+# 56 0011 1000 -
+# 57 0011 1001 -
+# 58 0011 1010 -
+# 59 0011 1011 -
+# 60 0011 1100 -
+# 61 0011 1101 -
+# 62 0011 1110 -
+# 63 0011 1111 -
#--------
-# Type 0100 :
-# 64 0100 0000 -
-# 65 0100 0001 -
-# 66 0100 0010 -
-# 67 0100 0011 -
-# 68 0100 0100 -
-# 69 0100 0101 -
-# 70 0100 0110 -
-# 71 0100 0111 -
-# 72 0100 1000 -
-# 73 0100 1001 -
-# 74 0100 1010 -
-# 75 0100 1011 -
-# 76 0100 1100 -
-# 77 0100 1101 -
-# 78 0100 1110 -
-# 79 0100 1111 -
+# Type 0100 :
+# 64 0100 0000 -
+# 65 0100 0001 -
+# 66 0100 0010 -
+# 67 0100 0011 -
+# 68 0100 0100 -
+# 69 0100 0101 -
+# 70 0100 0110 -
+# 71 0100 0111 -
+# 72 0100 1000 -
+# 73 0100 1001 -
+# 74 0100 1010 -
+# 75 0100 1011 -
+# 76 0100 1100 -
+# 77 0100 1101 -
+# 78 0100 1110 -
+# 79 0100 1111 -
#--------
# Type 0101 : Models/Geometry items
# 80 0101 0000 - Aim91x.ac
@@ -354,199 +354,202 @@ var GeoEventNotification =
# 97 0110 0001 - rocket.ac
# 98 0110 0010 - tracer.ac
# 99 0110 0011 - tracer2.ac
-# 100 0110 0100 -
-# 101 0110 0101 -
-# 102 0110 0110 -
-# 103 0110 0111 -
-# 104 0110 1000 -
-# 105 0110 1001 -
-# 106 0110 1010 -
-# 107 0110 1011 -
-# 108 0110 1100 -
-# 109 0110 1101 -
-# 110 0110 1110 -
-# 111 0110 1111 -
+# 100 0110 0100 -
+# 101 0110 0101 -
+# 102 0110 0110 -
+# 103 0110 0111 -
+# 104 0110 1000 -
+# 105 0110 1001 -
+# 106 0110 1010 -
+# 107 0110 1011 -
+# 108 0110 1100 -
+# 109 0110 1101 -
+# 110 0110 1110 -
+# 111 0110 1111 -
#--------
# Type 0111 : Models/Geometry items
-# 112 0111 0000 -
-# 113 0111 0001 -
-# 114 0111 0010 -
-# 115 0111 0011 -
-# 116 0111 0100 -
-# 117 0111 0101 -
-# 118 0111 0110 -
-# 119 0111 0111 -
-# 120 0111 1000 -
-# 121 0111 1001 -
-# 122 0111 1010 -
-# 123 0111 1011 -
-# 124 0111 1100 -
-# 125 0111 1101 -
-# 126 0111 1110 -
-# 127 0111 1111 -
+# 112 0111 0000 -
+# 113 0111 0001 -
+# 114 0111 0010 -
+# 115 0111 0011 -
+# 116 0111 0100 -
+# 117 0111 0101 -
+# 118 0111 0110 -
+# 119 0111 0111 -
+# 120 0111 1000 -
+# 121 0111 1001 -
+# 122 0111 1010 -
+# 123 0111 1011 -
+# 124 0111 1100 -
+# 125 0111 1101 -
+# 126 0111 1110 -
+# 127 0111 1111 -
#--------
# Type 1000 : Models/Geometry items
-# 128 1000 0000 -
-# 129 1000 0001 -
-# 130 1000 0010 -
-# 131 1000 0011 -
-# 132 1000 0100 -
-# 133 1000 0101 -
-# 134 1000 0110 -
-# 135 1000 0111 -
-# 136 1000 1000 -
-# 137 1000 1001 -
-# 138 1000 1010 -
-# 139 1000 1011 -
-# 140 1000 1100 -
-# 141 1000 1101 -
-# 142 1000 1110 -
-# 143 1000 1111 -
+# 128 1000 0000 -
+# 129 1000 0001 -
+# 130 1000 0010 -
+# 131 1000 0011 -
+# 132 1000 0100 -
+# 133 1000 0101 -
+# 134 1000 0110 -
+# 135 1000 0111 -
+# 136 1000 1000 -
+# 137 1000 1001 -
+# 138 1000 1010 -
+# 139 1000 1011 -
+# 140 1000 1100 -
+# 141 1000 1101 -
+# 142 1000 1110 -
+# 143 1000 1111 -
#--------
-# Type 1001 :
-# 144 1001 0000 -
-# 145 1001 0001 -
-# 146 1001 0010 -
-# 147 1001 0011 -
-# 148 1001 0100 -
-# 149 1001 0101 -
-# 150 1001 0110 -
-# 151 1001 0111 -
-# 152 1001 1000 -
-# 153 1001 1001 -
-# 154 1001 1010 -
-# 155 1001 1011 -
-# 156 1001 1100 -
-# 157 1001 1101 -
-# 158 1001 1110 -
-# 159 1001 1111 -
+# Type 1001 :
+# 144 1001 0000 -
+# 145 1001 0001 -
+# 146 1001 0010 -
+# 147 1001 0011 -
+# 148 1001 0100 -
+# 149 1001 0101 -
+# 150 1001 0110 -
+# 151 1001 0111 -
+# 152 1001 1000 -
+# 153 1001 1001 -
+# 154 1001 1010 -
+# 155 1001 1011 -
+# 156 1001 1100 -
+# 157 1001 1101 -
+# 158 1001 1110 -
+# 159 1001 1111 -
#--------
-# Type 1010 :
-# 160 1010 0000 -
-# 161 1010 0001 -
-# 162 1010 0010 -
-# 163 1010 0011 -
-# 164 1010 0100 -
-# 165 1010 0101 -
-# 166 1010 0110 -
-# 167 1010 0111 -
-# 168 1010 1000 -
-# 169 1010 1001 -
-# 170 1010 1010 -
-# 171 1010 1011 -
-# 172 1010 1100 -
-# 173 1010 1101 -
-# 174 1010 1110 -
-# 175 1010 1111 -
+# Type 1010 :
+# 160 1010 0000 -
+# 161 1010 0001 -
+# 162 1010 0010 -
+# 163 1010 0011 -
+# 164 1010 0100 -
+# 165 1010 0101 -
+# 166 1010 0110 -
+# 167 1010 0111 -
+# 168 1010 1000 -
+# 169 1010 1001 -
+# 170 1010 1010 -
+# 171 1010 1011 -
+# 172 1010 1100 -
+# 173 1010 1101 -
+# 174 1010 1110 -
+# 175 1010 1111 -
#--------
-# Type 1011 :
-# 176 1011 0000 -
-# 177 1011 0001 -
-# 178 1011 0010 -
-# 179 1011 0011 -
-# 180 1011 0100 -
-# 181 1011 0101 -
-# 182 1011 0110 -
-# 183 1011 0111 -
-# 184 1011 1000 -
-# 185 1011 1001 -
-# 186 1011 1010 -
-# 187 1011 1011 -
-# 188 1011 1100 -
-# 189 1011 1101 -
-# 190 1011 1110 -
-# 191 1011 1111 -
+# Type 1011 :
+# 176 1011 0000 -
+# 177 1011 0001 -
+# 178 1011 0010 -
+# 179 1011 0011 -
+# 180 1011 0100 -
+# 181 1011 0101 -
+# 182 1011 0110 -
+# 183 1011 0111 -
+# 184 1011 1000 -
+# 185 1011 1001 -
+# 186 1011 1010 -
+# 187 1011 1011 -
+# 188 1011 1100 -
+# 189 1011 1101 -
+# 190 1011 1110 -
+# 191 1011 1111 -
#--------
-# Type 1100 :
-# 192 1100 0000 -
-# 193 1100 0001 -
-# 194 1100 0010 -
-# 195 1100 0011 -
-# 196 1100 0100 -
-# 197 1100 0101 -
-# 198 1100 0110 -
-# 199 1100 0111 -
-# 200 1100 1000 -
-# 201 1100 1001 -
-# 202 1100 1010 -
-# 203 1100 1011 -
-# 204 1100 1100 -
-# 205 1100 1101 -
-# 206 1100 1110 -
-# 207 1100 1111 -
+# Type 1100 :
+# 192 1100 0000 -
+# 193 1100 0001 -
+# 194 1100 0010 -
+# 195 1100 0011 -
+# 196 1100 0100 -
+# 197 1100 0101 -
+# 198 1100 0110 -
+# 199 1100 0111 -
+# 200 1100 1000 -
+# 201 1100 1001 -
+# 202 1100 1010 -
+# 203 1100 1011 -
+# 204 1100 1100 -
+# 205 1100 1101 -
+# 206 1100 1110 -
+# 207 1100 1111 -
#--------
-# Type 1101 :
-# 208 1101 0000 -
-# 209 1101 0001 -
-# 210 1101 0010 -
-# 211 1101 0011 -
-# 212 1101 0100 -
-# 213 1101 0101 -
-# 214 1101 0110 -
-# 215 1101 0111 -
-# 216 1101 1000 -
-# 217 1101 1001 -
-# 218 1101 1010 -
-# 219 1101 1011 -
-# 220 1101 1100 -
-# 221 1101 1101 -
-# 222 1101 1110 -
-# 223 1101 1111 -
+# Type 1101 :
+# 208 1101 0000 -
+# 209 1101 0001 -
+# 210 1101 0010 -
+# 211 1101 0011 -
+# 212 1101 0100 -
+# 213 1101 0101 -
+# 214 1101 0110 -
+# 215 1101 0111 -
+# 216 1101 1000 -
+# 217 1101 1001 -
+# 218 1101 1010 -
+# 219 1101 1011 -
+# 220 1101 1100 -
+# 221 1101 1101 -
+# 222 1101 1110 -
+# 223 1101 1111 -
#--------
-# Type 1110 :
-# 224 1110 0000 -
-# 225 1110 0001 -
-# 226 1110 0010 -
-# 227 1110 0011 -
-# 228 1110 0100 -
-# 229 1110 0101 -
-# 230 1110 0110 -
-# 231 1110 0111 -
-# 232 1110 1000 -
-# 233 1110 1001 -
-# 234 1110 1010 -
-# 235 1110 1011 -
-# 236 1110 1100 -
-# 237 1110 1101 -
-# 238 1110 1110 -
-# 239 1110 1111 -
+# Type 1110 :
+# 224 1110 0000 -
+# 225 1110 0001 -
+# 226 1110 0010 -
+# 227 1110 0011 -
+# 228 1110 0100 -
+# 229 1110 0101 -
+# 230 1110 0110 -
+# 231 1110 0111 -
+# 232 1110 1000 -
+# 233 1110 1001 -
+# 234 1110 1010 -
+# 235 1110 1011 -
+# 236 1110 1100 -
+# 237 1110 1101 -
+# 238 1110 1110 -
+# 239 1110 1111 -
#--------
-# Type 1111 :
-# 240 1111 0000 -
-# 241 1111 0001 -
-# 242 1111 0010 -
-# 243 1111 0011 -
-# 244 1111 0100 -
-# 245 1111 0101 -
-# 246 1111 0110 -
-# 247 1111 0111 -
-# 248 1111 1000 -
-# 249 1111 1001 -
-# 250 1111 1010 -
-# 251 1111 1011 -
-# 252 1111 1100 -
-# 253 1111 1101 -
-# 254 1111 1110 -
-# 255 1111 1111 -
+# Type 1111 :
+# 240 1111 0000 -
+# 241 1111 0001 -
+# 242 1111 0010 -
+# 243 1111 0011 -
+# 244 1111 0100 -
+# 245 1111 0101 -
+# 246 1111 0110 -
+# 247 1111 0111 -
+# 248 1111 1000 -
+# 249 1111 1001 -
+# 250 1111 1010 -
+# 251 1111 1011 -
+# 252 1111 1100 -
+# 253 1111 1101 -
+# 254 1111 1110 -
+# 255 1111 1111 -
#
#
-# Use to transmit events that happen at a specific place; can be used to make
+# Use to transmit events that happen at a specific place; can be used to make
# models that are simulated locally (e.g. tankers) appear on other player's MP sessions.
-var PFDEventNotification =
+var PFDEventNotification =
{
# new:
# _ident - the identifier for the notification. not bridged.
# _pfd_id - numeric identification of the PFD within the model
-# _event_id - event ID.
+# _event_id - event ID.
# 1 softkey pushed.
# 2 select page by ID
+# 3 Change softkey button text
+# 4 hardkey pushed - i.e. non-soft keys that don't change function based on context.
# _event_param - param related to the event ID. implementation specific.
##
SoftKeyPushed : 1,
SelectPageById : 2,
- ChangeMenuText : 3, #event parameter contains hash of { Id: , Text: }
+ ChangeMenuText : 3, #event parameter contains array of { Id: , Text: } tuples
+ HardKeyPushed : 4, #event parameter contains single { Id: , Value: } tuple
DefaultType : "PFDEventNotification",
new: func(_ident, _device_id,_event_id,_event_parameter_id)
@@ -560,15 +563,15 @@ var PFDEventNotification =
new_class.bridgeProperties = func
{
- return
- [
+ return
+ [
{
getValue:func{return emesary.TransferByte.encode(new_class.Event_Id);},
- setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.Event_Id=dv.value;return dv},
+ setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.Event_Id=dv.value;return dv},
},
{
getValue:func{return emesary.TransferByte.encode(new_class.EventParameter);},
- setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.EventParameter=dv.value;return dv},
+ setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.EventParameter=dv.value;return dv},
},
];
};
diff --git a/Translations/default/menu.xml b/Translations/default/menu.xml
index 1ca2a4be7..8fdd8ec29 100644
--- a/Translations/default/menu.xml
+++ b/Translations/default/menu.xml
@@ -110,6 +110,7 @@
Monitor System Performance
Save Video Configuration
Browse Internal Properties
+ FG1000 MFD
Logging
Local Weather (Test)
Print Visible Scene Info
diff --git a/gui/dialogs/fg1000.xml b/gui/dialogs/fg1000.xml
new file mode 100644
index 000000000..41684afd8
--- /dev/null
+++ b/gui/dialogs/fg1000.xml
@@ -0,0 +1,892 @@
+
+
+
+ fg1000
+ vbox
+ true
+ 3
+
+
+ 1.0
+
+
+
+
+
+
+
+
+
+ hbox
+ 1
+ 1
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+ hbox
+ true
+ fill
+
+
+
+ vbox
+ center
+ top
+
+
+ vol
+
+ 30
+ /gui/dialogs/fg1000/nav-vol
+
+
+
+
+
+
+
+ hbox
+ center
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hdg
+ 20
+ /gui/dialogs/fg1000/hdg
+
+
+
+
+
+
+ table
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hbox
+ center
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ vbox
+ true
+
+
+
+ hbox
+ true
+ fill
+ fill
+
+
+
+
+ hbox
+ true
+ fill
+ top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ vbox
+ center
+ top
+
+
+ com-vol
+
+ 30
+ /gui/dialogs/fg1000/com-vol
+
+
+
+
+
+
+
+ hbox
+ center
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ baro
+ 20
+ /gui/dialogs/fg1000/baro
+
+
+
+
+
+
+
+ crs
+ 20
+ /gui/dialogs/fg1000/crs
+ dialog-apply
+
+
+
+
+
+
+
+
+
+
+ hbox
+ center
+ 0
+
+
+
+
+
+
+ table
+ center
+ 0
+
+
+
+
+
+
+
+
+
+ table
+ center
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hbox
+ center
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gui/menubar.xml b/gui/menubar.xml
index 5f0368a32..56283a292 100644
--- a/gui/menubar.xml
+++ b/gui/menubar.xml
@@ -342,21 +342,21 @@
+ -
+ fg1000
+
+ dialog-show
+ fg1000
+
+
+
-
logging