Use [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval), and [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout)
63 | Alternatively, if your image is accessible via a URL, you can simply call
64 | rocky.gbitmap_sequence_create(url).
65 | Have a look at the code of this example.
66 |
67 |
68 |
69 | The JS version of this API differs slightly from the
70 | C-Version.
71 | In particular, use graphics_draw_bitmap_sequence(ctx, sequence, point) to draw an image sequence.
72 |
Scroll down and explore Pebble's classic C API directly in your browser by clicking and dragging the highlighted elements in the code snippets!
58 |
59 |
60 |
61 |
62 |
63 |
graphics_fill_radial
64 |
graphics_fill_radial fills a circle clockwise between angle_start and angle_end, where 0° is the top of the circle. If the difference between angle_start and angle_end is greater than 360°, a full circle will be drawn and filled. If angle_start is greater than angle_end nothing will be drawn.
graphics_draw_arc draws a line arc clockwise between angle_start and angle_end, where 0° is the top of the circle. If the difference between angle_start and angle_end is greater than 360°, a full circle will be drawn.
101 |
136 |
189 |
190 |
191 |
--------------------------------------------------------------------------------
/examples/interactive/js/TangleKit/BVTouchable.js:
--------------------------------------------------------------------------------
1 | //
2 | // BVTouchable.js
3 | // ExplorableExplanations
4 | //
5 | // Created by Bret Victor on 3/10/11.
6 | // (c) 2011 Bret Victor. MIT open-source license.
7 | //
8 |
9 | (function () {
10 |
11 | var BVTouchable = this.BVTouchable = new Class ({
12 |
13 | initialize: function (el, delegate) {
14 | this.element = el;
15 | this.delegate = delegate;
16 | this.setTouchable(true);
17 | },
18 |
19 |
20 | //----------------------------------------------------------------------------------
21 | //
22 | // touches
23 | //
24 |
25 | setTouchable: function (isTouchable) {
26 | if (this.touchable === isTouchable) { return; }
27 | this.touchable = isTouchable;
28 | this.element.style.pointerEvents = (this.touchable || this.hoverable) ? "auto" : "none";
29 |
30 | if (isTouchable) {
31 | if (!this._mouseBound) {
32 | this._mouseBound = {
33 | mouseDown: this._mouseDown.bind(this),
34 | mouseMove: this._mouseMove.bind(this),
35 | mouseUp: this._mouseUp.bind(this),
36 | touchStart: this._touchStart.bind(this),
37 | touchMove: this._touchMove.bind(this),
38 | touchEnd: this._touchEnd.bind(this),
39 | touchCancel: this._touchCancel.bind(this)
40 | };
41 | }
42 | this.element.addEvent("mousedown", this._mouseBound.mouseDown);
43 | this.element.addEvent("touchstart", this._mouseBound.touchStart);
44 | }
45 | else {
46 | this.element.removeEvents("mousedown");
47 | this.element.removeEvents("touchstart");
48 | }
49 | },
50 |
51 | touchDidGoDown: function (touches) { this.delegate.touchDidGoDown(touches); },
52 | touchDidMove: function (touches) { this.delegate.touchDidMove(touches); },
53 | touchDidGoUp: function (touches) { this.delegate.touchDidGoUp(touches); },
54 |
55 | _mouseDown: function (event) {
56 | event.stop();
57 | this.element.getDocument().addEvents({
58 | mousemove: this._mouseBound.mouseMove,
59 | mouseup: this._mouseBound.mouseUp
60 | });
61 |
62 | this.touches = new BVTouches(event);
63 | this.touchDidGoDown(this.touches);
64 | },
65 |
66 | _mouseMove: function (event) {
67 | event.stop();
68 | this.touches._updateWithEvent(event);
69 | this.touchDidMove(this.touches);
70 | },
71 |
72 | _mouseUp: function (event) {
73 | event.stop();
74 | this.touches._goUpWithEvent(event);
75 | this.touchDidGoUp(this.touches);
76 |
77 | delete this.touches;
78 | this.element.getDocument().removeEvents({
79 | mousemove: this._mouseBound.mouseMove,
80 | mouseup: this._mouseBound.mouseUp
81 | });
82 | },
83 |
84 | _touchStart: function (event) {
85 | event.stop();
86 | if (this.touches || event.length > 1) { this._touchCancel(event); return; } // only-single touch for now
87 |
88 | this.element.getDocument().addEvents({
89 | touchmove: this._mouseBound.touchMove,
90 | touchend: this._mouseBound.touchEnd,
91 | touchcancel: this._mouseBound.touchCancel
92 | });
93 |
94 | this.touches = new BVTouches(event);
95 | this.touchDidGoDown(this.touches);
96 | },
97 |
98 | _touchMove: function (event) {
99 | event.stop();
100 | if (!this.touches) { return; }
101 |
102 | this.touches._updateWithEvent(event);
103 | this.touchDidMove(this.touches);
104 | },
105 |
106 | _touchEnd: function (event) {
107 | event.stop();
108 | if (!this.touches) { return; }
109 |
110 | this.touches._goUpWithEvent(event);
111 | this.touchDidGoUp(this.touches);
112 |
113 | delete this.touches;
114 | this.element.getDocument().removeEvents({
115 | touchmove: this._mouseBound.touchMove,
116 | touchend: this._mouseBound.touchEnd,
117 | touchcancel: this._mouseBound.touchCancel
118 | });
119 | },
120 |
121 | _touchCancel: function (event) {
122 | this._touchEnd(event);
123 | }
124 |
125 | });
126 |
127 |
128 | //====================================================================================
129 | //
130 | // BVTouches
131 | //
132 |
133 | var BVTouches = this.BVTouches = new Class({
134 |
135 | initialize: function (event) {
136 | this.globalPoint = { x:event.page.x, y:-event.page.y };
137 | this.translation = { x:0, y:0 };
138 | this.deltaTranslation = { x:0, y:0 };
139 | this.velocity = { x:0, y:0 };
140 | this.count = 1;
141 | this.event = event;
142 | this.timestamp = event.event.timeStamp;
143 | this.downTimestamp = this.timestamp;
144 | },
145 |
146 | _updateWithEvent: function (event, isRemoving) {
147 | this.event = event;
148 | if (!isRemoving) {
149 | var dx = event.page.x - this.globalPoint.x; // todo, transform to local coordinate space?
150 | var dy = -event.page.y - this.globalPoint.y;
151 | this.translation.x += dx;
152 | this.translation.y += dy;
153 | this.deltaTranslation.x += dx;
154 | this.deltaTranslation.y += dy;
155 | this.globalPoint.x = event.page.x;
156 | this.globalPoint.y = -event.page.y;
157 | }
158 |
159 | var timestamp = event.event.timeStamp;
160 | var dt = timestamp - this.timestamp;
161 | var isSamePoint = isRemoving || (dx === 0 && dy === 0);
162 | var isStopped = (isSamePoint && dt > 150);
163 |
164 | this.velocity.x = isStopped ? 0 : (isSamePoint || dt === 0) ? this.velocity.x : (dx / dt * 1000);
165 | this.velocity.y = isStopped ? 0 : (isSamePoint || dt === 0) ? this.velocity.y : (dy / dt * 1000);
166 | this.timestamp = timestamp;
167 | },
168 |
169 | _goUpWithEvent: function (event) {
170 | this._updateWithEvent(event, true);
171 | this.count = 0;
172 |
173 | var didMove = Math.abs(this.translation.x) > 10 || Math.abs(this.translation.y) > 10;
174 | var wasMoving = Math.abs(this.velocity.x) > 400 || Math.abs(this.velocity.y) > 400;
175 | this.wasTap = !didMove && !wasMoving && (this.getTimeSinceGoingDown() < 300);
176 | },
177 |
178 | getTimeSinceGoingDown: function () {
179 | return this.timestamp - this.downTimestamp;
180 | },
181 |
182 | resetDeltaTranslation: function () {
183 | this.deltaTranslation.x = 0;
184 | this.deltaTranslation.y = 0;
185 | }
186 |
187 | });
188 |
189 |
190 | //====================================================================================
191 |
192 | })();
193 |
--------------------------------------------------------------------------------
/examples/interactive/js/TangleKit/TKLogarithmicAdjustableNumber.js:
--------------------------------------------------------------------------------
1 | //
2 | // TKLogarithmicAdjustableNumber.js
3 | // A different sort of AdjustableNumber slider for Tangle.js
4 | //
5 | // Created by Tom Counsell on 5 August 2013
6 | // (c) 2013 Tom Counsell. MIT open-source license.
7 | //
8 |
9 |
10 | (function () {
11 |
12 |
13 |
14 |
15 | //----------------------------------------------------------
16 | //
17 | // TKLogarithmicAdjustableNumber
18 | //
19 | // Drag a number to adjust. The relationship between movement and the number is logarithmic
20 | //
21 | // Attributes: data-min (optional): minimum value
22 | // data-max (optional): maximum value
23 | // data-step (optional): granularity of adjustment (can be fractional)
24 |
25 | var isAnyAdjustableNumberDragging = false; // hack for dragging one value over another one
26 |
27 | Tangle.classes.TKLogarithmicAdjustableNumber = {
28 |
29 | initialize: function (element, options, tangle, variable) {
30 | this.element = element;
31 | this.tangle = tangle;
32 | this.variable = variable;
33 |
34 | this.min = (options.min !== undefined) ? parseFloat(options.min) : 1;
35 | this.max = (options.max !== undefined) ? parseFloat(options.max) : 10;
36 | this.step = (options.step !== undefined) ? parseFloat(options.step) : 1;
37 |
38 | this.initializeHover();
39 | this.initializeHelp();
40 | this.initializeDrag();
41 | },
42 |
43 |
44 | // hover
45 |
46 | initializeHover: function () {
47 | this.isHovering = false;
48 | this.element.addEvent("mouseenter", (function () { this.isHovering = true; this.updateRolloverEffects(); }).bind(this));
49 | this.element.addEvent("mouseleave", (function () { this.isHovering = false; this.updateRolloverEffects(); }).bind(this));
50 | },
51 |
52 | updateRolloverEffects: function () {
53 | this.updateStyle();
54 | this.updateCursor();
55 | this.updateHelp();
56 | },
57 |
58 | isActive: function () {
59 | return this.isDragging || (this.isHovering && !isAnyAdjustableNumberDragging);
60 | },
61 |
62 | updateStyle: function () {
63 | if (this.isDragging) { this.element.addClass("TKAdjustableNumberDown"); }
64 | else { this.element.removeClass("TKAdjustableNumberDown"); }
65 |
66 | if (!this.isDragging && this.isActive()) { this.element.addClass("TKAdjustableNumberHover"); }
67 | else { this.element.removeClass("TKAdjustableNumberHover"); }
68 | },
69 |
70 | updateCursor: function () {
71 | var body = document.getElement("body");
72 | if (this.isActive()) { body.addClass("TKCursorDragHorizontal"); }
73 | else { body.removeClass("TKCursorDragHorizontal"); }
74 | },
75 |
76 |
77 | // help
78 |
79 | initializeHelp: function () {
80 | this.helpElement = (new Element("div", { "class": "TKAdjustableNumberHelp" })).inject(this.element, "top");
81 | this.helpElement.setStyle("display", "none");
82 | this.helpElement.set("text", "drag");
83 | },
84 |
85 | updateHelp: function () {
86 | var size = this.element.getSize();
87 | var top = -size.y + 7;
88 | var left = Math.round(0.5 * (size.x - 20));
89 | var display = (this.isHovering && !isAnyAdjustableNumberDragging) ? "block" : "none";
90 | this.helpElement.setStyles({ left:left, top:top, display:display });
91 | },
92 |
93 |
94 | // drag
95 |
96 | initializeDrag: function () {
97 | this.isDragging = false;
98 | new BVTouchable(this.element, this);
99 | },
100 |
101 | touchDidGoDown: function (touches) {
102 | this.valueAtMouseDown = this.tangle.getValue(this.variable);
103 | this.isDragging = true;
104 | isAnyAdjustableNumberDragging = true;
105 | this.updateRolloverEffects();
106 | this.updateStyle();
107 | },
108 |
109 | touchDidMove: function (touches) {
110 | var logarithmicShift = touches.translation.x * Math.pow(10,Math.abs(touches.translation.x)/100) / 5;
111 | var value = this.valueAtMouseDown + (logarithmicShift * this.step);
112 | value = ((value / this.step).round() * this.step).limit(this.min, this.max);
113 | this.tangle.setValue(this.variable, value);
114 | this.updateHelp();
115 | },
116 |
117 | touchDidGoUp: function (touches) {
118 | this.isDragging = false;
119 | isAnyAdjustableNumberDragging = false;
120 | this.updateRolloverEffects();
121 | this.updateStyle();
122 | this.helpElement.setStyle("display", touches.wasTap ? "block" : "none");
123 | }
124 | };
125 | })();
126 |
127 |
--------------------------------------------------------------------------------
/examples/interactive/js/TangleKit/Tangle.js:
--------------------------------------------------------------------------------
1 | //
2 | // Tangle.js
3 | // Tangle 0.1.0
4 | //
5 | // Created by Bret Victor on 5/2/10.
6 | // (c) 2011 Bret Victor. MIT open-source license.
7 | //
8 | // ------ model ------
9 | //
10 | // var tangle = new Tangle(rootElement, model);
11 | // tangle.setModel(model);
12 | //
13 | // ------ variables ------
14 | //
15 | // var value = tangle.getValue(variableName);
16 | // tangle.setValue(variableName, value);
17 | // tangle.setValues({ variableName:value, variableName:value });
18 | //
19 | // ------ UI components ------
20 | //
21 | // Tangle.classes.myClass = {
22 | // initialize: function (element, options, tangle, variable) { ... },
23 | // update: function (element, value) { ... }
24 | // };
25 | // Tangle.formats.myFormat = function (value) { return "..."; };
26 | //
27 |
28 | var Tangle = this.Tangle = function (rootElement, modelClass) {
29 |
30 | var tangle = this;
31 | tangle.element = rootElement;
32 | tangle.setModel = setModel;
33 | tangle.getValue = getValue;
34 | tangle.setValue = setValue;
35 | tangle.setValues = setValues;
36 |
37 | var _model;
38 | var _nextSetterID = 0;
39 | var _setterInfosByVariableName = {}; // { varName: { setterID:7, setter:function (v) { } }, ... }
40 | var _varargConstructorsByArgCount = [];
41 |
42 |
43 | //----------------------------------------------------------
44 | //
45 | // construct
46 |
47 | initializeElements();
48 | setModel(modelClass);
49 | return tangle;
50 |
51 |
52 | //----------------------------------------------------------
53 | //
54 | // elements
55 |
56 | function initializeElements() {
57 | var elements = rootElement.getElementsByTagName("*");
58 | var interestingElements = [];
59 |
60 | // build a list of elements with class or data-var attributes
61 |
62 | for (var i = 0, length = elements.length; i < length; i++) {
63 | var element = elements[i];
64 | if (element.getAttribute("class") || element.getAttribute("data-var")) {
65 | interestingElements.push(element);
66 | }
67 | }
68 |
69 | // initialize interesting elements in this list. (Can't traverse "elements"
70 | // directly, because elements is "live", and views that change the node tree
71 | // will change elements mid-traversal.)
72 |
73 | for (var i = 0, length = interestingElements.length; i < length; i++) {
74 | var element = interestingElements[i];
75 |
76 | var varNames = null;
77 | var varAttribute = element.getAttribute("data-var");
78 | if (varAttribute) { varNames = varAttribute.split(" "); }
79 |
80 | var views = null;
81 | var classAttribute = element.getAttribute("class");
82 | if (classAttribute) {
83 | var classNames = classAttribute.split(" ");
84 | views = getViewsForElement(element, classNames, varNames);
85 | }
86 |
87 | if (!varNames) { continue; }
88 |
89 | var didAddSetter = false;
90 | if (views) {
91 | for (var j = 0; j < views.length; j++) {
92 | if (!views[j].update) { continue; }
93 | addViewSettersForElement(element, varNames, views[j]);
94 | didAddSetter = true;
95 | }
96 | }
97 |
98 | if (!didAddSetter) {
99 | var formatAttribute = element.getAttribute("data-format");
100 | var formatter = getFormatterForFormat(formatAttribute, varNames);
101 | addFormatSettersForElement(element, varNames, formatter);
102 | }
103 | }
104 | }
105 |
106 | function getViewsForElement(element, classNames, varNames) { // initialize classes
107 | var views = null;
108 |
109 | for (var i = 0, length = classNames.length; i < length; i++) {
110 | var clas = Tangle.classes[classNames[i]];
111 | if (!clas) { continue; }
112 |
113 | var options = getOptionsForElement(element);
114 | var args = [ element, options, tangle ];
115 | if (varNames) { args = args.concat(varNames); }
116 |
117 | var view = constructClass(clas, args);
118 |
119 | if (!views) { views = []; }
120 | views.push(view);
121 | }
122 |
123 | return views;
124 | }
125 |
126 | function getOptionsForElement(element) { // might use dataset someday
127 | var options = {};
128 |
129 | var attributes = element.attributes;
130 | var regexp = /^data-[\w\-]+$/;
131 |
132 | for (var i = 0, length = attributes.length; i < length; i++) {
133 | var attr = attributes[i];
134 | var attrName = attr.name;
135 | if (!attrName || !regexp.test(attrName)) { continue; }
136 |
137 | options[attrName.substr(5)] = attr.value;
138 | }
139 |
140 | return options;
141 | }
142 |
143 | function constructClass(clas, args) {
144 | if (typeof clas !== "function") { // class is prototype object
145 | var View = function () { };
146 | View.prototype = clas;
147 | var view = new View();
148 | if (view.initialize) { view.initialize.apply(view,args); }
149 | return view;
150 | }
151 | else { // class is constructor function, which we need to "new" with varargs (but no built-in way to do so)
152 | var ctor = _varargConstructorsByArgCount[args.length];
153 | if (!ctor) {
154 | var ctorArgs = [];
155 | for (var i = 0; i < args.length; i++) { ctorArgs.push("args[" + i + "]"); }
156 | var ctorString = "(function (clas,args) { return new clas(" + ctorArgs.join(",") + "); })";
157 | ctor = eval(ctorString); // nasty
158 | _varargConstructorsByArgCount[args.length] = ctor; // but cached
159 | }
160 | return ctor(clas,args);
161 | }
162 | }
163 |
164 |
165 | //----------------------------------------------------------
166 | //
167 | // formatters
168 |
169 | function getFormatterForFormat(formatAttribute, varNames) {
170 | if (!formatAttribute) { formatAttribute = "default"; }
171 |
172 | var formatter = getFormatterForCustomFormat(formatAttribute, varNames);
173 | if (!formatter) { formatter = getFormatterForSprintfFormat(formatAttribute, varNames); }
174 | if (!formatter) { log("Tangle: unknown format: " + formatAttribute); formatter = getFormatterForFormat(null,varNames); }
175 |
176 | return formatter;
177 | }
178 |
179 | function getFormatterForCustomFormat(formatAttribute, varNames) {
180 | var components = formatAttribute.split(" ");
181 | var formatName = components[0];
182 | if (!formatName) { return null; }
183 |
184 | var format = Tangle.formats[formatName];
185 | if (!format) { return null; }
186 |
187 | var formatter;
188 | var params = components.slice(1);
189 |
190 | if (varNames.length <= 1 && params.length === 0) { // one variable, no params
191 | formatter = format;
192 | }
193 | else if (varNames.length <= 1) { // one variable with params
194 | formatter = function (value) {
195 | var args = [ value ].concat(params);
196 | return format.apply(null, args);
197 | };
198 | }
199 | else { // multiple variables
200 | formatter = function () {
201 | var values = getValuesForVariables(varNames);
202 | var args = values.concat(params);
203 | return format.apply(null, args);
204 | };
205 | }
206 | return formatter;
207 | }
208 |
209 | function getFormatterForSprintfFormat(formatAttribute, varNames) {
210 | if (!sprintf || !formatAttribute.test(/\%/)) { return null; }
211 |
212 | var formatter;
213 | if (varNames.length <= 1) { // one variable
214 | formatter = function (value) {
215 | return sprintf(formatAttribute, value);
216 | };
217 | }
218 | else {
219 | formatter = function (value) { // multiple variables
220 | var values = getValuesForVariables(varNames);
221 | var args = [ formatAttribute ].concat(values);
222 | return sprintf.apply(null, args);
223 | };
224 | }
225 | return formatter;
226 | }
227 |
228 |
229 | //----------------------------------------------------------
230 | //
231 | // setters
232 |
233 | function addViewSettersForElement(element, varNames, view) { // element has a class with an update method
234 | var setter;
235 | if (varNames.length <= 1) {
236 | setter = function (value) { view.update(element, value); };
237 | }
238 | else {
239 | setter = function () {
240 | var values = getValuesForVariables(varNames);
241 | var args = [ element ].concat(values);
242 | view.update.apply(view,args);
243 | };
244 | }
245 |
246 | addSetterForVariables(setter, varNames);
247 | }
248 |
249 | function addFormatSettersForElement(element, varNames, formatter) { // tangle is injecting a formatted value itself
250 | var span = null;
251 | var setter = function (value) {
252 | if (!span) {
253 | span = document.createElement("span");
254 | element.insertBefore(span, element.firstChild);
255 | }
256 | span.innerHTML = formatter(value);
257 | };
258 |
259 | addSetterForVariables(setter, varNames);
260 | }
261 |
262 | function addSetterForVariables(setter, varNames) {
263 | var setterInfo = { setterID:_nextSetterID, setter:setter };
264 | _nextSetterID++;
265 |
266 | for (var i = 0; i < varNames.length; i++) {
267 | var varName = varNames[i];
268 | if (!_setterInfosByVariableName[varName]) { _setterInfosByVariableName[varName] = []; }
269 | _setterInfosByVariableName[varName].push(setterInfo);
270 | }
271 | }
272 |
273 | function applySettersForVariables(varNames) {
274 | var appliedSetterIDs = {}; // remember setterIDs that we've applied, so we don't call setters twice
275 |
276 | for (var i = 0, ilength = varNames.length; i < ilength; i++) {
277 | var varName = varNames[i];
278 | var setterInfos = _setterInfosByVariableName[varName];
279 | if (!setterInfos) { continue; }
280 |
281 | var value = _model[varName];
282 |
283 | for (var j = 0, jlength = setterInfos.length; j < jlength; j++) {
284 | var setterInfo = setterInfos[j];
285 | if (setterInfo.setterID in appliedSetterIDs) { continue; } // if we've already applied this setter, move on
286 | appliedSetterIDs[setterInfo.setterID] = true;
287 |
288 | setterInfo.setter(value);
289 | }
290 | }
291 | }
292 |
293 |
294 | //----------------------------------------------------------
295 | //
296 | // variables
297 |
298 | function getValue(varName) {
299 | var value = _model[varName];
300 | if (value === undefined) { log("Tangle: unknown variable: " + varName); return 0; }
301 | return value;
302 | }
303 |
304 | function setValue(varName, value) {
305 | var obj = {};
306 | obj[varName] = value;
307 | setValues(obj);
308 | }
309 |
310 | function setValues(obj) {
311 | var changedVarNames = [];
312 |
313 | for (var varName in obj) {
314 | var value = obj[varName];
315 | var oldValue = _model[varName];
316 | if (oldValue === undefined) { log("Tangle: setting unknown variable: " + varName); continue; }
317 | if (oldValue === value) { continue; } // don't update if new value is the same
318 |
319 | _model[varName] = value;
320 | changedVarNames.push(varName);
321 | }
322 |
323 | if (changedVarNames.length) {
324 | applySettersForVariables(changedVarNames);
325 | updateModel();
326 | }
327 | }
328 |
329 | function getValuesForVariables(varNames) {
330 | var values = [];
331 | for (var i = 0, length = varNames.length; i < length; i++) {
332 | values.push(getValue(varNames[i]));
333 | }
334 | return values;
335 | }
336 |
337 |
338 | //----------------------------------------------------------
339 | //
340 | // model
341 |
342 | function setModel(modelClass) {
343 | var ModelClass = function () { };
344 | ModelClass.prototype = modelClass;
345 | _model = new ModelClass;
346 |
347 | updateModel(true); // initialize and update
348 | }
349 |
350 | function updateModel(shouldInitialize) {
351 | var ShadowModel = function () {}; // make a shadow object, so we can see exactly which properties changed
352 | ShadowModel.prototype = _model;
353 | var shadowModel = new ShadowModel;
354 |
355 | if (shouldInitialize) { shadowModel.initialize(); }
356 | shadowModel.update();
357 |
358 | var changedVarNames = [];
359 | for (var varName in shadowModel) {
360 | if (!shadowModel.hasOwnProperty(varName)) { continue; }
361 | if (_model[varName] === shadowModel[varName]) { continue; }
362 |
363 | _model[varName] = shadowModel[varName];
364 | changedVarNames.push(varName);
365 | }
366 |
367 | applySettersForVariables(changedVarNames);
368 | }
369 |
370 |
371 | //----------------------------------------------------------
372 | //
373 | // debug
374 |
375 | function log (msg) {
376 | if (window.console) { window.console.log(msg); }
377 | }
378 |
379 | }; // end of Tangle
380 |
381 |
382 | //----------------------------------------------------------
383 | //
384 | // components
385 |
386 | Tangle.classes = {};
387 | Tangle.formats = {};
388 |
389 | Tangle.formats["default"] = function (value) { return "" + value; };
390 | Tangle.formats["none"] = function (value) { return ""; };
391 |
392 |
--------------------------------------------------------------------------------
/examples/interactive/js/TangleKit/sprintf.js:
--------------------------------------------------------------------------------
1 | /**
2 | sprintf() for JavaScript 0.7-beta1
3 | http://www.diveintojavascript.com/projects/javascript-sprintf
4 |
5 | Copyright (c) Alexandru Marasteanu
6 | All rights reserved.
7 |
8 | Redistribution and use in source and binary forms, with or without
9 | modification, are permitted provided that the following conditions are met:
10 | * Redistributions of source code must retain the above copyright
11 | notice, this list of conditions and the following disclaimer.
12 | * Redistributions in binary form must reproduce the above copyright
13 | notice, this list of conditions and the following disclaimer in the
14 | documentation and/or other materials provided with the distribution.
15 | * Neither the name of sprintf() for JavaScript nor the
16 | names of its contributors may be used to endorse or promote products
17 | derived from this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
30 |
31 | Changelog:
32 | 2010.09.06 - 0.7-beta1
33 | - features: vsprintf, support for named placeholders
34 | - enhancements: format cache, reduced global namespace pollution
35 |
36 | 2010.05.22 - 0.6:
37 | - reverted to 0.4 and fixed the bug regarding the sign of the number 0
38 | Note:
39 | Thanks to Raphael Pigulla (http://www.n3rd.org/)
40 | who warned me about a bug in 0.5, I discovered that the last update was
41 | a regress. I appologize for that.
42 |
43 | 2010.05.09 - 0.5:
44 | - bug fix: 0 is now preceeded with a + sign
45 | - bug fix: the sign was not at the right position on padded results (Kamal Abdali)
46 | - switched from GPL to BSD license
47 |
48 | 2007.10.21 - 0.4:
49 | - unit test and patch (David Baird)
50 |
51 | 2007.09.17 - 0.3:
52 | - bug fix: no longer throws exception on empty paramenters (Hans Pufal)
53 |
54 | 2007.09.11 - 0.2:
55 | - feature: added argument swapping
56 |
57 | 2007.04.03 - 0.1:
58 | - initial release
59 | **/
60 |
61 | var sprintf = (function() {
62 | function get_type(variable) {
63 | return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
64 | }
65 | function str_repeat(input, multiplier) {
66 | for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
67 | return output.join('');
68 | }
69 |
70 | var str_format = function() {
71 | if (!str_format.cache.hasOwnProperty(arguments[0])) {
72 | str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
73 | }
74 | return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
75 | };
76 |
77 | str_format.format = function(parse_tree, argv) {
78 | var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
79 | for (i = 0; i < tree_length; i++) {
80 | node_type = get_type(parse_tree[i]);
81 | if (node_type === 'string') {
82 | output.push(parse_tree[i]);
83 | }
84 | else if (node_type === 'array') {
85 | match = parse_tree[i]; // convenience purposes only
86 | if (match[2]) { // keyword argument
87 | arg = argv[cursor];
88 | for (k = 0; k < match[2].length; k++) {
89 | if (!arg.hasOwnProperty(match[2][k])) {
90 | throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
91 | }
92 | arg = arg[match[2][k]];
93 | }
94 | }
95 | else if (match[1]) { // positional argument (explicit)
96 | arg = argv[match[1]];
97 | }
98 | else { // positional argument (implicit)
99 | arg = argv[cursor++];
100 | }
101 |
102 | if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
103 | throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
104 | }
105 | switch (match[8]) {
106 | case 'b': arg = arg.toString(2); break;
107 | case 'c': arg = String.fromCharCode(arg); break;
108 | case 'd': arg = parseInt(arg, 10); break;
109 | case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
110 | case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
111 | case 'o': arg = arg.toString(8); break;
112 | case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
113 | case 'u': arg = Math.abs(arg); break;
114 | case 'x': arg = arg.toString(16); break;
115 | case 'X': arg = arg.toString(16).toUpperCase(); break;
116 | }
117 | arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
118 | pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
119 | pad_length = match[6] - String(arg).length;
120 | pad = match[6] ? str_repeat(pad_character, pad_length) : '';
121 | output.push(match[5] ? arg + pad : pad + arg);
122 | }
123 | }
124 | return output.join('');
125 | };
126 |
127 | str_format.cache = {};
128 |
129 | str_format.parse = function(fmt) {
130 | var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
131 | while (_fmt) {
132 | if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
133 | parse_tree.push(match[0]);
134 | }
135 | else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
136 | parse_tree.push('%');
137 | }
138 | else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
139 | if (match[2]) {
140 | arg_names |= 1;
141 | var field_list = [], replacement_field = match[2], field_match = [];
142 | if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
143 | field_list.push(field_match[1]);
144 | while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
145 | if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
146 | field_list.push(field_match[1]);
147 | }
148 | else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
149 | field_list.push(field_match[1]);
150 | }
151 | else {
152 | throw('[sprintf] huh?');
153 | }
154 | }
155 | }
156 | else {
157 | throw('[sprintf] huh?');
158 | }
159 | match[2] = field_list;
160 | }
161 | else {
162 | arg_names |= 2;
163 | }
164 | if (arg_names === 3) {
165 | throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
166 | }
167 | parse_tree.push(match);
168 | }
169 | else {
170 | throw('[sprintf] huh?');
171 | }
172 | _fmt = _fmt.substring(match[0].length);
173 | }
174 | return parse_tree;
175 | };
176 |
177 | return str_format;
178 | })();
179 |
180 | var vsprintf = function(fmt, argv) {
181 | argv.unshift(fmt);
182 | return sprintf.apply(null, argv);
183 | };
184 |
--------------------------------------------------------------------------------
/examples/interactive/js/tangle-rocky.js:
--------------------------------------------------------------------------------
1 | /*global Rocky:false, Tangle:false*/
2 |
3 | Rocky.bindTangle = function(args) {
4 | if (typeof (args) === 'undefined') {
5 | args = {};
6 | }
7 |
8 | var element = args.element;
9 | if (typeof (element) === 'undefined') {
10 | var target = document.documentElement; // start at the root element
11 | // find last HTMLElement child node
12 | while (target.childNodes.length && target.lastChild.nodeType === 1) {
13 | target = target.lastChild;
14 | }
15 | element = target.parentNode;
16 | }
17 |
18 | var canvas = args.canvas;
19 | if (typeof (canvas) === 'undefined') {
20 | canvas = element.querySelector('canvas');
21 | }
22 |
23 | var rocky = Rocky.bindCanvas(canvas);
24 |
25 | var tangle = new Tangle(element, {
26 | initialize: function() {
27 | var subElements = element.querySelectorAll('[data-var][data-init]');
28 | for (var i = 0; i < subElements.length; i++) {
29 | var subElement = subElements[i];
30 | var name = subElement.attributes['data-var'].value;
31 | var stringValue = subElement.attributes['data-init'].value;
32 |
33 | // this code can only handle numbers
34 | // as soon as we have more complex tangles,
35 | // we need a more capable implementation
36 | this[name] = parseFloat(stringValue);
37 | }
38 | if (typeof (args.initialize) === 'function') {
39 | args.initialize.call(this, element);
40 | }
41 | },
42 | update: function() {
43 | rocky.mark_dirty();
44 | }
45 | });
46 |
47 | rocky.update_proc = function(ctx, bounds) {
48 | args.update_proc(rocky, tangle, ctx, bounds);
49 | };
50 | rocky.mark_dirty();
51 | };
52 |
--------------------------------------------------------------------------------
/examples/motionEvents/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Compass
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
Compass
41 |
42 | This example demonstrates how one can use DeviceMotionEvent
43 | to mimic Pebble's compass application.
44 | Works best on a mobile device!
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
What's going on?
54 |
55 | This example is a simpler version of Pebble's compass application.
56 | The code subscribes to device motion events
57 | where available and uses some animated fake heading where it doesn't exist (e.g. on your desktop).
58 |
59 |
60 | Although the actual draw code, e.g. draw_ticks() is an interesting example of Rocky's drawing
61 | routines, the focus of this example is to show how one can leverage today's web APIs
62 | (here: window.addEventListener('deviceorientation', yourFunction)) to mix them with Rocky.js.
63 |
64 |
65 |
68 |
69 |
70 |
71 |
168 |
169 |
--------------------------------------------------------------------------------
/examples/readme.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | This folder contains a variety of examples designed to help you understand some of the things you can accomplish with Rocky.js.
4 |
5 | - [Simple Example](simple/index.html) – Minimal code example that binds Rocky.js to a canvas and draws some primitives.
6 | - [Interactive Documentation](interactive/index.html) – Interactive documentation for Pebble's C-API.
7 | - [TicToc](tictoc/index.html) – Implementation of a custom/extended API and basic watchface.
8 | - [Vector Graphics](pdc/index.html) – Draws and modifies vector graphics.
9 | - [GPath](gpath/index.html) – Shows how to use APIs around `GPath`.
10 | - [APNG/GIF](apng/index.html) – Renders an animatd GIF/animated PNG.
11 | - [Text](text/index.html) – Shows how to draw text and use fonts.
12 | - [Compass](motionEvents/index.html) – Uses DeviceMotionEvent to implement a simple compass app.
13 | - [Community Examples](community.html) - Additional examples built by community members.
14 |
--------------------------------------------------------------------------------
/examples/simple/butkus.json:
--------------------------------------------------------------------------------
1 | {
2 | "input": {
3 | "memoryFormat": "smallest",
4 | "original": "some.url",
5 | "storageFormat": "png"
6 | },
7 | "output": {
8 | "data": "iVBORw0KGgoAAAANSUhEUgAAABcAAAAZBAMAAAAoDqjjAAAAFVBMVEX///9VVQD/qlWqqlUAAAD//6qqVQCmRoadAAAAm0lEQVR42m3PwRECMQgFUOggkAr40QIIHexYgc4WYP9NGMh6Uk68TJIPREQi0ugqAWCXGFm2oQW0HzBm9tP+QA/guDDqUd8IxeNA94IzbjAuUCMJb/QdYURYnVbqvTJl43wmesugcb7WoByNWGbChWdbl3y9sYmeGA5VC3QkQlV7QC3/cjXILKytFxRW6+mGVCa/w8Pf12hSle0Hnx4abYUujacAAAAASUVORK5CYII=",
9 | "gbitmapFormat": 4,
10 | "outputFormat": "png"
11 | },
12 | "success": true
13 | }
--------------------------------------------------------------------------------
/examples/simple/butkus.pbi8:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pebble/rockyjs/239b0c3f4916178dd1584b234ed3fad9d5c06043/examples/simple/butkus.pbi8
--------------------------------------------------------------------------------
/examples/simple/butkus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pebble/rockyjs/239b0c3f4916178dd1584b234ed3fad9d5c06043/examples/simple/butkus.png
--------------------------------------------------------------------------------
/examples/simple/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Simple Example
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
Simple Example
41 |
42 | This is the example as seen at JSConf (YouTube), and is designed to help you understand how to get started with RockyJS.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
What's going on?
52 |
53 | This example demonstrates how to bind RockyJS to a canvas, and update the output at a regular interval. More information about the key RockyJS API calls for this example can be found below.
54 |
58 | This method creates an instance of Rocky, and binds it to a canvas object. Once we have a Rocky object, we can use it to invoke a subset of Pebble's C API, and have the results rendered on the canvas.
59 |
63 | This method pollutes the global namespace with a subset of Pebble's C API. This allows you to invoke (the implemented) functions from Pebble's C API without having to preface every call with rocky.functionName.
64 |
73 | Rocky's update_proc is a method that will be invoked everytime the canvas is marked as dirty with the mark_dirty method. The update_proc takes two parameters: ctx - the graphics context, and bounds - the bounds of the window.
74 |
42 | This example demostrates the APIs around text rendering and fonts.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
What's going on?
52 |
53 | Similar to the simple example it binds Rocky.js to a canvas, and update the output at a regular interval.
54 |
55 |
56 | This example uses fonts_get_system_font() to load one of Pebble's system fonts,
57 | as well as fonts_load_custom_font() and fonts_load_custom_font_with_data() to load custom fonts.
58 | Read more about custom fonts at the developer documentation page.
59 |
44 | This is a sample implementation of Pebble's TicToc watchface written entierly in JavaScript. This example, as seen at JSConf (YouTube), was run both in the browser using Rocky.js, and on a physical watch running modified firmware that included the Espruino runtime, and the API implemented in rocky-extended.js.
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
What's going on?
53 |
54 | This example demonstrates how you can create wrappers on top of Pebble's existing C-Style API to extend functionality and write code that "looks" more like JavaScript. There are 2 important files to look at to fully understand this example.
55 |
59 | The code in espruino-compat.js wraps and extends the available functionality to include a Window class similar to Pebble's standard Window object, and a TimerService similar to Pebble's TickTimerService.
60 |
61 |
62 | In addition to creating new objects for the developer to use, the Window constructor extends the context parameter passed into the rocky.update_proc callback to simplify a variety of graphics related API calls.
63 |