SVGCanvas

From FlightGear wiki
Jump to navigation Jump to search

SVGCanvas is a base class to populate a canvas from SVG and animate elements easily. The source file is $FGDATA/Nasal/canvas/api/svgcanvas.nas


Usage examples

A simple usecase is a speed booklet as it can be found in the CRJ700 (versions since 2020). A SVG is used as a layout template, data is filled in by Nasal code from tables.

screenshot of spreed booklet window

A shortened code sample:

var canvas_settings = {
    "size": [1024,1024],
    "view": [1024,1024],
};
var window_size = [512,512];
var speedbooklet_svg = "speedbooklet.svg";

# class Booklet extends SVGCanvas
var Booklet =
{
    bgcolor: [0.9, 0.9, 0.9, 1], #background color for canvas

    new: func(name, svgfile, speedtable) {
        var obj = {
            parents: [me, canvas.SVGCanvas.new(name, canvas_settings)],

            # put all element IDs you wish to animate in this vector (use a SVG editor like inkscape
            # to find/edit ID strings in SVG file, the strings used here must match the IDs in the SVG file)
            svg_keys: [
                "title", "tonns", "kilogram", "weight_unit", "overweight",
                "v1_8", "vr_8", "v2_8", "vt",
                "v1_20", "vr_20", "v2_20",
                "vref_0", "vref_1", "vref_8", "vref_20", "vref_30", "vref_45",
                "page", "left", "right",
            ],

            _pageN: props.getNode("/sim/gui/speedbooklet/page",1),

             #the speedtable class is not described in detail to keep this short, it stores data for the pages of the booklet
            speedtable: speedtable, 
        };

        obj.getCanvas().setColorBackground(me.bgcolor);
        obj.loadSVG(svgfile, obj.svg_keys);
        obj.init();
        return obj;
    },

    init: func() {
        # support mouse click for the page change buttons
        me["left"].addEventListener("click", func(e) { me.prevPage(); });
        me["right"].addEventListener("click", func(e) { me.nextPage(); });

        me._pageN.setIntValue(0);
        return ;
    },

    del: func() {
        if (me._L) removelistener(me._L);
        call(canvas.SVGCanvas.del, [], me, var err = []);
        return nil;
    },
    
    nextPage: func() {
        if (me._pageN.getValue() < me.speedtable.getNumberOfPages() - 1) {
            me._pageN.increment();
            me.update();
        }
        return me;
    },

    prevPage: func() {
        if (me._pageN.getValue() > 0) {
            me._pageN.decrement();
            me.update();
        }
        return me;
    },
    
    # update elements
    update: func() {
        var number = me._pageN.getValue();
        var speeds = me.speedtable.getPage(number);
        foreach (var key; keys(speeds)) {
            if (vecindex(me.svg_keys, key) != nil)
                me.updateTextElement(key, sprintf("%d", speeds[key]));
        }
        me["overweight"].setVisible(me.speedtable.isOverMLW(speeds.weight));
        me.updateTextElement("tonns", sprintf("%d", int(speeds.weight/1000)));
        me.updateTextElement("kilogram", sprintf("%03d", math.mod(speeds.weight, 1000)));
        me.updateTextElement("page", sprintf("%2d", number + 1));
    },
};

var window_title = "Speedbooklet "~aero;
var book = Booklet.new(window_title, speedbooklet_svg, st);
book.asWindow(window_size);

Class functions

new()

new(name, settings=nil);

Create a new SVGCanvas

name
Name of the canvas.
setting
Hash of canvas settings.

Example

var myCanvas = SVGCanvas.new("mySVG");
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);

del()

Destructor. Remove window (if any) and canvas.

loadSVG()

loadSVG(file, svg_keys, options=nil);

loads SVG file and create canvas.element objects for given IDs

file
filename of SVG to load
svg_keys
Vector of id strings. For each id a member will create in the SVGCanvas object. If there is an SVG object named <id>_clip, it will be automatically setup as clip for <id>
options
Optional hash with options passed to the SVG parser (canvas.parsesvg)

