d7a9b36c08
- Engines control now use the same axis methods as the flight controls; which allows for these to have an inverted checkbox. - Change the axis combo boxes to instead show a popup dialog (much like the button config). This is better because the items can be grouped sensibly. - Changed the way that the "all engines" controls work - the dialog binds these to a "engines/XXXX-all" for which there is a listener in controls.nas which propogates over all engines. - Added extra bindings to both the buttons and the axis dialogs. - moved the "None" button to the bottom and renamed it to the more comprehensible "Remove assignment"
778 lines
27 KiB
Text
778 lines
27 KiB
Text
# Joystick configuration library.
|
|
var DIALOGROOT = "/sim/gui/dialogs/joystick-config";
|
|
var MAX_AXES = 8;
|
|
var MAX_NASALS = 8;
|
|
var MAX_BUTTONS = 24;
|
|
|
|
# Hash of the custom axis/buttons
|
|
var custom_bindings = {};
|
|
|
|
# Class for an individual joystick axis binding
|
|
var Axis = {
|
|
new: func(name, prop, invertable) {
|
|
var m = { parents: [Axis] };
|
|
m.name = name;
|
|
m.prop = prop;
|
|
m.invertable = invertable;
|
|
m.inverted = 0;
|
|
return m;
|
|
},
|
|
|
|
clone: func() {
|
|
var m = { parents: [Axis] };
|
|
m.name = me.name;
|
|
m.prop = me.prop;
|
|
m.invertable = me.invertable;
|
|
m.inverted = me.inverted;
|
|
return m;
|
|
},
|
|
|
|
match: func(prop) {
|
|
return 0;
|
|
},
|
|
|
|
parse: func(prop) { },
|
|
|
|
readProps: func(props) {},
|
|
|
|
getName: func() { return me.name; },
|
|
getBinding: func(axis) { return props.Node.new(); },
|
|
isInvertable: func() { return me.invertable; },
|
|
isInverted: func() { return me.inverted; },
|
|
|
|
setInverted: func(b) {
|
|
if (me.invertable) me.inverted = b;
|
|
},
|
|
};
|
|
|
|
var CustomAxis = {
|
|
new: func() {
|
|
var m = { parents: [CustomAxis, Axis.new("Custom", "", 0) ] };
|
|
me.custom_binding = nil;
|
|
return m;
|
|
},
|
|
|
|
clone: func() {
|
|
var m = { parents: [CustomAxis, Axis.new("Custom", "", 0) ] };
|
|
m.custom_binding = me.custom_binding;
|
|
return m;
|
|
},
|
|
|
|
match: func(prop) {
|
|
var p = props.Node.new();
|
|
|
|
if (prop.getNode("binding") != nil) {
|
|
props.copy(prop.getNode("binding"), p);
|
|
me.custom_binding = p;
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
},
|
|
|
|
getBinding: func(axis) {
|
|
var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
|
|
p.getNode("desc", 1).setValue(me.name);
|
|
props.copy(me.custom_binding, p.getNode("binding", 1));
|
|
return p;
|
|
},
|
|
};
|
|
|
|
var UnboundAxis = {
|
|
new: func() {
|
|
var m = { parents: [UnboundAxis, Axis.new("None", "", 0) ] };
|
|
return m;
|
|
},
|
|
|
|
clone: func() {
|
|
var m = { parents: [UnboundAxis, Axis.new("None", "", 0) ] };
|
|
return m;
|
|
},
|
|
|
|
match: func(prop) {
|
|
return 1;
|
|
},
|
|
|
|
getBinding: func(axis) {
|
|
return nil;
|
|
},
|
|
};
|
|
|
|
|
|
var PropertyScaleAxis = {
|
|
new: func(name, prop, factor=1, offset=0) {
|
|
var m = { parents: [PropertyScaleAxis, Axis.new(name, prop, 1) ] };
|
|
m.prop=prop;
|
|
m.factor = factor;
|
|
m.offset = offset;
|
|
return m;
|
|
},
|
|
|
|
clone: func() {
|
|
var m = { parents: [PropertyScaleAxis, Axis.new(me.name, me.prop, 1) ] };
|
|
|
|
m.inverted = me.inverted;
|
|
|
|
m.prop= me.prop;
|
|
m.factor = me.factor;
|
|
m.offset = me.offset;
|
|
return m;
|
|
},
|
|
|
|
|
|
match: func(prop) {
|
|
var cmd = prop.getNode("binding", 1).getNode("command", 1).getValue();
|
|
var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
|
|
return ((cmd == "property-scale") and (p == me.prop));
|
|
},
|
|
|
|
parse: func(p) {
|
|
bindingNode = p.getNode("binding", 1);
|
|
me.prop = bindingNode.getNode("property", 1).getValue();
|
|
|
|
# Don't create an empty 'factor' node if it doesn't exist!
|
|
# (value 0 wouldn't be appropriate for a default factor)
|
|
factorNode = bindingNode.getNode("factor", 0);
|
|
me.factor = (factorNode != nil) ? factorNode.getValue() : 1.0;
|
|
|
|
me.inverted = (me.factor < 0);
|
|
me.offset = bindingNode.getNode("offset", 1).getValue();
|
|
},
|
|
|
|
getBinding: func(axis) {
|
|
var p = props.Node.new();
|
|
p = p.getNode("axis[" ~ axis ~ "]", 1);
|
|
p.getNode("desc", 1).setValue(me.name);
|
|
p.getNode("binding", 1).getNode("command", 1).setValue("property-scale");
|
|
p.getNode("binding", 1).getNode("property", 1).setValue(me.prop);
|
|
if (me.inverted) {
|
|
p.getNode("binding", 1).getNode("factor", 1).setValue(0 - me.factor);
|
|
} else {
|
|
p.getNode("binding", 1).getNode("factor", 1).setValue(me.factor);
|
|
}
|
|
p.getNode("binding", 1).getNode("offset", 1).setValue(me.offset);
|
|
return p;
|
|
},
|
|
|
|
};
|
|
|
|
var NasalScaleAxis = {
|
|
new: func(name, script, prop) {
|
|
var m = { parents: [NasalScaleAxis, Axis.new(name, prop, 0) ] };
|
|
m.script = script;
|
|
m.prop = prop;
|
|
return m;
|
|
},
|
|
|
|
clone: func() {
|
|
var m = { parents: [NasalScaleAxis, Axis.new(me.name, me.prop, 0) ] };
|
|
|
|
m.script = me.script;
|
|
m.prop = me.prop;
|
|
return m;
|
|
},
|
|
|
|
match: func(prop) {
|
|
var cmd = prop.getNode("binding", 1).getNode("command", 1).getValue();
|
|
var p = prop.getNode("binding", 1).getNode("script", 1).getValue();
|
|
if ((p != nil) and (cmd == "nasal")) {
|
|
p = string.trim(p);
|
|
p = string.replace(p, ";", "");
|
|
p = p ~ ";";
|
|
return (p == me.script);
|
|
} else {
|
|
return 0;
|
|
}
|
|
},
|
|
|
|
getBinding: func(axis) {
|
|
var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
|
|
p.getNode("desc", 1).setValue(me.name);
|
|
p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
|
|
p.getNode("binding", 1).getNode("script", 1).setValue(me.script);
|
|
return p;
|
|
},
|
|
};
|
|
|
|
var NasalLowHighAxis = {
|
|
new: func(name, lowscript, highscript, prop) {
|
|
var m = { parents: [NasalLowHighAxis, Axis.new(name, prop, 1) ] };
|
|
m.lowscript = lowscript;
|
|
m.highscript = highscript;
|
|
return m;
|
|
},
|
|
|
|
clone: func() {
|
|
var m = { parents: [NasalLowHighAxis, Axis.new(me.name, me.prop, 1) ] };
|
|
|
|
m.inverted = me.inverted;
|
|
|
|
m.lowscript = me.lowscript;
|
|
m.highscript = me.highscript;
|
|
return m;
|
|
},
|
|
|
|
match: func(prop) {
|
|
var cmd = prop.getNode("low", 1).getNode("binding", 1).getNode("command", 1).getValue();
|
|
var p = prop.getNode("low", 1).getNode("binding", 1).getNode("script", 1).getValue();
|
|
|
|
if ((p == nil) or (cmd != "nasal")) return 0;
|
|
p = string.trim(p);
|
|
p = string.replace(p, ";", "");
|
|
p = p ~ ";";
|
|
|
|
if (p == me.lowscript) {
|
|
return 1;
|
|
}
|
|
|
|
if (p == me.highscript) {
|
|
me.inverted = 1;
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
},
|
|
|
|
getBinding: func(axis) {
|
|
var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
|
|
p.getNode("desc", 1).setValue(me.name);
|
|
|
|
p.getNode("low", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
|
|
p.getNode("high", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
|
|
|
|
if (me.inverted) {
|
|
p.getNode("low", 1).getNode("binding", 1).getNode("script", 1).setValue(me.highscript);
|
|
p.getNode("high", 1).getNode("binding", 1).getNode("script", 1).setValue(me.lowscript);
|
|
} else {
|
|
p.getNode("low", 1).getNode("binding", 1).getNode("script", 1).setValue(me.lowscript);
|
|
p.getNode("high", 1).getNode("binding", 1).getNode("script", 1).setValue(me.highscript);
|
|
}
|
|
|
|
return p;
|
|
},
|
|
};
|
|
|
|
|
|
var axisBindings = [
|
|
PropertyScaleAxis.new("Aileron", "/controls/flight/aileron"),
|
|
PropertyScaleAxis.new("Elevator", "/controls/flight/elevator"),
|
|
PropertyScaleAxis.new("Rudder", "/controls/flight/rudder"),
|
|
NasalScaleAxis.new("Throttle (all)", "controls.throttleAxis();", "/controls/engines/engine[0]/throttle") ,
|
|
NasalScaleAxis.new("Mixture (all)", "controls.mixtureAxis();", "/controls/engines/engine[0]/mixture") ,
|
|
NasalScaleAxis.new("Propeller (all)", "controls.propellerAxis();", "/controls/engines/engine[0]/propeller-pitch") ,
|
|
|
|
# Controls of individual throttles, mixtures, propellers
|
|
NasalScaleAxis.new("Throttle 1", "controls.perEngineSelectedAxisHandler(0)([0]);", "/controls/engines/engine[0]/throttle") ,
|
|
NasalScaleAxis.new("Mixture 1", "controls.perEngineSelectedAxisHandler(1)([0]);", "/controls/engines/engine[0]/mixture") ,
|
|
NasalScaleAxis.new("Propeller 1", "controls.perEngineSelectedAxisHandler(2)([0]);", "/controls/engines/engine[0]/propeller-pitch") ,
|
|
NasalScaleAxis.new("Throttle 2", "controls.perEngineSelectedAxisHandler(0)([1]);", "/controls/engines/engine[1]/throttle") ,
|
|
NasalScaleAxis.new("Mixture 2", "controls.perEngineSelectedAxisHandler(1)([1]);", "/controls/engines/engine[1]/mixture") ,
|
|
NasalScaleAxis.new("Propeller 2", "controls.perEngineSelectedAxisHandler(2)([1]);", "/controls/engines/engine[1]/propeller-pitch") ,
|
|
|
|
# 2018.2 (RJH); change engine bindings to use the scaled axis configuration; the -all property
|
|
# that is used has a listener in controls.nas that will propogate the value to all configured engines.
|
|
# - mainly to allow the use of the "invert" option.
|
|
PropertyScaleAxis.new("Throttle All Engines", "/controls/engines/throttle-all") ,
|
|
PropertyScaleAxis.new("Mixture All Engines", "/controls/engines/mixture-all") ,
|
|
PropertyScaleAxis.new("Propeller All Engines", "/controls/engines/propeller-pitch-all") ,
|
|
PropertyScaleAxis.new("Throttle Engine 0", "/controls/engines/engine[0]/throttle") ,
|
|
PropertyScaleAxis.new("Mixture Engine 0", "/controls/engines/engine[0]/mixture") ,
|
|
PropertyScaleAxis.new("Propeller Pitch Engine 0", "/controls/engines/engine[0]/propeller-pitch") ,
|
|
PropertyScaleAxis.new("Throttle Engine 1", "/controls/engines/engine[1]/throttle") ,
|
|
PropertyScaleAxis.new("Mixture Engine 1", "/controls/engines/engine[1]/mixture") ,
|
|
PropertyScaleAxis.new("Propeller Pitch Engine 1", "/controls/engines/engine[1]/propeller-pitch"),
|
|
|
|
NasalLowHighAxis.new("View (horizontal)",
|
|
"setprop(\"/sim/current-view/goal-heading-offset-deg\", getprop(\"/sim/current-view/goal-heading-offset-deg\") + 30);",
|
|
"setprop(\"/sim/current-view/goal-heading-offset-deg\", getprop(\"/sim/current-view/goal-heading-offset-deg\") - 30);",
|
|
"/sim/current-view/goal-heading-offset-deg"),
|
|
NasalLowHighAxis.new("View (vertical)",
|
|
"setprop(\"/sim/current-view/goal-pitch-offset-deg\", getprop(\"/sim/current-view/goal-pitch-offset-deg\") - 20);",
|
|
"setprop(\"/sim/current-view/goal-pitch-offset-deg\", getprop(\"/sim/current-view/goal-pitch-offset-deg\") + 20);",
|
|
"/sim/current-view/goal-heading-offset-deg"),
|
|
PropertyScaleAxis.new("Aileron Trim", "/controls/flight/aileron-trim"),
|
|
PropertyScaleAxis.new("Elevator Trim", "/controls/flight/elevator-trim"),
|
|
PropertyScaleAxis.new("Rudder Trim", "/controls/flight/rudder-trim"),
|
|
PropertyScaleAxis.new("Brake Left", "/controls/gear/brake-left", 0.5, 1.0),
|
|
PropertyScaleAxis.new("Brake Right", "/controls/gear/brake-right", 0.5, 1.0),
|
|
NasalLowHighAxis.new("Aileron Trim Incremental", "controls.aileronTrim(-1);", "controls.aileronTrim(1);", "/controls/flight/aileron-trim-delta"),
|
|
NasalLowHighAxis.new("Elevator Trim Incremental", "controls.elevatorTrim(-1);", "controls.elevatorTrim(1);", "/controls/flight/elevator-trim-delta"),
|
|
NasalLowHighAxis.new("Rudder Trim Incremental", "controls.rudderTrim(-1);", "controls.rudderTrim(1);", "/controls/flight/rudder-trim-delta"),
|
|
|
|
PropertyScaleAxis.new("View Horizontal Axis", "/sim/current-view/goal-heading-offset-deg", 180, 0),
|
|
PropertyScaleAxis.new("View Vertical Axis", "/sim/current-view/goal-pitch-offset-deg", 180, 0),
|
|
CustomAxis.new(),
|
|
UnboundAxis.new(),
|
|
];
|
|
|
|
# Button bindings
|
|
var ButtonBinding = {
|
|
new: func(name, binding, repeatable) {
|
|
var m = { parents: [ButtonBinding] };
|
|
m.name = name;
|
|
m.binding = binding;
|
|
m.repeatable = repeatable;
|
|
return m;
|
|
},
|
|
|
|
clone: func() {
|
|
var m = { parents: [ButtonBinding] };
|
|
m.name = me.name;
|
|
m.binding= me.binding;
|
|
m.repeatable = me.repeatable;
|
|
return m;
|
|
},
|
|
|
|
match: func(prop) {
|
|
return 0;
|
|
},
|
|
|
|
getName: func() { return me.name; },
|
|
getBinding: func(button) { return nil; },
|
|
isRepeatable: func() { return me.repeatable; }
|
|
};
|
|
|
|
var CustomButton = {
|
|
new: func() {
|
|
var m = { parents: [CustomButton, ButtonBinding.new("Custom", "", 0) ] };
|
|
m.custom_binding = nil;
|
|
return m;
|
|
},
|
|
|
|
clone: func() {
|
|
var m = { parents: [CustomButton, ButtonBinding.new("Custom", "", 0) ] };
|
|
m.custom_binding = me.custom_binding;
|
|
return m;
|
|
},
|
|
|
|
match: func(prop) {
|
|
if (prop.getNode("binding") != nil) {
|
|
var p = props.Node.new();
|
|
props.copy(prop.getNode("binding"), p);
|
|
me.custom_binding = p;
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
},
|
|
|
|
getBinding: func(button) {
|
|
var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
|
|
p.getNode("desc", 1).setValue(me.name);
|
|
props.copy(me.custom_binding, p.getNode("binding", 1));
|
|
return p;
|
|
},
|
|
};
|
|
|
|
var UnboundButton = {
|
|
new: func() {
|
|
var m = { parents: [UnboundButton, ButtonBinding.new("None", "", 0) ] };
|
|
return m;
|
|
},
|
|
|
|
clone: func() {
|
|
var m = { parents: [UnboundButton, ButtonBinding.new("None", "", 0) ] };
|
|
return m;
|
|
},
|
|
|
|
match: func(prop) {
|
|
return (prop.getNode("binding") != nil);
|
|
},
|
|
|
|
getBinding: func(button) {
|
|
var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
|
|
p.getNode("desc", 1).setValue(me.name);
|
|
return p;
|
|
},
|
|
};
|
|
|
|
|
|
var PropertyToggleButton = {
|
|
new: func(name, prop) {
|
|
var m = { parents: [PropertyToggleButton, ButtonBinding.new(name, prop, 0) ] };
|
|
return m;
|
|
},
|
|
|
|
clone: func() {
|
|
var m = { parents: [PropertyToggleButton, ButtonBinding.new(me.name, me.binding, 0) ] };
|
|
return m;
|
|
},
|
|
|
|
match: func(prop) {
|
|
|
|
var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
|
|
var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
|
|
return ((c == "property-toggle") and (p == me.prop));
|
|
},
|
|
|
|
getBinding: func(button) {
|
|
var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
|
|
p.getNode("desc", 1).setValue(me.name);
|
|
p.getNode("binding", 1).getNode("command", 1).setValue("property-toggle");
|
|
p.getNode("binding", 1).getNode("property", 1).setValue(me.binding);
|
|
return p;
|
|
},
|
|
};
|
|
|
|
var PropertyAdjustButton = {
|
|
new: func(name, prop, step) {
|
|
var m = { parents: [PropertyAdjustButton, ButtonBinding.new(name, prop, 0) ] };
|
|
m.step = step;
|
|
return m;
|
|
},
|
|
|
|
clone: func() {
|
|
var m = { parents: [PropertyAdjustButton, ButtonBinding.new(me.name, me.binding, 0) ] };
|
|
m.step = me.step;
|
|
return m;
|
|
},
|
|
|
|
match: func(prop) {
|
|
var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
|
|
var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
|
|
var s = prop.getNode("binding", 1).getNode("step", 1).getValue();
|
|
return ((c == "property-adjust") and (p == me.binding) and (s == me.step));
|
|
},
|
|
|
|
getBinding: func(button) {
|
|
var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
|
|
p.getNode("desc", 1).setValue(me.name);
|
|
p.getNode("binding", 1).getNode("command", 1).setValue("property-adjust");
|
|
p.getNode("binding", 1).getNode("property", 1).setValue(me.binding);
|
|
p.getNode("binding", 1).getNode("step", 1).setValue(me.step);
|
|
return p;
|
|
},
|
|
};
|
|
|
|
var NasalButton = {
|
|
new: func(name, script, repeatable) {
|
|
var m = { parents: [NasalButton, ButtonBinding.new(name, script, repeatable) ] };
|
|
return m;
|
|
},
|
|
|
|
clone: func() {
|
|
var m = { parents: [NasalButton, ButtonBinding.new(me.name, me.binding, me.repeatable) ] };
|
|
return m;
|
|
},
|
|
|
|
match: func(prop) {
|
|
var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
|
|
var p = prop.getNode("binding", 1).getNode("script", 1).getValue();
|
|
|
|
if (p == nil) { return 0; }
|
|
|
|
p = string.trim(p);
|
|
p = string.replace(p, ";", "");
|
|
p = p ~ ";";
|
|
|
|
return ((c == "nasal") and (p == me.binding));
|
|
},
|
|
|
|
getBinding: func(button) {
|
|
var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
|
|
p.getNode("desc", 1).setValue(me.name);
|
|
p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
|
|
p.getNode("binding", 1).getNode("script", 1).setValue(me.binding);
|
|
p.getNode("repeatable", 1).setValue(me.repeatable);
|
|
return p;
|
|
},
|
|
};
|
|
|
|
|
|
var NasalHoldButton = {
|
|
new: func(name, script, scriptUp) {
|
|
var m = { parents: [NasalHoldButton, ButtonBinding.new(name, script, 0) ] };
|
|
m.scriptUp = scriptUp;
|
|
return m;
|
|
},
|
|
|
|
clone: func() {
|
|
var m = { parents: [NasalHoldButton, ButtonBinding.new(me.name, me.binding, 0) ] };
|
|
m.scriptUp = me.scriptUp;
|
|
return m;
|
|
},
|
|
|
|
match: func(prop) {
|
|
|
|
var c = prop.getNode("mod-up", 1).getNode("binding", 1).getNode("command", 1).getValue();
|
|
var p1 = prop.getNode("binding", 1).getNode("script", 1).getValue();
|
|
var p2 = prop.getNode("mod-up", 1).getNode("binding", 1).getNode("script", 1).getValue();
|
|
|
|
if (p2 == nil) { return 0; }
|
|
|
|
p1 = string.trim(p1);
|
|
p1 = string.replace(p1, ";", "");
|
|
p1 = p1 ~ ";";
|
|
|
|
return ((c == "nasal") and (p1 == me.binding));
|
|
},
|
|
|
|
getBinding: func(button) {
|
|
var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
|
|
p.getNode("desc", 1).setValue(me.name);
|
|
p.getNode("repeatable", 1).setValue("false");
|
|
p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
|
|
p.getNode("binding", 1).getNode("script", 1).setValue(me.binding);
|
|
p.getNode("mod-up", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
|
|
p.getNode("mod-up", 1).getNode("binding", 1).getNode("script", 1).setValue(me.scriptUp);
|
|
return p;
|
|
},
|
|
};
|
|
|
|
var buttonBindings = [
|
|
NasalButton.new("Throttle Up", "controls.incThrottle(0.01, 1.0);", 1),
|
|
NasalButton.new("Throttle Down", "controls.incThrottle(-0.01, -1.0);", 1),
|
|
NasalButton.new("Mixture Rich", "controls.adjMixture(1);", 1),
|
|
NasalButton.new("Mixture Lean", "controls.adjMixture(-1);", 1),
|
|
NasalButton.new("Propeller Fine", "controls.adjPropeller(1);", 1),
|
|
NasalButton.new("Propeller Coarse", "controls.adjPropeller(-1);", 1),
|
|
|
|
NasalButton.new("Elevator Trim Up", "controls.elevatorTrim(-1);", 1),
|
|
NasalButton.new("Elevator Trim Down", "controls.elevatorTrim(1);", 1),
|
|
NasalButton.new("Rudder Trim Left", "controls.rudderTrim(-1);", 1),
|
|
NasalButton.new("Rudder Trim Right", "controls.rudderTrim(1);", 1),
|
|
NasalButton.new("Aileron Trim Left", "controls.aileronTrim(-1);", 1),
|
|
NasalButton.new("Aileron Trim Right", "controls.aileronTrim(1);", 1),
|
|
NasalHoldButton.new("FGCom PTT", "controls.ptt(1);", "controls.ptt(0);"),
|
|
NasalHoldButton.new("Trigger", "controls.trigger(1);", "controls.trigger(0);"),
|
|
NasalButton.new("Flaps Up", "controls.flapsDown(-1);",0),
|
|
NasalButton.new("Flaps Down", "controls.flapsDown(1);",0),
|
|
NasalButton.new("Gear Up", "controls.gearDown(-1);",0),
|
|
NasalButton.new("Gear Down", "controls.gearDown(1);",0),
|
|
NasalButton.new("Gear Toggle", "controls.gearTogglePosition(1);",0),
|
|
NasalHoldButton.new("Spoilers Retract", "controls.stepSpoilers(-1);", "controls.stepSpoilers(0);"),
|
|
NasalHoldButton.new("Spoilers Deploy", "controls.stepSpoilers(1);", "controls.stepSpoilers(0);"),
|
|
NasalHoldButton.new("Brakes", "controls.applyBrakes(1);", "controls.applyBrakes(0);"),
|
|
NasalHoldButton.new("Brakes (air/wheel)", "controls.applyApplicableBrakes(1);", "controls.applyApplicableBrakes(0);"),
|
|
NasalHoldButton.new("Parking brakes", "controls.parkingBrakeToggle(0);", "controls.parkingBrakeToggle(1);"),
|
|
NasalHoldButton.new("NWS toggle", "controls.toggleNWS(0);", "controls.toggleNWS(1);"),
|
|
|
|
# with all of these it is expected the the armament system in the selected aircraft
|
|
# will manage the wrap around and or reset to zero.
|
|
PropertyAdjustButton.new("Pickle", "/controls/armament/pickle-target", "1"),
|
|
PropertyAdjustButton.new("Target next", "/controls/armament/target-selected", "1"),
|
|
PropertyAdjustButton.new("Target previous", "/controls/armament/target-selected", "-1"),
|
|
PropertyAdjustButton.new("Weapon next", "/controls/armament/weapon-selected", "1"),
|
|
PropertyAdjustButton.new("Weapon previous", "/controls/armament/weapon-selected", "-1"),
|
|
PropertyAdjustButton.new("Azimuth left", "/controls/radar/azimuth-deg", "-5"),
|
|
PropertyAdjustButton.new("Azimuth right", "/controls/radar/azimuth-deg", "5"),
|
|
PropertyAdjustButton.new("Elevation up", "/controls/radar/elevation-deg", "5"),
|
|
PropertyAdjustButton.new("Elevation down", "/controls/radar/elevation-deg", "-5"),
|
|
PropertyAdjustButton.new("Missile Reject", "/controls/armament/missile-reject", "1"),
|
|
|
|
NasalButton.new("View Decrease", "view.decrease(0.75);", 1),
|
|
NasalButton.new("View Increase", "view.increase(0.75);", 1),
|
|
NasalButton.new("View Cycle Forwards", "view.stepView(1);", 0),
|
|
NasalButton.new("View Cycle Backwards", "view.stepView(-1);", 0),
|
|
PropertyAdjustButton.new("View Left", "/sim/current-view/goal-heading-offset-deg", "30.0"),
|
|
PropertyAdjustButton.new("View Right", "/sim/current-view/goal-heading-offset-deg", "-30.0"),
|
|
PropertyAdjustButton.new("View Up", "/sim/current-view/goal-pitch-offset-deg", "20.0"),
|
|
PropertyAdjustButton.new("View Down", "/sim/current-view/goal-pitch-offset-deg", "-20.0"),
|
|
CustomButton.new(),
|
|
];
|
|
|
|
# Parse config from the /input tree and write it to the
|
|
# dialog_root.
|
|
var readConfig = func(dialog_root="/sim/gui/dialogs/joystick-config") {
|
|
|
|
var js_name = getprop(dialog_root ~ "/selected-joystick");
|
|
var joysticks = props.globals.getNode("/input/joysticks").getChildren("js");
|
|
|
|
if (size(joysticks) == 0) { return 0; }
|
|
|
|
if (js_name == nil) {
|
|
js_name = joysticks[0].getNode("id").getValue();
|
|
}
|
|
|
|
var js = nil;
|
|
|
|
forindex (var i; joysticks) {
|
|
if ((joysticks[i].getNode("id") != nil) and
|
|
(joysticks[i].getNode("id").getValue() == js_name))
|
|
{
|
|
js = joysticks[i];
|
|
setprop(dialog_root ~ "/selected-joystick", js_name);
|
|
setprop(dialog_root ~ "/selected-joystick-index", i);
|
|
setprop(dialog_root ~ "/selected-joystick-config", joysticks[i].getNode("source").getValue());
|
|
}
|
|
}
|
|
|
|
if (js == nil) {
|
|
# We didn't find the joystick we expected - default to the first
|
|
setprop(dialog_root ~ "/selected-joystick", joysticks[0].getNode("id").getValue());
|
|
setprop(dialog_root ~ "/selected-joystick-index", 0);
|
|
setprop(dialog_root ~ "/selected-joystick-config", joysticks[0].getNode("source").getValue());
|
|
}
|
|
|
|
# Set up the axes assignments
|
|
var axes = js.getChildren("axis");
|
|
|
|
for (var axis = 0; axis < MAX_AXES; axis = axis +1) {
|
|
|
|
var p = props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]", 1);
|
|
p.remove();
|
|
p = props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]", 1);
|
|
|
|
# Note that we can't simply use an index into the axes array
|
|
# as that doesn't work for a sparsley populated set of axes.
|
|
# E.g. one with n="3"
|
|
var a = js.getNode("axis[" ~ axis ~ "]");
|
|
|
|
if (a != nil) {
|
|
# Read properties from bindings
|
|
props.copy(a, p.getNode("original_binding", 1));
|
|
|
|
var binding = nil;
|
|
foreach (var b; joystick.axisBindings) {
|
|
if ((binding == nil) and (a != nil) and b.match(a)) {
|
|
binding = b.clone();
|
|
binding.parse(a);
|
|
p.getNode("binding", 1).setValue(binding.getName());
|
|
p.getNode("invertable", 1).setValue(binding.isInvertable());
|
|
p.getNode("inverted", 1).setValue(binding.isInverted());
|
|
}
|
|
}
|
|
|
|
if (binding == nil) {
|
|
# No binding for this axis
|
|
p.getNode("binding", 1).setValue("None");
|
|
p.getNode("invertable", 1).setValue(0);
|
|
p.getNode("inverted", 1).setValue(0);
|
|
p.removeChild("original_binding");
|
|
}
|
|
} else {
|
|
p.getNode("binding", 1).setValue("None");
|
|
p.getNode("invertable", 1).setValue(0);
|
|
p.getNode("inverted", 1).setValue(0);
|
|
p.removeChild("original_binding");
|
|
}
|
|
}
|
|
|
|
# Set up button assignment.
|
|
var buttons = js.getChildren("button");
|
|
|
|
for (var button = 0; button < MAX_BUTTONS; button = button + 1) {
|
|
var btn = props.globals.getNode(dialog_root ~ "/button[" ~ button ~ "]", 1);
|
|
btn.remove();
|
|
btn = props.globals.getNode(dialog_root ~ "/button[" ~ button ~ "]", 1);
|
|
|
|
# Note that we can't simply use an index into the buttons array
|
|
# as that doesn't work for a sparsley populated set of buttons.
|
|
# E.g. one with n="3"
|
|
var a = js.getNode("button[" ~ button ~ "]");
|
|
|
|
if (a != nil) {
|
|
# Read properties from bindings
|
|
props.copy(a, btn.getNode("original_binding", 1));
|
|
var binding = nil;
|
|
foreach (var b; joystick.buttonBindings) {
|
|
if ((binding == nil) and (a != nil) and b.match(a)) {
|
|
binding = b.clone();
|
|
btn.getNode("binding", 1).setValue(binding.getName());
|
|
props.copy(b.getBinding(button), btn.getNode("original_binding", 1));
|
|
}
|
|
}
|
|
|
|
if (b == nil) {
|
|
btn.getNode("binding", 1).setValue("None");
|
|
btn.removeChild("original_binding");
|
|
}
|
|
} else {
|
|
btn.getNode("binding", 1).setValue("None");
|
|
btn.removeChild("original_binding");
|
|
}
|
|
}
|
|
|
|
# Set up Nasal code.
|
|
var nasals = js.getChildren("nasal");
|
|
|
|
for (var nasal = 0; nasal < MAX_NASALS; nasal = nasal + 1) {
|
|
var nas = props.globals.getNode(dialog_root ~ "/nasal[" ~ nasal ~ "]", 1);
|
|
nas.remove();
|
|
nas = props.globals.getNode(dialog_root ~ "/nasal[" ~ nasal ~ "]", 1);
|
|
|
|
# Note that we can't simply use an index into the buttons array
|
|
# as that doesn't work for a sparsley populated set of buttons.
|
|
# E.g. one with n="3"
|
|
var a = js.getNode("nasal[" ~ nasal ~ "]");
|
|
if (a != nil) {
|
|
props.copy(a, nas.getNode("original_script", 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
var writeConfig = func(dialog_root="/sim/gui/dialogs/joystick-config", reset=0) {
|
|
|
|
# Write out the joystick file.
|
|
var config = props.Node.new();
|
|
var id = getprop(dialog_root ~ "/selected-joystick");
|
|
|
|
if (reset == 1) {
|
|
# We've been asked to reset the joystick config to the default. As we can't
|
|
# delete the configuration file, we achieve this by setting an invalid name
|
|
# tag that won't match.
|
|
config.getNode("name", 1).setValue("UNUSED INVALID CONFIG");
|
|
} else {
|
|
config.getNode("name", 1).setValue(id);
|
|
}
|
|
|
|
var nasals = props.globals.getNode(dialog_root).getChildren("nasal");
|
|
forindex (var nas; nasals) {
|
|
var nasalscript = config.getNode("nasal[" ~ nas ~ "]", 1);
|
|
props.copy(props.globals.getNode(dialog_root ~ "/nasal[" ~ nas ~ "]/original_script", 1), nasalscript);
|
|
}
|
|
|
|
var axes = props.globals.getNode(dialog_root).getChildren("axis");
|
|
forindex (var axis; axes) {
|
|
var name = getprop(dialog_root ~ "/axis[" ~ axis ~ "]/binding");
|
|
|
|
if (name != "None") {
|
|
foreach (var binding; axisBindings) {
|
|
if (binding.getName() == name) {
|
|
var b = binding.clone();
|
|
b.setInverted(getprop(dialog_root ~ "/axis[" ~ axis ~ "]/inverted"));
|
|
|
|
# Generate the axis and binding
|
|
var axisnode = config.getNode("axis[" ~ axis ~ "]", 1);
|
|
if (name == "Custom") {
|
|
props.copy(props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]/original_binding", 1), axisnode);
|
|
} else {
|
|
props.copy(b.getBinding(axis), axisnode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var buttons = props.globals.getNode(dialog_root).getChildren("button");
|
|
forindex (var btn; buttons) {
|
|
var name = getprop(dialog_root ~ "/button[" ~ btn ~ "]/binding");
|
|
|
|
if (name != "None") {
|
|
foreach (var binding; buttonBindings) {
|
|
if (binding.getName() == name) {
|
|
var b = binding.clone();
|
|
# Generate the axis and binding
|
|
var buttonprop = config.getNode("button[" ~ btn ~ "]", 1);
|
|
if (name == "Custom") {
|
|
props.copy(props.globals.getNode(dialog_root ~ "/button[" ~ btn ~ "]/original_binding", 1), buttonprop);
|
|
} else {
|
|
props.copy(b.getBinding(btn), buttonprop);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var filename = id;
|
|
filename = string.replace(filename, " ", "-");
|
|
filename = string.replace(filename, ".", "");
|
|
filename = string.replace(filename, "/", "");
|
|
|
|
# Write out the file
|
|
io.write_properties(getprop("/sim/fg-home") ~ "/Input/Joysticks/" ~ filename ~ ".xml", config);
|
|
}
|
|
|
|
var resetConfig = func(dialog_root="/sim/gui/dialogs/joystick-config") {
|
|
writeConfig(dialog_root, 1);
|
|
}
|