e510c8917f
- first stab at refactoring the map.nas module, and trying to let the API evolve according to our requirements - split up the module into separate files (some of them will disappear soon) - split up the "drawing" loops into separate functions so that they can be individually called - move actual "drawing" to map_layers.nas - introduce some OOP helpers to prepare a pure Layer-based design - prepare helpers: LayeredMap, GenericMap, AirportMap (TODO: use a real "Layer" class) - move airport features (taxiways, runways, parking, tower) to separate layers (i.e. canvas groups) - avoid using a single update callback and use different layer-specific callbacks to update individual layers more efficiently - add some boilerplate hashes to prepare the MVC design - allow lazy updating of layers, where canvas groups are only populated on demand, to save some time during instantiation, i.e. loading an airport without "parking" selected, will only populate the layer once the checkbox is checked - extend the original code such that it supports showing multiple airports at once - add some proof of concept "navaid" layer using SVG files for navaid symbols (added only NDB symbol from wikimedia commons) regressions: - runway highlighting needs to be re-implemented - parking highlighting will be done differently - enforcing a specific drawing order for layers is currently not explicitly supported, so that taxiways may be rendered on top of runways Also: - integrated with the latest changes in git/master (HEAD) -i.e. metar support - further generalized map.nas - partially moved instantiation from Nasal space to XML space (WIP) - create "toggle layer" checkboxes procedurally in Nasal space - prepared the code to be better reusable in other dialogs (e.g. route manager, map dialog etc) - completely removed the "highlighting" (runway/parking) feature for now, because we talked about re-implementing it anyhow
872 lines
24 KiB
Text
872 lines
24 KiB
Text
# Helper function to create a node with the first available index for the given
|
|
# path relative to the given node
|
|
#
|
|
var _createNodeWithIndex = func(node, path, min_index = 0)
|
|
{
|
|
var node = aircraft.makeNode(node);
|
|
|
|
# TODO do we need an upper limit? (50000 seems already seems unreachable)
|
|
for(var i = min_index; i < 50000; i += 1)
|
|
{
|
|
var p = path ~ "[" ~ i ~ "]";
|
|
if( node.getNode(p) == nil )
|
|
return node.getNode(p, 1);
|
|
}
|
|
|
|
debug.warn("Unable to get child (already 50000 exist)");
|
|
|
|
return nil;
|
|
};
|
|
|
|
# Internal helper
|
|
var _createColorNodes = func(parent, name)
|
|
{
|
|
var node = parent.getNode(name, 1);
|
|
return [ node.getNode("red", 1),
|
|
node.getNode("green", 1),
|
|
node.getNode("blue", 1),
|
|
node.getNode("alpha", 1) ];
|
|
};
|
|
|
|
var _setColorNodes = func(nodes, color)
|
|
{
|
|
if( typeof(nodes) != "vector" )
|
|
{
|
|
debug.warn("This element doesn't support setting color");
|
|
return;
|
|
}
|
|
|
|
if( size(color) == 1 )
|
|
color = color[0];
|
|
|
|
if( typeof(color) != "vector" )
|
|
return debug.warn("Wrong type for color");
|
|
|
|
if( size(color) < 3 or size(color) > 4 )
|
|
return debug.warn("Color needs 3 or 4 values (RGB or RGBA)");
|
|
|
|
for(var i = 0; i < size(color); i += 1)
|
|
nodes[i].setDoubleValue( color[i] );
|
|
|
|
if( size(color) == 3 )
|
|
# default alpha is 1
|
|
nodes[3].setDoubleValue(1);
|
|
};
|
|
|
|
var _getColor = func(color)
|
|
{
|
|
if( size(color) == 1 )
|
|
var color = color[0];
|
|
|
|
if( typeof(color) == 'scalar' )
|
|
return color;
|
|
if( typeof(color) != "vector" )
|
|
return debug.warn("Wrong type for color");
|
|
|
|
if( size(color) < 3 or size(color) > 4 )
|
|
return debug.warn("Color needs 3 or 4 values (RGB or RGBA)");
|
|
|
|
var str = 'rgb';
|
|
if( size(color) == 4 )
|
|
str ~= 'a';
|
|
str ~= '(';
|
|
|
|
# rgb = [0,255], a = [0,1]
|
|
for(var i = 0; i < size(color); i += 1)
|
|
str ~= (i > 0 ? ',' : '') ~ (i < 3 ? int(color[i] * 255) : color[i]);
|
|
|
|
return str ~ ')';
|
|
};
|
|
|
|
var _arg2valarray = func
|
|
{
|
|
var ret = arg;
|
|
while ( typeof(ret) == "vector"
|
|
and size(ret) == 1 and typeof(ret[0]) == "vector" )
|
|
ret = ret[0];
|
|
return ret;
|
|
}
|
|
|
|
# Transform
|
|
# ==============================================================================
|
|
# A transformation matrix which is used to transform an #Element on the canvas.
|
|
# The dimensions of the matrix are 3x3 where the last row is always 0 0 1:
|
|
#
|
|
# a c e
|
|
# b d f
|
|
# 0 0 1
|
|
#
|
|
# See http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined for details.
|
|
#
|
|
var Transform = {
|
|
new: func(node, vals = nil)
|
|
{
|
|
var m = {
|
|
parents: [Transform],
|
|
_node: node,
|
|
a: node.getNode("m[0]", 1),
|
|
b: node.getNode("m[1]", 1),
|
|
c: node.getNode("m[2]", 1),
|
|
d: node.getNode("m[3]", 1),
|
|
e: node.getNode("m[4]", 1),
|
|
f: node.getNode("m[5]", 1)
|
|
};
|
|
|
|
var use_vals = typeof(vals) == 'vector' and size(vals) == 6;
|
|
|
|
# initialize to identity matrix
|
|
m.a.setDoubleValue(use_vals ? vals[0] : 1);
|
|
m.b.setDoubleValue(use_vals ? vals[1] : 0);
|
|
m.c.setDoubleValue(use_vals ? vals[2] : 0);
|
|
m.d.setDoubleValue(use_vals ? vals[3] : 1);
|
|
m.e.setDoubleValue(use_vals ? vals[4] : 0);
|
|
m.f.setDoubleValue(use_vals ? vals[5] : 0);
|
|
|
|
return m;
|
|
},
|
|
setTranslation: func
|
|
{
|
|
var trans = _arg2valarray(arg);
|
|
|
|
me.e.setDoubleValue(trans[0]);
|
|
me.f.setDoubleValue(trans[1]);
|
|
|
|
return me;
|
|
},
|
|
# Set rotation (Optionally around a specified point instead of (0,0))
|
|
#
|
|
# setRotation(rot)
|
|
# setRotation(rot, cx, cy)
|
|
#
|
|
# @note If using with rotation center different to (0,0) don't use
|
|
# #setTranslation as it would interfere with the rotation.
|
|
setRotation: func(angle)
|
|
{
|
|
var center = _arg2valarray(arg);
|
|
|
|
var s = math.sin(angle);
|
|
var c = math.cos(angle);
|
|
|
|
me.a.setDoubleValue(c);
|
|
me.b.setDoubleValue(s);
|
|
me.c.setDoubleValue(-s);
|
|
me.d.setDoubleValue(c);
|
|
|
|
if( size(center) == 2 )
|
|
{
|
|
me.e.setDoubleValue( (-center[0] * c) + (center[1] * s) + center[0] );
|
|
me.f.setDoubleValue( (-center[0] * s) - (center[1] * c) + center[1] );
|
|
}
|
|
|
|
return me;
|
|
},
|
|
# Set scale (either as parameters or array)
|
|
#
|
|
# If only one parameter is given its value is used for both x and y
|
|
# setScale(x, y)
|
|
# setScale([x, y])
|
|
setScale: func
|
|
{
|
|
var scale = _arg2valarray(arg);
|
|
|
|
me.a.setDoubleValue(scale[0]);
|
|
me.d.setDoubleValue(size(scale) >= 2 ? scale[1] : scale[0]);
|
|
|
|
return me;
|
|
},
|
|
getScale: func()
|
|
{
|
|
# TODO handle rotation
|
|
return [me.a.getValue(), me.d.getValue()];
|
|
}
|
|
};
|
|
|
|
# Element
|
|
# ==============================================================================
|
|
# Baseclass for all elements on a canvas
|
|
#
|
|
var Element = {
|
|
# Constructor
|
|
#
|
|
# @param node Node to be used for element or vector [parent, type] for
|
|
# creation of a new node with name type and given parent
|
|
# @param id ID/Name (Should be unique)
|
|
new: func(node, id)
|
|
{
|
|
var m = { parents: [PropertyElement.new(node, id), Element] };
|
|
|
|
m._center = [
|
|
m._node.getNode("center[0]"),
|
|
m._node.getNode("center[1]")
|
|
];
|
|
|
|
return m;
|
|
},
|
|
# 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
|
|
setVisible: func(visible = 1)
|
|
{
|
|
me.setBool("visible", visible);
|
|
},
|
|
getVisible: func me.getBool("visible"),
|
|
# Hide element (Shortcut for setVisible(0))
|
|
hide: func me.setVisible(0),
|
|
# Show element (Shortcut for setVisible(1))
|
|
show: func me.setVisible(1),
|
|
# Toggle element visibility
|
|
toggleVisibility: func me.setVisible( !me.getVisible() ),
|
|
#
|
|
setGeoPosition: func(lat, lon)
|
|
{
|
|
me._getTf()._node.getNode("m-geo[4]", 1).setValue("N" ~ lat);
|
|
me._getTf()._node.getNode("m-geo[5]", 1).setValue("E" ~ lon);
|
|
return me;
|
|
},
|
|
# Create a new transformation matrix
|
|
#
|
|
# @param vals Default values (Vector of 6 elements)
|
|
createTransform: func(vals = nil)
|
|
{
|
|
var node = _createNodeWithIndex(me._node, "tf", 1); # tf[0] is reserved for
|
|
# setRotation
|
|
return Transform.new(node, vals);
|
|
},
|
|
# Shortcut for setting translation
|
|
setTranslation: func { me._getTf().setTranslation(arg); return me; },
|
|
# Set rotation around transformation center (see #setCenter).
|
|
#
|
|
# @note This replaces the the existing transformation. For additional scale or
|
|
# translation use additional transforms (see #createTransform).
|
|
setRotation: func(rot)
|
|
{
|
|
if( me['_tf_rot'] == nil )
|
|
# always use the first matrix slot to ensure correct rotation
|
|
# around transformation center.
|
|
me['_tf_rot'] = Transform.new(me._node.getNode("tf[0]", 1));
|
|
|
|
me._tf_rot.setRotation(rot, me.getCenter());
|
|
return me;
|
|
},
|
|
# Shortcut for setting scale
|
|
setScale: func { me._getTf().setScale(arg); return me; },
|
|
# Shortcut for getting scale
|
|
getScale: func me._getTf().getScale(),
|
|
# Set the fill/background/boundingbox color
|
|
#
|
|
# @param color Vector of 3 or 4 values in [0, 1]
|
|
setColorFill: func me.set('fill', _getColor(arg)),
|
|
#
|
|
getBoundingBox: func()
|
|
{
|
|
var bb = me._node.getNode("bounding-box");
|
|
if( bb != nil )
|
|
{
|
|
var min_x = bb.getNode("min-x").getValue();
|
|
|
|
if( min_x != nil )
|
|
return [ min_x,
|
|
bb.getNode("min-y").getValue(),
|
|
bb.getNode("max-x").getValue(),
|
|
bb.getNode("max-y").getValue() ];
|
|
}
|
|
|
|
return [0, 0, 0, 0];
|
|
},
|
|
# Set transformation center (currently only used for rotation)
|
|
setCenter: func()
|
|
{
|
|
var center = _arg2valarray(arg);
|
|
if( size(center) != 2 )
|
|
return debug.warn("invalid arg");
|
|
|
|
if( me._center[0] == nil )
|
|
me._center[0] = me._node.getNode("center[0]", 1);
|
|
if( me._center[1] == nil )
|
|
me._center[1] = me._node.getNode("center[1]", 1);
|
|
|
|
me._center[0].setDoubleValue(center[0] or 0);
|
|
me._center[1].setDoubleValue(center[1] or 0);
|
|
|
|
return me;
|
|
},
|
|
# Get transformation center
|
|
getCenter: func()
|
|
{
|
|
var bb = me.getBoundingBox();
|
|
var center = [0, 0];
|
|
|
|
if( me._center[0] != nil )
|
|
center[0] = me._center[0].getValue() or 0;
|
|
if( me._center[1] != nil )
|
|
center[1] = me._center[1].getValue() or 0;
|
|
|
|
if( bb[0] >= bb[2] or bb[1] >= bb[3] )
|
|
return center;
|
|
|
|
return [ 0.5 * (bb[0] + bb[2]) + center[0],
|
|
0.5 * (bb[1] + bb[3]) + center[1] ];
|
|
},
|
|
# Internal Transform for convenience transform functions
|
|
_getTf: func
|
|
{
|
|
if( me['_tf'] == nil )
|
|
me['_tf'] = me.createTransform();
|
|
return me._tf;
|
|
}
|
|
};
|
|
|
|
# Group
|
|
# ==============================================================================
|
|
# Class for a group element on a canvas
|
|
#
|
|
var Group = {
|
|
# public:
|
|
new: func(node, id)
|
|
{
|
|
return { parents: [Group, Element.new(node, id)] };
|
|
},
|
|
# Create a child of given type with specified id.
|
|
# type can be group, text
|
|
createChild: func(type, id = nil)
|
|
{
|
|
var factory = me._element_factories[type];
|
|
|
|
if( factory == nil )
|
|
{
|
|
debug.dump("canvas.Group.createChild(): unknown type (" ~ type ~ ")");
|
|
return nil;
|
|
}
|
|
|
|
return factory([me._node, type], id);
|
|
},
|
|
# Get a vector of all child elements
|
|
getChildren: func()
|
|
{
|
|
var children = [];
|
|
|
|
foreach(var c; me._node.getChildren())
|
|
if( me._isElementNode(c) )
|
|
append(children, me._wrapElement(c));
|
|
|
|
return children;
|
|
},
|
|
# Get first child with given id (breadth-first search)
|
|
#
|
|
# @note Use with care as it can take several miliseconds (for me eg. ~2ms).
|
|
getElementById: func(id)
|
|
{
|
|
# TODO can we improve the queue or better port this to C++ or use some kind
|
|
# of lookup hash? Searching is really slow now...
|
|
var stack = [me._node];
|
|
var index = 0;
|
|
|
|
while( index < size(stack) )
|
|
{
|
|
var node = stack[index];
|
|
index += 1;
|
|
|
|
if( node != me._node )
|
|
{
|
|
var node_id = node.getNode("id");
|
|
if( node_id != nil and node_id.getValue() == id )
|
|
return me._wrapElement(node);
|
|
}
|
|
|
|
foreach(var c; node.getChildren())
|
|
if( me._isElementNode(c) )
|
|
append(stack, c);
|
|
}
|
|
},
|
|
# Remove all children
|
|
removeAllChildren: func()
|
|
{
|
|
foreach(var type; keys(me._element_factories))
|
|
me._node.removeChildren(type, 0);
|
|
return me;
|
|
},
|
|
# private:
|
|
_isElementNode: func(el)
|
|
{
|
|
# element nodes have type NONE and valid element names (those in the factory
|
|
# list)
|
|
return el.getType() == "NONE"
|
|
and me._element_factories[ el.getName() ] != nil;
|
|
},
|
|
_wrapElement: func(node)
|
|
{
|
|
# Create element from existing node
|
|
return me._element_factories[ node.getName() ](node, nil);
|
|
}
|
|
};
|
|
|
|
# Map
|
|
# ==============================================================================
|
|
# Class for a group element on a canvas with possibly geopgraphic positions
|
|
# which automatically get projected according to the specified projection.
|
|
#
|
|
var Map = {
|
|
new: func(node, id)
|
|
{
|
|
return { parents: [Map, Group.new(node, id)] };
|
|
}
|
|
# TODO
|
|
};
|
|
|
|
# Text
|
|
# ==============================================================================
|
|
# Class for a text element on a canvas
|
|
#
|
|
var Text = {
|
|
new: func(node, id)
|
|
{
|
|
return { parents: [Text, Element.new(node, id)] };
|
|
},
|
|
# Set the text
|
|
setText: func(text)
|
|
{
|
|
# add space because osg seems to remove last character if its a space
|
|
me.set("text", typeof(text) == 'scalar' ? text ~ ' ' : "");
|
|
},
|
|
# Set alignment
|
|
#
|
|
# @param algin String, one of:
|
|
# left-top
|
|
# left-center
|
|
# left-bottom
|
|
# center-top
|
|
# center-center
|
|
# center-bottom
|
|
# right-top
|
|
# right-center
|
|
# right-bottom
|
|
# left-baseline
|
|
# center-baseline
|
|
# right-baseline
|
|
# left-bottom-baseline
|
|
# center-bottom-baseline
|
|
# right-bottom-baseline
|
|
#
|
|
setAlignment: func(align)
|
|
{
|
|
me.set("alignment", align);
|
|
},
|
|
# Set the font size
|
|
setFontSize: func(size, aspect = 1)
|
|
{
|
|
me.setDouble("character-size", size);
|
|
me.setDouble("character-aspect-ratio", aspect);
|
|
},
|
|
# Set font (by name of font file)
|
|
setFont: func(name)
|
|
{
|
|
me.set("font", name);
|
|
},
|
|
# Enumeration of values for drawing mode:
|
|
TEXT: 1, # The text itself
|
|
BOUNDINGBOX: 2, # A bounding box (only lines)
|
|
FILLEDBOUNDINGBOX: 4, # A filled bounding box
|
|
ALIGNMENT: 8, # Draw a marker (cross) at the position of the text
|
|
# Set draw mode. Binary combination of the values above. Since I haven't found
|
|
# a bitwise or we have to use a + instead.
|
|
#
|
|
# eg. my_text.setDrawMode(Text.TEXT + Text.BOUNDINGBOX);
|
|
setDrawMode: func(mode)
|
|
{
|
|
me.setInt("draw-mode", mode);
|
|
},
|
|
# Set bounding box padding
|
|
setPadding: func(pad)
|
|
{
|
|
me.setDouble("padding", pad);
|
|
},
|
|
setMaxWidth: func(w)
|
|
{
|
|
me.setDouble("max-width", w);
|
|
},
|
|
setColor: func me.set('fill', _getColor(arg)),
|
|
setColorFill: func me.set('background', _getColor(arg))
|
|
};
|
|
|
|
# Path
|
|
# ==============================================================================
|
|
# Class for an (OpenVG) path element on a canvas
|
|
#
|
|
var Path = {
|
|
# Path segment commands (VGPathCommand)
|
|
VG_CLOSE_PATH: 0,
|
|
VG_MOVE_TO: 2,
|
|
VG_MOVE_TO_ABS: 2,
|
|
VG_MOVE_TO_REL: 3,
|
|
VG_LINE_TO: 4,
|
|
VG_LINE_TO_ABS: 4,
|
|
VG_LINE_TO_REL: 5,
|
|
VG_HLINE_TO: 6,
|
|
VG_HLINE_TO_ABS: 6,
|
|
VG_HLINE_TO_REL: 7,
|
|
VG_VLINE_TO: 8,
|
|
VG_VLINE_TO_ABS: 8,
|
|
VG_VLINE_TO_REL: 9,
|
|
VG_QUAD_TO: 10,
|
|
VG_QUAD_TO_ABS: 10,
|
|
VG_QUAD_TO_REL: 11,
|
|
VG_CUBIC_TO: 12,
|
|
VG_CUBIC_TO_ABS: 12,
|
|
VG_CUBIC_TO_REL: 13,
|
|
VG_SQUAD_TO: 14,
|
|
VG_SQUAD_TO_ABS: 14,
|
|
VG_SQUAD_TO_REL: 15,
|
|
VG_SCUBIC_TO: 16,
|
|
VG_SCUBIC_TO_ABS: 16,
|
|
VG_SCUBIC_TO_REL: 17,
|
|
VG_SCCWARC_TO: 20, # Note that CC and CCW commands are swapped. This is
|
|
VG_SCCWARC_TO_ABS:20, # needed due to the different coordinate systems used.
|
|
VG_SCCWARC_TO_REL:21, # In OpenVG values along the y-axis increase from bottom
|
|
VG_SCWARC_TO: 18, # to top, whereas in the Canvas system it is flipped.
|
|
VG_SCWARC_TO_ABS: 18,
|
|
VG_SCWARC_TO_REL: 19,
|
|
VG_LCCWARC_TO: 24,
|
|
VG_LCCWARC_TO_ABS:24,
|
|
VG_LCCWARC_TO_REL:25,
|
|
VG_LCWARC_TO: 22,
|
|
VG_LCWARC_TO_ABS: 22,
|
|
VG_LCWARC_TO_REL: 23,
|
|
|
|
# Number of coordinates per command
|
|
num_coords: [
|
|
0, 0, # VG_CLOSE_PATH
|
|
2, 2, # VG_MOVE_TO
|
|
2, 2, # VG_LINE_TO
|
|
1, 1, # VG_HLINE_TO
|
|
1, 1, # VG_VLINE_TO
|
|
4, 4, # VG_QUAD_TO
|
|
6, 6, # VG_CUBIC_TO
|
|
2, 2, # VG_SQUAD_TO
|
|
4, 4, # VG_SCUBIC_TO
|
|
5, 5, # VG_SCCWARC_TO
|
|
5, 5, # VG_SCWARC_TO
|
|
5, 5, # VG_LCCWARC_TO
|
|
5, 5 # VG_LCWARC_TO
|
|
],
|
|
|
|
#
|
|
new: func(node, id)
|
|
{
|
|
return {
|
|
parents: [Path, Element.new(node, id)],
|
|
_num_cmds: 0,
|
|
_num_coords: 0
|
|
};
|
|
},
|
|
# Remove all existing path data
|
|
reset: func
|
|
{
|
|
me._node.removeChildren('cmd', 0);
|
|
me._node.removeChildren('coord', 0);
|
|
me._node.removeChildren('coord-geo', 0);
|
|
me._num_cmds = 0;
|
|
me._num_coords = 0;
|
|
return me;
|
|
},
|
|
# Set the path data (commands and coordinates)
|
|
setData: func(cmds, coords)
|
|
{
|
|
me.reset();
|
|
me._node.setValues({cmd: cmds, coord: coords});
|
|
me._num_cmds = size(cmds);
|
|
me._num_coords = size(coords);
|
|
return me;
|
|
},
|
|
setDataGeo: func(cmds, coords)
|
|
{
|
|
me.reset();
|
|
me._node.setValues({cmd: cmds, 'coord-geo': coords});
|
|
me._num_cmds = size(cmds);
|
|
me._num_coords = size(coords);
|
|
return me;
|
|
},
|
|
# Add a path segment
|
|
addSegment: 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._num_cmds += 1) ~ "]", cmd);
|
|
for(var i = 0; i < num_coords; i += 1)
|
|
me.setDouble("coord[" ~ (me._num_coords += 1) ~ "]", coords[i]);
|
|
}
|
|
|
|
return me;
|
|
},
|
|
# Move path cursor
|
|
moveTo: func me.addSegment(me.VG_MOVE_TO_ABS, arg),
|
|
move: func me.addSegment(me.VG_MOVE_TO_REL, arg),
|
|
# Add a line
|
|
lineTo: func me.addSegment(me.VG_LINE_TO_ABS, arg),
|
|
line: func me.addSegment(me.VG_LINE_TO_REL, arg),
|
|
# Add a horizontal line
|
|
horizTo: func me.addSegment(me.VG_HLINE_TO_ABS, arg),
|
|
horiz: func me.addSegment(me.VG_HLINE_TO_REL, arg),
|
|
# Add a vertical line
|
|
vertTo: func me.addSegment(me.VG_VLINE_TO_ABS, arg),
|
|
vert: func me.addSegment(me.VG_VLINE_TO_REL, arg),
|
|
# Add a quadratic Bézier curve
|
|
quadTo: func me.addSegment(me.VG_QUAD_TO_ABS, arg),
|
|
quad: func me.addSegment(me.VG_QUAD_TO_REL, arg),
|
|
# Add a cubic Bézier curve
|
|
cubicTo: func me.addSegment(me.VG_CUBIC_TO_ABS, arg),
|
|
cubic: func me.addSegment(me.VG_CUBIC_TO_REL, arg),
|
|
# Add a smooth quadratic Bézier curve
|
|
quadTo: func me.addSegment(me.VG_SQUAD_TO_ABS, arg),
|
|
quad: func me.addSegment(me.VG_SQUAD_TO_REL, arg),
|
|
# Add a smooth cubic Bézier curve
|
|
scubicTo: func me.addSegment(me.VG_SCUBIC_TO_ABS, arg),
|
|
scubic: func me.addSegment(me.VG_SCUBIC_TO_REL, arg),
|
|
# Draw an elliptical arc (shorter counter-clockwise arc)
|
|
arcSmallCCWTo: func me.addSegment(me.VG_SCCWARC_TO_ABS, arg),
|
|
arcSmallCCW: func me.addSegment(me.VG_SCCWARC_TO_REL, arg),
|
|
# Draw an elliptical arc (shorter clockwise arc)
|
|
arcSmallCWTo: func me.addSegment(me.VG_SCWARC_TO_ABS, arg),
|
|
arcSmallCW: func me.addSegment(me.VG_SCWARC_TO_REL, arg),
|
|
# Draw an elliptical arc (longer counter-clockwise arc)
|
|
arcLargeCCWTo: func me.addSegment(me.VG_LCCWARC_TO_ABS, arg),
|
|
arcLargeCCW: func me.addSegment(me.VG_LCCWARC_TO_REL, arg),
|
|
# Draw an elliptical arc (shorter clockwise arc)
|
|
arcLargeCWTo: func me.addSegment(me.VG_LCWARC_TO_ABS, arg),
|
|
arcLargeCW: func me.addSegment(me.VG_LCWARC_TO_REL, arg),
|
|
# Close the path (implicit lineTo to first point of path)
|
|
close: func me.addSegment(me.VG_CLOSE_PATH),
|
|
|
|
setColor: func me.setStroke(_getColor(arg)),
|
|
setColorFill: func me.setFill(_getColor(arg)),
|
|
|
|
setFill: func(fill)
|
|
{
|
|
me.set('fill', fill);
|
|
},
|
|
setStroke: func(stroke)
|
|
{
|
|
me.set('stroke', stroke);
|
|
},
|
|
setStrokeLineWidth: func(width)
|
|
{
|
|
me.setDouble('stroke-width', width);
|
|
},
|
|
# Set stroke linecap
|
|
#
|
|
# @param linecap String, "butt", "round" or "square"
|
|
#
|
|
# See http://www.w3.org/TR/SVG/painting.html#StrokeLinecapProperty for details
|
|
setStrokeLineCap: func(linecap)
|
|
{
|
|
me.set('stroke-linecap', linecap);
|
|
},
|
|
# Set stroke dasharray
|
|
#
|
|
# @param pattern Vector, Vector of alternating dash and gap lengths
|
|
# [on1, off1, on2, ...]
|
|
setStrokeDashArray: func(pattern)
|
|
{
|
|
if( typeof(pattern) == 'vector' )
|
|
me.set('stroke-dasharray', string.join(',', pattern));
|
|
else
|
|
debug.warn("setStrokeDashArray: vector expected!");
|
|
|
|
return me;
|
|
}
|
|
};
|
|
|
|
# Image
|
|
# ==============================================================================
|
|
# Class for an image element on a canvas
|
|
#
|
|
var Image = {
|
|
new: func(node, id)
|
|
{
|
|
var m = {
|
|
parents: [Image, Element.new(node, id)]
|
|
};
|
|
m.color_fill = _createColorNodes(m._node, "color-fill");
|
|
return m;
|
|
},
|
|
# Set image file to be used
|
|
#
|
|
# @param file Path to file or canvas (Use canvas://... for canvas, eg.
|
|
# canvas://by-index/texture[0])
|
|
setFile: func(file)
|
|
{
|
|
me.set("file", file);
|
|
},
|
|
# Set rectangular region of source image to be used
|
|
#
|
|
# @param left Rectangle minimum x coordinate
|
|
# @param top Rectangle minimum y coordinate
|
|
# @param right Rectangle maximum x coordinate
|
|
# @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)
|
|
{
|
|
me._node.getNode("source", 1).setValues({
|
|
left: left,
|
|
top: top,
|
|
right: right,
|
|
bottom: bottom,
|
|
normalized: normalized
|
|
});
|
|
return me;
|
|
},
|
|
# Set size of image element
|
|
setSize: func(width, height)
|
|
{
|
|
me._node.setValues({size: [width, height]});
|
|
return me;
|
|
}
|
|
};
|
|
|
|
# Element factories used by #Group elements to create children
|
|
Group._element_factories = {
|
|
"group": Group.new,
|
|
"map": Map.new,
|
|
"text": Text.new,
|
|
"path": Path.new,
|
|
"image": Image.new
|
|
};
|
|
|
|
# Canvas
|
|
# ==============================================================================
|
|
# Class for a canvas
|
|
#
|
|
var Canvas = {
|
|
# Place this canvas somewhere onto the object. Pass criterions for placement
|
|
# as a hash, eg:
|
|
#
|
|
# my_canvas.addPlacement({
|
|
# "texture": "EICAS.png",
|
|
# "node": "PFD-Screen",
|
|
# "parent": "Some parent name"
|
|
# });
|
|
#
|
|
# Note that we can choose whichever of the three filter criterions we use for
|
|
# matching the target object for our placement. If none of the three fields is
|
|
# given every texture of the model will be replaced.
|
|
addPlacement: func(vals)
|
|
{
|
|
var placement = _createNodeWithIndex(me.texture, "placement");
|
|
placement.setValues(vals);
|
|
return placement;
|
|
},
|
|
# Create a new group with the given name
|
|
#
|
|
# @param id Optional id/name for the group
|
|
createGroup: func(id = nil)
|
|
{
|
|
return Group.new([me.texture, "group"], id);
|
|
},
|
|
# Set the background color
|
|
#
|
|
# @param color Vector of 3 or 4 values in [0, 1]
|
|
setColorBackground: func { _setColorNodes(me.color, arg); return me; },
|
|
# Get path of canvas to be used eg. in Image::setFile
|
|
getPath: func()
|
|
{
|
|
return "canvas://by-index/texture[" ~ me._node.getIndex() ~ "]";
|
|
}
|
|
};
|
|
|
|
# Create a new canvas. Pass parameters as hash, eg:
|
|
#
|
|
# var my_canvas = canvas.new({
|
|
# "name": "PFD-Test",
|
|
# "size": [512, 512],
|
|
# "view": [768, 1024],
|
|
# "mipmapping": 1
|
|
# });
|
|
var new = func(vals)
|
|
{
|
|
var m = { parents: [Canvas] };
|
|
|
|
m.texture = _createNodeWithIndex(Canvas.property_root, "texture");
|
|
m.color = _createColorNodes(m.texture, "color-background");
|
|
m.texture.setValues(vals);
|
|
|
|
return m;
|
|
};
|
|
|
|
# Get the first existing canvas with the given name
|
|
#
|
|
# @param name Name of the canvas
|
|
# @return #Canvas, if canvas with #name exists
|
|
# nil, otherwise
|
|
var get = func(name)
|
|
{
|
|
var node_canvas = nil;
|
|
if( isa(name, props.Node) )
|
|
node_canvas = name;
|
|
else if( typeof(name) == 'scalar' )
|
|
{
|
|
foreach(var c; Canvas.property_root.getChildren("texture"))
|
|
{
|
|
if( c.getValue("name") == name )
|
|
node_canvas = c;
|
|
}
|
|
}
|
|
|
|
if( node_canvas == nil )
|
|
{
|
|
debug.warn("Canvas not found: " ~ name);
|
|
return nil;
|
|
}
|
|
|
|
return {
|
|
parents: [Canvas],
|
|
texture: node_canvas,
|
|
color: _createColorNodes(node_canvas, "color-background")
|
|
};
|
|
};
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Show warnings if API used with too old version of FlightGear without Canvas
|
|
# support (Wrapped in anonymous function do not polute the canvas namespace)
|
|
|
|
(func {
|
|
var legacy_dir = getprop("/sim/fg-root") ~ "/Nasal/canvas";
|
|
var version_str = getprop("/sim/version/flightgear");
|
|
if( string.scanf(version_str, "%u.%u.%u", var fg_version = []) < 1 )
|
|
debug.warn("Canvas: Error parsing flightgear version (" ~ version_str ~ ")");
|
|
else
|
|
{
|
|
if( fg_version[0] < 2
|
|
or (fg_version[0] == 2 and fg_version[1] < 8) )
|
|
{
|
|
debug.warn("Canvas: FlightGear version too old (" ~ version_str ~ ")");
|
|
gui.popupTip
|
|
(
|
|
"FlightGear v2.8.0 or newer needed for Canvas support!",
|
|
600,
|
|
{button: {legend: "Ok", binding: {command: "dialog-close"}}}
|
|
);
|
|
}
|
|
|
|
# Load support for older versions of FlightGear (TODO generalize :) )
|
|
if( fg_version[0] == 2 and fg_version[1] == 8 )
|
|
io.load_nasal(legacy_dir ~ "/api.nas.2.8", "canvas");
|
|
}
|
|
|
|
Canvas.property_root = props.globals.getNode("canvas/by-index", 1);
|
|
})();
|