split classes into separate files
This commit is contained in:
parent
d9fd674aea
commit
1094e5214e
10 changed files with 1328 additions and 1264 deletions
1290
Nasal/canvas/api.nas
1290
Nasal/canvas/api.nas
File diff suppressed because it is too large
Load diff
61
Nasal/canvas/api/canvas.nas
Normal file
61
Nasal/canvas/api/canvas.nas
Normal file
|
@ -0,0 +1,61 @@
|
|||
#-------------------------------------------------------------------------------
|
||||
# Canvas
|
||||
#-------------------------------------------------------------------------------
|
||||
# Class for a canvas
|
||||
# not to be confused with the namespace canvas (case sensitive!)
|
||||
#
|
||||
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 = me._node.addChild("placement", 0, 0);
|
||||
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._createGroup(id));
|
||||
},
|
||||
|
||||
# Get the group with the given name
|
||||
getGroup: func(id) {
|
||||
return Group.new(me._getGroup(id));
|
||||
},
|
||||
|
||||
# Set the background color
|
||||
#
|
||||
# @param color Vector of 3 or 4 values in [0, 1]
|
||||
setColorBackground: func {
|
||||
me.set("background", _getColor(arg));
|
||||
},
|
||||
|
||||
getColorBackground: func {
|
||||
me.get("background");
|
||||
},
|
||||
|
||||
# Get path of canvas to be used eg. in Image::setFile
|
||||
getPath: func() {
|
||||
return "canvas://by-index/texture["~me._node.getIndex()~"]";
|
||||
},
|
||||
|
||||
# Destructor
|
||||
#
|
||||
# releases associated canvas and makes this object unusable
|
||||
del: func {
|
||||
me._node.remove();
|
||||
me.parents = nil; # ensure all ghosts get destroyed
|
||||
}
|
||||
};
|
229
Nasal/canvas/api/element.nas
Normal file
229
Nasal/canvas/api/element.nas
Normal file
|
@ -0,0 +1,229 @@
|
|||
#-------------------------------------------------------------------------------
|
||||
# canvas.Element
|
||||
#-------------------------------------------------------------------------------
|
||||
# Baseclass for all elements on a canvas
|
||||
#
|
||||
var Element = {
|
||||
# Reference frames (for "clip" coordinates)
|
||||
GLOBAL: 0,
|
||||
PARENT: 1,
|
||||
LOCAL: 2,
|
||||
|
||||
# Constructor
|
||||
#
|
||||
# @param ghost Element ghost as retrieved from core methods
|
||||
new: func(ghost) {
|
||||
return {
|
||||
parents: [PropertyElement, Element, ghost],
|
||||
_node: props.wrapNode(ghost._node_ghost)
|
||||
};
|
||||
},
|
||||
|
||||
# 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);
|
||||
},
|
||||
|
||||
# Get the canvas this element is placed on
|
||||
getCanvas: func() {
|
||||
wrapCanvas(me._getCanvas());
|
||||
},
|
||||
|
||||
# Check if elements represent same instance
|
||||
#
|
||||
# @param el Other Element or element ghost
|
||||
equals: func(el) {
|
||||
return me._node.equals(el._node_ghost);
|
||||
},
|
||||
|
||||
# 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) {
|
||||
# tf[0] is reserved for setRotation, so min. index 1 is used here
|
||||
var node = me._node.addChild("tf", 1);
|
||||
return Transform.new(node, vals);
|
||||
},
|
||||
|
||||
# Shortcut for setting translation
|
||||
setTranslation: func {
|
||||
me._getTf().setTranslation(arg);
|
||||
return me;
|
||||
},
|
||||
|
||||
# Get translation set with #setTranslation
|
||||
getTranslation: func() {
|
||||
if (me["_tf"] == nil)
|
||||
return [0, 0];
|
||||
|
||||
return [me._tf.e.getValue(), me._tf.f.getValue()];
|
||||
},
|
||||
|
||||
# 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.
|
||||
# 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)
|
||||
);
|
||||
}
|
||||
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));
|
||||
},
|
||||
|
||||
getColorFill: func {
|
||||
me.get("fill");
|
||||
},
|
||||
|
||||
getTransformedBounds: func {
|
||||
me.getTightBoundingBox();
|
||||
},
|
||||
|
||||
# Calculate the transformation center based on bounding box and center-offset
|
||||
updateCenter: func {
|
||||
me.update();
|
||||
var bb = me.getTightBoundingBox();
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
# Set transformation center (currently only used for rotation)
|
||||
setCenter: func() {
|
||||
var center = _arg2valarray(arg);
|
||||
if (size(center) != 2){ return debug.warn("invalid arg"); }
|
||||
me._setupCenterNodes(center[0], center[1]);
|
||||
return me;
|
||||
},
|
||||
|
||||
# Get transformation center
|
||||
getCenter: func() {
|
||||
var center = [0, 0];
|
||||
me._setupCenterNodes();
|
||||
|
||||
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;
|
||||
}
|
||||
return center;
|
||||
},
|
||||
|
||||
getSize: func {
|
||||
var bb = me.getTightBoundingBox();
|
||||
return [bb[2] - bb[0], bb[3] - bb[1]];
|
||||
},
|
||||
|
||||
# convert bounding box vector into clip string (yes, different order)
|
||||
boundingbox2clip: func(bb) {
|
||||
return sprintf("rect(%d,%d,%d,%d)", bb[1], bb[2], bb[3], bb[0])
|
||||
},
|
||||
|
||||
# set clip by bounding box
|
||||
# bounding_box: [xmin, ymin, xmax, ymax]
|
||||
setClipByBoundingBox: func(bounding_box, clip_frame = nil) {
|
||||
if (clip_frame == nil) { clip_frame = Element.PARENT; }
|
||||
me.set("clip", me.boundingbox2clip(bounding_box));
|
||||
me.set("clip-frame", clip_frame);
|
||||
return me;
|
||||
},
|
||||
|
||||
# set clipping by bounding box of another element
|
||||
setClipByElement: func(clip_elem) {
|
||||
clip_elem.update();
|
||||
var bounds = clip_elem.getTightBoundingBox();
|
||||
me.setClipByBoundingBox(bounds, canvas.Element.PARENT);
|
||||
},
|
||||
|
||||
# Internal Transform for convenience transform functions
|
||||
_getTf: func {
|
||||
if (me["_tf"] == nil) { me["_tf"] = me.createTransform(); }
|
||||
return me._tf;
|
||||
},
|
||||
|
||||
_setupCenterNodes: func(cx = nil, cy = nil) {
|
||||
if (me["_center"] == nil) {
|
||||
me["_center"] = [me._node.getNode("center[0]", cx != nil),
|
||||
me._node.getNode("center[1]", cy != nil)];
|
||||
}
|
||||
if (cx != nil) {
|
||||
me._center[0].setDoubleValue(cx);
|
||||
}
|
||||
if (cy != nil) {
|
||||
me._center[1].setDoubleValue(cy);
|
||||
}
|
||||
},
|
||||
};
|
141
Nasal/canvas/api/group.nas
Normal file
141
Nasal/canvas/api/group.nas
Normal file
|
@ -0,0 +1,141 @@
|
|||
#-------------------------------------------------------------------------------
|
||||
# canvas.Group
|
||||
#-------------------------------------------------------------------------------
|
||||
# Class for a group element on a canvas
|
||||
#
|
||||
var Group = {
|
||||
new: func(ghost) {
|
||||
return { parents: [Group, Element.new(ghost)] };
|
||||
},
|
||||
|
||||
# Create a child of given type with specified id.
|
||||
# type can be group, text
|
||||
createChild: func(type, id = nil) {
|
||||
var ghost = me._createChild(type, id);
|
||||
var factory = me._getFactory(type);
|
||||
if (factory == nil) {
|
||||
return ghost;
|
||||
}
|
||||
return factory(ghost);
|
||||
},
|
||||
|
||||
# Create multiple children of given type
|
||||
createChildren: func(type, count) {
|
||||
var factory = me._getFactory(type);
|
||||
if (factory == nil) {
|
||||
return [];
|
||||
}
|
||||
var nodes = props._addChildren(me._node._g, [type, count, 0, 0]);
|
||||
for (var i = 0; i < count; i += 1) {
|
||||
nodes[i] = factory(me._getChild(nodes[i]));
|
||||
}
|
||||
return nodes;
|
||||
},
|
||||
|
||||
# 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) {
|
||||
return me.createChild("path").rect(x, y, w, h, cfg);
|
||||
},
|
||||
|
||||
# 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;
|
||||
},
|
||||
|
||||
# Recursively get all children of class specified by first param
|
||||
getChildrenOfType: func(type, array = nil) {
|
||||
var children = array;
|
||||
if (children == nil) {
|
||||
children = [];
|
||||
}
|
||||
var my_children = me.getChildren();
|
||||
if (typeof(type) != "vector") {
|
||||
type = [type];
|
||||
}
|
||||
foreach(var c; my_children) {
|
||||
foreach(var t; type) {
|
||||
if (isa(c, t)) {
|
||||
append(children, c);
|
||||
}
|
||||
}
|
||||
if (isa(c, canvas.Group)) {
|
||||
c.getChildrenOfType(type, children);
|
||||
}
|
||||
}
|
||||
return children;
|
||||
},
|
||||
|
||||
# Set color to children of type Path and Text. It is possible to optionally
|
||||
# specify which types of children should be affected by passing a vector as
|
||||
# the last agrument, ie. my_group.setColor(1,1,1,[Path]);
|
||||
setColor: func() {
|
||||
var color = arg;
|
||||
var types = [Path, Text];
|
||||
var arg_c = size(color);
|
||||
if (arg_c > 1 and typeof(color[-1]) == "vector") {
|
||||
types = color[-1];
|
||||
color = subvec(color, 0, arg_c - 1);
|
||||
}
|
||||
var children = me.getChildrenOfType(types);
|
||||
if (typeof(color) == "vector") {
|
||||
var first = color[0];
|
||||
if (typeof(first) == "vector")
|
||||
color = first;
|
||||
}
|
||||
foreach(var c; children)
|
||||
c.setColor(color);
|
||||
},
|
||||
|
||||
# Get first child with given id (breadth-first search)
|
||||
#
|
||||
# @note Use with care as it can take several miliseconds (for me eg. ~2ms).
|
||||
# TODO check with new C++ implementation
|
||||
getElementById: func(id) {
|
||||
var ghost = me._getElementById(id);
|
||||
if (ghost == nil) { return nil; }
|
||||
|
||||
var node = props.wrapNode(ghost._node_ghost);
|
||||
var factory = me._getFactory(node.getName());
|
||||
if (factory == nil) { return ghost; }
|
||||
return factory(ghost);
|
||||
},
|
||||
|
||||
# Remove all children
|
||||
removeAllChildren: func() {
|
||||
foreach(var type; keys(me._element_factories)) {
|
||||
me._node.removeChildren(type, 0);
|
||||
}
|
||||
return me;
|
||||
},
|
||||
|
||||
# private:
|
||||
# element nodes have type NONE and valid element names (those in the factory
|
||||
# list)
|
||||
_isElementNode: func(el) {
|
||||
return el.getType() == "NONE" and me._element_factories[el.getName()] != nil;
|
||||
},
|
||||
|
||||
# Create element from existing node
|
||||
_wrapElement: func(node) {
|
||||
return me._element_factories[node.getName()](me._getChild(node._g));
|
||||
},
|
||||
|
||||
_getFactory: func(type) {
|
||||
var factory = me._element_factories[type];
|
||||
if (factory == nil)
|
||||
debug.dump("canvas.Group.createChild(): unknown type ("~type~")");
|
||||
return factory;
|
||||
}
|
||||
};
|
34
Nasal/canvas/api/helpers.nas
Normal file
34
Nasal/canvas/api/helpers.nas
Normal file
|
@ -0,0 +1,34 @@
|
|||
#-------------------------------------------------------------------------------
|
||||
# canvas API helper functions
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
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;
|
||||
}
|
65
Nasal/canvas/api/image.nas
Normal file
65
Nasal/canvas/api/image.nas
Normal file
|
@ -0,0 +1,65 @@
|
|||
#-------------------------------------------------------------------------------
|
||||
# canvas.Image
|
||||
#-------------------------------------------------------------------------------
|
||||
# Class for an image element on a canvas
|
||||
#
|
||||
var Image = {
|
||||
new: func(ghost) {
|
||||
return {parents: [Image, Element.new(ghost)]};
|
||||
},
|
||||
|
||||
# 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("src", 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 {
|
||||
# Work with both positional arguments and named arguments.
|
||||
# Support first argument being a vector instead of four separate ones.
|
||||
if (size(arg) == 1) {
|
||||
arg = arg[0];
|
||||
}
|
||||
elsif (size(arg) and size(arg) < 4 and typeof(arg[0]) == "vector") {
|
||||
arg = arg[0]~arg[1:];
|
||||
}
|
||||
if (!contains(caller(0)[0], "normalized")) {
|
||||
if (size(arg) > 4)
|
||||
var normalized = arg[4];
|
||||
else var normalized = 1;
|
||||
}
|
||||
if (size(arg) >= 3)
|
||||
var (left,top,right,bottom) = arg;
|
||||
|
||||
me._node.getNode("source", 1).setValues({
|
||||
left: left,
|
||||
top: top,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
normalized: normalized
|
||||
});
|
||||
return me;
|
||||
},
|
||||
|
||||
# Set size of image element
|
||||
#
|
||||
# @param width
|
||||
# @param height
|
||||
# - or -
|
||||
# @param size ([width, height])
|
||||
setSize: func {
|
||||
me._node.setValues({size: _arg2valarray(arg)});
|
||||
return me;
|
||||
}
|
||||
};
|
||||
|
169
Nasal/canvas/api/map.nas
Normal file
169
Nasal/canvas/api/map.nas
Normal file
|
@ -0,0 +1,169 @@
|
|||
#-------------------------------------------------------------------------------
|
||||
# canvas.Map
|
||||
#-------------------------------------------------------------------------------
|
||||
# Class for a group element on a canvas with possibly geographic positions
|
||||
# which automatically get projected according to the specified projection.
|
||||
# Each map consists of an arbitrary number of layers (canvas groups)
|
||||
#
|
||||
var Map = {
|
||||
df_controller: nil,
|
||||
new: func(ghost) {
|
||||
return { parents: [Map, Group.new(ghost)], layers:{},
|
||||
controller:nil }
|
||||
.setController();
|
||||
},
|
||||
|
||||
del: func() {
|
||||
#print("canvas.Map.del()");
|
||||
if (me.controller != nil)
|
||||
me.controller.del(me);
|
||||
foreach (var k; keys(me.layers)) {
|
||||
me.layers[k].del();
|
||||
delete(me.layers, k);
|
||||
}
|
||||
# call inherited "del"
|
||||
me.parents = subvec(me.parents,1);
|
||||
me.del();
|
||||
},
|
||||
|
||||
setController: func(controller=nil, arg...) {
|
||||
if (me.controller != nil) me.controller.del(me);
|
||||
if (controller == nil) {
|
||||
controller = Map.df_controller;
|
||||
}
|
||||
elsif (typeof(controller) != "hash") {
|
||||
controller = Map.Controller.get(controller);
|
||||
}
|
||||
|
||||
if (controller == nil) {
|
||||
me.controller = nil;
|
||||
}
|
||||
else {
|
||||
if (!isa(controller, Map.Controller))
|
||||
die("OOP error: controller needs to inherit from Map.Controller");
|
||||
me.controller = call(controller.new, [me]~arg, controller, var err=[]); # try...
|
||||
if (size(err)) {
|
||||
if (err[0] != "No such member: new") # ... and either catch or rethrow
|
||||
die(err[0]);
|
||||
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");
|
||||
}
|
||||
|
||||
return me;
|
||||
},
|
||||
|
||||
getController: func() {
|
||||
return me.controller;
|
||||
},
|
||||
|
||||
addLayer: func(factory, type_arg=nil, priority=nil, style=nil, opts=nil, visible=1) {
|
||||
if (contains(me.layers, type_arg)) {
|
||||
logprint(DEV_ALERT, "addLayer() warning: overwriting existing layer:", type_arg);
|
||||
}
|
||||
|
||||
var options = opts;
|
||||
# Argument handling
|
||||
if (type_arg != nil) {
|
||||
var layer = factory.new(type:type_arg, group:me, map:me, style:style, options:options, visible:visible);
|
||||
var type = factory.get(type_arg);
|
||||
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;
|
||||
|
||||
if (priority == nil)
|
||||
priority = type.df_priority;
|
||||
if (priority != nil)
|
||||
layer.group.setInt("z-index", priority);
|
||||
|
||||
return layer; # return new layer to caller() so that we can directly work with it, i.e. to register event handlers (panning/zooming)
|
||||
},
|
||||
|
||||
getLayer: func(type_arg) {
|
||||
me.layers[type_arg];
|
||||
},
|
||||
|
||||
setRange: func(range) {
|
||||
me.set("range",range);
|
||||
},
|
||||
|
||||
setScreenRange: func(range) {
|
||||
me.set("screen-range",range);
|
||||
},
|
||||
|
||||
setPos: func(lat, lon, hdg=nil, range=nil, alt=nil) {
|
||||
# TODO: also propage setPos events to layers and symbols (e.g. for offset maps)
|
||||
me.set("ref-lat", lat);
|
||||
me.set("ref-lon", lon);
|
||||
if (hdg != nil)
|
||||
me.set("hdg", hdg);
|
||||
if (range != nil)
|
||||
me.setRange(range);
|
||||
if (alt != nil)
|
||||
me.set("altitude", alt);
|
||||
},
|
||||
|
||||
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"),
|
||||
getScreenRange: func me.get("screen-range"),
|
||||
getLatLon: func [me.get("ref-lat"), me.get("ref-lon")],
|
||||
|
||||
# N.B.: This always returns the same geo.Coord object,
|
||||
# so its values can and will change at any time (call
|
||||
# update() on the coord to ensure it is up-to-date,
|
||||
# which basically calls this method again).
|
||||
getPosCoord: func {
|
||||
var (lat, lon) = (me.get("ref-lat"), 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();
|
||||
var m = me;
|
||||
me.coord.update = func m.getPosCoord();
|
||||
}
|
||||
me.coord.set_latlon(lat,lon,alt or 0);
|
||||
return me.coord;
|
||||
},
|
||||
|
||||
# Update each layer on this Map. Called by
|
||||
# me.controller.
|
||||
update: func(predicate=nil) {
|
||||
var t = systime();
|
||||
foreach (var l; keys(me.layers)) {
|
||||
var layer = me.layers[l];
|
||||
# Only update if the predicate allows
|
||||
if (predicate == nil or predicate(layer)) {
|
||||
layer.update();
|
||||
}
|
||||
}
|
||||
logprint(_MP_dbg_lvl, "Took "~((systime()-t)*1000)~"ms to update map()");
|
||||
me.setBool("update", 1); # update any coordinates that changed, to avoid floating labels etc.
|
||||
return me;
|
||||
},
|
||||
};
|
||||
|
388
Nasal/canvas/api/path.nas
Normal file
388
Nasal/canvas/api/path.nas
Normal file
|
@ -0,0 +1,388 @@
|
|||
#-------------------------------------------------------------------------------
|
||||
# canvas.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(ghost)
|
||||
{
|
||||
return {
|
||||
parents: [Path, Element.new(ghost)],
|
||||
_first_cmd: 0,
|
||||
_first_coord: 0,
|
||||
_last_cmd: -1,
|
||||
_last_coord: -1
|
||||
};
|
||||
},
|
||||
|
||||
# Remove all existing path data
|
||||
reset: func {
|
||||
me._node.removeChildren("cmd", 0);
|
||||
me._node.removeChildren("coord", 0);
|
||||
me._node.removeChildren("coord-geo", 0);
|
||||
me._first_cmd = 0;
|
||||
me._first_coord = 0;
|
||||
me._last_cmd = -1;
|
||||
me._last_coord = -1;
|
||||
return me;
|
||||
},
|
||||
|
||||
# Set the path data (commands and coordinates)
|
||||
setData: func(cmds, coords) {
|
||||
me.reset();
|
||||
me._node.setValues({cmd: cmds, coord: coords});
|
||||
me._last_cmd = size(cmds) - 1;
|
||||
me._last_coord = size(coords) - 1;
|
||||
return me;
|
||||
},
|
||||
|
||||
setDataGeo: func(cmds, coords) {
|
||||
me.reset();
|
||||
me._node.setValues({cmd: cmds, "coord-geo": coords});
|
||||
me._last_cmd = size(cmds) - 1;
|
||||
me._last_coord = size(coords) - 1;
|
||||
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._last_cmd += 1)~"]", cmd);
|
||||
for (var i = 0; i < num_coords; i += 1)
|
||||
me.setDouble("coord["~(me._last_coord += 1)~"]", coords[i]);
|
||||
}
|
||||
|
||||
return me;
|
||||
},
|
||||
|
||||
addSegmentGeo: func(cmd, coords...) {
|
||||
var coords = _arg2valarray(coords);
|
||||
var num_coords = me.num_coords[cmd];
|
||||
if (size(coords) != num_coords) {
|
||||
debug.warn("Invalid number of arguments (expected "~num_coords~")");
|
||||
}
|
||||
else {
|
||||
me.setInt("cmd["~(me._last_cmd += 1)~"]", cmd);
|
||||
for (var i = 0; i < num_coords; i += 1)
|
||||
me.set("coord-geo["~(me._last_coord += 1)~"]", coords[i]);
|
||||
}
|
||||
return me;
|
||||
},
|
||||
|
||||
# Remove first segment
|
||||
pop_front: func {
|
||||
me._removeSegment(1);
|
||||
},
|
||||
|
||||
# Remove last segment
|
||||
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;
|
||||
},
|
||||
|
||||
# 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
|
||||
squadTo: func me.addSegment(me.VG_SQUAD_TO_ABS, arg),
|
||||
squad: 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),
|
||||
|
||||
# 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();
|
||||
},
|
||||
|
||||
# Add a (rounded) square to the path
|
||||
#
|
||||
# @param x Position of left border
|
||||
# @param y Position of top border
|
||||
# @param l length
|
||||
# @param cfg Optional settings (eg. {"border-top-radius": 5})
|
||||
square: func(x, y, l, cfg = nil) {
|
||||
return me.rect(x, y, l, l, cfg);
|
||||
},
|
||||
|
||||
# Add an ellipse to the path
|
||||
#
|
||||
# @param rx radius x
|
||||
# @param ry radius y
|
||||
# @param cx (optional) center x coordinate or vector [cx, cy]
|
||||
# @param cy (optional) center y coordinate
|
||||
ellipse: func(rx, ry, cx = nil, cy = nil) {
|
||||
if (typeof(cx) == "vector") {
|
||||
cy = cx[1];
|
||||
cx = cx[0];
|
||||
}
|
||||
else {
|
||||
cx = num(cx) or 0;
|
||||
cy = num(cy) or 0;
|
||||
}
|
||||
me.moveTo(cx - rx, cy)
|
||||
.arcSmallCW(rx, ry, 0, 2*rx, 0)
|
||||
.arcSmallCW(rx, ry, 0, -2*rx, 0);
|
||||
return me;
|
||||
},
|
||||
|
||||
# Add a circle to the path
|
||||
#
|
||||
# @param r radius
|
||||
# @param cx (optional) center x coordinate or vector [cx, cy]
|
||||
# @param cy (optional) center y coordinate
|
||||
circle: func(r, cx = nil, cy = nil) {
|
||||
return me.ellipse(r, r, cx, cy);
|
||||
},
|
||||
|
||||
setColor: func {
|
||||
me.setStroke(_getColor(arg));
|
||||
},
|
||||
|
||||
getColor: func {
|
||||
me.getStroke();
|
||||
},
|
||||
|
||||
setColorFill: func {
|
||||
me.setFill(_getColor(arg));
|
||||
},
|
||||
|
||||
getColorFill: func {
|
||||
me.getColorFill();
|
||||
},
|
||||
|
||||
setFill: func(fill) {
|
||||
me.set("fill", fill);
|
||||
},
|
||||
|
||||
setStroke: func(stroke) {
|
||||
me.set("stroke", stroke);
|
||||
},
|
||||
|
||||
getStroke: func {
|
||||
me.get("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 linejoin
|
||||
#
|
||||
# @param linejoin String, "miter", "round" or "bevel"
|
||||
#
|
||||
# See http://www.w3.org/TR/SVG/painting.html#StrokeLinejoinProperty for details
|
||||
setStrokeLineJoin: func(linejoin) {
|
||||
me.set("stroke-linejoin", linejoin);
|
||||
},
|
||||
|
||||
# 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;
|
||||
},
|
||||
|
||||
# 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;
|
||||
},
|
||||
};
|
||||
|
125
Nasal/canvas/api/text.nas
Normal file
125
Nasal/canvas/api/text.nas
Normal file
|
@ -0,0 +1,125 @@
|
|||
#-------------------------------------------------------------------------------
|
||||
# canvas.Text
|
||||
#-------------------------------------------------------------------------------
|
||||
# Class for a text element on a canvas
|
||||
#
|
||||
var Text = {
|
||||
new: func(ghost) {
|
||||
return { parents: [Text, Element.new(ghost)] };
|
||||
},
|
||||
|
||||
# Set the text
|
||||
setText: func(text) {
|
||||
me.set("text", typeof(text) == "scalar" ? text : "");
|
||||
},
|
||||
|
||||
getText: func() {
|
||||
return me.get("text");
|
||||
},
|
||||
|
||||
# enable reduced property I/O update function
|
||||
enableUpdate: func () {
|
||||
me._lasttext = "INIT_BLANK";
|
||||
me.updateText = func (text)
|
||||
{
|
||||
if (text == me._lasttext) {return;}
|
||||
me._lasttext = text;
|
||||
me.set("text", typeof(text) == "scalar" ? text : "");
|
||||
};
|
||||
},
|
||||
|
||||
# reduced property I/O text update template
|
||||
updateText: func (text) {
|
||||
die("updateText() requires enableUpdate() to be called first");
|
||||
},
|
||||
|
||||
|
||||
# enable fast setprop-based text writing
|
||||
enableFast: func () {
|
||||
me._node_path = me._node.getPath()~"/text";
|
||||
me.setTextFast = func(text) {
|
||||
setprop(me._node_path, text);
|
||||
};
|
||||
},
|
||||
|
||||
# fast, setprop-based text writing template
|
||||
setTextFast: func (text) {
|
||||
die("setTextFast() requires enableFast() to be called first");
|
||||
},
|
||||
|
||||
# append text to an existing string
|
||||
appendText: func(text) {
|
||||
me.set("text", (me.get("text") or "")~(typeof(text) == "scalar" ? text : ""));
|
||||
},
|
||||
|
||||
# Set alignment
|
||||
#
|
||||
# @param align 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: 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
|
||||
# Set draw mode. Binary combination of the values above. Since I have not
|
||||
# found a bitwise "or" we have to use a "+" instead.
|
||||
# e.g. 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));
|
||||
},
|
||||
|
||||
getColor: func {
|
||||
me.get("fill");
|
||||
},
|
||||
|
||||
setColorFill: func {
|
||||
me.set("background", _getColor(arg));
|
||||
},
|
||||
|
||||
getColorFill: func {
|
||||
me.get("background");
|
||||
},
|
||||
};
|
90
Nasal/canvas/api/transform.nas
Normal file
90
Nasal/canvas/api/transform.nas
Normal file
|
@ -0,0 +1,90 @@
|
|||
#-------------------------------------------------------------------------------
|
||||
# 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), #tx
|
||||
f: node.getNode("m[5]", 1), #ty
|
||||
};
|
||||
|
||||
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) do not 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.c.setDoubleValue(-s);
|
||||
me.b.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()];
|
||||
}
|
||||
};
|
Loading…
Add table
Reference in a new issue