Wiki
Clone wikiSAGE2 / 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) { }
-
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
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