Merge branch 'master' of gitorious.org:fg/fgdata
7
Aircraft/Generic/Systems/Tests/FailureMgr/test_all.nas
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Aggregation of all tests for the Failure Manager
|
||||
|
||||
io.include("Aircraft/Generic/Systems/Tests/FailureMgr/test_cycle_counter.nas");
|
||||
io.include("Aircraft/Generic/Systems/Tests/FailureMgr/test_altitude_trigger.nas");
|
||||
io.include("Aircraft/Generic/Systems/Tests/FailureMgr/test_mcbf_trigger.nas");
|
||||
io.include("Aircraft/Generic/Systems/Tests/FailureMgr/test_mtbf_trigger.nas");
|
||||
io.include("Aircraft/Generic/Systems/Tests/FailureMgr/test_failure_mode.nas");
|
|
@ -0,0 +1,155 @@
|
|||
# AltitudeTrigger unit tests
|
||||
#
|
||||
# Copyright (C) 2014 Anton Gomez Alvedro
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
io.include("Aircraft/Generic/Systems/Tests/test.nas");
|
||||
io.include("Aircraft/Generic/Systems/failures.nas");
|
||||
|
||||
var TestAltitudeTrigger = {
|
||||
|
||||
parents: [TestSuite],
|
||||
|
||||
setup: func {
|
||||
props.globals.initNode("/test");
|
||||
},
|
||||
|
||||
cleanup: func {
|
||||
me.trigger = nil;
|
||||
props.globals.getNode("/test").remove();
|
||||
},
|
||||
|
||||
test_binding: func {
|
||||
|
||||
setprop("/test/foreign-property", 25);
|
||||
|
||||
me.trigger = AltitudeTrigger.new(100, 200);
|
||||
me.trigger.bind("/test/");
|
||||
|
||||
assert_prop_exists("/test/reset");
|
||||
assert_prop_exists("/test/min-altitude-ft");
|
||||
assert_prop_exists("/test/min-altitude-ft");
|
||||
|
||||
me.trigger.unbind();
|
||||
|
||||
fail_if_prop_exists("/test/reset");
|
||||
fail_if_prop_exists("/test/min-altitude-ft");
|
||||
fail_if_prop_exists("/test/min-altitude-ft");
|
||||
|
||||
assert_prop_exists("/test/foreign-property");
|
||||
},
|
||||
|
||||
test_props_are_read_on_reset: func {
|
||||
|
||||
me.trigger = AltitudeTrigger.new(100, 200);
|
||||
me.trigger.bind("/test/");
|
||||
|
||||
assert(me.trigger.params["min-altitude-ft"] == 100);
|
||||
assert(me.trigger.params["max-altitude-ft"] == 200);
|
||||
|
||||
setprop("/test/min-altitude-ft", 1000);
|
||||
setprop("/test/max-altitude-ft", 2000);
|
||||
|
||||
assert(me.trigger.params["min-altitude-ft"] == 100);
|
||||
assert(me.trigger.params["max-altitude-ft"] == 200);
|
||||
|
||||
me.trigger.reset();
|
||||
|
||||
assert(me.trigger.params["min-altitude-ft"] == 1000);
|
||||
assert(me.trigger.params["max-altitude-ft"] == 2000);
|
||||
},
|
||||
|
||||
test_trigger_fires_within_min_and_max: func {
|
||||
|
||||
me.trigger = AltitudeTrigger.new(100, 200);
|
||||
|
||||
me.trigger._altitude_prop = "/test/fake-altitude-ft";
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
setprop("/test/fake-altitude-ft", 0);
|
||||
assert(me.trigger.update() == 0);
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
setprop("/test/fake-altitude-ft", 300);
|
||||
assert(me.trigger.update() == 0);
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
setprop("/test/fake-altitude-ft", 150);
|
||||
assert(me.trigger.update() == 1);
|
||||
assert(me.trigger.fired);
|
||||
},
|
||||
|
||||
test_trigger_accepts_nil_max: func {
|
||||
|
||||
me.trigger = AltitudeTrigger.new(500, nil);
|
||||
me.trigger._altitude_prop = "/test/fake-altitude-ft";
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
setprop("/test/fake-altitude-ft", -250);
|
||||
assert(me.trigger.update() == 0);
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
setprop("/test/fake-altitude-ft", 0);
|
||||
assert(me.trigger.update() == 0);
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
setprop("/test/fake-altitude-ft", 250);
|
||||
assert(me.trigger.update() == 0);
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
setprop("/test/fake-altitude-ft", 750);
|
||||
assert(me.trigger.update() == 1);
|
||||
assert(me.trigger.fired);
|
||||
},
|
||||
|
||||
test_trigger_accepts_nil_min: func {
|
||||
|
||||
me.trigger = AltitudeTrigger.new(nil, 500);
|
||||
me.trigger._altitude_prop = "/test/fake-altitude-ft";
|
||||
me.trigger.bind("/test/trigger/");
|
||||
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
setprop("/test/fake-altitude-ft", 750);
|
||||
assert(me.trigger.update() == 0);
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
setprop("/test/fake-altitude-ft", 500);
|
||||
assert(me.trigger.update() == 0);
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
setprop("/test/fake-altitude-ft", 250);
|
||||
assert(me.trigger.update() == 1);
|
||||
assert(me.trigger.fired);
|
||||
|
||||
me.trigger.reset();
|
||||
|
||||
setprop("/test/fake-altitude-ft", -250);
|
||||
assert(me.trigger.update() == 1);
|
||||
assert(me.trigger.fired);
|
||||
},
|
||||
|
||||
test_trigger_dies_if_both_params_are_nil: func {
|
||||
call(AltitudeTrigger.new, [nil, nil], AltitudeTrigger, var err = []);
|
||||
assert(size(err) > 0);
|
||||
},
|
||||
|
||||
test_to_str: func {
|
||||
me.trigger = AltitudeTrigger.new(100, 200);
|
||||
call(me.trigger.to_str, [], me.trigger, var err = []);
|
||||
assert(size(err) == 0);
|
||||
}
|
||||
};
|
124
Aircraft/Generic/Systems/Tests/FailureMgr/test_cycle_counter.nas
Normal file
|
@ -0,0 +1,124 @@
|
|||
# CycleCounter unit tests
|
||||
#
|
||||
# Copyright (C) 2014 Anton Gomez Alvedro
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
io.include("Aircraft/Generic/Systems/Tests/test.nas");
|
||||
io.include("Aircraft/Generic/Systems/failures.nas");
|
||||
|
||||
var TestCycleCounter = {
|
||||
|
||||
parents: [TestSuite],
|
||||
|
||||
setup: func {
|
||||
props.globals.initNode("/test");
|
||||
},
|
||||
|
||||
cleanup: func {
|
||||
props.globals.getNode("/test").remove();
|
||||
me.counter = nil;
|
||||
},
|
||||
|
||||
_shake_that_prop: func (pattern=nil) {
|
||||
|
||||
if (pattern == nil)
|
||||
pattern = [0, -10, 10, -10, 10, -10, 10, 0];
|
||||
|
||||
setprop("/test/property", pattern[0]);
|
||||
me.counter.reset();
|
||||
|
||||
var i = 0;
|
||||
var value = pattern[0];
|
||||
var target = pattern[1];
|
||||
var delta = 0;
|
||||
|
||||
while(i < size(pattern) - 1) {
|
||||
|
||||
target = pattern[i+1];
|
||||
delta = pattern[i+1] > pattern[i] ? 1 : -1;
|
||||
|
||||
while(value != target) {
|
||||
value += delta;
|
||||
setprop("/test/property", value);
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
},
|
||||
|
||||
test_cycles_dont_grow_while_disabled: func {
|
||||
me.counter = CycleCounter.new("/test/property");
|
||||
me._shake_that_prop();
|
||||
assert(me.counter.cycles == 0);
|
||||
},
|
||||
|
||||
test_cycles_grow_while_enabled: func {
|
||||
me.counter = CycleCounter.new("/test/property");
|
||||
|
||||
me._shake_that_prop();
|
||||
assert(me.counter.cycles == 0);
|
||||
|
||||
me.counter.enable();
|
||||
|
||||
me._shake_that_prop();
|
||||
assert(me.counter.cycles == 3);
|
||||
},
|
||||
|
||||
test_reset: func {
|
||||
me.counter = CycleCounter.new("/test/property");
|
||||
me.counter.enable();
|
||||
|
||||
me._shake_that_prop();
|
||||
assert(me.counter.cycles > 0);
|
||||
|
||||
me.counter.reset();
|
||||
assert(me.counter.cycles == 0);
|
||||
},
|
||||
|
||||
test_callback_every_half_cycle: func {
|
||||
var count = 0;
|
||||
|
||||
me.counter = CycleCounter.new(
|
||||
property: "/test/property",
|
||||
on_update: func (cycles) { count += 1 });
|
||||
|
||||
me.counter.enable();
|
||||
me._shake_that_prop();
|
||||
|
||||
assert(count == 6);
|
||||
},
|
||||
|
||||
test_callback_reports_cycle_count: func {
|
||||
var count = 0;
|
||||
var cb = func (cycles) {
|
||||
count += 1;
|
||||
assert(cycles == count * 0.5);
|
||||
};
|
||||
|
||||
me.counter = CycleCounter.new(
|
||||
property: "/test/property", on_update: cb);
|
||||
|
||||
me.counter.enable();
|
||||
me._shake_that_prop();
|
||||
},
|
||||
|
||||
test_counter_works_for_binary_props: func {
|
||||
me.counter = CycleCounter.new("/test/property");
|
||||
me.counter.enable();
|
||||
me._shake_that_prop([0, 1, 0, 1, 0, 1]);
|
||||
assert(me.counter.cycles == 2);
|
||||
}
|
||||
};
|
113
Aircraft/Generic/Systems/Tests/FailureMgr/test_failure_mode.nas
Normal file
|
@ -0,0 +1,113 @@
|
|||
# AltitudeTrigger unit tests
|
||||
#
|
||||
# Copyright (C) 2014 Anton Gomez Alvedro
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
io.include("Aircraft/Generic/Systems/Tests/test.nas");
|
||||
io.include("Aircraft/Generic/Systems/failures.nas");
|
||||
|
||||
var TestFailureMode = {
|
||||
|
||||
parents: [TestSuite],
|
||||
|
||||
setup: func {
|
||||
props.globals.initNode("/test");
|
||||
},
|
||||
|
||||
cleanup: func {
|
||||
me.mode = nil;
|
||||
props.globals.getNode("/test").remove();
|
||||
},
|
||||
|
||||
test_binding: func {
|
||||
var actuator = { parents: [FailureMgr.FailureActuator] };
|
||||
setprop("/test/foreign-property", 25);
|
||||
|
||||
me.mode = FailureMgr.FailureMode.new(
|
||||
id: "instruments/compass",
|
||||
description: "a description",
|
||||
actuator: actuator);
|
||||
|
||||
me.mode.bind("/test/");
|
||||
assert_prop_exists("/test/instruments/compass/failure-level");
|
||||
|
||||
me.mode.unbind();
|
||||
fail_if_prop_exists("/test/instruments/compass/failure-level");
|
||||
fail_if_prop_exists("/test/instruments/compass");
|
||||
assert_prop_exists("/test/foreign-property");
|
||||
},
|
||||
|
||||
test_set_failure_level_calls_actuator: func {
|
||||
var level = 0;
|
||||
var actuator = {
|
||||
parents: [FailureMgr.FailureActuator],
|
||||
set_failure_level: func (l) { level = l },
|
||||
};
|
||||
|
||||
me.mode = FailureMgr.FailureMode.new(
|
||||
id: "instruments/compass",
|
||||
description: "a description",
|
||||
actuator: actuator);
|
||||
me.mode.bind("/test/");
|
||||
|
||||
me.mode.set_failure_level(1);
|
||||
assert(level == 1);
|
||||
},
|
||||
|
||||
test_actuator_gets_called_from_prop: func {
|
||||
var level = 0;
|
||||
var actuator = {
|
||||
parents: [FailureMgr.FailureActuator],
|
||||
set_failure_level: func (l) { level = l },
|
||||
};
|
||||
|
||||
me.mode = FailureMgr.FailureMode.new(
|
||||
id: "instruments/compass",
|
||||
description: "a description",
|
||||
actuator: actuator);
|
||||
|
||||
me.mode.bind("/test/");
|
||||
setprop("/test/instruments/compass/failure-level", 1);
|
||||
assert(level == 1);
|
||||
},
|
||||
|
||||
test_setting_level_from_nasal_is_shown_in_prop: func {
|
||||
var level = 0;
|
||||
var actuator = {
|
||||
parents: [FailureMgr.FailureActuator],
|
||||
set_failure_level: func (l) { level = l },
|
||||
};
|
||||
|
||||
me.mode = FailureMgr.FailureMode.new(
|
||||
id: "instruments/compass",
|
||||
description: "a description",
|
||||
actuator: actuator);
|
||||
|
||||
me.mode.bind("/test/");
|
||||
|
||||
me.mode.set_failure_level(1);
|
||||
assert(level == 1);
|
||||
|
||||
var prop_value = getprop("/test/instruments/compass/failure-level");
|
||||
assert(prop_value == 1);
|
||||
|
||||
me.mode.set_failure_level(0.5);
|
||||
assert(level == 0.5);
|
||||
|
||||
prop_value = getprop("/test/instruments/compass/failure-level");
|
||||
assert(prop_value == 0.5);
|
||||
}
|
||||
};
|
116
Aircraft/Generic/Systems/Tests/FailureMgr/test_mcbf_trigger.nas
Normal file
|
@ -0,0 +1,116 @@
|
|||
# McbfTrigger unit tests
|
||||
#
|
||||
# Copyright (C) 2014 Anton Gomez Alvedro
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
io.include("Aircraft/Generic/Systems/Tests/test.nas");
|
||||
io.include("Aircraft/Generic/Systems/failures.nas");
|
||||
|
||||
var TestMcbfTrigger = {
|
||||
|
||||
parents: [TestSuite],
|
||||
|
||||
setup: func {
|
||||
props.globals.initNode("/test");
|
||||
},
|
||||
|
||||
cleanup: func {
|
||||
me.trigger = nil;
|
||||
props.globals.getNode("/test").remove();
|
||||
},
|
||||
|
||||
_do_one_cycle: func (prop) {
|
||||
setprop(prop, 10);
|
||||
setprop(prop, -10);
|
||||
setprop(prop, 0);
|
||||
},
|
||||
|
||||
test_binding: func {
|
||||
setprop("/test/property", 0);
|
||||
|
||||
me.trigger = McbfTrigger.new("/test/property", 3);
|
||||
me.trigger.bind("/test/");
|
||||
|
||||
assert_prop_exists("/test/reset");
|
||||
assert_prop_exists("/test/mcbf");
|
||||
|
||||
me.trigger.unbind();
|
||||
fail_if_prop_exists("/test/reset");
|
||||
fail_if_prop_exists("/test/mcbf");
|
||||
assert_prop_exists("/test/property");
|
||||
},
|
||||
|
||||
test_trigger_fires_after_activation_cycles: func {
|
||||
setprop("/test/property", 25);
|
||||
me.trigger = McbfTrigger.new("/test/property", 3);
|
||||
me.trigger.activation_cycles = 3;
|
||||
me.trigger.enable();
|
||||
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
for (var i = 1; i < 5; i += 1) {
|
||||
me._do_one_cycle("/test/property");
|
||||
assert(me.trigger.fired == (i > 3));
|
||||
}
|
||||
},
|
||||
|
||||
test_trigger_notifies_observer_once: func {
|
||||
var observer_called = 0;
|
||||
var on_fire = func observer_called += 1;
|
||||
|
||||
setprop("/test/property", 25);
|
||||
me.trigger = McbfTrigger.new("/test/property", 3);
|
||||
me.trigger.activation_cycles = 3;
|
||||
me.trigger.on_fire = on_fire;
|
||||
me.trigger.enable();
|
||||
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
for (var i = 1; i < 5; i += 1)
|
||||
me._do_one_cycle("/test/property");
|
||||
|
||||
assert(observer_called == 1);
|
||||
},
|
||||
|
||||
test_reset: func {
|
||||
setprop("/test/property", 25);
|
||||
me.trigger = McbfTrigger.new("/test/property", 3);
|
||||
me.trigger.activation_cycles = 3;
|
||||
me.trigger.bind("/test");
|
||||
me.trigger.enable();
|
||||
|
||||
for (var i = 1; i < 5; i += 1)
|
||||
me._do_one_cycle("/test/property");
|
||||
|
||||
assert(me.trigger.fired);
|
||||
|
||||
me.trigger.reset();
|
||||
me.trigger.activation_cycles = 3;
|
||||
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
for (var i = 1; i < 5; i += 1) {
|
||||
me._do_one_cycle("/test/property");
|
||||
assert(me.trigger.fired == (i > 3));
|
||||
}
|
||||
},
|
||||
|
||||
test_to_str: func {
|
||||
me.trigger = McbfTrigger.new("/test/property", 3);
|
||||
call(me.trigger.to_str, [], me.trigger, var err = []);
|
||||
assert(size(err) == 0);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,87 @@
|
|||
# MtbfTrigger unit tests
|
||||
#
|
||||
# Copyright (C) 2014 Anton Gomez Alvedro
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
io.include("Aircraft/Generic/Systems/Tests/test.nas");
|
||||
io.include("Aircraft/Generic/Systems/failures.nas");
|
||||
|
||||
var TestMtbfTrigger = {
|
||||
|
||||
parents: [TestSuite],
|
||||
|
||||
setup: func {
|
||||
props.globals.initNode("/test");
|
||||
},
|
||||
|
||||
cleanup: func {
|
||||
me.trigger = nil;
|
||||
props.globals.getNode("/test").remove();
|
||||
},
|
||||
|
||||
test_binding: func {
|
||||
|
||||
setprop("/test/foreign-property", 25);
|
||||
|
||||
me.trigger = MtbfTrigger.new(60);
|
||||
me.trigger.bind("/test/");
|
||||
|
||||
assert_prop_exists("/test/reset");
|
||||
assert_prop_exists("/test/mtbf");
|
||||
|
||||
me.trigger.unbind();
|
||||
|
||||
fail_if_prop_exists("/test/reset");
|
||||
fail_if_prop_exists("/test/mtbf");
|
||||
|
||||
assert_prop_exists("/test/foreign-property");
|
||||
},
|
||||
|
||||
test_props_are_read_on_reset: func {
|
||||
|
||||
me.trigger = MtbfTrigger.new(60);
|
||||
me.trigger.bind("/test/");
|
||||
assert(me.trigger.params["mtbf"] == 60);
|
||||
|
||||
setprop("/test/mtbf", 120);
|
||||
assert(me.trigger.params["mtbf"] == 60);
|
||||
|
||||
me.trigger.reset();
|
||||
assert(me.trigger.params["mtbf"] == 120);
|
||||
},
|
||||
|
||||
test_trigger_fires_after_fire_time: func {
|
||||
|
||||
me.trigger = MtbfTrigger.new(60);
|
||||
me.trigger._time_prop = "/test/fake-time-sec";
|
||||
me.trigger.fire_time = 60;
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
setprop("/test/fake-time-sec", 50);
|
||||
assert(me.trigger.update() == 0);
|
||||
assert(!me.trigger.fired);
|
||||
|
||||
setprop("/test/fake-time-sec", 70);
|
||||
assert(me.trigger.update() == 1);
|
||||
assert(me.trigger.fired);
|
||||
},
|
||||
|
||||
test_to_str: func {
|
||||
me.trigger = MtbfTrigger.new(60);
|
||||
call(me.trigger.to_str, [], me.trigger, var err = []);
|
||||
assert(size(err) == 0);
|
||||
}
|
||||
};
|
139
Aircraft/Generic/Systems/Tests/test.nas
Normal file
|
@ -0,0 +1,139 @@
|
|||
# Minimalistic framework for automated testing in Nasal
|
||||
#
|
||||
# Copyright (C) 2014 Anton Gomez Alvedro
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
|
||||
# TestSuite
|
||||
#
|
||||
# Tests are organized in test suites. Each test suite contains an arbitrary
|
||||
# number of tests, and two special methods "setup" and "cleanup". Setup is
|
||||
# called before every test, and cleanup is called after every test.
|
||||
#
|
||||
# In order to define a test suite, you have to create an object parented to
|
||||
# TestSuite. The testing framework will identify any method in your object whose
|
||||
# name starts with "test_" as a test case to be run.
|
||||
#
|
||||
# Important: The order in which test cases and test suites are executed
|
||||
# is undefined!
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# var MyTestSuite = {
|
||||
#
|
||||
# parents: [TestSuite],
|
||||
#
|
||||
# setup: func {
|
||||
# Stuff to do before every test...
|
||||
# This is optional. You don't need to provide it if you don't use it.
|
||||
# },
|
||||
#
|
||||
# cleanup: func {
|
||||
# Stuff to do after every test...
|
||||
# Also optional.
|
||||
# },
|
||||
#
|
||||
# my_auxiliary_function: func {
|
||||
# Methods that do not start with "test_" will not be executed by the
|
||||
# test runner. You can define as many auxiliary functions in the test
|
||||
# suite as you wish.
|
||||
# },
|
||||
#
|
||||
# test_trivial_test: func {
|
||||
# This is a real test (starts with "test_"), and it will be run by the
|
||||
# framework when test_run() is called.#
|
||||
# }
|
||||
# };
|
||||
|
||||
var TestSuite = {
|
||||
setup: func 0,
|
||||
cleanup: func 0
|
||||
};
|
||||
|
||||
# run_tests([namespace])
|
||||
#
|
||||
# Executes all test suites found in the given namespace. If no namespace is
|
||||
# specified, then the namespace where run_tests is defined is used by default.
|
||||
#
|
||||
# An effective way to work with the framework is to just include the framework
|
||||
# from your test files:
|
||||
#
|
||||
# io.include(".../test.nas");
|
||||
#
|
||||
# and then execute a script like this in the Nasal Console:
|
||||
#
|
||||
# delete(globals, "test");
|
||||
# io.load_nasal(".../my_test_suite.nas", "test");
|
||||
# test.run_tests();
|
||||
#
|
||||
# What this script does is: it empties the "test" namespace and then loads your
|
||||
# script into that namespace. The test framework will be loaded in there as
|
||||
# well if it was io.include'd in my_test_suite.nas. Finally, all test suites
|
||||
# in the "test" namespace are executed.
|
||||
|
||||
var run_tests = func(namespace=nil) {
|
||||
|
||||
var ns = namespace != nil ? namespace : closure(run_tests, 1);
|
||||
|
||||
var passed = 0;
|
||||
var failed = 0;
|
||||
var err = [];
|
||||
|
||||
foreach(var suite_name; keys(ns)) {
|
||||
var suite = ns[suite_name];
|
||||
|
||||
if (!isa(suite, TestSuite))
|
||||
continue;
|
||||
|
||||
print("Running test suite ", suite_name);
|
||||
|
||||
foreach (var test_name; keys(suite)) {
|
||||
|
||||
if (find("test_", test_name) != 0)
|
||||
continue;
|
||||
|
||||
# Run the test case
|
||||
setsize(err, 0);
|
||||
contains(suite, "setup") and call(suite.setup, [], suite, err);
|
||||
size(err) == 0 and call(suite[test_name], [], suite, err);
|
||||
size(err) == 0 and contains(suite, "cleanup") and call(suite.cleanup, [], suite, err);
|
||||
|
||||
if (size(err) == 0) {
|
||||
passed += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
failed += 1;
|
||||
print("Test ", test_name, " FAILED\n");
|
||||
debug.printerror(err);
|
||||
}
|
||||
}
|
||||
|
||||
print(sprintf("\n%d tests run. %d passed, %d failed",
|
||||
passed + failed, passed, failed));
|
||||
}
|
||||
|
||||
|
||||
var assert_prop_exists = func (prop) {
|
||||
assert(props.globals.getNode(prop) != nil,
|
||||
sprintf("Property %s does not exist", prop));
|
||||
}
|
||||
|
||||
|
||||
var fail_if_prop_exists = func (prop) {
|
||||
assert(props.globals.getNode(prop) == nil,
|
||||
sprintf("Property %s exists", prop));
|
||||
}
|
210
Aircraft/Generic/Systems/compat_failure_modes.nas
Normal file
|
@ -0,0 +1,210 @@
|
|||
# Compatibility failure modes
|
||||
#
|
||||
# Loads FailureMgr with the failure modes that where previously hardcoded,
|
||||
# emulating former behavior and allowing backward compatibility.
|
||||
#
|
||||
# Copyright (C) 2014 Anton Gomez Alvedro
|
||||
# Based on previous work by Stuart Buchanan, Erobo & John Denker
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
io.include("Aircraft/Generic/Systems/failures.nas");
|
||||
|
||||
|
||||
MTBF = 0;
|
||||
MCBF = 1;
|
||||
|
||||
SERV = 0;
|
||||
JAM = 1;
|
||||
ENG = 2;
|
||||
|
||||
|
||||
var compat_modes = [
|
||||
# Instruments
|
||||
{ id: "instrumentation/adf", type: MTBF, failure: SERV, desc: "ADF" },
|
||||
{ id: "instrumentation/dme", type: MTBF, failure: SERV, desc: "DME" },
|
||||
{ id: "instrumentation/airspeed-indicator", type: MTBF, failure: SERV, desc: "ASI" },
|
||||
{ id: "instrumentation/altimeter", type: MTBF, failure: SERV, desc: "Altimeter" },
|
||||
{ id: "instrumentation/attitude-indicator", type: MTBF, failure: SERV, desc: "Attitude Indicator" },
|
||||
{ id: "instrumentation/heading-indicator", type: MTBF, failure: SERV, desc: "Heading Indicator" },
|
||||
{ id: "instrumentation/magnetic-compass", type: MTBF, failure: SERV, desc: "Magnetic Compass" },
|
||||
{ id: "instrumentation/nav/gs", type: MTBF, failure: SERV, desc: "Nav 1 Glideslope" },
|
||||
{ id: "instrumentation/nav/cdi", type: MTBF, failure: SERV, desc: "Nav 1 CDI" },
|
||||
{ id: "instrumentation/nav[1]/gs", type: MTBF, failure: SERV, desc: "Nav 2 Glideslope" },
|
||||
{ id: "instrumentation/nav[1]/cdi", type: MTBF, failure: SERV, desc: "Nav 2 CDI" },
|
||||
{ id: "instrumentation/slip-skid-ball", type: MTBF, failure: SERV, desc: "Slip/Skid Ball" },
|
||||
{ id: "instrumentation/turn-indicator", type: MTBF, failure: SERV, desc: "Turn Indicator" },
|
||||
{ id: "instrumentation/vertical-speed-indicator", type: MTBF, failure: SERV, desc: "VSI" },
|
||||
|
||||
# Systems
|
||||
{ id: "systems/electrical", type: MTBF, failure: SERV, desc: "Electrical system" },
|
||||
{ id: "systems/pitot", type: MTBF, failure: SERV, desc: "Pitot system" },
|
||||
{ id: "systems/static", type: MTBF, failure: SERV, desc: "Static system" },
|
||||
{ id: "systems/vacuum", type: MTBF, failure: SERV, desc: "Vacuum system" },
|
||||
|
||||
# Controls
|
||||
{ id: "controls/flight/aileron", type: MTBF, failure: JAM, desc: "Aileron" },
|
||||
{ id: "controls/flight/elevator", type: MTBF, failure: JAM, desc: "Elevator" },
|
||||
{ id: "controls/flight/rudder", type: MTBF, failure: JAM, desc: "Rudder" },
|
||||
{ id: "controls/flight/flaps", type: MCBF, failure: JAM, desc: "Flaps" },
|
||||
{ id: "controls/flight/speedbrake", type: MCBF, failure: JAM, desc: "Speed Brake" },
|
||||
{ id: "controls/gear", type: MCBF, failure: SERV, desc: "Gear", prop: "/gear", mcbf_prop: "/controls/gear/gear-down" }
|
||||
];
|
||||
|
||||
|
||||
##
|
||||
# Handles the old failures.nas property tree interface,
|
||||
# sending the appropriate commands to the new FailureMgr.
|
||||
|
||||
var compat_listener = func(prop) {
|
||||
|
||||
var new_trigger = func {
|
||||
if (name == "mtbf") {
|
||||
MtbfTrigger.new(value);
|
||||
}
|
||||
else {
|
||||
var control = id;
|
||||
|
||||
forindex(var i; compat_modes) {
|
||||
var mode = compat_modes[i];
|
||||
if (mode.id == id and contains(compat_modes[i], "mcbf_prop")) {
|
||||
control = mode.mcbf_prop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
McbfTrigger.new(control, value);
|
||||
}
|
||||
};
|
||||
|
||||
var name = prop.getName();
|
||||
var value = prop.getValue();
|
||||
var id = string.replace(io.dirname(prop.getPath()), FailureMgr.proproot, "");
|
||||
id = string.trim(id, 0, func(c) c == `/`);
|
||||
|
||||
if (name == "serviceable") {
|
||||
FailureMgr.set_failure_level(id, 1 - value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (name == "failure-level") {
|
||||
setprop(io.dirname(prop.getPath()) ~ "/serviceable", value ? 0 : 1);
|
||||
return;
|
||||
}
|
||||
|
||||
# mtbf and mcbf parameter handling
|
||||
var trigger = FailureMgr.get_trigger(id);
|
||||
|
||||
if (value == 0) {
|
||||
trigger != nil and FailureMgr.set_trigger(id, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
if (trigger == nil) {
|
||||
FailureMgr.set_trigger(id, new_trigger());
|
||||
}
|
||||
else {
|
||||
trigger.set_param(name, value);
|
||||
trigger.reset();
|
||||
}
|
||||
}
|
||||
|
||||
##
|
||||
# Called from the ramdom-failures dialog to set the global MCBF parameter
|
||||
|
||||
var apply_global_mcbf = func(value) {
|
||||
foreach (var mode; compat_modes) {
|
||||
mode.type != MCBF and continue;
|
||||
setprop(FailureMgr.proproot ~ mode.id ~ "/mcbf", value);
|
||||
}
|
||||
}
|
||||
|
||||
##
|
||||
# Called from the ramdom-failures dialog to set the global MTBF parameter
|
||||
|
||||
var apply_global_mtbf = func(value) {
|
||||
foreach (var mode; compat_modes) {
|
||||
mode.type != MTBF and continue;
|
||||
setprop(FailureMgr.proproot ~ mode.id ~ "/mtbf", value);
|
||||
}
|
||||
}
|
||||
|
||||
##
|
||||
# Discover aircraft engines dynamically and add a failure mode to the
|
||||
# compat_modes table for each engine.
|
||||
|
||||
var populate_engine_data = func {
|
||||
|
||||
var engines = props.globals.getNode("/engines");
|
||||
var engine_id = 0;
|
||||
|
||||
foreach (var e; engines.getChildren("engine")) {
|
||||
var starter = e.getChild("starter");
|
||||
var running = e.getChild("running");
|
||||
|
||||
(starter != nil and starter != "" and starter.getType() != "NONE")
|
||||
or (running != nil and running != "" and running.getType() != "NONE")
|
||||
or continue;
|
||||
|
||||
var id = "engines/engine";
|
||||
if (engine_id > 0)
|
||||
id = id ~ "[" ~ engine_id ~ "]";
|
||||
|
||||
var entry = {
|
||||
id: id,
|
||||
desc: "Engine " ~ (engine_id + 1),
|
||||
type: MTBF,
|
||||
failure: ENG
|
||||
};
|
||||
|
||||
append(compat_modes, entry);
|
||||
engine_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
##
|
||||
# Subscribes all failure modes that the old failures.nas module did,
|
||||
# and recreates the same property tree interface (more or less).
|
||||
|
||||
var compat_setup = func {
|
||||
|
||||
removelistener(lsnr);
|
||||
populate_engine_data();
|
||||
|
||||
foreach (var m; compat_modes) {
|
||||
var control_prop = contains(m, "prop") ? m.prop : m.id;
|
||||
|
||||
FailureMgr.add_failure_mode(
|
||||
id: m.id,
|
||||
description: m.desc,
|
||||
actuator: if (m.failure == SERV) set_unserviceable(control_prop)
|
||||
elsif (m.failure == JAM) set_readonly(control_prop)
|
||||
else fail_engine(io.basename(control_prop)));
|
||||
|
||||
# Recreate the prop tree interface
|
||||
var prop = FailureMgr.proproot ~ m.id;
|
||||
var n = props.globals.initNode(prop ~ "/serviceable", 1, "BOOL");
|
||||
|
||||
setlistener(n, compat_listener, 0, 0);
|
||||
setlistener(prop ~ "/failure-level", compat_listener, 0, 0);
|
||||
|
||||
var trigger_type = (m.type == MTBF) ? "/mtbf" : "/mcbf";
|
||||
setprop(prop ~ trigger_type, 0);
|
||||
setlistener(prop ~ trigger_type, compat_listener, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var lsnr = setlistener("sim/signals/fdm-initialized", compat_setup);
|
365
Aircraft/Generic/Systems/failures.nas
Normal file
|
@ -0,0 +1,365 @@
|
|||
# Failure simulation library
|
||||
#
|
||||
# Collection of generic Triggers and FailureActuators for programming the
|
||||
# FailureMgr Nasal module.
|
||||
#
|
||||
# Copyright (C) 2014 Anton Gomez Alvedro
|
||||
# Based on previous work by Stuart Buchanan, Erobo & John Denker
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
|
||||
#
|
||||
# Functions for generating FailureActuators
|
||||
# ------------------------------------------
|
||||
|
||||
##
|
||||
# Returns an actuator object that will set the serviceable property at
|
||||
# the given node to zero when the level of failure is > 0.
|
||||
|
||||
var set_unserviceable = func(path) {
|
||||
|
||||
var prop = path ~ "/serviceable";
|
||||
|
||||
if (props.globals.getNode(prop) == nil)
|
||||
props.globals.initNode(prop, 1, "BOOL");
|
||||
|
||||
return {
|
||||
parents: [FailureMgr.FailureActuator],
|
||||
set_failure_level: func(level) setprop(prop, level > 0 ? 0 : 1),
|
||||
get_failure_level: func { getprop(prop) ? 0 : 1 }
|
||||
}
|
||||
}
|
||||
|
||||
##
|
||||
# Returns an actuator object that will make the given property read only.
|
||||
# This prevents any other system from updating it, and effectively jamming
|
||||
# whatever it is that is controlling.
|
||||
|
||||
var set_readonly = func(property) {
|
||||
return {
|
||||
parents: [FailureMgr.FailureActuator],
|
||||
|
||||
set_failure_level: func(level) {
|
||||
var pnode = props.globals.getNode(property);
|
||||
pnode.setAttribute("writable", level > 0 ? 0 : 1);
|
||||
},
|
||||
|
||||
get_failure_level: func {
|
||||
var pnode = props.globals.getNode(property);
|
||||
pnode.getAttribute("writable") ? 0 : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
##
|
||||
# Returns an an actuator object the manipulates engine controls (magnetos &
|
||||
# cutoff) to simulate an engine failure. Sets these properties to read only
|
||||
# while the system is failed.
|
||||
|
||||
var fail_engine = func(engine) {
|
||||
return {
|
||||
parents: [FailureMgr.FailureActuator],
|
||||
level: 0,
|
||||
magnetos: props.globals.getNode("/controls/engines/" ~ engine ~ "/magnetos", 1),
|
||||
cutoff: props.globals.getNode("/controls/engines/" ~ engine ~ "/cutoff", 1),
|
||||
|
||||
get_failure_level: func me.level,
|
||||
|
||||
set_failure_level: func(level) {
|
||||
if (level) {
|
||||
# Switch off the engine, and disable writing to it.
|
||||
me.magnetos.setValue(0);
|
||||
me.magnetos.setAttribute("writable", 0);
|
||||
me.cutoff.setValue(1);
|
||||
me.cutoff.setAttribute("writable", 0);
|
||||
}
|
||||
else {
|
||||
# Enable the properties, but don't set the magnetos, as they may
|
||||
# be off for a reason.
|
||||
me.magnetos.setAttribute("writable", 1);
|
||||
me.cutoff.setAttribute("writable", 1);
|
||||
me.cutoff.setValue(0);
|
||||
}
|
||||
me.level = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Triggers
|
||||
# ---------
|
||||
|
||||
##
|
||||
# Returns a random number from a Normal distribution with given mean and
|
||||
# standard deviation.
|
||||
|
||||
var norm_rand = func(mean, std) {
|
||||
var r = -2 * math.ln(1 - rand());
|
||||
var a = 2 * math.pi * (1 - rand());
|
||||
return mean + (math.sqrt(r) * math.sin(a) * std);
|
||||
};
|
||||
|
||||
##
|
||||
# Trigger object that will fire when aircraft altitude is between
|
||||
# min and max, both specified in feet. One of min or max may be nil for
|
||||
# expressing "altitude > x" or "altitude < x" conditions.
|
||||
|
||||
var AltitudeTrigger = {
|
||||
|
||||
parents: [FailureMgr.Trigger],
|
||||
requires_polling: 1,
|
||||
|
||||
new: func(min, max) {
|
||||
min != nil or max != nil or
|
||||
die("AltitudeTrigger.new: either min or max must be specified");
|
||||
|
||||
var m = FailureMgr.Trigger.new();
|
||||
m.parents = [AltitudeTrigger];
|
||||
m.params["min-altitude-ft"] = min;
|
||||
m.params["max-altitude-ft"] = max;
|
||||
m._altitude_prop = "/position/altitude-ft";
|
||||
return m;
|
||||
},
|
||||
|
||||
to_str: func {
|
||||
# TODO: Handle min or max == nil
|
||||
sprintf("Altitude between %d and %d ft",
|
||||
int(me.params["min-altitude-ft"]), int(me.params["max-altitude-ft"]))
|
||||
},
|
||||
|
||||
update: func {
|
||||
var alt = getprop(me._altitude_prop);
|
||||
|
||||
var min = me.params["min-altitude-ft"];
|
||||
var max = me.params["max-altitude-ft"];
|
||||
|
||||
me.fired = min != nil ? min < alt : 1;
|
||||
me.fired = max != nil ? me.fired and alt < max : me.fired;
|
||||
}
|
||||
};
|
||||
|
||||
##
|
||||
# Trigger object that fires when the aircraft's position is within a certain
|
||||
# distance of a given waypoint.
|
||||
|
||||
var WaypointTrigger = {
|
||||
|
||||
parents: [FailureMgr.Trigger],
|
||||
requires_polling: 1,
|
||||
|
||||
new: func(lat, lon, distance) {
|
||||
var wp = geo.Coord.new();
|
||||
wp.set_latlon(lat, lon);
|
||||
|
||||
var m = FailureMgr.Trigger.new();
|
||||
m.parents = [WaypointTrigger];
|
||||
m.params["latitude-deg"] = lat;
|
||||
m.params["longitude-deg"] = lon;
|
||||
m.params["distance-nm"] = distance;
|
||||
m.waypoint = wp;
|
||||
return m;
|
||||
},
|
||||
|
||||
reset: func {
|
||||
call(FailureMgr.Trigger.reset, [], me);
|
||||
me.waypoint.set_latlon(me.params["latitude-deg"],
|
||||
me.params["longitude-deg"]);
|
||||
},
|
||||
|
||||
to_str: func {
|
||||
sprintf("Within %.2f miles of %s", me.params["distance-nm"],
|
||||
geo.format(me.waypoint.lat, me.waypoint.lon));
|
||||
},
|
||||
|
||||
update: func {
|
||||
var d = geo.aircraft_position().distance_to(me.waypoint) * M2NM;
|
||||
me.fired = d < me.params["distance-nm"];
|
||||
}
|
||||
};
|
||||
|
||||
##
|
||||
# Trigger object that will fire on average after the specified time.
|
||||
|
||||
var MtbfTrigger = {
|
||||
|
||||
parents: [FailureMgr.Trigger],
|
||||
# TODO: make this trigger async
|
||||
requires_polling: 1,
|
||||
|
||||
new: func(mtbf) {
|
||||
var m = FailureMgr.Trigger.new();
|
||||
m.parents = [MtbfTrigger];
|
||||
m.params["mtbf"] = mtbf;
|
||||
m.fire_time = 0;
|
||||
m._time_prop = "/sim/time/elapsed-sec";
|
||||
return m;
|
||||
},
|
||||
|
||||
reset: func {
|
||||
call(FailureMgr.Trigger.reset, [], me);
|
||||
# TODO: use an elapsed time prop that accounts for speed-up and pause
|
||||
me.fire_time = getprop(me._time_prop)
|
||||
+ norm_rand(me.params["mtbf"], me.params["mtbf"] / 10);
|
||||
},
|
||||
|
||||
to_str: func {
|
||||
sprintf("Mean time between failures: %f.1 mins", me.params["mtbf"] / 60);
|
||||
},
|
||||
|
||||
update: func {
|
||||
me.fired = getprop(me._time_prop) > me.fire_time;
|
||||
}
|
||||
};
|
||||
|
||||
##
|
||||
# Trigger object that will fire exactly after the given timeout.
|
||||
|
||||
var TimeoutTrigger = {
|
||||
|
||||
parents: [FailureMgr.Trigger],
|
||||
# TODO: make this trigger async
|
||||
requires_polling: 1,
|
||||
|
||||
new: func(timeout) {
|
||||
var m = FailureMgr.Trigger.new();
|
||||
m.parents = [TimeoutTrigger];
|
||||
m.params["timeout-sec"] = timeout;
|
||||
fire_time = 0;
|
||||
return m;
|
||||
},
|
||||
|
||||
reset: func {
|
||||
call(FailureMgr.Trigger.reset, [], me);
|
||||
# TODO: use an elapsed time prop that accounts for speed-up and pause
|
||||
me.fire_time = getprop("/sim/time/elapsed-sec")
|
||||
+ me.params["timeout-sec"];
|
||||
},
|
||||
|
||||
to_str: func {
|
||||
sprintf("Fixed delay: %d minutes", me.params["timeout-sec"] / 60);
|
||||
},
|
||||
|
||||
update: func {
|
||||
me.fired = getprop("/sim/time/elapsed-sec") > me.fire_time;
|
||||
}
|
||||
};
|
||||
|
||||
##
|
||||
# Simple approach to count usage cycles for a given property. Every time
|
||||
# the propery variation changes in direction, we count half a cycle.
|
||||
# If the property represents aileron angular position, for example, this
|
||||
# would count roughly the number of times the aileron has been actuated.
|
||||
|
||||
var CycleCounter = {
|
||||
|
||||
new: func(property, on_update = nil) {
|
||||
return {
|
||||
parents: [CycleCounter],
|
||||
cycles: 0,
|
||||
_property: property,
|
||||
_on_update: on_update,
|
||||
_prev_value: getprop(property),
|
||||
_prev_delta: 0,
|
||||
_lsnr: nil
|
||||
};
|
||||
},
|
||||
|
||||
enable: func {
|
||||
if (me._lsnr == nil)
|
||||
me._lsnr = setlistener(me._property, func (p) me._on_prop_change(p), 0, 0);
|
||||
},
|
||||
|
||||
disable: func {
|
||||
if (me._lsnr != nil) removelistener(me._lsnr);
|
||||
},
|
||||
|
||||
reset: func {
|
||||
me.cycles = 0;
|
||||
me._prev_value = getprop(me._property);
|
||||
me._prev_delta = 0;
|
||||
},
|
||||
|
||||
_on_prop_change: func(prop) {
|
||||
|
||||
# TODO: Implement a filter for avoiding spureous values.
|
||||
|
||||
var value = prop.getValue();
|
||||
var delta = value - me._prev_value;
|
||||
if (delta == 0) return;
|
||||
|
||||
if (delta * me._prev_delta < 0) {
|
||||
# Property variation has changed direction
|
||||
me.cycles += 0.5;
|
||||
if (me._on_update != nil) me._on_update(me.cycles);
|
||||
}
|
||||
|
||||
me._prev_delta = delta;
|
||||
me._prev_value = value;
|
||||
}
|
||||
};
|
||||
|
||||
##
|
||||
# Trigger object that will fire on average after a property has gone through
|
||||
# mcbf (mean cycles between failures) cycles.
|
||||
|
||||
var McbfTrigger = {
|
||||
|
||||
parents: [FailureMgr.Trigger],
|
||||
requires_polling: 0,
|
||||
|
||||
new: func(property, mcbf) {
|
||||
var m = FailureMgr.Trigger.new();
|
||||
m.parents = [McbfTrigger];
|
||||
m.params["mcbf"] = mcbf;
|
||||
m.counter = CycleCounter.new(property, func(c) call(m._on_cycle, [c], m));
|
||||
m.activation_cycles = 0;
|
||||
m.enabled = 0;
|
||||
return m;
|
||||
},
|
||||
|
||||
enable: func {
|
||||
me.counter.enable();
|
||||
me.enabled = 1;
|
||||
},
|
||||
|
||||
disable: func {
|
||||
me.counter.disable();
|
||||
me.enabled = 0;
|
||||
},
|
||||
|
||||
reset: func {
|
||||
call(FailureMgr.Trigger.reset, [], me);
|
||||
me.counter.reset();
|
||||
me.activation_cycles =
|
||||
norm_rand(me.params["mcbf"], me.params["mcbf"] / 10);
|
||||
|
||||
me.enabled and me.counter.enable();
|
||||
},
|
||||
|
||||
to_str: func {
|
||||
sprintf("Mean cycles between failures: %.2f", me.params["mcbf"]);
|
||||
},
|
||||
|
||||
_on_cycle: func(cycles) {
|
||||
if (!me.fired and cycles > me.activation_cycles) {
|
||||
# TODO: Why this doesn't work?
|
||||
# me.counter.disable();
|
||||
me.fired = 1;
|
||||
me.on_fire();
|
||||
}
|
||||
}
|
||||
};
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 66 KiB |
327
Nasal/FailureMgr/private.nas
Normal file
|
@ -0,0 +1,327 @@
|
|||
# Failure Manager implementation
|
||||
#
|
||||
# Monitors trigger conditions periodically and fires failure modes when those
|
||||
# conditions are met. It also provides a central access point for publishing
|
||||
# failure modes to the user interface and the property tree.
|
||||
#
|
||||
# Copyright (C) 2014 Anton Gomez Alvedro
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
|
||||
##
|
||||
# Represents one way things can go wrong, for example "a blown tire".
|
||||
|
||||
var FailureMode = {
|
||||
|
||||
##
|
||||
# id: Unique identifier for this failure mode.
|
||||
# eg: "engine/carburetor-ice"
|
||||
#
|
||||
# description: Short text description, suitable for printing to the user.
|
||||
# eg: "Ice in the carburetor"
|
||||
#
|
||||
# actuator: Object implementing the FailureActuator interface.
|
||||
# Used by the failure manager to apply a certain level of
|
||||
# failure to the failure mode.
|
||||
|
||||
new: func(id, description, actuator) {
|
||||
return {
|
||||
parents: [FailureMode],
|
||||
id: id,
|
||||
description: description,
|
||||
actuator: actuator,
|
||||
_path: nil
|
||||
};
|
||||
},
|
||||
|
||||
##
|
||||
# Applies a certain level of failure to this failure mode.
|
||||
# level: Floating point number in the range [0, 1] zero being no failure
|
||||
# and 1 total failure.
|
||||
|
||||
set_failure_level: func(level) {
|
||||
me._path != nil or
|
||||
die("FailureMode.set_failure_level: Unbound failure mode");
|
||||
|
||||
setprop(me._path ~ me.id ~ "/failure-level", level);
|
||||
},
|
||||
|
||||
##
|
||||
# Internal version that actually does the job.
|
||||
|
||||
_set_failure_level: func(level) {
|
||||
me.actuator.set_failure_level(level);
|
||||
me._log_failure(sprintf("%s failure level %d%%",
|
||||
me.description, level*100));
|
||||
},
|
||||
|
||||
##
|
||||
# Returns the level of failure currently being simulated.
|
||||
|
||||
get_failure_level: func me.actuator.get_failure_level(),
|
||||
|
||||
##
|
||||
# Creates an interface for this failure mode in the property tree at the
|
||||
# given location. Currently the interface is just:
|
||||
#
|
||||
# path/failure-level (double, rw)
|
||||
|
||||
bind: func(path) {
|
||||
me._path == nil or die("FailureMode.bind: mode already bound");
|
||||
|
||||
var prop = path ~ me.id ~ "/failure-level";
|
||||
props.globals.initNode(prop, me.actuator.get_failure_level(), "DOUBLE");
|
||||
setlistener(prop, func (p) me._set_failure_level(p.getValue()), 0, 0);
|
||||
me._path = path;
|
||||
},
|
||||
|
||||
##
|
||||
# Remove bound properties from the property tree.
|
||||
|
||||
unbind: func {
|
||||
me._path != nil and props.globals.getNode(me._path ~ me.id).remove();
|
||||
me._path = nil;
|
||||
},
|
||||
|
||||
##
|
||||
# Send a message to the logging facilities, currently the screen and
|
||||
# the console.
|
||||
|
||||
_log_failure: func(message) {
|
||||
print(getprop("/sim/time/gmt-string") ~ " : " ~ message);
|
||||
if (getprop(proproot ~ "/display-on-screen"))
|
||||
screen.log.write(message, 1.0, 0.0, 0.0);
|
||||
},
|
||||
};
|
||||
|
||||
##
|
||||
# Implements the FailureMgr functionality.
|
||||
#
|
||||
# It is wrapped into an object to leave the door open to several evolution
|
||||
# approaches, for example moving the implementation down to the C++ engine,
|
||||
# or supporting several independent instances of the failure manager.
|
||||
# Additionally, it also serves to isolate low level implementation details
|
||||
# into its own namespace.
|
||||
|
||||
var _failmgr = {
|
||||
|
||||
timer: nil,
|
||||
update_period: 10, # 0.1 Hz
|
||||
failure_modes: {},
|
||||
pollable_trigger_count: 0,
|
||||
|
||||
init: func {
|
||||
me.timer = maketimer(me.update_period, func me._update());
|
||||
setlistener("sim/signals/reinit", func me._on_reinit());
|
||||
|
||||
props.globals.initNode(proproot ~ "display-on-screen", 1, "BOOL");
|
||||
props.globals.initNode(proproot ~ "enabled", 1, "BOOL");
|
||||
setlistener(proproot ~ "enabled",
|
||||
func (n) { n.getValue() ? me._enable() : me._disable() });
|
||||
},
|
||||
|
||||
##
|
||||
# Subscribe a new failure mode to the system.
|
||||
# mode: FailureMode object.
|
||||
|
||||
add_failure_mode: func(mode) {
|
||||
contains(me.failure_modes, mode.id) and
|
||||
die("add_failure_mode: failure mode already exists: " ~ id);
|
||||
|
||||
me.failure_modes[mode.id] = { mode: mode, trigger: nil };
|
||||
mode.bind(proproot);
|
||||
},
|
||||
|
||||
##
|
||||
# Remove a failure mode from the system.
|
||||
# id: FailureMode id string, e.g. "systems/pitot"
|
||||
|
||||
remove_failure_mode: func(id) {
|
||||
contains(me.failure_modes, id) or
|
||||
die("remove_failure_mode: failure mode does not exist: " ~ mode_id);
|
||||
|
||||
var trigger = me.failure_modes[id].trigger;
|
||||
if (trigger != nil)
|
||||
me._discard_trigger(trigger);
|
||||
|
||||
me.failure_modes[id].unbind();
|
||||
props.globals.getNode(proproot ~ id).remove();
|
||||
delete(me.failure_modes, id);
|
||||
},
|
||||
|
||||
##
|
||||
# Removes all failure modes from the system.
|
||||
|
||||
remove_all: func {
|
||||
foreach(var id; keys(me.failure_modes))
|
||||
me.remove_failure_mode(id);
|
||||
},
|
||||
|
||||
##
|
||||
# Attach a trigger to the given failure mode. Discards the current trigger
|
||||
# if any.
|
||||
#
|
||||
# mode_id: FailureMode id string, e.g. "systems/pitot"
|
||||
# trigger: Trigger object or nil.
|
||||
|
||||
set_trigger: func(mode_id, trigger) {
|
||||
contains(me.failure_modes, mode_id) or
|
||||
die("set_trigger: failure mode does not exist: " ~ mode_id);
|
||||
|
||||
var mode = me.failure_modes[mode_id];
|
||||
|
||||
if (mode.trigger != nil)
|
||||
me._discard_trigger(mode.trigger);
|
||||
|
||||
mode.trigger = trigger;
|
||||
if (trigger == nil) return;
|
||||
|
||||
trigger.bind(proproot ~ mode_id);
|
||||
trigger.on_fire = func _failmgr.on_trigger_activated(trigger);
|
||||
trigger.reset();
|
||||
|
||||
if (trigger.requires_polling) {
|
||||
me.pollable_trigger_count += 1;
|
||||
|
||||
if (me.enabled() and !me.timer.isRunning)
|
||||
me.timer.start();
|
||||
}
|
||||
|
||||
trigger.enable();
|
||||
},
|
||||
|
||||
##
|
||||
# Returns the trigger object attached to the given failure mode.
|
||||
# mode_id: FailureMode id string, e.g. "systems/pitot"
|
||||
|
||||
get_trigger: func(mode_id) {
|
||||
contains(me.failure_modes, mode_id) or
|
||||
die("get_trigger: failure mode does not exist: " ~ mode_id);
|
||||
|
||||
return me.failure_modes[mode_id].trigger;
|
||||
},
|
||||
|
||||
##
|
||||
# Observer interface. Called from asynchronous triggers when they fire.
|
||||
# trigger: Reference to the calling trigger.
|
||||
|
||||
on_trigger_activated: func(trigger) {
|
||||
var found = 0;
|
||||
|
||||
foreach (var id; keys(me.failure_modes)) {
|
||||
if (me.failure_modes[id].trigger == trigger) {
|
||||
me.failure_modes[id].mode.set_failure_level(1);
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
found or die("FailureMgr.on_trigger_activated: trigger not found");
|
||||
},
|
||||
|
||||
##
|
||||
# Enable the failure manager.
|
||||
|
||||
_enable: func {
|
||||
foreach(var id; keys(me.failure_modes)) {
|
||||
var trigger = me.failure_modes[id].trigger;
|
||||
trigger != nil and trigger.enable();
|
||||
}
|
||||
|
||||
if (me.pollable_trigger_count > 0)
|
||||
me.timer.start();
|
||||
},
|
||||
|
||||
##
|
||||
# Suspends failure manager activity. Pollable triggers will not be updated
|
||||
# and all triggers will be disabled.
|
||||
|
||||
_disable: func {
|
||||
me.timer.stop();
|
||||
|
||||
foreach(var id; keys(me.failure_modes)) {
|
||||
var trigger = me.failure_modes[id].trigger;
|
||||
trigger != nil and trigger.disable();
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
##
|
||||
# Returns enabled status.
|
||||
|
||||
enabled: func {
|
||||
getprop(proproot ~ "enabled");
|
||||
},
|
||||
|
||||
##
|
||||
# Poll loop. Updates pollable triggers and applies a failure level
|
||||
# when they fire.
|
||||
|
||||
_update: func {
|
||||
foreach (var id; keys(me.failure_modes)) {
|
||||
var failure = me.failure_modes[id];
|
||||
|
||||
if (failure.trigger != nil and !failure.trigger.fired) {
|
||||
var level = failure.trigger.update();
|
||||
if (level > 0 and level != failure.mode.get_failure_level())
|
||||
failure.mode.set_failure_level(level);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
##
|
||||
# Detaches a trigger from the system.
|
||||
|
||||
_discard_trigger: func(trigger) {
|
||||
trigger.disable();
|
||||
trigger.unbind();
|
||||
|
||||
if (trigger.requires_polling) {
|
||||
me.pollable_trigger_count -= 1;
|
||||
me.pollable_trigger_count == 0 and me.timer.stop();
|
||||
}
|
||||
},
|
||||
|
||||
##
|
||||
# Reinit listener. Sets all failure modes to "working fine".
|
||||
|
||||
_on_reinit: func {
|
||||
foreach (var id; keys(me.failure_modes)) {
|
||||
var failure = me.failure_modes[id];
|
||||
|
||||
failure.mode.set_failure_level(0);
|
||||
|
||||
if (failure.trigger != nil) {
|
||||
me._discard_trigger(failure.trigger);
|
||||
failure.trigger = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
##
|
||||
# Module initialization
|
||||
|
||||
var _init = func {
|
||||
removelistener(lsnr);
|
||||
_failmgr.init();
|
||||
|
||||
# Load legacy failure modes for backwards compatibility
|
||||
io.load_nasal(getprop("/sim/fg-root") ~
|
||||
"/Aircraft/Generic/Systems/compat_failure_modes.nas");
|
||||
}
|
||||
|
||||
var lsnr = setlistener("/nasal/FailureMgr/loaded", _init);
|
229
Nasal/FailureMgr/public.nas
Normal file
|
@ -0,0 +1,229 @@
|
|||
# Failure Manager public interface
|
||||
#
|
||||
# Copyright (C) 2014 Anton Gomez Alvedro
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
|
||||
var proproot = "sim/failure-manager/";
|
||||
|
||||
|
||||
##
|
||||
# Subscribe a new failure mode to the system.
|
||||
#
|
||||
# id: Unique identifier for this failure mode.
|
||||
# eg: "engine/carburetor-ice"
|
||||
#
|
||||
# description: Short text description, suitable for printing to the user.
|
||||
# eg: "Ice in the carburetor"
|
||||
#
|
||||
# actuator: Object implementing the FailureActuator interface.
|
||||
# Used by the failure manager to apply a certain level of
|
||||
# failure to the failure mode.
|
||||
|
||||
var add_failure_mode = func(id, description, actuator) {
|
||||
_failmgr.add_failure_mode(
|
||||
FailureMode.new(id, description, actuator));
|
||||
}
|
||||
|
||||
##
|
||||
# Remove a failure mode from the system.
|
||||
# id: FailureMode id string, e.g. "systems/pitot"
|
||||
|
||||
var remove_failure_mode = func(id) {
|
||||
_failmgr.remove_failure_mode(id);
|
||||
}
|
||||
|
||||
##
|
||||
# Removes all failure modes from the failure manager.
|
||||
|
||||
var remove_all = func {
|
||||
_failmgr.remove_all();
|
||||
}
|
||||
|
||||
##
|
||||
# Attaches a trigger to the given failure mode. Discards the current trigger
|
||||
# if any.
|
||||
#
|
||||
# mode_id: FailureMode id string, e.g. "systems/pitot"
|
||||
# trigger: Trigger object or nil. Nil will just detach the current trigger
|
||||
|
||||
var set_trigger = func(mode_id, trigger) {
|
||||
_failmgr.set_trigger(mode_id, trigger);
|
||||
}
|
||||
|
||||
##
|
||||
# Returns the trigger object attached to the given failure mode.
|
||||
# mode_id: FailureMode id string, e.g. "systems/pitot"
|
||||
|
||||
var get_trigger = func(mode_id) {
|
||||
_failmgr.get_trigger(mode_id);
|
||||
}
|
||||
|
||||
##
|
||||
# Applies a certain level of failure to this failure mode.
|
||||
#
|
||||
# mode_id: Failure mode id string.
|
||||
# level: Floating point number in the range [0, 1]
|
||||
# Zero represents no failure and one means total failure.
|
||||
|
||||
var set_failure_level = func (mode_id, level) {
|
||||
setprop(proproot ~ mode_id ~ "/failure-level", level);
|
||||
}
|
||||
|
||||
##
|
||||
# Allows applications to disable the failure manager and restore it later on.
|
||||
# While disabled, no failure modes will be activated from the failure manager.
|
||||
|
||||
var enable = func setprop(proproot ~ "enabled", 1);
|
||||
var disable = func setprop(proproot ~ "enabled", 0);
|
||||
|
||||
##
|
||||
# Encapsulates a condition that when met, will make the failure manager to
|
||||
# apply a certain level of failure to the failure mode it is bound to.
|
||||
#
|
||||
# Two types of triggers are supported: pollable and asynchronous.
|
||||
#
|
||||
# Pollable triggers require periodic check for trigger conditions. For example,
|
||||
# an altitude trigger will need to poll current altitude until the fire
|
||||
# condition is reached.
|
||||
#
|
||||
# Asynchronous trigger do not require periodic updates. They can detect
|
||||
# the firing condition by themselves by using timers or listeners.
|
||||
# Async triggers must call the inherited method on_fire() to let the Failure
|
||||
# Manager know about the fired condition.
|
||||
#
|
||||
# See Aircraft/Generic/Systems/failures.nas for concrete examples of triggers.
|
||||
|
||||
var Trigger = {
|
||||
|
||||
# 1 for pollable triggers, 0 for async triggers.
|
||||
requires_polling: 0,
|
||||
|
||||
new: func {
|
||||
return {
|
||||
parents: [Trigger],
|
||||
params: {},
|
||||
fired: 0,
|
||||
|
||||
##
|
||||
# Async triggers shall call the on_fire() callback when their fire
|
||||
# conditions are met to notify the failure manager.
|
||||
on_fire: func 0,
|
||||
|
||||
_path: nil
|
||||
};
|
||||
},
|
||||
|
||||
##
|
||||
# Enables/disables the trigger. While a trigger is disabled, any timer
|
||||
# or listener that could potentially own shall be disabled.
|
||||
|
||||
enable: func,
|
||||
disable: func,
|
||||
|
||||
##
|
||||
# Forces a check of the firing conditions. Returns 1 if the trigger fired,
|
||||
# 0 otherwise.
|
||||
|
||||
update: func 0,
|
||||
|
||||
##
|
||||
# Returns a printable string describing the trigger condition.
|
||||
|
||||
to_str: func "undefined trigger",
|
||||
|
||||
##
|
||||
# Modify a trigger parameter. Parameters will take effect after the next
|
||||
# call to reset()
|
||||
|
||||
set_param: func(param, value) {
|
||||
contains(me.params, param) or
|
||||
die("Trigger.set_param: undefined param: " ~ param);
|
||||
|
||||
me._path != nil or
|
||||
die("Trigger.set_param: Unbound trigger");
|
||||
|
||||
setprop(sprintf("%s/%s",me._path, param), value);
|
||||
},
|
||||
|
||||
##
|
||||
# Reload trigger parameters and reset internal state, i.e. start from
|
||||
# scratch. If the trigger was fired, the trigger is set to not fired.
|
||||
|
||||
reset: func {
|
||||
me._path or die("Trigger.reset: unbound trigger");
|
||||
|
||||
foreach (var p; keys(me.params))
|
||||
me.params[p] = getprop(sprintf("%s/%s", me._path, p));
|
||||
|
||||
me.fired = 0;
|
||||
me._path != nil and setprop(me._path ~ "/reset", 0);
|
||||
},
|
||||
|
||||
##
|
||||
# Creates an interface for the trigger in the property tree.
|
||||
# Every parameter in the params hash will be exposed, in addition to
|
||||
# a path/reset property for resetting the trigger from the prop tree.
|
||||
|
||||
bind: func(path) {
|
||||
me._path == nil or
|
||||
die("Trigger.bind(): attempt to bind an already bound trigger");
|
||||
|
||||
me._path = path;
|
||||
props.globals.getNode(path) != nil or props.globals.initNode(path);
|
||||
props.globals.getNode(path).setValues(me.params);
|
||||
|
||||
var reset_prop = path ~ "/reset";
|
||||
props.globals.initNode(reset_prop, 0, "BOOL");
|
||||
setlistener(reset_prop, func me.reset(), 0, 0);
|
||||
},
|
||||
|
||||
##
|
||||
# Removes this trigger's interface from the property tree.
|
||||
|
||||
unbind: func {
|
||||
props.globals.getNode(me._path ~ "/reset").remove();
|
||||
foreach (var p; keys(me.params))
|
||||
props.globals.getNode(me._path ~ "/" ~ p).remove();
|
||||
|
||||
me._path = nil;
|
||||
}
|
||||
};
|
||||
|
||||
##
|
||||
# FailureActuators encapsulate the actions required for activating the actual
|
||||
# failure simulation.
|
||||
#
|
||||
# Traditionally this action was just manipulating a "serviceable" property
|
||||
# somewhere, but the FailureActuator gives you more flexibility, allowing you
|
||||
# to touch several properties at once or call other Nasal scripts, for example.
|
||||
#
|
||||
# See Aircraft/Generic/Systems/failure.nas and
|
||||
# Aircraft/Generic/Systems/compat_failures.nas for some examples of actuators.
|
||||
|
||||
var FailureActuator = {
|
||||
|
||||
##
|
||||
# Called from the failure manager to activate a certain level of failure.
|
||||
# level: Target level of failure [0 to 1].
|
||||
|
||||
set_failure_level: func(level) 0,
|
||||
|
||||
##
|
||||
# Returns the level of failure that is currently being simulated.
|
||||
|
||||
get_failure_level: func 0,
|
||||
};
|
|
@ -1,115 +0,0 @@
|
|||
#############################################################################
|
||||
#
|
||||
# Simple sequenced ATC background chatter function
|
||||
#
|
||||
# Written by Curtis Olson
|
||||
# Started 8 Jan 2006.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
#############################################################################
|
||||
# Global shared variables
|
||||
#############################################################################
|
||||
|
||||
var fg_root = nil;
|
||||
var chatter = "UK";
|
||||
var chatter_dir = "";
|
||||
|
||||
var chatter_min_interval = 20.0;
|
||||
var chatter_max_interval = 40.0;
|
||||
var next_interval = nil;
|
||||
|
||||
var chatter_index = 0;
|
||||
var chatter_size = 0;
|
||||
var chatter_list = 0;
|
||||
|
||||
|
||||
#############################################################################
|
||||
# Chatter is initialized only when actually enabled. See listener connected
|
||||
# to /sim/sound/chatter/enabled.
|
||||
#############################################################################
|
||||
|
||||
var chatter_init = func {
|
||||
# default values
|
||||
fg_root = getprop("/sim/fg-root");
|
||||
chatter_dir = sprintf("%s/ATC/Chatter/%s", fg_root, chatter);
|
||||
chatter_list = directory( chatter_dir );
|
||||
chatter_size = size(chatter_list);
|
||||
# seed the random number generator (with time) so we don't start in
|
||||
# same place in the sequence each run.
|
||||
srand();
|
||||
chatter_index = int( chatter_size * rand() );
|
||||
}
|
||||
|
||||
|
||||
#############################################################################
|
||||
# main update function to be called each frame
|
||||
#############################################################################
|
||||
|
||||
var chatter_update = func {
|
||||
if ( chatter_index >= chatter_size ) {
|
||||
chatter_index = 0;
|
||||
}
|
||||
|
||||
if ( substr(chatter_list[chatter_index],
|
||||
size(chatter_list[chatter_index]) - 4) == ".wav" )
|
||||
{
|
||||
var vol =getprop("/sim/sound/chatter/volume");
|
||||
if(vol == nil){vol = 0.5;}
|
||||
tmpl = { path : chatter_dir, file : chatter_list[chatter_index] , volume : vol};
|
||||
if ( getprop("/sim/sound/chatter/enabled") ) {
|
||||
# go through the motions, but only schedule the message to play
|
||||
# if atc-chatter is enabled.
|
||||
printlog("info", "update atc chatter ", chatter_list[chatter_index] );
|
||||
fgcommand("play-audio-sample", props.Node.new(tmpl) );
|
||||
}
|
||||
} else {
|
||||
# skip non-wav file found in directory
|
||||
}
|
||||
|
||||
chatter_index = chatter_index + 1;
|
||||
nextChatter();
|
||||
}
|
||||
|
||||
|
||||
#############################################################################
|
||||
# Use the nasal timer to update every 10 seconds
|
||||
#############################################################################
|
||||
|
||||
var nextChatter = func {
|
||||
if (!getprop("/sim/sound/chatter/enabled"))
|
||||
{
|
||||
next_interval = nil;
|
||||
return;
|
||||
}
|
||||
|
||||
# schedule next message in next min-max interval seconds so we have a bit
|
||||
# of a random pacing
|
||||
next_interval = chatter_min_interval
|
||||
+ int(rand() * (chatter_max_interval - chatter_min_interval));
|
||||
|
||||
# printlog("info", "next chatter in ", next_interval, " seconds");
|
||||
|
||||
settimer(chatter_update, next_interval );
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# Start chatter processing. Also connected to chatter/enabled property as a
|
||||
# listener.
|
||||
#############################################################################
|
||||
|
||||
var startChatter = func {
|
||||
if ( getprop("/sim/sound/chatter/enabled") ) {
|
||||
if (fg_root == nil)
|
||||
chatter_init();
|
||||
if (next_interval == nil)
|
||||
nextChatter();
|
||||
}
|
||||
}
|
||||
|
||||
# connect listener
|
||||
_setlistener("/sim/sound/chatter/enabled", startChatter);
|
||||
|
||||
# start chatter immediately, if enable is already set.
|
||||
settimer(startChatter, 0);
|
||||
|
|
@ -65,5 +65,13 @@ var PropertyElement = {
|
|||
{
|
||||
me._node.getNode(key, 1).getBoolValue();
|
||||
},
|
||||
|
||||
# Trigger an update of the element
|
||||
#
|
||||
# Elements are automatically updated once a frame, with a delay of one frame.
|
||||
# If you wan't to get an element updated in the current frame you have to use
|
||||
# this method.
|
||||
update: func
|
||||
{
|
||||
me.setBool("update", 1);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -161,6 +161,11 @@ var Element = {
|
|||
|
||||
return factory(parent_ghost);
|
||||
},
|
||||
# Get the canvas this element is placed on
|
||||
getCanvas: func()
|
||||
{
|
||||
wrapCanvas(me._getCanvas());
|
||||
},
|
||||
# Check if elements represent same instance
|
||||
#
|
||||
# @param el Other Element or element ghost
|
||||
|
@ -168,15 +173,6 @@ var Element = {
|
|||
{
|
||||
return me._node.equals(el._node_ghost);
|
||||
},
|
||||
# Trigger an update of the element
|
||||
#
|
||||
# Elements are automatically updated once a frame, with a delay of one frame.
|
||||
# If you wan't to get an element updated in the current frame you have to use
|
||||
# this method.
|
||||
update: func()
|
||||
{
|
||||
me.setInt("update", 1);
|
||||
},
|
||||
# Hide/Show element
|
||||
#
|
||||
# @param visible Whether the element should be visible
|
||||
|
@ -416,6 +412,7 @@ var Group = {
|
|||
# ==============================================================================
|
||||
# Class for a group element on a canvas with possibly geopgraphic positions
|
||||
# which automatically get projected according to the specified projection.
|
||||
# Each map consists of an arbitrary number of layers (canvas groups)
|
||||
#
|
||||
var Map = {
|
||||
df_controller: nil,
|
||||
|
@ -436,7 +433,7 @@ var Map = {
|
|||
me.parents = subvec(me.parents,1);
|
||||
me.del();
|
||||
},
|
||||
setController: func(controller=nil)
|
||||
setController: func(controller=nil, arg...)
|
||||
{
|
||||
if (me.controller != nil) me.controller.del(me);
|
||||
if (controller == nil)
|
||||
|
@ -449,7 +446,7 @@ var Map = {
|
|||
} else {
|
||||
if (!isa(controller, Map.Controller))
|
||||
die("OOP error: controller needs to inherit from Map.Controller");
|
||||
me.controller = call(func controller.new(me), nil, var err=[]); # try...
|
||||
me.controller = call(controller.new, [me]~arg, controller, var err=[]); # try...
|
||||
if (size(err)) {
|
||||
if (err[0] != "No such member: new") # ... and either catch or rethrow
|
||||
die(err[0]);
|
||||
|
@ -463,25 +460,29 @@ var Map = {
|
|||
|
||||
return me;
|
||||
},
|
||||
addLayer: func(factory, type_arg=nil, priority=nil, style=nil, options=nil)
|
||||
addLayer: func(factory, type_arg=nil, priority=nil, style=nil, options=nil, visible=1)
|
||||
{
|
||||
if(contains(me.layers, type_arg))
|
||||
printlog("warn", "addLayer() warning: overwriting existing layer:", type_arg);
|
||||
|
||||
# print("addLayer():", type_arg);
|
||||
|
||||
# Argument handling
|
||||
if (type_arg != nil)
|
||||
if (type_arg != nil) {
|
||||
var layer = factory.new(type:type_arg, group:me, map:me, style:style, options:options, visible:visible);
|
||||
var type = factory.get(type_arg);
|
||||
else var type = factory;
|
||||
var key = type_arg;
|
||||
} else {
|
||||
var layer = factory.new(group:me, map:me, style:style, options:options, visible:visible);
|
||||
var type = factory;
|
||||
var key = factory.type;
|
||||
}
|
||||
me.layers[type_arg] = layer;
|
||||
|
||||
me.layers[type_arg] = type.new(group:me, map:me, style:style,options:options);
|
||||
if (priority == nil)
|
||||
priority = type.df_priority;
|
||||
if (priority != nil)
|
||||
me.layers[type_arg].group.setInt("z-index", priority);
|
||||
layer.group.setInt("z-index", priority);
|
||||
|
||||
return me;
|
||||
return layer; # return new layer to caller() so that we can directly work with it, i.e. to register event handlers (panning/zooming)
|
||||
},
|
||||
getLayer: func(type_arg) me.layers[type_arg],
|
||||
|
||||
|
@ -490,6 +491,7 @@ var Map = {
|
|||
|
||||
setPos: func(lat, lon, hdg=nil, range=nil, alt=nil)
|
||||
{
|
||||
# TODO: also propage setPos events to layers and symbols (e.g. for offset maps)
|
||||
me.set("ref-lat", lat);
|
||||
me.set("ref-lon", lon);
|
||||
if (hdg != nil)
|
||||
|
@ -497,7 +499,7 @@ var Map = {
|
|||
if (range != nil)
|
||||
me.setRange(range);
|
||||
if (alt != nil)
|
||||
me.set("altitude", hdg);
|
||||
me.set("altitude", alt);
|
||||
},
|
||||
getPos: func
|
||||
{
|
||||
|
@ -513,6 +515,10 @@ var Map = {
|
|||
getAlt: func me.get("altitude"),
|
||||
getRange: func me.get("range"),
|
||||
getLatLon: func [me.get("ref-lat"), me.get("ref-lon")],
|
||||
# N.B.: This always returns the same geo.Coord object,
|
||||
# so its values can and will change at any time (call
|
||||
# update() on the coord to ensure it is up-to-date,
|
||||
# which basically calls this method again).
|
||||
getPosCoord: func
|
||||
{
|
||||
var (lat, lon) = (me.get("ref-lat"),
|
||||
|
@ -526,6 +532,8 @@ var Map = {
|
|||
}
|
||||
if (!contains(me, "coord")) {
|
||||
me.coord = geo.Coord.new();
|
||||
var m = me;
|
||||
me.coord.update = func m.getPosCoord();
|
||||
}
|
||||
me.coord.set_latlon(lat,lon,alt or 0);
|
||||
return me.coord;
|
||||
|
@ -534,12 +542,14 @@ var Map = {
|
|||
# me.controller.
|
||||
update: func(predicate=nil)
|
||||
{
|
||||
var t = systime();
|
||||
foreach (var l; keys(me.layers)) {
|
||||
var layer = me.layers[l];
|
||||
# Only update if the predicate allows
|
||||
if (predicate == nil or predicate(layer))
|
||||
call(layer.update, arg, layer);
|
||||
layer.update();
|
||||
}
|
||||
printlog(_MP_dbg_lvl, "Took "~((systime()-t)*1000)~"ms to update map()");
|
||||
return me;
|
||||
},
|
||||
};
|
||||
|
@ -745,6 +755,24 @@ var Path = {
|
|||
|
||||
return me;
|
||||
},
|
||||
addSegmentGeo: func(cmd, coords...)
|
||||
{
|
||||
var coords = _arg2valarray(coords);
|
||||
var num_coords = me.num_coords[cmd];
|
||||
if( size(coords) != num_coords )
|
||||
debug.warn
|
||||
(
|
||||
"Invalid number of arguments (expected " ~ num_coords ~ ")"
|
||||
);
|
||||
else
|
||||
{
|
||||
me.setInt("cmd[" ~ (me._last_cmd += 1) ~ "]", cmd);
|
||||
for(var i = 0; i < num_coords; i += 1)
|
||||
me.set("coord-geo[" ~ (me._last_coord += 1) ~ "]", coords[i]);
|
||||
}
|
||||
|
||||
return me;
|
||||
},
|
||||
# Remove first segment
|
||||
pop_front: func me._removeSegment(1),
|
||||
# Remove last segment
|
||||
|
@ -981,8 +1009,22 @@ var Image = {
|
|||
# @param bottom Rectangle maximum y coordinate
|
||||
# @param normalized Whether to use normalized ([0,1]) or image
|
||||
# ([0, image_width]/[0, image_height]) coordinates
|
||||
setSourceRect: func(left, top, right, bottom, normalized = 1)
|
||||
setSourceRect: func
|
||||
{
|
||||
# Work with both positional arguments and named arguments.
|
||||
# Support first argument being a vector instead of four separate ones.
|
||||
if (size(arg) == 1)
|
||||
arg = arg[0];
|
||||
elsif (size(arg) and size(arg) < 4 and typeof(arg[0]) == 'vector')
|
||||
arg = arg[0]~arg[1:];
|
||||
if (!contains(caller(0)[0], "normalized")) {
|
||||
if (size(arg) > 4)
|
||||
var normalized = arg[4];
|
||||
else var normalized = 1;
|
||||
}
|
||||
if (size(arg) >= 3)
|
||||
var (left,top,right,bottom) = arg;
|
||||
|
||||
me._node.getNode("source", 1).setValues({
|
||||
left: left,
|
||||
top: top,
|
||||
|
@ -1033,7 +1075,7 @@ var Canvas = {
|
|||
# given every texture of the model will be replaced.
|
||||
addPlacement: func(vals)
|
||||
{
|
||||
var placement = me.texture.addChild("placement", 0, 0);
|
||||
var placement = me._node.addChild("placement", 0, 0);
|
||||
placement.setValues(vals);
|
||||
return placement;
|
||||
},
|
||||
|
@ -1052,31 +1094,32 @@ var Canvas = {
|
|||
# Set the background color
|
||||
#
|
||||
# @param color Vector of 3 or 4 values in [0, 1]
|
||||
setColorBackground: func () { me.texture.getNode('background', 1).setValue(_getColor(arg)); me; },
|
||||
getColorBackground: func me.texture.get('background'),
|
||||
setColorBackground: func me.set('background', _getColor(arg)),
|
||||
getColorBackground: func me.get('background'),
|
||||
# Get path of canvas to be used eg. in Image::setFile
|
||||
getPath: func()
|
||||
{
|
||||
return "canvas://by-index/texture[" ~ me.texture.getIndex() ~ "]";
|
||||
return "canvas://by-index/texture[" ~ me._node.getIndex() ~ "]";
|
||||
},
|
||||
# Destructor
|
||||
#
|
||||
# releases associated canvas and makes this object unusable
|
||||
del: func
|
||||
{
|
||||
me.texture.remove();
|
||||
me._node.remove();
|
||||
me.parents = nil; # ensure all ghosts get destroyed
|
||||
}
|
||||
};
|
||||
|
||||
var wrapCanvas = func(canvas_ghost)
|
||||
# @param g Canvas ghost
|
||||
var wrapCanvas = func(g)
|
||||
{
|
||||
var m = {
|
||||
parents: [PropertyElement, Canvas, canvas_ghost],
|
||||
texture: props.wrapNode(canvas_ghost._node_ghost)
|
||||
if( g != nil and g._impl == nil )
|
||||
g._impl = {
|
||||
parents: [PropertyElement, Canvas],
|
||||
_node: props.wrapNode(g._node_ghost)
|
||||
};
|
||||
m._node = m.texture;
|
||||
return m;
|
||||
return g;
|
||||
}
|
||||
|
||||
# Create a new canvas. Pass parameters as hash, eg:
|
||||
|
@ -1090,7 +1133,7 @@ var wrapCanvas = func(canvas_ghost)
|
|||
var new = func(vals)
|
||||
{
|
||||
var m = wrapCanvas(_newCanvasGhost());
|
||||
m.texture.setValues(vals);
|
||||
m._node.setValues(vals);
|
||||
return m;
|
||||
};
|
||||
|
||||
|
@ -1108,11 +1151,7 @@ var get = func(arg)
|
|||
else
|
||||
die("canvas.new: Invalid argument.");
|
||||
|
||||
var canvas_ghost = _getCanvasGhost(node._g);
|
||||
if( canvas_ghost == nil )
|
||||
return nil;
|
||||
|
||||
return wrapCanvas(canvas_ghost);
|
||||
return wrapCanvas(_getCanvasGhost(node._g));
|
||||
};
|
||||
|
||||
var getDesktop = func()
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
Nothing set in stone yet, we'll document things once the API becomes more stable and once it has been used in several dialogs and instruments
|
||||
|
||||
At the moment, this implements the notion of a "LayeredMap", a LayeredMap is a conventional Canvas Map which has support for easily managing "Layers",
|
||||
which are internally mapped to Canvas Groups. Each Group's "visible" property is managed by the LayeredMap, so that layers can be easily
|
||||
toggled on/off, i.e. via checkboxes.
|
||||
|
||||
Basically, the idea is this, we'll have a MVC (Model/View/Controller) setup, where:
|
||||
- the Model is mapped to the drawable's meta information (i.e. position)
|
||||
- the View is mapped to a conventional canvas group
|
||||
- the Controller is mapped to a bunch of property/timer callbacks to control the Model/View
|
||||
|
||||
|
||||
|
||||
|
||||
Model = PositionedSource
|
||||
View = Canvas
|
||||
Controller = control properties (zoom, range etc)
|
||||
|
||||
LayerElement = callback to create a canvas group
|
||||
Layer = canvas.Group
|
||||
|
||||
Map -> LayeredMap -> GenericMap -> AirportMap
|
||||
|
|
@ -7,12 +7,14 @@ var gui = {
|
|||
var gui_dir = getprop("/sim/fg-root") ~ "/Nasal/canvas/gui/";
|
||||
var loadGUIFile = func(file) io.load_nasal(gui_dir ~ file, "canvas");
|
||||
var loadWidget = func(name) loadGUIFile("widgets/" ~ name ~ ".nas");
|
||||
var loadDialog = func(name) loadGUIFile("dialogs/" ~ name ~ ".nas");
|
||||
|
||||
loadGUIFile("Config.nas");
|
||||
loadGUIFile("Style.nas");
|
||||
loadGUIFile("Widget.nas");
|
||||
loadGUIFile("styles/DefaultStyle.nas");
|
||||
loadWidget("Button");
|
||||
loadWidget("Label");
|
||||
loadWidget("ScrollArea");
|
||||
|
||||
var style = DefaultStyle.new("AmbianceClassic");
|
||||
|
@ -24,23 +26,24 @@ var WindowButton = {
|
|||
_name: name
|
||||
};
|
||||
m._focus_policy = m.NoFocus;
|
||||
m._setRoot( parent.createChild("image", "WindowButton-" ~ name) );
|
||||
m._setView({_root: parent.createChild("image", "WindowButton-" ~ name)});
|
||||
return m;
|
||||
},
|
||||
# protected:
|
||||
_onStateChange: func
|
||||
{
|
||||
var file = style._dir_decoration ~ "/" ~ me._name;
|
||||
file ~= me._window._focused ? "_focused" : "_unfocused";
|
||||
var window_focus = me._windowFocus();
|
||||
file ~= window_focus ? "_focused" : "_unfocused";
|
||||
|
||||
if( me._active )
|
||||
if( me._down )
|
||||
file ~= "_pressed";
|
||||
else if( me._hover )
|
||||
file ~= "_prelight";
|
||||
else if( me._window._focused )
|
||||
else if( window_focus )
|
||||
file ~= "_normal";
|
||||
|
||||
me._root.set("src", file ~ ".png");
|
||||
me._view._root.set("src", file ~ ".png");
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -53,6 +56,7 @@ var Window = {
|
|||
var ghost = _newWindowGhost(id);
|
||||
var m = {
|
||||
parents: [Window, PropertyElement, ghost],
|
||||
_ghost: ghost,
|
||||
_node: props.wrapNode(ghost._node_ghost),
|
||||
_focused: 0,
|
||||
_focused_widget: nil,
|
||||
|
@ -80,7 +84,7 @@ var Window = {
|
|||
|
||||
if( me["_canvas"] != nil )
|
||||
{
|
||||
var placements = me._canvas.texture.getChildren("placement");
|
||||
var placements = me._canvas._node.getChildren("placement");
|
||||
# Do not remove canvas if other placements exist
|
||||
if( size(placements) > 1 )
|
||||
foreach(var p; placements)
|
||||
|
@ -108,7 +112,7 @@ var Window = {
|
|||
];
|
||||
|
||||
me._canvas = new({
|
||||
size: [2 * size[0], 2 * size[1]],
|
||||
size: [size[0], size[1]],
|
||||
view: size,
|
||||
placement: {
|
||||
type: "window",
|
||||
|
@ -124,7 +128,10 @@ var Window = {
|
|||
"blend-destination-alpha": "one"
|
||||
});
|
||||
|
||||
me._canvas._focused_widget = nil;
|
||||
me._canvas.data("focused", me._focused);
|
||||
me._canvas.addEventListener("mousedown", func me.raise());
|
||||
|
||||
return me._canvas;
|
||||
},
|
||||
# Set an existing canvas to be used for this Window
|
||||
|
@ -135,26 +142,30 @@ var Window = {
|
|||
|
||||
canvas_.addPlacement({type: "window", "id": me.get("id")});
|
||||
me['_canvas'] = canvas_;
|
||||
canvas_.data("focused", me._focused);
|
||||
|
||||
# prevent resizing if canvas is placed from somewhere else
|
||||
me.onResize = nil;
|
||||
},
|
||||
# Get the displayed canvas
|
||||
getCanvas: func()
|
||||
getCanvas: func(create = 0)
|
||||
{
|
||||
if( me['_canvas'] == nil and create )
|
||||
me.createCanvas();
|
||||
|
||||
return me['_canvas'];
|
||||
},
|
||||
getCanvasDecoration: func()
|
||||
{
|
||||
return wrapCanvas(me._getCanvasDecoration());
|
||||
},
|
||||
addWidget: func(w)
|
||||
setLayout: func(l)
|
||||
{
|
||||
append(me._widgets, w);
|
||||
w._window = me;
|
||||
if( size(me._widgets) == 2 )
|
||||
w.setFocus();
|
||||
w._onStateChange();
|
||||
if( me['_canvas'] == nil )
|
||||
me.createCanvas();
|
||||
|
||||
me._canvas.update(); # Ensure placement is applied
|
||||
me._ghost.setLayout(l);
|
||||
return me;
|
||||
},
|
||||
#
|
||||
|
@ -223,6 +234,8 @@ var Window = {
|
|||
# protected:
|
||||
_onStateChange: func
|
||||
{
|
||||
var event = canvas.CustomEvent.new("wm.focus-" ~ (me._focused ? "in" : "out"));
|
||||
|
||||
if( me._getCanvasDecoration() != nil )
|
||||
{
|
||||
# Stronger shadow for focused windows
|
||||
|
@ -233,10 +246,16 @@ var Window = {
|
|||
me._title_bar_bg.set("fill", style.getColor("title" ~ suffix));
|
||||
me._title.set( "fill", style.getColor("title-text" ~ suffix));
|
||||
me._top_line.set( "stroke", style.getColor("title-highlight" ~ suffix));
|
||||
|
||||
me.getCanvasDecoration()
|
||||
.data("focused", me._focused)
|
||||
.dispatchEvent(event);
|
||||
}
|
||||
|
||||
foreach(var w; me._widgets)
|
||||
w._onStateChange();
|
||||
if( me.getCanvas() != nil )
|
||||
me.getCanvas()
|
||||
.data("focused", me._focused)
|
||||
.dispatchEvent(event);
|
||||
},
|
||||
# private:
|
||||
_propCallback: func(child, mode)
|
||||
|
@ -387,8 +406,7 @@ var Window = {
|
|||
|
||||
var button_close = WindowButton.new(title_bar, "close")
|
||||
.move(x, y);
|
||||
button_close.onClick = func me.del();
|
||||
me.addWidget(button_close);
|
||||
button_close.listen("clicked", func me.del());
|
||||
|
||||
# title
|
||||
me._title = title_bar.createChild("text", "title")
|
||||
|
@ -453,11 +471,10 @@ var initDemo = func
|
|||
{
|
||||
debug.dump( props.wrapNode(event.target._node_ghost) );
|
||||
});
|
||||
my_canvas.addEventListener("click", func
|
||||
my_canvas.addEventListener("click", func(e)
|
||||
{
|
||||
var dlg = canvas.Window.new([400,300], "dialog")
|
||||
.set("resize", 1);
|
||||
|
||||
var my_canvas = dlg.createCanvas()
|
||||
.set("background", style.getColor("bg_color"));
|
||||
|
||||
|
@ -466,7 +483,7 @@ var initDemo = func
|
|||
my_canvas.addEventListener("drag", func(e) { printf("drag: screen(%.1f|%.1f) client(%.1f|%.1f) local(%.1f|%.1f) delta(%.1f|%.1f)", e.screenX, e.screenY, e.clientX, e.clientY, e.localX, e.localY, e.deltaX, e.deltaY); });
|
||||
my_canvas.addEventListener("wheel", func(e) { printf("wheel: screen(%.1f|%.1f) client(%.1f|%.1f) %.1f", e.screenX, e.screenY, e.clientX, e.clientY, e.deltaY); });
|
||||
var root = my_canvas.createGroup();
|
||||
root.createChild("image")
|
||||
root.createChild("image")
|
||||
.set("src", "http://wiki.flightgear.org/skins/common/images/icons-fg-135.png");
|
||||
var text =
|
||||
root.createChild("text")
|
||||
|
@ -492,22 +509,25 @@ root.createChild("image")
|
|||
text.addEventListener("mousemove", func(e) { printf("move: screen(%.1f|%.1f) client(%.1f|%.1f) local(%.1f|%.1f) delta(%.1f|%.1f)", e.screenX, e.screenY, e.clientX, e.clientY, e.localX, e.localY, e.deltaX, e.deltaY); });
|
||||
text.set("fill", style.getColor("text_color"));
|
||||
|
||||
dlg.addWidget( gui.widgets.Button.new(root, style, {size: [64, 26]})
|
||||
gui.widgets.Button.new(root, style, {})
|
||||
.setText("Ok")
|
||||
.move(20, 250) );
|
||||
dlg.addWidget( gui.widgets.Button.new(root, style, {size: [64, 26]})
|
||||
.move(20, 250)
|
||||
.setSize(64, 26);
|
||||
gui.widgets.Button.new(root, style, {})
|
||||
.setText("Apply")
|
||||
.move(100, 250) );
|
||||
dlg.addWidget( gui.widgets.Button.new(root, style, {size: [64, 64]})
|
||||
.move(100, 250)
|
||||
.setSize(64, 26);
|
||||
gui.widgets.Button.new(root, style, {})
|
||||
.setText("Cancel")
|
||||
.move(180, 200) );
|
||||
.move(180, 200)
|
||||
.setSize(64, 64);
|
||||
|
||||
var scroll = gui.widgets.ScrollArea.new(root, style, {size: [96, 128]})
|
||||
.move(20, 100);
|
||||
var scroll = gui.widgets.ScrollArea.new(root, style, {})
|
||||
.move(20, 100)
|
||||
.setSize(96, 128);
|
||||
var txt = scroll.getContent().createChild("text")
|
||||
.set("text", "01hallo\n02asdasd\n03\n04\n05asdasd06\n07ß\n08\n09asdasd\n10\n11");
|
||||
scroll.update();
|
||||
dlg.addWidget(scroll);
|
||||
|
||||
txt.addEventListener("mouseover", func txt.set("fill", "red"));
|
||||
txt.addEventListener("mouseout", func txt.set("fill", "green"));
|
||||
|
|
|
@ -17,5 +17,10 @@ var Config = {
|
|||
return val;
|
||||
|
||||
return default;
|
||||
},
|
||||
set: func(key, value)
|
||||
{
|
||||
me._cfg[key] = value;
|
||||
return me;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,19 +8,50 @@ gui.Widget = {
|
|||
#
|
||||
new: func(derived)
|
||||
{
|
||||
return {
|
||||
var m = canvas.Widget.new({
|
||||
parents: [derived, gui.Widget],
|
||||
_focused: 0,
|
||||
_focus_policy: gui.Widget.NoFocus,
|
||||
_hover: 0,
|
||||
_root: nil,
|
||||
_size: [64, 64]
|
||||
};
|
||||
_enabled: 1,
|
||||
_view: nil,
|
||||
_pos: [0, 0],
|
||||
_size: [32, 32]
|
||||
});
|
||||
|
||||
m.setMinimumSize([16, 16]);
|
||||
m.setSizeHint([32, 32]);
|
||||
m.setMaximumSize([m._MAX_SIZE, m._MAX_SIZE]);
|
||||
|
||||
m.setSetGeometryFunc(m._impl.setGeometry);
|
||||
|
||||
return m;
|
||||
},
|
||||
setFixedSize: func(x, y)
|
||||
{
|
||||
me.setMinimumSize([x, y]);
|
||||
me.setSizeHint([x, y]);
|
||||
me.setMaximumSize([x, y]);
|
||||
},
|
||||
setEnabled: func(enabled)
|
||||
{
|
||||
if( me._enabled == enabled )
|
||||
return me;
|
||||
|
||||
me._enabled = enabled;
|
||||
me.clearFocus();
|
||||
|
||||
me._onStateChange();
|
||||
return me;
|
||||
},
|
||||
# Move the widget to the given position (relative to its parent)
|
||||
move: func(x, y)
|
||||
{
|
||||
me._root.setTranslation(x, y);
|
||||
me._pos[0] = x;
|
||||
me._pos[1] = y;
|
||||
|
||||
if( me._view != nil )
|
||||
me._view._root.setTranslation(x, y);
|
||||
return me;
|
||||
},
|
||||
#
|
||||
|
@ -28,6 +59,20 @@ gui.Widget = {
|
|||
{
|
||||
me._size[0] = w;
|
||||
me._size[1] = h;
|
||||
|
||||
if( me._view != nil )
|
||||
me._view.setSize(me, w, h);
|
||||
return me;
|
||||
},
|
||||
# Set geometry of widget (usually used by layouting system)
|
||||
#
|
||||
# @param geom [<x>, <y>, <width>, <height>]
|
||||
setGeometry: func(geom)
|
||||
{
|
||||
me.move(geom[0], geom[1]);
|
||||
me.setSize(geom[2], geom[3]);
|
||||
me._onStateChange();
|
||||
return me;
|
||||
},
|
||||
#
|
||||
setFocus: func
|
||||
|
@ -35,13 +80,17 @@ gui.Widget = {
|
|||
if( me._focused )
|
||||
return me;
|
||||
|
||||
if( me._window._focused_widget != nil )
|
||||
me._window._focused_widget.clearFocus();
|
||||
var canvas = me.getCanvas();
|
||||
if( canvas._focused_widget != nil )
|
||||
canvas._focused_widget.clearFocus();
|
||||
|
||||
if( !me._enabled )
|
||||
return me;
|
||||
|
||||
me._focused = 1;
|
||||
me._window._focused_widget = me;
|
||||
canvas._focused_widget = me;
|
||||
|
||||
me.onFocusIn();
|
||||
me._trigger("focus-in");
|
||||
me._onStateChange();
|
||||
|
||||
return me;
|
||||
|
@ -53,38 +102,61 @@ gui.Widget = {
|
|||
return me;
|
||||
|
||||
me._focused = 0;
|
||||
me._window._focused_widget = nil;
|
||||
me.getCanvas()._focused_widget = nil;
|
||||
|
||||
me.onFocusOut();
|
||||
me._trigger("focus-out");
|
||||
me._onStateChange();
|
||||
|
||||
return me;
|
||||
},
|
||||
onFocusIn: func {},
|
||||
onFocusOut: func {},
|
||||
onMouseEnter: func {},
|
||||
onMouseLeave: func {},
|
||||
listen: func(type, cb)
|
||||
{
|
||||
me._view._root.addEventListener("cb." ~ type, cb);
|
||||
return me;
|
||||
},
|
||||
# protected:
|
||||
_MAX_SIZE: 32768, # size for "no size-limit"
|
||||
_onStateChange: func {},
|
||||
_setRoot: func(el)
|
||||
_setView: func(view)
|
||||
{
|
||||
me._root = el;
|
||||
el.addEventListener("mouseenter", func {
|
||||
me._view = view;
|
||||
|
||||
var root = view._root;
|
||||
var canvas = root.getCanvas();
|
||||
me.setCanvas(canvas);
|
||||
|
||||
canvas.addEventListener("wm.focus-in", func {
|
||||
me._onStateChange();
|
||||
});
|
||||
canvas.addEventListener("wm.focus-out", func {
|
||||
me._onStateChange();
|
||||
});
|
||||
|
||||
root.addEventListener("mouseenter", func {
|
||||
me._hover = 1;
|
||||
me.onMouseEnter();
|
||||
me._trigger("mouse-enter");
|
||||
me._onStateChange();
|
||||
});
|
||||
el.addEventListener("mousedown", func {
|
||||
root.addEventListener("mousedown", func {
|
||||
if( bits.test(me._focus_policy, me.ClickFocus / 2) )
|
||||
{
|
||||
me.setFocus();
|
||||
me._window.setFocus();
|
||||
}
|
||||
});
|
||||
el.addEventListener("mouseleave", func {
|
||||
root.addEventListener("mouseleave", func {
|
||||
me._hover = 0;
|
||||
me.onMouseLeave();
|
||||
me._trigger("mouse-leave");
|
||||
me._onStateChange();
|
||||
});
|
||||
},
|
||||
_trigger: func(type, data = nil)
|
||||
{
|
||||
me._view._root.dispatchEvent(
|
||||
canvas.CustomEvent.new("cb." ~ type, {detail: data})
|
||||
);
|
||||
return me;
|
||||
},
|
||||
_windowFocus: func
|
||||
{
|
||||
var canvas = me.getCanvas();
|
||||
return canvas != nil ? canvas.data("focused") : 0;
|
||||
}
|
||||
};
|
||||
|
|
124
Nasal/canvas/gui/dialogs/AircraftCenter.nas
Normal file
|
@ -0,0 +1,124 @@
|
|||
var AircraftCenter = {
|
||||
show: func
|
||||
{
|
||||
var dlg = canvas.Window.new([550,500], "dialog")
|
||||
.set("title", "Aircraft Center")
|
||||
.set("resize", 1);
|
||||
dlg.getCanvas(1)
|
||||
.set("background", canvas.style.getColor("bg_color"));
|
||||
var root = dlg.getCanvas().createGroup();
|
||||
|
||||
var vbox = VBoxLayout.new();
|
||||
dlg.setLayout(vbox);
|
||||
|
||||
var packages = pkg.root.search({
|
||||
'rating-FDM': 2,
|
||||
'rating-cockpit': 2,
|
||||
'rating-model': 2,
|
||||
'rating-systems': 1
|
||||
});
|
||||
|
||||
var info_text =
|
||||
"Install/remove aircrafts (Showing " ~ size(packages) ~ " aircrafts)";
|
||||
|
||||
vbox.addItem(
|
||||
gui.widgets.Label.new(root, style, {wordWrap: 1})
|
||||
.setText(info_text)
|
||||
);
|
||||
|
||||
var scroll = gui.widgets.ScrollArea.new(root, style, {size: [96, 128]})
|
||||
.move(20, 100);
|
||||
vbox.addItem(scroll, 1);
|
||||
|
||||
var content = scroll.getContent()
|
||||
.set("font", "LiberationFonts/LiberationSans-Bold.ttf")
|
||||
.set("character-size", 16)
|
||||
.set("alignment", "left-center");
|
||||
|
||||
var list = VBoxLayout.new();
|
||||
scroll.setLayout(list);
|
||||
|
||||
foreach(var package; packages)
|
||||
{
|
||||
var row = HBoxLayout.new();
|
||||
list.addItem(row);
|
||||
|
||||
var image_label = gui.widgets.Label.new(content, style, {});
|
||||
image_label.setFixedSize(171, 128);
|
||||
row.addItem(image_label);
|
||||
|
||||
var thumbs = package.thumbnails;
|
||||
if( size(thumbs) > 0 )
|
||||
image_label.setImage(thumbs[0]);
|
||||
else
|
||||
image_label.setText("No thumbnail available");
|
||||
|
||||
var detail_box = VBoxLayout.new();
|
||||
row.addItem(detail_box);
|
||||
row.addSpacing(5);
|
||||
|
||||
var title_box = HBoxLayout.new();
|
||||
detail_box.addItem(title_box);
|
||||
|
||||
title_box.addItem(
|
||||
gui.widgets.Label.new(content, style, {})
|
||||
.setText(package.name)
|
||||
);
|
||||
title_box.addStretch(1);
|
||||
(func {
|
||||
var p = package;
|
||||
var b = gui.widgets.Button.new(content, style, {});
|
||||
var installed = p.installed;
|
||||
var install_text = sprintf("Install (%.1fMB)", p.fileSize/1024/1024);
|
||||
|
||||
if( installed )
|
||||
b.setText("Remove");
|
||||
else
|
||||
b.setText(install_text);
|
||||
|
||||
b.listen("clicked", func
|
||||
{
|
||||
if( installed )
|
||||
{
|
||||
p.uninstall();
|
||||
installed = 0;
|
||||
b.setText(install_text);
|
||||
}
|
||||
else
|
||||
{
|
||||
b.setEnabled(0)
|
||||
.setText("Wait...");
|
||||
p.install()
|
||||
.progress(func(i, cur, total)
|
||||
b.setText(sprintf("%.1f%%", (cur / total) * 100))
|
||||
)
|
||||
.fail(func b.setText('Failed'))
|
||||
.done(func {
|
||||
installed = 1;
|
||||
b.setText("Remove")
|
||||
.setEnabled(1);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
title_box.addItem(b);
|
||||
})();
|
||||
|
||||
var description = package.description;
|
||||
if( size(description) <= 0 )
|
||||
{
|
||||
foreach(var cat; ["FDM", "systems", "cockpit", "model"])
|
||||
description ~= cat ~ ": " ~ package.lprop("rating/" ~ cat) ~ "\n";
|
||||
}
|
||||
|
||||
detail_box.addItem(
|
||||
gui.widgets.Label.new(content, style, {wordWrap: 1})
|
||||
.setText(description)
|
||||
);
|
||||
|
||||
detail_box.addStretch(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AircraftCenter.show();
|
|
@ -27,32 +27,40 @@ DefaultStyle.widgets.button = {
|
|||
padding: [6, 8, 6, 8],
|
||||
new: func(parent, cfg)
|
||||
{
|
||||
me.element = parent.createChild("group", "button");
|
||||
me.size = cfg.get("size", [26, 26]);
|
||||
|
||||
me._root = parent.createChild("group", "button");
|
||||
me._bg =
|
||||
me.element.rect( 3,
|
||||
3,
|
||||
me.size[0] - 6,
|
||||
me.size[1] - 6,
|
||||
{"border-radius": 5} );
|
||||
me._root.createChild("path");
|
||||
me._border =
|
||||
me.element.createChild("image", "button")
|
||||
.set("slice", "10 12") #"7")
|
||||
.setSize(me.size);
|
||||
me._root.createChild("image", "button")
|
||||
.set("slice", "10 12"); #"7")
|
||||
me._label =
|
||||
me.element.createChild("text")
|
||||
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
|
||||
me._root.createChild("text")
|
||||
.set("font", "LiberationFonts/LiberationSans-Regular.ttf")
|
||||
.set("character-size", 14)
|
||||
.set("alignment", "center-baseline");
|
||||
},
|
||||
setText: func(text)
|
||||
setSize: func(model, w, h)
|
||||
{
|
||||
me._bg.reset()
|
||||
.rect(3, 3, w - 6, h - 6, {"border-radius": 5});
|
||||
me._border.setSize(w, h);
|
||||
},
|
||||
setText: func(model, text)
|
||||
{
|
||||
me._label.set("text", text);
|
||||
|
||||
# TODO get real font metrics
|
||||
model.setMinimumSize([size(text) * 5 + 6, 16]);
|
||||
model.setSizeHint([size(text) * 8 + 16, 32]);
|
||||
|
||||
return me;
|
||||
},
|
||||
update: func(active, focused, hover, backdrop)
|
||||
update: func(model)
|
||||
{
|
||||
var backdrop = !model._windowFocus();
|
||||
var (w, h) = model._size;
|
||||
var file = me._style._dir_widgets ~ "/";
|
||||
|
||||
if( backdrop )
|
||||
{
|
||||
file ~= "backdrop-";
|
||||
|
@ -62,42 +70,159 @@ DefaultStyle.widgets.button = {
|
|||
me._label.set("fill", me._style.getColor("fg_color"));
|
||||
file ~= "button";
|
||||
|
||||
if( active )
|
||||
if( model._down )
|
||||
{
|
||||
file ~= "-active";
|
||||
me._label.setTranslation(me.size[0] / 2 + 1, me.size[1] / 2 + 6);
|
||||
me._label.setTranslation(w / 2 + 1, h / 2 + 6);
|
||||
}
|
||||
else
|
||||
me._label.setTranslation(me.size[0] / 2, me.size[1] / 2 + 5);
|
||||
me._label.setTranslation(w / 2, h / 2 + 5);
|
||||
|
||||
|
||||
if( focused and !backdrop )
|
||||
if( model._enabled )
|
||||
{
|
||||
if( model._focused and !backdrop )
|
||||
file ~= "-focused";
|
||||
|
||||
if( hover and !active )
|
||||
if( model._hover and !model._down )
|
||||
{
|
||||
file ~= "-hover";
|
||||
me._bg.set("fill", me._style.getColor("button_bg_color_hover"));
|
||||
}
|
||||
else
|
||||
me._bg.set("fill", me._style.getColor("button_bg_color"));
|
||||
}
|
||||
else
|
||||
{
|
||||
file ~= "-disabled";
|
||||
me._bg.set("fill", me._style.getColor("button_bg_color_insensitive"));
|
||||
}
|
||||
|
||||
me._border.set("src", file ~ ".png");
|
||||
}
|
||||
};
|
||||
|
||||
# A label
|
||||
DefaultStyle.widgets.label = {
|
||||
new: func(parent, cfg)
|
||||
{
|
||||
me._root = parent.createChild("group", "label");
|
||||
},
|
||||
setSize: func(model, w, h)
|
||||
{
|
||||
if( me['_bg'] != nil )
|
||||
me._bg.reset().rect(0, 0, w, h);
|
||||
if( me['_img'] != nil )
|
||||
me._img.set("size[0]", w)
|
||||
.set("size[1]", h);
|
||||
if( me['_text'] != nil )
|
||||
{
|
||||
# TODO different alignment
|
||||
me._text.setTranslation(2, 2 + h / 2);
|
||||
me._text.set(
|
||||
"max-width",
|
||||
model._cfg.get("wordWrap", 0) ? (w - 4) : 0
|
||||
);
|
||||
}
|
||||
return me;
|
||||
},
|
||||
setText: func(model, text)
|
||||
{
|
||||
if( text == nil or size(text) == 0 )
|
||||
{
|
||||
model.setHeightForWidthFunc(nil);
|
||||
return me._deleteElement('text');
|
||||
}
|
||||
|
||||
me._createElement("text", "text")
|
||||
.set("text", text)
|
||||
.set("fill", "black");
|
||||
|
||||
if( model._cfg.get("wordWrap", 0) )
|
||||
{
|
||||
var m = me;
|
||||
model.setHeightForWidthFunc(func(w) m.heightForWidth(w));
|
||||
}
|
||||
else
|
||||
{
|
||||
# TODO get real font metrics
|
||||
model.setMinimumSize([size(text) * 5 + 4, 14]);
|
||||
model.setSizeHint([size(text) * 5 + 14, 24]);
|
||||
}
|
||||
|
||||
return me;
|
||||
},
|
||||
setImage: func(model, img)
|
||||
{
|
||||
if( img == nil or size(img) == 0 )
|
||||
return me._deleteElement('img');
|
||||
|
||||
me._createElement("img", "image")
|
||||
.set("src", img)
|
||||
.set("preserveAspectRatio", "xMidYMid slice");
|
||||
|
||||
return me;
|
||||
},
|
||||
# @param bg CSS color or 'none'
|
||||
setBackground: func(model, bg)
|
||||
{
|
||||
if( bg == nil or bg == "none" )
|
||||
return me._deleteElement("bg");
|
||||
|
||||
me._createElement("bg", "path")
|
||||
.set("fill", bg);
|
||||
|
||||
me.setSize(model, model._size[0], model._size[1]);
|
||||
return me;
|
||||
},
|
||||
heightForWidth: func(w)
|
||||
{
|
||||
if( me['_text'] == nil )
|
||||
return -1;
|
||||
|
||||
return math.max(14, me._text.heightForWidth(w - 4));
|
||||
},
|
||||
# protected:
|
||||
_createElement: func(name, type)
|
||||
{
|
||||
var mem = '_' ~ name;
|
||||
if( me[ mem ] == nil )
|
||||
{
|
||||
me[ mem ] = me._root.createChild(type, "label-" ~ name);
|
||||
|
||||
if( type == "text" )
|
||||
{
|
||||
me[ mem ].set("font", "LiberationFonts/LiberationSans-Regular.ttf")
|
||||
.set("character-size", 14)
|
||||
.set("alignment", "left-center");
|
||||
}
|
||||
}
|
||||
return me[ mem ];
|
||||
},
|
||||
_deleteElement: func(name)
|
||||
{
|
||||
name = '_' ~ name;
|
||||
if( me[ name ] != nil )
|
||||
{
|
||||
me[ name ].del();
|
||||
me[ name ] = nil;
|
||||
}
|
||||
return me;
|
||||
}
|
||||
};
|
||||
|
||||
# ScrollArea
|
||||
DefaultStyle.widgets["scroll-area"] = {
|
||||
new: func(parent, cfg)
|
||||
{
|
||||
me.element = parent.createChild("group", "scroll-area");
|
||||
me._root = parent.createChild("group", "scroll-area");
|
||||
|
||||
me._bg = me.element.createChild("path", "background")
|
||||
me._bg = me._root.createChild("path", "background")
|
||||
.set("fill", "#e0e0e0");
|
||||
me.content = me.element.createChild("group", "scroll-content")
|
||||
me.content = me._root.createChild("group", "scroll-content")
|
||||
.set("clip-frame", Element.PARENT);
|
||||
me.vert = me._newScroll(me.element, "vert");
|
||||
me.horiz = me._newScroll(me.element, "horiz");
|
||||
me.vert = me._newScroll(me._root, "vert");
|
||||
me.horiz = me._newScroll(me._root, "horiz");
|
||||
},
|
||||
setColorBackground: func
|
||||
{
|
||||
|
@ -105,25 +230,25 @@ DefaultStyle.widgets["scroll-area"] = {
|
|||
var arg = arg[0];
|
||||
me._bg.setColorFill(arg);
|
||||
},
|
||||
update: func(widget)
|
||||
update: func(model)
|
||||
{
|
||||
me.horiz.reset();
|
||||
if( widget._max_scroll[0] > 1 )
|
||||
if( model._max_scroll[0] > 1 )
|
||||
# only show scroll bar if horizontally scrollable
|
||||
me.horiz.moveTo(widget._pos[0], widget._size[1] - 2)
|
||||
.horiz(widget._size[0] - widget._max_scroll[0]);
|
||||
me.horiz.moveTo(model._scroll_pos[0], model._size[1] - 2)
|
||||
.horiz(model._size[0] - model._max_scroll[0]);
|
||||
|
||||
me.vert.reset();
|
||||
if( widget._max_scroll[1] > 1 )
|
||||
if( model._max_scroll[1] > 1 )
|
||||
# only show scroll bar if vertically scrollable
|
||||
me.vert.moveTo(widget._size[0] - 2, widget._pos[1])
|
||||
.vert(widget._size[1] - widget._max_scroll[1]);
|
||||
me.vert.moveTo(model._size[0] - 2, model._scroll_pos[1])
|
||||
.vert(model._size[1] - model._max_scroll[1]);
|
||||
|
||||
me._bg.reset()
|
||||
.rect(0, 0, widget._size[0], widget._size[1]);
|
||||
.rect(0, 0, model._size[0], model._size[1]);
|
||||
me.content.set(
|
||||
"clip",
|
||||
"rect(0, " ~ widget._size[0] ~ ", " ~ widget._size[1] ~ ", 0)"
|
||||
"rect(0, " ~ model._size[0] ~ ", " ~ model._size[1] ~ ", 0)"
|
||||
);
|
||||
},
|
||||
# private:
|
||||
|
|
|
@ -4,59 +4,71 @@ gui.widgets.Button = {
|
|||
var cfg = Config.new(cfg);
|
||||
var m = gui.Widget.new(gui.widgets.Button);
|
||||
m._focus_policy = m.StrongFocus;
|
||||
m._active = 0;
|
||||
m._down = 0;
|
||||
m._checkable = 0;
|
||||
m._flat = cfg.get("flat", 0);
|
||||
|
||||
if( style != nil and !m._flat )
|
||||
{
|
||||
m._button = style.createWidget(parent, "button", cfg);
|
||||
m._setRoot(m._button.element);
|
||||
}
|
||||
m._setView( style.createWidget(parent, "button", cfg) );
|
||||
|
||||
return m;
|
||||
},
|
||||
setText: func(text)
|
||||
{
|
||||
me._button.setText(text);
|
||||
me._view.setText(me, text);
|
||||
return me;
|
||||
},
|
||||
setActive: func
|
||||
setCheckable: func(checkable)
|
||||
{
|
||||
if( me._active )
|
||||
me._checkable = checkable;
|
||||
return me;
|
||||
},
|
||||
setChecked: func(checked = 1)
|
||||
{
|
||||
if( !me._checkable or me._down == checked )
|
||||
return me;
|
||||
|
||||
me._active = 1;
|
||||
me._trigger("clicked", {checked: checked});
|
||||
me._trigger("toggled", {checked: checked});
|
||||
|
||||
me._down = checked;
|
||||
me._onStateChange();
|
||||
return me;
|
||||
},
|
||||
clearActive: func
|
||||
setDown: func(down = 1)
|
||||
{
|
||||
if( !me._active )
|
||||
if( me._checkable or me._down == down )
|
||||
return me;
|
||||
|
||||
me._active = 0;
|
||||
me._down = down;
|
||||
me._onStateChange();
|
||||
return me;
|
||||
},
|
||||
onClick: func {},
|
||||
toggle: func
|
||||
{
|
||||
if( !me._checkable )
|
||||
me._trigger("clicked", {checked: 0});
|
||||
else
|
||||
me.setChecked(!me._down);
|
||||
|
||||
return me;
|
||||
},
|
||||
# protected:
|
||||
_onStateChange: func
|
||||
{
|
||||
if( me._button != nil )
|
||||
me._button.update(me._active, me._focused, me._hover, !me._window._focused);
|
||||
if( me._view != nil )
|
||||
me._view.update(me);
|
||||
},
|
||||
_setRoot: func(el)
|
||||
_setView: func(view)
|
||||
{
|
||||
el.addEventListener("mousedown", func me.setActive());
|
||||
el.addEventListener("mouseup", func me.clearActive());
|
||||
var el = view._root;
|
||||
el.addEventListener("mousedown", func if( me._enabled ) me.setDown(1));
|
||||
el.addEventListener("mouseup", func if( me._enabled ) me.setDown(0));
|
||||
el.addEventListener("click", func if( me._enabled ) me.toggle());
|
||||
|
||||
# Use 'call' to ensure 'me' is not set and can be used in the closure of
|
||||
# custom callbacks. TODO pass 'me' as argument?
|
||||
el.addEventListener("click", func call(me.onClick));
|
||||
|
||||
el.addEventListener("mouseleave",func me.clearActive());
|
||||
el.addEventListener("mouseleave",func me.setDown(0));
|
||||
el.addEventListener("drag", func(e) e.stopPropagation());
|
||||
|
||||
call(gui.Widget._setRoot, [el], me);
|
||||
call(gui.Widget._setView, [view], me);
|
||||
}
|
||||
};
|
||||
|
|
26
Nasal/canvas/gui/widgets/Label.nas
Normal file
|
@ -0,0 +1,26 @@
|
|||
gui.widgets.Label = {
|
||||
new: func(parent, style, cfg)
|
||||
{
|
||||
var m = gui.Widget.new(gui.widgets.Label);
|
||||
m._cfg = Config.new(cfg);
|
||||
m._focus_policy = m.NoFocus;
|
||||
m._setView( style.createWidget(parent, "label", m._cfg) );
|
||||
|
||||
return m;
|
||||
},
|
||||
setText: func(text)
|
||||
{
|
||||
me._view.setText(me, text);
|
||||
return me;
|
||||
},
|
||||
setImage: func(img)
|
||||
{
|
||||
me._view.setImage(me, img);
|
||||
return me;
|
||||
},
|
||||
setBackground: func(bg)
|
||||
{
|
||||
me._view.setBackground(me, bg);
|
||||
return me;
|
||||
}
|
||||
};
|
|
@ -3,38 +3,28 @@ gui.widgets.ScrollArea = {
|
|||
{
|
||||
var cfg = Config.new(cfg);
|
||||
var m = gui.Widget.new(gui.widgets.ScrollArea);
|
||||
m._focus_policy = m.StrongFocus;
|
||||
m._active = 0;
|
||||
m._pos = [0,0];
|
||||
m._size = cfg.get("size", m._size);
|
||||
m._focus_policy = m.NoFocus;
|
||||
m._scroll_pos = [0,0];
|
||||
m._max_scroll = [0, 0];
|
||||
m._content_size = [0, 0];
|
||||
m._layout = nil;
|
||||
|
||||
if( style != nil )
|
||||
{
|
||||
m._scroll = style.createWidget(parent, "scroll-area", cfg);
|
||||
m._setRoot(m._scroll.element);
|
||||
m._setView( style.createWidget(parent, "scroll-area", cfg) );
|
||||
|
||||
m._scroll.vert.addEventListener("mousedown", func(e) m._dragStart(e));
|
||||
m._scroll.horiz.addEventListener("mousedown", func(e) m._dragStart(e));
|
||||
|
||||
m._scroll.vert.addEventListener
|
||||
(
|
||||
"drag",
|
||||
func(e) m.moveTo(m._pos[0], m._drag_offsetY + e.clientY)
|
||||
);
|
||||
m._scroll.horiz.addEventListener
|
||||
(
|
||||
"drag",
|
||||
func(e) m.moveTo(m._drag_offsetX + e.clientX, m._pos[1])
|
||||
);
|
||||
}
|
||||
m.setMinimumSize([32, 32]);
|
||||
|
||||
return m;
|
||||
},
|
||||
setLayout: func(l)
|
||||
{
|
||||
me._layout = l;
|
||||
l.setParent(me);
|
||||
return me.update();
|
||||
},
|
||||
getContent: func()
|
||||
{
|
||||
return me._scroll.content;
|
||||
return me._view.content;
|
||||
},
|
||||
# Set the background color for the content area.
|
||||
#
|
||||
|
@ -43,7 +33,7 @@ gui.widgets.ScrollArea = {
|
|||
{
|
||||
if( size(arg) == 1 )
|
||||
var arg = arg[0];
|
||||
me._scroll.setColorBackground(arg);
|
||||
me._view.setColorBackground(arg);
|
||||
},
|
||||
# Reset the size of the content area, e.g. on window resize.
|
||||
#
|
||||
|
@ -56,7 +46,7 @@ gui.widgets.ScrollArea = {
|
|||
me._size = [x,y];
|
||||
me.update();
|
||||
},
|
||||
# Move the scrollable area to the coordinates x,y (or as far as possible) and
|
||||
# Move the scrollbars to the coordinates x,y (or as far as possible) and
|
||||
# update.
|
||||
#
|
||||
# @param x The x coordinate (positive is right)
|
||||
|
@ -65,15 +55,15 @@ gui.widgets.ScrollArea = {
|
|||
{
|
||||
var bb = me._updateBB();
|
||||
|
||||
me._pos[0] = math.max(0, math.min(x, me._max_scroll[0]));
|
||||
me._pos[1] = math.max(0, math.min(y, me._max_scroll[1]));
|
||||
me._scroll_pos[0] = math.max(0, math.min(x, me._max_scroll[0]));
|
||||
me._scroll_pos[1] = math.max(0, math.min(y, me._max_scroll[1]));
|
||||
|
||||
me.update(bb);
|
||||
},
|
||||
# Move the scrollable area to the top-most position and update.
|
||||
moveToTop: func()
|
||||
{
|
||||
me._pos[1] = 0;
|
||||
me._scroll_pos[1] = 0;
|
||||
|
||||
me.update();
|
||||
},
|
||||
|
@ -82,14 +72,14 @@ gui.widgets.ScrollArea = {
|
|||
{
|
||||
var bb = me._updateBB();
|
||||
|
||||
me._pos[1] = me._max_scroll[1];
|
||||
me._scroll_pos[1] = me._max_scroll[1];
|
||||
|
||||
me.update(bb);
|
||||
},
|
||||
# Move the scrollable area to the left-most position and update.
|
||||
moveToLeft: func()
|
||||
{
|
||||
me._pos[0] = 0;
|
||||
me._scroll_pos[0] = 0;
|
||||
|
||||
me.update();
|
||||
},
|
||||
|
@ -98,10 +88,32 @@ gui.widgets.ScrollArea = {
|
|||
{
|
||||
var bb = me._updateBB();
|
||||
|
||||
me._pos[0] = me._max_scroll[0];
|
||||
me._scroll_pos[0] = me._max_scroll[0];
|
||||
|
||||
me.update(bb);
|
||||
},
|
||||
# Get position of scrollable content
|
||||
getContentPosition: func()
|
||||
{
|
||||
var pos = [0, 0];
|
||||
|
||||
if( me._max_scroll[0] > 1 )
|
||||
pos[0] = (me._scroll_pos[0] / me._max_scroll[0])
|
||||
* (me._content_size[0] - me._size[0]);
|
||||
if( me._max_scroll[1] > 1 )
|
||||
pos[1] = (me._scroll_pos[1] / me._max_scroll[1])
|
||||
* (me._content_size[1] - me._size[1]);
|
||||
|
||||
return pos;
|
||||
},
|
||||
# Move content to the given position
|
||||
moveContentTo: func(x, y)
|
||||
{
|
||||
var scroll_x = x / (me._content_size[0] - me._size[0]) * me._max_scroll[0];
|
||||
var scroll_y = y / (me._content_size[1] - me._size[1]) * me._max_scroll[1];
|
||||
|
||||
return me.moveTo(scroll_x, scroll_y);
|
||||
},
|
||||
# Update scroll bar and content area.
|
||||
#
|
||||
# Needs to be called when the size of the content changes.
|
||||
|
@ -110,37 +122,92 @@ gui.widgets.ScrollArea = {
|
|||
if (bb == nil) bb = me._updateBB();
|
||||
if (bb == nil) return me;
|
||||
|
||||
var offset = [ me._content_offset[0],
|
||||
me._content_offset[1] ];
|
||||
|
||||
if( me._max_scroll[0] > 1 )
|
||||
offset[0] -= (me._pos[0] / me._max_scroll[0])
|
||||
* (me._content_size[0] - me._size[0]);
|
||||
if( me._max_scroll[1] > 1 )
|
||||
offset[1] -= (me._pos[1] / me._max_scroll[1])
|
||||
* (me._content_size[1] - me._size[1]);
|
||||
|
||||
var pos = me.getContentPosition();
|
||||
var offset = [ me._content_offset[0] - pos[0],
|
||||
me._content_offset[1] - pos[1] ];
|
||||
me.getContent().setTranslation(offset);
|
||||
|
||||
me._scroll.update(me);
|
||||
me._view.update(me);
|
||||
me.getContent().update();
|
||||
|
||||
return me;
|
||||
},
|
||||
# protected:
|
||||
_setRoot: func(el)
|
||||
_setView: func(view)
|
||||
{
|
||||
el.addEventListener("wheel", func(e) me.moveTo(me._pos[0], me._pos[1] - e.deltaY));
|
||||
view.vert.addEventListener("mousedown", func(e) me._dragStart(e));
|
||||
view.horiz.addEventListener("mousedown", func(e) me._dragStart(e));
|
||||
view._root.addEventListener("mousedown", func(e)
|
||||
{
|
||||
var pos = me.getContentPosition();
|
||||
me._drag_offsetX = pos[0] + e.clientX;
|
||||
me._drag_offsetY = pos[1] + e.clientY;
|
||||
});
|
||||
|
||||
call(gui.Widget._setRoot, [el], me);
|
||||
view.vert.addEventListener
|
||||
(
|
||||
"drag",
|
||||
func(e)
|
||||
{
|
||||
if( !me._enabled )
|
||||
return;
|
||||
|
||||
me.moveTo(me._scroll_pos[0], me._drag_offsetY + e.clientY);
|
||||
e.stopPropagation();
|
||||
}
|
||||
);
|
||||
view.horiz.addEventListener
|
||||
(
|
||||
"drag",
|
||||
func(e)
|
||||
{
|
||||
if( !me._enabled )
|
||||
return;
|
||||
|
||||
me.moveTo(me._drag_offsetX + e.clientX, me._scroll_pos[1]);
|
||||
e.stopPropagation();
|
||||
}
|
||||
);
|
||||
|
||||
view._root.addEventListener
|
||||
(
|
||||
"drag",
|
||||
func(e)
|
||||
{
|
||||
if( !me._enabled )
|
||||
return;
|
||||
|
||||
me.moveContentTo( me._drag_offsetX - e.clientX,
|
||||
me._drag_offsetY - e.clientY );
|
||||
e.stopPropagation();
|
||||
}
|
||||
);
|
||||
view._root.addEventListener
|
||||
(
|
||||
"wheel",
|
||||
func(e)
|
||||
{
|
||||
if( !me._enabled )
|
||||
return;
|
||||
|
||||
me.moveTo(me._scroll_pos[0], me._scroll_pos[1] - e.deltaY);
|
||||
e.stopPropagation();
|
||||
}
|
||||
);
|
||||
|
||||
call(gui.Widget._setView, [view], me);
|
||||
},
|
||||
_dragStart: func(e)
|
||||
{
|
||||
me._drag_offsetX = me._pos[0] - e.clientX;
|
||||
me._drag_offsetY = me._pos[1] - e.clientY;
|
||||
me._drag_offsetX = me._scroll_pos[0] - e.clientX;
|
||||
me._drag_offsetY = me._scroll_pos[1] - e.clientY;
|
||||
e.stopPropagation();
|
||||
},
|
||||
_updateBB: func() {
|
||||
_updateBB: func()
|
||||
{
|
||||
# TODO only update on content resize
|
||||
if( me._layout == nil )
|
||||
{
|
||||
var bb = me.getContent().getTightBoundingBox();
|
||||
|
||||
if( bb[2] < bb[0] or bb[3] < bb[1] )
|
||||
|
@ -148,17 +215,43 @@ gui.widgets.ScrollArea = {
|
|||
var w = bb[2] - bb[0];
|
||||
var h = bb[3] - bb[1];
|
||||
|
||||
var cur_offset = me.getContent().getTranslation();
|
||||
me._content_offset = [cur_offset[0] - bb[0], cur_offset[1] - bb[1]];
|
||||
}
|
||||
else
|
||||
{
|
||||
var min_size = me._layout.minimumSize();
|
||||
var max_size = me._layout.maximumSize();
|
||||
var size_hint = me._layout.sizeHint();
|
||||
var w = math.min(max_size[0], math.max(size_hint[0], me._size[0]));
|
||||
var h = math.max(
|
||||
math.min(max_size[1], math.max(size_hint[1], me._size[1])),
|
||||
me._layout.heightForWidth(w)
|
||||
);
|
||||
|
||||
me._layout.setGeometry([0, 0, w, h]);
|
||||
|
||||
# Layout always has the origin at (0, 0)
|
||||
me._content_offset = [0, 0];
|
||||
}
|
||||
|
||||
if( w > me._size[0] )
|
||||
me._max_scroll[0] = me._size[0] * (1 - me._size[0] / w);
|
||||
else me._max_scroll[0] = 0;
|
||||
{
|
||||
var scroller_size = math.max(12, me._size[0] * (me._size[0] / w));
|
||||
me._max_scroll[0] = me._size[0] - scroller_size;
|
||||
}
|
||||
else
|
||||
me._max_scroll[0] = 0;
|
||||
|
||||
if( h > me._size[1] )
|
||||
me._max_scroll[1] = me._size[1] * (1 - me._size[1] / h);
|
||||
else me._max_scroll[1] = 0;
|
||||
{
|
||||
var scroller_size = math.max(12, me._size[1] * (me._size[1] / h));
|
||||
me._max_scroll[1] = me._size[1] - scroller_size;
|
||||
}
|
||||
else
|
||||
me._max_scroll[1] = 0;
|
||||
|
||||
me._content_size[0] = w;
|
||||
me._content_size[1] = h;
|
||||
|
||||
var cur_offset = me.getContent().getTranslation();
|
||||
me._content_offset = [cur_offset[0] - bb[0], cur_offset[1] - bb[1]];
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
46
Nasal/canvas/map/ALT-profile.lcontroller
Normal file
|
@ -0,0 +1,46 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'ALT-profile';
|
||||
var parents = [SymbolLayer.Controller];
|
||||
var __self__ = caller(0)[0];
|
||||
SymbolLayer.Controller.add(name, __self__);
|
||||
SymbolLayer.add(name, {
|
||||
parents: [MultiSymbolLayer],
|
||||
type: name, # Symbol type
|
||||
df_controller: __self__, # controller to use by default -- this one
|
||||
df_options: { # default configuration options
|
||||
active_node: "/autopilot/route-manager/active",
|
||||
vnav_node: "/autopilot/route-manager/vnav/",
|
||||
types: ["tc", "td", "sc", "ed"],
|
||||
}
|
||||
});
|
||||
var new = func(layer) {
|
||||
var m = {
|
||||
parents: [__self__],
|
||||
layer: layer,
|
||||
map: layer.map,
|
||||
listeners: [],
|
||||
};
|
||||
layer.searcher._equals = func(a,b) a.getName() == b.getName();
|
||||
append(m.listeners, setlistener(layer.options.active_node, func m.layer.update() ));
|
||||
m.addVisibilityListener();
|
||||
|
||||
return m;
|
||||
};
|
||||
var del = func() {
|
||||
foreach (var l; me.listeners)
|
||||
removelistener(l);
|
||||
};
|
||||
|
||||
var searchCmd = func {
|
||||
var results = [];
|
||||
var symNode = props.globals.getNode(me.layer.options.vnav_node);
|
||||
if (symNode != nil)
|
||||
foreach (var t; me.layer.options.types) {
|
||||
var n = symNode.getNode(t);
|
||||
if (n != nil)
|
||||
append(results, n);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
32
Nasal/canvas/map/ALT-profile.symbol
Normal file
|
@ -0,0 +1,32 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'ALT-profile';
|
||||
var parents = [DotSym];
|
||||
var __self__ = caller(0)[0];
|
||||
DotSym.makeinstance( name, __self__ );
|
||||
|
||||
SymbolLayer.get(name).df_style = {
|
||||
radius: 13,
|
||||
};
|
||||
|
||||
var element_type = "group";
|
||||
|
||||
var init = func {
|
||||
var name = me.model.getName();
|
||||
var disptext = chr(string.toupper(name[0]))~"/"~chr(string.toupper(name[1]));
|
||||
var radius = me.style.radius;
|
||||
me.element.createChild("path")
|
||||
.setStrokeLineWidth(5)
|
||||
.moveTo(-radius, 0)
|
||||
.arcLargeCW(radius, radius, 0, 2 * radius, 0)
|
||||
.arcLargeCW(radius, radius, 0, -2 * radius, 0)
|
||||
.setColor(0.195,0.96,0.097);
|
||||
me.element.createChild("text")
|
||||
.setDrawMode( canvas.Text.TEXT )
|
||||
.setText(disptext)
|
||||
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
|
||||
.setFontSize(28)
|
||||
.setTranslation(25,35)
|
||||
.setColor(0.195,0.96,0.097);
|
||||
}
|
||||
|
|
@ -5,23 +5,18 @@ var parents = [SymbolLayer.Controller];
|
|||
var __self__ = caller(0)[0];
|
||||
SymbolLayer.Controller.add(name, __self__);
|
||||
SymbolLayer.add(name, {
|
||||
parents: [SymbolLayer],
|
||||
parents: [SingleSymbolLayer],
|
||||
type: name, # Symbol type
|
||||
df_controller: __self__, # controller to use by default -- this one
|
||||
df_style: {},
|
||||
});
|
||||
# N.B.: if used, this SymbolLayer should be updated every frame
|
||||
# by the Map Controller, or as often as the position is changed.
|
||||
var new = func(layer) {
|
||||
layer.searcher._equals = func(a,b) {
|
||||
a == b;
|
||||
}
|
||||
return {
|
||||
parents: [__self__],
|
||||
map: layer.map,
|
||||
_model: layer.map.getPosCoord(),
|
||||
};
|
||||
};
|
||||
var del = func;
|
||||
|
||||
var searchCmd = func {
|
||||
var c = me.map.getPosCoord();
|
||||
return [c];
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'APS';
|
||||
var parents = [Symbol.Controller];
|
||||
var __self__ = caller(0)[0];
|
||||
Symbol.Controller.add(name, __self__);
|
||||
Symbol.registry[ name ].df_controller = __self__;
|
||||
var new = func(model) ; # this controller doesn't need an instance
|
||||
|
|
@ -1,16 +1,19 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'APS';
|
||||
var parents = [DotSym];
|
||||
var parents = [SVGSymbol];
|
||||
var __self__ = caller(0)[0];
|
||||
DotSym.makeinstance( name, __self__ );
|
||||
|
||||
var element_type = "group";
|
||||
var svg_path = "Nasal/canvas/map/Images/boeingAirplane.svg";
|
||||
var element_id = "airplane";
|
||||
|
||||
var init = func {
|
||||
canvas.parsesvg(me.element, "Nasal/canvas/map/boeingAirplane.svg");
|
||||
me.draw();
|
||||
# Rotate with the main aircraft.
|
||||
# Will have to be adapted if intended for use with other aircraft
|
||||
# (but one could simply copy the layer for that).
|
||||
var draw = func {
|
||||
var rot = getprop("/orientation/heading-deg");
|
||||
rot -= me.layer.map.getHdg();
|
||||
me.element.setRotation(rot*D2R);
|
||||
};
|
||||
var draw = func me.element.setRotation(me.layer.map.getHdg());
|
||||
|
||||
|
|
|
@ -5,9 +5,18 @@ var parents = [SymbolLayer.Controller];
|
|||
var __self__ = caller(0)[0];
|
||||
SymbolLayer.Controller.add(name, __self__);
|
||||
SymbolLayer.add(name, {
|
||||
parents: [SymbolLayer],
|
||||
parents: [MultiSymbolLayer],
|
||||
type: name, # Symbol type
|
||||
df_controller: __self__, # controller to use by default -- this one
|
||||
df_style: {
|
||||
line_width: 3,
|
||||
scale_factor: 1,
|
||||
debug: 1,
|
||||
color_default: [0,0.6,0.85],
|
||||
label_font_color:[0,0.6,0.85],
|
||||
label_font_size: 28,
|
||||
},
|
||||
|
||||
});
|
||||
var a_instance = nil;
|
||||
var new = func(layer) {
|
||||
|
@ -17,7 +26,8 @@ var new = func(layer) {
|
|||
map: layer.map,
|
||||
listeners: [],
|
||||
};
|
||||
__self__.a_instance = m;
|
||||
m.addVisibilityListener();
|
||||
|
||||
return m;
|
||||
};
|
||||
var del = func() {
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'APT';
|
||||
var parents = [Symbol.Controller];
|
||||
var __self__ = caller(0)[0];
|
||||
Symbol.Controller.add(name, __self__);
|
||||
Symbol.registry[ name ].df_controller = __self__;
|
||||
var new = func(model) ; # this controller doesn't need an instance
|
||||
var LayerController = SymbolLayer.Controller.registry[ name ];
|
||||
var isActive = func(model) LayerController.a_instance.isActive(model);
|
||||
var query_range = func()
|
||||
die( name~".scontroller.query_range /MUST/ be provided by implementation" );
|
||||
|
|
@ -6,47 +6,28 @@ var __self__ = caller(0)[0];
|
|||
DotSym.makeinstance( name, __self__ );
|
||||
|
||||
var element_type = "group"; # we want a group, becomes "me.element"
|
||||
var icon_fix = nil;
|
||||
var text_fix = nil;
|
||||
var icon_apt = nil;
|
||||
var text_apt = nil;
|
||||
|
||||
# add the draw routine from airports-nd.draw here
|
||||
var draw = func {
|
||||
if (me.icon_fix != nil) return;
|
||||
var init = func {
|
||||
var icon_apt = me.element.createChild("path", name ~ " icon" )
|
||||
.moveTo(-17,0)
|
||||
.arcSmallCW(17,17,0,34,0)
|
||||
.arcSmallCW(17,17,0,-34,0)
|
||||
.close()
|
||||
.setColor(0,0.6,0.85)
|
||||
.setStrokeLineWidth(3);
|
||||
.setColor(me.layer.style.color_default)
|
||||
.setStrokeLineWidth(me.layer.style.line_width);
|
||||
var text_apt = me.element.createChild("text", name ~ " label")
|
||||
.setDrawMode( canvas.Text.TEXT )
|
||||
.setTranslation(17,35)
|
||||
.setText(me.model.id)
|
||||
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
|
||||
.setColor(0,0.6,0.85)
|
||||
.setFontSize(28);
|
||||
#me.element.setGeoPosition(lat, lon)
|
||||
# .set("z-index",1); # FIXME: this needs to be configurable!!
|
||||
.setColor(me.layer.style.label_font_color)
|
||||
.setFontSize(me.layer.style.label_font_size);
|
||||
|
||||
# disabled:
|
||||
if(0) {
|
||||
# the fix symbol
|
||||
me.icon_fix = me.element.createChild("path")
|
||||
.moveTo(-15,15)
|
||||
.lineTo(0,-15)
|
||||
.lineTo(15,15)
|
||||
.close()
|
||||
.setStrokeLineWidth(3)
|
||||
.setColor(0,0.6,0.85)
|
||||
.setScale(0.5,0.5); # FIXME: do proper LOD handling here - we need to scale according to current texture dimensions vs. original/design dimensions
|
||||
# the fix label
|
||||
me.text_fix = me.element.createChild("text")
|
||||
.setDrawMode( canvas.Text.TEXT )
|
||||
.setText(me.model.id)
|
||||
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
|
||||
.setFontSize(28)
|
||||
.setTranslation(5,25);
|
||||
}
|
||||
# FIXME: this applies scale to the whole group, better do this separately for each element?
|
||||
me.element.setScale(me.layer.style.scale_factor);
|
||||
};
|
||||
var draw = func;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ var parents = [SymbolLayer.Controller];
|
|||
var __self__ = caller(0)[0];
|
||||
SymbolLayer.Controller.add(name, __self__);
|
||||
SymbolLayer.add(name, {
|
||||
parents: [SymbolLayer],
|
||||
parents: [NavaidSymbolLayer],
|
||||
type: name, # Symbol type
|
||||
df_controller: __self__, # controller to use by default -- this one
|
||||
df_style: {
|
||||
|
@ -25,33 +25,16 @@ var new = func(layer) {
|
|||
listeners: [],
|
||||
query_type:'dme',
|
||||
};
|
||||
##
|
||||
# default styling parameters - can be overridden via addLayer( style:{key:value, ...} )
|
||||
|
||||
if (contains(m.layer,'style')) return m; # we already have a proper style
|
||||
|
||||
# otherwise, set up a default style:
|
||||
m.layer.style={};
|
||||
m.layer.style.debug = 0; # HACK: setting this enables benchmarking and printlog statements
|
||||
m.layer.style.animation_test = 0;
|
||||
|
||||
# these are used by the draw() routines, see DME.symbol
|
||||
m.layer.style.scale_factor = 1.0 ; # applied to the whole group for now
|
||||
m.layer.style.line_width = 3.0;
|
||||
m.layer.style.color_tuned = [0,1,0];
|
||||
m.layer.style.color_default = [0,0.6,0.85];
|
||||
m.addVisibilityListener();
|
||||
|
||||
return m;
|
||||
};
|
||||
|
||||
## TODO: also move this to generator helper
|
||||
var del = func() {
|
||||
foreach (var l; me.listeners)
|
||||
removelistener(l);
|
||||
};
|
||||
|
||||
var searchCmd = func {
|
||||
printlog(_MP_dbg_lvl, "Running query:", me.query_type);
|
||||
var range = me.map.getRange();
|
||||
if (range == nil) return;
|
||||
return positioned.findWithinRange(me.map.getPosCoord(), range, me.query_type);
|
||||
};
|
||||
var searchCmd = NavaidSymbolLayer.make(query:'dme');
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'DME';
|
||||
var parents = [Symbol.Controller];
|
||||
var __self__ = caller(0)[0];
|
||||
Symbol.Controller.add(name, __self__);
|
||||
Symbol.registry[ name ].df_controller = __self__;
|
||||
var new = func(model, symbol) ; # this controller doesn't need an instance
|
||||
|
|
@ -49,11 +49,6 @@ var del = func {
|
|||
|
||||
# var DMEIcon = StyleableElement.new( [{color:IconColor}, ] );
|
||||
|
||||
###
|
||||
# helper to tell if the symbol is already initialized or not
|
||||
# TODO: encapsulate API-wise (this is a very common thing to do...)
|
||||
var is_initialized = func me.icon_dme != nil;
|
||||
|
||||
###
|
||||
# FIXME: these should probably be part of MapStructure itself
|
||||
# TODO: Need to come up with a StyleableElement class for this sort of stuff
|
||||
|
@ -64,7 +59,7 @@ var apply_styling = func {
|
|||
var current_color = me.icon_dme.getColor();
|
||||
var required_color = nil;
|
||||
|
||||
if (typeof(me.layer.map.controller["is_tuned"]) == 'func' and me.layer.map.controller.is_tuned(me.model.frequency/100))
|
||||
if (typeof(me.map.controller["is_tuned"]) == 'func' and me.map.controller.is_tuned(me.model.frequency/100))
|
||||
#TODO: once we support caching/instancing, we cannot just change the symbol like this - we need to use a different symbol from the cache instead
|
||||
# which is why these things need to be done ONCE during init to set up a cache entry for each symbol variation to come up with a corresponding raster image
|
||||
# TODO: API-wise it would make sense to maintain a vector of required keys, so that the style hash can be validated in the ctor of the layer
|
||||
|
@ -90,7 +85,6 @@ var apply_styling = func {
|
|||
# TODO: also needs to be aware of current range, so that proper LOD handling can be implemented
|
||||
var apply_scale = func {
|
||||
# add all symbols here that need scaling
|
||||
# print("Scaling:", me.layer.style.scale_factor);
|
||||
me.icon_dme.setScale( me.layer.style.scale_factor );
|
||||
}
|
||||
|
||||
|
@ -106,11 +100,8 @@ var init_cache = func {
|
|||
|
||||
##
|
||||
# init is done separately to prepare support for caching (instanced symbols)
|
||||
# NOTE: People should not be "hard-coding" things like color/size here
|
||||
# these need to be encapsulated via a Hash lookup, so that things can be
|
||||
# easily customized
|
||||
#
|
||||
var init_symbols = func {
|
||||
var init = func {
|
||||
# debug.dump(me.layer.style);
|
||||
me.icon_dme = me.element.createChild("path")
|
||||
.moveTo(-15,0)
|
||||
|
@ -127,31 +118,21 @@ var init_symbols = func {
|
|||
.horiz(-14.5)
|
||||
.vert(-14.5)
|
||||
.close()
|
||||
.setStrokeLineWidth( me.layer.style.line_width ); #TODO: this should be style-able
|
||||
.setStrokeLineWidth( me.layer.style.line_width );
|
||||
|
||||
# finally scale the symbol as requested, this is done last so that people can override this when creating the layer
|
||||
me.apply_scale();
|
||||
if (me.layer.style.animation_test) {
|
||||
|
||||
me.timer = maketimer(0.33, func me.animate() );
|
||||
me.timer.start();
|
||||
}
|
||||
}
|
||||
|
||||
var updateRun = func {
|
||||
# check if the current station is tuned or not - and style the symbol accordingly (color)
|
||||
me.apply_styling();
|
||||
me.draw();
|
||||
}
|
||||
|
||||
##
|
||||
# this method is REQUIRED (basically, the entry point for drawing - most others are just helpers)
|
||||
# this method is REQUIRED
|
||||
# check if the current station is tuned or not - and style the symbol accordingly (color)
|
||||
var draw = func {
|
||||
# print("DME:draw()");
|
||||
# Init: will set up the symbol if it isn't already
|
||||
if ( !me.is_initialized() )
|
||||
me.init_symbols();
|
||||
|
||||
# wrapper for custom styling, based on tuned/default colors (see lookup hash above)
|
||||
me.updateRun();
|
||||
me.apply_styling();
|
||||
};
|
||||
|
||||
|
|
|
@ -5,17 +5,9 @@ var parents = [SymbolLayer.Controller];
|
|||
var __self__ = caller(0)[0];
|
||||
SymbolLayer.Controller.add(name, __self__);
|
||||
SymbolLayer.add(name, {
|
||||
parents: [SymbolLayer],
|
||||
parents: [NavaidSymbolLayer],
|
||||
type: name, # Symbol type
|
||||
df_controller: __self__, # controller to use by default -- this one
|
||||
|
||||
df_style: {
|
||||
line_width: 3,
|
||||
scale_factor: 0.5,
|
||||
debug: 1,
|
||||
color_default: [0, 0.6, 0.85],
|
||||
}
|
||||
|
||||
});
|
||||
var new = func(layer) {
|
||||
var m = {
|
||||
|
@ -25,6 +17,8 @@ var new = func(layer) {
|
|||
listeners: [],
|
||||
query_type:'fix',
|
||||
};
|
||||
m.addVisibilityListener();
|
||||
|
||||
return m;
|
||||
};
|
||||
var del = func() {
|
||||
|
@ -33,10 +27,5 @@ var del = func() {
|
|||
removelistener(l);
|
||||
};
|
||||
|
||||
var searchCmd = func {
|
||||
printlog(_MP_dbg_lvl, "Running query:", me.query_type);
|
||||
var range = me.map.getRange();
|
||||
if (range == nil) return;
|
||||
return positioned.findWithinRange(me.map.getPosCoord(), range, me.query_type);
|
||||
};
|
||||
var searchCmd = NavaidSymbolLayer.make('fix');
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'FIX';
|
||||
var parents = [Symbol.Controller];
|
||||
var __self__ = caller(0)[0];
|
||||
Symbol.Controller.add(name, __self__);
|
||||
Symbol.registry[ name ].df_controller = __self__;
|
||||
var new = func(model) ; # this controller doesn't need an instance
|
||||
|
|
@ -5,45 +5,45 @@ var parents = [DotSym];
|
|||
var __self__ = caller(0)[0];
|
||||
DotSym.makeinstance( name, __self__ );
|
||||
|
||||
SymbolLayer.get(name).df_style = {
|
||||
line_width: 3,
|
||||
scale_factor: 1,
|
||||
font: "LiberationFonts/LiberationSans-Regular.ttf",
|
||||
font_color: [0,0,0],
|
||||
font_size: 28,
|
||||
color: [0, 0.6, 0.85],
|
||||
show_labels: 1,
|
||||
};
|
||||
|
||||
|
||||
var element_type = "group"; # we want a group, becomes "me.element"
|
||||
var icon_fix = nil;
|
||||
var text_fix = nil;
|
||||
|
||||
##
|
||||
# used during initialization to populate the symbol cache with a FIX symbol
|
||||
#
|
||||
var drawFIX = func(color, width) func(group) {
|
||||
|
||||
var symbol = group.createChild("path")
|
||||
var drawFIX = func(group) {
|
||||
group.createChild("path")
|
||||
.moveTo(-15,15)
|
||||
.lineTo(0,-15)
|
||||
.lineTo(15,15)
|
||||
.close()
|
||||
.setStrokeLineWidth(width)
|
||||
.setColor(color)
|
||||
.setScale(0.5,0.5); # FIXME: do proper LOD handling here - we need to scale according to current texture dimensions vs. original/design dimensions
|
||||
return symbol;
|
||||
.setStrokeLineWidth(line_width)
|
||||
.setColor(color);
|
||||
}
|
||||
|
||||
var icon_fix_cached = [
|
||||
SymbolCache32x32.add(
|
||||
name: "FIX",
|
||||
callback: drawFIX( color:[0, 0.6, 0.85], width:3 ), # TODO: use the style hash to encapsulate styling stuff
|
||||
draw_mode: SymbolCache.DRAW_CENTERED
|
||||
)
|
||||
];
|
||||
var cache = StyleableCacheable.new(
|
||||
name:name, draw_func: drawFIX,
|
||||
cache: SymbolCache32x32,
|
||||
draw_mode: SymbolCache.DRAW_CENTERED,
|
||||
relevant_keys: ["line_width", "color"],
|
||||
);
|
||||
|
||||
var draw = func {
|
||||
if (me.icon_fix != nil) return; # fix icon already initialized
|
||||
# initialize the fix symbol
|
||||
me.icon_fix = icon_fix_cached[0].render(me.element);
|
||||
var init = func {
|
||||
# initialize the cached fix symbol
|
||||
cache.render(me.element, me.style).setScale(me.style.scale_factor);
|
||||
|
||||
# non-cached stuff:
|
||||
# FIXME: labels need to be LOD-aware (i.e. aware of MapController range, so that we can hide/show them)
|
||||
me.text_fix = me.element.createChild("text")
|
||||
.setDrawMode( canvas.Text.TEXT )
|
||||
.setText(me.model.id)
|
||||
.setFont("LiberationFonts/LiberationSans-Regular.ttf") # TODO: encapsulate styling stuff
|
||||
.setFontSize(28) # TODO: encapsulate styling stuff
|
||||
.setTranslation(5,25);
|
||||
if (me.style.show_labels)
|
||||
me.text_fix = me.newText(me.model.id).setScale(me.style.scale_factor);
|
||||
}
|
||||
var draw = func;
|
||||
|
|
58
Nasal/canvas/map/FLT.lcontroller
Normal file
|
@ -0,0 +1,58 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'FLT';
|
||||
var parents = [SymbolLayer.Controller];
|
||||
var __self__ = caller(0)[0];
|
||||
SymbolLayer.Controller.add(name, __self__);
|
||||
SymbolLayer.add(name, {
|
||||
parents: [MultiSymbolLayer],
|
||||
append: func(idx, item, model) {
|
||||
while (size(me.list) <= idx) {
|
||||
append(me.list, nil);
|
||||
} if (me.list[idx] == nil) {
|
||||
me.list[idx] = me.add_sym(model);
|
||||
}
|
||||
append(model, item);
|
||||
var pos = me.controller.getpos(item);
|
||||
var cmd = me.list[idx].element._last_cmd == -1 ? canvas.Path.VG_MOVE_TO : canvas.Path.VG_LINE_TO;
|
||||
me.list[idx].element.addSegmentGeo(cmd, ["N"~pos[0],"E"~pos[1]]);
|
||||
#props.dump(me.list[idx].element._node);
|
||||
},
|
||||
type: name, # Symbol type
|
||||
df_controller: __self__, # controller to use by default -- this one
|
||||
add_sym: func(model) {
|
||||
return Symbol.new(me.type, me.group, me, model);
|
||||
},
|
||||
});
|
||||
var new = func(layer) {
|
||||
var m = {
|
||||
parents: [__self__],
|
||||
layer: layer,
|
||||
listeners: [],
|
||||
models: [],
|
||||
active_path: 0,
|
||||
};
|
||||
layer.searcher._equals = func(a,b) a == b;
|
||||
m.addVisibilityListener();
|
||||
|
||||
return m;
|
||||
};
|
||||
var del = func() {
|
||||
foreach (var l; me.listeners)
|
||||
removelistener(l);
|
||||
};
|
||||
|
||||
var searchCmd = func() {
|
||||
printlog(_MP_dbg_lvl, "Running query: FLT");
|
||||
var hist = aircraft.history();
|
||||
var path = hist.pathForHistory(1000);
|
||||
printlog(_MP_dbg_lvl, "FLT size: "~size(path));
|
||||
while (size(me.models) <= me.active_path) append(me.models, []);
|
||||
for (var i=size(me.models[me.active_path]); i<size(path); i+=1)
|
||||
me.layer.append(me.active_path, path[i], me.models[me.active_path]);
|
||||
# TODO: filter by in_range()?
|
||||
#debug.dump(me.models);
|
||||
return me.models;
|
||||
};
|
||||
var getpos = Symbol.Controller.getpos;
|
||||
|
19
Nasal/canvas/map/FLT.symbol
Normal file
|
@ -0,0 +1,19 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
|
||||
# Class things:
|
||||
var name = 'FLT';
|
||||
var parents = [LineSymbol];
|
||||
var __self__ = caller(0)[0];
|
||||
LineSymbol.makeinstance( name, __self__ );
|
||||
|
||||
SymbolLayer.get(name).df_style = { # style to use by default
|
||||
line_width: 5,
|
||||
color: [0,0,1]
|
||||
};
|
||||
|
||||
var init = func {
|
||||
me.element.setColor(me.layer.style.color)
|
||||
.setStrokeLineWidth(me.layer.style.line_width);
|
||||
me.needs_update = 0;
|
||||
};
|
||||
|
BIN
Nasal/canvas/map/Images/Circuitodiattesa.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
6742
Nasal/canvas/map/Images/HoldDirectEntry.svg
Normal file
After Width: | Height: | Size: 508 KiB |
6292
Nasal/canvas/map/Images/HoldParallelEntry.svg
Normal file
After Width: | Height: | Size: 474 KiB |
6289
Nasal/canvas/map/Images/HoldTeardropEntry.svg
Normal file
After Width: | Height: | Size: 474 KiB |
6565
Nasal/canvas/map/Images/HoldTeardropEntry2.svg
Normal file
After Width: | Height: | Size: 474 KiB |
|
@ -58,7 +58,7 @@
|
|||
transform="translate(-364.652,-344.745)">
|
||||
<g
|
||||
id="airplane"
|
||||
transform="translate(364.652,346.745)"
|
||||
transform="translate(320.73272,291.98516)"
|
||||
inkscape:label="#g3781">
|
||||
<path
|
||||
id="path3783"
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 5.8 MiB After Width: | Height: | Size: 5.8 MiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
@ -5,9 +5,10 @@ var parents = [SymbolLayer.Controller];
|
|||
var __self__ = caller(0)[0];
|
||||
SymbolLayer.Controller.add(name, __self__);
|
||||
SymbolLayer.add(name, {
|
||||
parents: [SymbolLayer],
|
||||
parents: [NavaidSymbolLayer],
|
||||
type: name, # Symbol type
|
||||
df_controller: __self__, # controller to use by default -- this one
|
||||
df_style: {},
|
||||
});
|
||||
var new = func(layer) {
|
||||
var m = {
|
||||
|
@ -17,6 +18,8 @@ var new = func(layer) {
|
|||
listeners: [],
|
||||
query_type:'ndb',
|
||||
};
|
||||
m.addVisibilityListener();
|
||||
|
||||
return m;
|
||||
};
|
||||
var del = func() {
|
||||
|
@ -24,10 +27,6 @@ var del = func() {
|
|||
removelistener(l);
|
||||
};
|
||||
|
||||
var searchCmd = func {
|
||||
printlog(_MP_dbg_lvl, "Running query:", me.query_type);
|
||||
var range = me.map.getRange();
|
||||
if (range == nil) return;
|
||||
return positioned.findWithinRange(me.map.getPosCoord(), range, me.query_type);
|
||||
};
|
||||
var searchCmd = NavaidSymbolLayer.make('ndb');
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'NDB';
|
||||
var parents = [Symbol.Controller];
|
||||
var __self__ = caller(0)[0];
|
||||
Symbol.Controller.add(name, __self__);
|
||||
Symbol.registry[ name ].df_controller = __self__;
|
||||
var new = func(model) ; # this controller doesn't need an instance
|
||||
var LayerController = SymbolLayer.Controller.registry[ name ];
|
||||
|
|
@ -1,19 +1,7 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'NDB';
|
||||
var parents = [DotSym];
|
||||
var __self__ = caller(0)[0];
|
||||
DotSym.makeinstance( name, __self__ );
|
||||
|
||||
var element_type = "group"; # we want a group, becomes "me.element", which we parse a SVG onto
|
||||
var svg_path = "/gui/dialogs/images/ndb_symbol.svg"; # speaking of path, this is our path to use
|
||||
var local_svg_path = nil; # track changes in the SVG's path
|
||||
|
||||
var draw = func {
|
||||
if (me.svg_path == me.local_svg_path) return;
|
||||
me.element.removeAllChildren();
|
||||
me.local_svg_path = me.svg_path;
|
||||
canvas.parsesvg(me.element, me.svg_path);
|
||||
me.inited = 1;
|
||||
};
|
||||
DotSym.makeinstance('NDB', {
|
||||
parents: [SVGSymbol],
|
||||
svg_path: "/gui/dialogs/images/ndb_symbol.svg",
|
||||
#cacheable: 1,
|
||||
});
|
||||
|
||||
|
|
54
Nasal/canvas/map/RTE.lcontroller
Normal file
|
@ -0,0 +1,54 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# TODO: this layer doesn't make sense to support for AI/MP traffic, because we don't currently have access to flightplan/routing info
|
||||
# that also applies to other layers like WPT or even navaid layers that handle station tuning based on local radio settings
|
||||
#
|
||||
# Class things:
|
||||
var name = 'RTE';
|
||||
var parents = [SymbolLayer.Controller];
|
||||
var __self__ = caller(0)[0];
|
||||
SymbolLayer.Controller.add(name, __self__);
|
||||
SymbolLayer.add(name, {
|
||||
parents: [MultiSymbolLayer],
|
||||
type: name, # Symbol type
|
||||
df_controller: __self__, # controller to use by default -- this one
|
||||
df_options: { # default configuration options
|
||||
active_node: "/autopilot/route-manager/active",
|
||||
current_wp_node: "/autopilot/route-manager/current-wp",
|
||||
}
|
||||
});
|
||||
var new = func(layer) {
|
||||
var m = {
|
||||
parents: [__self__],
|
||||
layer: layer,
|
||||
map: layer.map,
|
||||
listeners: [],
|
||||
};
|
||||
layer.searcher._equals = func(l,r) 0; # TODO: create model objects instead?
|
||||
append(m.listeners, setlistener(layer.options.active_node, func m.layer.update() ));
|
||||
m.addVisibilityListener();
|
||||
|
||||
return m;
|
||||
};
|
||||
var del = func() {
|
||||
foreach (var l; me.listeners)
|
||||
removelistener(l);
|
||||
};
|
||||
|
||||
var searchCmd = func {
|
||||
# FIXME: do we return the current route even if it isn't active?
|
||||
printlog(_MP_dbg_lvl, "Running query: ", name);
|
||||
var plans = []; # TODO: multiple flightplans?
|
||||
|
||||
# http://wiki.flightgear.org/Nasal_Flightplan
|
||||
var fp = flightplan();
|
||||
var fpSize = fp.getPlanSize();
|
||||
if (!getprop(me.layer.options.active_node)) fpSize = 0;
|
||||
var coords = [];
|
||||
for (var i=0; i<fpSize; i += 1) {
|
||||
var leg = fp.getWP(i);
|
||||
coords ~= leg.path();
|
||||
}
|
||||
append(plans, coords);
|
||||
return plans;
|
||||
};
|
||||
|
18
Nasal/canvas/map/RTE.symbol
Normal file
|
@ -0,0 +1,18 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
|
||||
# Class things:
|
||||
var name = 'RTE';
|
||||
var parents = [LineSymbol];
|
||||
var __self__ = caller(0)[0];
|
||||
LineSymbol.makeinstance( name, __self__ );
|
||||
|
||||
SymbolLayer.get(name).df_style = { # style to use by default
|
||||
line_width: 5,
|
||||
color: [1,0,1]
|
||||
};
|
||||
|
||||
var init = func {
|
||||
me.element.setColor(me.style.color)
|
||||
.setStrokeLineWidth(me.style.line_width);
|
||||
};
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# FIXME: this needs to be configurable via the ctor to optionally differentiate between MP and AI traffic, and exclude ground/sea traffic
|
||||
# or at least use a different, non-TCAS symbol for those (looking pretty weird ATM)
|
||||
#
|
||||
# Class things:
|
||||
var name = 'TFC';
|
||||
var parents = [SymbolLayer.Controller];
|
||||
var __self__ = caller(0)[0];
|
||||
SymbolLayer.Controller.add(name, __self__);
|
||||
SymbolLayer.add(name, {
|
||||
parents: [SymbolLayer],
|
||||
parents: [MultiSymbolLayer],
|
||||
type: name, # Symbol type
|
||||
df_controller: __self__, # controller to use by default -- this one
|
||||
df_style: { debug: 0 }, # style to use by default
|
||||
});
|
||||
|
||||
var model_root = props.globals.getNode("/ai/models/");
|
||||
|
@ -22,6 +24,8 @@ var new = func(layer) {
|
|||
searchCmd: searchCmd_default,
|
||||
};
|
||||
layer.searcher._equals = func(l,r) l.equals(r);
|
||||
m.addVisibilityListener();
|
||||
|
||||
return m;
|
||||
};
|
||||
var del = func() {
|
||||
|
@ -40,6 +44,7 @@ var TrafficModel = {
|
|||
id: id,
|
||||
node: node,
|
||||
pos: node.getNode("position",1),
|
||||
type: node.getName(),
|
||||
};
|
||||
return m;
|
||||
},
|
||||
|
@ -57,6 +62,13 @@ var TrafficModel = {
|
|||
get_alt: func() (me.getValue("position/altitude-ft") or 0),
|
||||
};
|
||||
|
||||
var get_alt_diff = func(model) {
|
||||
# debug.dump( keys(me) );
|
||||
var model_alt = model.get_alt();
|
||||
var alt = me.map.getAlt();
|
||||
if (alt == nil or model_alt == nil) return 0;
|
||||
return alt-model_alt;
|
||||
};
|
||||
|
||||
##
|
||||
# dummy/placeholder (will be overridden in ctor and set to the default callback)
|
||||
|
@ -66,6 +78,8 @@ var searchCmd_default = func {
|
|||
# TODO: this would be a good candidate for splitting across frames
|
||||
printlog(_MP_dbg_lvl, "Doing query: "~name);
|
||||
|
||||
if (me.map.getRange() == nil) return;
|
||||
|
||||
var result = [];
|
||||
var models = 0;
|
||||
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'TFC';
|
||||
var parents = [Symbol.Controller];
|
||||
var __self__ = caller(0)[0];
|
||||
Symbol.Controller.add(name, __self__);
|
||||
Symbol.registry[name].df_controller = __self__;
|
||||
var new = func(model, symbol) ; # this controller doesn't need an instance
|
||||
var get_alt_diff = func(model) {
|
||||
# debug.dump( keys(me) );
|
||||
var model_alt = model.get_alt();
|
||||
var alt = getprop("/position/altitude-ft"); # FIXME: hardcoded - right, we should probably generalize the "NDSourceDriver logic found in navdisplay.mfd and make it part of MapStructure
|
||||
if (alt == nil or model_alt == nil) return 0;
|
||||
return alt-model_alt;
|
||||
};
|
||||
|
|
@ -5,6 +5,17 @@ var parents = [DotSym];
|
|||
var __self__ = caller(0)[0];
|
||||
DotSym.makeinstance( name, __self__ );
|
||||
|
||||
SymbolLayer.get(name).df_style = { # style to use by default
|
||||
line_width: 3,
|
||||
scale_factor: 1,
|
||||
color_by_lvl: {
|
||||
3: [1,0,0], # resolution advisory
|
||||
2: [1,0.5,0], # traffic advisory
|
||||
1: [1,1,1], # proximate traffic
|
||||
},
|
||||
color_default: [1,1,1]
|
||||
};
|
||||
|
||||
var element_type = "group"; # we want a group, becomes "me.element"
|
||||
var text_tcas = nil;
|
||||
var icon_tcas = nil;
|
||||
|
@ -12,8 +23,12 @@ var arrow_tcas = [nil,nil];
|
|||
var arrow_type = nil;
|
||||
|
||||
var draw_tcas_arrow = nil;
|
||||
var color = nil;
|
||||
var threatLvl = 0e-0; # NaN to update even when threatLvl == nil
|
||||
|
||||
# TODO: how to integrate both styling and caching?
|
||||
var draw = func {
|
||||
# TODO: get rid of draw_tcas_arrow hacks
|
||||
if (draw_tcas_arrow == nil)
|
||||
draw_tcas_arrow = [
|
||||
draw_tcas_arrow_above_500,
|
||||
|
@ -23,7 +38,7 @@ var draw = func {
|
|||
# print("Drawing traffic for:", callsign );
|
||||
var threatLvl = me.model.get_threat_lvl();
|
||||
var vspeed = me.model.get_vspd();
|
||||
var altDiff = me.controller.get_alt_diff(me.model);
|
||||
var altDiff = me.layer.controller.get_alt_diff(me.model);
|
||||
# Init
|
||||
if (me.text_tcas == nil) {
|
||||
me.text_tcas = me.element.createChild("text")
|
||||
|
@ -49,48 +64,54 @@ var draw = func {
|
|||
me.arrow_tcas[arrow_type] = me.draw_tcas_arrow[arrow_type](me.element);
|
||||
me.arrow_tcas[arrow_type].show();
|
||||
}
|
||||
## TODO: threat level symbols should also be moved to *.draw files
|
||||
if (threatLvl != me.threatLvl) {
|
||||
me.threatLvl = threatLvl;
|
||||
## TODO: should threat level symbols also be moved to *.draw files?
|
||||
if (threatLvl == 3) {
|
||||
# resolution advisory
|
||||
me.icon_tcas.moveTo(-17,-17)
|
||||
.horiz(34)
|
||||
.vert(34)
|
||||
.horiz(-34)
|
||||
.close()
|
||||
.setColor(1,0,0)
|
||||
.setColorFill(1,0,0);
|
||||
me.text_tcas.setColor(1,0,0);
|
||||
me.arrow_tcas[me.arrow_type].setColor(1,0,0);
|
||||
.close();
|
||||
} elsif (threatLvl == 2) {
|
||||
# traffic advisory
|
||||
me.icon_tcas.moveTo(-17,0)
|
||||
.arcSmallCW(17,17,0,34,0)
|
||||
.arcSmallCW(17,17,0,-34,0)
|
||||
.setColor(1,0.5,0)
|
||||
.setColorFill(1,0.5,0);
|
||||
me.text_tcas.setColor(1,0.5,0);
|
||||
me.arrow_tcas[me.arrow_type].setColor(1,0.5,0);
|
||||
.arcSmallCW(17,17,0,-34,0);
|
||||
} elsif (threatLvl == 1) {
|
||||
# proximate traffic
|
||||
me.icon_tcas.moveTo(-10,0)
|
||||
.lineTo(0,-17)
|
||||
.lineTo(10,0)
|
||||
.lineTo(0,17)
|
||||
.close()
|
||||
.setColor(1,1,1)
|
||||
.setColorFill(1,1,1);
|
||||
me.text_tcas.setColor(1,1,1);
|
||||
me.arrow_tcas[me.arrow_type].setColor(1,1,1);
|
||||
.close();
|
||||
} else {
|
||||
# other traffic
|
||||
me.icon_tcas.moveTo(-10,0)
|
||||
.lineTo(0,-17)
|
||||
.lineTo(10,0)
|
||||
.lineTo(0,17)
|
||||
.close()
|
||||
.setColor(1,1,1);
|
||||
me.text_tcas.setColor(1,1,1);
|
||||
me.arrow_tcas[me.arrow_type].setColor(1,1,1);
|
||||
.close();
|
||||
}
|
||||
}
|
||||
var color = nil;
|
||||
if (threatLvl != nil)
|
||||
if ((var c = me.style.color_by_lvl[threatLvl]) != nil)
|
||||
var color = canvas._getColor(c);
|
||||
if (color == nil)
|
||||
color = canvas._getColor(me.style.color_default);
|
||||
if (me.color != color) {
|
||||
me.color = color;
|
||||
me.icon_tcas.setColor(color);
|
||||
me.text_tcas.setColor(color);
|
||||
me.arrow_tcas[me.arrow_type].setColor(color);
|
||||
if (num(threatLvl) == nil or (threatLvl < 1 or threatLvl > 3)) {
|
||||
color = [0,0,0,0];
|
||||
}
|
||||
me.icon_tcas.setColorFill(color);
|
||||
}
|
||||
if (me.style.scale_factor != me.element.getScale())
|
||||
me.element.setScale(me.style.scale_factor);
|
||||
};
|
||||
|
||||
|
|
|
@ -5,21 +5,11 @@ var parents = [SymbolLayer.Controller];
|
|||
var __self__ = caller(0)[0];
|
||||
SymbolLayer.Controller.add(name, __self__);
|
||||
SymbolLayer.add(name, {
|
||||
parents: [SymbolLayer],
|
||||
parents: [NavaidSymbolLayer],
|
||||
type: name, # Symbol type
|
||||
df_controller: __self__, # controller to use by default -- this one
|
||||
});
|
||||
var new = func(layer) {
|
||||
|
||||
if(0) {
|
||||
# TODO: generalize and move to MapStructure.nas
|
||||
var required_controllers = [ {name: 'query_range',type:'func'}, ];
|
||||
foreach(var c; required_controllers) {
|
||||
if (!contains(layer.map.controller, c.name) or typeof(layer.map.controller[c.name]) !=c.type)
|
||||
die("Required controller is missing/invalid:"~ c.name ~ ' ['~c.type~']' );
|
||||
}
|
||||
}
|
||||
|
||||
var m = {
|
||||
parents: [__self__],
|
||||
layer: layer,
|
||||
|
@ -38,6 +28,8 @@ if(0) {
|
|||
}
|
||||
#call(debug.dump, keys(layer));
|
||||
m.changed_freq(update:0);
|
||||
m.addVisibilityListener();
|
||||
|
||||
return m;
|
||||
};
|
||||
var del = func() {
|
||||
|
@ -59,10 +51,6 @@ var changed_freq = func(update=1) {
|
|||
me.active_vors[ navN.getIndex() ] = navN.getValue("frequencies/selected-mhz");
|
||||
if (update) me.layer.update();
|
||||
};
|
||||
var searchCmd = func {
|
||||
printlog(_MP_dbg_lvl, "Running query:", me.query_type);
|
||||
var range = me.map.getRange();
|
||||
if (range == nil) return;
|
||||
return positioned.findWithinRange(me.map.getPosCoord(), range, me.query_type);
|
||||
};
|
||||
|
||||
var searchCmd = NavaidSymbolLayer.make('vor');
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'VOR';
|
||||
var parents = [Symbol.Controller];
|
||||
var __self__ = caller(0)[0];
|
||||
Symbol.Controller.add(name, __self__);
|
||||
Symbol.registry[name].df_controller = __self__;
|
||||
var new = func(model) ; # this controller doesn't need an instance
|
||||
|
|
@ -5,33 +5,31 @@ var parents = [DotSym];
|
|||
var __self__ = caller(0)[0];
|
||||
DotSym.makeinstance( name, __self__ );
|
||||
|
||||
SymbolLayer.get(name).df_style = { # style to use by default
|
||||
line_width: 3,
|
||||
range_line_width: 3,
|
||||
radial_line_width: 3,
|
||||
range_dash_array: [5, 15, 5, 15, 5],
|
||||
radial_dash_array: [15, 5, 15, 5, 15],
|
||||
scale_factor: 1,
|
||||
active_color: [0, 1, 0],
|
||||
inactive_color: [0, 0.6, 0.85],
|
||||
};
|
||||
|
||||
var element_type = "group"; # we want a group, becomes "me.element"
|
||||
var icon_vor = nil; # a handle to the cached icon
|
||||
var range_vor = nil; # two elements that get drawn when needed
|
||||
var radial_vor = nil; # if one is nil, the other has to be nil
|
||||
var active = -1;
|
||||
|
||||
###
|
||||
# this function returns a new function that renders the symbol
|
||||
# into a canvas group
|
||||
# the signature of the first func should contain all customizable
|
||||
# parameters (such as color/width etc)
|
||||
#
|
||||
# you will typically have one such function for each cacheable symbol
|
||||
# and only call it once during initialization (once for each cached style/variation)
|
||||
# the signature of the 2nd func remains untouched, it's internally used
|
||||
##
|
||||
# Callback for drawing a new VOR based on styling
|
||||
#
|
||||
# NOTE: callbacks that create symbols for caching MUST render them using a
|
||||
# transparent background !!
|
||||
#
|
||||
var drawVOR = func(color, width=3) return func(group) {
|
||||
|
||||
# init_calls += 1; # TODO: doing this here is too fragile, this should be done by MapStructure ...
|
||||
# if( init_calls >= 2)
|
||||
# print("Warning: draw() routine for a cached element has been called more than once, should only happen during reset/reinit");
|
||||
|
||||
# print("drawing vor");
|
||||
var symbol = group.createChild("path")
|
||||
var drawVOR = func(group) {
|
||||
group.createChild("path")
|
||||
.moveTo(-15,0)
|
||||
.lineTo(-7.5,12.5)
|
||||
.lineTo(7.5,12.5)
|
||||
|
@ -39,35 +37,16 @@ var drawVOR = func(color, width=3) return func(group) {
|
|||
.lineTo(7.5,-12.5)
|
||||
.lineTo(-7.5,-12.5)
|
||||
.close()
|
||||
.setStrokeLineWidth(width)
|
||||
.setColor( color );
|
||||
|
||||
return symbol; # make this explicit, not everybody knows Nasal internals inside/out ...
|
||||
.setStrokeLineWidth(line_width)
|
||||
.setColor(color);
|
||||
};
|
||||
|
||||
##
|
||||
# a vector with pre-created symbols that are cached during initialization
|
||||
# this needs to be done for each variation of each symbol, i.e. color/width etc
|
||||
# note that scaling should not be done here, likewise for positioning via setGeoPosition()
|
||||
#
|
||||
# FIXME: We still need to encode styling information as part of the key/name lookup
|
||||
# so that the cache entry's name contains styling info, or different maps/layers may
|
||||
# overwrite their symbols
|
||||
#
|
||||
# icon_vor_cache[0] - inactive symbol
|
||||
# icon_vor_cache[1] - active symbol
|
||||
var icon_vor_cached = [
|
||||
SymbolCache32x32.add(
|
||||
name: "VOR-INACTIVE",
|
||||
callback: drawVOR( color:[0, 0.6, 0.85], width:3 ),
|
||||
draw_mode: SymbolCache.DRAW_CENTERED
|
||||
),
|
||||
SymbolCache32x32.add(
|
||||
name: "VOR-ACTIVE",
|
||||
callback: drawVOR( color:[0, 1, 0], width:3 ),
|
||||
draw_mode: SymbolCache.DRAW_CENTERED
|
||||
),
|
||||
];
|
||||
var cache = StyleableCacheable.new(
|
||||
name:name, draw_func: drawVOR,
|
||||
cache: SymbolCache32x32,
|
||||
draw_mode: SymbolCache.DRAW_CENTERED,
|
||||
relevant_keys: ["line_width", "color"],
|
||||
);
|
||||
|
||||
##
|
||||
# TODO: make this a part of the framework, for use by other layers/symbols etc
|
||||
|
@ -84,16 +63,14 @@ var controller_check = func(layer, controller, arg...) {
|
|||
}
|
||||
|
||||
var draw = func {
|
||||
# Init
|
||||
if (0 and me.model.id == "SAC") # hack to test isActive() around KSMF
|
||||
setprop("instrumentation/nav[0]/frequencies/selected-mhz", me.model.frequency/100);
|
||||
var active = controller_check(me.layer, 'isActive', me.model);
|
||||
#print(me.model.id,"/", me.model.frequency/100, " isActive:", active);
|
||||
if (active != me.active) {
|
||||
#print("VOR.symbol: active != me.active: del/render event triggered");
|
||||
if (me.icon_vor != nil) me.icon_vor.del();
|
||||
# look up the correct symbol from the cache and render it into the group as a raster image
|
||||
me.icon_vor = icon_vor_cached[active or 0].render(me.element);
|
||||
#me.icon_vor = icon_vor_cached[active or 0].render(me.element);
|
||||
me.style.color = active ? me.style.active_color : me.style.inactive_color;
|
||||
me.icon_vor = cache.render(me.element, me.style).setScale(me.style.scale_factor);
|
||||
|
||||
me.active = active; # update me.active flag
|
||||
}
|
||||
# Update (also handles non-cached stuff, such as text labels or animations)
|
||||
|
@ -101,24 +78,24 @@ var draw = func {
|
|||
if (active) {
|
||||
if (me.range_vor == nil) {
|
||||
# initialize me.range and me.radial_vor
|
||||
var rangeNm = me.layer.map.getRange();
|
||||
var rangeNm = me.map.getRange();
|
||||
# print("VOR is tuned:", me.model.id);
|
||||
var radius = (me.model.range_nm/rangeNm)*345;
|
||||
me.range_vor = me.element.createChild("path")
|
||||
.moveTo(-radius,0)
|
||||
.arcSmallCW(radius,radius,0,2*radius,0)
|
||||
.arcSmallCW(radius,radius,0,-2*radius,0)
|
||||
.setStrokeLineWidth(3)
|
||||
.setStrokeDashArray([5, 15, 5, 15, 5])
|
||||
.setColor(0,1,0);
|
||||
.setStrokeLineWidth(me.style.range_line_width)
|
||||
.setStrokeDashArray(me.style.range_dash_array)
|
||||
.setColor(me.style.active_color);
|
||||
|
||||
var course = me.layer.map.controller.get_tuned_course(me.model.frequency/100);
|
||||
var course = me.map.controller.get_tuned_course(me.model.frequency/100);
|
||||
me.radial_vor = me.element.createChild("path")
|
||||
.moveTo(0,-radius)
|
||||
.vert(2*radius)
|
||||
.setStrokeLineWidth(3)
|
||||
.setStrokeDashArray([15, 5, 15, 5, 15])
|
||||
.setColor(0,1,0)
|
||||
.setStrokeLineWidth(me.style.radial_line_width)
|
||||
.setStrokeDashArray(me.style.radial_dash_array)
|
||||
.setColor(me.style.active_color)
|
||||
.setRotation(course*D2R);
|
||||
}
|
||||
me.range_vor.show();
|
||||
|
|
|
@ -5,17 +5,26 @@ var parents = [SymbolLayer.Controller];
|
|||
var __self__ = caller(0)[0];
|
||||
SymbolLayer.Controller.add(name, __self__);
|
||||
SymbolLayer.add(name, {
|
||||
parents: [SymbolLayer],
|
||||
parents: [MultiSymbolLayer],
|
||||
type: name, # Symbol type
|
||||
df_controller: __self__, # controller to use by default -- this one
|
||||
df_options: { # default configuration options
|
||||
active_node: "/autopilot/route-manager/active",
|
||||
current_wp_node: "/autopilot/route-manager/current-wp",
|
||||
}
|
||||
});
|
||||
var new = func(layer) {
|
||||
var m = {
|
||||
parents: [__self__],
|
||||
layer: layer,
|
||||
options: layer.options,
|
||||
map: layer.map,
|
||||
listeners: [],
|
||||
};
|
||||
layer.searcher._equals = func(l,r) l.equals(r);
|
||||
append(m.listeners, setlistener(layer.options.active_node, func m.layer.update() ));
|
||||
m.addVisibilityListener();
|
||||
|
||||
return m;
|
||||
};
|
||||
var del = func() {
|
||||
|
@ -24,15 +33,37 @@ var del = func() {
|
|||
removelistener(l);
|
||||
};
|
||||
|
||||
var WPT_model = {
|
||||
new: func(fp, idx, offset=0) {
|
||||
var m = { parents:[WPT_model], idx:(idx+offset) };
|
||||
var wp = fp.getWP(idx);
|
||||
|
||||
m.name = wp.wp_name;
|
||||
var alt = wp.alt_cstr;
|
||||
if (alt != 0)
|
||||
m.name ~= "\n"~alt;
|
||||
|
||||
if (idx) var n = wp.path()[-1];
|
||||
else var n = fp.getWP(1).path()[0];
|
||||
(m.lat,m.lon) = (n.lat,n.lon);
|
||||
return m;
|
||||
},
|
||||
equals: func(other) {
|
||||
# this is set on symbol init, so use this for equality...
|
||||
me.name == other.name
|
||||
},
|
||||
};
|
||||
|
||||
var searchCmd = func {
|
||||
printlog(_MP_dbg_lvl, "Running query: "~name);
|
||||
|
||||
if (!getprop(me.options.active_node)) return [];
|
||||
|
||||
var fp = flightplan();
|
||||
var fpSize = fp.getPlanSize();
|
||||
var result = [];
|
||||
for (var i = 1; i <fpSize; i+=1)
|
||||
append(result, fp.getWP(i).path()[0] );
|
||||
|
||||
for (var i = 0; i < fpSize; i+=1)
|
||||
append(result, WPT_model.new(fp, i));
|
||||
return result;
|
||||
};
|
||||
|
||||
|
|
|
@ -5,9 +5,26 @@ var parents = [Symbol.Controller];
|
|||
var __self__ = caller(0)[0];
|
||||
Symbol.Controller.add(name, __self__);
|
||||
Symbol.registry[ name ].df_controller = __self__;
|
||||
var new = func(model) ; # this controller doesn't need an instance
|
||||
var LayerController = SymbolLayer.Controller.registry[ name ];
|
||||
var isActive = func(model) LayerController.a_instance.isActive(model);
|
||||
var query_range = func()
|
||||
die( name~".scontroller.query_range /MUST/ be provided by implementation" );
|
||||
var last_idx = nil;
|
||||
|
||||
var new = func(symbol, model) {
|
||||
var m = {
|
||||
parents:[__self__],
|
||||
listeners: [],
|
||||
symbol: symbol,
|
||||
model: model,
|
||||
};
|
||||
append(m.listeners, setlistener(symbol.layer.options.current_wp_node, func(n) {
|
||||
var n = n.getValue();
|
||||
if (m.last_idx == model.idx or n == model.idx)
|
||||
symbol.update();
|
||||
m.last_idx = n;
|
||||
}, 0, 0));
|
||||
m.last_idx = getprop(symbol.layer.options.current_wp_node);
|
||||
|
||||
return m;
|
||||
}
|
||||
var del = func() {
|
||||
foreach (var l; me.listeners)
|
||||
removelistener(l);
|
||||
}
|
||||
|
|
|
@ -5,14 +5,22 @@ var parents = [DotSym];
|
|||
var __self__ = caller(0)[0];
|
||||
DotSym.makeinstance( name, __self__ );
|
||||
|
||||
var element_type = "group"; # we want a group, becomes "me.element"
|
||||
var base = nil;
|
||||
var text_wps = nil;
|
||||
SymbolLayer.get(name).df_style = { # style to use by default
|
||||
line_width: 3,
|
||||
scale_factor: 1,
|
||||
font: "LiberationFonts/LiberationSans-Regular.ttf",
|
||||
font_size: 28,
|
||||
font_color: [1,0,1],
|
||||
active_color: [1,0,1],
|
||||
inactive_color: [1,1,1],
|
||||
};
|
||||
|
||||
var draw = func {
|
||||
if (me.base != nil) return;
|
||||
me.base = me.element.createChild("path")
|
||||
.setStrokeLineWidth(3)
|
||||
var element_type = "group"; # we want a group, becomes "me.element"
|
||||
var active = nil;
|
||||
|
||||
var init = func {
|
||||
me.path = me.element.createChild("path")
|
||||
.setStrokeLineWidth(me.style.line_width)
|
||||
.moveTo(0,-25)
|
||||
.lineTo(-5,-5)
|
||||
.lineTo(-25,0)
|
||||
|
@ -22,14 +30,23 @@ var draw = func {
|
|||
.lineTo(25,0)
|
||||
.lineTo(5,-5)
|
||||
.setColor(1,1,1)
|
||||
.close();
|
||||
.close()
|
||||
.setScale(me.style.scale_factor);
|
||||
|
||||
me.text_wps = wpt_grp.createChild("text")
|
||||
.setDrawMode( canvas.Text.TEXT )
|
||||
.setText(name)
|
||||
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
|
||||
.setFontSize(28)
|
||||
.setTranslation(25,35)
|
||||
.setColor(1,0,1);
|
||||
me.newText(me.model.name, me.style.font_color).setTranslation(25,35)
|
||||
.setScale(me.style.scale_factor);
|
||||
|
||||
me.draw();
|
||||
};
|
||||
var draw = func {
|
||||
var active = (getprop(me.options.current_wp_node) == me.model.idx);
|
||||
if (active != me.active) {
|
||||
me.path.setColor(
|
||||
active ?
|
||||
me.style.active_color :
|
||||
me.style.inactive_color
|
||||
);
|
||||
me.active = active;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
76
Nasal/canvas/map/WXR.lcontroller
Normal file
|
@ -0,0 +1,76 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'WXR';
|
||||
var parents = [SymbolLayer.Controller];
|
||||
var __self__ = caller(0)[0];
|
||||
SymbolLayer.Controller.add(name, __self__);
|
||||
SymbolLayer.add(name, {
|
||||
parents: [MultiSymbolLayer],
|
||||
type: name, # Symbol type
|
||||
df_controller: __self__, # controller to use by default -- this one
|
||||
});
|
||||
|
||||
var new = func(layer) {
|
||||
var m = {
|
||||
parents: [__self__],
|
||||
layer: layer,
|
||||
map: layer.map,
|
||||
listeners: [],
|
||||
searchCmd: searchCmd_default,
|
||||
};
|
||||
layer.searcher._equals = func(l,r) l.equals(r);
|
||||
m.addVisibilityListener();
|
||||
|
||||
return m;
|
||||
};
|
||||
var del = func() {
|
||||
#print(name~".lcontroller.del()");
|
||||
foreach (var l; me.listeners)
|
||||
removelistener(l);
|
||||
};
|
||||
|
||||
##
|
||||
# dummy/placeholder (will be overridden in ctor and set to the default callback)
|
||||
var searchCmd = func;
|
||||
|
||||
# FIXME: TFC/WXR seems only to be updated on range change/setPos currently ?
|
||||
var searchCmd_default = func {
|
||||
# print("WXR running");
|
||||
if (me.map.getRange() == nil) return;
|
||||
|
||||
printlog(_MP_dbg_lvl, "Doing query: "~name);
|
||||
var results = [];
|
||||
foreach (var n; props.globals.getNode("/instrumentation/wxradar",1).getChildren("storm")) {
|
||||
|
||||
# Model 3 degree radar beam
|
||||
var stormLat = n.getNode("latitude-deg").getValue();
|
||||
var stormLon = n.getNode("longitude-deg").getValue();
|
||||
|
||||
print("processing storm at:",stormLat,'/',stormLon);
|
||||
|
||||
# FIXME: once ported to MapStructure, these should use the encapsulated "aircraft source"/driver stuff
|
||||
var acLat = getprop("/position/latitude-deg");
|
||||
var acLon = getprop("/position/longitude-deg");
|
||||
var stormGeo = geo.Coord.new();
|
||||
var acGeo = geo.Coord.new();
|
||||
|
||||
stormGeo.set_latlon(stormLat, stormLon);
|
||||
acGeo.set_latlon(acLat, acLon);
|
||||
|
||||
var directDistance = acGeo.direct_distance_to(stormGeo);
|
||||
var beamH = 0.1719 * directDistance; # M2FT * tan(3deg)
|
||||
var beamBase = getprop("position/altitude-ft") - beamH;
|
||||
|
||||
if (n.getNode("top-altitude-ft").getValue() > beamBase) {
|
||||
var tmp = geo.Coord.new();
|
||||
tmp.set_latlon(stormLat, stormLon);
|
||||
tmp.radiusNm = n.getNode("radius-nm").getValue();
|
||||
tmp._node = n;
|
||||
tmp.equals = func(r) me._node.equals(r._node);
|
||||
append(results, tmp);
|
||||
}
|
||||
} # foreach
|
||||
|
||||
#print("WXR results:", size(results));
|
||||
return results;
|
||||
} # searchCmd_default
|
23
Nasal/canvas/map/WXR.symbol
Normal file
|
@ -0,0 +1,23 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'WXR'; # storms/weather layer for LW/AW (use thunderstom scenario for testing)
|
||||
var parents = [DotSym];
|
||||
var __self__ = caller(0)[0];
|
||||
DotSym.makeinstance( name, __self__ );
|
||||
|
||||
var element_type = "group"; # we want a group, becomes "me.element"
|
||||
|
||||
# TODO: how to integrate both styling and caching?
|
||||
var draw = func {
|
||||
# TODO: once this works properly, consider using caching here
|
||||
# FIXME: scaling seems way off in comparison with the navdisplay - i.e. hard-coded assumptions about texture size/view port ?
|
||||
me.element.createChild("image")
|
||||
.setFile("Nasal/canvas/map/Images/storm.png")
|
||||
.setSize(128*me.model.radiusNm,128*me.model.radiusNm)
|
||||
.setTranslation(-64*me.model.radiusNm,-64*me.model.radiusNm)
|
||||
.setCenter(0,0);
|
||||
# .setScale(0.3);
|
||||
# TODO: overlapping storms should probably set their z-index according to altitudes
|
||||
|
||||
};
|
||||
|
|
@ -18,12 +18,17 @@ SOURCES["main"] = {
|
|||
|
||||
# Layers which get updated every frame
|
||||
var update_instant = [
|
||||
"TFC",
|
||||
"APS",
|
||||
];
|
||||
# Update at a slightly lower rate, but still
|
||||
# unconditionally
|
||||
var update_quickly = [
|
||||
"TFC", "FLT",
|
||||
];
|
||||
|
||||
var new = func(map, source='main') {
|
||||
if (source != 'main')
|
||||
die ("AI/MP traffic not yet supported (WIP)!");
|
||||
if (!contains(SOURCES, source))
|
||||
__die("AI/MP traffic not yet supported (WIP)!");
|
||||
|
||||
var m = {
|
||||
parents: [__self__],
|
||||
|
@ -32,42 +37,56 @@ var new = func(map, source='main') {
|
|||
_pos: nil, _time: nil, _range: nil,
|
||||
};
|
||||
m.timer1 = maketimer(0, m, update_pos);
|
||||
m.timer2 = maketimer(0, m, update_layers);
|
||||
m.timer1.start();
|
||||
m.timer2.start();
|
||||
m.timer2 = maketimer(0.4, m, update_layers);
|
||||
m.start();
|
||||
m.update_pos();
|
||||
return m;
|
||||
};
|
||||
var del = func(map) {
|
||||
if (map != me.map) die();
|
||||
var start = func() {
|
||||
me.timer1.start();
|
||||
me.timer2.start();
|
||||
};
|
||||
var stop = func() {
|
||||
me.timer1.stop();
|
||||
me.timer2.stop();
|
||||
};
|
||||
var del = func(map) {
|
||||
if (map != me.map) die();
|
||||
me.stop();
|
||||
};
|
||||
|
||||
# Controller methods
|
||||
var update_pos = func {
|
||||
var (lat,lon) = me.source.getPosition();
|
||||
me.map.setPos(lat:lat, lon:lon,
|
||||
hdg:getprop("/orientation/heading-deg"),
|
||||
hdg:me.source.getHeading(),
|
||||
alt:me.source.getAltitude());
|
||||
foreach (var t; update_instant)
|
||||
if ((var l=me.map.getLayer(t)) != nil)
|
||||
l.update();
|
||||
};
|
||||
var update_layers = func {
|
||||
var do_all = 1;
|
||||
var pos = me.map.getPosCoord();
|
||||
var time = systime();
|
||||
var range = me.map.getRange();
|
||||
if (me._pos == nil)
|
||||
me._pos = geo.Coord.new(pos);
|
||||
# Always update if range changed
|
||||
# FIXME: FIX doesn't update unless range is changed?
|
||||
elsif (range == me._range) {
|
||||
var dist_m = me._pos.direct_distance_to(pos);
|
||||
# 2 NM until we update again
|
||||
if (dist_m < 2 * NM2M) return;
|
||||
if (dist_m < 2 * NM2M) do_all = 0;
|
||||
# Update at most every 4 seconds to avoid excessive stutter:
|
||||
elsif (time - me._time < 4) return;
|
||||
elsif (time - me._time < 4) do_all = 0;
|
||||
}
|
||||
if (!do_all) {
|
||||
foreach (var t; update_quickly)
|
||||
if ((var l=me.map.getLayer(t)) != nil)
|
||||
l.update();
|
||||
return;
|
||||
} else
|
||||
printlog(_MP_dbg_lvl, "update aircraft position");
|
||||
var (x,y,z) = pos.xyz();
|
||||
me._pos.set_xyz(x,y,z);
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
###
|
||||
#
|
||||
#
|
||||
|
||||
var draw_dme = func (group, dme, controller=nil, lod=0) {
|
||||
var lat = dme.lat;
|
||||
var lon = dme.lon;
|
||||
var name = dme.id;
|
||||
var freq = dme.frequency;
|
||||
|
||||
var dme_grp = group.createChild("group","dme");
|
||||
var icon_dme = dme_grp .createChild("path", name)
|
||||
.moveTo(-15,0)
|
||||
.line(-12.5,-7.5)
|
||||
.line(7.5,-12.5)
|
||||
.line(12.5,7.5)
|
||||
.lineTo(7.5,-12.5)
|
||||
.line(12.5,-7.5)
|
||||
.line(7.5,12.5)
|
||||
.line(-12.5,7.5)
|
||||
.lineTo(15,0)
|
||||
.lineTo(7.5,12.5)
|
||||
.vert(14.5)
|
||||
.horiz(-14.5)
|
||||
.vert(-14.5)
|
||||
.close()
|
||||
.setStrokeLineWidth(3)
|
||||
.setColor(0,0.6,0.85);
|
||||
dme_grp.setGeoPosition(lat, lon)
|
||||
.set("z-index",3);
|
||||
|
||||
if( controller != nil and controller['is_tuned'](freq))
|
||||
icon_dme.setColor(0,1,0);
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
var DMELayer = {};
|
||||
DMELayer.new = func(group,name,controller=nil) {
|
||||
var m=Layer.new(group, name, DMEModel, controller);
|
||||
m.setDraw (func draw_layer(layer:m, callback: draw_dme, lod:0) );
|
||||
return m;
|
||||
}
|
||||
|
||||
register_layer("dme", DMELayer);
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
var DMEModel = {};
|
||||
DMEModel.new = func make( LayerModel, DMEModel );
|
||||
|
||||
DMEModel.init = func {
|
||||
me._view.reset();
|
||||
|
||||
if(0) {
|
||||
debug.dump( me._controller );
|
||||
print(typeof(
|
||||
me._controller.query_range()
|
||||
));
|
||||
}
|
||||
|
||||
var results = positioned.findWithinRange(me._controller.query_range() ,"dme");
|
||||
foreach(result; results) {
|
||||
me.push(result);
|
||||
}
|
||||
|
||||
me.notifyView();
|
||||
}
|
|
@ -9,7 +9,7 @@ FixModel.init = func {
|
|||
var numNum = 0;
|
||||
foreach(result; results) {
|
||||
# Skip airport fixes
|
||||
if(string.match(result.id,"*[^0-9]")) {
|
||||
if(string.match(result.id,"*[^0-9]")) { # this may need to be moved to FIX.lcontroller?
|
||||
me.push(result);
|
||||
numNum = numNum + 1;
|
||||
}
|
||||
|
|
|
@ -3,605 +3,24 @@
|
|||
# See: http://wiki.flightgear.org/Canvas_ND_Framework
|
||||
# ==============================================================================
|
||||
|
||||
##
|
||||
# do we really need to keep track of each drawable here ??
|
||||
var i = 0;
|
||||
|
||||
|
||||
##
|
||||
# pseudo DSL-ish: use these as placeholders in the config hash below
|
||||
var ALWAYS = func 1;
|
||||
var NOTHING = func nil;
|
||||
|
||||
##
|
||||
# so that we only need to update a single line ...
|
||||
# this file contains a hash that declares features in a generic fashion
|
||||
# we want to get rid of the bloated update() method sooner than later
|
||||
# PLEASE DO NOT ADD any code to update() !!
|
||||
# Instead, help clean up the file and move things over to the navdisplay.styles file
|
||||
#
|
||||
var trigger_update = func(layer) layer._model.init();
|
||||
|
||||
##
|
||||
# TODO: move ND-specific implementation details into this lookup hash
|
||||
# so that other aircraft and ND types can be more easily supported
|
||||
# This is the only sane way to keep on generalizing the framework, so that we can
|
||||
# also support different makes/models of NDs in the future
|
||||
#
|
||||
# any aircraft-specific ND behavior should be wrapped here,
|
||||
# to isolate/decouple things in the generic NavDisplay class
|
||||
# a huge bloated update() method is going to make that basically IMPOSSIBLE
|
||||
#
|
||||
# TODO: move this to an XML config file
|
||||
#
|
||||
var NDStyles = {
|
||||
##
|
||||
# this configures the 744 ND to help generalize the NavDisplay class itself
|
||||
'Boeing': {
|
||||
font_mapper: func(family, weight) {
|
||||
if( family == "Liberation Sans" and weight == "normal" )
|
||||
return "LiberationFonts/LiberationSans-Regular.ttf";
|
||||
},
|
||||
|
||||
# where all the symbols are stored
|
||||
# TODO: SVG elements should be renamed to use boeing/airbus prefix
|
||||
# aircraft developers should all be editing the same ND.svg image
|
||||
# the code can deal with the differences now
|
||||
svg_filename: "Nasal/canvas/map/boeingND.svg",
|
||||
##
|
||||
## this loads and configures existing layers (currently, *.layer files in Nasal/canvas/map)
|
||||
##
|
||||
|
||||
layers: [
|
||||
{ name:'FIX', isMapStructure:1, update_on:['toggle_range','toggle_waypoints'],
|
||||
# FIXME: this is a really ugly place for controller code
|
||||
predicate: func(nd, layer) {
|
||||
# print("Running fix layer predicate");
|
||||
# toggle visibility here
|
||||
var visible=nd.get_switch('toggle_waypoints') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
|
||||
layer.group.setVisible( nd.get_switch('toggle_waypoints') );
|
||||
if (visible) {
|
||||
#print("Updating MapStructure ND layer: FIX");
|
||||
# (Hopefully) smart update
|
||||
layer.update();
|
||||
}
|
||||
}, # end of layer update predicate
|
||||
}, # end of FIX layer
|
||||
|
||||
# Should redraw every 10 seconds
|
||||
{ name:'storms', update_on:['toggle_range','toggle_weather','toggle_display_mode'],
|
||||
predicate: func(nd, layer) {
|
||||
# print("Running fixes predicate");
|
||||
var visible=nd.get_switch('toggle_weather') and nd.get_switch('toggle_display_mode') != "PLAN";
|
||||
if (visible) {
|
||||
#print("storms update requested!");
|
||||
trigger_update( layer );
|
||||
}
|
||||
layer._view.setVisible(visible);
|
||||
}, # end of layer update predicate
|
||||
}, # end of storms layer
|
||||
|
||||
{ name:'airplaneSymbol', disabled:1, update_on:['toggle_display_mode'],
|
||||
predicate: func(nd, layer) {
|
||||
var visible = nd.get_switch('toggle_display_mode') == "PLAN";
|
||||
if (visible) {
|
||||
trigger_update( layer );
|
||||
} layer._view.setVisible(visible);
|
||||
},
|
||||
},
|
||||
{ name:'APS', isMapStructure:1, update_on:['toggle_display_mode'],
|
||||
predicate: func(nd, layer) {
|
||||
var visible = nd.get_switch('toggle_display_mode') == "PLAN";
|
||||
layer.group.setVisible( visible );
|
||||
if (visible) {
|
||||
layer.update();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{ name:'APT', isMapStructure:1, update_on:['toggle_range','toggle_airports','toggle_display_mode'],
|
||||
predicate: func(nd, layer) {
|
||||
# toggle visibility here
|
||||
var visible=nd.get_switch('toggle_airports') and nd.in_mode('toggle_display_mode', ['MAP']);
|
||||
layer.group.setVisible( visible );
|
||||
if (visible) {
|
||||
#print("Updating MapStructure ND layer: APT");
|
||||
layer.update();
|
||||
}
|
||||
}, # end of layer update predicate
|
||||
}, # end of APT layer
|
||||
|
||||
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
|
||||
{ name:'VOR', isMapStructure:1, update_on:['toggle_range','toggle_stations','toggle_display_mode'],
|
||||
# FIXME: this is a really ugly place for controller code
|
||||
predicate: func(nd, layer) {
|
||||
# toggle visibility here
|
||||
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
|
||||
layer.group.setVisible( visible );
|
||||
if (visible) {
|
||||
#print("Updating MapStructure ND layer: VOR");
|
||||
layer.update();
|
||||
}
|
||||
}, # end of layer update predicate
|
||||
}, # end of VOR layer
|
||||
|
||||
{ name:'DME', isMapStructure:1, update_on:['toggle_range','toggle_stations'],
|
||||
# FIXME: this is a really ugly place for controller code
|
||||
predicate: func(nd, layer) {
|
||||
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
|
||||
# toggle visibility here
|
||||
layer.group.setVisible( visible );
|
||||
if (visible) {
|
||||
#print("Updating MapStructure ND layer: DME");
|
||||
layer.update();
|
||||
}
|
||||
}, # end of layer update predicate
|
||||
}, # end of DME layer
|
||||
|
||||
{ name:'TFC', isMapStructure:1, update_on:['toggle_range','toggle_traffic'],
|
||||
predicate: func(nd, layer) {
|
||||
var visible = nd.get_switch('toggle_traffic');
|
||||
layer.group.setVisible( visible );
|
||||
if (visible) {
|
||||
#print("Updating MapStructure ND layer: TFC");
|
||||
layer.update();
|
||||
}
|
||||
}, # end of layer update predicate
|
||||
}, # end of traffic layer
|
||||
|
||||
{ name:'runway-nd', update_on:['toggle_range','toggle_display_mode'],
|
||||
predicate: func(nd, layer) {
|
||||
var visible = (nd.rangeNm() <= 40) and getprop("autopilot/route-manager/active") and nd.in_mode('toggle_display_mode', ['MAP','PLAN']) ;
|
||||
if (visible)
|
||||
trigger_update( layer ); # clear & redraw
|
||||
layer._view.setVisible( visible );
|
||||
}, # end of layer update predicate
|
||||
}, # end of airports-nd layer
|
||||
|
||||
{ name:'route', update_on:['toggle_range','toggle_display_mode'],
|
||||
predicate: func(nd, layer) {
|
||||
var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN']));
|
||||
if (visible)
|
||||
trigger_update( layer ); # clear & redraw
|
||||
layer._view.setVisible( visible );
|
||||
}, # end of layer update predicate
|
||||
}, # end of route layer
|
||||
|
||||
## add other layers here, layer names must match the registered names as used in *.layer files for now
|
||||
## this will all change once we're using Philosopher's MapStructure framework
|
||||
|
||||
], # end of vector with configured layers
|
||||
|
||||
# This is where SVG elements are configured by providing "behavior" hashes, i.e. for animations
|
||||
|
||||
# to animate each SVG symbol, specify behavior via callbacks (predicate, and true/false implementation)
|
||||
# SVG identifier, callback etc
|
||||
# TODO: update_on([]), update_mode (update() vs. timers/listeners)
|
||||
# TODO: support putting symbols on specific layers
|
||||
features: [
|
||||
{
|
||||
# TODO: taOnly doesn't need to use getprop polling in update(), use a listener instead!
|
||||
id: 'taOnly', # the SVG ID
|
||||
impl: { # implementation hash
|
||||
init: func(nd, symbol), # for updateCenter stuff, called during initialization in the ctor
|
||||
predicate: func(nd) getprop("instrumentation/tcas/inputs/mode") == 2, # the condition
|
||||
is_true: func(nd) nd.symbols.taOnly.show(), # if true, run this
|
||||
is_false: func(nd) nd.symbols.taOnly.hide(), # if false, run this
|
||||
}, # end of taOnly behavior/callbacks
|
||||
}, # end of taOnly
|
||||
{
|
||||
id: 'tas',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.aircraft_source.get_spd() > 100,
|
||||
is_true: func(nd) {
|
||||
nd.symbols.tas.setText(sprintf("%3.0f",getprop("/velocities/airspeed-kt") ));
|
||||
nd.symbols.tas.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.tas.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'tasLbl',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.aircraft_source.get_spd() > 100,
|
||||
is_true: func(nd) nd.symbols.tasLbl.show(),
|
||||
is_false: func(nd) nd.symbols.tasLbl.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ilsFreq',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.ilsFreq.show();
|
||||
if(getprop("instrumentation/nav/in-range"))
|
||||
nd.symbols.ilsFreq.setText(getprop("instrumentation/nav/nav-id"));
|
||||
else
|
||||
nd.symbols.ilsFreq.setText(getprop("instrumentation/nav/frequencies/selected-mhz-fmt"));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.ilsFreq.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ilsLbl',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.ilsLbl.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.ilsLbl.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'wpActiveId',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) getprop("/autopilot/route-manager/wp/id") != nil and getprop("autopilot/route-manager/active")
|
||||
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.wpActiveId.setText(getprop("/autopilot/route-manager/wp/id"));
|
||||
nd.symbols.wpActiveId.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.wpActiveId.hide(),
|
||||
}, # of wpActiveId.impl
|
||||
}, # of wpActiveId
|
||||
{
|
||||
id: 'wpActiveDist',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and getprop("autopilot/route-manager/active")
|
||||
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.wpActiveDist.setText(sprintf("%3.01f",getprop("/autopilot/route-manager/wp/dist")));
|
||||
nd.symbols.wpActiveDist.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.wpActiveDist.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'wpActiveDistLbl',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and getprop("autopilot/route-manager/active")
|
||||
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.wpActiveDistLbl.show();
|
||||
if(getprop("/autopilot/route-manager/wp/dist") > 1000)
|
||||
nd.symbols.wpActiveDistLbl.setText(" NM");
|
||||
},
|
||||
is_false: func(nd) nd.symbols.wpActiveDistLbl.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'eta',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) getprop("autopilot/route-manager/wp/eta") != nil and getprop("autopilot/route-manager/active")
|
||||
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
|
||||
is_true: func(nd) {
|
||||
var etaSec = getprop("/sim/time/utc/day-seconds")+getprop("autopilot/route-manager/wp/eta-seconds");
|
||||
var h = math.floor(etaSec/3600);
|
||||
etaSec=etaSec-3600*h;
|
||||
var m = math.floor(etaSec/60);
|
||||
etaSec=etaSec-60*m;
|
||||
var s = etaSec/10;
|
||||
if (h>24) h=h-24;
|
||||
nd.symbols.eta.setText(sprintf("%02.0f%02.0f.%01.0fz",h,m,s));
|
||||
nd.symbols.eta.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.eta.hide(),
|
||||
}, # of eta.impl
|
||||
}, # of eta
|
||||
{
|
||||
id: 'gsGroup',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']),
|
||||
is_true: func(nd) {
|
||||
if(nd.get_switch('toggle_centered'))
|
||||
nd.symbols.gsGroup.setTranslation(0,0);
|
||||
else
|
||||
nd.symbols.gsGroup.setTranslation(0,150);
|
||||
nd.symbols.gsGroup.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.gsGroup.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'hdg',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','MAP','VOR']),
|
||||
is_true: func(nd) {
|
||||
var hdgText = "";
|
||||
if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT")
|
||||
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
|
||||
{
|
||||
if(nd.get_switch('toggle_true_north'))
|
||||
hdgText = nd.aircraft_source.get_trk_tru();
|
||||
else
|
||||
hdgText = nd.aircraft_source.get_trk_mag();
|
||||
} else {
|
||||
if(nd.get_switch('toggle_true_north'))
|
||||
hdgText = nd.aircraft_source.get_hdg_tru();
|
||||
else
|
||||
hdgText = nd.aircraft_source.get_hdg_mag();
|
||||
}
|
||||
if(hdgText < 0.5) hdgText = 360 + hdgText;
|
||||
elsif(hdgText >= 360.5) hdgText = hdgText - 360;
|
||||
nd.symbols.hdg.setText(sprintf("%03.0f", hdgText));
|
||||
},
|
||||
is_false: NOTHING,
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'hdgGroup',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','MAP','VOR']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.hdgGroup.show();
|
||||
if(nd.get_switch('toggle_centered'))
|
||||
nd.symbols.hdgGroup.setTranslation(0,100);
|
||||
else
|
||||
nd.symbols.hdgGroup.setTranslation(0,0);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.hdgGroup.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'gs',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
common: func(nd) nd.symbols.gs.setText(sprintf("%3.0f",nd.aircraft_source.get_gnd_spd() )),
|
||||
predicate: func(nd) nd.aircraft_source.get_gnd_spd() >= 30,
|
||||
is_true: func(nd) {
|
||||
nd.symbols.gs.setFontSize(36);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.gs.setFontSize(52),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'rangeArcs',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) !nd.get_switch('toggle_centered') and nd.get_switch('toggle_rangearc'),
|
||||
is_true: func(nd) nd.symbols.rangeArcs.show(),
|
||||
is_false: func(nd) nd.symbols.rangeArcs.hide(),
|
||||
}, # of rangeArcs.impl
|
||||
}, # of rangeArcs
|
||||
{
|
||||
id:'rangePln1',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
|
||||
is_true: func(nd) {
|
||||
nd.symbols.rangePln1.show();
|
||||
nd.symbols.rangePln1.setText(sprintf("%3.0f",nd.rangeNm()));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.rangePln1.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'rangePln2',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
|
||||
is_true: func(nd) {
|
||||
nd.symbols.rangePln2.show();
|
||||
nd.symbols.rangePln2.setText(sprintf("%3.0f",nd.rangeNm()/2));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.rangePln2.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'rangePln3',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
|
||||
is_true: func(nd) {
|
||||
nd.symbols.rangePln3.show();
|
||||
nd.symbols.rangePln3.setText(sprintf("%3.0f",nd.rangeNm()/2));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.rangePln3.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'rangePln4',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
|
||||
is_true: func(nd) {
|
||||
nd.symbols.rangePln4.show();
|
||||
nd.symbols.rangePln4.setText(sprintf("%3.0f",nd.rangeNm()));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.rangePln4.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'crsLbl',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
|
||||
is_true: func(nd) nd.symbols.crsLbl.show(),
|
||||
is_false: func(nd) nd.symbols.crsLbl.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'crs',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.crs.show();
|
||||
if(getprop("instrumentation/nav/radials/selected-deg") != nil)
|
||||
nd.symbols.crs.setText(sprintf("%03.0f",getprop("instrumentation/nav/radials/selected-deg")));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.crs.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'dmeLbl',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
|
||||
is_true: func(nd) nd.symbols.dmeLbl.show(),
|
||||
is_false: func(nd) nd.symbols.dmeLbl.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'dme',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.dme.show();
|
||||
if(getprop("instrumentation/dme/in-range"))
|
||||
nd.symbols.dme.setText(sprintf("%3.1f",getprop("instrumentation/dme/indicated-distance-nm")));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.dme.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'trkInd2',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['MAP','APP','VOR']) and nd.get_switch('toggle_centered')),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.trkInd2.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.trkInd2.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'vorCrsPtr',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and !nd.get_switch('toggle_centered')),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.vorCrsPtr.show();
|
||||
if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT")
|
||||
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
|
||||
nd.symbols.vorCrsPtr.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_trk_mag())*D2R);
|
||||
else
|
||||
nd.symbols.vorCrsPtr.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_hdg_mag())*D2R);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.vorCrsPtr.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'vorCrsPtr2',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and nd.get_switch('toggle_centered')),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.vorCrsPtr2.show();
|
||||
if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT")
|
||||
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
|
||||
nd.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_trk_mag())*D2R);
|
||||
else
|
||||
nd.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_hdg_mag())*D2R);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.vorCrsPtr2.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gsDiamond',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']) and getprop("instrumentation/nav/gs-in-range"),
|
||||
is_true: func(nd) {
|
||||
var gs_deflection = getprop("instrumentation/nav/gs-needle-deflection-norm");
|
||||
if(gs_deflection != nil)
|
||||
nd.symbols.gsDiamond.setTranslation(gs_deflection*150,0);
|
||||
if(abs(gs_deflection) < 0.99)
|
||||
nd.symbols.gsDiamond.setColorFill(1,0,1,1);
|
||||
else
|
||||
nd.symbols.gsDiamond.setColorFill(0,0,0,1);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.gsGroup.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'locPtr',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and !nd.get_switch('toggle_centered') and getprop("instrumentation/nav/in-range")),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.locPtr.show();
|
||||
var deflection = getprop("instrumentation/nav/heading-needle-deflection-norm");
|
||||
nd.symbols.locPtr.setTranslation(deflection*150,0);
|
||||
if(abs(deflection) < 0.99)
|
||||
nd.symbols.locPtr.setColorFill(1,0,1,1);
|
||||
else
|
||||
nd.symbols.locPtr.setColorFill(0,0,0,1);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.locPtr.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'locPtr2',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and nd.get_switch('toggle_centered') and getprop("instrumentation/nav/in-range")),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.locPtr2.show();
|
||||
var deflection = getprop("instrumentation/nav/heading-needle-deflection-norm");
|
||||
nd.symbols.locPtr2.setTranslation(deflection*150,0);
|
||||
if(abs(deflection) < 0.99)
|
||||
nd.symbols.locPtr2.setColorFill(1,0,1,1);
|
||||
else
|
||||
nd.symbols.locPtr2.setColorFill(0,0,0,1);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.locPtr2.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'wind',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: ALWAYS,
|
||||
is_true: func(nd) {
|
||||
var windDir = getprop("environment/wind-from-heading-deg");
|
||||
if(!nd.get_switch('toggle_true_north'))
|
||||
windDir = windDir - getprop("environment/magnetic-variation-deg");
|
||||
if(windDir < 0.5) windDir = 360 + windDir;
|
||||
elsif(windDir >= 360.5) windDir = windDir - 360;
|
||||
nd.symbols.wind.setText(sprintf("%03.0f / %02.0f",windDir,getprop("environment/wind-speed-kt")));
|
||||
},
|
||||
is_false: NOTHING,
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'windArrow',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (!(nd.in_mode('toggle_display_mode', ['PLAN']) and (nd.get_switch('toggle_display_type') == "LCD")) and nd.aircraft_source.get_spd() > 100),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.windArrow.show();
|
||||
var windArrowRot = getprop("environment/wind-from-heading-deg");
|
||||
if((nd.in_mode('toggle_display_mode', ['MAP','PLAN']) and nd.get_switch('toggle_display_type') == "CRT")
|
||||
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
|
||||
windArrowRot = windArrowRot - nd.aircraft_source.get_trk_mag();
|
||||
else
|
||||
windArrowRot = windArrowRot - nd.aircraft_source.get_hdg_mag();
|
||||
nd.symbols.windArrow.setRotation(windArrowRot*D2R);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.windArrow.hide(),
|
||||
},
|
||||
},
|
||||
|
||||
], # end of vector with features
|
||||
|
||||
|
||||
}, # end of Boeing style
|
||||
#####
|
||||
##
|
||||
## add support for other aircraft/ND types and styles here (Airbus etc)
|
||||
##
|
||||
##
|
||||
|
||||
}; # end of NDStyles
|
||||
io.include("Nasal/canvas/map/navdisplay.styles");
|
||||
|
||||
##
|
||||
# encapsulate hdg/lat/lon source, so that the ND may also display AI/MP aircraft in a pilot-view at some point (aka stress-testing)
|
||||
#
|
||||
# TODO: this predates aircraftpos.controller (MapStructure) should probably be unified to some degree ...
|
||||
|
||||
var NDSourceDriver = {};
|
||||
NDSourceDriver.new = func {
|
||||
|
@ -666,17 +85,9 @@ var default_switches = {
|
|||
'toggle_display_type': {path: '/mfd/display-type', value:'CRT', type:'STRING'}, # valid values are: CRT or LCD
|
||||
'toggle_true_north': {path: '/mfd/true-north', value:0, type:'BOOL'},
|
||||
'toggle_rangearc': {path: '/mfd/rangearc', value:0, type:'BOOL'},
|
||||
'toggle_track_heading': {path: '/trk-selected', value:0, type:'BOOL'},
|
||||
'toggle_track_heading':{path: '/trk-selected', value:0, type:'BOOL'},
|
||||
};
|
||||
|
||||
# Hack to update weather radar once every 10 seconds
|
||||
var update_weather = func {
|
||||
if (getprop("/instrumentation/efis/inputs/wxr") != nil)
|
||||
setprop("/instrumentation/efis/inputs/wxr",getprop("/instrumentation/efis/inputs/wxr"));
|
||||
settimer(update_weather, 10);
|
||||
}
|
||||
update_weather();
|
||||
|
||||
##
|
||||
# TODO:
|
||||
# - introduce a MFD class (use it also for PFD/EICAS)
|
||||
|
@ -690,6 +101,8 @@ var NavDisplay = {
|
|||
print("Cleaning up NavDisplay");
|
||||
# shut down all timers and other loops here
|
||||
me.update_timer.stop();
|
||||
foreach(var t; me.timers)
|
||||
t.stop();
|
||||
foreach(var l; me.listeners)
|
||||
removelistener(l);
|
||||
# clean up MapStructure
|
||||
|
@ -701,6 +114,11 @@ var NavDisplay = {
|
|||
NavDisplay.id -= 1;
|
||||
},
|
||||
|
||||
addtimer: func(interval, cb) {
|
||||
append(me.timers, var job=maketimer(interval, cb));
|
||||
return job; # so that we can directly work with the timer (start/stop)
|
||||
},
|
||||
|
||||
listen: func(p,c) {
|
||||
append(me.listeners, setlistener(p,c));
|
||||
},
|
||||
|
@ -741,6 +159,8 @@ var NavDisplay = {
|
|||
};
|
||||
}, # of connectAI
|
||||
|
||||
setTimerInterval: func(update_time=0.05) me.update_timer.restart(update_time),
|
||||
|
||||
# TODO: the ctor should allow customization, for different aircraft
|
||||
# especially properties and SVG files/handles (747, 757, 777 etc)
|
||||
new : func(prop1, switches=default_switches, style='Boeing') {
|
||||
|
@ -749,6 +169,7 @@ var NavDisplay = {
|
|||
|
||||
m.inited = 0;
|
||||
|
||||
m.timers=[];
|
||||
m.listeners=[]; # for cleanup handling
|
||||
m.aircraft_source = NDSourceDriver.new(); # uses the main aircraft as the driver/source (speeds, position, heading)
|
||||
|
||||
|
@ -799,12 +220,11 @@ var NavDisplay = {
|
|||
|
||||
return m;
|
||||
},
|
||||
newMFD: func(canvas_group, parent=nil, options=nil)
|
||||
newMFD: func(canvas_group, parent=nil, options=nil, update_time=0.05)
|
||||
{
|
||||
if (me.inited) die("MFD already was added to scene");
|
||||
me.inited = 1;
|
||||
#me.listen("/sim/signals/reinit", func(n) me.del() );
|
||||
me.update_timer = maketimer(0.05, func me.update() ); # TODO: make interval configurable via ctor
|
||||
me.update_timer = maketimer(update_time, func me.update() );
|
||||
me.nd = canvas_group;
|
||||
me.canvas_handle = parent;
|
||||
|
||||
|
@ -814,7 +234,6 @@ var NavDisplay = {
|
|||
me.symbols = {}; # storage for SVG elements, to avoid namespace pollution (all SVG elements end up here)
|
||||
|
||||
foreach(var feature; me.nd_style.features ) {
|
||||
# print("Setting up SVG feature:", feature.id);
|
||||
me.symbols[feature.id] = me.nd.getElementById(feature.id).updateCenter();
|
||||
if(contains(feature.impl,'init')) feature.impl.init(me.nd, feature); # call The element's init code (i.e. updateCenter)
|
||||
}
|
||||
|
@ -839,10 +258,9 @@ var NavDisplay = {
|
|||
|
||||
me.map = me.nd.createChild("map","map")
|
||||
.set("clip", "rect(124, 1024, 1024, 0)")
|
||||
.set("screen-range", "700");
|
||||
.set("screen-range", 700);
|
||||
|
||||
# this callback will be passed onto the model via the controller hash, and used for the positioned queries, to specify max query range:
|
||||
var get_range = func me.get_switch('toggle_range');
|
||||
me.update_sub(); # init some map properties based on switches
|
||||
|
||||
# predicate for the draw controller
|
||||
var is_tuned = func(freq) {
|
||||
|
@ -876,7 +294,6 @@ var NavDisplay = {
|
|||
var controller = {
|
||||
parents: [canvas.Map.Controller],
|
||||
_pos: nil, _time: nil,
|
||||
query_range: func get_range(),
|
||||
is_tuned:is_tuned,
|
||||
get_tuned_course:get_course_by_freq,
|
||||
get_position: get_current_position,
|
||||
|
@ -919,18 +336,31 @@ var NavDisplay = {
|
|||
foreach(var layer; me.nd_style.layers) {
|
||||
if(layer['disabled']) continue; # skip this layer
|
||||
#print("newMFD(): Setting up ND layer:", layer.name);
|
||||
# huge hack for the alt-arc, which is not rendered as a map group, but directly as part of the toplevel ND group
|
||||
var render_target = (!contains(layer,'not_a_map') or !layer.not_a_map) ? me.map : me.nd;
|
||||
|
||||
var the_layer = nil;
|
||||
if(!layer['isMapStructure']) # set up an old layer
|
||||
the_layer = me.layers[layer.name] = canvas.MAP_LAYERS[layer.name].new( render_target, layer.name, controller );
|
||||
if(!layer['isMapStructure']) # set up an old INEFFICIENT and SLOW layer
|
||||
the_layer = me.layers[layer.name] = canvas.MAP_LAYERS[layer.name].new( me.map, layer.name, controller );
|
||||
else {
|
||||
printlog(_MP_dbg_lvl, "Setting up MapStructure-based layer for ND, name:", layer.name);
|
||||
var opt = options != nil and options[layer.name] != nil ? options[layer.name] :nil;
|
||||
# print("Options is: ", opt!=nil?"enabled":"disabled");
|
||||
render_target.addLayer(factory: canvas.SymbolLayer, type_arg: layer.name, options:opt);
|
||||
the_layer = me.layers[layer.name] = render_target.getLayer(layer.name);
|
||||
me.map.addLayer(
|
||||
factory: canvas.SymbolLayer,
|
||||
type_arg: layer.name,
|
||||
options:opt,
|
||||
visible:0,
|
||||
priority: layer['z-index']
|
||||
);
|
||||
the_layer = me.layers[layer.name] = me.map.getLayer(layer.name);
|
||||
if (1) (func {
|
||||
var l = layer;
|
||||
var _predicate = l.predicate;
|
||||
l.predicate = func {
|
||||
var t = systime();
|
||||
call(_predicate, arg, me);
|
||||
printlog(_MP_dbg_lvl, "Took "~((systime()-t)*1000)~"ms to update layer "~l.name);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
# now register all layer specific notification listeners and their corresponding update predicate/callback
|
||||
|
@ -938,6 +368,14 @@ var NavDisplay = {
|
|||
# so that it can directly access the ND instance and its own layer (without having to know the layer's name)
|
||||
var event_handler = make_event_handler(layer.predicate, the_layer);
|
||||
foreach(var event; layer.update_on) {
|
||||
# this handles timers
|
||||
if (typeof(event)=='hash' and contains(event, 'rate_hz')) {
|
||||
#print("FIXME: navdisplay.mfd timer handling is broken ATM");
|
||||
var job=me.addtimer(1/event.rate_hz, event_handler);
|
||||
job.start();
|
||||
}
|
||||
# and this listeners
|
||||
else
|
||||
# print("Setting up subscription:", event, " for ", layer.name, " handler id:", id(event_handler) );
|
||||
me.listen_switch(event, event_handler);
|
||||
} # foreach event subscription
|
||||
|
@ -950,17 +388,7 @@ var NavDisplay = {
|
|||
# start the update timer, which makes sure that the update() will be called
|
||||
me.update_timer.start();
|
||||
|
||||
|
||||
# next, radio & autopilot & listeners
|
||||
# TODO: move this to .init field in layers hash or to model files
|
||||
foreach(var n; var radios = [
|
||||
"instrumentation/nav/frequencies/selected-mhz",
|
||||
"instrumentation/nav[1]/frequencies/selected-mhz"])
|
||||
me.listen(n, func() {
|
||||
# me.drawvor();
|
||||
# me.drawdme();
|
||||
});
|
||||
# TODO: move this to the route.model
|
||||
# TODO: move this to RTE.lcontroller ?
|
||||
me.listen("/autopilot/route-manager/current-wp", func(activeWp) {
|
||||
canvas.updatewp( activeWp.getValue() );
|
||||
});
|
||||
|
@ -972,13 +400,10 @@ var NavDisplay = {
|
|||
foreach(var m; modes) if(me.get_switch(switch)==m) return 1;
|
||||
return 0;
|
||||
},
|
||||
# each model should keep track of when it last got updated, using current lat/lon
|
||||
# in update(), we can then check if the aircraft has traveled more than 0.5-1 nm (depending on selected range)
|
||||
# and update each model accordingly
|
||||
update: func() # FIXME: This stuff is still too aircraft specific, cannot easily be reused by other aircraft
|
||||
# Helper function for below (update()) and above (newMFD())
|
||||
# to ensure position etc. are correct.
|
||||
update_sub: func()
|
||||
{
|
||||
var _time = systime();
|
||||
|
||||
# Variables:
|
||||
var userLat = me.aircraft_source.get_lat();
|
||||
var userLon = me.aircraft_source.get_lon();
|
||||
|
@ -1044,12 +469,23 @@ var NavDisplay = {
|
|||
pos.lon = userLon;
|
||||
}
|
||||
call(me.map.setPos, [pos.lat, pos.lon], me.map, pos);
|
||||
},
|
||||
# each model should keep track of when it last got updated, using current lat/lon
|
||||
# in update(), we can then check if the aircraft has traveled more than 0.5-1 nm (depending on selected range)
|
||||
# and update each model accordingly
|
||||
# TODO: Hooray is still waiting for a really rainy weekend to clean up all the mess here... so plz don't add to it!
|
||||
update: func() # FIXME: This stuff is still too aircraft specific, cannot easily be reused by other aircraft
|
||||
{
|
||||
var _time = systime();
|
||||
|
||||
call(me.update_sub, nil, nil, caller(0)[0]); # call this in the same namespace to "steal" its variables
|
||||
|
||||
# MapStructure update!
|
||||
if (me.map.controller.should_update_all()) {
|
||||
me.map.update();
|
||||
} else {
|
||||
# TODO: ugly list here
|
||||
# FIXME: use a VOLATILE layer helper here that handles TFC, APS, WXR etc ?
|
||||
me.map.update(func(layer) (var n=layer.type) == "TFC" or n == "APS");
|
||||
}
|
||||
|
||||
|
@ -1345,8 +781,7 @@ var NavDisplay = {
|
|||
me.symbols['status.arpt'].setVisible( me.get_switch('toggle_airports') and me.in_mode('toggle_display_mode', ['MAP']));
|
||||
me.symbols['status.sta'].setVisible( me.get_switch('toggle_stations') and me.in_mode('toggle_display_mode', ['MAP']));
|
||||
# Okay, _how_ do we hook this up with FGPlot?
|
||||
#print("ND update took "~(systime()-_time)~" seconds");
|
||||
printlog(_MP_dbg_lvl, "Total ND update took "~((systime()-_time)*100)~"ms");
|
||||
setprop("/instrumentation/navdisplay["~ NavDisplay.id ~"]/update-ms", systime() - _time);
|
||||
|
||||
}
|
||||
} # of update() method (50% of our file ...seriously?)
|
||||
};
|
||||
|
|
619
Nasal/canvas/map/navdisplay.styles
Normal file
|
@ -0,0 +1,619 @@
|
|||
# ==============================================================================
|
||||
# Boeing Navigation Display by Gijs de Rooy
|
||||
# See: http://wiki.flightgear.org/Canvas_ND_Framework
|
||||
# ==============================================================================
|
||||
# This file makes use of the MapStructure framework, see: http://wiki.flightgear.org/Canvas_MapStructure
|
||||
#
|
||||
# Sooner or later, some parts will be revamped by coming up with a simple animation framework: http://wiki.flightgear.org/NavDisplay#mapping_vs._SVG_animation
|
||||
|
||||
##
|
||||
# pseudo DSL-ish: use these as placeholders in the config hash below
|
||||
var ALWAYS = func 1;
|
||||
var NOTHING = func nil;
|
||||
|
||||
##
|
||||
# TODO: move ND-specific implementation details into this lookup hash
|
||||
# so that other aircraft and ND types can be more easily supported
|
||||
#
|
||||
# any aircraft-specific ND behavior should be wrapped here,
|
||||
# to isolate/decouple things in the generic NavDisplay class
|
||||
#
|
||||
# TODO: move this to an XML config file (maybe supporting SGCondition and/or SGStateMachine markup for the logic?)
|
||||
#
|
||||
var NDStyles = {
|
||||
##
|
||||
# this configures the 744 ND to help generalize the NavDisplay class itself
|
||||
'Boeing': {
|
||||
font_mapper: func(family, weight) {
|
||||
if( family == "Liberation Sans" and weight == "normal" )
|
||||
return "LiberationFonts/LiberationSans-Regular.ttf";
|
||||
},
|
||||
|
||||
# where all the symbols are stored
|
||||
# TODO: SVG elements should be renamed to use boeing/airbus prefix
|
||||
# aircraft developers should all be editing the same ND.svg image
|
||||
# the code can deal with the differences now
|
||||
svg_filename: "Nasal/canvas/map/Images/boeingND.svg",
|
||||
##
|
||||
## this loads and configures existing layers (currently, *.layer files in Nasal/canvas/map)
|
||||
##
|
||||
|
||||
|
||||
# TODO: phase out isMapStructure flag once map.nas & *.draw files are killed
|
||||
layers: [
|
||||
# TODO: take z-indices from *.draw files -- now handled by MapStructure in the addLayer method.
|
||||
{ name:'FIX', isMapStructure:1, update_on:['toggle_range','toggle_waypoints'],
|
||||
# FIXME: this is a really ugly place for controller code
|
||||
predicate: func(nd, layer) {
|
||||
# print("Running fix layer predicate");
|
||||
# toggle visibility here
|
||||
var visible=nd.get_switch('toggle_waypoints') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
|
||||
layer.group.setVisible( nd.get_switch('toggle_waypoints') );
|
||||
if (visible) {
|
||||
#print("Updating MapStructure ND layer: FIX");
|
||||
# (Hopefully) smart update
|
||||
layer.update();
|
||||
}
|
||||
}, # end of layer update predicate
|
||||
'z-index': 3,
|
||||
}, # end of FIX layer
|
||||
|
||||
# Should redraw every 10 seconds TODO: use new MapStructure/WXR here once that works properly (Gijs should check first!)
|
||||
{ name:'WXR', isMapStructure:1, update_on:[ {rate_hz: 0.1}, 'toggle_range','toggle_weather','toggle_display_mode'],
|
||||
predicate: func(nd, layer) {
|
||||
#print("Running storms predicate");
|
||||
var visible=nd.get_switch('toggle_weather') and nd.get_switch('toggle_display_mode') != "PLAN";
|
||||
layer.group.setVisible(visible);
|
||||
if (visible) {
|
||||
print("storms update requested! (timer issue when closing the dialog?)");
|
||||
layer.update();
|
||||
}
|
||||
}, # end of layer update predicate
|
||||
}, # end of storms/WXR layer
|
||||
|
||||
{ name:'APS', isMapStructure:1, update_on:['toggle_display_mode'],
|
||||
predicate: func(nd, layer) {
|
||||
var visible = nd.get_switch('toggle_display_mode') == "PLAN";
|
||||
layer.group.setVisible( visible );
|
||||
if (visible) {
|
||||
layer.update();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{ name:'APT', isMapStructure:1, update_on:['toggle_range','toggle_airports','toggle_display_mode'],
|
||||
predicate: func(nd, layer) {
|
||||
# toggle visibility here
|
||||
var visible=nd.get_switch('toggle_airports') and nd.in_mode('toggle_display_mode', ['MAP']);
|
||||
layer.group.setVisible( visible );
|
||||
if (visible) {
|
||||
#print("Updating MapStructure ND layer: APT");
|
||||
layer.update();
|
||||
}
|
||||
}, # end of layer update predicate
|
||||
'z-index': 1,
|
||||
}, # end of APT layer
|
||||
|
||||
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
|
||||
{ name:'VOR', isMapStructure:1, update_on:['toggle_range','toggle_stations','toggle_display_mode'],
|
||||
# FIXME: this is a really ugly place for controller code
|
||||
predicate: func(nd, layer) {
|
||||
# toggle visibility here
|
||||
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
|
||||
layer.group.setVisible( visible );
|
||||
if (visible) {
|
||||
#print("Updating MapStructure ND layer: VOR");
|
||||
layer.update();
|
||||
}
|
||||
}, # end of layer update predicate
|
||||
'z-index': 3,
|
||||
}, # end of VOR layer
|
||||
|
||||
{ name:'DME', isMapStructure:1, update_on:['toggle_range','toggle_stations'],
|
||||
# FIXME: this is a really ugly place for controller code
|
||||
predicate: func(nd, layer) {
|
||||
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
|
||||
# toggle visibility here
|
||||
layer.group.setVisible( visible );
|
||||
if (visible) {
|
||||
#print("Updating MapStructure ND layer: DME");
|
||||
layer.update();
|
||||
}
|
||||
}, # end of layer update predicate
|
||||
'z-index': 3,
|
||||
}, # end of DME layer
|
||||
|
||||
{ name:'TFC', isMapStructure:1, update_on:['toggle_range','toggle_traffic'],
|
||||
predicate: func(nd, layer) {
|
||||
var visible = nd.get_switch('toggle_traffic');
|
||||
layer.group.setVisible( visible );
|
||||
if (visible) {
|
||||
#print("Updating MapStructure ND layer: TFC");
|
||||
layer.update();
|
||||
}
|
||||
}, # end of layer update predicate
|
||||
'z-index': 1,
|
||||
}, # end of traffic layer
|
||||
|
||||
{ name:'runway-nd', update_on:['toggle_range','toggle_display_mode'],
|
||||
predicate: func(nd, layer) {
|
||||
#print("runway-nd wants to be ported to MapStructure");
|
||||
var visible = (nd.rangeNm() <= 40) and getprop("autopilot/route-manager/active") and nd.in_mode('toggle_display_mode', ['MAP','PLAN']) ;
|
||||
if (visible)
|
||||
layer._model.init(); # clear & redraw
|
||||
layer._view.setVisible( visible );
|
||||
}, # end of layer update predicate
|
||||
}, # end of airports-nd layer
|
||||
|
||||
{ name:'RTE', isMapStructure:1, update_on:['toggle_range','toggle_display_mode'],
|
||||
predicate: func(nd, layer) {
|
||||
var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN']));
|
||||
layer.group.setVisible( visible );
|
||||
if (visible)
|
||||
layer.update();
|
||||
}, # end of layer update predicate
|
||||
'z-index': 2, # apparently route.draw doesn't have a z-index?
|
||||
}, # end of route layer
|
||||
{ name:'WPT', isMapStructure:1, update_on:['toggle_range','toggle_display_mode'],
|
||||
predicate: func(nd, layer) {
|
||||
var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN']));
|
||||
layer.group.setVisible( visible );
|
||||
|
||||
if (visible)
|
||||
layer.update();
|
||||
}, # end of layer update predicate
|
||||
'z-index': 4,
|
||||
}, # end of waypoint layer
|
||||
{ name:'ALT-profile', isMapStructure:1, update_on:['toggle_range','toggle_display_mode'],
|
||||
predicate: func(nd, layer) {
|
||||
var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN']));
|
||||
layer.group.setVisible( visible );
|
||||
|
||||
if (visible)
|
||||
layer.update();
|
||||
}, # end of layer update predicate
|
||||
'z-index': 4,
|
||||
}, # end of altitude profile layer
|
||||
|
||||
## add other layers here, layer names must match the registered names as used in *.layer files for now
|
||||
## this will all change once we're using Philosopher's MapStructure framework
|
||||
|
||||
], # end of vector with configured layers
|
||||
|
||||
# This is where SVG elements are configured by providing "behavior" hashes, i.e. for animations
|
||||
|
||||
# to animate each SVG symbol, specify behavior via callbacks (predicate, and true/false implementation)
|
||||
# SVG identifier, callback etc
|
||||
# TODO: update_on([]), update_mode (update() vs. timers/listeners)
|
||||
# TODO: support putting symbols on specific layers
|
||||
features: [
|
||||
{
|
||||
# TODO: taOnly doesn't need to use getprop polling in update(), use a listener instead!
|
||||
id: 'taOnly', # the SVG ID
|
||||
impl: { # implementation hash
|
||||
init: func(nd, symbol), # for updateCenter stuff, called during initialization in the ctor
|
||||
predicate: func(nd) getprop("instrumentation/tcas/inputs/mode") == 2, # the condition
|
||||
is_true: func(nd) nd.symbols.taOnly.show(), # if true, run this
|
||||
is_false: func(nd) nd.symbols.taOnly.hide(), # if false, run this
|
||||
}, # end of taOnly behavior/callbacks
|
||||
}, # end of taOnly
|
||||
{
|
||||
id: 'tas',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.aircraft_source.get_spd() > 100,
|
||||
is_true: func(nd) {
|
||||
nd.symbols.tas.setText(sprintf("%3.0f",getprop("/velocities/airspeed-kt") ));
|
||||
nd.symbols.tas.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.tas.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'tasLbl',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.aircraft_source.get_spd() > 100,
|
||||
is_true: func(nd) nd.symbols.tasLbl.show(),
|
||||
is_false: func(nd) nd.symbols.tasLbl.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ilsFreq',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.ilsFreq.show();
|
||||
if(getprop("instrumentation/nav/in-range"))
|
||||
nd.symbols.ilsFreq.setText(getprop("instrumentation/nav/nav-id"));
|
||||
else
|
||||
nd.symbols.ilsFreq.setText(getprop("instrumentation/nav/frequencies/selected-mhz-fmt"));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.ilsFreq.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ilsLbl',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.ilsLbl.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.ilsLbl.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'wpActiveId',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) getprop("/autopilot/route-manager/wp/id") != nil and getprop("autopilot/route-manager/active")
|
||||
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.wpActiveId.setText(getprop("/autopilot/route-manager/wp/id"));
|
||||
nd.symbols.wpActiveId.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.wpActiveId.hide(),
|
||||
}, # of wpActiveId.impl
|
||||
}, # of wpActiveId
|
||||
{
|
||||
id: 'wpActiveDist',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and getprop("autopilot/route-manager/active")
|
||||
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.wpActiveDist.setText(sprintf("%3.01f",getprop("/autopilot/route-manager/wp/dist")));
|
||||
nd.symbols.wpActiveDist.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.wpActiveDist.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'wpActiveDistLbl',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and getprop("autopilot/route-manager/active")
|
||||
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.wpActiveDistLbl.show();
|
||||
if(getprop("/autopilot/route-manager/wp/dist") > 1000)
|
||||
nd.symbols.wpActiveDistLbl.setText(" NM");
|
||||
},
|
||||
is_false: func(nd) nd.symbols.wpActiveDistLbl.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'eta',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) getprop("autopilot/route-manager/wp/eta") != nil and getprop("autopilot/route-manager/active")
|
||||
and nd.in_mode('toggle_display_mode', ['MAP','PLAN']),
|
||||
is_true: func(nd) {
|
||||
var etaSec = getprop("/sim/time/utc/day-seconds")+getprop("autopilot/route-manager/wp/eta-seconds");
|
||||
var h = math.floor(etaSec/3600);
|
||||
etaSec=etaSec-3600*h;
|
||||
var m = math.floor(etaSec/60);
|
||||
etaSec=etaSec-60*m;
|
||||
var s = etaSec/10;
|
||||
if (h>24) h=h-24;
|
||||
nd.symbols.eta.setText(sprintf("%02.0f%02.0f.%01.0fz",h,m,s));
|
||||
nd.symbols.eta.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.eta.hide(),
|
||||
}, # of eta.impl
|
||||
}, # of eta
|
||||
{
|
||||
id: 'gsGroup',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']),
|
||||
is_true: func(nd) {
|
||||
if(nd.get_switch('toggle_centered'))
|
||||
nd.symbols.gsGroup.setTranslation(0,0);
|
||||
else
|
||||
nd.symbols.gsGroup.setTranslation(0,150);
|
||||
nd.symbols.gsGroup.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.gsGroup.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'hdg',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','MAP','VOR']),
|
||||
is_true: func(nd) {
|
||||
var hdgText = "";
|
||||
if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT")
|
||||
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
|
||||
{
|
||||
if(nd.get_switch('toggle_true_north'))
|
||||
hdgText = nd.aircraft_source.get_trk_tru();
|
||||
else
|
||||
hdgText = nd.aircraft_source.get_trk_mag();
|
||||
} else {
|
||||
if(nd.get_switch('toggle_true_north'))
|
||||
hdgText = nd.aircraft_source.get_hdg_tru();
|
||||
else
|
||||
hdgText = nd.aircraft_source.get_hdg_mag();
|
||||
}
|
||||
if(hdgText < 0.5) hdgText = 360 + hdgText;
|
||||
elsif(hdgText >= 360.5) hdgText = hdgText - 360;
|
||||
nd.symbols.hdg.setText(sprintf("%03.0f", hdgText));
|
||||
},
|
||||
is_false: NOTHING,
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'hdgGroup',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','MAP','VOR']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.hdgGroup.show();
|
||||
if(nd.get_switch('toggle_centered'))
|
||||
nd.symbols.hdgGroup.setTranslation(0,100);
|
||||
else
|
||||
nd.symbols.hdgGroup.setTranslation(0,0);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.hdgGroup.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'gs',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
common: func(nd) nd.symbols.gs.setText(sprintf("%3.0f",nd.aircraft_source.get_gnd_spd() )),
|
||||
predicate: func(nd) nd.aircraft_source.get_gnd_spd() >= 30,
|
||||
is_true: func(nd) {
|
||||
nd.symbols.gs.setFontSize(36);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.gs.setFontSize(52),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'rangeArcs',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) !nd.get_switch('toggle_centered') and nd.get_switch('toggle_rangearc'),
|
||||
is_true: func(nd) nd.symbols.rangeArcs.show(),
|
||||
is_false: func(nd) nd.symbols.rangeArcs.hide(),
|
||||
}, # of rangeArcs.impl
|
||||
}, # of rangeArcs
|
||||
{
|
||||
id:'rangePln1',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
|
||||
is_true: func(nd) {
|
||||
nd.symbols.rangePln1.show();
|
||||
nd.symbols.rangePln1.setText(sprintf("%3.0f",nd.rangeNm()));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.rangePln1.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'rangePln2',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
|
||||
is_true: func(nd) {
|
||||
nd.symbols.rangePln2.show();
|
||||
nd.symbols.rangePln2.setText(sprintf("%3.0f",nd.rangeNm()/2));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.rangePln2.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'rangePln3',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
|
||||
is_true: func(nd) {
|
||||
nd.symbols.rangePln3.show();
|
||||
nd.symbols.rangePln3.setText(sprintf("%3.0f",nd.rangeNm()/2));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.rangePln3.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'rangePln4',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.get_switch('toggle_display_mode') == "PLAN",
|
||||
is_true: func(nd) {
|
||||
nd.symbols.rangePln4.show();
|
||||
nd.symbols.rangePln4.setText(sprintf("%3.0f",nd.rangeNm()));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.rangePln4.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'crsLbl',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
|
||||
is_true: func(nd) nd.symbols.crsLbl.show(),
|
||||
is_false: func(nd) nd.symbols.crsLbl.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'crs',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.crs.show();
|
||||
if(getprop("instrumentation/nav/radials/selected-deg") != nil)
|
||||
nd.symbols.crs.setText(sprintf("%03.0f",getprop("instrumentation/nav/radials/selected-deg")));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.crs.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'dmeLbl',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
|
||||
is_true: func(nd) nd.symbols.dmeLbl.show(),
|
||||
is_false: func(nd) nd.symbols.dmeLbl.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'dme',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP','VOR']),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.dme.show();
|
||||
if(getprop("instrumentation/dme/in-range"))
|
||||
nd.symbols.dme.setText(sprintf("%3.1f",getprop("instrumentation/dme/indicated-distance-nm")));
|
||||
},
|
||||
is_false: func(nd) nd.symbols.dme.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'trkInd2',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['MAP','APP','VOR']) and nd.get_switch('toggle_centered')),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.trkInd2.show();
|
||||
},
|
||||
is_false: func(nd) nd.symbols.trkInd2.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'vorCrsPtr',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and !nd.get_switch('toggle_centered')),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.vorCrsPtr.show();
|
||||
if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT")
|
||||
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
|
||||
nd.symbols.vorCrsPtr.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_trk_mag())*D2R);
|
||||
else
|
||||
nd.symbols.vorCrsPtr.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_hdg_mag())*D2R);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.vorCrsPtr.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'vorCrsPtr2',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and nd.get_switch('toggle_centered')),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.vorCrsPtr2.show();
|
||||
if((nd.in_mode('toggle_display_mode', ['MAP']) and nd.get_switch('toggle_display_type') == "CRT")
|
||||
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
|
||||
nd.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_trk_mag())*D2R);
|
||||
else
|
||||
nd.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-nd.aircraft_source.get_hdg_mag())*D2R);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.vorCrsPtr2.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gsDiamond',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) nd.in_mode('toggle_display_mode', ['APP']) and getprop("instrumentation/nav/gs-in-range"),
|
||||
is_true: func(nd) {
|
||||
var gs_deflection = getprop("instrumentation/nav/gs-needle-deflection-norm");
|
||||
if(gs_deflection != nil)
|
||||
nd.symbols.gsDiamond.setTranslation(gs_deflection*150,0);
|
||||
if(abs(gs_deflection) < 0.99)
|
||||
nd.symbols.gsDiamond.setColorFill(1,0,1,1);
|
||||
else
|
||||
nd.symbols.gsDiamond.setColorFill(0,0,0,1);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.gsGroup.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'locPtr',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and !nd.get_switch('toggle_centered') and getprop("instrumentation/nav/in-range")),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.locPtr.show();
|
||||
var deflection = getprop("instrumentation/nav/heading-needle-deflection-norm");
|
||||
nd.symbols.locPtr.setTranslation(deflection*150,0);
|
||||
if(abs(deflection) < 0.99)
|
||||
nd.symbols.locPtr.setColorFill(1,0,1,1);
|
||||
else
|
||||
nd.symbols.locPtr.setColorFill(0,0,0,1);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.locPtr.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'locPtr2',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (nd.in_mode('toggle_display_mode', ['APP','VOR']) and nd.get_switch('toggle_centered') and getprop("instrumentation/nav/in-range")),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.locPtr2.show();
|
||||
var deflection = getprop("instrumentation/nav/heading-needle-deflection-norm");
|
||||
nd.symbols.locPtr2.setTranslation(deflection*150,0);
|
||||
if(abs(deflection) < 0.99)
|
||||
nd.symbols.locPtr2.setColorFill(1,0,1,1);
|
||||
else
|
||||
nd.symbols.locPtr2.setColorFill(0,0,0,1);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.locPtr2.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'wind',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: ALWAYS,
|
||||
is_true: func(nd) {
|
||||
var windDir = getprop("environment/wind-from-heading-deg");
|
||||
if(!nd.get_switch('toggle_true_north'))
|
||||
windDir = windDir - getprop("environment/magnetic-variation-deg");
|
||||
if(windDir < 0.5) windDir = 360 + windDir;
|
||||
elsif(windDir >= 360.5) windDir = windDir - 360;
|
||||
nd.symbols.wind.setText(sprintf("%03.0f / %02.0f",windDir,getprop("environment/wind-speed-kt")));
|
||||
},
|
||||
is_false: NOTHING,
|
||||
},
|
||||
},
|
||||
{
|
||||
id:'windArrow',
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (!(nd.in_mode('toggle_display_mode', ['PLAN']) and (nd.get_switch('toggle_display_type') == "LCD")) and nd.aircraft_source.get_spd() > 100),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.windArrow.show();
|
||||
var windArrowRot = getprop("environment/wind-from-heading-deg");
|
||||
if((nd.in_mode('toggle_display_mode', ['MAP','PLAN']) and nd.get_switch('toggle_display_type') == "CRT")
|
||||
or (nd.get_switch('toggle_track_heading') and nd.get_switch('toggle_display_type') == "LCD"))
|
||||
windArrowRot = windArrowRot - nd.aircraft_source.get_trk_mag();
|
||||
else
|
||||
windArrowRot = windArrowRot - nd.aircraft_source.get_hdg_mag();
|
||||
nd.symbols.windArrow.setRotation(windArrowRot*D2R);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.windArrow.hide(),
|
||||
},
|
||||
},
|
||||
|
||||
], # end of vector with features
|
||||
|
||||
|
||||
}, # end of Boeing style
|
||||
#####
|
||||
##
|
||||
## add support for other aircraft/ND types and styles here (Airbus etc)
|
||||
## or move to other files.
|
||||
##
|
||||
## see: http://wiki.flightgear.org/NavDisplay#Custom_ND_Styles
|
||||
## and: http://wiki.flightgear.org/NavDisplay#Adding_new_features
|
||||
|
||||
}; # end of NDStyles
|
||||
|
|
@ -11,7 +11,7 @@ var draw_storm = func (group, storm, controller=nil, lod=0) {
|
|||
var storm_grp = group.createChild("group","storm"); # one group for each storm
|
||||
|
||||
storm_grp.createChild("image")
|
||||
.setFile("Nasal/canvas/map/storm.png")
|
||||
.setFile("Nasal/canvas/map/Images/storm.png")
|
||||
.setSize(128*radiusNm,128*radiusNm)
|
||||
.setTranslation(-64*radiusNm,-64*radiusNm)
|
||||
.setCenter(0,0);
|
||||
|
|
|
@ -8,18 +8,20 @@ StormModel.init = func {
|
|||
foreach (var n; props.globals.getNode("/instrumentation/wxradar",1).getChildren("storm")) {
|
||||
# Model 3 degree radar beam
|
||||
var stormLat = n.getNode("latitude-deg").getValue();
|
||||
stormLon = n.getNode("longitude-deg").getValue();
|
||||
acLat = getprop("/position/latitude-deg");
|
||||
acLon = getprop("/position/longitude-deg");
|
||||
stormGeo = geo.Coord.new();
|
||||
acGeo = geo.Coord.new();
|
||||
var stormLon = n.getNode("longitude-deg").getValue();
|
||||
|
||||
# FIXME: once ported to MapStructure, these should use the encapsulated "aircraft source"/driver stuff
|
||||
var acLat = getprop("/position/latitude-deg");
|
||||
var acLon = getprop("/position/longitude-deg");
|
||||
var stormGeo = geo.Coord.new();
|
||||
var acGeo = geo.Coord.new();
|
||||
|
||||
stormGeo.set_latlon(stormLat, stormLon);
|
||||
acGeo.set_latlon(acLat, acLon);
|
||||
|
||||
var directDistance = acGeo.direct_distance_to(stormGeo);
|
||||
beamH = 0.1719 * directDistance; # M2FT * tan(3deg)
|
||||
beamBase = getprop("position/altitude-ft") - beamH;
|
||||
var beamH = 0.1719 * directDistance; # M2FT * tan(3deg)
|
||||
var beamBase = getprop("position/altitude-ft") - beamH;
|
||||
|
||||
if (n.getNode("top-altitude-ft").getValue() > beamBase) {
|
||||
me.push( { lat: stormLat, lon : stormLon, radiusNm : n.getNode("radius-nm").getValue() } );
|
||||
|
|
|
@ -67,22 +67,22 @@
|
|||
|
||||
# ANSI color code wrappers (see $ man console_codes)
|
||||
#
|
||||
var _title = func(s) globals.string.color("33;42;1", s); # backtrace header
|
||||
var _section = func(s) globals.string.color("37;41;1", s); # backtrace frame
|
||||
var _error = func(s) globals.string.color("31;1", s); # internal errors
|
||||
var _bench = func(s) globals.string.color("37;45;1", s); # benchmark info
|
||||
var _title = func(s, color=nil) globals.string.color("33;42;1", s, color); # backtrace header
|
||||
var _section = func(s, color=nil) globals.string.color("37;41;1", s, color); # backtrace frame
|
||||
var _error = func(s, color=nil) globals.string.color("31;1", s, color); # internal errors
|
||||
var _bench = func(s, color=nil) globals.string.color("37;45;1", s); # benchmark info
|
||||
|
||||
var _nil = func(s) globals.string.color("32", s); # nil
|
||||
var _string = func(s) globals.string.color("31", s); # "foo"
|
||||
var _num = func(s) globals.string.color("31", s); # 0.0
|
||||
var _bracket = func(s) globals.string.color("", s); # [ ]
|
||||
var _brace = func(s) globals.string.color("", s); # { }
|
||||
var _angle = func(s) globals.string.color("", s); # < >
|
||||
var _vartype = func(s) globals.string.color("33", s); # func ghost
|
||||
var _proptype = func(s) globals.string.color("34", s); # BOOL INT LONG DOUBLE ...
|
||||
var _path = func(s) globals.string.color("36", s); # /some/property/path
|
||||
var _internal = func(s) globals.string.color("35", s); # me parents
|
||||
var _varname = func(s) s; # variable_name
|
||||
var _nil = func(s, color=nil) globals.string.color("32", s, color); # nil
|
||||
var _string = func(s, color=nil) globals.string.color("31", s, color); # "foo"
|
||||
var _num = func(s, color=nil) globals.string.color("31", s, color); # 0.0
|
||||
var _bracket = func(s, color=nil) globals.string.color("", s, color); # [ ]
|
||||
var _brace = func(s, color=nil) globals.string.color("", s, color); # { }
|
||||
var _angle = func(s, color=nil) globals.string.color("", s, color); # < >
|
||||
var _vartype = func(s, color=nil) globals.string.color("33", s, color); # func ghost
|
||||
var _proptype = func(s, color=nil) globals.string.color("34", s, color); # BOOL INT LONG DOUBLE ...
|
||||
var _path = func(s, color=nil) globals.string.color("36", s, color); # /some/property/path
|
||||
var _internal = func(s, color=nil) globals.string.color("35", s, color); # me parents
|
||||
var _varname = func(s, color=nil) s; # variable_name
|
||||
|
||||
|
||||
##
|
||||
|
@ -142,7 +142,7 @@ var _tree = func(n, graph = 1, prefix = "", level = 0) {
|
|||
}
|
||||
|
||||
|
||||
var attributes = func(p, verbose = 1) {
|
||||
var attributes = func(p, verbose = 1, color=nil) {
|
||||
var r = p.getAttribute("readable") ? "" : "r";
|
||||
var w = p.getAttribute("writable") ? "" : "w";
|
||||
var R = p.getAttribute("trace-read") ? "R" : "";
|
||||
|
@ -159,24 +159,24 @@ var attributes = func(p, verbose = 1) {
|
|||
type ~= ", L" ~ l;
|
||||
if (verbose and (var c = p.getAttribute("references")) > 2)
|
||||
type ~= ", #" ~ (c - 2);
|
||||
return _proptype(type ~ ")");
|
||||
return _proptype(type ~ ")", color);
|
||||
}
|
||||
|
||||
|
||||
var _dump_prop = func(p) {
|
||||
_path(p.getPath()) ~ " = " ~ debug.string(p.getValue()) ~ " " ~ attributes(p);
|
||||
var _dump_prop = func(p, color=nil) {
|
||||
_path(p.getPath(), color) ~ " = " ~ debug.string(p.getValue()) ~ " " ~ attributes(p);
|
||||
}
|
||||
|
||||
|
||||
var _dump_var = func(v) {
|
||||
var _dump_var = func(v, color=nil) {
|
||||
if (v == "me" or v == "parents")
|
||||
return _internal(v);
|
||||
return _internal(v, color);
|
||||
else
|
||||
return _varname(v);
|
||||
return _varname(v, color);
|
||||
}
|
||||
|
||||
|
||||
var _dump_string = func(str) {
|
||||
var _dump_string = func(str, color=nil) {
|
||||
var s = "'";
|
||||
for (var i = 0; i < size(str); i += 1) {
|
||||
var c = str[i];
|
||||
|
@ -193,55 +193,55 @@ var _dump_string = func(str) {
|
|||
else
|
||||
s ~= sprintf("\\x%02x", c);
|
||||
}
|
||||
return _string(s ~ "'");
|
||||
return _string(s ~ "'", color);
|
||||
}
|
||||
|
||||
|
||||
# dump hash keys as variables if they are valid variable names, or as string otherwise
|
||||
var _dump_key = func(s) {
|
||||
var _dump_key = func(s, color=nil) {
|
||||
if (num(s) != nil)
|
||||
return _num(s);
|
||||
return _num(s, color);
|
||||
if (!size(s))
|
||||
return _dump_string(s);
|
||||
return _dump_string(s, color);
|
||||
if (!globals.string.isalpha(s[0]) and s[0] != `_`)
|
||||
return _dump_string(s);
|
||||
return _dump_string(s, color);
|
||||
for (var i = 1; i < size(s); i += 1)
|
||||
if (!globals.string.isalnum(s[i]) and s[i] != `_`)
|
||||
return _dump_string(s);
|
||||
_dump_var(s);
|
||||
return _dump_string(s, color);
|
||||
_dump_var(s, color);
|
||||
}
|
||||
|
||||
|
||||
var string = func(o) {
|
||||
var string = func(o, color=nil) {
|
||||
var t = typeof(o);
|
||||
if (t == "nil") {
|
||||
return _nil("nil");
|
||||
return _nil("nil", color);
|
||||
|
||||
} elsif (t == "scalar") {
|
||||
return num(o) == nil ? _dump_string(o) : _num(o~"");
|
||||
return num(o) == nil ? _dump_string(o, color) : _num(o~"", color);
|
||||
|
||||
} elsif (t == "vector") {
|
||||
var s = "";
|
||||
forindex (var i; o)
|
||||
s ~= (i == 0 ? "" : ", ") ~ debug.string(o[i]);
|
||||
return _bracket("[") ~ s ~ _bracket("]");
|
||||
s ~= (i == 0 ? "" : ", ") ~ debug.string(o[i], color);
|
||||
return _bracket("[", color) ~ s ~ _bracket("]", color);
|
||||
|
||||
} elsif (t == "hash") {
|
||||
if (contains(o, "parents") and typeof(o.parents) == "vector"
|
||||
and size(o.parents) == 1 and o.parents[0] == props.Node)
|
||||
return _angle("<") ~ _dump_prop(o) ~ _angle(">");
|
||||
return _angle("<", color) ~ _dump_prop(o, color) ~ _angle(">", color);
|
||||
|
||||
var k = keys(o);
|
||||
var s = "";
|
||||
forindex (var i; k)
|
||||
s ~= (i == 0 ? "" : ", ") ~ _dump_key(k[i]) ~ ": " ~ debug.string(o[k[i]]);
|
||||
return _brace("{") ~ " " ~ s ~ " " ~ _brace("}");
|
||||
s ~= (i == 0 ? "" : ", ") ~ _dump_key(k[i], color) ~ ": " ~ debug.string(o[k[i]], color);
|
||||
return _brace("{", color) ~ " " ~ s ~ " " ~ _brace("}", color);
|
||||
|
||||
} elsif (t == "ghost") {
|
||||
return _angle("<") ~ _nil(ghosttype(o)) ~ _angle(">");
|
||||
return _angle("<", color) ~ _nil(ghosttype(o), color) ~ _angle(">", color);
|
||||
|
||||
} else {
|
||||
return _angle("<") ~ _vartype(t) ~ _angle(">");
|
||||
return _angle("<", color) ~ _vartype(t, color) ~ _angle(">", color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,230 +0,0 @@
|
|||
# failures.nas a manager for failing systems based on MTBF/MCBF
|
||||
|
||||
# Time between MTBF checks
|
||||
var dt = 10;
|
||||
|
||||
# Root property for failure information
|
||||
var failure_root = "/sim/failure-manager";
|
||||
|
||||
# Enumerations
|
||||
var type = { MTBF : 1, MCBF: 2 };
|
||||
var fail = { SERVICEABLE : 1, JAM : 2, ENGINE: 3};
|
||||
|
||||
# This hash contains a mapping from property entry to a failure object
|
||||
# containing the following members:
|
||||
# type: MTBF|MCBF Mean Time Between Failures/Mean Cycle Between Failures
|
||||
# desc: <description> Description of property for screen output
|
||||
# failure: SERVICEABLE Property has a "serviceable" child that can be set to false
|
||||
# failure: JAM Property is failed by marking as Read-Only
|
||||
# failure: ENGINE Special case for engines, where a variety of properties are set.
|
||||
# failure: <prop> Property is failed by setting another property to false
|
||||
var breakHash = {
|
||||
"/instrumentation/adf" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "ADF" },
|
||||
"/instrumentation/dme" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "DME" },
|
||||
"/instrumentation/airspeed-indicator" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "ASI" },
|
||||
"/instrumentation/altimeter" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Altimeter" },
|
||||
"/instrumentation/attitude-indicator" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Attitude Indicator" },
|
||||
"/instrumentation/heading-indicator" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Heading Indicator" },
|
||||
"/instrumentation/magnetic-compass" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Magnetic Compass" },
|
||||
"/instrumentation/nav[0]/gs" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Nav 1 Glideslope" },
|
||||
"/instrumentation/nav[0]/cdi" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Nav 1 CDI" },
|
||||
"/instrumentation/nav[1]/gs" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Nav 2 Glideslope" },
|
||||
"/instrumentation/nav[1]/cdi" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Nav 2 CDI" },
|
||||
"/instrumentation/slip-skid-ball" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Slip/Skid Ball" },
|
||||
"/instrumentation/turn-indicator" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Turn Indicator" },
|
||||
"/instrumentation/vertical-speed-indicator" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "VSI" },
|
||||
"/systems/electrical" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Electrical system" },
|
||||
"/systems/pitot" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Pitot system" },
|
||||
"/systems/static" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Static system" },
|
||||
"/systems/vacuum" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Vacuum system" },
|
||||
"/controls/gear/gear-down" : { type: type.MCBF, failure: "/gear/serviceable", desc: "Gear" },
|
||||
"/controls/flight/aileron" : { type: type.MTBF, failure: fail.JAM, desc: "Aileron" },
|
||||
"/controls/flight/elevator" : { type: type.MTBF, failure: fail.JAM, desc: "Elevator" },
|
||||
"/controls/flight/rudder" : { type: type.MTBF, failure: fail.JAM, desc: "Rudder" },
|
||||
"/controls/flight/flaps" : { type: type.MCBF, failure: fail.JAM, desc: "Flaps" },
|
||||
"/controls/flight/speedbrake" : { type: type.MCBF, failure: fail.JAM, desc: "Speed Brake" }
|
||||
};
|
||||
|
||||
# Return the failure entry for a given property
|
||||
var getFailure = func (prop) {
|
||||
var o = breakHash[prop];
|
||||
|
||||
if (o.failure == fail.SERVICEABLE) {
|
||||
return prop ~ "/serviceable";
|
||||
} elsif (o.failure == fail.ENGINE) {
|
||||
return failure_root ~ prop ~ "/serviceable";
|
||||
} elsif (o.failure == fail.JAM) {
|
||||
return failure_root ~ prop ~ "/serviceable";
|
||||
} else {
|
||||
return o.failure;
|
||||
}
|
||||
}
|
||||
|
||||
# Fail a given property, either using a serviceable flag, or by jamming the property
|
||||
var failProp = func(prop) {
|
||||
|
||||
var o = breakHash[prop];
|
||||
var p = getFailure(prop);
|
||||
|
||||
if (getprop(p) == 1) {
|
||||
setprop(p, 0);
|
||||
|
||||
# We always print to the console
|
||||
print(getprop("/sim/time/gmt-string") ~ " : " ~ o.desc ~ " failed");
|
||||
|
||||
if (getprop(failure_root ~ "/display-on-screen")) {
|
||||
# Display message to the screen in red
|
||||
screen.log.write(o.desc ~ " failed", 1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Unfail a given property, used for resetting a failure state.
|
||||
var unfailProp = func(prop)
|
||||
{
|
||||
var p = getFailure(prop);
|
||||
setprop(p, 1);
|
||||
}
|
||||
|
||||
# Unfail all the failed properties
|
||||
var unfail = func {
|
||||
foreach(var prop; keys(breakHash)) {
|
||||
unfailProp(prop);
|
||||
}
|
||||
}
|
||||
|
||||
# Listener to jam a property. Note that the property to jam is
|
||||
# encoded within the property name
|
||||
var jamListener = func(p) {
|
||||
var jamprop = string.replace(p.getParent().getPath(), failure_root, "");
|
||||
#jamprop = string.replace(jamprop, "/serviceable", "");
|
||||
var prop = props.globals.getNode(jamprop);
|
||||
if (p.getValue()) {
|
||||
prop.setAttribute("writable", 1);
|
||||
} else {
|
||||
prop.setAttribute("writable", 0);
|
||||
}
|
||||
}
|
||||
|
||||
# Listener for an engine property. Note that the engine to set is
|
||||
# encoded within the property name. We set both the magnetos and
|
||||
# cutoff to handle different engine models.
|
||||
var engineListener = func(p) {
|
||||
var e = string.replace(p.getParent().getPath(), failure_root, "");
|
||||
var prop = props.globals.getNode(e);
|
||||
if (p.getValue()) {
|
||||
# Enable the properties, but don't set the magnetos, as they may
|
||||
# be off for a reason.
|
||||
var magnetos = props.globals.getNode("/controls/" ~ e ~ "/magnetos", 1);
|
||||
var cutoff = props.globals.getNode("/controls/" ~ e ~ "/cutoff", 1);
|
||||
magnetos.setAttribute("writable", 1);
|
||||
cutoff.setAttribute("writable", 1);
|
||||
cutoff.setValue(0);
|
||||
} else {
|
||||
# Switch off the engine, and disable writing to it.
|
||||
var magnetos = props.globals.getNode("/controls/" ~ e ~ "/magnetos", 1);
|
||||
var cutoff = props.globals.getNode("/controls/" ~ e ~ "/cutoff", 1);
|
||||
magnetos.setValue(0);
|
||||
cutoff.setValue(1);
|
||||
magnetos.setAttribute("writable", 0);
|
||||
cutoff.setAttribute("writable", 0);
|
||||
}
|
||||
}
|
||||
|
||||
# Perform a MCBF check against a failure property.
|
||||
var checkMCBF = func(prop) {
|
||||
var mcbf = getprop(failure_root ~ prop.getPath() ~ "/mcbf");
|
||||
# mcbf == mean cycles between failures
|
||||
# hence 2*mcbf is the number of _half-cycles_ between failures,
|
||||
# which is relevant because we do this check on each half-cycle:
|
||||
if ((mcbf > 0) and !int(2 * mcbf * rand())) {
|
||||
# Get the property information.
|
||||
failProp(prop.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
# Timer based loop to check MTBF properties
|
||||
var checkMTBF = func {
|
||||
foreach(var prop; keys(breakHash)) {
|
||||
var o = breakHash[prop];
|
||||
if (o.type == type.MTBF) {
|
||||
var mtbf = getprop(failure_root ~ prop ~ "/mtbf");
|
||||
if (mtbf and !int(rand() * mtbf / dt)) {
|
||||
failProp(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
settimer(checkMTBF, dt);
|
||||
}
|
||||
|
||||
# Function to set all MTBF failures to a give value. Mainly for testing.
|
||||
var setAllMTBF = func(mtbf) {
|
||||
foreach(var prop; keys(breakHash)) {
|
||||
var o = breakHash[prop];
|
||||
if (o.type == type.MTBF) {
|
||||
setprop(failure_root ~ prop ~ "/mtbf", mtbf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Function to set all MCBF failures to a give value. Mainly for testing.
|
||||
var setAllMCBF = func(mcbf) {
|
||||
foreach(var prop; keys(breakHash)) {
|
||||
var o = breakHash[prop];
|
||||
if (o.type == type.MCBF) {
|
||||
setprop(failure_root ~ prop ~ "/mcbf", mcbf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Initialization, called once Nasal and the FDM are loaded properly.
|
||||
var fdm_init_listener = _setlistener("/sim/signals/fdm-initialized", func {
|
||||
removelistener(fdm_init_listener); # uninstall, so we're only called once
|
||||
srand();
|
||||
|
||||
# Engines are added dynamically because there may be an arbitrary number
|
||||
var i = 1;
|
||||
foreach (var e; props.globals.getNode("/engines").getChildren("engine")) {
|
||||
breakHash[e.getPath()] = { type: type.MTBF, failure: fail.ENGINE, desc : "Engine " ~ i };
|
||||
i = i+1;
|
||||
}
|
||||
|
||||
# Set up serviceable, MCBF and MTBF properties.
|
||||
foreach(var prop; keys(breakHash)) {
|
||||
var o = breakHash[prop];
|
||||
var t = "/mcbf";
|
||||
|
||||
if (o.type == type.MTBF) {
|
||||
t = "/mtbf";
|
||||
}
|
||||
|
||||
# Set up the MTBF/MCFB properties to 0. Note that they are in a separate
|
||||
# subtree, as there's no guarantee that the property isn't a leaf.
|
||||
props.globals.initNode(failure_root ~ prop ~ t, 0);
|
||||
|
||||
if (o.failure == fail.SERVICEABLE) {
|
||||
# If the property has a serviceable property, set it if appropriate.
|
||||
props.globals.initNode(prop ~ "/serviceable", 1, "BOOL");
|
||||
} elsif (o.failure == fail.JAM) {
|
||||
# In the JAM case, we actually have a dummy serviceable property for the GUI.
|
||||
props.globals.initNode(failure_root ~ prop ~ "/serviceable", 1, "BOOL");
|
||||
setlistener(failure_root ~ prop ~ "/serviceable", jamListener);
|
||||
} elsif (o.failure == fail.ENGINE) {
|
||||
# In the JAM case, we actually have a dummy serviceable property for the GUI.
|
||||
props.globals.initNode(failure_root ~ prop ~ "/serviceable", 1, "BOOL");
|
||||
setlistener(failure_root ~ prop ~ "/serviceable", engineListener);
|
||||
} else {
|
||||
# If the serviceable property is actually defined, check it is set.
|
||||
props.globals.initNode(o.failure, 1, "BOOL");
|
||||
}
|
||||
|
||||
if (o.type == type.MCBF) {
|
||||
# Set up listener for MCBF properties, only when the value changes.
|
||||
setlistener(prop, checkMCBF, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
# Start checking for failures.
|
||||
checkMTBF();
|
||||
});
|
||||
|
|
@ -27,6 +27,15 @@ var L2GAL = 1 / GAL2L;
|
|||
# container for local variables, so as not to clutter the global namespace
|
||||
var __ = {};
|
||||
|
||||
##
|
||||
# Aborts execution if <condition> evaluates to false.
|
||||
# Prints an optional message if present, or just "assertion failed!"
|
||||
#
|
||||
var assert = func (condition, message=nil) {
|
||||
message != nil or (message = "assertion failed!");
|
||||
condition or die(message);
|
||||
}
|
||||
|
||||
##
|
||||
# Returns true if the first object is an instance of the second
|
||||
# (class) object. Example: isa(someObject, props.Node)
|
||||
|
@ -180,4 +189,3 @@ settimer(func {
|
|||
if(size(file) > 4 and substr(file, -4) == ".nas")
|
||||
io.load_nasal(path ~ "/" ~ file, substr(file, 0, size(file) - 4));
|
||||
}, 0);
|
||||
|
||||
|
|
|
@ -1381,11 +1381,6 @@ _setlistener("/sim/signals/fdm-initialized", func {
|
|||
do_welcome = 0;
|
||||
});
|
||||
|
||||
# load ATC chatter module on demand
|
||||
setprop("/nasal/atc-chatter/enabled", getprop("/sim/sound/chatter/enabled"));
|
||||
_setlistener("/sim/sound/chatter/enabled", func {
|
||||
setprop("/nasal/atc-chatter/enabled", getprop("/sim/sound/chatter/enabled"));
|
||||
});
|
||||
|
||||
##
|
||||
# overwrite custom shader settings when quality-level is set on startup
|
||||
|
|
|
@ -47,8 +47,6 @@ var include = func(file) {
|
|||
if (contains(ns, module))
|
||||
return;
|
||||
|
||||
ns[module] = "included";
|
||||
|
||||
var code = call(compile, [readfile(path), path], var err = []);
|
||||
if (size(err)) {
|
||||
if (find("Parse error:", err[0]) < 0)
|
||||
|
@ -57,6 +55,7 @@ var include = func(file) {
|
|||
die(sprintf("%s\n in included file: %s", err[0], path));
|
||||
}
|
||||
|
||||
ns[module] = "included";
|
||||
call(bind(code, ns, fn), [], nil, ns);
|
||||
}
|
||||
|
||||
|
@ -425,4 +424,3 @@ _setlistener("/sim/signals/nasal-dir-initialized", func {
|
|||
}
|
||||
})();
|
||||
});
|
||||
|
||||
|
|
|
@ -227,6 +227,11 @@ var wrapNode = func(node) { { parents : [Node], _g : node } }
|
|||
#
|
||||
var globals = wrapNode(_globals());
|
||||
|
||||
##
|
||||
# Shortcut for props.globals.getNode().
|
||||
#
|
||||
var getNode = func return call(props.globals.getNode, arg, props.globals);
|
||||
|
||||
##
|
||||
# Sets all indexed property children to a single value. arg[0]
|
||||
# specifies a property name (e.g. /controls/engines/engine), arg[1] a
|
||||
|
|
|
@ -15,7 +15,7 @@ var RouteManagerDelegate = {
|
|||
|
||||
departureChanged: func
|
||||
{
|
||||
debug.dump('saw departure changed');
|
||||
printlog('info', 'saw departure changed');
|
||||
me.flightplan.clearWPType('sid');
|
||||
if (me.flightplan.departure == nil)
|
||||
return;
|
||||
|
@ -36,13 +36,13 @@ var RouteManagerDelegate = {
|
|||
|
||||
# and we have a SID
|
||||
var sid = me.flightplan.sid;
|
||||
debug.dump('routing via SID ' ~ sid.id);
|
||||
printlog('info', 'routing via SID ' ~ sid.id);
|
||||
me.flightplan.insertWaypoints(sid.route(me.flightplan.departure_runway), 1);
|
||||
},
|
||||
|
||||
arrivalChanged: func
|
||||
{
|
||||
debug.dump('saw arrival changed');
|
||||
printlog('info', 'saw arrival changed');
|
||||
me.flightplan.clearWPType('star');
|
||||
me.flightplan.clearWPType('approach');
|
||||
if (me.flightplan.destination == nil)
|
||||
|
@ -57,17 +57,17 @@ var RouteManagerDelegate = {
|
|||
}
|
||||
|
||||
if (me.flightplan.star != nil) {
|
||||
debug.dump('routing via STAR ' ~ me.flightplan.star.id);
|
||||
printlog('info', 'routing via STAR ' ~ me.flightplan.star.id);
|
||||
var wps = me.flightplan.star.route(me.flightplan.destination_runway);
|
||||
me.flightplan.insertWaypoints(wps, -1);
|
||||
}
|
||||
|
||||
if (me.flightplan.approach != nil) {
|
||||
debug.dump('routing via approach ' ~ me.flightplan.approach.id);
|
||||
printlog('info', 'routing via approach ' ~ me.flightplan.approach.id);
|
||||
var wps = me.flightplan.approach.route();
|
||||
me.flightplan.insertWaypoints(wps, -1);
|
||||
} else {
|
||||
debug.dump('routing direct to runway ' ~ me.flightplan.destination_runway.id);
|
||||
printlog('info', 'routing direct to runway ' ~ me.flightplan.destination_runway.id);
|
||||
# no approach, just use the runway waypoint
|
||||
var wp = createWPFrom(me.flightplan.destination_runway);
|
||||
wp.wp_role = 'approach';
|
||||
|
@ -77,14 +77,14 @@ var RouteManagerDelegate = {
|
|||
|
||||
cleared: func
|
||||
{
|
||||
debug.dump("saw active flightplan cleared, deactivating");
|
||||
printlog('info', "saw active flightplan cleared, deactivating");
|
||||
# see http://https://code.google.com/p/flightgear-bugs/issues/detail?id=885
|
||||
fgcommand("activate-flightplan", props.Node.new({"activate": 0}));
|
||||
},
|
||||
|
||||
endOfFlightPlan: func
|
||||
{
|
||||
debug.dump("end of flight-plan, deactivating");
|
||||
printlog('info', "end of flight-plan, deactivating");
|
||||
fgcommand("activate-flightplan", props.Node.new({"activate": 0}));
|
||||
}
|
||||
};
|
||||
|
@ -105,7 +105,7 @@ var FMSDelegate = {
|
|||
var wow = getprop('gear/gear[0]/wow');
|
||||
var gs = getprop('velocities/groundspeed-kt');
|
||||
if (wow and (gs < 25)) {
|
||||
debug.dump('touchdown on destination runway, end of route.');
|
||||
printlog('info', 'touchdown on destination runway, end of route.');
|
||||
me.landingCheck.stop();
|
||||
# record touch-down time?
|
||||
me.flightplan.finish();
|
||||
|
@ -118,7 +118,7 @@ var FMSDelegate = {
|
|||
|
||||
endOfFlightPlan: func
|
||||
{
|
||||
debug.dump('end of flight-plan');
|
||||
printlog('info', 'end of flight-plan');
|
||||
},
|
||||
|
||||
currentWaypointChanged: func
|
||||
|
@ -128,12 +128,12 @@ var FMSDelegate = {
|
|||
me.landingCheck = nil; # delete timer
|
||||
}
|
||||
|
||||
#debug.dump('saw current WP changed, now ' ~ me.flightplan.current);
|
||||
#printlog('info', 'saw current WP changed, now ' ~ me.flightplan.current);
|
||||
var active = me.flightplan.currentWP();
|
||||
if (active == nil) return;
|
||||
|
||||
if (active.alt_cstr_type == "at") {
|
||||
debug.dump('new WP has valid altitude restriction, setting on AP');
|
||||
printlog('info', 'new WP has valid altitude restriction, setting on AP');
|
||||
setprop('/autopilot/settings/target-altitude-ft', active.alt_cstr);
|
||||
}
|
||||
|
||||
|
|
|
@ -486,10 +486,11 @@ var scanf = func(test, format, result) {
|
|||
# ANSI colors (see $ man console_codes)
|
||||
#
|
||||
var setcolors = func(enabled) {
|
||||
if (enabled and getprop("/sim/startup/stderr-to-terminal"))
|
||||
color = func(color, s) { "\x1b[" ~ color ~ "m" ~ s ~ "\x1b[m" }
|
||||
else
|
||||
color = func(dummy, s) { s }
|
||||
color_enabled = (enabled and getprop("/sim/startup/stderr-to-terminal"));
|
||||
}
|
||||
var color = func(color, s, enabled=nil) {
|
||||
if (enabled == nil) enabled = color_enabled;
|
||||
return enabled ? "\x1b[" ~ color ~ "m" ~ s ~ "\x1b[m" : s;
|
||||
}
|
||||
|
||||
|
||||
|
@ -499,10 +500,9 @@ var setcolors = func(enabled) {
|
|||
#
|
||||
# print(string.color("31;1", "this is red"));
|
||||
#
|
||||
var color = func nil;
|
||||
setcolors(getprop("/sim/startup/terminal-ansi-colors"));
|
||||
var color_enabled = 0;
|
||||
_setlistener("/sim/signals/nasal-dir-initialized", func {
|
||||
setlistener("/sim/startup/terminal-ansi-colors", func(n) setcolors(n.getBoolValue()));
|
||||
setlistener("/sim/startup/terminal-ansi-colors", func(n) setcolors(n.getBoolValue()), 1, 0);
|
||||
});
|
||||
|
||||
|
||||
|
|
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 7.6 KiB |