10093284ba
the "voice" part. It's no longer possible to display the three letters {|} in screen message at the moment, but they aren't overly useful. We may want to support escaping in the future, if necessary.
314 lines
8.1 KiB
Text
314 lines
8.1 KiB
Text
# screen log window
|
|
# =================
|
|
#
|
|
#
|
|
# simple use:
|
|
#
|
|
# foo = screen.window.new()
|
|
# foo.write("message in the middle of the screen");
|
|
#
|
|
#
|
|
# advanced use:
|
|
#
|
|
# bar = screen.window.new(nil, -100, 3, 10);
|
|
# bar.fg = [1, 1, 1, 1]; # choose white color
|
|
# bar.align = "left";
|
|
#
|
|
# bar.write("first line");
|
|
# bar.write("second line (red)", 1, 0, 0);
|
|
#
|
|
#
|
|
#
|
|
# arguments:
|
|
# x ... x coordinate
|
|
# y ... y coordinate
|
|
# positive coords position relative to the left/lower corner,
|
|
# negative coords from the right/upper corner, nil centers
|
|
# maxlines ... max number of displayed lines; if more are pushed into the
|
|
# screen, then the ones on top fall off
|
|
# autoscroll ... seconds that each line should be shown; can be less if
|
|
# a message falls off; if 0 then don't scroll at all
|
|
#
|
|
|
|
var isspace = func(c) { c == ` ` or c == `\t` or c == `\n` or c == `\r` }
|
|
|
|
|
|
##
|
|
# trim spaces at the left (lr < 0), at the right (lr > 0), or both (lr = 0)
|
|
#
|
|
var trim = func(s, lr = 0) {
|
|
var l = 0;
|
|
if (lr <= 0) {
|
|
for (; l < size(s); l += 1) {
|
|
if (!isspace(s[l])) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
var r = size(s) - 1;
|
|
if (lr >= 0) {
|
|
for (; r >= 0; r -= 1) {
|
|
if (!isspace(s[r])) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return r < l ? "" : substr(s, l, r - l + 1);
|
|
}
|
|
|
|
|
|
##
|
|
# convert string for output; replaces tabs by spaces, and skips
|
|
# delimitiers and the voice part in "{text|voice}" constructions
|
|
#
|
|
var sanitize = func(s) {
|
|
var r = "";
|
|
var skip = 0;
|
|
for (var i = 0; i < size(s); i += 1) {
|
|
var c = s[i];
|
|
if (c == `\t`) {
|
|
r ~= ' ';
|
|
} elsif (c == `{`) {
|
|
#
|
|
} elsif (c == `|`) {
|
|
skip = 1;
|
|
} elsif (c == `}`) {
|
|
skip = 0;
|
|
} elsif (!skip) {
|
|
r ~= chr(c);
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
|
|
|
|
var dialog_id = 0;
|
|
var theme_font = nil;
|
|
|
|
window = {
|
|
new : func(x = nil, y = nil, maxlines = 10, autoscroll = 10) {
|
|
var m = { parents : [window] };
|
|
#
|
|
# "public"
|
|
m.x = x;
|
|
m.y = y;
|
|
m.maxlines = maxlines;
|
|
m.autoscroll = autoscroll; # display time in seconds
|
|
m.sticky = 0; # reopens on old place
|
|
m.font = nil;
|
|
m.bg = [0, 0, 0, 0]; # background color
|
|
m.fg = [0.9, 0.4, 0.2, 1]; # default foreground color
|
|
m.align = "center"; # "left", "right", "center"
|
|
#
|
|
# "private"
|
|
m.name = "__screen_window_" ~ (dialog_id += 1) ~ "__";
|
|
m.lines = [];
|
|
m.skiptimer = 0;
|
|
m.dialog = nil;
|
|
m.namenode = props.Node.new({ "dialog-name" : m.name });
|
|
setlistener("/sim/startup/xsize", func { m._redraw_() });
|
|
setlistener("/sim/startup/ysize", func { m._redraw_() });
|
|
return m;
|
|
},
|
|
|
|
write : func(msg, r = nil, g = nil, b = nil, a = nil) {
|
|
if (me.namenode == nil) { return }
|
|
if (r == nil) { r = me.fg[0] }
|
|
if (g == nil) { g = me.fg[1] }
|
|
if (b == nil) { b = me.fg[2] }
|
|
if (a == nil) { a = me.fg[3] }
|
|
foreach (var line; split("\n", trim(msg))) {
|
|
line = sanitize(trim(line));
|
|
append(me.lines, [line, r, g, b, a]);
|
|
if (size(me.lines) > me.maxlines) {
|
|
me.lines = subvec(me.lines, 1);
|
|
if (me.autoscroll) {
|
|
me.skiptimer += 1;
|
|
}
|
|
}
|
|
if (me.autoscroll) {
|
|
settimer(func { me._timeout_() }, me.autoscroll, 1);
|
|
}
|
|
}
|
|
me.show();
|
|
},
|
|
|
|
show : func {
|
|
if (me.dialog != nil) {
|
|
me.close();
|
|
}
|
|
|
|
me.dialog = gui.Widget.new();
|
|
me.dialog.set("name", me.name);
|
|
if (me.x != nil) { me.dialog.set("x", me.x) }
|
|
if (me.y != nil) { me.dialog.set("y", me.y) }
|
|
me.dialog.set("layout", "vbox");
|
|
me.dialog.set("default-padding", 2);
|
|
if (me.font != nil) {
|
|
me.dialog.setFont(me.font);
|
|
} elsif (theme_font != nil) {
|
|
me.dialog.setFont(theme_font);
|
|
}
|
|
me.dialog.setColor(me.bg[0], me.bg[1], me.bg[2], me.bg[3]);
|
|
|
|
foreach (var line; me.lines) {
|
|
var w = me.dialog.addChild("text");
|
|
w.set("halign", me.align);
|
|
w.set("label", line[0]);
|
|
w.setColor(line[1], line[2], line[3], line[4]);
|
|
}
|
|
|
|
fgcommand("dialog-new", me.dialog.prop());
|
|
fgcommand("dialog-show", me.namenode);
|
|
},
|
|
|
|
close : func {
|
|
fgcommand("dialog-close", me.namenode);
|
|
if (me.dialog != nil and me.sticky) {
|
|
me.x = me.dialog.prop().getNode("lastx").getValue();
|
|
me.y = me.dialog.prop().getNode("lasty").getValue();
|
|
}
|
|
},
|
|
|
|
_timeout_ : func {
|
|
if (me.skiptimer > 0) {
|
|
me.skiptimer -= 1;
|
|
return;
|
|
}
|
|
if (size(me.lines) > 1) {
|
|
me.lines = subvec(me.lines, 1);
|
|
me.show();
|
|
} else {
|
|
me.close();
|
|
dialog = nil;
|
|
me.lines = [];
|
|
}
|
|
},
|
|
|
|
_redraw_ : func {
|
|
if (me.dialog != nil) {
|
|
me.close();
|
|
me.show();
|
|
}
|
|
},
|
|
};
|
|
|
|
|
|
|
|
var log = nil;
|
|
|
|
var L = _setlistener("/sim/signals/nasal-dir-initialized", func {
|
|
removelistener(L);
|
|
setlistener("/sim/gui/current-style", func {
|
|
var theme = getprop("/sim/gui/current-style");
|
|
theme_font = getprop("/sim/gui/style[" ~ theme ~ "]/fonts/message-display/name");
|
|
}, 1);
|
|
|
|
log = window.new(nil, -30, 10, 10);
|
|
|
|
var b = "/sim/screen/";
|
|
setlistener(b ~ "black", func { log.write(cmdarg().getValue(), 0, 0, 0) });
|
|
setlistener(b ~ "white", func { log.write(cmdarg().getValue(), 1, 1, 1) });
|
|
setlistener(b ~ "red", func { log.write(cmdarg().getValue(), 0.8, 0, 0) });
|
|
setlistener(b ~ "green", func { log.write(cmdarg().getValue(), 0, 0.6, 0) });
|
|
setlistener(b ~ "blue", func { log.write(cmdarg().getValue(), 0, 0, 0.8) });
|
|
setlistener(b ~ "yellow", func { log.write(cmdarg().getValue(), 0.8, 0.8, 0) });
|
|
setlistener(b ~ "magenta", func { log.write(cmdarg().getValue(), 0.7, 0, 0.7) });
|
|
setlistener(b ~ "cyan", func { log.write(cmdarg().getValue(), 0, 0.6, 0.6) });
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
##############################################################################
|
|
# functions that make use of the window class (and don't belong anywhere else)
|
|
##############################################################################
|
|
|
|
|
|
msg_repeat = func {
|
|
if (getprop("/sim/tutorial/running")) {
|
|
var last = getprop("/sim/tutorial/last-message");
|
|
if (last == nil) {
|
|
return;
|
|
}
|
|
setprop("/sim/messages/pilot", "Say again ...");
|
|
settimer(func { setprop("/sim/messages/copilot", last) }, 1.5);
|
|
|
|
} else {
|
|
var last = atc.getValue();
|
|
if (last == nil) {
|
|
return;
|
|
}
|
|
setprop("/sim/messages/pilot", "This is " ~ callsign.getValue() ~ ". Say again, over.");
|
|
settimer(func {
|
|
atc.setValue(atclast.getValue());
|
|
}, 6);
|
|
}
|
|
}
|
|
|
|
|
|
var atc = nil;
|
|
var callsign = nil;
|
|
var atclast = nil;
|
|
var listener = {};
|
|
|
|
var M = _setlistener("/sim/signals/nasal-dir-initialized", func {
|
|
removelistener(M);
|
|
# set /sim/screen/nomap=true to prevent default message mapping
|
|
var nomap = getprop("/sim/screen/nomap");
|
|
if (nomap != nil and nomap) {
|
|
return;
|
|
}
|
|
|
|
callsign = props.globals.getNode("/sim/user/callsign", 1);
|
|
atc = props.globals.getNode("/sim/messages/atc", 1);
|
|
atclast = props.globals.getNode("/sim/messages/atc-last", 1);
|
|
atclast.setValue("");
|
|
|
|
# map ATC messages to the screen log and to the voice subsystem
|
|
var map = func(type, msg, r, g, b) {
|
|
setprop("/sim/sound/voices/" ~ type, msg);
|
|
screen.log.write(msg, r, g, b);
|
|
printlog("info", "{", type, "} ", msg);
|
|
|
|
# save last ATC message for user callsign, unless this was already
|
|
# a repetition; insert "I say again" appropriately
|
|
if (type == "atc") {
|
|
var cs = callsign.getValue();
|
|
if (find(", I say again: ", atc.getValue()) < 0
|
|
and (var pos = find(cs, msg)) >= 0) {
|
|
var m = substr(msg, 0, pos + size(cs));
|
|
msg = substr(msg, pos + size(cs));
|
|
|
|
if ((pos = find("Tower, ", msg)) >= 0) {
|
|
m ~= substr(msg, 0, pos + 7);
|
|
msg = substr(msg, pos + 7);
|
|
} else {
|
|
m ~= ", ";
|
|
}
|
|
m ~= "I say again: " ~ msg;
|
|
atclast.setValue(m);
|
|
printlog("debug", "ATC_LAST_MESSAGE: ", m);
|
|
}
|
|
}
|
|
}
|
|
|
|
var m = "/sim/messages/";
|
|
listener["atc"] = setlistener(m ~ "atc",
|
|
func { map("atc", cmdarg().getValue(), 0.7, 1.0, 0.7) });
|
|
listener["approach"] = setlistener(m ~ "approach",
|
|
func { map("approach", cmdarg().getValue(), 0.7, 1.0, 0.7) });
|
|
listener["ground"] = setlistener(m ~ "ground",
|
|
func { map("ground", cmdarg().getValue(), 0.7, 1.0, 0.7) });
|
|
|
|
listener["pilot"] = setlistener(m ~ "pilot",
|
|
func { map("pilot", cmdarg().getValue(), 1.0, 0.8, 0.0) });
|
|
listener["copilot"] = setlistener(m ~ "copilot",
|
|
func { map("copilot", cmdarg().getValue(), 1.0, 1.0, 1.0) });
|
|
listener["ai-plane"] = setlistener(m ~ "ai-plane",
|
|
func { map("ai-plane", cmdarg().getValue(), 0.9, 0.4, 0.2) });
|
|
});
|
|
|
|
|