2 var _getColor = func(color)
7 if( typeof(color) == 'scalar' )
9 if( typeof(color) != "vector" )
10 return debug.warn("Wrong type for color");
12 if( size(color) < 3 or size(color) > 4 )
13 return debug.warn("Color needs 3 or 4 values (RGB or RGBA)");
16 if( size(color) == 4 )
20 # rgb = [0,255], a = [0,1]
21 for(var i = 0; i < size(color); i += 1)
22 str ~= (i > 0 ? ',' : '') ~ (i < 3 ? int(color[i] * 255) : color[i]);
27 var _arg2valarray = func
30 while ( typeof(ret) == "vector"
31 and size(ret) == 1 and typeof(ret[0]) == "vector" )
37 # ==============================================================================
38 # A transformation matrix which is used to transform an #Element on the canvas.
39 # The dimensions of the matrix are 3x3 where the last row is always 0 0 1:
45 # See http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined for details.
48 new: func(node, vals = nil)
53 a: node.getNode("m[0]", 1),
54 b: node.getNode("m[1]", 1),
55 c: node.getNode("m[2]", 1),
56 d: node.getNode("m[3]", 1),
57 e: node.getNode("m[4]", 1),
58 f: node.getNode("m[5]", 1)
61 var use_vals = typeof(vals) == 'vector' and size(vals) == 6;
63 # initialize to identity matrix
64 m.a.setDoubleValue(use_vals ? vals[0] : 1);
65 m.b.setDoubleValue(use_vals ? vals[1] : 0);
66 m.c.setDoubleValue(use_vals ? vals[2] : 0);
67 m.d.setDoubleValue(use_vals ? vals[3] : 1);
68 m.e.setDoubleValue(use_vals ? vals[4] : 0);
69 m.f.setDoubleValue(use_vals ? vals[5] : 0);
75 var trans = _arg2valarray(arg);
77 me.e.setDoubleValue(trans[0]);
78 me.f.setDoubleValue(trans[1]);
82 # Set rotation (Optionally around a specified point instead of (0,0))
85 # setRotation(rot, cx, cy)
87 # @note If using with rotation center different to (0,0) don't use
88 # #setTranslation as it would interfere with the rotation.
89 setRotation: func(angle)
91 var center = _arg2valarray(arg);
93 var s = math.sin(angle);
94 var c = math.cos(angle);
96 me.a.setDoubleValue(c);
97 me.b.setDoubleValue(s);
98 me.c.setDoubleValue(-s);
99 me.d.setDoubleValue(c);
101 if( size(center) == 2 )
103 me.e.setDoubleValue( (-center[0] * c) + (center[1] * s) + center[0] );
104 me.f.setDoubleValue( (-center[0] * s) - (center[1] * c) + center[1] );
109 # Set scale (either as parameters or array)
111 # If only one parameter is given its value is used for both x and y
116 var scale = _arg2valarray(arg);
118 me.a.setDoubleValue(scale[0]);
119 me.d.setDoubleValue(size(scale) >= 2 ? scale[1] : scale[0]);
125 # TODO handle rotation
126 return [me.a.getValue(), me.d.getValue()];
131 # ==============================================================================
132 # Baseclass for all elements on a canvas
135 # Reference frames (for "clip" coordinates)
142 # @param ghost Element ghost as retrieved from core methods
146 parents: [PropertyElement, Element, ghost],
147 _node: props.wrapNode(ghost._node_ghost)
150 # Get parent group/element
153 var parent_ghost = me._getParent();
154 if( parent_ghost == nil )
157 var type = props.wrapNode(parent_ghost._node_ghost).getName();
158 var factory = me._getFactory(type);
162 return factory(parent_ghost);
164 # Check if elements represent same instance
166 # @param el Other Element or element ghost
169 return me._node.equals(el._node_ghost);
171 # Trigger an update of the element
173 # Elements are automatically updated once a frame, with a delay of one frame.
174 # If you wan't to get an element updated in the current frame you have to use
178 me.setInt("update", 1);
182 # @param visible Whether the element should be visible
183 setVisible: func(visible = 1)
185 me.setBool("visible", visible);
187 getVisible: func me.getBool("visible"),
188 # Hide element (Shortcut for setVisible(0))
189 hide: func me.setVisible(0),
190 # Show element (Shortcut for setVisible(1))
191 show: func me.setVisible(1),
192 # Toggle element visibility
193 toggleVisibility: func me.setVisible( !me.getVisible() ),
195 setGeoPosition: func(lat, lon)
197 me._getTf()._node.getNode("m-geo[4]", 1).setValue("N" ~ lat);
198 me._getTf()._node.getNode("m-geo[5]", 1).setValue("E" ~ lon);
201 # Create a new transformation matrix
203 # @param vals Default values (Vector of 6 elements)
204 createTransform: func(vals = nil)
206 var node = me._node.addChild("tf", 1); # tf[0] is reserved for
208 return Transform.new(node, vals);
210 # Shortcut for setting translation
211 setTranslation: func { me._getTf().setTranslation(arg); return me; },
212 # Get translation set with #setTranslation
213 getTranslation: func()
215 if( me['_tf'] == nil )
218 return [me._tf.e.getValue(), me._tf.f.getValue()];
220 # Set rotation around transformation center (see #setCenter).
222 # @note This replaces the the existing transformation. For additional scale or
223 # translation use additional transforms (see #createTransform).
224 setRotation: func(rot)
226 if( me['_tf_rot'] == nil )
227 # always use the first matrix slot to ensure correct rotation
228 # around transformation center.
229 # tf-rot-index can be set to change the slot to be used. This is used for
230 # example by the SVG parser to apply the rotation after all
231 # transformations defined in the SVG file.
232 me['_tf_rot'] = Transform.new(
233 me._node.getNode("tf[" ~ me.get("tf-rot-index", 0) ~ "]", 1)
236 me._tf_rot.setRotation(rot, me.getCenter());
239 # Shortcut for setting scale
240 setScale: func { me._getTf().setScale(arg); return me; },
241 # Shortcut for getting scale
242 getScale: func me._getTf().getScale(),
243 # Set the fill/background/boundingbox color
245 # @param color Vector of 3 or 4 values in [0, 1]
246 setColorFill: func me.set('fill', _getColor(arg)),
248 getBoundingBox: func()
250 var bb = me._node.getNode("bounding-box");
253 var min_x = bb.getNode("min-x").getValue();
257 bb.getNode("min-y").getValue(),
258 bb.getNode("max-x").getValue(),
259 bb.getNode("max-y").getValue() ];
264 # Calculate the transformation center based on bounding box and center-offset
268 var bb = me.getTransformedBounds();
270 if( bb[0] > bb[2] or bb[1] > bb[3] )
275 (bb[0] + bb[2]) / 2 + (me.get("center-offset-x") or 0),
276 (bb[1] + bb[3]) / 2 + (me.get("center-offset-y") or 0)
280 # Set transformation center (currently only used for rotation)
283 var center = _arg2valarray(arg);
284 if( size(center) != 2 )
285 return debug.warn("invalid arg");
287 me._setupCenterNodes(center[0], center[1]);
290 # Get transformation center
294 me._setupCenterNodes();
296 if( me._center[0] != nil )
297 center[0] = me._center[0].getValue() or 0;
298 if( me._center[1] != nil )
299 center[1] = me._center[1].getValue() or 0;
303 # Internal Transform for convenience transform functions
306 if( me['_tf'] == nil )
307 me['_tf'] = me.createTransform();
310 _setupCenterNodes: func(cx = nil, cy = nil)
312 if( me["_center"] == nil )
314 me._node.getNode("center[0]", cx != nil),
315 me._node.getNode("center[1]", cy != nil)
319 me._center[0].setDoubleValue(cx);
321 me._center[1].setDoubleValue(cy);
326 # ==============================================================================
327 # Class for a group element on a canvas
333 return { parents: [Group, Element.new(ghost)] };
335 # Create a child of given type with specified id.
336 # type can be group, text
337 createChild: func(type, id = nil)
339 var ghost = me._createChild(type, id);
340 var factory = me._getFactory(type);
344 return factory(ghost);
346 # Create multiple children of given type
347 createChildren: func(type, count)
349 var factory = me._getFactory(type);
353 var nodes = props._addChildren(me._node._g, [type, count, 0, 0]);
354 for(var i = 0; i < count; i += 1)
355 nodes[i] = factory( me._getChild(nodes[i]) );
359 # Create a path child drawing a (rounded) rectangle
361 # @param x Position of left border
362 # @param y Position of top border
365 # @param cfg Optional settings (eg. {"border-top-radius": 5})
366 rect: func(x, y, w, h, cfg = nil)
368 return me.createChild("path").rect(x, y, w, h, cfg);
370 # Get a vector of all child elements
375 foreach(var c; me._node.getChildren())
376 if( me._isElementNode(c) )
377 append(children, me._wrapElement(c));
381 # Get first child with given id (breadth-first search)
383 # @note Use with care as it can take several miliseconds (for me eg. ~2ms).
384 # TODO check with new C++ implementation
385 getElementById: func(id)
387 var ghost = me._getElementById(id);
391 var node = props.wrapNode(ghost._node_ghost);
392 var factory = me._getFactory( node.getName() );
396 return factory(ghost);
398 # Remove all children
399 removeAllChildren: func()
401 foreach(var type; keys(me._element_factories))
402 me._node.removeChildren(type, 0);
406 _isElementNode: func(el)
408 # element nodes have type NONE and valid element names (those in the factory
410 return el.getType() == "NONE"
411 and me._element_factories[ el.getName() ] != nil;
413 _wrapElement: func(node)
415 # Create element from existing node
416 return me._element_factories[ node.getName() ]( me._getChild(node._g) );
418 _getFactory: func(type)
420 var factory = me._element_factories[type];
423 debug.dump("canvas.Group.createChild(): unknown type (" ~ type ~ ")");
430 # ==============================================================================
431 # Class for a group element on a canvas with possibly geopgraphic positions
432 # which automatically get projected according to the specified projection.
438 return { parents: [Map, Group.new(ghost)], layers:{} }.setController();
442 #print("canvas.Map.del()");
443 if (me.controller != nil)
444 me.controller.del(me);
445 foreach (var k; keys(me.layers)) {
447 delete(me.layers, k);
449 # call inherited 'del'
450 me.parents = subvec(me.parents,1);
453 setController: func(controller=nil)
455 if (controller == nil)
456 controller = Map.df_controller;
457 elsif (typeof(controller) != 'hash')
458 controller = Map.Controller.get(controller);
460 if (controller == nil) {
463 if (!isa(controller, Map.Controller))
464 die("OOP error: controller needs to inherit from Map.Controller");
465 me.controller = controller.new(me);
466 if (!isa(me.controller, controller))
467 die("OOP error: created instance needs to inherit from specific controller class");
472 addLayer: func(factory, type_arg=nil, priority=nil)
474 if(contains(me.layers, type_arg))
475 print("addLayer() warning: overwriting existing layer:", type_arg);
477 # print("addLayer():", type_arg);
481 var type = factory.get(type_arg);
482 else var type = factory;
484 me.layers[type_arg]= type.new(me);
486 priority = type.df_priority;
488 me.layers[type_arg].setInt("z-index", priority);
491 getLayer: func(type_arg) me.layers[type_arg],
492 setPos: func(lat, lon, hdg=nil, range=nil)
494 me.set("ref-lat", lat);
495 me.set("ref-lon", lon);
499 me.set("range", range);
501 # Update each layer on this Map. Called by
505 foreach (var l; keys(me.layers)) {
506 var layer = me.layers[l];
507 call(layer.update, arg, layer);
514 # ==============================================================================
515 # Class for a text element on a canvas
520 return { parents: [Text, Element.new(ghost)] };
525 me.set("text", typeof(text) == 'scalar' ? text : "");
529 # @param align String, one of:
542 # left-bottom-baseline
543 # center-bottom-baseline
544 # right-bottom-baseline
546 setAlignment: func(align)
548 me.set("alignment", align);
551 setFontSize: func(size, aspect = 1)
553 me.setDouble("character-size", size);
554 me.setDouble("character-aspect-ratio", aspect);
556 # Set font (by name of font file)
559 me.set("font", name);
561 # Enumeration of values for drawing mode:
562 TEXT: 1, # The text itself
563 BOUNDINGBOX: 2, # A bounding box (only lines)
564 FILLEDBOUNDINGBOX: 4, # A filled bounding box
565 ALIGNMENT: 8, # Draw a marker (cross) at the position of the text
566 # Set draw mode. Binary combination of the values above. Since I haven't found
567 # a bitwise or we have to use a + instead.
569 # eg. my_text.setDrawMode(Text.TEXT + Text.BOUNDINGBOX);
570 setDrawMode: func(mode)
572 me.setInt("draw-mode", mode);
574 # Set bounding box padding
575 setPadding: func(pad)
577 me.setDouble("padding", pad);
581 me.setDouble("max-width", w);
583 setColor: func me.set('fill', _getColor(arg)),
584 setColorFill: func me.set('background', _getColor(arg))
588 # ==============================================================================
589 # Class for an (OpenVG) path element on a canvas
592 # Path segment commands (VGPathCommand)
616 VG_SCUBIC_TO_ABS: 16,
617 VG_SCUBIC_TO_REL: 17,
618 VG_SCCWARC_TO: 20, # Note that CC and CCW commands are swapped. This is
619 VG_SCCWARC_TO_ABS:20, # needed due to the different coordinate systems used.
620 VG_SCCWARC_TO_REL:21, # In OpenVG values along the y-axis increase from bottom
621 VG_SCWARC_TO: 18, # to top, whereas in the Canvas system it is flipped.
622 VG_SCWARC_TO_ABS: 18,
623 VG_SCWARC_TO_REL: 19,
625 VG_LCCWARC_TO_ABS:24,
626 VG_LCCWARC_TO_REL:25,
628 VG_LCWARC_TO_ABS: 22,
629 VG_LCWARC_TO_REL: 23,
631 # Number of coordinates per command
633 0, 0, # VG_CLOSE_PATH
642 5, 5, # VG_SCCWARC_TO
644 5, 5, # VG_LCCWARC_TO
652 parents: [Path, Element.new(ghost)],
659 # Remove all existing path data
662 me._node.removeChildren('cmd', 0);
663 me._node.removeChildren('coord', 0);
664 me._node.removeChildren('coord-geo', 0);
671 # Set the path data (commands and coordinates)
672 setData: func(cmds, coords)
675 me._node.setValues({cmd: cmds, coord: coords});
676 me._last_cmd = size(cmds) - 1;
677 me._last_coord = size(coords) - 1;
680 setDataGeo: func(cmds, coords)
683 me._node.setValues({cmd: cmds, 'coord-geo': coords});
684 me._last_cmd = size(cmds) - 1;
685 me._last_coord = size(coords) - 1;
689 addSegment: func(cmd, coords...)
691 var coords = _arg2valarray(coords);
692 var num_coords = me.num_coords[cmd];
693 if( size(coords) != num_coords )
696 "Invalid number of arguments (expected " ~ num_coords ~ ")"
700 me.setInt("cmd[" ~ (me._last_cmd += 1) ~ "]", cmd);
701 for(var i = 0; i < num_coords; i += 1)
702 me.setDouble("coord[" ~ (me._last_coord += 1) ~ "]", coords[i]);
707 # Remove first segment
708 pop_front: func me._removeSegment(1),
709 # Remove last segment
710 pop_back: func me._removeSegment(0),
711 # Get the number of segments
712 getNumSegments: func()
714 return me._last_cmd - me._first_cmd + 1;
716 # Get the number of coordinates (each command has 0..n coords)
719 return me._last_coord - me._first_coord + 1;
722 moveTo: func me.addSegment(me.VG_MOVE_TO_ABS, arg),
723 move: func me.addSegment(me.VG_MOVE_TO_REL, arg),
725 lineTo: func me.addSegment(me.VG_LINE_TO_ABS, arg),
726 line: func me.addSegment(me.VG_LINE_TO_REL, arg),
727 # Add a horizontal line
728 horizTo: func me.addSegment(me.VG_HLINE_TO_ABS, arg),
729 horiz: func me.addSegment(me.VG_HLINE_TO_REL, arg),
730 # Add a vertical line
731 vertTo: func me.addSegment(me.VG_VLINE_TO_ABS, arg),
732 vert: func me.addSegment(me.VG_VLINE_TO_REL, arg),
733 # Add a quadratic Bézier curve
734 quadTo: func me.addSegment(me.VG_QUAD_TO_ABS, arg),
735 quad: func me.addSegment(me.VG_QUAD_TO_REL, arg),
736 # Add a cubic Bézier curve
737 cubicTo: func me.addSegment(me.VG_CUBIC_TO_ABS, arg),
738 cubic: func me.addSegment(me.VG_CUBIC_TO_REL, arg),
739 # Add a smooth quadratic Bézier curve
740 squadTo: func me.addSegment(me.VG_SQUAD_TO_ABS, arg),
741 squad: func me.addSegment(me.VG_SQUAD_TO_REL, arg),
742 # Add a smooth cubic Bézier curve
743 scubicTo: func me.addSegment(me.VG_SCUBIC_TO_ABS, arg),
744 scubic: func me.addSegment(me.VG_SCUBIC_TO_REL, arg),
745 # Draw an elliptical arc (shorter counter-clockwise arc)
746 arcSmallCCWTo: func me.addSegment(me.VG_SCCWARC_TO_ABS, arg),
747 arcSmallCCW: func me.addSegment(me.VG_SCCWARC_TO_REL, arg),
748 # Draw an elliptical arc (shorter clockwise arc)
749 arcSmallCWTo: func me.addSegment(me.VG_SCWARC_TO_ABS, arg),
750 arcSmallCW: func me.addSegment(me.VG_SCWARC_TO_REL, arg),
751 # Draw an elliptical arc (longer counter-clockwise arc)
752 arcLargeCCWTo: func me.addSegment(me.VG_LCCWARC_TO_ABS, arg),
753 arcLargeCCW: func me.addSegment(me.VG_LCCWARC_TO_REL, arg),
754 # Draw an elliptical arc (shorter clockwise arc)
755 arcLargeCWTo: func me.addSegment(me.VG_LCWARC_TO_ABS, arg),
756 arcLargeCW: func me.addSegment(me.VG_LCWARC_TO_REL, arg),
757 # Close the path (implicit lineTo to first point of path)
758 close: func me.addSegment(me.VG_CLOSE_PATH),
760 # Add a (rounded) rectangle to the path
762 # @param x Position of left border
763 # @param y Position of top border
766 # @param cfg Optional settings (eg. {"border-top-radius": 5})
767 rect: func(x, y, w, h, cfg = nil)
769 var opts = (cfg != nil) ? cfg : {};
771 # resolve border-[top-,bottom-][left-,right-]radius
772 var br = opts["border-radius"];
773 if( typeof(br) == 'scalar' )
776 var _parseRadius = func(id)
778 if( (var r = opts["border-" ~ id ~ "-radius"]) == nil )
780 # parse top, bottom, left, right separate if no value specified for
782 foreach(var s; ["top", "bottom", "left", "right"])
784 if( id.starts_with(s ~ "-") )
786 r = opts["border-" ~ s ~ "-radius"];
794 else if( typeof(r) == 'scalar' )
801 if( (var r = _parseRadius("top-left")) != nil )
803 me.moveTo(x, y + r[1])
804 .arcSmallCWTo(r[0], r[1], 0, x + r[0], y);
810 if( (r = _parseRadius("top-right")) != nil )
812 me.horizTo(x + w - r[0])
813 .arcSmallCWTo(r[0], r[1], 0, x + w, y + r[1]);
819 if( (r = _parseRadius("bottom-right")) != nil )
821 me.vertTo(y + h - r[1])
822 .arcSmallCWTo(r[0], r[1], 0, x + w - r[0], y + h);
828 if( (r = _parseRadius("bottom-left")) != nil )
831 .arcSmallCWTo(r[0], r[1], 0, x, y + h - r[1]);
839 setColor: func me.setStroke(_getColor(arg)),
840 setColorFill: func me.setFill(_getColor(arg)),
844 me.set('fill', fill);
846 setStroke: func(stroke)
848 me.set('stroke', stroke);
850 setStrokeLineWidth: func(width)
852 me.setDouble('stroke-width', width);
856 # @param linecap String, "butt", "round" or "square"
858 # See http://www.w3.org/TR/SVG/painting.html#StrokeLinecapProperty for details
859 setStrokeLineCap: func(linecap)
861 me.set('stroke-linecap', linecap);
863 # Set stroke dasharray
865 # @param pattern Vector, Vector of alternating dash and gap lengths
866 # [on1, off1, on2, ...]
867 setStrokeDashArray: func(pattern)
869 if( typeof(pattern) == 'vector' )
870 me.set('stroke-dasharray', string.join(',', pattern));
872 debug.warn("setStrokeDashArray: vector expected!");
878 _removeSegment: func(front)
880 if( me.getNumSegments() < 1 )
882 debug.warn("No segment available");
886 var cmd = front ? me._first_cmd : me._last_cmd;
887 var num_coords = me.num_coords[ me.get("cmd[" ~ cmd ~ "]") ];
888 if( me.getNumCoords() < num_coords )
890 debug.warn("To few coords available");
893 me._node.removeChild("cmd", cmd);
895 var first_coord = front ? me._first_coord : me._last_coord - num_coords + 1;
896 for(var i = 0; i < num_coords; i += 1)
897 me._node.removeChild("coord", first_coord + i);
902 me._first_coord += num_coords;
907 me._last_coord -= num_coords;
915 # ==============================================================================
916 # Class for an image element on a canvas
921 return {parents: [Image, Element.new(ghost)]};
923 # Set image file to be used
925 # @param file Path to file or canvas (Use canvas://... for canvas, eg.
926 # canvas://by-index/texture[0])
929 me.set("file", file);
931 # Set rectangular region of source image to be used
933 # @param left Rectangle minimum x coordinate
934 # @param top Rectangle minimum y coordinate
935 # @param right Rectangle maximum x coordinate
936 # @param bottom Rectangle maximum y coordinate
937 # @param normalized Whether to use normalized ([0,1]) or image
938 # ([0, image_width]/[0, image_height]) coordinates
939 setSourceRect: func(left, top, right, bottom, normalized = 1)
941 me._node.getNode("source", 1).setValues({
946 normalized: normalized
950 # Set size of image element
955 # @param size ([width, height])
958 me._node.setValues({size: _arg2valarray(arg)});
963 # Element factories used by #Group elements to create children
964 Group._element_factories = {
973 # ==============================================================================
977 # Place this canvas somewhere onto the object. Pass criterions for placement
980 # my_canvas.addPlacement({
981 # "texture": "EICAS.png",
982 # "node": "PFD-Screen",
983 # "parent": "Some parent name"
986 # Note that we can choose whichever of the three filter criterions we use for
987 # matching the target object for our placement. If none of the three fields is
988 # given every texture of the model will be replaced.
989 addPlacement: func(vals)
991 var placement = me.texture.addChild("placement", 0, 0);
992 placement.setValues(vals);
995 # Create a new group with the given name
997 # @param id Optional id/name for the group
998 createGroup: func(id = nil)
1000 return Group.new(me._createGroup(id));
1002 # Get the group with the given name
1005 return Group.new(me._getGroup(id));
1007 # Set the background color
1009 # @param color Vector of 3 or 4 values in [0, 1]
1010 setColorBackground: func () { me.texture.getNode('background', 1).setValue(_getColor(arg)); me; },
1011 # Get path of canvas to be used eg. in Image::setFile
1014 return "canvas://by-index/texture[" ~ me.texture.getIndex() ~ "]";
1018 # releases associated canvas and makes this object unusable
1021 me.texture.remove();
1022 me.parents = nil; # ensure all ghosts get destroyed
1026 var wrapCanvas = func(canvas_ghost)
1029 parents: [PropertyElement, Canvas, canvas_ghost],
1030 texture: props.wrapNode(canvas_ghost._node_ghost)
1032 m._node = m.texture;
1036 # Create a new canvas. Pass parameters as hash, eg:
1038 # var my_canvas = canvas.new({
1039 # "name": "PFD-Test",
1040 # "size": [512, 512],
1041 # "view": [768, 1024],
1044 var new = func(vals)
1046 var m = wrapCanvas(_newCanvasGhost());
1047 m.texture.setValues(vals);
1051 # Get the first existing canvas with the given name
1053 # @param name Name of the canvas
1054 # @return #Canvas, if canvas with #name exists
1058 if( isa(arg, props.Node) )
1060 else if( typeof(arg) == "hash" )
1061 var node = props.Node.new(arg);
1063 die("canvas.new: Invalid argument.");
1065 var canvas_ghost = _getCanvasGhost(node._g);
1066 if( canvas_ghost == nil )
1069 return wrapCanvas(canvas_ghost);
1072 var getDesktop = func()
1074 return Group.new(_getDesktopGhost());