├── .eslintrc ├── .gitignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── docs └── readme.md ├── examples ├── apng │ ├── banana.gif │ ├── banana.png │ └── index.html ├── common │ └── css │ │ └── style.css ├── community.html ├── gpath │ └── index.html ├── interactive │ ├── css │ │ └── TangleKit.css │ ├── index.html │ └── js │ │ ├── TangleKit │ │ ├── BVTouchable.js │ │ ├── TKLogarithmicAdjustableNumber.js │ │ ├── Tangle.js │ │ ├── TangleKit.js │ │ ├── mootools.js │ │ └── sprintf.js │ │ └── tangle-rocky.js ├── motionEvents │ └── index.html ├── pdc │ └── index.html ├── readme.md ├── simple │ ├── butkus.json │ ├── butkus.pbi8 │ ├── butkus.png │ └── index.html ├── text │ └── index.html └── tictoc │ ├── index.html │ └── js │ ├── rocky-extended.js │ └── tictoc.js ├── html ├── css │ ├── bootstrap.min.css │ └── style.css ├── img │ ├── basaltLight.png │ └── forkBanner.png ├── markdown │ └── template.html └── misc │ └── githubBanner.html ├── package.json ├── readme.md ├── src ├── html-binding.js ├── symbols-generated.js ├── symbols-manual.js └── transpiled.js ├── tasks ├── dist.js └── utils.js └── test ├── generated └── graphicsTypes.js └── manual ├── apng.js ├── gbitmap.js ├── gfont.js ├── gpath.js ├── graphicsTypes.js └── pdc.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "pebble", 3 | "rules": { 4 | "max-params": 0, 5 | "strict": [2, "never"] 6 | }, 7 | } 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules 4 | .cache 5 | build 6 | .grunt/* 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.2" 4 | before_script: 5 | - npm install grunt-cli -g 6 | after_success: 7 | - grunt pre-publish publish-ci 8 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | var glob = require('glob'); 4 | 5 | require('load-grunt-tasks')(grunt); 6 | 7 | var pkg = grunt.file.readJSON('package.json'); 8 | var githubBanner = grunt.template.process(grunt.file.read('html/misc/githubBanner.html'), {data: {pkg: pkg}}); 9 | var isMasterBuildOnTravis = process.env.TRAVIS_BRANCH === 'master'; 10 | 11 | grunt.initConfig({ 12 | tintin_root: process.env.TINTIN_ROOT, 13 | pkg: pkg, 14 | rockyjs_path: isMasterBuildOnTravis ? "dist/rocky-dev.js" : "dist/rocky.js", 15 | license_banner: "/* Copyright © 2015-2016 Pebble Technology Corp., All Rights Reserved. <%=pkg.license%> */\n\n", 16 | uglify: { 17 | options: { 18 | banner: '<%=license_banner%>' 19 | }, 20 | applib: { 21 | src: '<%= tintin_root %>/build/applib/applib-targets/emscripten/applib.js', 22 | dest: 'src/transpiled.js' 23 | } 24 | }, 25 | concat: { 26 | options: { 27 | stripBanners: true, 28 | banner: '<%=license_banner%>' 29 | }, 30 | rockyjs: { 31 | src: ['src/html-binding.js', 'src/symbols-manual.js', 'src/symbols-generated.js', 32 | 'src/transpiled.js'], 33 | dest: 'build/<%= rockyjs_path %>' 34 | } 35 | }, 36 | newer: { 37 | options: { 38 | cache: '.cache' 39 | } 40 | }, 41 | processhtml: { 42 | examples:{ 43 | options: { 44 | process: true, 45 | data: {rockyjs_path: "<%=rockyjs_path%>", github_banner: githubBanner} 46 | }, 47 | files: [ 48 | { 49 | expand: true, 50 | cwd: 'examples', 51 | src: ['**/*.*', '!**/*.md'], 52 | dest: 'build/examples' 53 | }, 54 | { 55 | expand: true, 56 | cwd: 'html', 57 | src: ['css/*'], 58 | dest: 'build' 59 | 60 | } 61 | ] 62 | } 63 | }, 64 | md2html: { 65 | all: { 66 | files: [ 67 | { 68 | expand: true, 69 | src: ['examples/*.md', 'docs/*.md', '*.md'], 70 | dest: 'build', 71 | ext: '.html', 72 | rename: function(dir, file) { 73 | // readme.html -> index.html 74 | return dir + "/" + file.replace(/(\breadme)\.html$/, "/index.html") 75 | } 76 | } 77 | ], 78 | options: { 79 | basePath: 'build', 80 | layout: 'html/markdown/template.html', 81 | templateData: { 82 | pkg: pkg, 83 | github_banner: githubBanner, 84 | } 85 | } 86 | } 87 | }, 88 | copy: { 89 | build: { 90 | files: [ 91 | { 92 | expand: true, 93 | cwd: 'html', 94 | src: ['img/*'], 95 | dest: 'build' 96 | } 97 | ] 98 | } 99 | }, 100 | eslint: { 101 | src: ['src/**/*.js', '!src/transpiled.js', 'examples/**/*.js', '!examples/interactive/js/TangleKit/**/*.js'], 102 | options: { 103 | format: 'unix' 104 | }, 105 | test: ['test/**/*.js'] 106 | }, 107 | mochaTest: { 108 | all: { 109 | src: ['test/**/*.js'] 110 | } 111 | }, 112 | 'gh-pages': { 113 | options: { 114 | base: 'build' 115 | }, 116 | publish: { 117 | src: ['**'] 118 | }, 119 | 'publish-ci': { 120 | options: { 121 | user: { 122 | name: 'travis', 123 | email: 'travis@pebble.com' 124 | }, 125 | repo: 'https://' + process.env.GH_TOKEN + '@github.com/pebble/rockyjs.git', 126 | message: 'publish gh-pages (auto)' + getDeployMessage(), 127 | silent: true // don't leak the token 128 | }, 129 | src: ['**/*'] 130 | } 131 | }, 132 | modify_json: { 133 | options: { 134 | fields: { 135 | main: 'build/<%=rockyjs_path%>' 136 | }, 137 | indent: 4 138 | }, 139 | files: ['package.json'] 140 | } 141 | }); 142 | 143 | // see http://bartvds.github.io/demo-travis-gh-pages/ 144 | function getDeployMessage() { 145 | var ret = '\n\n'; 146 | if (process.env.TRAVIS !== 'true') { 147 | ret += 'missing env vars for travis-ci'; 148 | return ret; 149 | } 150 | ret += 'branch: ' + process.env.TRAVIS_BRANCH + '\n'; 151 | ret += 'SHA: ' + process.env.TRAVIS_COMMIT + '\n'; 152 | ret += 'range SHA: ' + process.env.TRAVIS_COMMIT_RANGE + '\n'; 153 | ret += 'build id: ' + process.env.TRAVIS_BUILD_ID + '\n'; 154 | ret += 'build number: ' + process.env.TRAVIS_BUILD_NUMBER + '\n'; 155 | return ret; 156 | } 157 | 158 | grunt.registerTask('publish-ci', function() { 159 | // need this 160 | this.requires(['pre-publish']); 161 | 162 | // only deploy under these conditions 163 | if (process.env.TRAVIS === 'true' && 164 | process.env.TRAVIS_BRANCH === 'master' && 165 | process.env.TRAVIS_SECURE_ENV_VARS === 'true' 166 | && process.env.TRAVIS_PULL_REQUEST === 'false') { 167 | grunt.log.writeln('executing deployment'); 168 | // queue deploy 169 | grunt.task.run('gh-pages:publish-ci'); 170 | } 171 | else { 172 | grunt.log.writeln('skipped deployment'); 173 | grunt.log.writeln('TRAVIS', process.env.TRAVIS_SECURE_ENV_VARS); 174 | grunt.log.writeln('TRAVIS_BRANCH', process.env.TRAVIS_BRANCH); 175 | grunt.log.writeln('TRAVIS_SECURE_ENV_VARS', process.env.TRAVIS_SECURE_ENV_VARS); 176 | grunt.log.writeln('TRAVIS_PULL_REQUEST', process.env.TRAVIS_PULL_REQUEST); 177 | } 178 | }); 179 | 180 | require('./tasks/dist')(grunt); 181 | 182 | var build_tasks = ['eslint:src']; 183 | 184 | // only run uglify per default if transpiled applib exists at TINTIN_ROOT 185 | if (glob.sync(grunt.config('uglify').applib.src).length > 0) { 186 | build_tasks.push("newer:uglify:applib"); 187 | } else { 188 | grunt.verbose.write("Cannot find transpiled applib at " + grunt.config('uglify').applib.src + " - skipping uglify") 189 | } 190 | 191 | build_tasks.push('concat:rockyjs', 'processhtml:examples', 'md2html', 'copy', 'modify_json'); 192 | 193 | grunt.registerTask('pre-publish', 'should not be called directly', ['build', 'build-missing-dists']); 194 | 195 | grunt.registerTask('build', build_tasks); 196 | grunt.registerTask('default', ['build']); 197 | grunt.registerTask('test', ['build', 'eslint:test', 'mochaTest']); 198 | grunt.registerTask('publish', ['pre-publish', 'gh-pages:publish']); 199 | }; 200 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | PEBBLE JAVASCRIPT LICENSE AGREEMENT 2 | 3 | 1. DEFINITIONS. 4 | 1.1 “Pebble” means Pebble Technology Corp., a Delaware corporation having 5 | its principal place of business at 900 Middlefield Road, Redwood City, CA, 6 | 94063. 7 | 8 | 1.2 “Pebble Platform” means the Pebble hardware mobile device firmware and 9 | software application platform. 10 | 11 | 1.3 “Pebble JavaScript Software” means the Pebble JavaScript software 12 | licensed to you under the terms of this Pebble JavaScript License Agreement 13 | (the “Agreement”). 14 | 15 | 2. AGREEMENT FORMATION. 16 | 2.1. Acceptance of this Agreement. 17 | You must accept this Agreement before you can use the Pebble JavaScript 18 | software. By using any portion of the Pebble JavaScript software, you hereby 19 | agree to the terms and conditions of this Agreement. Do not accept this 20 | Agreement or use the Pebble JavaScript Software if you or your company is 21 | barred from use of the Pebble JavaScript Software under the laws or regulations 22 | of the United States or any other country. 23 | 24 | 2.2. Authority. 25 | You represent and warrant that you are of the legal age of majority in the 26 | country in which you reside (typically 18). If you are agreeing to be bound by 27 | this Agreement on behalf of your employer or another entity, you represent and 28 | warrant that you have full legal authority to bind your employer or such entity 29 | to this Agreement. If you do not have such legal authority, you may not accept 30 | the Agreement or use the Pebble JavaScript Software on behalf of your employer 31 | or any other entity. 32 | 33 | 3. PEBBLE JAVASCRIPT SOFTWARE LICENSE AND DISTRIBUTION TERMS. 34 | 3.1. License. 35 | Subject to the terms and conditions contained herein, Pebble grants to you a 36 | personal, limited, non-transferable, non-sublicensable, non-exclusive and 37 | worldwide, license to use and modify the Pebble JavaScript Software solely to 38 | a) develop, test and operate applications or portions of applications, that run 39 | solely on the Pebble Platform and b) develop, test and operate tools that 40 | create applications or portions of applications, that run solely on the Pebble 41 | Platform. Pebble grants to you a limited, transferable, non-sublicensable, 42 | non-exclusive and worldwide license to transfer the Pebble JavaScript Software 43 | and any modifications you make to the Pebble JavaScript Software solely to 44 | allow others to a) develop, test and operate applications or portions of 45 | applications, that run solely on the Pebble Platform and b) develop, test and 46 | operate tools that create applications or portions of applications, that run 47 | solely on the Pebble Platform. Except as described above, you will have no 48 | right to license, distribute or otherwise transfer the Pebble JavaScript 49 | Software or any rights therein. Your rights in the Pebble JavaScript Software 50 | will be limited to those expressly granted in this Agreement. 51 | 52 | 3.2. Distribution. 53 | You must provide a copy of this Agreement or a link to this Agreement along 54 | with any distribution of the Pebble JavaScript Software or your modifications 55 | to the Pebble JavaScript Software. 56 | 57 | 4. OPEN SOURCE COMPONENTS. 58 | Use, reproduction and distribution of components of the Pebble JavaScript 59 | Software licensed under an open source software license are governed solely by 60 | the terms of the applicable open source software license and not this 61 | Agreement. The list of open source components in this JavaScript Software can 62 | be found at https://www.pebble.com/legal/open_source. 63 | 64 | 5. RESTRICTIONS. 65 | You will not:(a) disassemble, decompile or reverse engineer any part of the 66 | Pebble JavaScript Software (except as described above in Section 3); (b) copy 67 | or otherwise reproduce the Pebble JavaScript Software (except as described 68 | above in Section 3 and with all labeling and copyright notices intact), in 69 | whole or in part, or modify, adapt, alter, translate or incorporate into or 70 | with other software or create a derivative work of any part of the Pebble 71 | JavaScript Software except as described in Section 3; (c) remove, modify or 72 | otherwise tamper with notices or legends on the Pebble JavaScript Software or 73 | any labeling on any physical media; (d) use the Pebble JavaScript Software in 74 | any manner to provide service bureau, time sharing or other computer services 75 | to third parties; or (e) commercially distribute (i) hardware with the Pebble 76 | JavaScript Software or (ii) hardware purposed for running Pebble JavaScript 77 | Software, without prior written approval from Pebble. 78 | 79 | 6. OWNERSHIP AND USE. 80 | 81 | 6.1. Intellectual Property Rights. 82 | Title to and ownership of the Pebble JavaScript Software will remain 83 | exclusively in Pebble at all times. You will promptly notify Pebble of any 84 | claim which may be adverse to Pebble’s interest in the Pebble JavaScript 85 | Software. The Pebble JavaScript Software will be used only by you, unless 86 | Pebble authorizes other parties in advance in writing to use the Pebble 87 | JavaScript Software under your supervision. 88 | 89 | 6.2. Feedback. 90 | If you provide Pebble with verbal and/or written feedback related to your use 91 | of the Pebble JavaScript Software, including, but not limited to, a report of 92 | any errors which you may discover in the Pebble JavaScript Software, or 93 | suggestions for improvements or changes to the Pebble JavaScript Software, you 94 | hereby grant Pebble a perpetual, irrevocable right to use such feedback to 95 | develop and improve the Pebble JavaScript Software, the Pebble Platform or any 96 | other Pebble product or service. 97 | 98 | 6.3. Pebble JavaScript Software Changes. 99 | You agree that the form and nature of the Pebble JavaScript Software may change 100 | without prior notice and that future versions of the Pebble JavaScript Software 101 | may be incompatible with applications developed on previous versions of the 102 | Pebble JavaScript Software. You agree that Pebble may stop (permanently or 103 | temporarily) providing the Pebble JavaScript Software (or any features within 104 | the Pebble JavaScript Software) at Pebble’s sole discretion at any time and 105 | without prior notice. 106 | 107 | 6.4. Trademarks and Rights Notices. 108 | Nothing in this Agreement gives you a right to use any of Pebble’s trade names, 109 | trademarks, service marks, logos, domain names or other distinctive brand 110 | features. You agree that you will not adopt, use or attempt to register, 111 | whether as a corporate name, domain name, product name, trademark, service mark 112 | or other indication of origin, any trademark of Pebble or any mark that is 113 | confusingly similar to or will dilute the distinctive nature of the Pebble 114 | trademarks. You also agree that you will not include the term “Pebble” as part 115 | of the name for any application that you develop using the Pebble JavaScript 116 | Software. Acceptable usage of Pebble’s trademarks can be found in trademark 117 | guidelines on Pebble’s website. You agree that you will not remove, obscure or 118 | alter any proprietary rights notices (including copyright and trademark 119 | notices) that may be affixed to or contained within the Pebble JavaScript 120 | Software. 121 | 122 | 6.5. Compliance with Law and Industry Standards. 123 | You agree to only use the Pebble JavaScript Software to enable the writing of 124 | applications for purposes that are permitted by: (a) this Agreement; and (b) 125 | any applicable law, regulation or generally accepted practices or guidelines in 126 | the relevant jurisdictions. 127 | 128 | 7. TERM AND TERMINATION. 129 | This Agreement is effective until terminated by Pebble as described below. 130 | 131 | 7.1. Pebble may terminate this Agreement at any time if: (a) you have 132 | breached any provision of the this Agreement; (b) Pebble is legally required to 133 | do so; or (c) Pebble decides not to provide the Pebble JavaScript Software or 134 | services related to the Pebble JavaScript Software to users in the country in 135 | which you reside. 136 | 137 | 7.2. Upon any termination of this Agreement you will immediately: (a) cease 138 | the use of all of the Pebble JavaScript Software; and (b) destroy all copies of 139 | the Pebble JavaScript Software (except for any sample code from the Pebble 140 | JavaScript Software that you have embedded in your authorized applications). 141 | The provisions of Sections 1, 6, 7.3, and 8 through 10 inclusive of this 142 | Agreement will survive any termination or expiration of this Agreement. 143 | 144 | 8. NO WARRANTY. 145 | The Pebble JavaScript Software is provided to you on an "AS IS" basis and 146 | without warranty of any kind. PEBBLE DISCLAIMS ALL WARRANTIES, STATUTORY, 147 | EXPRESS OR IMPLIED, RELATING TO THE PEBBLE JAVASCRIPT SOFTWARE, INCLUDING, BUT 148 | NOT LIMITED TO, THE WARRANTIES OF NON-INFRINGEMENT OF THIRD PARTY RIGHTS, 149 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU AGREE THAT YOUR USE 150 | OF THE PEBBLE JAVASCRIPT SOFTWARE IS AT YOUR OWN DISCRETION AND RISK AND YOU 151 | ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE 152 | OR ANY LOSS OF DATA THAT RESULTS FROM SUCH USE. 153 | 154 | 9. EXPORT. 155 | You acknowledge and agree that you will not import, export, or re-export, 156 | directly or indirectly, any commodity (including, without limitation, the 157 | Pebble JavaScript Software or related information) to any country in violation 158 | of the laws and regulations of any applicable jurisdiction. This restriction 159 | expressly includes, without limitation, the export regulations of the United 160 | States, and the import and export restrictions of the various European 161 | countries. You further agree to defend, indemnify and hold harmless Pebble, 162 | its affiliates and their respective directors, officers, employees, agents and 163 | representatives from any losses, costs, claims or other liabilities arising out 164 | of your breach of this Section 9. 165 | 166 | 10. GENERAL PROVISIONS. 167 | This Agreement is not assignable or transferable, in whole or in part, by you, 168 | whether involuntarily, by merger, operation of law or otherwise, without 169 | Pebble’s prior written consent. Any amalgamation or merger of a party with any 170 | third party, or the purchase or all or substantially all of the assets of a 171 | party, will be deemed an assignment requiring consent. Any attempted transfer 172 | in violation of this section is void. A waiver of any default hereunder or of 173 | any of the terms and conditions of this Agreement will not be deemed to be a 174 | continuing waiver or a waiver of any other default or of any other term or 175 | condition, but will apply solely to the instance to which such waiver is 176 | directed. The exercise of any right or remedy provided in this Agreement will 177 | be without prejudice to the right to exercise any other right or remedy 178 | provided by law or equity, except as expressly limited by this Agreement. 179 | Captions in this Agreement are for the convenience of the parties only and will 180 | not affect the interpretation or construction of this Agreement. In the event 181 | any provision of this Agreement is held to be invalid or unenforceable, such 182 | provision will be severed from the remainder of this Agreement, and such 183 | remainder will remain in force and effect. The parties agree to replace any 184 | such invalid provision with a valid provision that most closely approximates 185 | the intent and economic effect of the invalid provision. This Agreement 186 | constitutes the entire agreement between the parties relating to this subject 187 | matter and supersedes all prior and/or simultaneous representations, 188 | discussions, negotiations and agreements relating to the Pebble JavaScript 189 | Software, whether written or oral. 190 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # API Documentation 2 | 3 | This document is split into two major sections: 4 | 5 | - [Rocky.js Web API](#rockyjs-web-api) - The APIs used to create and update an instance of Rocky.js on a webpage. 6 | - [Pebble API Compatibility](#pebble-api-compatibility) - A subset of Pebble's [C-style API](https://developer.pebble.com/docs/c) that can invoked through JavaScript. 7 | 8 | ## Rocky.js Web API 9 | 10 | Rocky.js exposes a number of helper functions outside Pebble's C-Style API to help you work with Rocky.js in a browser based environment. 11 | 12 | ### Rocky.bindCanvas(el) 13 | 14 | The `bindCanvas` method creates an instance of Rocky.js, binds that instance to the supplied canvas element, then returns the instance to be used later in code. 15 | 16 | ```js 17 | // Create an instance of Rocky.js and bind it to a canvas with id="pebble" 18 | var rocky = Rocky.bindCanvas(document.getElementById("pebble")); 19 | ``` 20 | 21 | ### rocky.export_global_c_symbols() 22 | 23 | Rocky exposes a [subset of Pebble's C-Style](#pebble-api-compatibility) that can be invoked with `rocky.c_api_function_name(...)`. The `export_global_c_symbols` adds all of the available methods from the C-Style API to the global namespace, removing the need to preface each API call with `rocky.`. 24 | 25 | The following two examples demonstrate how `rocky.export_global_c_symbols()` affects a simple implementation: 26 | 27 | ``` 28 | // Calling a C-Style API without rocky.export_global_c_symbols 29 | var rocky = Rocky.bindCanvas(document.getElementById("pebble")); 30 | 31 | rocky.update_proc = function (ctx, bounds) { 32 | rocky.graphics_context_set_stroke_color(ctx, rocky.GColorRed); 33 | rocky.graphics_context_set_stroke_width(ctx, 10); 34 | rocky.graphics_draw_line(ctx, [50, 0], [100, 50]); 35 | }; 36 | 37 | ``` 38 | 39 | ``` 40 | // Calling a C-Style API after rocky.export_global_c_symbols 41 | var rocky = Rocky.bindCanvas(document.getElementById("pebble")); 42 | rocky.export_global_c_symbols(); 43 | 44 | rocky.update_proc = function (ctx, bounds) { 45 | graphics_context_set_stroke_color(ctx, GColorRed); 46 | graphics_context_set_stroke_width(ctx, 10); 47 | graphics_draw_line(ctx, [50, 0], [100, 50]); 48 | }; 49 | ``` 50 | 51 | **NOTE 1:** This method should *only* be invoked in isolated playgrounds when there is a single instance of Rocky. 52 | 53 | **NOTE 2:** Adding functions to the global namespace is generally considered an anti-pattern. The `export_global_c_symbols` may be invoked with an optional parameter (a namespace object) - when invoked in this way, the functions will be bound to the namespace object rather than the window. 54 | 55 | ### rocky.mark_dirty() 56 | 57 | The `mark_dirty` method indicates to the specified instance of Rocky that `rocky.update_proc` should be invoked. 58 | 59 | ``` 60 | var rocky = Rocky.bindCanvas(document.getElementById("pebble")); 61 | 62 | var value = 0; 63 | 64 | rocky.update_proc = function(ctx, bounds) { 65 | graphics_context_set_stroke_color(ctx, GColorRed); 66 | graphics_context_set_stroke_width(ctx, 10); 67 | graphics_draw_line(ctx, [value, 0], [100, value]); 68 | }; 69 | 70 | // Update value and mark the canvas as dirty 20/sec 71 | setInterval(function() { 72 | value += 1; 73 | rocky.mark_dirty(); 74 | }, 1000 / 20); 75 | ``` 76 | 77 | ### rocky.update_proc 78 | 79 | Instances of Rocky.js include a property, `update_proc`, that will be invoked each time the instance is marked dirty with `rocky.mark_dirty`. The update_proc method should be set to a callback function with parameters: 80 | 81 | - `ctx`: A JavaScript version of the [GContext](https://developer.pebble.com/docs/c/Graphics/Graphics_Context/) object. 82 | - `bounds`: A JavaScript version of the [GRect](https://developer.pebble.com/docs/c/Graphics/Graphics_Types/#GRect) object indicating the bounds of the virtual display. 83 | 84 | See [rocky.mark_dirty](#rocky-mark_dirty) for sample usage. 85 | 86 | ## Pebble API Compatibility 87 | 88 | Rocky.js currently implements a subset of Pebble's C-Style API. This section of the document outlines what methods have been implemented, as well as recommendations for how to manage some of the sections of the API that are not implemented. 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 |
C APIStatusNotes
[Accelerometer Service](https://developer.pebble.com/docs/c/Foundation/Event_Service/AccelerometerService/)Standard JS/HTML5 APIUse [Device Motion and Orientation APIs](https://developer.mozilla.org/en-US/docs/Web/API/Detecting_device_orientation)
[Animation](https://developer.pebble.com/docs/c/User_Interface/Animation/)Standard JS/HTML5 APIUse [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval)
[AppFocus Service](https://developer.pebble.com/docs/c/Foundation/Event_Service/AppFocusService/)Planned
[AppMessage](https://developer.pebble.com/docs/c/Foundation/AppMessage/)PlannedWill use [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) semantics
[AppSync](https://developer.pebble.com/docs/c/Foundation/AppSync/)Not Currently Planned
[Battery Service](https://developer.pebble.com/docs/c/Foundation/Event_Service/BatteryStateService/)Standard JS/HTML5 APIUse [BatteryStatus API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API)
[Bitmap](https://developer.pebble.com/docs/c/Graphics/Graphics_Types/#gbitmap_get_bytes_per_row)Implemented
[BitmapSequence](https://developer.pebble.com/docs/c/Graphics/Graphics_Types/#gbitmap_sequence_create_with_resource)Implemented
[Clicks](https://developer.pebble.com/docs/c/User_Interface/Clicks/)Not Currently Planned
[Compass Service](https://developer.pebble.com/docs/c/Foundation/Event_Service/CompassService/)Standard JS/HTML5 APIUse [Device Motion and Orientation APIs](https://developer.mozilla.org/en-US/docs/Web/API/Detecting_device_orientation)
[Connection Service](https://developer.pebble.com/docs/c/Foundation/Event_Service/ConnectionService/)Planned
[DataLogging](https://developer.pebble.com/docs/c/Foundation/DataLogging/)Not Currently Planned
Date/TimeStandard JS/HTML5 APIUse [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)
[Dictation](https://developer.pebble.com/docs/c/Foundation/Dictation/)Standard JS/HTML5 APIUse [Web Speech API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API)
[DrawCommand](https://developer.pebble.com/docs/c/Graphics/Draw_Commands/)Implemented
[Drawing Paths](https://developer.pebble.com/docs/c/Graphics/Drawing_Paths/)Implemented
[Drawing Primitives](https://developer.pebble.com/docs/c/Graphics/Drawing_Primitives/)ImplementedFramebuffer APIs will not be part of Rocky.js
[Drawing Text](https://developer.pebble.com/docs/c/Graphics/Drawing_Text/)Implemented
[Fonts](https://developer.pebble.com/docs/c/Graphics/Fonts/)Implemented
[Graphics Context](https://developer.pebble.com/docs/c/Graphics/Graphics_Context/)Implemented
[Graphics Types](https://developer.pebble.com/docs/c/Graphics/Graphics_Types/)Implemented
[Heap](https://developer.pebble.com/docs/c/Foundation/Memory_Management/)Not Currently PlannedStill investigating memory management
[Launch Reason](https://developer.pebble.com/docs/c/Foundation/Launch_Reason/)Not Currently Planned
[Layer](https://developer.pebble.com/docs/c/User_Interface/Layers/)Not Currently Planned
[Logging](https://developer.pebble.com/docs/c/Foundation/Logging/)Standard JS/HTML5 APIUse [console.log](https://developer.mozilla.org/en-US/docs/Web/API/Console/log)
[Light](https://developer.pebble.com/docs/c/User_Interface/Light/)Planned
[Persistant Storage](https://developer.pebble.com/docs/c/Foundation/Storage/)Standard JS/HTML5 APIUse [localStorage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)
[SmartStraps](https://developer.pebble.com/docs/c/Smartstrap/)Not Currently Planned
[TickService](https://developer.pebble.com/docs/c/Foundation/Event_Service/TickTimerService/)Standard JS/HTML5 APIUse [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval)
[Timer](https://developer.pebble.com/docs/c/Foundation/Timer/)Standard JS/HTML5 APIUse [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)
[Vibes](https://developer.pebble.com/docs/c/User_Interface/Vibes/)Standard JS/HTML5 APIUse [Navigator.vibrate](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vibrate)
[Wakeup](https://developer.pebble.com/docs/c/Foundation/Wakeup/)Not Currently Planned
[WatchInfo](https://developer.pebble.com/docs/c/Foundation/WatchInfo/)Planned
[Window](https://developer.pebble.com/docs/c/User_Interface/Window/)Not Currently Planned
[WindowStack](https://developer.pebble.com/docs/c/User_Interface/Window_Stack/)Not Currently PlannedOr similar functionality
[Worker](https://developer.pebble.com/docs/c/Worker/)Not Currently Planned
281 | -------------------------------------------------------------------------------- /examples/apng/banana.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble/rockyjs/239b0c3f4916178dd1584b234ed3fad9d5c06043/examples/apng/banana.gif -------------------------------------------------------------------------------- /examples/apng/banana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble/rockyjs/239b0c3f4916178dd1584b234ed3fad9d5c06043/examples/apng/banana.png -------------------------------------------------------------------------------- /examples/apng/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Animated PNGs 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 | 46 | 47 |
48 | 49 |
50 | 51 |
52 |

What's going on?

53 |

54 | Similar to the simple example it binds Rocky.js to a canvas, and update the output at a regular interval. 55 |

56 |

57 | This example uses animated PNGs 58 | to construct a GBitmapSequence. 59 | APNGs are supported by Pebble natively. You need to 60 | convert GIFs to APNGs before using them. 61 |

62 |

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 |

73 |
74 | 77 |
78 | 79 | 80 | 129 | 130 | -------------------------------------------------------------------------------- /examples/common/css/style.css: -------------------------------------------------------------------------------- 1 | canvas.rocky { 2 | border: 1px solid darkgray; 3 | image-rendering: pixelated; 4 | margin: 20px; 5 | } 6 | 7 | pre.interactive-code { 8 | margin-top: 20px; 9 | } 10 | -------------------------------------------------------------------------------- /examples/community.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Community Examples 12 | 13 | 14 |
15 | 24 |
25 |

Simply Square by NiVZ78

26 |

27 | Simply Square by NiVZ78 on jsbin.com 28 |

29 |
30 |
31 |

91 Dub by orviwan

32 |

33 | 91 Dub by orviwan on jsbin.com 34 |

35 |
36 |
37 |

Isotime by Chris Lewis

38 |

39 | Isotime by Chris Lewis on jsbin.com 40 |

41 |
42 |
43 |

Game of Life by Matt Langlois

44 |

45 | Game of Life on jsbin.com 46 |

47 |
48 | 49 | 50 |
51 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /examples/gpath/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 | 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 | The example makes use of GPath a simple API to 57 | draw polygons into Pebble's graphic context. 58 |

59 |

60 | Every second, it choses a random fill color and stroke width. 61 | The outline of the path is closed or open at random. 62 |

63 |
64 | 67 |
68 | 69 | 70 | 115 | 116 | -------------------------------------------------------------------------------- /examples/interactive/css/TangleKit.css: -------------------------------------------------------------------------------- 1 | /* 2 | * TangleKit.css 3 | * Tangle 0.1.0 4 | * 5 | * Created by Bret Victor on 6/10/11. 6 | * (c) 2011 Bret Victor. MIT open-source license. 7 | * 8 | */ 9 | 10 | 11 | /* cursor */ 12 | 13 | .TKCursorDragHorizontal { 14 | cursor: pointer; 15 | cursor: move; 16 | cursor: col-resize; 17 | } 18 | 19 | 20 | /* TKToggle */ 21 | 22 | .TKToggle, 23 | .TKIncrement { 24 | color: #46f; 25 | border-bottom: 1px dashed #46f; 26 | cursor: pointer; 27 | } 28 | 29 | /* TKSelect */ 30 | 31 | .TKSelect { 32 | color: #46f; 33 | border-bottom: 1px dashed #46f; 34 | cursor: pointer; 35 | } 36 | 37 | /* TKAdjustableNumber and similar */ 38 | 39 | .TKAdjustableNumber, .TKLogarithmicAdjustableNumber { 40 | position:relative; 41 | color: #46f; 42 | border-bottom: 1px dashed #46f; 43 | } 44 | 45 | .TKAdjustableNumberHover { 46 | } 47 | 48 | .TKAdjustableNumberDown { 49 | color: #00c; 50 | border-bottom: 1px dashed #00c; 51 | } 52 | 53 | .TKAdjustableNumberHelp { 54 | position:absolute; 55 | color: #00f; 56 | font: 9px "Helvetica-Neue", "Arial", sans-serif; 57 | } 58 | 59 | 60 | .TKInlineSlider { 61 | display: inline-block; 62 | position:relative; 63 | background-color: #ccc; 64 | width: 200px; 65 | height: 12px; 66 | cursor: pointer; 67 | cursor: move; 68 | cursor: col-resize; 69 | } 70 | .TKInlineSliderBar { 71 | position:absolute; 72 | background-color: #f00; 73 | height: 12px; 74 | top: 0px; 75 | left: 0px; 76 | } 77 | 78 | .TKExpandingListItem { 79 | color: #46f; 80 | border-bottom: 1px dashed #46f; 81 | cursor: pointer; 82 | } 83 | .TKExpandingListItem:hover { 84 | color: #00c; 85 | border-bottom: 1px dashed #00c; 86 | } 87 | 88 | 89 | .TKExpandingSetItem { 90 | color: #aaa; 91 | border-bottom: 1px dashed #aaa; 92 | cursor: pointer; 93 | } 94 | .TKExpandingSetItem:hover { 95 | color: #666; 96 | border-bottom: 1px dashed #666; 97 | } 98 | .TKExpandingSetItemSelected { 99 | color: #46f; 100 | font-weight:bold; 101 | border-bottom: 1px dashed #46f; 102 | cursor: pointer; 103 | } 104 | .TKExpandingSetItemSelected:hover { 105 | color: #02f; 106 | border-bottom: 1px dashed #02f; 107 | } 108 | .TKExpandingSetSummary { 109 | color: #46f; 110 | border-bottom: 1px dashed #46f; 111 | cursor: pointer; 112 | } 113 | 114 | .TKAdjustableNumberDown .TKAdjustableNumberHelp { 115 | color: #f00; 116 | } 117 | 118 | .BarGraphBarDiv { 119 | background-color: #aaa; 120 | height: 6px; 121 | margin-top: 1px; 122 | } 123 | 124 | .TKProgressCalendar-completed { 125 | background-color: green; 126 | color: white; 127 | font-weight: bold; 128 | } -------------------------------------------------------------------------------- /examples/interactive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Pebble Interactive API 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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | 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.

65 | 66 |
67 |
68 | 69 |
70 |
 71 | 
 72 | const GRect rect = GRect(, , , );
 77 | 
 78 | // Set to true to draw the bounding box
 79 | if (falsetrue) {
 80 |   graphics_set_stroke_color(ctx, GColorGray);
 81 |   graphics_draw_rect(ctx, rect);
 82 | }
 83 | 
 84 | graphics_fill_radial(
 85 |   ctx, rect,
 86 |   GOvalScaleModeFitCircleGOvalScaleModeFillCircle,
 87 |   , /* inset */
 88 |   DEG_TO_TRIGANGLE(), /* angle_start */
 89 |   DEG_TO_TRIGANGLE()  /* angle_end */
 90 | );
 91 | 
 92 |             
93 |
94 |
95 | 96 |
97 | 98 |

graphics_draw_arc

99 |

100 |

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 |

102 |
103 |
104 | 105 |
106 |
107 | const GRect rect = GRect(, , , );
112 | 
113 | // Set to true to draw the bounding box
114 | if (falsetrue) {
115 |   graphics_set_stroke_color(ctx, GColorGray);
116 |   graphics_draw_rect(ctx, rect);
117 |   graphics_set_stroke_color(ctx, GColorBlack);
118 | }
119 | 
120 | graphics_context_set_stroke_width(ctx, );
121 | graphics_draw_arc(
122 |   ctx, rect,
123 |   GOvalScaleModeFitCircleGOvalScaleModeFillCircle,
124 |   DEG_TO_TRIGANGLE(), /* angle_start */
125 |   DEG_TO_TRIGANGLE()  /* angle_end */
126 | );
127 | 
128 |             
129 |
130 | 131 |
132 | 135 |
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 | 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 | 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 |

55 | 56 |

var rocky = Rocky.bindCanvas(el)

57 |

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 |

60 | 61 |

rocky.export_global_c_symbols()

62 |

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 |

65 | 66 |

rocky.mark_dirty()

67 |

68 | This method indicates to Rocky that update_proc should be invoked. 69 |

70 | 71 |

rocky.update_proc

72 |

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 |

75 |

Dog image courtesy of OpenGameArt.org

76 | 77 |
78 | 81 |
82 | 83 | 84 | 143 | 144 | -------------------------------------------------------------------------------- /examples/text/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 | 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 |

60 |
61 | 64 |
65 | 66 | 67 | 99 | 100 | -------------------------------------------------------------------------------- /examples/tictoc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | TicToc Example 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 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 |

56 | 57 |

js/rocky-extended.js

58 |

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 |

64 | 65 |

js/tictoc.js

66 |

67 | The code in tictoc.js implements the TicToc watchface using the extended API defined in rocky-extended.js. 68 |

69 |
70 | 73 |
74 | 75 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /examples/tictoc/js/rocky-extended.js: -------------------------------------------------------------------------------- 1 | /*eslint "no-unused-vars": [2, { "args": "none", "vars": "local" }]*/ 2 | /*global Rocky:true, TimerService:true, Window:true*/ 3 | 4 | if (typeof (Rocky) === 'undefined') { 5 | Rocky = {}; 6 | } 7 | 8 | TimerService = { 9 | 'subscribe': function(granularity, cb) { 10 | function timer_loop() { 11 | var date = new Date(); 12 | var dt = { 13 | hour: date.getHours(), 14 | min: date.getMinutes(), 15 | sec: date.getSeconds() 16 | }; 17 | cb(dt); 18 | } 19 | setInterval(timer_loop, 1000); 20 | timer_loop(); 21 | } 22 | }; 23 | 24 | Window = function() { 25 | var that = this; 26 | this.rootLayer = { 27 | 'bounds': {'origin': {'x': 0, 'y': 0}, 'size': {'w': 144, 'h': 168}}, 28 | 'markDirty': function() { 29 | var rocky = Rocky.activeBinding; 30 | rocky.mark_dirty(); 31 | }, 32 | 'setUpdateProc': function(proc) { 33 | var rocky = Rocky.activeBinding; 34 | rocky.update_proc = function(ctx, bounds) { 35 | var enhanced_context = { 36 | setFillColor: function(color) { 37 | var rocky = Rocky.activeBinding; 38 | rocky.graphics_context_set_fill_color(ctx, color); 39 | }, 40 | setStrokeColor: function(color) { 41 | var rocky = Rocky.activeBinding; 42 | rocky.graphics_context_set_stroke_color(ctx, color); 43 | }, 44 | setStrokeWidth: function(color) { 45 | var rocky = Rocky.activeBinding; 46 | rocky.graphics_context_set_stroke_width(ctx, color); 47 | }, 48 | drawLine: function(p0, p1) { 49 | var rocky = Rocky.activeBinding; 50 | rocky.graphics_draw_line(ctx, [p0.x, p0.y], [p1.x, p1.y]); 51 | }, 52 | fillRect: function(rect) { 53 | var rocky = Rocky.activeBinding; 54 | rocky.graphics_fill_rect(ctx, 55 | [rect.origin.x, rect.origin.y, rect.size.w, rect.size.h]); 56 | }, 57 | fillCircle: function(center, radius) { 58 | var rocky = Rocky.activeBinding; 59 | var rect = rocky.GRect(center.x - radius, center.y - radius, 60 | radius * 2, radius * 2); 61 | rocky.graphics_fill_radial(ctx, rect, rocky.GOvalScaleModeFillCircle, 62 | radius * 2, 0, Math.PI * 2); 63 | } 64 | }; 65 | proc.apply(that.rootLayer, [enhanced_context]); 66 | }; 67 | } 68 | }; 69 | 70 | this.push = function() { 71 | if (typeof (this.handlers.load) === 'function') { 72 | this.handlers.load.apply(this); 73 | } 74 | }; 75 | 76 | this.setWindowHandlers = function(handlers) { 77 | this.handlers = handlers; 78 | }; 79 | }; 80 | -------------------------------------------------------------------------------- /examples/tictoc/js/tictoc.js: -------------------------------------------------------------------------------- 1 | /*eslint no-unused-vars: 0*/ 2 | /* global TimerService:false, Window:false _timeout:true, _intervalId:true */ 3 | 4 | // some library code, could be extracted into some JS files 5 | var scheduleAnimation = function(options) { 6 | options = options || {}; 7 | var msDelay = options.delay || 0; 8 | var msDuration = options.duration || 0; 9 | var updateHandler = options.update || function() { 10 | }; 11 | var stopHandler = options.stop || function() { 12 | }; 13 | 14 | // todo: remove these globals by fixing jswrap_interactive.c 15 | _timeout = setTimeout(function() { 16 | var msPassed = 0; 17 | var msPerStep = 1000 / 30; 18 | 19 | _intervalId = setInterval(function() { 20 | msPassed += msPerStep; 21 | var progress = Math.min(1, msPassed / msDuration); 22 | updateHandler(progress); 23 | if (progress >= 1) { 24 | stopHandler(); 25 | clearInterval(_intervalId); 26 | } 27 | }, msPerStep); 28 | updateHandler(0); 29 | }, msDelay); 30 | }; 31 | 32 | var interpolate = function(f, a, b) { 33 | return a * (1 - f) + f * b; 34 | }; 35 | 36 | var MINUTE_UNIT = 1 << 1; 37 | var win = new Window(); 38 | var angle_funny = 0; 39 | 40 | win.setWindowHandlers({ 41 | load: function() { 42 | var MINUTE_HAND_MARGIN = 7; 43 | var HOUR_HAND_MARGIN = 7 * 4; 44 | var GColorWhite = 255; 45 | var GColorBlack = 192; 46 | var GColorRed = 240; 47 | var STROKE_WIDTH = 8; 48 | var CLOCK_RADIUS = 65; 49 | var DOT_Y = 8; 50 | 51 | var canvasLayer = this.rootLayer; 52 | var bounds = canvasLayer.bounds; 53 | 54 | var data = { 55 | clockRadius: 0, 56 | center: {x: bounds.size.w / 2, y: bounds.size.h / 2}, 57 | dotY: bounds.size.h / 2, 58 | dotY2: bounds.size.h / 2 59 | }; 60 | 61 | TimerService.subscribe(MINUTE_UNIT, function(tick_time, changed) { 62 | data.lastTime = { 63 | hours: tick_time.hour % 12, 64 | minutes: tick_time.min 65 | }; 66 | canvasLayer.markDirty(); 67 | }, true); 68 | 69 | var bgcolor = 0; 70 | canvasLayer.setUpdateProc(function(ctx) { 71 | ctx.setFillColor(192 | bgcolor); 72 | ctx.setStrokeWidth(STROKE_WIDTH); 73 | var bounds = this.bounds; 74 | ctx.fillRect(bounds); 75 | 76 | var modeTime = data.lastTime; 77 | var minuteAngle = modeTime.minutes / 60; 78 | var hourAngle = (modeTime.hours + minuteAngle) / 12; 79 | 80 | var pt = function(ratioAngle, margin, funny) { 81 | var radAngle = ratioAngle * 2 * Math.PI; 82 | radAngle += funny; 83 | var len = data.clockRadius - margin; 84 | return { 85 | x: Math.sin(radAngle) * len + data.center.x, 86 | y: -Math.cos(radAngle) * len + data.center.y 87 | }; 88 | }; 89 | 90 | var minuteHand = pt(minuteAngle, MINUTE_HAND_MARGIN, angle_funny); 91 | var hourHand = pt(hourAngle, HOUR_HAND_MARGIN, -angle_funny); 92 | 93 | if (data.clockRadius > MINUTE_HAND_MARGIN) { 94 | ctx.setStrokeColor(GColorWhite); 95 | ctx.drawLine(data.center, minuteHand); 96 | } 97 | 98 | if (data.clockRadius > HOUR_HAND_MARGIN) { 99 | ctx.setStrokeColor(GColorRed); 100 | ctx.drawLine(data.center, hourHand); 101 | // fill a circle to make a cleaner center 102 | ctx.setFillColor(GColorRed); 103 | ctx.fillCircle(data.center, STROKE_WIDTH / 2); 104 | } 105 | 106 | // draw a 12 o clock indicator 107 | ctx.setStrokeColor(GColorWhite); 108 | ctx.drawLine({ 109 | x: bounds.size.w / 2, 110 | y: data.dotY 111 | }, { 112 | x: bounds.size.w / 2, 113 | y: data.dotY2 114 | }); 115 | }); 116 | 117 | scheduleAnimation({ 118 | delay: 0, 119 | duration: 1550, 120 | update: function(progress) { 121 | function f(prop, max) { 122 | data[prop] = interpolate(progress, data[prop], max); 123 | } 124 | 125 | f('clockRadius', CLOCK_RADIUS + 1); 126 | f('dotY', DOT_Y); 127 | f('dotY2', DOT_Y); 128 | canvasLayer.markDirty(); 129 | }, 130 | stop: function() { 131 | data.dotY = DOT_Y; 132 | data.dotY2 = DOT_Y; 133 | canvasLayer.markDirty(); 134 | 135 | scheduleAnimation({ 136 | delay: 1000, 137 | duration: 3000, 138 | update: function(progress) { 139 | angle_funny = progress * 2 * Math.PI; 140 | canvasLayer.markDirty(); 141 | } 142 | }); 143 | 144 | } 145 | }); 146 | 147 | } 148 | }); 149 | 150 | win.push(); 151 | console.log('Hello, JSConf!'); 152 | -------------------------------------------------------------------------------- /html/css/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Bowlby+One+SC|Roboto:400,700,400italic); 2 | 3 | body { 4 | font: 16px/1.5 'Roboto', sans-serif; 5 | } 6 | 7 | body { 8 | padding-bottom: 2em; 9 | } 10 | 11 | body > .container:first-child > .row:first-child > div > p:first-child { 12 | display: none; 13 | } 14 | 15 | h1 { 16 | font-size: 3em; 17 | } 18 | 19 | h1 + blockquote { 20 | border: 0 none; 21 | max-width: 600px; 22 | text-align: center; 23 | margin: 0 auto 3em; 24 | padding: 0; 25 | } 26 | 27 | h1, h2 { 28 | font-family: "Bowlby One SC"; 29 | overflow: hidden; 30 | text-align: center; 31 | margin: 2em 0 1em; 32 | text-transform: uppercase; 33 | } 34 | 35 | h1:before, h1:after, h2:before, h2:after { 36 | background-color: #ddd; 37 | content: ""; 38 | display: inline-block; 39 | height: 1px; 40 | position: relative; 41 | vertical-align: middle; 42 | width: 50%; 43 | } 44 | 45 | h1:before, h2:before { 46 | right: 0.5em; 47 | margin-left: -50%; 48 | } 49 | 50 | h1:after, h2:after { 51 | left: 0.5em; 52 | margin-right: -50%; 53 | } 54 | 55 | h3 { 56 | margin: 1em 0 0.5em; 57 | } 58 | 59 | #githubForkLink { 60 | background: url(../img/forkBanner.png) no-repeat top right; 61 | position: absolute; 62 | display: block; 63 | width: 149px; 64 | height: 149px; 65 | top: 0; 66 | right: 0; 67 | 68 | /* Hide the text. */ 69 | text-indent: 100%; 70 | white-space: nowrap; 71 | overflow: hidden; 72 | } 73 | 74 | table.table-compatibility>tbody>tr.standard { 75 | background-color: #d9edf7; 76 | } 77 | 78 | table.table-compatibility>tbody>tr.implemented { 79 | background-color: #dff0db; 80 | } 81 | 82 | table.table-compatibility>tbody>tr.partial { 83 | background-color: #ffffff; 84 | } 85 | 86 | table.table-compatibility>tbody>tr.planned { 87 | background-color: #fcf8e3; 88 | } 89 | 90 | table.table-compatibility>tbody>tr.not-planned { 91 | background-color: #f2dede 92 | } 93 | -------------------------------------------------------------------------------- /html/img/basaltLight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble/rockyjs/239b0c3f4916178dd1584b234ed3fad9d5c06043/html/img/basaltLight.png -------------------------------------------------------------------------------- /html/img/forkBanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble/rockyjs/239b0c3f4916178dd1584b234ed3fad9d5c06043/html/img/forkBanner.png -------------------------------------------------------------------------------- /html/markdown/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Rocky.js 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 |
22 |
23 |
24 | <%=document%> 25 |
26 |
27 | <%=github_banner%> 28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /html/misc/githubBanner.html: -------------------------------------------------------------------------------- 1 | Fork me on GitHub 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rocky.js", 3 | "title": "Run Pebble code in the browser", 4 | "version": "0.3.0", 5 | "main": "build/dist/rocky.js", 6 | "scripts": { 7 | "test": "grunt test" 8 | }, 9 | "homepage": "http://pebble.github.io/rockyjs/", 10 | "bugs": { 11 | "url": "https://github.com/pebble/rockyjs/issues" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/pebble/rockyjs.git" 16 | }, 17 | "license": "http://pebble.github.io/rockyjs/LICENSE", 18 | "keywords": [ 19 | "pebble", 20 | "browser", 21 | "emscripten" 22 | ], 23 | "devDependencies": { 24 | "atob": "^2.0.0", 25 | "chai": "^3.4.1", 26 | "eslint-config-pebble": "^1.2.0", 27 | "eslint-plugin-standard": "^1.3.1", 28 | "fs-extra": "^0.26.4", 29 | "glob": "^6.0.1", 30 | "grunt": "~0.4.5", 31 | "grunt-contrib-concat": "^0.5.1", 32 | "grunt-contrib-copy": "^0.8.2", 33 | "grunt-contrib-uglify": "^0.11.0", 34 | "grunt-eslint": "^17.3.1", 35 | "grunt-gh-pages": "^1.0.0", 36 | "grunt-md2html": "^0.3.0", 37 | "grunt-mocha-test": "^0.12.7", 38 | "grunt-modify-json": "^0.1.3", 39 | "grunt-newer": "^1.1.1", 40 | "grunt-processhtml": "^0.3.8", 41 | "load-grunt-tasks": "^3.4.0", 42 | "mocha": "^2.3.4", 43 | "q": "^1.4.1", 44 | "simple-git": "^1.21.0", 45 | "sinon": "^1.17.2", 46 | "xmlhttprequest": "^1.8.0" 47 | } 48 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/pebble/rockyjs.svg?branch=master)](https://travis-ci.org/pebble/rockyjs) 2 | 3 | # Rocky.js 4 | 5 | Pebble is developing a framework for building embedded JavaScript watchapps. In April, we shipped a firmware update for our smartwatches that included an embedded [JerryScript](https://github.com/pebble/jerryscript) engine, and a JavaScript implementation of TicToc, our default watchface. 6 | 7 | This repository currently contains some of our previous explorations with JavaScript, in which we transpiled our smartwatch firmware to JavaScript, and ran virtual smartwatches in the browser. 8 | 9 | We will be updating this repository as we progress towards enabling developers to write embedded JavaScript applications. 10 | 11 | ## Next Steps 12 | 13 | We are now in the process of implementing JavaScript APIs for our developers, and creating the tools and resources developers will need to create and test embedded JavaScript applications for Pebble smartwatches. 14 | 15 | We are planning to release a preview SDK later this summer, with everything developers will need to create watchfaces written in JavaScript! 16 | 17 | ## APIs 18 | 19 | Pebble is designing its JavaScript APIs with the existing JavaScript ecosystem in mind. Wherever possible, standard web APIs will be used - the display will be treated as a [canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API), the compass and accelerometer will be queried with the [device orientation API](https://developer.mozilla.org/en-US/docs/Web/API/Detecting_device_orientation), and you'll interact with your PebbleKit JS code by passing messages in a manner similar to [web workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers). 20 | 21 | ### Learn More 22 | 23 | If you're interested in staying up to date with our Rocky.js development efforts, you can subscribe to our [JSApps newsletter](http://pbl.io/jsapps), and follow [@pebbledev](https://twitter.com/pebbledev) (or simply star this repository). 24 | 25 | ## LICENSE 26 | 27 | Rocky.js is licensed under the [PEBBLE JAVASCRIPT LICENSE AGREEMENT](https://github.com/pebble/rockyjs/blob/master/LICENSE). 28 | -------------------------------------------------------------------------------- /src/html-binding.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright © 2015-2016 Pebble Technology Corp., 4 | All Rights Reserved. http://pebble.github.io/rockyjs/LICENSE 5 | 6 | This describes functionality to create an instance of Rocky and 7 | bind it to an HTML canvas element. 8 | 9 | */ 10 | 11 | /*global Rocky:true*/ 12 | 13 | if (typeof (Rocky) === 'undefined') { 14 | Rocky = {}; 15 | } 16 | 17 | Rocky.bindCanvas = function(canvas, options) { 18 | options = options || {}; 19 | 20 | // instance of the Emscripten module 21 | var module = this.Module(); 22 | 23 | // in a future version, these values should adapt automatically 24 | // also, we want the ability to create framebuffers of larger sizes 25 | var canvasW = canvas.width; 26 | var canvasH = canvas.height; 27 | var framebufferW = 144; 28 | var framebufferH = 168; 29 | 30 | // scale gives us the ability to do a nearest-neighbor scaling 31 | var scale = options.scale || 32 | Math.min(canvasW / framebufferW, canvasH / framebufferH); 33 | 34 | // pixel access to read (framebuffer) and write to (canvas) 35 | var canvasCtx = canvas.getContext('2d'); 36 | var canvasPixelData = canvasCtx.createImageData(canvasW, canvasH); 37 | var canvasPixels = canvasPixelData.data; 38 | var framebufferPixelPTR = module.ccall('emx_graphics_get_pixels', 'number', []); 39 | var framebufferPixels = new Uint8Array(module.HEAPU8.buffer, 40 | framebufferPixelPTR, 41 | canvasW * canvasH); 42 | var graphicsContext = module.ccall('app_state_get_graphics_context', 'number', []); 43 | 44 | // result of this function 45 | var binding = { 46 | module: module, 47 | update_proc: function(ctx, bounds) { 48 | // meant to be override by clients 49 | // will be called whenever a clients calls #mark_dirty() 50 | }, 51 | mark_dirty: function() { 52 | // initializes the graphics context and framebuffer with default values 53 | // before rendering 54 | var bounds = binding.GRect(0, 0, framebufferW, framebufferH); 55 | binding.graphics_context_set_fill_color(graphicsContext, binding.GColorWhite); 56 | binding.graphics_fill_rect(graphicsContext, bounds); 57 | 58 | binding.graphics_context_set_fill_color(graphicsContext, binding.GColorBlack); 59 | binding.graphics_context_set_stroke_color(graphicsContext, 60 | binding.GColorBlack); 61 | binding.graphics_context_set_stroke_width(graphicsContext, 1); 62 | binding.graphics_context_set_antialiased(graphicsContext, true); 63 | 64 | binding.graphics_context_set_text_color(graphicsContext, binding.GColorBlack); 65 | 66 | binding.graphics_context_set_compositing_mode(graphicsContext, 67 | binding.GCompOpSet); 68 | 69 | binding.update_proc(graphicsContext, bounds); 70 | binding.render_framebuffer(canvasCtx); 71 | }, 72 | render_framebuffer: function() { 73 | // renders current state of the framebuffer to the bound canvas 74 | // respecting the passed scale 75 | for (var y = 0; y < canvasH; y++) { 76 | var pebbleY = (y / scale) >> 0; 77 | if (pebbleY >= framebufferH) { 78 | break; 79 | } 80 | for (var x = 0; x < canvasW; x++) { 81 | var pebbleX = (x / scale) >> 0; 82 | if (pebbleX >= framebufferW) { 83 | break; 84 | } 85 | var pebbleOffset = pebbleY * framebufferW + pebbleX; 86 | var in_values = framebufferPixels[pebbleOffset]; 87 | var r = ((in_values >> 4) & 0x3) * 85; 88 | var g = ((in_values >> 2) & 0x3) * 85; 89 | var b = ((in_values >> 0) & 0x3) * 85; 90 | var canvasOffset = (y * canvasW + x) * 4; 91 | canvasPixels[canvasOffset + 0] = r; 92 | canvasPixels[canvasOffset + 1] = g; 93 | canvasPixels[canvasOffset + 2] = b; 94 | canvasPixels[canvasOffset + 3] = 255; 95 | } 96 | } 97 | canvasCtx.putImageData(canvasPixelData, 0, 0); 98 | }, 99 | export_global_c_symbols: function(global) { 100 | // meant for simple scenarios where all C-like 101 | // functions can live outside of this binding object 102 | if (typeof (global) === 'undefined') { 103 | global = window; 104 | } 105 | 106 | for (var key in binding) { 107 | if (non_c_binding_keys.indexOf(key) < 0 && binding.hasOwnProperty(key)) { 108 | var value = binding[key]; 109 | global[key] = (typeof (value) === 'function') ? 110 | value.bind(binding) : value; 111 | } 112 | } 113 | } 114 | }; 115 | 116 | // collect all functions of `binding` here 117 | // so we can filter them out in export_global_c_symbols() 118 | var non_c_binding_keys = []; 119 | for (var key in binding) { 120 | if (binding.hasOwnProperty(key)) { 121 | non_c_binding_keys.push(key); 122 | } 123 | } 124 | 125 | // will enhance the binding object by various function 126 | // from ./symbols-*.js of this folder 127 | non_c_binding_keys = non_c_binding_keys.concat(Rocky.addGeneratedSymbols(binding)); 128 | non_c_binding_keys = non_c_binding_keys.concat(Rocky.addManualSymbols(binding)); 129 | 130 | // useful if clients only have a single Rocky instance 131 | Rocky.activeBinding = binding; 132 | 133 | // schedule one render pass for the next run iteration of the run loop 134 | setTimeout(function() { binding.mark_dirty(); }, 0); 135 | 136 | return binding; 137 | }; 138 | -------------------------------------------------------------------------------- /tasks/dist.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | var taskName = 'build-missing-dists'; 4 | 5 | // location of our temporary working copy we use to build tags 6 | var tempCheckout = '.grunt/' + taskName; 7 | 8 | // register a few promise related convenience functions 9 | var exec = require('./utils')(grunt).exec; 10 | 11 | var Q = require("q"); 12 | var fs = require('fs-extra'); 13 | var simpleGit = require("simple-git"); 14 | 15 | // rocky.js filename for a given version 16 | function rockyNameForTag(version) { 17 | return 'build/dist/rocky-' + version + '.js'; 18 | } 19 | 20 | // true, if a versioned rocky file exists in the destination folder 21 | function versionedRockyExists(tag) { 22 | try { 23 | return fs.statSync(rockyNameForTag(tag)).isFile(); 24 | } catch (err) { 25 | return false; 26 | } 27 | } 28 | 29 | // checks for a valid version string to find tags and verify valid filed names, e.g. x.y.z, x.y.z-beta1 30 | function isValidRockyVersion(version) { 31 | return version.match(/^\d+\.\d+\.\d+(-\n+)?$/); 32 | } 33 | 34 | // creates a promise that expresses all steps for a given tag 35 | function checkoutNpmBuildCopy(tag) { 36 | return simpleGit(tempCheckout).promising('checkout', tag) 37 | .then(function() { 38 | return exec('npm install', {cwd: tempCheckout}); 39 | }) 40 | .then(function() { 41 | return exec('grunt build', {cwd: tempCheckout}); 42 | }) 43 | .then(function() { 44 | // copy the resulting file from this version and name it according to its version 45 | // but: only do so if our checks on the file name pass 46 | var taggedPkg = grunt.file.readJSON(tempCheckout + '/package.json'); 47 | var taggedVersion = taggedPkg.version; 48 | var destName = rockyNameForTag(taggedVersion); 49 | if (!isValidRockyVersion(taggedVersion)) { 50 | grunt.log.warn("skipping file", destName, "Version", taggedVersion, "doesn't follow expected format."); 51 | return false; 52 | } 53 | if (versionedRockyExists(taggedVersion)) { 54 | grunt.log.warn("skipping file", destName, "File already exists."); 55 | return false; 56 | } 57 | var srcName = tempCheckout + '/' + taggedPkg.main; 58 | grunt.log.write("copy", srcName, '=>', destName, "..."); 59 | return Q.nfcall(fs.copy, srcName, destName).thenLogOk(); 60 | }); 61 | } 62 | 63 | // actual grunt task 64 | var desc = 'Checks out and builds tags from remote repository and copies versioned output to ./build/dist'; 65 | grunt.registerTask(taskName, desc, function() { 66 | var done = this.async(); 67 | 68 | // delete temp folder 69 | exec( 'rm -rf ' + tempCheckout) 70 | // checkout Rocky.js repo into temp folder 71 | .then(function() { 72 | var url = grunt.file.readJSON('package.json').repository.url; 73 | grunt.verbose.writeln('cloning', url, 'into', tempCheckout); 74 | return simpleGit('.').promising("clone", url, tempCheckout); 75 | }) 76 | // checkout gh-pages and copy all missing rocky-x.y.z.js from its dist folder to build/dist 77 | // we do this so that we will have gh-pages' dist folder + this working copy's rocky.js 78 | .then(function() { 79 | return simpleGit(tempCheckout).promising('checkout', 'gh-pages'); 80 | }) 81 | .then(function() { 82 | // copy all rocky.js files from gh-pages' dist into this working copy's build folder, but 83 | // preserve existing files in build/dist (presumably only the one we built from this working copy before) 84 | return exec('cp -n ' + tempCheckout + '/dist/*.js build/dist') 85 | .catch(function(){return "this is fine since cp -n returns 1 if there was a file already."}); 86 | }) 87 | // retrieve all tags to continue with the actual copy 88 | .then(function() { 89 | return simpleGit(tempCheckout).fetchTags().promising('tags'); 90 | }) 91 | // filter tags: keep version tags we don't have a rocky-x.y.z.js for, yet 92 | .then(function(tags) { 93 | var foundTags = tags.all.filter(function(tag) { 94 | return isValidRockyVersion(tag); 95 | }); 96 | grunt.verbose.writeln('found tags', foundTags); 97 | 98 | var relevantTags = foundTags.filter(function(tag) { 99 | return !versionedRockyExists(tag); 100 | }); 101 | grunt.log.writeln('relevant tags', relevantTags); 102 | return relevantTags; 103 | }) 104 | // create a sequence of promises to build all relevant tags 105 | .then(function(relevantTags) { 106 | var tasks = relevantTags.map(function(tag) { 107 | return function(){return checkoutNpmBuildCopy(tag);}; 108 | }); 109 | return tasks.reduce(Q.when, Q(true)); 110 | }) 111 | // correctly terminate async grunt task 112 | .done(function(){done(true);}, function(error){ 113 | grunt.log.error(error); 114 | done(false); 115 | }); 116 | }); 117 | 118 | }; 119 | -------------------------------------------------------------------------------- /tasks/utils.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | var Q = require("q"); 4 | // add a few grunt-specific convenience functions to Promise 5 | (function() { 6 | var thenLogWithFunc = function (func) { 7 | return function () { 8 | var logArgs = arguments; 9 | return this.then(function () { 10 | func.apply(undefined, logArgs); 11 | return arguments.length > 0 ? arguments[0] : undefined; 12 | }); 13 | }; 14 | }; 15 | Q.makePromise.prototype.thenLog = thenLogWithFunc(grunt.log.writeln); 16 | Q.makePromise.prototype.thenLogOk = thenLogWithFunc(function(){grunt.log.ok("OK")}); 17 | Q.makePromise.prototype.thenLogVerbose = thenLogWithFunc(grunt.verbose.writeln); 18 | })(); 19 | 20 | // add support for promises to simpleGit 21 | // use it by calling git.promising('checkout', '0.1.0') 22 | var git = require('simple-git/src/git'); 23 | git.prototype.promising = function(fnName) { 24 | var arguments = Array.prototype.slice.call(arguments, 1); 25 | grunt.log.write("git", fnName, arguments.join(), "..."); 26 | return Q.nfapply(this[fnName].bind(this), arguments).thenLogOk(); 27 | }; 28 | git.prototype.fetchTags = function (then) { 29 | return this._run(['fetch', '--tags'], function (err, data) { 30 | then && then(err, !err && this._parseFetch(data)); 31 | }); 32 | }; 33 | 34 | // promise wrapper around exec + logging 35 | var _exec = Q.nfbind(require('child_process').exec); 36 | var exec = function(cmd, options) { 37 | grunt.log.write("exec:", cmd, options || {}, "..."); 38 | return _exec.apply(undefined, arguments).thenLogOk(); 39 | }; 40 | 41 | return { 42 | exec: exec 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /test/generated/graphicsTypes.js: -------------------------------------------------------------------------------- 1 | /*eslint "no-unused-expressions": 0*/ 2 | /* globals describe:false, it:false, before:false, beforeEach:false */ 3 | 4 | var Module = require('../../src/transpiled.js'); 5 | var addManualSymbols = require('../../src/symbols-manual.js').addManualSymbols; 6 | var addGeneratedSymbols = 7 | require('../../src/symbols-generated.js').addGeneratedSymbols; 8 | var expect = require('chai').expect; 9 | 10 | function instance() { 11 | var rocky = {module: Module()}; 12 | addManualSymbols(rocky); 13 | addGeneratedSymbols(rocky); 14 | return rocky; 15 | } 16 | 17 | describe('Module', function() { 18 | it('exists', function() { 19 | expect(Module).to.not.be.an('undefined'); 20 | }); 21 | 22 | it('can create instances', function() { 23 | expect(instance()).to.not.be.an('undefined'); 24 | }); 25 | 26 | it('grants access to functions', function() { 27 | expect(instance().grect_standardize).to.be.a('function'); 28 | }); 29 | }); 30 | 31 | describe('Graphic Types', function() { 32 | var rocky; 33 | var GRect; 34 | var gcolor_legible_over; 35 | var gpoint_equal; 36 | var grect_equal; 37 | var grect_is_emtpy; 38 | var grect_standardize; 39 | var grect_clip; 40 | var grect_contains_point; 41 | var grect_center_point; 42 | var grect_crop; 43 | var grect_align; 44 | var gpoint_from_polar; 45 | var grect_centered_from_polar; 46 | var gbitmap_get_format; 47 | var gbitmap_get_bounds; 48 | 49 | before(function() { 50 | rocky = instance(); 51 | GRect = rocky.GRect; 52 | gcolor_legible_over = rocky.gcolor_legible_over; 53 | gpoint_equal = rocky.gpoint_equal; 54 | grect_equal = rocky.grect_equal; 55 | grect_is_emtpy = rocky.grect_is_emtpy; 56 | grect_standardize = rocky.grect_standardize; 57 | grect_clip = rocky.grect_clip; 58 | grect_contains_point = rocky.grect_contains_point; 59 | grect_center_point = rocky.grect_center_point; 60 | grect_crop = rocky.grect_crop; 61 | grect_align = rocky.grect_align; 62 | gpoint_from_polar = rocky.gpoint_from_polar; 63 | grect_centered_from_polar = rocky.grect_centered_from_polar; 64 | gbitmap_get_format = rocky.gbitmap_get_format; 65 | gbitmap_get_bounds = rocky.gbitmap_get_bounds; 66 | }); 67 | 68 | describe('gcolor_legible_over', function() { 69 | it('works for common cases', function() { 70 | expect(gcolor_legible_over(rocky.GColorYellow)).to.equal(rocky.GColorBlack); 71 | }); 72 | }); 73 | 74 | describe('gpoint_equal', function() { 75 | it('works for common cases', function() { 76 | expect(gpoint_equal([10, 10], [10, 10])).to.be.true; 77 | expect(gpoint_equal([10, 10], [5, 7])).to.be.false; 78 | }); 79 | }); 80 | describe('grect_equal', function() { 81 | it('works for common cases', function() { 82 | expect(grect_equal([1, 2, 3, 4], [1, 2, 3, 4])).to.be.true; 83 | expect(grect_equal([1, 2, 3, 4], [1, 2, 3, 5])).to.be.false; 84 | }); 85 | }); 86 | describe('grect_is_emtpy', function() { 87 | it('works for common cases', function() { 88 | expect(grect_is_emtpy([1, 2, 0, 0])).to.be.true; 89 | expect(grect_is_emtpy([1, 2, 3, 4])).to.be.false; 90 | }); 91 | }); 92 | describe('grect_standardize', function() { 93 | it('changes a rect', function() { 94 | var rect = GRect(10, 10, -5, 15); 95 | grect_standardize(rect); 96 | expect(rect).to.eql({x: 5, y: 10, w: 5, h: 15}); 97 | }); 98 | }); 99 | describe('grect_clip', function() { 100 | it('changes a rect', function() { 101 | var rect = GRect(10, 10, 10, 10); 102 | grect_clip(rect, [15, 15, 10, 10]); 103 | expect(rect).to.eql({x: 15, y: 15, w: 5, h: 5}); 104 | }); 105 | }); 106 | describe('grect_contains_point', function() { 107 | it('works for common cases', function() { 108 | expect(grect_contains_point([10, 10, 5, 5], [12, 12])).to.be.true; 109 | expect(grect_contains_point([10, 10, 5, 5], [5, 12])).to.be.false; 110 | }); 111 | }); 112 | describe('grect_center_point', function() { 113 | it('works for common cases', function() { 114 | expect(grect_center_point([10, 10, 5, 8])).to.eql({x: 12, y: 14}); 115 | }); 116 | }); 117 | describe('grect_crop', function() { 118 | it('works for common cases', function() { 119 | expect(grect_crop([10, 20, 5, 8], 2)).to.eql({x: 12, y: 22, w: 1, h: 4}); 120 | }); 121 | }); 122 | describe('grect_align', function() { 123 | it('works for common cases', function() { 124 | var rect = GRect(1, 2, 3, 4); 125 | grect_align(rect, [10, 20, 30, 40], rocky.GAlignTopLeft, false); 126 | expect(rect).to.eql({x: 10, y: 20, w: 3, h: 4}); 127 | }); 128 | }); 129 | describe('gpoint_from_polar', function() { 130 | it('works for common cases', function() { 131 | var pt = gpoint_from_polar([10, 20, 30, 40], 132 | rocky.GOvalScaleModeFitCircle, Math.PI); 133 | expect(pt).to.eql({x: 24, y: 54}); 134 | }); 135 | }); 136 | describe('grect_centered_from_polar', function() { 137 | it('works for common cases', function() { 138 | var rect = grect_centered_from_polar([10, 20, 30, 40], 139 | rocky.GOvalScaleModeFitCircle, Math.PI, [4, 5]); 140 | expect(rect).to.eql({x: 23, y: 52, w: 4, h: 5}); 141 | }); 142 | }); 143 | describe('GBitmap', function() { 144 | var data = new Int8Array([0x0c,0x00,0x08,0x10,0x00,0x00,0x00,0x00,0x17,0x00,0x19,0x00,0x00,0x00,0x00,0x44,0x44,0x44,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x44,0x55,0x55,0x55,0x54,0x40,0x00,0x00,0x00,0x00,0x00,0x04,0x55,0x55,0x55,0x55,0x55,0x54,0x00,0x00,0x00,0x00,0x00,0x45,0x55,0x55,0x55,0x55,0x55,0x55,0x40,0x00,0x00,0x00,0x00,0x45,0x55,0x55,0x55,0x55,0x55,0x55,0x40,0x00,0x00,0x00,0x04,0x55,0x35,0x55,0x55,0x55,0x55,0x35,0x54,0x00,0x00,0x00,0x04,0x55,0x35,0x55,0x55,0x55,0x55,0x35,0x54,0x00,0x00,0x00,0x04,0x55,0x45,0x15,0x55,0x55,0x15,0x35,0x54,0x00,0x00,0x00,0x04,0x53,0x45,0x55,0x55,0x55,0x55,0x43,0x54,0x00,0x00,0x00,0x04,0x33,0x45,0x55,0x11,0x15,0x55,0x43,0x34,0x00,0x00,0x00,0x04,0x34,0x04,0x55,0x51,0x55,0x54,0x04,0x34,0x00,0x00,0x00,0x00,0x40,0x00,0x44,0x33,0x34,0x40,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x04,0x53,0x33,0x33,0x54,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x45,0x55,0x56,0x55,0x55,0x40,0x00,0x00,0x44,0x00,0x00,0x00,0x45,0x55,0x66,0x65,0x55,0x40,0x00,0x00,0x43,0x40,0x00,0x04,0x55,0x53,0x66,0x63,0x55,0x54,0x00,0x04,0x33,0x40,0x04,0x44,0x35,0x53,0x66,0x63,0x55,0x34,0x44,0x04,0x35,0x40,0x45,0x55,0x34,0x55,0x56,0x55,0x54,0x35,0x55,0x43,0x35,0x40,0x45,0x53,0x34,0x55,0x45,0x45,0x54,0x33,0x55,0x43,0x55,0x40,0x45,0x53,0x33,0x45,0x45,0x45,0x43,0x33,0x55,0x45,0x54,0x00,0x45,0x55,0x34,0x45,0x54,0x55,0x44,0x35,0x55,0x45,0x54,0x00,0x04,0x55,0x55,0x45,0x54,0x55,0x45,0x55,0x54,0x55,0x40,0x00,0x00,0x45,0x55,0x45,0x54,0x55,0x45,0x55,0x44,0x44,0x00,0x00,0x00,0x04,0x24,0x33,0x34,0x33,0x34,0x24,0x00,0x00,0x00,0x00,0x00,0x04,0x44,0x44,0x44,0x44,0x44,0x44,0x00,0x00,0x00,0x00,0x00,0xc0,0xe4,0xe9,0xd4,0xf9,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]); // eslint-disable-line 145 | var bmp; 146 | beforeEach(function() { 147 | bmp = rocky.gbitmap_create_with_data(data); 148 | }); 149 | 150 | describe('gbitmap_get_format', function() { 151 | it('fails for null and undefined', function() { 152 | expect(function() {gbitmap_get_format(null);}).to.throw(); 153 | expect(function() {gbitmap_get_format(undefined);}).to.throw(); 154 | }); 155 | 156 | it('works for common cases', function() { 157 | expect(gbitmap_get_format(bmp)).to.equal(4); 158 | bmp.data = null; 159 | expect(gbitmap_get_format(bmp)).to.equal(0xff); 160 | }); 161 | }); 162 | 163 | describe('gbitmap_get_bounds', function() { 164 | it('fails for null and undefined', function() { 165 | expect(function() {gbitmap_get_bounds(null);}).to.throw(); 166 | expect(function() {gbitmap_get_bounds(undefined);}).to.throw(); 167 | }); 168 | 169 | it('works for common cases', function() { 170 | expect(gbitmap_get_bounds(bmp)).to.eql({x: 0, y: 0, w: 23, h: 25}); 171 | bmp.data = null; 172 | expect(gbitmap_get_bounds(bmp)).to.eql({x: 0, y: 0, w: 0, h: 0}); 173 | }); 174 | }); 175 | }); 176 | 177 | }); 178 | -------------------------------------------------------------------------------- /test/manual/apng.js: -------------------------------------------------------------------------------- 1 | /*eslint "no-unused-expressions": 0*/ 2 | /*eslint max-len: [2, 100, 4]*/ 3 | /* globals describe:false, it:false */ 4 | /* globals before:false, beforeEach: false, afterEach: false */ 5 | 6 | global.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; 7 | var Module = require('../../src/transpiled.js'); 8 | var addManualSymbols = require('../../src/symbols-manual.js').addManualSymbols; 9 | var addGeneratedSymbols = 10 | require('../../src/symbols-generated.js').addGeneratedSymbols; 11 | var expect = require('chai').expect; 12 | var sinon = require('sinon'); 13 | global.atob = require('atob'); 14 | 15 | describe('GBitmapSequence', function() { 16 | var rocky, sandbox; 17 | var apngData, sequence; 18 | 19 | before(function() { 20 | rocky = { 21 | module: Module() 22 | }; 23 | addManualSymbols(rocky); 24 | addGeneratedSymbols(rocky); 25 | }); 26 | 27 | beforeEach(function() { 28 | sandbox = sinon.sandbox.create(); 29 | apngData = [0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x23,0x08,0x03,0x00,0x00,0x00,0x2d,0xf2,0x93,0x56,0x00,0x00,0x00,0x08,0x61,0x63,0x54,0x4c,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0xb9,0x3d,0x8b,0xd1,0x00,0x00,0x00,0x18,0x50,0x4c,0x54,0x45,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0xcc,0xcc,0x00,0x33,0x66,0x00,0xff,0x00,0x00,0x99,0x99,0x00,0x88,0x64,0x16,0x55,0x00,0x00,0x00,0x01,0x74,0x52,0x4e,0x53,0x00,0x40,0xe6,0xd8,0x66,0x00,0x00,0x00,0x1a,0x66,0x63,0x54,0x4c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0x64,0x00,0x00,0x69,0x21,0x1e,0xf0,0x00,0x00,0x00,0x96,0x49,0x44,0x41,0x54,0x38,0xcb,0xdd,0xd2,0x41,0x0a,0xc0,0x30,0x08,0x44,0xd1,0xf9,0x31,0xa6,0xf7,0xbf,0x71,0x91,0x10,0x02,0x45,0x74,0x5f,0x17,0xc5,0x92,0xc7,0x10,0x6d,0xf5,0xeb,0x82,0x16,0x74,0x84,0xe7,0x69,0xc5,0x80,0x5a,0x8c,0x31,0xa0,0x13,0x13,0x3a,0x31,0x29,0x27,0xa1,0x0a,0xc1,0x30,0x33,0x8a,0x10,0x80,0x23,0xda,0x8c,0xe6,0x1e,0x4c,0x9a,0x75,0x94,0x02,0x58,0xab,0x12,0x62,0x05,0xa8,0x05,0x74,0x2b,0xdb,0x4b,0xcd,0xcf,0xe2,0x39,0x36,0x40,0xc0,0x07,0x98,0x81,0x38,0x42,0x60,0xc6,0x57,0x18,0x37,0x62,0xbf,0x67,0x19,0x01,0x22,0x22,0xc9,0x10,0x01,0xd8,0x00,0xc9,0xb3,0xff,0xd5,0x75,0x80,0xcb,0x5d,0x49,0xb9,0x43,0x9c,0x23,0xf7,0x1c,0x84,0x00,0xdc,0x73,0x70,0xe7,0x4f,0xee,0x70,0xa7,0x85,0x4d,0xa3,0x4f,0x44,0x14,0xb7,0xcb,0xbf,0x3b,0xdc,0xe6,0xd4,0x0b,0x06,0x5a,0x02,0x92,0x65,0x31,0x30,0x02,0x00,0x00,0x00,0x1a,0x66,0x63,0x54,0x4c,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x04,0x00,0x0a,0x00,0x64,0x00,0x00,0x5b,0x9d,0x42,0xda,0x00,0x00,0x00,0x98,0x66,0x64,0x41,0x54,0x00,0x00,0x00,0x02,0x38,0xcb,0x75,0xd2,0x51,0x0e,0xc4,0x20,0x08,0x04,0x50,0x47,0xc4,0xde,0xff,0xc6,0x2d,0x4c,0x24,0xee,0x8e,0xf2,0x45,0x78,0x1d,0x5b,0x4c,0xdb,0x56,0x40,0xd3,0x22,0xdc,0x08,0xcf,0x73,0x95,0x0e,0x9c,0xa5,0xf7,0x22,0x95,0x51,0x24,0x32,0x54,0x10,0x75,0x0a,0xc1,0x60,0x66,0x60,0x48,0x22,0x22,0x92,0x39,0xbf,0x07,0x4b,0x64,0x1d,0x8a,0x86,0xe6,0x0c,0x51,0x9a,0x01,0x67,0x01,0x6e,0xab,0xd6,0x25,0xd4,0x64,0x7d,0x01,0x65,0xcd,0x60,0x46,0x02,0x21,0x67,0x31,0x0c,0x31,0x3e,0x56,0x11,0xce,0x2a,0xb3,0x45,0x98,0xc9,0x33,0xdd,0x9b,0x23,0x61,0x64,0xef,0xf5,0x47,0x64,0x4f,0x40,0xf3,0xcf,0xf6,0x2a,0xf8,0x2f,0x47,0x6c,0xaf,0xc0,0x93,0xf3,0x24,0x91,0x6a,0xf4,0x96,0xb5,0xe5,0x5e,0x6b,0x27,0xb6,0xbf,0x62,0xd8,0xbb,0xa2,0xa8,0xea,0x16,0xbc,0xa0,0xb2,0x02,0xaa,0x1f,0xae,0xa3,0x53,0x00,0x00,0x00,0x1a,0x66,0x63,0x54,0x4c,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0x64,0x01,0x00,0x10,0xb3,0x49,0x14,0x00,0x00,0x00,0xa4,0x66,0x64,0x41,0x54,0x00,0x00,0x00,0x04,0x38,0xcb,0xa5,0x91,0x41,0x0e,0xc3,0x20,0x0c,0x04,0x19,0x03,0xe5,0xff,0x3f,0x6e,0xed,0x28,0x75,0xdc,0x48,0x8b,0xd4,0x2c,0x07,0x1c,0xcf,0xc4,0x20,0xd1,0x7e,0x02,0x4d,0x86,0xb5,0x78,0x28,0x8c,0x05,0x8a,0x63,0x36,0xa4,0x60,0x2e,0x20,0x85,0xfb,0x08,0x20,0x4b,0x0c,0xa0,0x20,0x7a,0x3f,0x3a,0x51,0x76,0x7a,0x7e,0xe3,0x2c,0xba,0x5f,0x21,0x56,0xfd,0x61,0x3f,0xa1,0x01,0xb3,0xde,0xe1,0xbc,0xe5,0x84,0xb3,0x9a,0x87,0x82,0x79,0x16,0x5c,0x9b,0x57,0x85,0xc8,0x0b,0x12,0x57,0x85,0x97,0xc7,0x8f,0x48,0x5c,0x14,0x70,0x6e,0x83,0xe4,0x35,0x18,0xfa,0x35,0x30,0xcf,0x78,0x2e,0xb0,0x15,0x36,0xaf,0xbd,0x15,0x50,0x82,0x1e,0xd0,0x08,0xae,0x04,0x08,0x2e,0x0d,0xa6,0x12,0x1c,0x4e,0xc5,0x43,0x10,0xc6,0x81,0xfe,0x16,0xf0,0xe4,0x7e,0xc7,0xfd,0x93,0x10,0xa2,0xa8,0x4a,0xb6,0x8b,0x9a,0xa9,0x83,0xeb,0x21,0x6f,0x0d,0x46,0x02,0xf3,0x68,0x92,0x5c,0x07,0x00,0x00,0x00,0x1a,0x66,0x63,0x54,0x4c,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x1b,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x0a,0x00,0x64,0x01,0x00,0x5d,0x51,0x4d,0xe4,0x00,0x00,0x00,0x9f,0x66,0x64,0x41,0x54,0x00,0x00,0x00,0x06,0x38,0xcb,0x7d,0xd2,0x01,0x0e,0x84,0x20,0x0c,0x44,0x51,0x7e,0x4b,0xe5,0xfe,0x37,0x36,0xd4,0x55,0x20,0xe9,0x6c,0x8d,0xa6,0xe1,0x31,0xa0,0xc4,0x76,0x14,0x34,0x55,0x8c,0x81,0x34,0x6d,0x30,0xba,0xb4,0x6e,0x06,0x35,0x4d,0x33,0x54,0x4c,0x1b,0x60,0x40,0x49,0xee,0x9e,0x77,0x69,0x79,0xed,0x06,0x55,0x2e,0xd7,0xc6,0xfd,0x51,0xfa,0xb6,0x1f,0xcc,0x39,0xdb,0xcc,0x61,0xdf,0x8b,0xe6,0xe8,0x96,0xe3,0x02,0x8e,0x5c,0x03,0xe2,0xb7,0xe6,0x35,0x2b,0x2d,0xbe,0x6f,0x89,0x48,0x33,0x26,0xf1,0x0e,0xb4,0x17,0xd3,0x0c,0x30,0x0e,0x4a,0x0c,0x7a,0xb7,0x59,0x44,0x44,0x75,0xd4,0xf2,0x40,0xff,0x5b,0xd7,0x86,0xb4,0x0c,0x6a,0x4b,0xa4,0x29,0x04,0x61,0x01,0x10,0xc2,0xf2,0x51,0xd3,0x63,0x21,0x62,0x22,0x08,0xac,0x5d,0x8b,0xbf,0x8c,0xd5,0xed,0xe6,0xb3,0x58,0xdd,0x6e,0x59,0x67,0x7b,0x03,0xdd,0xcc,0x02,0xb9,0x61,0xc0,0x85,0x91,0x00,0x00,0x00,0x1a,0x66,0x63,0x54,0x4c,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x1d,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x06,0x00,0x0a,0x00,0x64,0x00,0x00,0x31,0xeb,0x39,0xbe,0x00,0x00,0x00,0x89,0x66,0x64,0x41,0x54,0x00,0x00,0x00,0x08,0x38,0xcb,0x6d,0xce,0x41,0x0e,0x04,0x21,0x08,0x44,0x51,0xbe,0x34,0xf6,0xfd,0x6f,0x3c,0x53,0x84,0x04,0xd3,0x58,0x0b,0x53,0xbe,0x10,0xd1,0x3a,0x60,0x33,0x70,0x63,0xde,0x97,0x89,0x2c,0xe9,0xc0,0xb5,0x18,0xf8,0xdc,0xf4,0x29,0x1d,0xa3,0x28,0xdf,0x51,0xdc,0x1d,0x67,0x2a,0x30,0x75,0xce,0x72,0x7d,0x97,0xf9,0x61,0xe9,0xde,0xc0,0x57,0xc5,0xbb,0xb1,0xb7,0x71,0x53,0xad,0x6b,0x56,0x47,0x2c,0xaf,0xbb,0xe1,0x2e,0x2e,0xc5,0x10,0x48,0xfd,0x1c,0xc6,0x4b,0x41,0x2f,0x27,0x53,0xb3,0x06,0x84,0xce,0x64,0xc4,0xb5,0x33,0xc2,0x82,0x62,0x0b,0x49,0x71,0x18,0x72,0xf8,0xb7,0x66,0x05,0x25,0x9a,0x81,0xb3,0x57,0xd1,0x6f,0x20,0x29,0x7b,0xa9,0xc2,0xd9,0x92,0x33,0x5d,0x7e,0x9d,0xd6,0x02,0x92,0x0a,0x40,0xc5,0xf7,0x00,0x00,0x00,0x1a,0x66,0x63,0x54,0x4c,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x04,0x00,0x0a,0x00,0x64,0x00,0x00,0xeb,0xfd,0x72,0x7e,0x00,0x00,0x00,0x94,0x66,0x64,0x41,0x54,0x00,0x00,0x00,0x0a,0x38,0xcb,0x95,0xcf,0x51,0x0e,0xc5,0x20,0x08,0x44,0x51,0xaf,0x94,0x76,0xff,0x3b,0x7e,0x10,0x42,0xb4,0x05,0x3f,0x1e,0x5f,0x13,0x4f,0x46,0x65,0xec,0x03,0xa3,0x1f,0x38,0x11,0xcf,0x43,0x0f,0xcc,0x94,0x02,0x73,0xd2,0xc2,0x75,0x92,0x2b,0xa4,0xaf,0xe0,0xd3,0x55,0x10,0x11,0x84,0x5e,0x00,0xfe,0xe8,0x70,0x7c,0x87,0x5c,0xa8,0xca,0x7d,0x03,0x9d,0x18,0x99,0xf5,0x8b,0xc2,0x49,0xa6,0xd9,0x4e,0x40,0x8a,0xdb,0x3a,0x43,0x04,0x3c,0x07,0x11,0x20,0x82,0x8b,0x6c,0x25,0x3c,0xc7,0x59,0x74,0xf6,0x52,0x76,0x06,0xa0,0x3a,0x54,0x4d,0x9c,0xf0,0xbc,0x7e,0xa2,0x66,0x04,0x81,0xe5,0xcf,0x24,0x8d,0x3a,0xb8,0x81,0x16,0x50,0x75,0xeb,0x6e,0xd3,0x12,0xf2,0x26,0x6a,0x8c,0xdd,0x72,0xaf,0x88,0x6f,0x11,0xb6,0xb4,0xc8,0xe7,0x95,0x7e,0xa0,0xba,0x02,0xaa,0x14,0x0c,0x6a,0xc2,0x00,0x00,0x00,0x1a,0x66,0x63,0x54,0x4c,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0x64,0x01,0x00,0xd1,0x84,0xd5,0xf2,0x00,0x00,0x00,0xa6,0x66,0x64,0x41,0x54,0x00,0x00,0x00,0x0c,0x38,0xcb,0xa5,0x92,0x01,0x0e,0x03,0x21,0x08,0x04,0x19,0x95,0xf3,0xff,0x3f,0x6e,0xa0,0xb5,0xc4,0xa3,0xc1,0xa4,0xb7,0xc6,0x08,0xec,0x04,0x8d,0x2a,0x37,0x81,0x94,0x62,0x4e,0x1e,0x01,0x30,0x47,0x0d,0x8c,0xd6,0xa0,0xf0,0x0d,0x68,0xd4,0x0d,0x32,0x00,0x44,0x08,0x0d,0xd8,0x2c,0x7a,0x87,0x95,0xf7,0xde,0x7d,0x46,0x8e,0xec,0x05,0x1f,0x2b,0x77,0xeb,0xdc,0x41,0x00,0x5d,0x67,0xdc,0xcf,0xa0,0xdf,0x50,0xd5,0x11,0x98,0xcd,0x44,0x14,0x4d,0x91,0xc1,0x85,0x2b,0xec,0x0d,0x11,0x06,0x97,0x89,0xb0,0x77,0x82,0xd1,0x30,0x1f,0xd5,0xea,0x25,0x88,0xab,0xcc,0x80,0x5f,0xf5,0x13,0x80,0x03,0x20,0x9c,0x81,0x71,0x00,0xc8,0x40,0x6e,0x51,0x03,0x4e,0x20,0x25,0x01,0x15,0xa0,0xd4,0xbe,0xa8,0xcd,0xca,0x77,0xa0,0x20,0xde,0xd6,0xdf,0x00,0xa6,0x58,0xb3,0xdd,0x3f,0xff,0xd9,0x83,0x8c,0x44,0x39,0xd0,0xbc,0xc1,0x8f,0xf8,0x05,0x0d,0xb2,0x02,0xf3,0x3f,0x32,0xc6,0x71,0x00,0x00,0x00,0x1a,0x66,0x63,0x54,0x4c,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x1b,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x02,0x00,0x0a,0x00,0x64,0x00,0x00,0xf4,0x2a,0x4c,0x01,0x00,0x00,0x00,0xa2,0x66,0x64,0x41,0x54,0x00,0x00,0x00,0x0e,0x38,0xcb,0x9d,0xd2,0xdb,0x0e,0x84,0x20,0x0c,0x45,0x51,0x77,0x69,0xe5,0xff,0xff,0x78,0xd2,0x92,0x9c,0x40,0x42,0x7d,0x98,0x63,0x34,0xd0,0x65,0xbd,0xa0,0xcf,0x1e,0x78,0xba,0x30,0x27,0x7f,0x99,0x4f,0x68,0x08,0x33,0xef,0xcc,0xd2,0xe8,0xac,0x6b,0x04,0x0c,0xe0,0x46,0x63,0x8c,0xda,0xb9,0x59,0x6d,0xa7,0x01,0x97,0x3e,0xa0,0x6a,0xb0,0xdd,0xcf,0xd7,0x24,0xcb,0xeb,0x7c,0x3d,0xa6,0xcd,0xed,0x2a,0x47,0x1f,0xf0,0x82,0xfa,0xaa,0x16,0x35,0x7d,0x33,0x78,0x59,0xe8,0x65,0x22,0x11,0x92,0xcc,0x59,0x85,0x8c,0x10,0x63,0xad,0xa8,0x48,0x18,0x58,0xc6,0x9d,0x10,0x29,0x69,0x5a,0xec,0xc6,0xfa,0xcf,0xf0,0x65,0x34,0xd6,0xb4,0xa5,0x15,0x35,0x06,0x1d,0xd5,0x32,0x11,0x8d,0xd5,0xe1,0x4e,0xcb,0xa2,0x69,0xbb,0x37,0x02,0xa1,0xd1,0x29,0xfa,0x4f,0x34,0x52,0x18,0x19,0x34,0x3a,0xac,0x72,0x0c,0x2b,0x3f,0xe3,0x10,0x02,0xba,0x09,0xd5,0xf1,0xf9,0x00,0x00,0x00,0x18,0x74,0x45,0x58,0x74,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x00,0x67,0x69,0x66,0x32,0x61,0x70,0x6e,0x67,0x2e,0x73,0x66,0x2e,0x6e,0x65,0x74,0x96,0xff,0x13,0xc8,0x00,0x00,0x00,0x00,0x49,0x45,0x4e,0x44,0xae,0x42,0x60,0x82]; // eslint-disable-line 30 | sequence = rocky.gbitmap_sequence_create_with_data(apngData); 31 | }); 32 | 33 | afterEach(function() { 34 | sandbox.verify(); 35 | sandbox.restore(); 36 | }); 37 | 38 | it('supports gbitmap_sequence_create_with_data', function() { 39 | expect(sequence).to.not.be.undefined; 40 | }); 41 | 42 | it('supports capture and release', function() { 43 | expect(sequence).to.not.have.a.property('currentFrameIdx'); 44 | expect(sequence).to.not.have.a.property('playCount'); 45 | var ptr = sequence.captureCPointer(); 46 | expect(sequence) 47 | .to.have.a.property('currentFrameIdx') 48 | .which.equals(0); 49 | expect(sequence) 50 | .to.have.a.property('playCount') 51 | .which.equals(-1); 52 | 53 | sequence.releaseCPointer(ptr); 54 | expect(sequence) 55 | .to.have.a.property('currentFrameIdx') 56 | .which.equals(0); 57 | expect(sequence) 58 | .to.have.a.property('playCount') 59 | .which.equals(-1); 60 | }); 61 | 62 | it('supports gbitmap_sequence_get_current_frame_delay_ms', function() { 63 | var delay = rocky.gbitmap_sequence_get_current_frame_delay_ms(sequence); 64 | // although 0 seems to be incorrect 65 | // it is what our C implementation returns for the first frame 66 | expect(delay).to.equal(0); 67 | // second frame makes more sense :) 68 | rocky.gbitmap_sequence_update_bitmap_next_frame(sequence); 69 | delay = rocky.gbitmap_sequence_get_current_frame_delay_ms(sequence); 70 | expect(delay).to.equal(100); 71 | }); 72 | 73 | it('supports gbitmap_sequence_get_total_num_frames', function() { 74 | var num_frames = rocky.gbitmap_sequence_get_total_num_frames(sequence); 75 | expect(num_frames).to.equal(8); 76 | }); 77 | 78 | it('supports play_count', function() { 79 | var play_count = rocky.gbitmap_sequence_get_play_count(sequence); 80 | expect(play_count).to.equal(-1); 81 | rocky.gbitmap_sequence_set_play_count(sequence, 123); 82 | play_count = rocky.gbitmap_sequence_get_play_count(sequence); 83 | expect(play_count).to.equal(123); 84 | }); 85 | 86 | it('supports gbitmap_sequence_get_bitmap_size', function() { 87 | var size = rocky.gbitmap_sequence_get_bitmap_size(sequence); 88 | expect(size).to.eql({w: 33, h: 35}); 89 | }); 90 | 91 | it('supports gbitmap_sequence_update_bitmap_next_frame', function() { 92 | expect(sequence) 93 | .to.not.have.a.property('currentFrameIdx'); 94 | var result = rocky.gbitmap_sequence_update_bitmap_next_frame(sequence); 95 | expect(result).to.be.true; 96 | expect(sequence) 97 | .to.have.a.property('currentFrameIdx') 98 | .which.equals(1); 99 | result = rocky.gbitmap_sequence_update_bitmap_next_frame(sequence); 100 | expect(result).to.be.true; 101 | expect(sequence) 102 | .to.have.a.property('currentFrameIdx') 103 | .which.equals(2); 104 | 105 | rocky.gbitmap_sequence_update_bitmap_next_frame(sequence); // 3 106 | rocky.gbitmap_sequence_update_bitmap_next_frame(sequence); // 4 107 | rocky.gbitmap_sequence_update_bitmap_next_frame(sequence); // 5 108 | rocky.gbitmap_sequence_update_bitmap_next_frame(sequence); // 6 109 | 110 | result = rocky.gbitmap_sequence_update_bitmap_next_frame(sequence); // 7 111 | expect(result).to.be.true; 112 | expect(sequence) 113 | .to.have.a.property('currentFrameIdx') 114 | .which.equals(7); 115 | 116 | result = rocky.gbitmap_sequence_update_bitmap_next_frame(sequence); // 0 117 | expect(result).to.be.true; 118 | expect(sequence) 119 | .to.have.a.property('currentFrameIdx') 120 | .which.equals(8); 121 | 122 | result = rocky.gbitmap_sequence_update_bitmap_next_frame(sequence); // 1 123 | expect(result).to.be.true; 124 | expect(sequence) 125 | .to.have.a.property('currentFrameIdx') 126 | .which.equals(1); 127 | }); 128 | 129 | it('supports gbitmap_sequence_seek_to_elapsed', function() { 130 | expect(sequence) 131 | .to.not.have.a.property('currentFrameIdx'); 132 | var result = rocky.gbitmap_sequence_seek_to_elapsed(sequence, 0); 133 | expect(result).to.be.true; 134 | expect(sequence) 135 | .to.have.a.property('currentFrameIdx') 136 | .which.equals(0); 137 | 138 | result = rocky.gbitmap_sequence_seek_to_elapsed(sequence, 1); 139 | expect(result).to.be.true; 140 | // as frame 0 incorrecly stores a duration of 0, any elapsed != 0 leads to frame 1 141 | expect(sequence) 142 | .to.have.a.property('currentFrameIdx') 143 | .which.equals(1); 144 | 145 | result = rocky.gbitmap_sequence_seek_to_elapsed(sequence, 100); 146 | expect(result).to.be.true; 147 | expect(sequence) 148 | .to.have.a.property('currentFrameIdx') 149 | .which.equals(1); 150 | 151 | result = rocky.gbitmap_sequence_seek_to_elapsed(sequence, 101); 152 | expect(result).to.be.true; 153 | expect(sequence) 154 | .to.have.a.property('currentFrameIdx') 155 | .which.equals(2); 156 | 157 | result = rocky.gbitmap_sequence_seek_to_elapsed(sequence, 550); 158 | expect(result).to.be.true; 159 | expect(sequence) 160 | .to.have.a.property('currentFrameIdx') 161 | .which.equals(1 + 5); 162 | 163 | // works with overflows 164 | result = rocky.gbitmap_sequence_seek_to_elapsed(sequence, 12 * 100); 165 | expect(result).to.be.true; 166 | expect(sequence) 167 | .to.have.a.property('currentFrameIdx') 168 | .which.equals(4); 169 | 170 | // works backwards :) 171 | result = rocky.gbitmap_sequence_seek_to_elapsed(sequence, 1); 172 | expect(result).to.be.true; 173 | expect(sequence) 174 | .to.have.a.property('currentFrameIdx') 175 | .which.equals(1); 176 | }); 177 | 178 | it('supports gbitmap_sequence_get_current_frame_idx', function() { 179 | var frameIdx = rocky.gbitmap_sequence_get_current_frame_idx(sequence); 180 | expect(frameIdx).to.equal(0); 181 | rocky.gbitmap_sequence_update_bitmap_next_frame(sequence); 182 | frameIdx = rocky.gbitmap_sequence_get_current_frame_idx(sequence); 183 | expect(frameIdx).to.equal(1); 184 | }); 185 | 186 | describe('graphics_draw_bitmap_sequence', function() { 187 | it('does not smoke', function() { 188 | rocky.graphics_draw_bitmap_sequence(0, sequence, [10, 20]); 189 | expect(sequence.currentFrameIdx).to.equal(0); 190 | }); 191 | it('can handle uninitialized data', function() { 192 | delete sequence.data; 193 | rocky.graphics_draw_bitmap_sequence(0, sequence, [10, 20]); 194 | }); 195 | }); 196 | 197 | describe('gbitmap_sequence_create', function() { 198 | it('reflects status and data according to Resources singleton', function() { 199 | var expectation = sandbox.mock(rocky.Resources) 200 | .expects('load').once().withArgs({ 201 | url: 'someUrl', convertPath: '/convert/imagesequence', proxyArgs: [] 202 | }) 203 | .returns('someInitialStatus'); 204 | var font = rocky.gbitmap_sequence_create({url: 'someUrl'}); 205 | var dataCallback = expectation.firstCall.args[1]; 206 | expect(dataCallback).to.be.a('function'); 207 | 208 | expect(font.status).to.equal('someInitialStatus'); 209 | var base64encoded = 'c2VxdWVuY2UgZGF0YQ=='; // "sequence data" 210 | dataCallback('new status', {output: {data: base64encoded}}); 211 | expect(font.status).to.equal('new status'); 212 | // first 8 bytes are removed 213 | expect(font.data).to.equal('sequence data'); 214 | }); 215 | }); 216 | 217 | }); 218 | -------------------------------------------------------------------------------- /test/manual/gbitmap.js: -------------------------------------------------------------------------------- 1 | /*eslint "no-unused-expressions": 0*/ 2 | /*eslint max-len: [2, 100, 4]*/ 3 | /* globals describe:false, it:false, xit:false, beforeEach:false, afterEach:false */ 4 | 5 | global.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; 6 | var symbols = require('../../src/symbols-manual.js').symbols; 7 | var expect = require('chai').expect; 8 | var sinon = require('sinon'); 9 | global.atob = require('atob'); 10 | 11 | describe('GBitmap', function() { 12 | 13 | var sandbox; 14 | beforeEach(function() { 15 | sandbox = sinon.sandbox.create(); 16 | symbols.module = {ccall: function() {}}; 17 | }); 18 | 19 | afterEach(function() { 20 | sandbox.verify(); 21 | delete symbols.module; 22 | sandbox.restore(); 23 | delete global.XMLHttpRequest; 24 | }); 25 | 26 | describe('gbitmap_create', function() { 27 | var gbitmap_create = symbols.gbitmap_create; 28 | it('creates an object with expected fields', function() { 29 | var bmp = gbitmap_create(); 30 | expect(bmp).to.be.an('object'); 31 | expect(bmp.status).to.be.a('string'); 32 | expect(bmp.captureCPointer).to.be.a('function'); 33 | expect(bmp.releaseCPointer).to.be.a('function'); 34 | }); 35 | 36 | it('implements captureCPointer for pbi', function() { 37 | var bmp = gbitmap_create(); 38 | bmp.data = 'some data'; 39 | sandbox.mock(symbols.Data) 40 | .expects('captureCPointerWithData').once().withArgs('some data') 41 | .returns([123, 10]); 42 | sandbox.mock(symbols.module) 43 | .expects('ccall').once() 44 | .withArgs('gbitmap_create_with_data', 'number', ['number'], [123]) 45 | .returns(456); 46 | 47 | expect(bmp.captureCPointer()).to.equal(456); 48 | expect(bmp.dataPtr).to.equal(123); 49 | expect(bmp.bmpPtr).to.equal(456); 50 | }); 51 | 52 | it('implements captureCPointer for png', function() { 53 | var bmp = gbitmap_create(); 54 | bmp.data = 'some data'; 55 | bmp.dataFormat = 'png'; 56 | sandbox.mock(symbols.Data) 57 | .expects('captureCPointerWithData').once().withArgs('some data') 58 | .returns([123, 10]); 59 | sandbox.mock(symbols.module) 60 | .expects('ccall').once() 61 | .withArgs('gbitmap_create_from_png_data', 'number', 62 | ['number', 'number'], [123, 10]) 63 | .returns(456); 64 | 65 | expect(bmp.captureCPointer()).to.equal(456); 66 | expect(bmp.dataPtr).to.equal(123); 67 | expect(bmp.bmpPtr).to.equal(456); 68 | }); 69 | 70 | it('implements releaseCPointer with Data and module', function() { 71 | var bmp = gbitmap_create(); 72 | bmp.dataPtr = 123; 73 | bmp.bmpPtr = 456; 74 | 75 | sandbox.mock(symbols.module) 76 | .expects('ccall').once() 77 | .withArgs('gbitmap_destroy', 'void', ['number'], [456]); 78 | sandbox.mock(symbols.Data) 79 | .expects('releaseCPointer').once().withArgs(123); 80 | 81 | bmp.releaseCPointer(bmp.bmpPtr); 82 | expect(bmp.dataPtr).to.be.undefined; 83 | expect(bmp.bmpPtr).to.be.undefined; 84 | }); 85 | 86 | it('reflects status and data according to Recources singleton', function() { 87 | var expectation = sandbox.mock(symbols.Resources) 88 | .expects('load').once() 89 | .withArgs({url: 'someUrl', convertPath: '/convert/image', proxyArgs: []}) 90 | .returns('someInitialStatus'); 91 | var bmp = gbitmap_create('someUrl'); 92 | var dataCallback = expectation.firstCall.args[1]; 93 | expect(dataCallback).to.be.a('function'); 94 | 95 | expect(bmp.status).to.equal('someInitialStatus'); 96 | var base64encoded = 'c29tZSBkYXRh'; // "some data" 97 | dataCallback('new status', {output: {data: base64encoded}}); 98 | expect(bmp.status).to.equal('new status'); 99 | expect(bmp.data).to.equal('some data'); 100 | }); 101 | 102 | describe('.onload and .onerror', function() { 103 | var bmp; 104 | var server; 105 | 106 | beforeEach(function() { 107 | server = sinon.fakeServer.create(); 108 | }); 109 | 110 | afterEach(function() { 111 | bmp.onload.verify(); 112 | bmp.onerror.verify(); 113 | server.restore(); 114 | }); 115 | 116 | it('calls .onload on success', function() { 117 | server.respondWith('GET', symbols.Resources.defaultProxy + '/convert/image?url=someUrl', 118 | [200, { 'Content-Type': 'application/json' }, 119 | '{"output": {"data": "c29tZSBkYXRh", "outputFormat": "png"}}']); 120 | // c29tZSBkYXRh is "some data" in base64 121 | 122 | bmp = gbitmap_create('someUrl'); 123 | expect(bmp.status).to.equal('loading'); 124 | 125 | bmp.onload = sinon.expectation.create('onload').once(); 126 | bmp.onerror = sinon.expectation.create('onerror').never(); 127 | 128 | server.respond(); 129 | expect(bmp.status).to.equal('loaded'); 130 | expect(bmp.data).to.equal('some data'); 131 | expect(bmp.dataFormat).to.equal('png'); 132 | }); 133 | 134 | xit('calls .onerror on failure', function() { 135 | bmp = gbitmap_create('someUrl'); 136 | expect(bmp.status).to.equal('loading'); 137 | 138 | bmp.onload = sinon.expectation.create('onload').never(); 139 | bmp.onerror = sinon.expectation.create('onerror').once(); 140 | 141 | server.respond(); 142 | expect(bmp.status).to.equal('error'); // 404 143 | }); 144 | }); 145 | }); 146 | 147 | describe('gbitmap_create_with_data', function() { 148 | it('simply passes the data', function() { 149 | var bmp = symbols.gbitmap_create_with_data('some data'); 150 | expect(bmp).to.be.an('object'); 151 | expect(bmp.status).to.equal('loaded'); 152 | expect(bmp.data).to.equal('some data'); 153 | expect(bmp.captureCPointer).to.be.a('function'); 154 | expect(bmp.releaseCPointer).to.be.a('function'); 155 | }); 156 | 157 | it('handles undefined data', function() { 158 | var bmp = symbols.gbitmap_create_with_data(); 159 | sandbox.mock(symbols.Data) 160 | .expects('captureCPointerWithData').once().withArgs(undefined).returns(0); 161 | sandbox.mock(symbols.module) 162 | .expects('ccall').never(); 163 | expect(bmp.captureCPointer()).to.equal(0); 164 | }); 165 | }); 166 | 167 | describe('Resources', function() { 168 | beforeEach(function() { 169 | this.defaultProxy = symbols.Resources.defaultProxy; 170 | }); 171 | 172 | afterEach(function() { 173 | symbols.Resources.defaultProxy = this.defaultProxy; 174 | }); 175 | 176 | describe('constructURL', function() { 177 | it('can handle errorneous cases', function() { 178 | var url = symbols.Resources.constructURL(undefined); 179 | expect(url).to.be.undefined; 180 | 181 | url = symbols.Resources.constructURL({}); 182 | expect(url).to.be.undefined; 183 | }); 184 | 185 | it('has a default proxy', function() { 186 | expect(symbols.Resources.defaultProxy).to.equal('http://butkus.pebbledev.com'); 187 | }); 188 | 189 | it('can handle a proxy', function() { 190 | var config = { 191 | url: 'http://foo.com?bar=baz', 192 | proxy: 'http://proxy.com' 193 | }; 194 | var url = symbols.Resources.constructURL(config); 195 | expect(url).to.equal( 196 | 'http://proxy.com?url=http%3A%2F%2Ffoo.com%3Fbar%3Dbaz' 197 | ); 198 | 199 | config.convertPath = '/convert/image'; 200 | url = symbols.Resources.constructURL(config); 201 | expect(url).to.equal( 202 | 'http://proxy.com/convert/image?url=http%3A%2F%2Ffoo.com%3Fbar%3Dbaz' 203 | ); 204 | }); 205 | 206 | it('can handle a default proxy and URL', function() { 207 | delete symbols.Resources.defaultProxy; 208 | var url = symbols.Resources.constructURL({url: 'http://foo.com?bar=baz'}); 209 | expect(url).to.equal('http://foo.com?bar=baz'); 210 | url = symbols.Resources.constructURL({ 211 | url: 'http://foo.com?bar=baz', convertPath: '/p'}); 212 | expect(url).to.equal('http://foo.com?bar=baz'); 213 | 214 | symbols.Resources.defaultProxy = 'http://proxy.com'; 215 | url = symbols.Resources.constructURL({url: 'http://foo.com?bar=baz'}); 216 | expect(url).to.equal( 217 | 'http://proxy.com?url=http%3A%2F%2Ffoo.com%3Fbar%3Dbaz' 218 | ); 219 | url = symbols.Resources.constructURL({ 220 | url: 'http://foo.com?bar=baz', convertPath: '/p'}); 221 | expect(url).to.equal( 222 | 'http://proxy.com/p?url=http%3A%2F%2Ffoo.com%3Fbar%3Dbaz' 223 | ); 224 | 225 | url = symbols.Resources.constructURL({ 226 | url: 'http://foo.com?bar=baz', 227 | proxy: 'http://overrulingProxy.com' 228 | }); 229 | expect(url).to.equal( 230 | 'http://overrulingProxy.com?url=http%3A%2F%2Ffoo.com%3Fbar%3Dbaz' 231 | ); 232 | url = symbols.Resources.constructURL({ 233 | url: 'http://foo.com?bar=baz', 234 | proxy: 'http://overrulingProxy.com', 235 | convertPath: '/p' 236 | }); 237 | expect(url).to.equal( 238 | 'http://overrulingProxy.com/p?url=http%3A%2F%2Ffoo.com%3Fbar%3Dbaz' 239 | ); 240 | 241 | delete symbols.Resources.defaultProxy; 242 | }); 243 | 244 | it('prefers dataURL over anything else', function() { 245 | var url = symbols.Resources.constructURL({ 246 | dataURL: 'dataURL', 247 | url: 'http://foo.com?bar=baz', 248 | proxy: 'http://proxy.com' 249 | }); 250 | expect(url).to.equal('dataURL'); 251 | }); 252 | 253 | it('adds proxy args', function() { 254 | var url = symbols.Resources.constructURL({ 255 | url: 'http://foo.com?bar=baz', 256 | proxy: 'http://proxy.com', 257 | proxyArgs: [] 258 | }); 259 | expect(url).to.equal( 260 | 'http://proxy.com?url=http%3A%2F%2Ffoo.com%3Fbar%3Dbaz' 261 | ); 262 | 263 | url = symbols.Resources.constructURL({ 264 | url: 'http://foo.com?bar=baz', 265 | proxy: 'http://proxy.com', 266 | proxyArgs: [['a', 123], ['b', '3:2']] 267 | }); 268 | expect(url).to.equal( 269 | 'http://proxy.com?url=http%3A%2F%2Ffoo.com%3Fbar%3Dbaz&a=123&b=3%3A2' 270 | ); 271 | }); 272 | }); 273 | 274 | describe('config', function() { 275 | it('fills config with empty proxy args', function() { 276 | var config = symbols.Resources.config({ 277 | url: 'http://foo', 278 | someArg: 123, 279 | anotherArg: 456 280 | }, 'convert/some'); 281 | expect(config).to.eql({ 282 | url: 'http://foo', 283 | convertPath: 'convert/some', 284 | anotherArg: 456, 285 | someArg: 123, 286 | proxyArgs: [] 287 | }); 288 | }); 289 | 290 | it('maps proxy args with values from config', function() { 291 | var config = symbols.Resources.config({ 292 | url: 'http://foo', 293 | someArg: 123, 294 | anotherArg: 456 295 | }, 'convert/some', ['someArg']); 296 | expect(config).to.eql({ 297 | url: 'http://foo', 298 | convertPath: 'convert/some', 299 | anotherArg: 456, 300 | someArg: 123, 301 | proxyArgs: [['someArg', 123]] 302 | }); 303 | }); 304 | 305 | it('keeps configs proxy args with values from config', function() { 306 | var config = symbols.Resources.config({ 307 | url: 'http://foo', 308 | someArg: 123, 309 | anotherArg: 456, 310 | proxyArgs: [['a', 'b']] 311 | }, 'convert/some', ['someArg']); 312 | expect(config).to.eql({ 313 | url: 'http://foo', 314 | convertPath: 'convert/some', 315 | anotherArg: 456, 316 | someArg: 123, 317 | proxyArgs: [['a', 'b']] 318 | }); 319 | }); 320 | }); 321 | }); 322 | 323 | }); 324 | -------------------------------------------------------------------------------- /test/manual/gfont.js: -------------------------------------------------------------------------------- 1 | /*eslint "no-unused-expressions": 0*/ 2 | /*eslint max-len: [2, 100, 4]*/ 3 | /* globals describe:false, it:false, xit: false */ 4 | /* globals before:false, beforeEach: false, afterEach: false */ 5 | 6 | var Module = require('../../src/transpiled.js'); 7 | var addManualSymbols = require('../../src/symbols-manual.js').addManualSymbols; 8 | var addGeneratedSymbols = 9 | require('../../src/symbols-generated.js').addGeneratedSymbols; 10 | var expect = require('chai').expect; 11 | var sinon = require('sinon'); 12 | global.atob = require('atob'); 13 | 14 | describe('GFont', function() { 15 | var rocky; 16 | before(function() { 17 | rocky = { 18 | module: Module() 19 | }; 20 | addManualSymbols(rocky); 21 | addGeneratedSymbols(rocky); 22 | }); 23 | 24 | describe('fonts_get_system_font', function() { 25 | var fonts_get_system_font; 26 | before(function() { 27 | fonts_get_system_font = rocky.fonts_get_system_font; 28 | }); 29 | 30 | it('returns null for some cases', function() { 31 | expect(fonts_get_system_font(undefined)).to.be.null; 32 | expect(fonts_get_system_font(null)).to.be.null; 33 | }); 34 | 35 | xit('returns callback font for unknown keys', function() { 36 | var font = fonts_get_system_font('unknownFontId'); 37 | expect(font).to.have.a.property('captureCPointer').which.is.a('function'); 38 | expect(font).to.have.a.property('releaseCPointer').which.is.a('function'); 39 | expect(font.captureCPointer()).to.not.equal(0); 40 | }); 41 | 42 | it('returns works with valid system fonts', function() { 43 | var font = fonts_get_system_font('RESOURCE_ID_GOTHIC_18_BOLD'); 44 | expect(font).to.have.a.property('captureCPointer').which.is.a('function'); 45 | expect(font).to.have.a.property('releaseCPointer').which.is.a('function'); 46 | expect(font).to.have.a.property('status').equal('loaded'); 47 | 48 | expect(font.captureCPointer()).to.not.equal(0); 49 | }); 50 | }); 51 | 52 | describe('graphics_text_layout_get_content_size_with_attributes', function() { 53 | var graphics_text_layout_get_content_size; 54 | var font; 55 | before(function() { 56 | font = rocky.fonts_get_system_font('RESOURCE_ID_GOTHIC_18_BOLD'); 57 | graphics_text_layout_get_content_size = 58 | rocky.graphics_text_layout_get_content_size; 59 | }); 60 | 61 | it('returns empty size for empty string', function() { 62 | var size = graphics_text_layout_get_content_size( 63 | '', font, [0, 0, 100, 100], 0, 0); 64 | expect(size).to.eql({w: 0, h: 0}); 65 | }); 66 | 67 | it('returns empty size for empty font', function() { 68 | var font = {captureCPointer: function() {return null;}}; 69 | 70 | var size = graphics_text_layout_get_content_size( 71 | 'some text', font, [0, 0, 100, 100], 0, 0); 72 | expect(size).to.eql({w: 0, h: 0}); 73 | }); 74 | 75 | it('works for normal cases', function() { 76 | var size = graphics_text_layout_get_content_size( 77 | 'some text', font, [0, 0, 100, 100], 0, 0); 78 | expect(size).to.eql({w: 67, h: 18}); 79 | }); 80 | }); 81 | 82 | describe('non-system fonts', function() { 83 | var sandbox, font; 84 | beforeEach(function() { 85 | sandbox = sinon.sandbox.create(); 86 | font = rocky.fonts_load_custom_font_with_data('some data'); 87 | }); 88 | 89 | afterEach(function() { 90 | sandbox.verify(); 91 | sandbox.restore(); 92 | }); 93 | 94 | describe('fonts_load_custom_font_with_data', function() { 95 | it('simply passes the data', function() { 96 | expect(font).to.be.an('object'); 97 | expect(font).to.have.a.property('status').which.equals('loaded'); 98 | expect(font).to.have.a.property('data').which.equals('some data'); 99 | expect(font).to.have.a.property('captureCPointer').which.is.a('function'); 100 | expect(font).to.have.a.property('releaseCPointer').which.is.a('function'); 101 | }); 102 | }); 103 | 104 | describe('capture and release pointer', function() { 105 | it('returns null if not loaded', function() { 106 | font.status = 'loading'; 107 | sandbox.mock(rocky.module) 108 | .expects('ccall').never(); 109 | var cPtr = font.captureCPointer(); 110 | expect(cPtr).to.equal(0); 111 | }); 112 | 113 | it('captureCPointer', function() { 114 | sandbox.mock(rocky.module.Runtime) 115 | .expects('addFunction').twice() 116 | .onCall(0).returns(123) 117 | .onCall(1).returns(456); 118 | var ccall = sandbox.mock(rocky.module) 119 | .expects('ccall').twice(); 120 | ccall.onCall(0).returns(789) 121 | .onCall(1).returns(321); 122 | 123 | var cPtr = font.captureCPointer(); 124 | expect(cPtr).to.equal(321); 125 | expect(font).to.have.a.property('read_cb').which.equals(123); 126 | expect(font).to.have.a.property('get_size_cb').which.equals(456); 127 | expect(font).to.have.a.property('resourceId').which.equals(789); 128 | 129 | // sinon's mocks don't seem to work with multiple expectations on the same mock 130 | // hence this check afterwards 131 | expect(ccall.getCall(0).args).to.eql( 132 | ['emx_resources_register_custom', 'number', ['number', 'number'], [123, 456]]); 133 | expect(ccall.getCall(1).args).to.eql( 134 | ['fonts_load_custom_font', 'number', ['number'], [789]]); 135 | }); 136 | 137 | it('releaseCPointer', function() { 138 | var ccall = sandbox.mock(rocky.module) 139 | .expects('ccall').twice(); 140 | sandbox.mock(rocky.module.Runtime) 141 | .expects('removeFunction').twice() 142 | .onCall(0).returns(456) // registering the get_size_cb 143 | .onCall(1).returns(123); // registering the read_cb 144 | 145 | font.read_cb = 123; 146 | font.get_size_cb = 456; 147 | font.resourceId = 789; 148 | font.releaseCPointer(321); 149 | 150 | expect(font).to.not.have.a.property('read_cb'); 151 | expect(font).to.not.have.a.property('get_size_cb'); 152 | expect(font).to.not.have.a.property('resourceId'); 153 | 154 | // sinon's mocks don't seem to work with multiple expectations on the same mock 155 | // hence this check afterwards 156 | expect(ccall.getCall(0).args).to.eql( 157 | ['fonts_unload_custom_font', 'void', ['number'], [321]]); 158 | expect(ccall.getCall(1).args).to.eql( 159 | ['emx_resources_remove_custom', 'void', ['number'], [789]]); 160 | }); 161 | }); 162 | 163 | describe('fonts_load_custom_font', function() { 164 | it('reflects status and data according to Recources singleton', function() { 165 | var expectation = sandbox.mock(rocky.Resources) 166 | .expects('load').once().withArgs({ 167 | url: 'someUrl', convertPath: '/convert/font', height: 123, proxyArgs: [['height', 123]] 168 | }) 169 | .returns('someInitialStatus'); 170 | var font = rocky.fonts_load_custom_font({url: 'someUrl', height: 123}); 171 | var dataCallback = expectation.firstCall.args[1]; 172 | expect(dataCallback).to.be.a('function'); 173 | 174 | expect(font.status).to.equal('someInitialStatus'); 175 | var base64encoded = 'c29tZSBkYXRh'; // "some data" 176 | dataCallback('new status', {output: {data: base64encoded}}); 177 | expect(font.status).to.equal('new status'); 178 | expect(font.data).to.equal('some data'); 179 | }); 180 | }); 181 | }); 182 | }); 183 | -------------------------------------------------------------------------------- /test/manual/gpath.js: -------------------------------------------------------------------------------- 1 | /*eslint "no-unused-expressions": 0*/ 2 | /* globals describe:false, it:false, beforeEach:false, afterEach:false */ 3 | 4 | var symbols = require('../../src/symbols-manual.js').symbols; 5 | var expect = require('chai').expect; 6 | var sinon = require('sinon'); 7 | 8 | describe('GPath', function() { 9 | 10 | var sandbox; 11 | beforeEach(function() { 12 | sandbox = sinon.sandbox.create(); 13 | symbols.module = {ccall: function() {}}; 14 | }); 15 | 16 | afterEach(function() { 17 | sandbox.verify(); 18 | delete symbols.module; 19 | sandbox.restore(); 20 | delete global.XMLHttpRequest; 21 | }); 22 | 23 | describe('gpath_create', function() { 24 | var gpath_create = symbols.gpath_create; 25 | it('creates an object from array of points', function() { 26 | var path = gpath_create([[1, 2], [3, 4]]); 27 | expect(path).to.have.property('points').eql([{x: 1, y: 2}, {x: 3, y: 4}]); 28 | expect(path).to.have.property('offset').eql({x: 0, y: 0}); 29 | expect(path).to.have.property('rotation').eql(0); 30 | expect(path).to.have.property('captureCPointer').which.is.a('function'); 31 | expect(path).to.have.property('releaseCPointer').which.is.a('function'); 32 | }); 33 | 34 | it('creates an object from array of points', function() { 35 | var path = gpath_create({points: [[1, 2]], offset: [2, 3], rotation: 123}); 36 | expect(path).to.have.property('points').eql([{x: 1, y: 2}]); 37 | expect(path).to.have.property('offset').eql({x: 2, y: 3}); 38 | expect(path).to.have.property('rotation').eql(123); 39 | expect(path).to.have.property('captureCPointer').which.is.a('function'); 40 | expect(path).to.have.property('releaseCPointer').which.is.a('function'); 41 | }); 42 | }); 43 | 44 | describe('gpath_rotate_to', function() { 45 | var gpath_rotate_to = symbols.gpath_rotate_to; 46 | it('works for common cases', function() { 47 | var path = {rotation: 123}; 48 | gpath_rotate_to(path, 456); 49 | expect(path).to.eql({rotation: 456}); 50 | }); 51 | }); 52 | 53 | describe('gpath_move_to', function() { 54 | var gpath_move_to = symbols.gpath_move_to; 55 | it('works for common cases', function() { 56 | var path = {offset: [1, 2]}; 57 | gpath_move_to(path, [3, 4]); 58 | expect(path).to.eql({offset: {x: 3, y: 4}}); 59 | }); 60 | }); 61 | 62 | }); 63 | -------------------------------------------------------------------------------- /test/manual/graphicsTypes.js: -------------------------------------------------------------------------------- 1 | /*eslint "no-unused-expressions": 0*/ 2 | /* globals describe:false, it:false */ 3 | 4 | var symbols = require('../../src/symbols-manual.js').symbols; 5 | var expect = require('chai').expect; 6 | 7 | describe('Graphic Types', function() { 8 | describe('DEG_TO_TRIGANGLE', function() { 9 | var DEG_TO_TRIGANGLE = symbols.DEG_TO_TRIGANGLE; 10 | it('works with simple values', function() { 11 | expect(DEG_TO_TRIGANGLE(0)).to.equal(0); 12 | expect(DEG_TO_TRIGANGLE(270)).to.equal(Math.PI * 3 / 2); 13 | expect(DEG_TO_TRIGANGLE(-45)).to.equal(-Math.PI / 4); 14 | }); 15 | }); 16 | 17 | describe('GSize', function() { 18 | var GSize = symbols.GSize; 19 | describe('array', function() { 20 | it('works with arrays', function() { 21 | expect(GSize([1, 2])).to.eql({w: 1, h: 2}); 22 | }); 23 | it('ignores everything beyond two elements', function() { 24 | expect(GSize([1, 2, 3])).to.eql({w: 1, h: 2}); 25 | }); 26 | it('treats too short arrays as undefined values', function() { 27 | expect(GSize([1])).to.eql({w: 1, h: undefined}); 28 | var size = GSize([]); 29 | expect(size.w).to.equal(undefined); 30 | expect(size.h).to.equal(undefined); 31 | }); 32 | }); 33 | describe('object', function() { 34 | it('works with object', function() { 35 | expect(GSize({w: 1, h: 2})).to.eql({w: 1, h: 2}); 36 | }); 37 | it('ignores everything beyond w,h', function() { 38 | var obj = {w: 1, h: 2, z: 3}; 39 | var size = GSize(obj); 40 | expect(size).to.eql({w: 1, h: 2, z: 3}); 41 | expect(size).to.equal(obj); 42 | }); 43 | it('treats missing properties as undefined', function() { 44 | expect(GSize({w: 1})).to.eql({w: 1, h: undefined}); 45 | expect(GSize({h: 2})).to.eql({w: undefined, h: 2}); 46 | expect(GSize({z: 3})).to.eql({w: undefined, h: undefined, z: 3}); 47 | var size = GSize([]); 48 | expect(size.w).to.equal(undefined); 49 | expect(size.h).to.equal(undefined); 50 | }); 51 | }); 52 | describe('values', function() { 53 | it('works with values', function() { 54 | expect(GSize(1, 2)).to.eql({w: 1, h: 2}); 55 | }); 56 | it('ignores everything beyond two values', function() { 57 | expect(GSize(1, 2, 3)).to.eql({w: 1, h: 2}); 58 | }); 59 | it('treats missing values as undefined', function() { 60 | expect(GSize(1)).to.eql({w: 1, h: undefined}); 61 | }); 62 | }); 63 | }); 64 | 65 | describe('GPoint', function() { 66 | var GPoint = symbols.GPoint; 67 | describe('array', function() { 68 | it('works with arrays', function() { 69 | expect(GPoint([1, 2])).to.eql({x: 1, y: 2}); 70 | }); 71 | it('ignores everything beyond two elements', function() { 72 | expect(GPoint([1, 2, 3])).to.eql({x: 1, y: 2}); 73 | }); 74 | it('treats too short arrays as undefined values', function() { 75 | expect(GPoint([1])).to.eql({x: 1, y: undefined}); 76 | var empty = GPoint([]); 77 | expect(empty.x).to.equal(undefined); 78 | expect(empty.y).to.equal(undefined); 79 | }); 80 | }); 81 | describe('object', function() { 82 | it('works with object', function() { 83 | expect(GPoint({x: 1, y: 2})).to.eql({x: 1, y: 2}); 84 | }); 85 | it('ignores everything beyond x,y', function() { 86 | expect(GPoint({x: 1, y: 2, z: 3})).to.eql({x: 1, y: 2, z: 3}); 87 | }); 88 | it('treats missing properties as undefined', function() { 89 | expect(GPoint({x: 1})).to.eql({x: 1, y: undefined}); 90 | expect(GPoint({y: 2})).to.eql({x: undefined, y: 2}); 91 | expect(GPoint({z: 3})).to.eql({x: undefined, y: undefined, z: 3}); 92 | var point = GPoint([]); 93 | expect(point.x).to.equal(undefined); 94 | expect(point.y).to.equal(undefined); 95 | }); 96 | }); 97 | describe('values', function() { 98 | it('works with values', function() { 99 | expect(GPoint(1, 2)).to.eql({x: 1, y: 2}); 100 | }); 101 | it('ignores everything beyond two values', function() { 102 | expect(GPoint(1, 2, 3)).to.eql({x: 1, y: 2}); 103 | }); 104 | it('treats missing values as undefined', function() { 105 | expect(GPoint(1)).to.eql({x: 1, y: undefined}); 106 | }); 107 | }); 108 | }); 109 | 110 | describe('GRect', function() { 111 | var GRect = symbols.GRect; 112 | describe('array', function() { 113 | it('works with arrays', function() { 114 | expect(GRect([1, 2, 3, 4])).to.eql({x: 1, y: 2, w: 3, h: 4}); 115 | }); 116 | it('ignores everything beyond four elements', function() { 117 | expect(GRect([1, 2, 3, 4, 5])).to.eql({x: 1, y: 2, w: 3, h: 4}); 118 | }); 119 | it('treats too short arrays as undefined values', function() { 120 | expect(GRect([1, 2, 3])).to.eql({x: 1, y: 2, w: 3, h: undefined}); 121 | expect(GRect([1, 2])).to.eql({x: 1, y: 2, w: undefined, h: undefined}); 122 | expect(GRect([1])).to.eql({x: 1, y: undefined, w: undefined, h: undefined}); 123 | var empty = GRect([]); 124 | expect(empty.x).to.equal(undefined); 125 | expect(empty.y).to.equal(undefined); 126 | expect(empty.w).to.equal(undefined); 127 | expect(empty.h).to.equal(undefined); 128 | }); 129 | }); 130 | describe('object', function() { 131 | it('works with object', function() { 132 | expect(GRect({x: 1, y: 2, w: 3, h: 4})).to.eql({x: 1, y: 2, w: 3, h: 4}); 133 | }); 134 | it('preserves identity x,y,w,h', function() { 135 | var obj = {x: 1, y: 2, w: 3, h: 4, z: 5}; 136 | var rect = GRect(obj); 137 | expect(rect).to.equal(obj); 138 | }); 139 | it('treats missing properties as undefined', function() { 140 | expect(GRect({x: 1, y: 2, w: 3})).to.eql({x: 1, y: 2, w: 3, h: undefined}); 141 | expect(GRect({x: 1, y: 2})).to.eql({x: 1, y: 2, w: undefined, h: undefined}); 142 | expect(GRect({x: 1})) 143 | .to.eql({x: 1, y: undefined, w: undefined, h: undefined}); 144 | expect(GRect({})) 145 | .to.eql({x: undefined, y: undefined, w: undefined, h: undefined}); 146 | }); 147 | }); 148 | describe('values', function() { 149 | it('works with values', function() { 150 | expect(GRect(1, 2, 3, 4)).to.eql({x: 1, y: 2, w: 3, h: 4}); 151 | }); 152 | it('ignores everything beyond four values', function() { 153 | expect(GRect(1, 2, 3, 4, 5)).to.eql({x: 1, y: 2, w: 3, h: 4}); 154 | }); 155 | it('treats missing values as undefined', function() { 156 | expect(GRect(1, 2, 3)).to.eql({x: 1, y: 2, w: 3, h: undefined}); 157 | expect(GRect(1, 2)).to.eql({x: 1, y: 2, w: undefined, h: undefined}); 158 | expect(GRect(1)).to.eql({x: 1, y: undefined, w: undefined, h: undefined}); 159 | expect(GRect()) 160 | .to.eql({x: undefined, y: undefined, w: undefined, h: undefined}); 161 | }); 162 | }); 163 | }); 164 | describe('grect_inset', function() { 165 | var grect_inset = symbols.grect_inset; 166 | var rect = {x: 10, y: 20, w: 30, h: 40}; 167 | 168 | it('works for postive and negative insets', function() { 169 | expect(grect_inset(rect, {top: 2, right: 3, bottom: 4, left: 5})) 170 | .to.eql({x: 15, y: 22, w: 22, h: 34}); 171 | expect(grect_inset(rect, {top: -2, right: -3, bottom: -4, left: -5})) 172 | .to.eql({x: 5, y: 18, w: 38, h: 46}); 173 | }); 174 | it('returns GRectZero for too large insets', function() { 175 | expect(grect_inset(rect, {top: 100, right: 50, bottom: 25, left: 12})) 176 | .to.eql({x: 0, y: 0, w: 0, h: 0}); 177 | }); 178 | }); 179 | describe('GEdgeInsets', function() { 180 | var GEdgeInsets = symbols.GEdgeInsets; 181 | it('accepts an array', function() { 182 | expect(GEdgeInsets([1, 2, 3, 4])) 183 | .to.eql({top: 1, right: 2, bottom: 3, left: 4}); 184 | expect(GEdgeInsets([1, 2, 3, 4, 5])) 185 | .to.eql({top: 1, right: 2, bottom: 3, left: 4}); 186 | expect(GEdgeInsets([1, 2, 3])) 187 | .to.eql({top: 1, right: 2, bottom: 3, left: undefined}); 188 | }); 189 | it('accepts an object', function() { 190 | expect(GEdgeInsets({top: 1, right: 2, bottom: 3, left: 4})) 191 | .to.eql({top: 1, right: 2, bottom: 3, left: 4}); 192 | expect(GEdgeInsets({top: 1, right: 2, bottom: 3, left: 4, z: 5})) 193 | .to.eql({top: 1, right: 2, bottom: 3, left: 4}); 194 | expect(GEdgeInsets({top: 1, right: 2, bottom: 3})) 195 | .to.eql({top: 1, right: 2, bottom: 3, left: undefined}); 196 | }); 197 | it('accepts values', function() { 198 | expect(GEdgeInsets()) 199 | .to.eql({top: undefined, right: undefined, 200 | bottom: undefined, left: undefined}); 201 | expect(GEdgeInsets(1)) 202 | .to.eql({top: 1, right: 1, bottom: 1, left: 1}); 203 | expect(GEdgeInsets(1, 2)) 204 | .to.eql({top: 1, right: 2, bottom: 1, left: 2}); 205 | expect(GEdgeInsets(1, 2, 3)) 206 | .to.eql({top: 1, right: 2, bottom: 3, left: 2}); 207 | expect(GEdgeInsets(1, 2, 3, 4)) 208 | .to.eql({top: 1, right: 2, bottom: 3, left: 4}); 209 | expect(GEdgeInsets(1, 2, 3, 4, 5)) 210 | .to.eql({top: 1, right: 2, bottom: 3, left: 4}); 211 | }); 212 | }); 213 | }); 214 | --------------------------------------------------------------------------------