2012-08-01 22:31:54 +00:00
|
|
|
# Internal helper
|
2012-08-23 19:05:52 +00:00
|
|
|
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 ~ ')';
|
|
|
|
};
|
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
var _arg2valarray = func
|
|
|
|
{
|
2012-08-01 23:28:56 +00:00
|
|
|
var ret = arg;
|
|
|
|
while ( typeof(ret) == "vector"
|
|
|
|
and size(ret) == 1 and typeof(ret[0]) == "vector" )
|
|
|
|
ret = ret[0];
|
|
|
|
return ret;
|
2012-08-01 22:31:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# 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],
|
2012-07-11 22:21:30 +00:00
|
|
|
_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)
|
2012-08-01 22:31:54 +00:00
|
|
|
};
|
2012-11-04 13:25:36 +00:00
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
var use_vals = typeof(vals) == 'vector' and size(vals) == 6;
|
2012-11-04 13:25:36 +00:00
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
# 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);
|
2012-11-04 13:25:36 +00:00
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
return m;
|
|
|
|
},
|
|
|
|
setTranslation: func
|
|
|
|
{
|
|
|
|
var trans = _arg2valarray(arg);
|
|
|
|
|
|
|
|
me.e.setDoubleValue(trans[0]);
|
|
|
|
me.f.setDoubleValue(trans[1]);
|
2012-11-04 13:25:36 +00:00
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
return me;
|
|
|
|
},
|
2012-08-01 23:28:56 +00:00
|
|
|
# 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.
|
2012-08-01 22:31:54 +00:00
|
|
|
setRotation: func(angle)
|
|
|
|
{
|
2012-08-01 23:28:56 +00:00
|
|
|
var center = _arg2valarray(arg);
|
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
var s = math.sin(angle);
|
|
|
|
var c = math.cos(angle);
|
|
|
|
|
2012-08-01 23:28:56 +00:00
|
|
|
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] );
|
|
|
|
}
|
2012-11-04 13:25:36 +00:00
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
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]);
|
2012-11-04 13:25:36 +00:00
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
return me;
|
|
|
|
},
|
|
|
|
getScale: func()
|
|
|
|
{
|
|
|
|
# TODO handle rotation
|
|
|
|
return [me.a.getValue(), me.d.getValue()];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
# Element
|
|
|
|
# ==============================================================================
|
|
|
|
# Baseclass for all elements on a canvas
|
|
|
|
#
|
|
|
|
var Element = {
|
2013-11-03 20:01:33 +00:00
|
|
|
# Reference frames (for "clip" coordinates)
|
|
|
|
GLOBAL: 0,
|
|
|
|
PARENT: 1,
|
|
|
|
LOCAL: 2,
|
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
# Constructor
|
|
|
|
#
|
2012-11-30 16:39:39 +00:00
|
|
|
# @param ghost Element ghost as retrieved from core methods
|
|
|
|
new: func(ghost)
|
2012-08-01 22:31:54 +00:00
|
|
|
{
|
2013-01-09 11:14:31 +00:00
|
|
|
return {
|
2012-11-30 16:39:39 +00:00
|
|
|
parents: [PropertyElement, Element, ghost],
|
|
|
|
_node: props.wrapNode(ghost._node_ghost)
|
|
|
|
};
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
2013-10-23 15:33:12 +00:00
|
|
|
# Get parent group/element
|
|
|
|
getParent: func()
|
|
|
|
{
|
|
|
|
var parent_ghost = me._getParent();
|
|
|
|
if( parent_ghost == nil )
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
var type = props.wrapNode(parent_ghost._node_ghost).getName();
|
|
|
|
var factory = me._getFactory(type);
|
|
|
|
if( factory == nil )
|
|
|
|
return parent_ghost;
|
|
|
|
|
|
|
|
return factory(parent_ghost);
|
|
|
|
},
|
2014-06-01 10:25:34 +00:00
|
|
|
# Get the canvas this element is placed on
|
|
|
|
getCanvas: func()
|
|
|
|
{
|
|
|
|
wrapCanvas(me._getCanvas());
|
|
|
|
},
|
2013-06-29 12:43:06 +00:00
|
|
|
# Check if elements represent same instance
|
|
|
|
#
|
|
|
|
# @param el Other Element or element ghost
|
|
|
|
equals: func(el)
|
|
|
|
{
|
|
|
|
return me._node.equals(el._node_ghost);
|
|
|
|
},
|
2012-08-01 23:28:56 +00:00
|
|
|
# Hide/Show element
|
|
|
|
#
|
|
|
|
# @param visible Whether the element should be visible
|
|
|
|
setVisible: func(visible = 1)
|
|
|
|
{
|
|
|
|
me.setBool("visible", visible);
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
Canvas Scripting Layer (Mapping):
- 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
2012-09-20 23:49:17 +00:00
|
|
|
getVisible: func me.getBool("visible"),
|
2012-08-01 23:28:56 +00:00
|
|
|
# Hide element (Shortcut for setVisible(0))
|
|
|
|
hide: func me.setVisible(0),
|
|
|
|
# Show element (Shortcut for setVisible(1))
|
|
|
|
show: func me.setVisible(1),
|
Canvas Scripting Layer (Mapping):
- 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
2012-09-20 23:49:17 +00:00
|
|
|
# Toggle element visibility
|
|
|
|
toggleVisibility: func me.setVisible( !me.getVisible() ),
|
2012-07-11 22:21:30 +00:00
|
|
|
#
|
|
|
|
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);
|
2012-08-01 23:28:56 +00:00
|
|
|
return me;
|
2012-07-11 22:21:30 +00:00
|
|
|
},
|
2012-08-01 22:31:54 +00:00
|
|
|
# Create a new transformation matrix
|
|
|
|
#
|
|
|
|
# @param vals Default values (Vector of 6 elements)
|
|
|
|
createTransform: func(vals = nil)
|
|
|
|
{
|
2012-10-13 13:20:27 +00:00
|
|
|
var node = me._node.addChild("tf", 1); # tf[0] is reserved for
|
|
|
|
# setRotation
|
2012-08-01 22:31:54 +00:00
|
|
|
return Transform.new(node, vals);
|
|
|
|
},
|
|
|
|
# Shortcut for setting translation
|
2012-08-01 23:28:56 +00:00
|
|
|
setTranslation: func { me._getTf().setTranslation(arg); return me; },
|
2014-03-19 21:32:36 +00:00
|
|
|
# Get translation set with #setTranslation
|
|
|
|
getTranslation: func()
|
|
|
|
{
|
|
|
|
if( me['_tf'] == nil )
|
|
|
|
return [0, 0];
|
|
|
|
|
|
|
|
return [me._tf.e.getValue(), me._tf.f.getValue()];
|
|
|
|
},
|
2012-08-01 23:28:56 +00:00
|
|
|
# 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.
|
2014-02-07 23:38:30 +00:00
|
|
|
# tf-rot-index can be set to change the slot to be used. This is used for
|
|
|
|
# example by the SVG parser to apply the rotation after all
|
|
|
|
# transformations defined in the SVG file.
|
|
|
|
me['_tf_rot'] = Transform.new(
|
|
|
|
me._node.getNode("tf[" ~ me.get("tf-rot-index", 0) ~ "]", 1)
|
|
|
|
);
|
2012-08-01 23:28:56 +00:00
|
|
|
|
|
|
|
me._tf_rot.setRotation(rot, me.getCenter());
|
|
|
|
return me;
|
|
|
|
},
|
2012-08-01 22:31:54 +00:00
|
|
|
# Shortcut for setting scale
|
2012-08-01 23:28:56 +00:00
|
|
|
setScale: func { me._getTf().setScale(arg); return me; },
|
2012-08-01 22:31:54 +00:00
|
|
|
# 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]
|
2012-08-23 19:05:52 +00:00
|
|
|
setColorFill: func me.set('fill', _getColor(arg)),
|
2014-04-29 02:26:32 +00:00
|
|
|
getColorFill: func me.get('fill'),
|
2012-08-01 23:28:56 +00:00
|
|
|
#
|
2014-03-31 11:34:50 +00:00
|
|
|
getTransformedBounds: func me.getTightBoundingBox(),
|
2013-01-09 11:14:31 +00:00
|
|
|
# Calculate the transformation center based on bounding box and center-offset
|
|
|
|
updateCenter: func
|
|
|
|
{
|
|
|
|
me.update();
|
2014-03-31 11:34:50 +00:00
|
|
|
var bb = me.getTightBoundingBox();
|
2013-01-09 11:14:31 +00:00
|
|
|
|
|
|
|
if( bb[0] > bb[2] or bb[1] > bb[3] )
|
|
|
|
return;
|
|
|
|
|
|
|
|
me._setupCenterNodes
|
|
|
|
(
|
|
|
|
(bb[0] + bb[2]) / 2 + (me.get("center-offset-x") or 0),
|
|
|
|
(bb[1] + bb[3]) / 2 + (me.get("center-offset-y") or 0)
|
|
|
|
);
|
|
|
|
return me;
|
|
|
|
},
|
2012-08-01 23:28:56 +00:00
|
|
|
# Set transformation center (currently only used for rotation)
|
|
|
|
setCenter: func()
|
|
|
|
{
|
|
|
|
var center = _arg2valarray(arg);
|
|
|
|
if( size(center) != 2 )
|
|
|
|
return debug.warn("invalid arg");
|
2012-11-04 13:25:36 +00:00
|
|
|
|
2013-01-09 11:14:31 +00:00
|
|
|
me._setupCenterNodes(center[0], center[1]);
|
2012-08-01 23:28:56 +00:00
|
|
|
return me;
|
|
|
|
},
|
|
|
|
# Get transformation center
|
|
|
|
getCenter: func()
|
|
|
|
{
|
|
|
|
var center = [0, 0];
|
2012-11-30 16:39:39 +00:00
|
|
|
me._setupCenterNodes();
|
|
|
|
|
2012-08-01 23:28:56 +00:00
|
|
|
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;
|
|
|
|
|
2013-01-09 11:14:31 +00:00
|
|
|
return center;
|
2012-08-01 23:28:56 +00:00
|
|
|
},
|
2012-08-01 22:31:54 +00:00
|
|
|
# Internal Transform for convenience transform functions
|
|
|
|
_getTf: func
|
|
|
|
{
|
|
|
|
if( me['_tf'] == nil )
|
|
|
|
me['_tf'] = me.createTransform();
|
|
|
|
return me._tf;
|
2012-11-30 16:39:39 +00:00
|
|
|
},
|
2013-01-09 11:14:31 +00:00
|
|
|
_setupCenterNodes: func(cx = nil, cy = nil)
|
2012-11-30 16:39:39 +00:00
|
|
|
{
|
|
|
|
if( me["_center"] == nil )
|
|
|
|
me["_center"] = [
|
2013-01-09 11:14:31 +00:00
|
|
|
me._node.getNode("center[0]", cx != nil),
|
|
|
|
me._node.getNode("center[1]", cy != nil)
|
2012-11-30 16:39:39 +00:00
|
|
|
];
|
2013-01-09 11:14:31 +00:00
|
|
|
|
|
|
|
if( cx != nil )
|
|
|
|
me._center[0].setDoubleValue(cx);
|
|
|
|
if( cy != nil )
|
|
|
|
me._center[1].setDoubleValue(cy);
|
2012-08-01 23:28:56 +00:00
|
|
|
}
|
2012-08-01 22:31:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
# Group
|
|
|
|
# ==============================================================================
|
|
|
|
# Class for a group element on a canvas
|
|
|
|
#
|
|
|
|
var Group = {
|
2012-09-17 16:12:57 +00:00
|
|
|
# public:
|
2012-11-30 16:39:39 +00:00
|
|
|
new: func(ghost)
|
2012-08-01 22:31:54 +00:00
|
|
|
{
|
2012-11-30 16:39:39 +00:00
|
|
|
return { parents: [Group, Element.new(ghost)] };
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
2012-08-01 23:28:56 +00:00
|
|
|
# Create a child of given type with specified id.
|
2012-08-01 22:31:54 +00:00
|
|
|
# type can be group, text
|
2012-08-01 23:28:56 +00:00
|
|
|
createChild: func(type, id = nil)
|
2012-08-01 22:31:54 +00:00
|
|
|
{
|
2012-11-30 16:39:39 +00:00
|
|
|
var ghost = me._createChild(type, id);
|
2012-10-14 15:32:19 +00:00
|
|
|
var factory = me._getFactory(type);
|
2012-08-01 22:31:54 +00:00
|
|
|
if( factory == nil )
|
2012-11-30 16:39:39 +00:00
|
|
|
return ghost;
|
2012-10-14 15:32:19 +00:00
|
|
|
|
2012-11-30 16:39:39 +00:00
|
|
|
return factory(ghost);
|
2012-08-01 23:28:56 +00:00
|
|
|
},
|
2012-10-14 15:32:19 +00:00
|
|
|
# Create multiple children of given type
|
|
|
|
createChildren: func(type, count)
|
|
|
|
{
|
|
|
|
var factory = me._getFactory(type);
|
|
|
|
if( factory == nil )
|
|
|
|
return [];
|
|
|
|
|
2012-11-30 16:39:39 +00:00
|
|
|
var nodes = props._addChildren(me._node._g, [type, count, 0, 0]);
|
2012-10-14 15:32:19 +00:00
|
|
|
for(var i = 0; i < count; i += 1)
|
2012-11-30 16:39:39 +00:00
|
|
|
nodes[i] = factory( me._getChild(nodes[i]) );
|
2012-10-14 15:32:19 +00:00
|
|
|
|
|
|
|
return nodes;
|
|
|
|
},
|
2013-01-22 16:47:51 +00:00
|
|
|
# Create a path child drawing a (rounded) rectangle
|
|
|
|
#
|
|
|
|
# @param x Position of left border
|
|
|
|
# @param y Position of top border
|
|
|
|
# @param w Width
|
|
|
|
# @param h Height
|
|
|
|
# @param cfg Optional settings (eg. {"border-top-radius": 5})
|
|
|
|
rect: func(x, y, w, h, cfg = nil)
|
|
|
|
{
|
2013-02-23 18:19:32 +00:00
|
|
|
return me.createChild("path").rect(x, y, w, h, cfg);
|
2013-01-22 16:47:51 +00:00
|
|
|
},
|
2012-09-17 16:12:57 +00:00
|
|
|
# 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;
|
|
|
|
},
|
2012-08-01 23:28:56 +00:00
|
|
|
# Get first child with given id (breadth-first search)
|
|
|
|
#
|
|
|
|
# @note Use with care as it can take several miliseconds (for me eg. ~2ms).
|
2012-11-30 16:39:39 +00:00
|
|
|
# TODO check with new C++ implementation
|
2012-08-01 23:28:56 +00:00
|
|
|
getElementById: func(id)
|
|
|
|
{
|
2012-11-30 16:39:39 +00:00
|
|
|
var ghost = me._getElementById(id);
|
|
|
|
if( ghost == nil )
|
|
|
|
return nil;
|
2012-11-04 13:25:36 +00:00
|
|
|
|
2012-11-30 16:39:39 +00:00
|
|
|
var node = props.wrapNode(ghost._node_ghost);
|
|
|
|
var factory = me._getFactory( node.getName() );
|
|
|
|
if( factory == nil )
|
|
|
|
return ghost;
|
|
|
|
|
|
|
|
return factory(ghost);
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
|
|
|
# Remove all children
|
|
|
|
removeAllChildren: func()
|
|
|
|
{
|
|
|
|
foreach(var type; keys(me._element_factories))
|
|
|
|
me._node.removeChildren(type, 0);
|
2012-08-01 23:28:56 +00:00
|
|
|
return me;
|
2012-09-17 16:12:57 +00:00
|
|
|
},
|
|
|
|
# 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
|
2012-11-30 16:39:39 +00:00
|
|
|
return me._element_factories[ node.getName() ]( me._getChild(node._g) );
|
2012-10-14 15:32:19 +00:00
|
|
|
},
|
|
|
|
_getFactory: func(type)
|
|
|
|
{
|
|
|
|
var factory = me._element_factories[type];
|
|
|
|
|
|
|
|
if( factory == nil )
|
|
|
|
debug.dump("canvas.Group.createChild(): unknown type (" ~ type ~ ")");
|
|
|
|
|
|
|
|
return factory;
|
2012-08-01 22:31:54 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-08-01 23:28:56 +00:00
|
|
|
# Map
|
2012-07-11 22:21:30 +00:00
|
|
|
# ==============================================================================
|
2012-08-01 23:28:56 +00:00
|
|
|
# Class for a group element on a canvas with possibly geopgraphic positions
|
|
|
|
# which automatically get projected according to the specified projection.
|
2014-05-25 18:11:13 +00:00
|
|
|
# Each map consists of an arbitrary number of layers (canvas groups)
|
2012-07-11 22:21:30 +00:00
|
|
|
#
|
|
|
|
var Map = {
|
2013-12-01 12:29:22 +00:00
|
|
|
df_controller: nil,
|
2012-11-30 16:39:39 +00:00
|
|
|
new: func(ghost)
|
2012-07-11 22:21:30 +00:00
|
|
|
{
|
2014-04-29 02:26:32 +00:00
|
|
|
return { parents: [Map, Group.new(ghost)], layers:{}, controller:nil }.setController();
|
2013-12-01 12:29:22 +00:00
|
|
|
},
|
|
|
|
del: func()
|
|
|
|
{
|
|
|
|
#print("canvas.Map.del()");
|
2014-01-24 03:13:54 +00:00
|
|
|
if (me.controller != nil)
|
2013-12-01 12:29:22 +00:00
|
|
|
me.controller.del(me);
|
2014-01-24 03:13:54 +00:00
|
|
|
foreach (var k; keys(me.layers)) {
|
|
|
|
me.layers[k].del();
|
|
|
|
delete(me.layers, k);
|
2013-12-01 12:29:22 +00:00
|
|
|
}
|
|
|
|
# call inherited 'del'
|
|
|
|
me.parents = subvec(me.parents,1);
|
|
|
|
me.del();
|
|
|
|
},
|
2014-05-25 18:11:13 +00:00
|
|
|
setController: func(controller=nil, arg...)
|
2013-12-01 12:29:22 +00:00
|
|
|
{
|
2014-04-29 02:26:32 +00:00
|
|
|
if (me.controller != nil) me.controller.del(me);
|
2013-12-01 12:29:22 +00:00
|
|
|
if (controller == nil)
|
|
|
|
controller = Map.df_controller;
|
|
|
|
elsif (typeof(controller) != 'hash')
|
|
|
|
controller = Map.Controller.get(controller);
|
2014-02-02 18:00:32 +00:00
|
|
|
|
|
|
|
if (controller == nil) {
|
|
|
|
me.controller = nil;
|
|
|
|
} else {
|
|
|
|
if (!isa(controller, Map.Controller))
|
|
|
|
die("OOP error: controller needs to inherit from Map.Controller");
|
2014-05-25 18:11:13 +00:00
|
|
|
me.controller = call(controller.new, [me]~arg, controller, var err=[]); # try...
|
2014-04-29 02:26:32 +00:00
|
|
|
if (size(err)) {
|
|
|
|
if (err[0] != "No such member: new") # ... and either catch or rethrow
|
|
|
|
die(err[0]);
|
|
|
|
else
|
|
|
|
me.controller = controller;
|
|
|
|
} elsif (me.controller == nil) {
|
|
|
|
me.controller = controller;
|
|
|
|
} elsif (me.controller != controller and !isa(me.controller, controller))
|
|
|
|
die("OOP error: created instance needs to inherit from or be the specific controller class");
|
2014-02-02 18:00:32 +00:00
|
|
|
}
|
2013-12-01 12:29:22 +00:00
|
|
|
|
|
|
|
return me;
|
|
|
|
},
|
2014-05-25 18:11:13 +00:00
|
|
|
addLayer: func(factory, type_arg=nil, priority=nil, style=nil, options=nil, visible=1)
|
2013-12-01 12:29:22 +00:00
|
|
|
{
|
2014-01-10 03:04:36 +00:00
|
|
|
if(contains(me.layers, type_arg))
|
2014-04-29 02:26:32 +00:00
|
|
|
printlog("warn", "addLayer() warning: overwriting existing layer:", type_arg);
|
2014-01-10 03:04:36 +00:00
|
|
|
|
2013-12-01 12:29:22 +00:00
|
|
|
# Argument handling
|
2014-05-25 18:11:13 +00:00
|
|
|
if (type_arg != nil) {
|
|
|
|
var layer = factory.new(type:type_arg, group:me, map:me, style:style, options:options, visible:visible);
|
2013-12-01 12:29:22 +00:00
|
|
|
var type = factory.get(type_arg);
|
2014-05-25 18:11:13 +00:00
|
|
|
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;
|
2013-12-01 12:29:22 +00:00
|
|
|
|
|
|
|
if (priority == nil)
|
|
|
|
priority = type.df_priority;
|
|
|
|
if (priority != nil)
|
2014-05-25 18:11:13 +00:00
|
|
|
layer.group.setInt("z-index", priority);
|
2014-04-29 02:26:32 +00:00
|
|
|
|
2014-05-25 18:11:13 +00:00
|
|
|
return layer; # return new layer to caller() so that we can directly work with it, i.e. to register event handlers (panning/zooming)
|
2013-12-01 12:29:22 +00:00
|
|
|
},
|
2014-01-10 03:04:36 +00:00
|
|
|
getLayer: func(type_arg) me.layers[type_arg],
|
2014-04-29 02:26:32 +00:00
|
|
|
|
|
|
|
setRange: func(range) me.set("range",range),
|
|
|
|
getRange: func me.get('range'),
|
|
|
|
|
|
|
|
setPos: func(lat, lon, hdg=nil, range=nil, alt=nil)
|
2013-12-01 12:29:22 +00:00
|
|
|
{
|
2014-05-25 18:11:13 +00:00
|
|
|
# TODO: also propage setPos events to layers and symbols (e.g. for offset maps)
|
2013-12-01 12:29:22 +00:00
|
|
|
me.set("ref-lat", lat);
|
|
|
|
me.set("ref-lon", lon);
|
|
|
|
if (hdg != nil)
|
|
|
|
me.set("hdg", hdg);
|
2014-01-10 03:04:36 +00:00
|
|
|
if (range != nil)
|
2014-04-29 02:26:32 +00:00
|
|
|
me.setRange(range);
|
|
|
|
if (alt != nil)
|
2014-05-25 18:11:13 +00:00
|
|
|
me.set("altitude", alt);
|
2014-04-29 02:26:32 +00:00
|
|
|
},
|
|
|
|
getPos: func
|
|
|
|
{
|
|
|
|
return [me.get("ref-lat"),
|
|
|
|
me.get("ref-lon"),
|
|
|
|
me.get("hdg"),
|
|
|
|
me.get("range"),
|
|
|
|
me.get("altitude")];
|
|
|
|
},
|
|
|
|
getLat: func me.get("ref-lat"),
|
|
|
|
getLon: func me.get("ref-lon"),
|
|
|
|
getHdg: func me.get("hdg"),
|
|
|
|
getAlt: func me.get("altitude"),
|
|
|
|
getRange: func me.get("range"),
|
|
|
|
getLatLon: func [me.get("ref-lat"), me.get("ref-lon")],
|
2014-05-25 18:11:13 +00:00
|
|
|
# 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).
|
2014-04-29 02:26:32 +00:00
|
|
|
getPosCoord: func
|
|
|
|
{
|
|
|
|
var (lat, lon) = (me.get("ref-lat"),
|
|
|
|
me.get("ref-lon"));
|
|
|
|
var alt = me.get("altitude");
|
|
|
|
if (lat == nil or lon == nil) {
|
|
|
|
if (contains(me, "coord")) {
|
|
|
|
debug.warn("canvas.Map: lost ref-lat and/or ref-lon source");
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
if (!contains(me, "coord")) {
|
|
|
|
me.coord = geo.Coord.new();
|
2014-05-25 18:11:13 +00:00
|
|
|
var m = me;
|
|
|
|
me.coord.update = func m.getPosCoord();
|
2014-04-29 02:26:32 +00:00
|
|
|
}
|
|
|
|
me.coord.set_latlon(lat,lon,alt or 0);
|
|
|
|
return me.coord;
|
2013-12-01 12:29:22 +00:00
|
|
|
},
|
|
|
|
# Update each layer on this Map. Called by
|
|
|
|
# me.controller.
|
2014-04-29 02:26:32 +00:00
|
|
|
update: func(predicate=nil)
|
2013-12-01 12:29:22 +00:00
|
|
|
{
|
2014-05-25 18:11:13 +00:00
|
|
|
var t = systime();
|
2014-01-10 03:04:36 +00:00
|
|
|
foreach (var l; keys(me.layers)) {
|
|
|
|
var layer = me.layers[l];
|
2014-04-29 02:26:32 +00:00
|
|
|
# Only update if the predicate allows
|
|
|
|
if (predicate == nil or predicate(layer))
|
2014-05-25 18:11:13 +00:00
|
|
|
layer.update();
|
2014-01-10 03:04:36 +00:00
|
|
|
}
|
2014-05-25 18:11:13 +00:00
|
|
|
printlog(_MP_dbg_lvl, "Took "~((systime()-t)*1000)~"ms to update map()");
|
2013-12-01 12:29:22 +00:00
|
|
|
return me;
|
|
|
|
},
|
2012-07-11 22:21:30 +00:00
|
|
|
};
|
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
# Text
|
|
|
|
# ==============================================================================
|
|
|
|
# Class for a text element on a canvas
|
|
|
|
#
|
|
|
|
var Text = {
|
2012-11-30 16:39:39 +00:00
|
|
|
new: func(ghost)
|
2012-08-01 22:31:54 +00:00
|
|
|
{
|
2012-11-30 16:39:39 +00:00
|
|
|
return { parents: [Text, Element.new(ghost)] };
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
|
|
|
# Set the text
|
|
|
|
setText: func(text)
|
|
|
|
{
|
2013-02-09 11:15:34 +00:00
|
|
|
me.set("text", typeof(text) == 'scalar' ? text : "");
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
2014-04-18 21:49:11 +00:00
|
|
|
appendText: func(text)
|
|
|
|
{
|
|
|
|
me.set("text", (me.get("text") or "") ~ (typeof(text) == 'scalar' ? text : ""));
|
|
|
|
},
|
2012-08-01 22:31:54 +00:00
|
|
|
# Set alignment
|
|
|
|
#
|
2013-12-01 12:29:22 +00:00
|
|
|
# @param align String, one of:
|
2012-08-01 22:31:54 +00:00
|
|
|
# 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)
|
|
|
|
{
|
2012-08-01 23:28:56 +00:00
|
|
|
me.set("alignment", align);
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
|
|
|
# Set the font size
|
2012-08-01 23:28:56 +00:00
|
|
|
setFontSize: func(size, aspect = 1)
|
2012-08-01 22:31:54 +00:00
|
|
|
{
|
2012-08-01 23:28:56 +00:00
|
|
|
me.setDouble("character-size", size);
|
|
|
|
me.setDouble("character-aspect-ratio", aspect);
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
|
|
|
# Set font (by name of font file)
|
|
|
|
setFont: func(name)
|
|
|
|
{
|
2012-08-01 23:28:56 +00:00
|
|
|
me.set("font", name);
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
|
|
|
# Enumeration of values for drawing mode:
|
2014-04-18 21:49:11 +00:00
|
|
|
TEXT: 0x01, # The text itself
|
|
|
|
BOUNDINGBOX: 0x02, # A bounding box (only lines)
|
|
|
|
FILLEDBOUNDINGBOX: 0x04, # A filled bounding box
|
|
|
|
ALIGNMENT: 0x08, # Draw a marker (cross) at the position of the text
|
2012-08-01 22:31:54 +00:00
|
|
|
# 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)
|
|
|
|
{
|
2012-08-01 23:28:56 +00:00
|
|
|
me.setInt("draw-mode", mode);
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
|
|
|
# Set bounding box padding
|
|
|
|
setPadding: func(pad)
|
|
|
|
{
|
2012-08-01 23:28:56 +00:00
|
|
|
me.setDouble("padding", pad);
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
2012-08-01 23:28:56 +00:00
|
|
|
setMaxWidth: func(w)
|
2012-08-01 22:31:54 +00:00
|
|
|
{
|
2012-08-01 23:28:56 +00:00
|
|
|
me.setDouble("max-width", w);
|
2012-08-23 19:05:52 +00:00
|
|
|
},
|
|
|
|
setColor: func me.set('fill', _getColor(arg)),
|
2014-04-29 02:26:32 +00:00
|
|
|
getColor: func me.get('fill'),
|
|
|
|
|
|
|
|
setColorFill: func me.set('background', _getColor(arg)),
|
|
|
|
getColorFill: func me.get('background'),
|
2012-08-01 22:31:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
# 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,
|
2012-08-01 23:28:56 +00:00
|
|
|
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,
|
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
# Number of coordinates per command
|
2012-08-01 23:28:56 +00:00
|
|
|
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
|
|
|
|
],
|
2012-08-01 22:31:54 +00:00
|
|
|
|
|
|
|
#
|
2012-11-30 16:39:39 +00:00
|
|
|
new: func(ghost)
|
2012-08-01 22:31:54 +00:00
|
|
|
{
|
2012-08-23 19:05:52 +00:00
|
|
|
return {
|
2012-11-30 16:39:39 +00:00
|
|
|
parents: [Path, Element.new(ghost)],
|
2013-06-27 21:14:49 +00:00
|
|
|
_first_cmd: 0,
|
|
|
|
_first_coord: 0,
|
|
|
|
_last_cmd: -1,
|
|
|
|
_last_coord: -1
|
2012-08-01 22:31:54 +00:00
|
|
|
};
|
|
|
|
},
|
2012-08-01 23:28:56 +00:00
|
|
|
# Remove all existing path data
|
|
|
|
reset: func
|
2012-08-01 22:31:54 +00:00
|
|
|
{
|
|
|
|
me._node.removeChildren('cmd', 0);
|
|
|
|
me._node.removeChildren('coord', 0);
|
2012-08-01 23:28:56 +00:00
|
|
|
me._node.removeChildren('coord-geo', 0);
|
2013-06-27 21:14:49 +00:00
|
|
|
me._first_cmd = 0;
|
|
|
|
me._first_coord = 0;
|
|
|
|
me._last_cmd = -1;
|
|
|
|
me._last_coord = -1;
|
2012-08-01 23:28:56 +00:00
|
|
|
return me;
|
|
|
|
},
|
|
|
|
# Set the path data (commands and coordinates)
|
|
|
|
setData: func(cmds, coords)
|
|
|
|
{
|
|
|
|
me.reset();
|
2012-08-01 22:31:54 +00:00
|
|
|
me._node.setValues({cmd: cmds, coord: coords});
|
2013-06-27 21:14:49 +00:00
|
|
|
me._last_cmd = size(cmds) - 1;
|
|
|
|
me._last_coord = size(coords) - 1;
|
2012-08-01 23:28:56 +00:00
|
|
|
return me;
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
2012-07-11 22:21:30 +00:00
|
|
|
setDataGeo: func(cmds, coords)
|
|
|
|
{
|
2012-08-01 23:28:56 +00:00
|
|
|
me.reset();
|
2012-07-11 22:21:30 +00:00
|
|
|
me._node.setValues({cmd: cmds, 'coord-geo': coords});
|
2013-06-27 21:14:49 +00:00
|
|
|
me._last_cmd = size(cmds) - 1;
|
|
|
|
me._last_coord = size(coords) - 1;
|
2012-08-01 23:28:56 +00:00
|
|
|
return me;
|
2012-07-11 22:21:30 +00:00
|
|
|
},
|
2012-08-01 23:28:56 +00:00
|
|
|
# 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
|
|
|
|
(
|
2012-09-04 20:53:13 +00:00
|
|
|
"Invalid number of arguments (expected " ~ num_coords ~ ")"
|
2012-08-01 23:28:56 +00:00
|
|
|
);
|
|
|
|
else
|
|
|
|
{
|
2013-06-27 21:14:49 +00:00
|
|
|
me.setInt("cmd[" ~ (me._last_cmd += 1) ~ "]", cmd);
|
2012-08-01 23:28:56 +00:00
|
|
|
for(var i = 0; i < num_coords; i += 1)
|
2013-06-27 21:14:49 +00:00
|
|
|
me.setDouble("coord[" ~ (me._last_coord += 1) ~ "]", coords[i]);
|
2012-08-01 23:28:56 +00:00
|
|
|
}
|
2012-11-04 13:25:36 +00:00
|
|
|
|
2012-08-01 23:28:56 +00:00
|
|
|
return me;
|
|
|
|
},
|
2014-05-25 18:11:13 +00:00
|
|
|
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;
|
|
|
|
},
|
2013-06-27 21:14:49 +00:00
|
|
|
# Remove first segment
|
|
|
|
pop_front: func me._removeSegment(1),
|
|
|
|
# Remove last segment
|
|
|
|
pop_back: func me._removeSegment(0),
|
|
|
|
# Get the number of segments
|
|
|
|
getNumSegments: func()
|
|
|
|
{
|
|
|
|
return me._last_cmd - me._first_cmd + 1;
|
|
|
|
},
|
|
|
|
# Get the number of coordinates (each command has 0..n coords)
|
|
|
|
getNumCoords: func()
|
|
|
|
{
|
|
|
|
return me._last_coord - me._first_coord + 1;
|
|
|
|
},
|
2012-08-01 23:28:56 +00:00
|
|
|
# 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
|
2013-12-01 12:29:22 +00:00
|
|
|
squadTo: func me.addSegment(me.VG_SQUAD_TO_ABS, arg),
|
|
|
|
squad: func me.addSegment(me.VG_SQUAD_TO_REL, arg),
|
2012-08-01 23:28:56 +00:00
|
|
|
# Add a smooth cubic Bézier curve
|
2012-09-04 20:53:13 +00:00
|
|
|
scubicTo: func me.addSegment(me.VG_SCUBIC_TO_ABS, arg),
|
|
|
|
scubic: func me.addSegment(me.VG_SCUBIC_TO_REL, arg),
|
2012-08-01 23:28:56 +00:00
|
|
|
# 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),
|
|
|
|
|
2013-02-23 18:19:32 +00:00
|
|
|
# Add a (rounded) rectangle to the path
|
|
|
|
#
|
|
|
|
# @param x Position of left border
|
|
|
|
# @param y Position of top border
|
|
|
|
# @param w Width
|
|
|
|
# @param h Height
|
|
|
|
# @param cfg Optional settings (eg. {"border-top-radius": 5})
|
|
|
|
rect: func(x, y, w, h, cfg = nil)
|
|
|
|
{
|
|
|
|
var opts = (cfg != nil) ? cfg : {};
|
|
|
|
|
|
|
|
# resolve border-[top-,bottom-][left-,right-]radius
|
|
|
|
var br = opts["border-radius"];
|
|
|
|
if( typeof(br) == 'scalar' )
|
|
|
|
br = [br, br];
|
|
|
|
|
|
|
|
var _parseRadius = func(id)
|
|
|
|
{
|
|
|
|
if( (var r = opts["border-" ~ id ~ "-radius"]) == nil )
|
|
|
|
{
|
|
|
|
# parse top, bottom, left, right separate if no value specified for
|
|
|
|
# single corner
|
|
|
|
foreach(var s; ["top", "bottom", "left", "right"])
|
|
|
|
{
|
|
|
|
if( id.starts_with(s ~ "-") )
|
|
|
|
{
|
|
|
|
r = opts["border-" ~ s ~ "-radius"];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( r == nil )
|
|
|
|
return br;
|
|
|
|
else if( typeof(r) == 'scalar' )
|
|
|
|
return [r, r];
|
|
|
|
else
|
|
|
|
return r;
|
|
|
|
};
|
|
|
|
|
|
|
|
# top-left
|
|
|
|
if( (var r = _parseRadius("top-left")) != nil )
|
|
|
|
{
|
|
|
|
me.moveTo(x, y + r[1])
|
|
|
|
.arcSmallCWTo(r[0], r[1], 0, x + r[0], y);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
me.moveTo(x, y);
|
|
|
|
|
|
|
|
# top-right
|
|
|
|
if( (r = _parseRadius("top-right")) != nil )
|
|
|
|
{
|
|
|
|
me.horizTo(x + w - r[0])
|
|
|
|
.arcSmallCWTo(r[0], r[1], 0, x + w, y + r[1]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
me.horizTo(x + w);
|
|
|
|
|
|
|
|
# bottom-right
|
|
|
|
if( (r = _parseRadius("bottom-right")) != nil )
|
|
|
|
{
|
|
|
|
me.vertTo(y + h - r[1])
|
|
|
|
.arcSmallCWTo(r[0], r[1], 0, x + w - r[0], y + h);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
me.vertTo(y + h);
|
|
|
|
|
|
|
|
# bottom-left
|
|
|
|
if( (r = _parseRadius("bottom-left")) != nil )
|
|
|
|
{
|
|
|
|
me.horizTo(x + r[0])
|
|
|
|
.arcSmallCWTo(r[0], r[1], 0, x, y + h - r[1]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
me.horizTo(x);
|
|
|
|
|
|
|
|
return me.close();
|
|
|
|
},
|
|
|
|
|
2012-08-23 19:05:52 +00:00
|
|
|
setColor: func me.setStroke(_getColor(arg)),
|
2014-04-29 02:26:32 +00:00
|
|
|
getColor: func me.getStroke(),
|
2012-11-04 13:25:36 +00:00
|
|
|
|
2014-04-29 02:26:32 +00:00
|
|
|
setColorFill: func me.setFill(_getColor(arg)),
|
|
|
|
getColorFill: func me.getColorFill(),
|
2012-08-23 19:05:52 +00:00
|
|
|
setFill: func(fill)
|
|
|
|
{
|
|
|
|
me.set('fill', fill);
|
|
|
|
},
|
|
|
|
setStroke: func(stroke)
|
|
|
|
{
|
|
|
|
me.set('stroke', stroke);
|
|
|
|
},
|
2014-04-29 02:26:32 +00:00
|
|
|
getStroke: func me.get('stroke'),
|
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
setStrokeLineWidth: func(width)
|
|
|
|
{
|
2012-08-01 23:28:56 +00:00
|
|
|
me.setDouble('stroke-width', width);
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
2012-08-01 23:28:56 +00:00
|
|
|
# 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)
|
2012-08-01 22:31:54 +00:00
|
|
|
{
|
|
|
|
if( typeof(pattern) == 'vector' )
|
2012-08-23 19:05:52 +00:00
|
|
|
me.set('stroke-dasharray', string.join(',', pattern));
|
2012-08-01 22:31:54 +00:00
|
|
|
else
|
2012-08-01 23:28:56 +00:00
|
|
|
debug.warn("setStrokeDashArray: vector expected!");
|
|
|
|
|
|
|
|
return me;
|
2013-06-27 21:14:49 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
# private:
|
|
|
|
_removeSegment: func(front)
|
|
|
|
{
|
|
|
|
if( me.getNumSegments() < 1 )
|
|
|
|
{
|
|
|
|
debug.warn("No segment available");
|
|
|
|
return me;
|
|
|
|
}
|
|
|
|
|
|
|
|
var cmd = front ? me._first_cmd : me._last_cmd;
|
|
|
|
var num_coords = me.num_coords[ me.get("cmd[" ~ cmd ~ "]") ];
|
|
|
|
if( me.getNumCoords() < num_coords )
|
|
|
|
{
|
|
|
|
debug.warn("To few coords available");
|
|
|
|
}
|
|
|
|
|
|
|
|
me._node.removeChild("cmd", cmd);
|
|
|
|
|
|
|
|
var first_coord = front ? me._first_coord : me._last_coord - num_coords + 1;
|
|
|
|
for(var i = 0; i < num_coords; i += 1)
|
|
|
|
me._node.removeChild("coord", first_coord + i);
|
|
|
|
|
|
|
|
if( front )
|
|
|
|
{
|
|
|
|
me._first_cmd += 1;
|
|
|
|
me._first_coord += num_coords;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
me._last_cmd -= 1;
|
|
|
|
me._last_coord -= num_coords;
|
|
|
|
}
|
|
|
|
|
|
|
|
return me;
|
|
|
|
},
|
2012-08-01 22:31:54 +00:00
|
|
|
};
|
|
|
|
|
2012-08-05 21:43:43 +00:00
|
|
|
# Image
|
|
|
|
# ==============================================================================
|
|
|
|
# Class for an image element on a canvas
|
|
|
|
#
|
|
|
|
var Image = {
|
2012-11-30 16:39:39 +00:00
|
|
|
new: func(ghost)
|
2012-08-05 21:43:43 +00:00
|
|
|
{
|
2012-11-30 16:39:39 +00:00
|
|
|
return {parents: [Image, Element.new(ghost)]};
|
2012-08-05 21:43:43 +00:00
|
|
|
},
|
2012-08-09 14:53:09 +00:00
|
|
|
# Set image file to be used
|
|
|
|
#
|
|
|
|
# @param file Path to file or canvas (Use canvas://... for canvas, eg.
|
|
|
|
# canvas://by-index/texture[0])
|
2012-08-05 21:43:43 +00:00
|
|
|
setFile: func(file)
|
|
|
|
{
|
2014-03-20 10:46:48 +00:00
|
|
|
me.set("src", file);
|
2012-08-09 14:53:09 +00:00
|
|
|
},
|
|
|
|
# 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
|
2014-05-25 18:11:13 +00:00
|
|
|
setSourceRect: func
|
2012-08-09 14:53:09 +00:00
|
|
|
{
|
2014-05-25 18:11:13 +00:00
|
|
|
# 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;
|
|
|
|
|
2012-08-09 14:53:09 +00:00
|
|
|
me._node.getNode("source", 1).setValues({
|
|
|
|
left: left,
|
|
|
|
top: top,
|
|
|
|
right: right,
|
|
|
|
bottom: bottom,
|
|
|
|
normalized: normalized
|
|
|
|
});
|
|
|
|
return me;
|
|
|
|
},
|
|
|
|
# Set size of image element
|
2013-03-09 13:02:10 +00:00
|
|
|
#
|
|
|
|
# @param width
|
|
|
|
# @param height
|
|
|
|
# - or -
|
|
|
|
# @param size ([width, height])
|
|
|
|
setSize: func
|
2012-08-09 14:53:09 +00:00
|
|
|
{
|
2013-03-09 13:02:10 +00:00
|
|
|
me._node.setValues({size: _arg2valarray(arg)});
|
2012-08-09 14:53:09 +00:00
|
|
|
return me;
|
2012-08-05 21:43:43 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
# Element factories used by #Group elements to create children
|
|
|
|
Group._element_factories = {
|
|
|
|
"group": Group.new,
|
2012-07-11 22:21:30 +00:00
|
|
|
"map": Map.new,
|
2012-08-01 22:31:54 +00:00
|
|
|
"text": Text.new,
|
2012-08-05 21:43:43 +00:00
|
|
|
"path": Path.new,
|
|
|
|
"image": Image.new
|
2012-08-01 22:31:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
# 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"
|
|
|
|
# });
|
2012-11-04 13:25:36 +00:00
|
|
|
#
|
2012-08-01 22:31:54 +00:00
|
|
|
# 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)
|
|
|
|
{
|
2014-06-01 10:25:34 +00:00
|
|
|
var placement = me._node.addChild("placement", 0, 0);
|
2012-08-01 22:31:54 +00:00
|
|
|
placement.setValues(vals);
|
|
|
|
return placement;
|
|
|
|
},
|
|
|
|
# Create a new group with the given name
|
|
|
|
#
|
2012-08-01 23:28:56 +00:00
|
|
|
# @param id Optional id/name for the group
|
|
|
|
createGroup: func(id = nil)
|
2012-08-01 22:31:54 +00:00
|
|
|
{
|
2013-06-06 20:46:37 +00:00
|
|
|
return Group.new(me._createGroup(id));
|
|
|
|
},
|
|
|
|
# Get the group with the given name
|
|
|
|
getGroup: func(id)
|
|
|
|
{
|
|
|
|
return Group.new(me._getGroup(id));
|
2012-08-01 22:31:54 +00:00
|
|
|
},
|
|
|
|
# Set the background color
|
|
|
|
#
|
|
|
|
# @param color Vector of 3 or 4 values in [0, 1]
|
2014-06-01 10:25:34 +00:00
|
|
|
setColorBackground: func me.set('background', _getColor(arg)),
|
|
|
|
getColorBackground: func me.get('background'),
|
2012-08-09 20:08:44 +00:00
|
|
|
# Get path of canvas to be used eg. in Image::setFile
|
|
|
|
getPath: func()
|
|
|
|
{
|
2014-06-01 10:25:34 +00:00
|
|
|
return "canvas://by-index/texture[" ~ me._node.getIndex() ~ "]";
|
2013-01-01 12:16:19 +00:00
|
|
|
},
|
|
|
|
# Destructor
|
|
|
|
#
|
|
|
|
# releases associated canvas and makes this object unusable
|
|
|
|
del: func
|
|
|
|
{
|
2014-06-01 10:25:34 +00:00
|
|
|
me._node.remove();
|
2013-01-01 12:16:19 +00:00
|
|
|
me.parents = nil; # ensure all ghosts get destroyed
|
2012-08-09 20:08:44 +00:00
|
|
|
}
|
2012-08-01 22:31:54 +00:00
|
|
|
};
|
|
|
|
|
2014-06-01 10:25:34 +00:00
|
|
|
# @param g Canvas ghost
|
|
|
|
var wrapCanvas = func(g)
|
2012-11-18 22:28:53 +00:00
|
|
|
{
|
2014-06-01 10:25:34 +00:00
|
|
|
if( g != nil and g._impl == nil )
|
|
|
|
g._impl = {
|
|
|
|
parents: [PropertyElement, Canvas],
|
|
|
|
_node: props.wrapNode(g._node_ghost)
|
|
|
|
};
|
|
|
|
return g;
|
2012-11-18 22:28:53 +00:00
|
|
|
}
|
|
|
|
|
2012-08-01 22:31:54 +00:00
|
|
|
# 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)
|
|
|
|
{
|
2012-11-18 22:28:53 +00:00
|
|
|
var m = wrapCanvas(_newCanvasGhost());
|
2014-06-01 10:25:34 +00:00
|
|
|
m._node.setValues(vals);
|
2012-08-01 22:31:54 +00:00
|
|
|
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
|
2012-11-18 22:28:53 +00:00
|
|
|
var get = func(arg)
|
2012-08-01 22:31:54 +00:00
|
|
|
{
|
2012-11-18 22:28:53 +00:00
|
|
|
if( isa(arg, props.Node) )
|
|
|
|
var node = arg;
|
|
|
|
else if( typeof(arg) == "hash" )
|
|
|
|
var node = props.Node.new(arg);
|
|
|
|
else
|
|
|
|
die("canvas.new: Invalid argument.");
|
|
|
|
|
2014-06-01 10:25:34 +00:00
|
|
|
return wrapCanvas(_getCanvasGhost(node._g));
|
2012-08-01 22:31:54 +00:00
|
|
|
};
|
2012-08-01 23:28:56 +00:00
|
|
|
|
2013-07-24 22:59:52 +00:00
|
|
|
var getDesktop = func()
|
|
|
|
{
|
|
|
|
return Group.new(_getDesktopGhost());
|
|
|
|
};
|