Howto talk:Implementing a simple GCA system: Difference between revisions
Jump to navigation
Jump to search
(Created page with "<syntaxhighlight lang="nasal"> io.include("gca.nas"); ### # # UI code starts here # ### var (width,height) = (320,450); var title = 'GCA Dialog '; var window = canvas.Wind...") |
m (https://forum.flightgear.org/viewtopic.php?f=30&t=32882&p=319259#p319259) |
||
Line 1: | Line 1: | ||
<syntaxhighlight lang="nasal"> | <syntaxhighlight lang="nasal"> | ||
### | ### | ||
Line 8: | Line 6: | ||
# | # | ||
### | ### | ||
var configureGCA = func( gcaObject ) { | |||
var (width,height) = (320,450); | var (width,height) = (320,450); | ||
Line 132: | Line 132: | ||
var inputs = [ | var inputs = [ | ||
{text: 'Position root', default_value:'/position', focus:1, callback: | {text: 'Position root', default_value:'/position', focus:1, callback:GCAController.setPosition, tooltip:'property path to position node', validate: 'AircraftRoot', convert:nil, unit: 'property path'}, | ||
{text: 'Airport', default_value:'KSFO', focus:0, callback: | {text: 'Airport', default_value:'KSFO', focus:0, callback:GCAController.setAirport, tooltip:'ICAO ID, e.g. KSFO', validate: 'Airport', convert:nil, unit:'ICAO'}, | ||
{text: 'Runway', default_value:'28R', focus:0, callback: | {text: 'Runway', default_value:'28R', focus:0, callback:GCAController.setRunway, tooltip:'runway identifier, e.g. 28L', validate: 'Runway', convert:nil, unit:'rwy'}, | ||
{text: 'Final Approach', default_value:'10.00', focus:0, callback: | {text: 'Final Approach', default_value:'10.00', focus:0, callback:GCAController.setFinalApproach, tooltip:'length of final approach leg', validate: 'FinalApproach', convert:nil, unit:'nm'}, | ||
{text: 'Glidepath', default_value:'3.00', focus:0, callback: | {text: 'Glidepath', default_value:'3.00', focus:0, callback:GCAController.setGlidepath, tooltip:'glidepath in degrees, e.g. 3', validate: 'Glidepath', convert:nil, unit:'degrees'}, | ||
{text: 'Safety Slope', default_value:'2.00', focus:0, callback: | {text: 'Safety Slope', default_value:'2.00', focus:0, callback:GCAController.setSafetySlope, tooltip:'safety slope in degrees', validate: 'SafetySlope', convert:nil, unit:'degrees'}, | ||
{text: 'Decision Height', default_value:'200.00', focus:0, callback: | {text: 'Decision Height', default_value:'200.00', focus:0, callback:GCAController.setDecisionHeight, tooltip:'decision height (vertical offset)', validate: 'DecisionHeight', convert:nil, unit:'ft'}, | ||
{text: 'Touchdown Offset', default_value:'0.00', focus:0, callback: | {text: 'Touchdown Offset', default_value:'0.00', focus:0, callback:GCAController.setTouchdownOffset, tooltip:'touchdown offset', validate: 'TouchdownOffset', convert:nil, unit:'m'}, | ||
{text: 'Transmission interval', default_value:'5.00', focus:0, callback: | {text: 'Transmission interval', default_value:'5.00', focus:0, callback:GCAController.setTransmissionInterval, tooltip:'Controller/timer resolution', validate: 'TransmissionInterval', convert:nil, unit:'secs'}, | ||
{text: 'Transmission channel', default_value:'/sim/messages/approach', focus:0, callback: | {text: 'Transmission channel', default_value:'/sim/messages/approach', focus:0, callback:GCAController.setTransmissionChannel, tooltip:'property to use for transmissions. For example: /sim/multiplay/chat or /sim/sound/voices/approach', validate: 'TransmissionProperty', convert:nil, unit:'property'}, | ||
]; # input fields | ]; # input fields | ||
Line 176: | Line 176: | ||
var gcaRunning = 0; | var gcaRunning = 0; | ||
var myGCA = nil; | # var myGCA = nil; | ||
var toggleFields = func(enabled) { | var toggleFields = func(enabled) { | ||
Line 188: | Line 188: | ||
# create new object | # create new object | ||
myGCA = GCAController.new(); | # myGCA = GCAController.new(); | ||
foreach(var field; inputs) { | foreach(var field; inputs) { | ||
Line 195: | Line 195: | ||
# call vector | # call vector | ||
print(field.callback); | print(field.callback); | ||
call(GCAController[field.callback], [value], | # http://wiki.flightgear.org/Nasal_library#call.28.29 | ||
call(GCAController[field.callback], [value], gcaObject ); | |||
} | } | ||
myGCA.start(); | # myGCA.start(); | ||
gcaObject.start(); | |||
} | } | ||
Line 206: | Line 208: | ||
if (gcaRunning) { | if (gcaRunning) { | ||
gcaObject.stop(); | |||
gcaRunning = 0; | gcaRunning = 0; | ||
toggleFields(!gcaRunning); # set editable | toggleFields(!gcaRunning); # set editable | ||
Line 229: | Line 231: | ||
setupWidgetTooltip(widget:button, tooltip: "toggle GCA on/off"); | setupWidgetTooltip(widget:button, tooltip: "toggle GCA on/off"); | ||
myLayout.addItem(button); | myLayout.addItem(button); | ||
}; # configureGCA(); | |||
</syntaxhighlight> | </syntaxhighlight> |
Revision as of 14:22, 23 September 2017
###
#
# UI code starts here
#
###
var configureGCA = func( gcaObject ) {
var (width,height) = (320,450);
var title = 'GCA Dialog ';
var window = canvas.Window.new([width,height],"dialog").set('title',title);
window.del = func()
{
print("Cleaning up window:",title,"\n");
call(canvas.Window.del, [], me);
};
# adding a canvas to the new window and setting up background colors/transparency
var myCanvas = window.createCanvas().set("background", canvas.style.getColor("bg_color"));
# creating the top-level/root group which will contain all other elements/group
var root = myCanvas.createGroup();
# create a new layout
var myLayout = canvas.VBoxLayout.new();
# assign it to the Canvas
myCanvas.setLayout(myLayout);
var setupWidgetTooltip = func(widget, tooltip) {
widget._view._root.addEventListener("mouseover", func gui.popupTip(tooltip) );
} # setupWidgetTooltip
var setupLabeledInput = func(root, layout, input) {
var label = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0});
var unit_suffix = sprintf(" (%s):", input.unit);
label.setText(input.text~unit_suffix);
layout.addItem(label);
var field = canvas.gui.widgets.LineEdit.new(root, canvas.style, {});
layout.addItem(field);
field.setText(input.default_value);
if (input.focus)
field.setFocus();
setupWidgetTooltip(widget:field, tooltip: input.tooltip);
var el = field._view._root;
el.addEventListener("keypress", func (e) {
# colorize valid/invalid inputs
var color = (validationHelpers[input.validate]( field.text() ) == 0) ? [0,1,0] : [1,0,0];
field._view._root.setColorFill(color);
});
return field; # return to caller
} # setupLabeledInput()
var validationHelpers = {
_internalState: {},
'AircraftRoot': func(input) {
var root = props.getNode(input);
if (root == nil) return 1; # error
var required_props = ['altitude-ft', 'longitude-deg', 'latitude-deg'];
foreach(var rp; required_props) {
if (root.getNode(rp) == nil) return STATUS.FAILURE;
} # foreach
return STATUS.SUCCESS; # valid root node
},
'Airport': func(input) {
var match = airportinfo(input);
if (match == nil or typeof(match) != 'ghost') return STATUS.FAILURE;
validationHelpers._internalState.airport = match;
return STATUS.SUCCESS;
},
'Runway': func(input) {
var runways = validationHelpers._internalState.airport.runways;
if (typeof(runways)!="hash" or !size(keys(runways))) return STATUS.FAILURE;
if (runways[input] == nil) return STATUS.FAILURE;
return STATUS.SUCCESS;
},
'FinalApproach': func(input) {
return 0;
},
'Glidepath': func(input) {
return 0;
},
'SafetySlope': func(input) {
return 0;
},
'DecisionHeight': func(input) {
return 0;
},
'TouchdownOffset': func(input) {
return 0;
},
'TransmissionInterval': func(input) {
return 0;
},
'TransmissionProperty': func(input) {
return 0;
},
}; # validationHelpers;
var inputs = [
{text: 'Position root', default_value:'/position', focus:1, callback:GCAController.setPosition, tooltip:'property path to position node', validate: 'AircraftRoot', convert:nil, unit: 'property path'},
{text: 'Airport', default_value:'KSFO', focus:0, callback:GCAController.setAirport, tooltip:'ICAO ID, e.g. KSFO', validate: 'Airport', convert:nil, unit:'ICAO'},
{text: 'Runway', default_value:'28R', focus:0, callback:GCAController.setRunway, tooltip:'runway identifier, e.g. 28L', validate: 'Runway', convert:nil, unit:'rwy'},
{text: 'Final Approach', default_value:'10.00', focus:0, callback:GCAController.setFinalApproach, tooltip:'length of final approach leg', validate: 'FinalApproach', convert:nil, unit:'nm'},
{text: 'Glidepath', default_value:'3.00', focus:0, callback:GCAController.setGlidepath, tooltip:'glidepath in degrees, e.g. 3', validate: 'Glidepath', convert:nil, unit:'degrees'},
{text: 'Safety Slope', default_value:'2.00', focus:0, callback:GCAController.setSafetySlope, tooltip:'safety slope in degrees', validate: 'SafetySlope', convert:nil, unit:'degrees'},
{text: 'Decision Height', default_value:'200.00', focus:0, callback:GCAController.setDecisionHeight, tooltip:'decision height (vertical offset)', validate: 'DecisionHeight', convert:nil, unit:'ft'},
{text: 'Touchdown Offset', default_value:'0.00', focus:0, callback:GCAController.setTouchdownOffset, tooltip:'touchdown offset', validate: 'TouchdownOffset', convert:nil, unit:'m'},
{text: 'Transmission interval', default_value:'5.00', focus:0, callback:GCAController.setTransmissionInterval, tooltip:'Controller/timer resolution', validate: 'TransmissionInterval', convert:nil, unit:'secs'},
{text: 'Transmission channel', default_value:'/sim/messages/approach', focus:0, callback:GCAController.setTransmissionChannel, tooltip:'property to use for transmissions. For example: /sim/multiplay/chat or /sim/sound/voices/approach', validate: 'TransmissionProperty', convert:nil, unit:'property'},
]; # input fields
foreach(var input; inputs) {
# TODO: pass input hash
input.widget = setupLabeledInput(root, myLayout, input);
}
var validateFields = func() {
foreach(var f; inputs) {
var result = validationHelpers[f.validate] ( f.widget.text() );
if (result == STATUS.FAILURE) {
canvas.MessageBox.critical(
"Validation error",
"Error validating "~f.text,
cb = nil,
buttons = canvas.MessageBox.Ok
); # MessageBox
} # error handling
} # foreach
return STATUS.SUCCESS; # all validations passed
} # validateFields()
###
# global stuff
#
var gcaRunning = 0;
# var myGCA = nil;
var toggleFields = func(enabled) {
foreach(var i; inputs) {
i.widget.setEnabled(enabled);
}
}
var buildCGA = func() {
# create new object
# myGCA = GCAController.new();
foreach(var field; inputs) {
var value = field.widget.text();
# var converted_value = typeof(field.convert != nil) ? field.convert(value) : value;
# call vector
print(field.callback);
# http://wiki.flightgear.org/Nasal_library#call.28.29
call(GCAController[field.callback], [value], gcaObject );
}
# myGCA.start();
gcaObject.start();
}
var toggleGCA = func() {
if (gcaRunning) {
gcaObject.stop();
gcaRunning = 0;
toggleFields(!gcaRunning); # set editable
return;
}
if (!gcaRunning and validateFields()==0) {
gcaRunning = 1;
toggleFields(!gcaRunning); # set readonly
buildCGA();
return;
}
} # toggleGCA()
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})
.setText("Start/Stop")
.setFixedSize(75, 25)
.listen("clicked", toggleGCA);
setupWidgetTooltip(widget:button, tooltip: "toggle GCA on/off");
myLayout.addItem(button);
}; # configureGCA();