Wiki

Clone wiki

SAGE2 / SAGE2 Application API

SAGE2 Application Methods:

Method Parameters
init(data) data is an object with initialization parameters, such as x, y, width, height, and date
load(date) called when state is updated (mainly used for remote sync, date is the date
draw(date) date is the date (used to calculate t and dt for animations). Within your application, call 'refresh' if you need a redraw. If it's interactive application, you can enable 'animation' in the file "instruction.json". the default frame rate is 60fps. Maximum frame rate can be controlled by the variable maxFPS (this.maxFPS = 20.0 for instance)
startResize(date) date is the date
resize(date) date is the date. Application can trigger a resize if needed by calling this.sendResize (newWidth, newHeight)
startMove(date) date is the date
move(date) date is the date
isMaster() returns true if Display Client is marked as the master, otherwise false
broadcast(functionName, data) calls function in non-master Display clients, functionName is the function's name and data is the parameter
event(type, position, user, data, date) type is the type of event, position contains the x and y positions of the event, user contains data about the user who triggered the event, data is an object containing extra data about the event, date is the date. See event description below.
quit() called when an application is closed

Boiler-plate for a canvas application

var myApp = SAGE2_App.extend( {
    init: function(data) {
        // data: contains initialization parameters, such as `x`, `y`, `width`, `height`, and `date`
        this.SAGE2Init(<html-tag container for your app (eg. 'img', 'canvas')>, data);

        this.resizeEvents = "continuous";//see below for other options

        // initialize your variables
        this.myvalue = 5.0;
    },

    //load function allows application to begin with a particular state.  Needed for remote site collaboration.
    load: function(date) {
        //your load code here- update app based on this.state
    },

    draw: function(date) {
        // application specific 'draw'
        this.log("Draw");
    },

    resize: function(date) {
        // to do:  may be a super class resize
        // or your resize code here
        this.refresh(date); //redraw after resize
    },

    event: function(type, position, user, data, date) {
        // see event handling description below

        // may need to update state here

        // may need to redraw
        this.refresh(date);
    },

    move: function(date) {
        // this.sage2_x, this.sage2_y give x,y position of upper left corner of app in global wall coordinates
                // this.sage2_width, this.sage2_height give width and height of app in global wall coordinates
                // date: when it happened
        this.refresh(date);
       },

    quit: function() {
        // It's the end
        this.log("Done");
    }
});

Function Descriptions

Init

Draw

Resize

Load

Event

#!javascript

event: function(type, position, user, data, date) {

}
type can be any one of the following:

  • pointerPress (button down) - data.button will indicate whether it is 'left' or 'right' button that caused the event

  • pointerRelease (button up) - data.button will indicate whether it is 'left' or 'right' button that caused the event

  • pointerMove

  • pointerDoubleClick (left button double click)

  • pointerScroll (moving the wheel) - data.wheelDelta will contain the amount scrolled. If looking at a page of text, positive value would make the text go up. While negative would bring the text down.

  • keyboard (printable key events from the keyboard) - data.code is the character code, data.character is the printable character's string

  • specialKey (key events for all keys on the keyboard including Backspace, Delete, Shift, etc...) - data.code is the character code, data.state is 'up' or 'down'

  • widgetEvent (button click, slider lock, slider drag, slider release, text input enter) - data.identifier is the id of the UI element that generated the event. data.action describes the kind of event that was generated. data.text contains text in case of text input enter event.

position has x and y to give the position of the event.

user has id, label, and color to describe the user who generated the event

date object may be used for synchronizing.

Quit

External Libraries

How to write an app

instructions.json

A new application can be created by running npm run newapp in the base directory of SAGE2.

All properties are optional and are set with default values if not specified.

Property name Type Values Notes
main_script string <path> The path to the file containing the application definition
resize string 'proportional', 'free' The resize mode of the window
width int The width of the window in pixels
height int The height of the window in pixels
animation boolean unknown
sticky boolean unknown
dependencies [string] <path>, <uri> A list of dependencies of the application
load object An object passed into the load function at startup
icon string <path> The file containing an application icon
title string
version string The current version of the application
description string A short description of the purpose of your application
author string
license string The name of the license of the application
keywords [string] A list of keywords associated with the application
filetypes [string] A list of filetypes that the application can open
directory string unknown

load into app library

Widgets

How to make widgets

Context menu

How to make user interface application context menus

Inter-application communication

Inter-application communication

Example Application: Clock

var clock = SAGE2_App.extend( {
    init: function(data) {
        this.SAGE2Init("canvas", data);

        this.resizeEvents = "continuous";

        // application specific 'init'
        this.ctx = this.element.getContext("2d");
        this.minDim = Math.min(this.element.width, this.element.height);
        this.timer = 0.0;
        this.redraw = true;
        this.log("Clock created");
    },

    load: function(date) {

    },

    draw: function(date) {
        // application specific 'draw'
        // only redraw if more than 1 sec has passed
        this.timer = this.timer + this.dt;
        if(this.timer >= 1.0) {
            this.timer = 0.0;
            this.redraw = true;
        }

        if(this.redraw) {
            // clear canvas
            this.ctx.clearRect(0,0, this.element.width, this.element.height);

            this.ctx.fillStyle = "rgba(255, 255, 255, 1.0)"
            this.ctx.fillRect(0,0, this.element.width, this.element.height)

            var radius = 0.95 * this.minDim / 2;
            var centerX = this.element.width / 2;
            var centerY = this.element.height / 2;

            // outside of clock
            this.ctx.lineWidth = (3.0/100.0) * this.minDim;
            this.ctx.strokeStyle = "rgba(85, 100, 120, 1.0)";
            this.ctx.beginPath();
            this.ctx.arc(centerX, centerY, radius, 0, Math.PI*2);
            this.ctx.closePath();
            this.ctx.stroke();

            // tick marks
            var theta = 0;
            var distance = radius * 0.90; // 90% from the center
            var x = 0;
            var y = 0;

            // second dots
            this.ctx.lineWidth = (0.5/100.0) * this.minDim;
            this.ctx.strokeStyle = "rgba(20, 50, 120, 1.0)";

            for(var i=0; i<60; i++){
                // calculate theta
                theta = theta + (6 * Math.PI/180);
                // calculate x,y
                x = centerX + distance * Math.cos(theta);
                y = centerY + distance * Math.sin(theta);

                this.ctx.beginPath();
                this.ctx.arc(x, y, (1.0/100.0) * this.minDim, 0, Math.PI*2);
                this.ctx.closePath();
                this.ctx.stroke();
            }

            // hour dots
            this.ctx.lineWidth = (2.5/100.0) * this.minDim;
            this.ctx.strokeStyle = "rgba(20, 50, 120, 1.0)";

            for(var i=0; i<12; i++){
                // calculate theta
                theta = theta + (30 * Math.PI/180);
                // calculate x,y
                x = centerX + distance * Math.cos(theta);
                y = centerY + distance * Math.sin(theta);

                this.ctx.beginPath();
                this.ctx.arc(x, y, (1.0/100.0) * this.minDim, 0, Math.PI*2, true);
                this.ctx.closePath();
                this.ctx.stroke();
            }

            // second hand
            var handSize = radius * 0.80; // 80% of the radius
            var sec = date.getSeconds();

            theta = (6 * Math.PI / 180);
            x = centerX + handSize * Math.cos(sec*theta - Math.PI/2);
            y = centerY + handSize * Math.sin(sec*theta - Math.PI/2);

            this.ctx.lineWidth = (1.0/100.0) * this.minDim;
            this.ctx.strokeStyle = "rgba(70, 35, 50, 1.0)";
            this.ctx.lineCap = "round";

            this.ctx.beginPath();
            this.ctx.moveTo(x, y);
            this.ctx.lineTo(centerX, centerY);
            this.ctx.moveTo(x, y);
            this.ctx.closePath();
            this.ctx.stroke();

            // minute hand
            handSize = radius * 0.60; // 60% of the radius
            var min = date.getMinutes() + sec/60;

            theta = (6 * Math.PI / 180);
            x = centerX + handSize * Math.cos(min*theta - Math.PI/2);
            y = centerY + handSize * Math.sin(min*theta - Math.PI/2);

            this.ctx.lineWidth = (1.5/100.0) * this.minDim;
            this.ctx.strokeStyle = "rgba(70, 35, 50, 1.0)";
            this.ctx.lineCap = "round";

            this.ctx.beginPath();
            this.ctx.moveTo(x, y);
            this.ctx.lineTo(centerX, centerY);
            this.ctx.moveTo(x, y);
            this.ctx.closePath();
            this.ctx.stroke();

            // hour hand
            handSize = radius * 0.40; // 40% of the radius
            var hour = date.getHours() + min/60;

            theta = (30 * Math.PI / 180);
            x = centerX + handSize * Math.cos(hour * theta - Math.PI/2);
            y = centerY + handSize * Math.sin(hour * theta - Math.PI/2);

            this.ctx.lineWidth = (2.0/100.0) * this.minDim;
            this.ctx.strokeStyle = "rgba(70, 35, 50, 1.0)";
            this.ctx.lineCap = "round";

            this.ctx.beginPath();
            this.ctx.moveTo(x, y);
            this.ctx.lineTo(centerX, centerY);
            this.ctx.moveTo(x, y);
            this.ctx.closePath();
            this.ctx.stroke();

            this.redraw = false;
        }
    },

    resize: function(date) {
        this.minDim = Math.min(this.element.width, this.element.height);
        this.redraw = true;

        this.refresh(date);
    },

    event: function(type, position, user, data, date) {
        // this.refresh(date);
    },

    quit: function() {
        // done
    }
});

Updated