- Code: Select all
# A combo element like that in PUI
var Combo = {
# Make a new one off of a Canvas group
#
# @param group The Canvas group to make this a child of
# @param values The list of values to start out with
new: func(group, values=nil, default=nil) {
var me = {
parents:[Combo, group.createChild("group")],
values: values==nil?[]:values, #vector<string> of values: should this be in the property tree?
_values: [], #vector<canvas.Text>: handles to canvas.Text objects to manipulate each value
state: 0, #-1: show all; other: index of selected value
listener: func{}, #a function that gets called when clicked
spacing: 18, #parameter: y increment between text elements
border: 4, #parameter: spacing around various elements
font_sz: 14,
width: 170, #width of this box
};
if (default != nil) me.state = me.findValue(default);
me.addEventListener("click", func(e) {
var y=me._getTf().f.getValue();
if (me.state == -1) {
y-=me.spacing/2; #correct for the center alignment of the text
var s = nil;
for (var i=1; i<=size(me.values); i+=1)
if (e.clientY < y+me.spacing*i) {var s = me.state = i-1; break}
if (s != nil)
me.listener(me.values[s], s, e);
else
die("Invalid mouse click");
} else {
me.state = -1;
me.listener(nil, -1, e);
}
me._drawValues();
});
me.fill = me.createChild("path");
me._makeValues();
me._drawValues();
return me;
},
# Private; make text objects out of each value
_makeValues: func() {
foreach (var v; me._values) v.del();
me._values = setsize([], size(me.values));
forindex (var i; me.values) {
me._values[i] = me.parents[1].createChild("text")
.setText(me.values[i])
.setTranslation(0, 0)
.setAlignment("left-center")
.setFontSize(me.font_sz)
.setFont("LiberationFonts/LiberationSans-Bold.ttf")
.setColor(1,1,1)
.hide();
}
if (me.state < size(me.values))
me._values[me.state].show();
},
# Private: position the values based upon me.state
_drawValues: func() {
if (size(me._values) != size(me.values)) print("Sizes differ!");
#var x=me._getTf().e.getValue();
#var y=me._getTf().f.getValue();
if (me.state >= 0) {
foreach (var v; me._values) v.hide().setTranslation(0,0);
if (me.state < size(me._values))
me._values[me.state].show();
me.fill.reset()
.moveTo(-me.border,-me.font_sz/2-me.border)
.horizTo(me.width)
.vertTo(me.font_sz/2+me.border)
.horizTo(-me.border)
.close()
.setColorFill(0,0,0);
} else {
var incr = -me.spacing;
foreach (var v; me._values) {
v.show().setTranslation(0,incr+=me.spacing);
}
me.fill.reset()
.moveTo(-me.border,-me.font_sz/2-me.border)
.horizTo(me.width)
.vertTo(me.font_sz/2+me.border+incr)
.horizTo(-me.border)
.close()
.setColorFill(0,0,0);
}
},
# Add a new value into the list at the specified position
#
# @param value The value (string)
# @param idx The index to add it in at; default: the end
addValue: func(value, idx=-1) {
if (!size(me.values) and (idx == 0 or idx == -1)) {
me.values = [value];
me._makeValues();
return me;
}
if (idx < 0) idx += size(me.values);
if (idx < 0 or idx >= size(me.values)) return me;
if (idx == size(me.values)-1)
me.values = me.values[:idx]~[value];
else
me.values = me.values[:idx]~[value]~me.values[idx+1:];
me._makeValues();
return me;
},
# Remove a value from the list
#
# @param value Either the value itself or its index
removeValue: func(value) {
var idx = me.findValue(value);
if (idx == nil or idx < 0 or idx >= size(me.values)) return me;
if (idx == size(me.values)-1)
me.values = me.values[:idx-1];
elsif (idx == 0)
me.values = me.values[idx+1:];
else
me.values = me.values[:idx-1]~me.values[idx+1:];
me._makeValues();
return me;
},
# Set the listener for this box. Gets called when a new value is
# selected with the value and index as arguments
setListener: func(listener_fn) {
me.listener = listener_fn;
return me;
},
# Find a value in the list
#
# @param value Either the value itself or its index
findValue: func(value) {
if (num(value) != nil) return value;
forindex (var i; me.values)
if (me.values[i] == value) return i;
return nil;
},
# Set the direction for this to open
#
# @param dir 1 for up, -1 for down
setDir: func(dir=1) {
me.set("direction", dir);
},
#_getTf: func() {
# me.parents[1].parents[1].parents[1]._getTf();
#},
};
And my surrounding code (you'll have to modify the io.load_nasal usage to use outside of my joystick ):
- Code: Select all
io.load_nasal(Joystick.Root ~ "/gui-elements.nas", "elements"); #the Combo class
var state = {};
var mode_editor = nil;
var make_window = func {
mode_editor = getprop("/sim/version/flightgear") == "2.10.0" ? canvas.Dialog.new([400,300]) : canvas.Window.new([400,300]);
var my_canvas = mode_editor.createCanvas()
.setColorBackground(0,0,0,0);
var root = my_canvas.createGroup();
var title_bar = root.createChild("group");
title_bar.addEventListener("drag", func(e) { mode_editor.move(e.deltaX, e.deltaY); });
var x = 0;
var y = 0;
var rx = 8;
var ry = 8;
var w = 400;
var h = 20;
title_bar.createChild("path")
.moveTo(x + w - rx, y)
.arcSmallCWTo(rx, ry, 0, x + w, y + ry)
.vertTo(y + h)
.horizTo(x)
.vertTo(y + ry)
.arcSmallCWTo(rx, ry, 0, x + rx, y)
.close()
.setColorFill(0.25,0.24,0.22)
.setStrokeLineWidth(0);
y = 20;
h = 280;
root.createChild("path")
.moveTo(x + w, y)
.vertTo(y + h)
.horizTo(x)
.vertTo(y)
.setColorFill(1,1,1)
.setColor(0,0,0);
x = 8;
y = 5;
w = 10;
h = 10;
title_bar.createChild("path", "icon-close")
.moveTo(x, y)
.lineTo(x + w, y + h)
.moveTo(x + w, y)
.lineTo(x, y + h)
.setColor(1,0,0)
.setStrokeLineWidth(3)
.addEventListener("click", func {mode_editor.del(); mode_editor = nil});
title_bar.createChild("text", "dialog-caption")
.setText("Joystick Mode Editor")
.setTranslation(x + w + 8, 4)
.setAlignment("left-top")
.setFontSize(14)
.setFont("LiberationFonts/LiberationSans-Bold.ttf")
.setColor(1,1,1);
var body = root.createChild("group");
x = 4;
y = 14;
body.createChild("text")
.setText("Selected joystick:")
.setTranslation(x, 20+y+2)
.setAlignment("left-center")
.setFontSize(14)
.setFont("LiberationFonts/LiberationSans-Bold.ttf")
.setColor(0,0,0);
x += 150;
var joystick_list = elements.Combo.new(body, [], 0)
.setTranslation(x, 20+y)
.setListener(func gui.popupTip("Joystick is "~(arg[0]==nil?"being selected":arg[0]~" at index "~arg[1])));
x = 4;
y = 40;
body.createChild("text")
.setText("Selected mode:")
.setTranslation(x, 20+y)
.setAlignment("left-center")
.setFontSize(14)
.setFont("LiberationFonts/LiberationSans-Bold.ttf")
.setColor(0,0,0);
x += 150;
var mode_list = elements.Combo.new(body, [], 0)
.setTranslation(x, 20+y)
.setListener(func gui.popupTip("Mode is "~(arg[0]==nil?"being selected":arg[0]~" at index "~arg[1])));
var w = 400;
body.createChild("path")
.moveTo(4,75)
.horizTo(w-4)
.setColor(0,0,0);
joystick_list.addValue("[none]").addValue("Cyborg-X").addValue("Missing").addValue("One?");
#foreach (var js; Joystick.getParent().getChildren("js"))
# joystick_list.addValue(js.getNode("name").getValue());
foreach (var mode; Joystick.getChildren("mode"))
mode_list.addValue(mode.getNode("name").getValue());
state = caller(0)[0]; #make this visible as more than a temporary namespace to make debugging possible
};
var toggle_window = func {
if (mode_editor != nil) {mode_editor.del(); mode_editor = nil}
else {
io.load_nasal(Joystick.Root ~ "/gui.nas", namespace);
make_window();
}
};
P.S. One thing that I noticed is that text, based upon it's content, has a different bounding box -- and that screws up the alignment, as text looks best if it is aligned by its baseline. That means text with parenthesis gets a higher baseline than text without those characters.
P.P.S. Thanks Thomas for all your work on Canvas!!!