Example

var myCanvas = SVGCanvas.new("mySVG");
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);

asWindow()

asWindow(window_size);

opens the canvas in a window

window_size
vector [size_x, size_y] passed to canvas.Window.new

Example

var myCanvas = SVGCanvas.new("mySVG");
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);

getPath()

wrapper for canvas.getPath()

getCanvas()

return the canvas object

getRoot()

return the top level canvas group element created by new()

updateTextElement()

updateTextElement(svgkey, text, color=nil);

update text and color of a canvas text element

svgkey
Name (ID) of the text element.
text
New value for text element
color
Optional new color. Can be either a vector e.g. [r,g,b] or a color name from canvas.colors

Semi-private class methods

The following methods are ment for creating derived classes, not to be called directly. They all return listener functions ("handlers") to be used in a setlistener() call. The idea is to easily animate canvas elements based on properties that change "irregularly" and "not too often", e.g. not every frame. For properties that change regularly and/or very often, an update function called by a timer may be more efficient. If you plan for a rather complex display with regular updates, see Canvas EFIS Framework which extends SVGCanvas.

_makeListener_showHide()

_makeListener_showHide(svgkeys, value=nil);

returns generic listener to show/hide element(s) based on property node value

svgkeys
string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.
value
optional value to trigger show(); otherwise node.value will be implicitly treated as bool

_makeListener_rotate()

_makeListener_rotate(svgkeys, factors=nil);

returns listener to set rotation of element(s) based on property node value (optionally multiplied by a factor).

svgkeys
string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.
factors
optional, number (if svgkeys is a single key) or hash of numbers {"svgkey" : factor}, missing keys will be treated as 1, e.g. rotate by property value.

Example

# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas
# note: for the rudder trim a factor -1 is used to match the animation with the property
setlistener("controls/flight/rudder-trim", me._makeListener_rotate("rudderTrim", -1), 1, 0);
setlistener("controls/flight/aileron-trim", me._makeListener_rotate("ailTrim"), 1, 0);

_makeListener_translate()

_makeListener_translate(svgkeys, fx, fy);

returns listener to set rotation of element(s) based on property node value (optionally multiplied by a factor).

svgkeys
string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.
fx
number or hash of numbers {"svgkey" : factor}, missing keys will be treated as 0 (=no op)
fy
number or hash of numbers {"svgkey" : factor}, missing keys will be treated as 0 (=no op)

Example

# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas
# two elements are animated by a single property, the translation in x direction is zero and in y direction the property is multiplied by -139.46 (which can probably be found using a SVG editor and check the size of the element)
setlistener("/surface-positions/spoiler-ob-ground-pos-norm", me._makeListener_translate(["spoilerIndL3","spoilerIndR3"], 0, -139.46), 1, 0);

_makeListener_setColor()

_makeListener_setColor(svgkeys, color_true, color_false);

returns generic listener to cange color of element(s) based on property node value

svgkeys
string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.
color_true
Color to be set if node evaluates to true, can be either a vector e.g. [r,g,b] or a color name from canvas.colors
color_false
Color to be set, if node evaluates to false, can be either a vector e.g. [r,g,b] or a color name from canvas.colors

Example

# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas
# change the color of pump symbol with the ID "xflowPump" to either yellow or white depending on the inop property
setlistener("systems/fuel/xflow-pump/inop", me._makeListener_setColor("xflowPump", me.colors["amber"], me.colors["white"]), 1, 0);

_makeListener_updateText()

_makeListener_updateText(svgkeys, format="%s", default="");

Returns a listener that calls updateTextElement(key, sprintf(format, n.getValue() or default));

svgkeys
string (single ID) or vector of stings (IDs).
format
optional format sting for sprintf
default
text to use if node.getValue() fails

Example

# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas
setlistener("/controls/autoflight/speed-select", me._makeListener_updateText("iasref.text", "%d", 0), 1, 0);