diff --git a/Nasal/IOrules b/Nasal/IOrules index b130958f7..a67bcf3da 100644 --- a/Nasal/IOrules +++ b/Nasal/IOrules @@ -38,3 +38,5 @@ WRITE ALLOW $FG_HOME/state/*.xml WRITE ALLOW $FG_HOME/aircraft-data/*.xml WRITE ALLOW $FG_HOME/Wildfire/*.xml WRITE ALLOW $FG_HOME/runtime-jetways/*.xml +WRITE ALLOW $FG_HOME/Input/Joysticks/*.xml + diff --git a/Nasal/joystick.nas b/Nasal/joystick.nas new file mode 100644 index 000000000..f79481c14 --- /dev/null +++ b/Nasal/joystick.nas @@ -0,0 +1,686 @@ +# Joystick configuration library. +var DIALOGROOT = "/sim/gui/dialogs/joystick-config"; +var MAX_AXES = 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, deadband=0, factor=1, offset=0) { + var m = { parents: [PropertyScaleAxis, Axis.new(name, prop, 1) ] }; + m.prop=prop; + m.deadband = deadband; + 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.deadband = me.deadband; + 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) { + me.deadband = p.getNode("binding", 1).getNode("dead-band", 1).getValue(); + if (p.getNode("binding", 1).getNode("factor", 1).getValue() != nil) { + me.inverted = (p.getNode("binding", 1).getNode("factor", 1).getValue() < 0); + } + me.offset = p.getNode("binding", 1).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); + p.getNode("binding", 1).getNode("dead-band", 1).setValue(me.deadband); + 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", "controls.throttleAxis();", "/controls/engines/engine[0]/throttle") , + NasalScaleAxis.new("Mixture", "controls.mixtureAxis();", "/controls/engines/engine[0]/mixture") , + NasalScaleAxis.new("Propeller", "controls.propellerAxis();", "/controls/engines/engine[0]/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, 0.5, 1.0), + PropertyScaleAxis.new("Brake Right", "/controls/gear/brake-right", 0, 0.5, 1.0), + NasalLowHighAxis.new("Aileron Trim", "controls.aileronTrim(-1);", "controls.aileronTrim(1);", "/controls/flight/aileron-trim"), + NasalLowHighAxis.new("Elevator Trim", "controls.elevatorTrim(-1);", "controls.elevatorTrim(1);", "/controls/flight/elevator-trim"), + NasalLowHighAxis.new("Rudder Trim", "controls.rudderTrim(-1);", "controls.rudderTrim(1);", "/controls/flight/rudder-trim"), + 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("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(-1);"), + NasalHoldButton.new("Trigger", "controls.trigger(1);", "controls.trigger(0);"), + NasalHoldButton.new("Flaps Up", "controls.flapsDown(-1);", "controls.flapsDown(0);"), + NasalHoldButton.new("Flaps Down", "controls.flapsDown(1);", "controls.flapsDown(0);"), + NasalHoldButton.new("Gear Up", "controls.gearDown(-1);", "controls.gearDown(0);"), + NasalHoldButton.new("Gear Down", "controls.gearDown(1);", "controls.gearDown(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);"), + + 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(joystick) == 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()); + } + } + + # 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, p.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"); + } + } +} + +var writeConfig = func(dialog_root="/sim/gui/dialogs/joystick-config") { + + # Write out the joystick file. + var config = props.Node.new(); + var id = getprop(dialog_root ~ "/selected-joystick"); + config.getNode("name", 1).setValue(id); + + 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); +} + + diff --git a/gui/dialogs/button-config.xml b/gui/dialogs/button-config.xml new file mode 100644 index 000000000..81d6fb19f --- /dev/null +++ b/gui/dialogs/button-config.xml @@ -0,0 +1,437 @@ + + + + + + + + + + + button-config + vbox + true + true + 3 + + + hbox + 1 + + true + + + + + + true + + + + + + + + left + + + + + + + table + + + 0 + 0 + + + + + + + + + + + + + + + + + 0 + 1 + + + + + + + + + + + + + + + + + 0 + 2 + + + + + + + + + + + + + + + 0 + 3 + + + + + + + + + + + + + + + + + + + + + + + + true + + hbox + + + + true + + + + diff --git a/gui/dialogs/joystick-config.xml b/gui/dialogs/joystick-config.xml new file mode 100644 index 000000000..912298a4e --- /dev/null +++ b/gui/dialogs/joystick-config.xml @@ -0,0 +1,463 @@ + + + + + 5) { + row = row +1; + col = 0; + } +} + +var updateConfig = func() { + joystick.writeConfig(); + fgcommand("reinit", props.Node.new({"subsystem": "input"})); + fgcommand("dialog-close", props.Node.new({"dialog-name": "joystick-config"})); + fgcommand("dialog-show", props.Node.new({"dialog-name": "joystick-config"})); +} + ]]> + + + + + + joystick-config + vbox + true + 3 + + + hbox + 1 + + true + + + + + + true + + + + + + + table + 2 + + + 150 + 0 + 0 + + + + + right + 1 + 0 + + + + + left + %.5f + /controls/flight/aileron + 1 + 2 + 0 + + + + + right + 1 + 1 + + + + + left + %.5f + /controls/flight/elevator + 1 + 2 + 1 + + + + + right + 3 + 0 + + + + + left + %.5f + /controls/flight/rudder + 1 + 4 + 0 + + + + + right + 3 + 1 + + + + + left + %.5f + /controls/engines/engine/throttle + 1 + 4 + 1 + + + + 150 + 5 + 0 + + + + + + table + left + + + 0 + 0 + right + + + + + jsselect + 0 + 1 + left + /sim/gui/dialogs/joystick-config/selected-joystick + 350 + + dialog-apply + jsselect + + + nasal + + + + + + 1 + 0 + right + + + + + 1 + 1 + left + + /sim/gui/dialogs/joystick-config/selected-joystick-config + + + + + + + + + hbox + top + fill + + + + table + top + axistable + + + + + 0 + 0 + + + + + 0 + 1 + + + + + 0 + 2 + + + + + 0 + 3 + + + + + + + + + + + + + + table + top + buttontable + + + + 00 + + + + + 01 + + + + + 02 + + + + + 03 + + + + + 04 + + + + + 05 + + + + + buttontemplate + + + 10 + right + 1 + 1 + /devices/status/joysticks/joystick[0]/button[0] + + + + 11 + /sim/gui/dialogs/joystick-config/button[0] + + left + true + + + + + + + + + + + + + + + hbox + + + true + + + + + + true + + + +