# Internal helper var _getColor = func(color) { if( size(color) == 1 ) var color = color[0]; if( typeof(color) == 'scalar' ) return color; if( typeof(color) != "vector" ) return debug.warn("Wrong type for color"); if( size(color) < 3 or size(color) > 4 ) return debug.warn("Color needs 3 or 4 values (RGB or RGBA)"); var str = 'rgb'; if( size(color) == 4 ) str ~= 'a'; str ~= '('; # rgb = [0,255], a = [0,1] for(var i = 0; i < size(color); i += 1) str ~= (i > 0 ? ',' : '') ~ (i < 3 ? int(color[i] * 255) : color[i]); return str ~ ')'; }; var _arg2valarray = func { var ret = arg; while ( typeof(ret) == "vector" and size(ret) == 1 and typeof(ret[0]) == "vector" ) ret = ret[0]; return ret; } # Transform # ============================================================================== # A transformation matrix which is used to transform an #Element on the canvas. # The dimensions of the matrix are 3x3 where the last row is always 0 0 1: # # a c e # b d f # 0 0 1 # # See http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined for details. # var Transform = { new: func(node, vals = nil) { var m = { parents: [Transform], _node: node, a: node.getNode("m[0]", 1), b: node.getNode("m[1]", 1), c: node.getNode("m[2]", 1), d: node.getNode("m[3]", 1), e: node.getNode("m[4]", 1), f: node.getNode("m[5]", 1) }; var use_vals = typeof(vals) == 'vector' and size(vals) == 6; # initialize to identity matrix m.a.setDoubleValue(use_vals ? vals[0] : 1); m.b.setDoubleValue(use_vals ? vals[1] : 0); m.c.setDoubleValue(use_vals ? vals[2] : 0); m.d.setDoubleValue(use_vals ? vals[3] : 1); m.e.setDoubleValue(use_vals ? vals[4] : 0); m.f.setDoubleValue(use_vals ? vals[5] : 0); return m; }, setTranslation: func { var trans = _arg2valarray(arg); me.e.setDoubleValue(trans[0]); me.f.setDoubleValue(trans[1]); return me; }, # Set rotation (Optionally around a specified point instead of (0,0)) # # setRotation(rot) # setRotation(rot, cx, cy) # # @note If using with rotation center different to (0,0) don't use # #setTranslation as it would interfere with the rotation. setRotation: func(angle) { var center = _arg2valarray(arg); var s = math.sin(angle); var c = math.cos(angle); me.a.setDoubleValue(c); me.b.setDoubleValue(s); me.c.setDoubleValue(-s); me.d.setDoubleValue(c); if( size(center) == 2 ) { me.e.setDoubleValue( (-center[0] * c) + (center[1] * s) + center[0] ); me.f.setDoubleValue( (-center[0] * s) - (center[1] * c) + center[1] ); } return me; }, # Set scale (either as parameters or array) # # If only one parameter is given its value is used for both x and y # setScale(x, y) # setScale([x, y]) setScale: func { var scale = _arg2valarray(arg); me.a.setDoubleValue(scale[0]); me.d.setDoubleValue(size(scale) >= 2 ? scale[1] : scale[0]); return me; }, getScale: func() { # TODO handle rotation return [me.a.getValue(), me.d.getValue()]; } }; # Element # ============================================================================== # Baseclass for all elements on a canvas # var Element = { # 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) { var node = me._node.addChild("tf", 1); # tf[0] is reserved for # setRotation return Transform.new(node, vals); }, # Shortcut for setting translation setTranslation: func { me._getTf().setTranslation(arg); return me; }, # 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; }, # 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); } }; # Group # ============================================================================== # Class for a group element on a canvas # var Group = { # public: 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; }, # 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: _isElementNode: func(el) { # element nodes have type NONE and valid element names (those in the factory # list) return el.getType() == "NONE" and me._element_factories[ el.getName() ] != nil; }, _wrapElement: func(node) { # Create element from existing node return me._element_factories[ node.getName() ]( 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; } }; # Map # ============================================================================== # Class for a group element on a canvas with possibly geopgraphic 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; }, addLayer: func(factory, type_arg=nil, priority=nil, style=nil, options=nil, visible=1) { if(contains(me.layers, type_arg)) printlog("warn", "addLayer() warning: overwriting existing layer:", type_arg); # 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), getRange: func me.get('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"), 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(); } printlog(_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; }, }; # 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 : ""); }, 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 haven't found # a bitwise or we have to use a + instead. # # eg. my_text.setDrawMode(Text.TEXT + Text.BOUNDINGBOX); setDrawMode: func(mode) { me.setInt("draw-mode", mode); }, # Set bounding box padding setPadding: func(pad) { me.setDouble("padding", pad); }, setMaxWidth: func(w) { me.setDouble("max-width", w); }, setColor: func me.set('fill', _getColor(arg)), getColor: func me.get('fill'), setColorFill: func me.set('background', _getColor(arg)), getColorFill: func me.get('background'), }; # 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(); }, 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 # 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; }, }; # 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; } }; # Element factories used by #Group elements to create children Group._element_factories = { "group": Group.new, "map": Map.new, "text": Text.new, "path": Path.new, "image": Image.new }; # Canvas # ============================================================================== # Class for a canvas # var Canvas = { # Place this canvas somewhere onto the object. Pass criterions for placement # as a hash, eg: # # my_canvas.addPlacement({ # "texture": "EICAS.png", # "node": "PFD-Screen", # "parent": "Some parent name" # }); # # Note that we can choose whichever of the three filter criterions we use for # matching the target object for our placement. If none of the three fields is # given every texture of the model will be replaced. addPlacement: func(vals) { var placement = 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 } }; # @param g Canvas ghost var wrapCanvas = func(g) { if( g != nil and g._impl == nil ) g._impl = { parents: [PropertyElement, Canvas], _node: props.wrapNode(g._node_ghost) }; return g; } # Create a new canvas. Pass parameters as hash, eg: # # var my_canvas = canvas.new({ # "name": "PFD-Test", # "size": [512, 512], # "view": [768, 1024], # "mipmapping": 1 # }); var new = func(vals) { var m = wrapCanvas(_newCanvasGhost()); m._node.setValues(vals); return m; }; # Get the first existing canvas with the given name # # @param name Name of the canvas # @return #Canvas, if canvas with #name exists # nil, otherwise var get = func(arg) { 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."); return wrapCanvas(_getCanvasGhost(node._g)); }; var getDesktop = func() { return Group.new(_getDesktopGhost()); };