├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .npmignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── ROADMAP.md ├── code-of-conduct.md ├── docs ├── Alignment.html ├── App.js.html ├── BasePolicy.html ├── BaseWidget.html ├── BoxLayout.html ├── Copyable.html ├── ExpandingPolicy.html ├── FixedLayout.html ├── FixedPolicy.html ├── GraphicsGen.html ├── GraphicsGen.js.html ├── HBoxLayout.html ├── LayoutManager.html ├── Loader.html ├── Padding.html ├── Padding.js.html ├── Point.html ├── Point.js.html ├── ST.Alignment.html ├── ST.App.html ├── ST.GraphicsGen.html ├── ST.Layouts.BaseLayout.html ├── ST.Layouts.BoxLayout.html ├── ST.Layouts.FixedLayout.html ├── ST.Layouts.HBoxLayout.html ├── ST.Layouts.VBoxLayout.html ├── ST.Padding.html ├── ST.Point.html ├── ST.Settings.html ├── ST.Size.html ├── ST.SizePolicies.BasePolicy.html ├── ST.SizePolicies.ExpandingPolicy.html ├── ST.SizePolicies.FixedPolicy.html ├── ST.SizePolicies.SharedExpandingPolicy.html ├── ST.Theme.html ├── ST.Widgets.BaseWidget.html ├── ST.Widgets.Button.html ├── ST.Widgets.Container.html ├── ST.Widgets.Image.html ├── ST.Widgets.Label.html ├── ST.Widgets.Panel.html ├── ST.Widgets.SITransform.html ├── ST.Widgets.Slider.html ├── ST.Widgets.StageWidget.html ├── ST.Widgets.TextButton.html ├── Settings.js.html ├── SharedExpandingPolicy.html ├── Signal.html ├── Size.html ├── Size.js.html ├── SizeManager.html ├── Slot.html ├── Theme.html ├── Theme.js.html ├── UI.Alignment.Alignment.html ├── UI.Alignment.html ├── UI.App.html ├── UI.BaseLayout.html ├── UI.Copyable.html ├── UI.GraphicsGen.html ├── UI.Label.html ├── UI.LayoutManager.html ├── UI.Layouts.BaseLayout.html ├── UI.Layouts.BoxLayout.html ├── UI.Layouts.FixedLayout.html ├── UI.Layouts.HBoxLayout.html ├── UI.Layouts.VBoxLayout.html ├── UI.Layouts.exports.HBoxLayout.html ├── UI.Layouts.exports.VBoxLayout.html ├── UI.Padding.html ├── UI.Point.html ├── UI.Renderer.html ├── UI.Signal.html ├── UI.Size.html ├── UI.SizeManager.html ├── UI.SizePolicies.BasePolicy.html ├── UI.SizePolicies.ExpandingPolicy.html ├── UI.SizePolicies.FixedPolicy.html ├── UI.SizePolicies.SharedExpandingPolicy.html ├── UI.Slot.html ├── UI.Theme.html ├── UI.UIGraphics.html ├── UI.WidgetShader.html ├── UI.Widgets.BaseWidget.html ├── UI.Widgets.Button.html ├── UI.Widgets.Container.html ├── UI.Widgets.Image.html ├── UI.Widgets.Label.html ├── UI.Widgets.Panel.html ├── UI.Widgets.SITransform.html ├── UI.Widgets.Slider.html ├── UI.Widgets.StageWidget.html ├── UI.Widgets.TextButton.html ├── UI.module.exports.html ├── UIGraphics.html ├── UIGraphics.js.html ├── VBoxLayout.html ├── _config.yml ├── classes.list.html ├── const.js.html ├── drawcount.js.html ├── external-Container.html ├── external-EventEmitter.html ├── external-PIXI.html ├── external-TransformStatic.html ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── global.html ├── img │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png ├── index.html ├── index.js.html ├── layoutSys_Alignment.js.html ├── layoutSys_layouts_BaseLayout.js.html ├── layoutSys_layouts_BoxLayout.js.html ├── layoutSys_layouts_FixedLayout.js.html ├── layoutSys_layouts_HBoxLayout.js.html ├── layoutSys_layouts_VBoxLayout.js.html ├── layoutSys_sizePolicies_BasePolicy.js.html ├── layoutSys_sizePolicies_ExpandingPolicy.js.html ├── layoutSys_sizePolicies_FixedPolicy.js.html ├── layoutSys_sizePolicies_SharedExpandingPolicy.js.html ├── module.exports.html ├── options.js.html ├── quicksearch.html ├── scripts │ ├── bootstrap.min.js │ ├── docstrap.lib.js │ ├── fulltext-search-ui.js │ ├── fulltext-search.js │ ├── jaguar.js │ ├── jquery.min.js │ ├── jquery.min.map │ ├── lunr.min.js │ ├── prettify │ │ ├── Apache-License-2.0.txt │ │ ├── jquery.min.js │ │ ├── lang-css.js │ │ └── prettify.js │ ├── sunlight.js │ ├── toc.js │ ├── underscore-min.js │ └── underscore-min.map ├── styles │ ├── bootstrap.min.css │ ├── darkstrap.css │ ├── jaguar.css │ ├── prettify-jsdoc.css │ ├── prettify-tomorrow.css │ ├── site.cerulean.css │ ├── site.cosmo.css │ ├── site.cyborg.css │ ├── site.darkly.css │ ├── site.darkstrap.css │ ├── site.dibs-bootstrap.css │ ├── site.flatly.css │ ├── site.journal.css │ ├── site.lumen.css │ ├── site.paper.css │ ├── site.readable.css │ ├── site.sandstone.css │ ├── site.simplex.css │ ├── site.slate.css │ ├── site.spacelab.css │ ├── site.superhero.css │ ├── site.united.css │ ├── site.yeti.css │ ├── sunlight.dark.css │ └── sunlight.default.css ├── widgets_BaseWidget.js.html ├── widgets_Button.js.html ├── widgets_Container.js.html ├── widgets_Image.js.html ├── widgets_Label.js.html ├── widgets_Panel.js.html ├── widgets_SITransform.js.html ├── widgets_Slider.js.html ├── widgets_StageWidget.js.html └── widgets_TextButton.js.html ├── gulpfile.js ├── inch.json ├── jsdoc.json ├── package.json ├── screenshot.jpg ├── scripts └── fixJsdoc.js ├── src ├── App.js ├── GraphicsGen.js ├── Padding.js ├── Point.js ├── Settings.js ├── Size.js ├── Theme.js ├── const.js ├── demos │ ├── demos.js │ └── index.html ├── drawcount.js ├── index.js ├── layoutSys │ ├── Alignment.js │ ├── index.js │ ├── layouts │ │ ├── BaseLayout.js │ │ ├── BoxLayout.js │ │ ├── FixedLayout.js │ │ ├── HBoxLayout.js │ │ ├── VBoxLayout.js │ │ └── index.js │ └── sizePolicies │ │ ├── BasePolicy.js │ │ ├── ExpandingPolicy.js │ │ ├── FixedPolicy.js │ │ ├── SharedExpandingPolicy.js │ │ └── index.js └── widgets │ ├── BaseWidget.js │ ├── Button.js │ ├── Container.js │ ├── Image.js │ ├── Label.js │ ├── Panel.js │ ├── SITransform.js │ ├── Slider.js │ ├── StageWidget.js │ ├── TextButton.js │ └── index.js ├── styleSheets ├── greyToadTheme.js └── holyStyle.js └── test ├── index.js ├── runner.html ├── spec ├── App-spec.js ├── GraphicsGen-spec.js ├── Padding-spec.js ├── Point-spec.js ├── Settings-spec.js ├── Size-spec.js ├── Theme-spec.js ├── index.js ├── layoutSys │ ├── Alignment-spec.js │ ├── index.js │ ├── layouts │ │ ├── BaseLayout-spec.js │ │ ├── BoxLayout-spec.js │ │ ├── FixedLayout-spec.js │ │ ├── HBoxLayout-spec.js │ │ ├── VBoxLayout-spec.js │ │ └── index.js │ └── sizePolicies │ │ ├── BasePolicy-spec.js │ │ ├── ExpandingPolicy-spec.js │ │ ├── FixedPolicy-spec.js │ │ ├── SharedExpandingPolicy-spec.js │ │ └── index.js └── widgets │ ├── BaseWidget-spec.js │ ├── Button-spec.js │ ├── Image-spec.js │ ├── Label-spec.js │ ├── Panel-spec.js │ ├── SITransform-spec.js │ ├── Slider-spec.js │ ├── StageWidget-spec.js │ ├── TextButton-spec.js │ └── index.js └── test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015" ], 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs. 2 | # More information at http://EditorConfig.org 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 4 11 | 12 | [{package.json,bower.json,.travis.yml}] 13 | 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 6, 9 | "sourceType": "module", 10 | "ecmaFeatures": { 11 | 12 | } 13 | }, 14 | "extends": "google", 15 | "rules": { 16 | "linebreak-style": 0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Lib dir used for npm 40 | lib/ 41 | 42 | # other 43 | www/ 44 | 45 | # Dist folder 46 | dist/ 47 | 48 | # Typescript v1 declaration files 49 | typings/ 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional REPL history 58 | .node_repl_history 59 | 60 | # Output of 'npm pack' 61 | *.tgz 62 | 63 | # Yarn Integrity file 64 | .yarn-integrity 65 | 66 | # dotenv environment variables file 67 | .env 68 | 69 | # release protocol 70 | ReleaseProtocol.txt 71 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ignore src folder (use lib) 2 | src/ 3 | 4 | # other files 5 | .gitignore 6 | .gitattributes 7 | .travis.yml 8 | gulpfile.js 9 | inch.json 10 | jsdoc.json 11 | 12 | # ignore test 13 | test/ 14 | 15 | # ignore docs 16 | docs/ 17 | 18 | # ignore dist 19 | dist/ 20 | 21 | # ignore scripts 22 | scripts/ 23 | 24 | # other 25 | www/ 26 | 27 | # Logs 28 | logs 29 | *.log 30 | npm-debug.log* 31 | yarn-debug.log* 32 | yarn-error.log* 33 | 34 | # Dependency directories 35 | node_modules/ 36 | jspm_packages/ 37 | 38 | # Typescript v1 declaration files 39 | typings/ 40 | 41 | # Optional npm cache directory 42 | .npm 43 | 44 | # Optional eslint cache 45 | .eslintcache 46 | 47 | # Optional REPL history 48 | .node_repl_history 49 | 50 | # Output of 'npm pack' 51 | *.tgz 52 | 53 | # Yarn Integrity file 54 | .yarn-integrity 55 | 56 | # dotenv environment variables file 57 | .env 58 | 59 | # release protocol 60 | ReleaseProtocol.txt 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | 5 | install: 6 | - npm install 7 | - npm install pixi-gl-core -save 8 | - npm install bit-twiddle 9 | 10 | cache: 11 | directories: 12 | - node_modules 13 | 14 | before_script: 15 | - npm install -g gulp 16 | 17 | script: 18 | - gulp 19 | - gulp test 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Sabertooth 2 | 3 | ### You're awesome!:blush: 4 | 5 | Thank you for considering contributing to Sabertooth. Big commitments aren't required. Even small things like grammatical errors in the [docs](https://abydosdigital.github.io/Sabertooth/) really add up. Sabertooth is in its infancy and will need your help to reach its potential. Every contribution is valued and greatly appreciated. 6 | 7 | >Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, and helping you finalize your pull requests. 8 | 9 | > Sabertooth is an open source project and we love to receive contributions from our community — you! There are numerous ways to contribute, from writing tutorials or blog posts, improving the [documentation](https://abydosdigital.github.io/Sabertooth/), submitting bug reports and feature requests or writing code which can be incorporated into Sabertooth itself. 10 | 11 | See the [ROADMAP](ROADMAP.md) to view the current state of features and to see some of the things we're trying to accomplish. 12 | 13 | ### Code of Conduct 14 | Please note that this project is released with a [Contributor Code of Conduct](code-of-conduct.md). By participating in this project you agree to abide by its terms. 15 | 16 | ### Pull Request 17 | 18 | > When woking on large features or bug fixes it's best to split the work(break it down) into different branches/ pull request. This simplifies the process of reviewing and merging. 19 | 20 | Requirements for acceptance: 21 | * If it solves an issue it must reference the issue in the title 22 | * New features or methods must have at least basic corresponding test. 23 | * New classes and methods must have documentation(JSDoc comments). 24 | * It must match the coding style of the rest of the project and must pass the linter. 25 | * It must pass the travis-ci build and test. 26 | 27 | > PR's should be merged with the dev branch unless otherwise stated. 28 | 29 | # Your First Contribution 30 | No worries, we'd love to help you!:thumbsup: 31 | 32 | > Unsure where to begin contributing to Sabertooth? You can start by looking through the issue tracker for something simple or maybe read through the [docs](https://abydosdigital.github.io/Sabertooth/) to see if anything needs improvement. 33 | 34 | If you're new to Github check out these friendly tutorials: 35 | * [makeapullrequest.com](http://makeapullrequest.com/) 36 | * [firsttimersonly.com](http://www.firsttimersonly.com/) 37 | * [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) 38 | 39 | At this point, you're ready to make your changes! Feel free to ask for help; everyone is a beginner at first :smile_cat: 40 | 41 | If a maintainer asks you to "rebase" your PR, they're saying that a lot of code has changed, and that you need to update your branch so it's easier to merge. 42 | 43 | # Getting started 44 | 45 | ### Follow these instructions and you'll be on your way in no time.:smile: 46 | 47 | 1. Create your own fork of the code 48 | 2. Create a new branch for the fix or feature 49 | 3. Do the changes in your fork 50 | 4. If you like the change and think the project could use it: 51 | * Be sure you have followed the code style for the project. 52 | * Send a pull request to the dev branch describing the changes you've made. 53 | 54 | ### Large contributions 55 | 56 | Big changes need to fit with the direction of the project. There could be potential issues you didn't think of or maybe someone is already working on that particular feature. It's probably best to make an issue for your proposal first so that we can determine the best way to proceed. 57 | 58 | ### Small contributions 59 | 60 | As a rule of thumb, changes are obvious fixes if they do not introduce any new functionality or creative thinking. As long as the change does not affect functionality, some likely examples include the following: 61 | * Spelling / grammar fixes 62 | * Typo correction, white space and formatting changes 63 | * Comment clean up 64 | * Bug fixes that change default return values or error codes stored in constants 65 | * Adding logging messages or debugging output 66 | * Changes to ‘metadata’ files like Gemfile, .gitignore, build scripts, etc. 67 | * Moving source files from one directory or package to another 68 | 69 | # How to report a bug 70 | 71 | When filing an issue, make sure to answer these five questions: 72 | 73 | 1. What version of Sabertooth are you using? 74 | 2. What operating system and processor architecture are you using? 75 | 3. What did you do? 76 | 4. What did you expect to see? 77 | 5. What did you see instead? 78 | 79 | # How to suggest a feature or enhancement 80 | 81 | ### Features 82 | 83 | >Feature request should only be opened for things that add new functionality to the project. 84 | To suggest a feature, open an issue and try to explain thoroughly why it should be added and how it should work. Please use the feature request label. 85 | 86 | ### Enhancements 87 | 88 | >Only suggest enhancements for things that improve upon existing functionality. To propose an enhancement, open an issue and try to explain thoroughly why it should be added and how it should work. Please use the enhancement label. 89 | 90 | # Questions 91 | 92 | Feel free to direct any further questions to AbydosDigital@gmail.com. 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2017, Zach Moore 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This was a fun project to build and experiment with. **It is not to be taken seriously.** A QT style UI in JS and WebGL would be too slow and bulky. 2 | 3 | My goal was to learn about QT's layout system, create a JS framework from scratch and setup continuous integration and automated testing with travis-ci. 4 | 5 | --- 6 | 7 | ## Sabertooth 8 | 9 | ### A hardware accelerated UI framework for JavaScript. 10 | Sabertooth is inspired by [QT](https://www.qt.io/), uses super-fast [PIXI.js](https://github.com/pixijs/pixi.js) under-the-hood and renders in webGL with canvas fallback. Widgets are extensions of PIXI.Container and have most of those properties, such as tint, alpha, blending modes and filters(shaders). Custom widgets and custom styling are easily accomplished by sub-classing existing widgets and modifying or creating new style sheets. 11 | 12 | * [Documentation](https://abydosdigital.github.io/Sabertooth/) 13 | 14 | (travis ci fails because the project has not been maintained and dependencies are out of date) 15 | [![Build Status](https://travis-ci.org/AbydosDigital/Sabertooth.svg?branch=master)](https://travis-ci.org/AbydosDigital/Sabertooth) 16 | [![Inline docs](http://inch-ci.org/github/AbydosDigital/SaberTooth.svg?branch=master)](http://inch-ci.org/github/AbydosDigital/SaberTooth) 17 | 18 | ![Alt text](/screenshot.jpg?raw=true "Optional Title") 19 | 20 | *** 21 | 23 | 24 | ### Get Started 25 | 26 | #### The easiest way to get started with Sabertooth is to [download](https://github.com/AbydosDigital/Sabertooth/releases) the latest release. 27 | 29 | 30 | **Example:** 31 | ```html 32 | 33 | 57 | ``` 58 | 59 | #### Install with npm: 60 | ``` 61 | $> npm install Sabertooth 62 | ``` 63 | **Example:** 64 | ```javascript 65 | import * as ST from 'sabertooth'; // for es6 modules 66 | 67 | let app = new ST.App(/*options*/); 68 | 69 | let tb = new ST.Widgets.TextButton(app.root, { 70 | text: 'My Button', 71 | x: 100, 72 | y: 100, 73 | width: 200, 74 | height: 30, 75 | }); 76 | 77 | // Set a click callback 78 | tb.on('click', ()=>{ 79 | console.log('My Button Was Clicked!'); 80 | }); 81 | 82 | //main loop 83 | let main = function () { 84 | app.update(); // Update UI logic 85 | requestAnimationFrame(main); // Loop 86 | } 87 | 88 | main(); // Begin the main application loop 89 | ``` 90 | 91 | #### Test 92 | ``` 93 | gulp test 94 | ``` 95 | 96 | #### To build locally: 97 | * Fork Sabertooth 98 | * Clone 99 | * CD to the project directory 100 | * Install dependencies: 101 | ``` 102 | npm install 103 | ``` 104 | Build: 105 | ``` 106 | gulp 107 | ``` 108 | 109 | ### Contributing 110 | 111 | Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. 112 | 113 | ### Versioning 114 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/AbydosDigital/SaberTooth/tags). 115 | 116 | ### Authors 117 | * **Zach Moore** *- initial work -* [@Zachacious](https://github.com/Zachacious) 118 | 119 | See also the list of [contributors](https://github.com/AbydosDigital/Sabertooth/graphs/contributors) who participated in this project. 120 | 121 | ### License 122 | This project is licensed under the **ISC** license. See [LICENSE.md](LICENSE) for more details. 123 | 124 | ### Acknowledgements 125 | Special thanks to: 126 | * [GoodBoyDigital](http://www.goodboydigital.com/) - [Pixi.js](http://www.pixijs.com/) 127 | * [Primus](https://github.com/primus) - [EventEmitter3](https://github.com/primus/eventemitter3) 128 | 129 | ### Questions? 130 | 131 | Please contact AbydosDigital@gmail.com with any further questions. 132 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Sabertooth Project Roadmap 2 | This is meant to be a rough outline of where we're at and the journey ahead. Feel free to add your own ideas or updates:smile: 3 | 4 | Follow [@Zachacious](https://twitter.com/Zachacious) on twitter to stay up-to-date on the latest. 5 | 6 | ~ *All of the below is subject to change because of natural project evolution and depending on who's willing to work on what and when* ~ 7 | 8 | **v0.3.0** 9 | * Make UI's skinnable 10 | * Bitmap labels 11 | 12 | **v0.4.0** 13 | * Keyboard input system 14 | * Scrollable viewports 15 | 16 | **v0.5.0** 17 | * List View 18 | * Tree View 19 | 20 | **v0.6.0** 21 | * Progress bar 22 | * Spring 23 | * Divider 24 | * Checkbox 25 | * RadioButton 26 | 27 | **v0.7.0** 28 | * TabView 29 | * Toolbars 30 | 31 | **v0.8.0** 32 | * Menus 33 | 34 | **v0.9.0** 35 | * Text input 36 | 37 | **v1.0.0:** 38 | * Simplify api names -- ST.Widgets.Button = ST.Button 39 | 40 | **v1.1.0** 41 | * TitleBar 42 | * Windows 43 | 44 | **v1.2.0** 45 | * Undo 46 | 47 | **v1.3.0** 48 | * Actions 49 | 50 | **v1.4.0** 51 | * Full working demos for mobile(Cocoonjs), web, and desktop(Electron) 52 | 53 | **v2.0.0** 54 | * Animations 55 | * Designer App 56 | 57 | --- 58 | ### Full list of features 59 | 60 | :white_circle: = Not implemented | 61 | :white_check_mark: = Already implimented 62 | 63 | * App:white_check_mark: - Handles creating the PIXI renderer, the root widget and automatic resizing 64 | * Theme:white_check_mark: - Creates themes from style sheets stored in js files 65 | * Skin::white_circle: - Use images and bitmap fonts instead of 'Flat Style' rectangles for theme. 66 | * Layouts 67 | * BaseLayout:white_check_mark: 68 | * FixedLayout:white_check_mark: - Arrange widgets via user defined positions 69 | * BoxLayout:white_check_mark: - Stack widgets vertically or horizontal 70 | * GridLayout:white_circle: - Arrange widgets in a grid 71 | * Size Policies 72 | * BasePolicy:white_check_mark: 73 | * FixedPolicy:white_check_mark: - Use user defined width or height 74 | * ExpandingPolicy:white_check_mark: - Fill the parent widget 75 | * SharedExpandingPolicy:white_check_mark: - Share space with siblings - fill the parent 76 | * CompressPolicy:white_circle: - Shrink to fit children contained within 77 | * Widgets 78 | * BaseWidget:white_check_mark: 79 | * Label:white_check_mark: - Show text 80 | * BitmapLabel:white_circle: - Show text using a bitmap font 81 | * Button:white_check_mark: 82 | * TextButton:white_check_mark: - Button with text 83 | * Image:white_check_mark: - Sprite Texture with widget properties 84 | * Panel:white_check_mark: - Container with solid background 85 | * Slider:white_check_mark: - Track with a button that slides to change values 86 | * Container:white_check_mark: - Non-renderable container of widgets 87 | * StageWidget( root widget ):white_check_mark: - Counts its own size with getBounds() - use as root widget 88 | * ProgressBar:white_circle: - Bar that fills over time 89 | * ScrollView:white_circle: - Content that moves with scrollbars 90 | * ListView:white_circle: 91 | * TreeView:white_circle: 92 | * Spring:white_circle: - Use to compress widgets in certain layouts 93 | * Divider:white_circle: - Simple bar use to divide space 94 | * TabView:white_circle: 95 | * Splitter:white_circle: - Adjustable bar that splits content areas 96 | * Window:white_circle: - Free moving windows 97 | * TitleBar( for windows ):white_circle: 98 | * Menu:white_circle: - Traditional and responsive menus of all kinds 99 | * Toolbar:white_circle: 100 | * ToolButton:white_circle: 101 | * MenuButton:white_circle: 102 | * Checkbox:white_circle: 103 | * RadioButton:white_circle: 104 | * TextInput:white_circle: 105 | * MultiLineTextInput:white_circle: 106 | * Alignments( for layouts ):white_check_mark: - Provides an offset for layout positioning 107 | * Settings:white_check_mark: - Configurable global settings for Sabertooth classes to use 108 | * Padding( for widgets ):white_check_mark: 109 | * Actions( QT style ):white_circle: - See [QT Actions](http://doc.qt.io/qt-5/qaction.html) 110 | * Undo:white_circle: - See [QT Undo](http://doc.qt.io/qt-4.8/qundo.html) 111 | * MVC:white_circle: - Use 'Item?' sub-classes interchangeably with different views 112 | * Keyboard Input:white_circle: 113 | * Designer App:white_circle: - Design and create widget layouts visually 114 | * Demo Project(s):white_circle: - To test desktop and mobile fully 115 | * Animations:white_circle: - For transitions 116 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at AbydosDigital@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /docs/HBoxLayout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Documentation Class: HBoxLayout 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 57 | 58 | 59 |
60 |
61 | 62 | 63 |
64 | 65 |
66 | 67 | 68 |

Class: HBoxLayout

69 |
70 | 71 |
72 | 73 |

74 | HBoxLayout 75 |

76 | 77 | 78 |
79 | 80 | 81 |
82 |
83 | 84 | 85 |
86 |
87 |

new HBoxLayout(hostWidget [, spacing])

88 | 89 | 90 |
91 |
92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
Parameters:
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 | 142 | 143 | 144 | 151 | 152 | 153 | 154 | 157 | 158 | 159 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 180 | 181 | 182 | 191 | 192 | 193 | 194 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 |
NameTypeArgumentDefaultDescription
hostWidget 134 | 135 | 136 | UI.Widgets.BaseWidget 137 | 138 | 139 | 140 | 141 | 145 | 146 | 147 | 148 | 149 | 150 | 155 | 156 |
    160 |
  • the widget this layout belongs to
  • 161 |
spacing 172 | 173 | 174 | Number 175 | 176 | 177 | 178 | 179 | 183 | 184 | <optional>
185 | 186 | 187 | 188 | 189 | 190 |
195 | 196 | 4 197 | 198 |

the space in pixels between widgets

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 | 281 | 282 | 283 | 284 | 285 |
286 | 287 |
288 | 289 | 290 | 291 | 292 |
293 |
294 | 295 |
296 | 297 | 298 |
299 | 300 |
301 | 302 | 303 |
304 |
305 | 306 | 307 | 321 | 322 | 323 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 417 | 418 | 419 | 420 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-tactile -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zachacious/Sabertooth/02840b880fbb75f30b993398cd7627af08f911b7/docs/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zachacious/Sabertooth/02840b880fbb75f30b993398cd7627af08f911b7/docs/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zachacious/Sabertooth/02840b880fbb75f30b993398cd7627af08f911b7/docs/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zachacious/Sabertooth/02840b880fbb75f30b993398cd7627af08f911b7/docs/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /docs/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zachacious/Sabertooth/02840b880fbb75f30b993398cd7627af08f911b7/docs/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /docs/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zachacious/Sabertooth/02840b880fbb75f30b993398cd7627af08f911b7/docs/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /docs/scripts/fulltext-search-ui.js: -------------------------------------------------------------------------------- 1 | window.SearcherDisplay = (function($) { 2 | /** 3 | * This class provides support for displaying quick search text results to users. 4 | */ 5 | function SearcherDisplay() { } 6 | 7 | SearcherDisplay.prototype.init = function() { 8 | this._displayQuickSearch(); 9 | }; 10 | 11 | /** 12 | * This method creates the quick text search entry in navigation menu and wires all required events. 13 | */ 14 | SearcherDisplay.prototype._displayQuickSearch = function() { 15 | var quickSearch = $(document.createElement("iframe")), 16 | body = $("body"), 17 | self = this; 18 | 19 | quickSearch.attr("src", "quicksearch.html"); 20 | quickSearch.css("width", "0px"); 21 | quickSearch.css("height", "0px"); 22 | 23 | body.append(quickSearch); 24 | 25 | $(window).on("message", function(msg) { 26 | var msgData = msg.originalEvent.data; 27 | 28 | if (msgData.msgid != "docstrap.quicksearch.done") { 29 | return; 30 | } 31 | 32 | var results = msgData.results || []; 33 | 34 | self._displaySearchResults(results); 35 | }); 36 | 37 | function startSearch() { 38 | var searchTerms = $('#search-input').prop("value"); 39 | if (searchTerms) { 40 | quickSearch[0].contentWindow.postMessage({ 41 | "searchTerms": searchTerms, 42 | "msgid": "docstrap.quicksearch.start" 43 | }, "*"); 44 | } 45 | } 46 | 47 | $('#search-input').on('keyup', function(evt) { 48 | if (evt.keyCode != 13) { 49 | return; 50 | } 51 | startSearch(); 52 | return false; 53 | }); 54 | $('#search-submit').on('click', function() { 55 | startSearch(); 56 | return false; 57 | }); 58 | }; 59 | 60 | /** 61 | * This method displays the quick text search results in a modal dialog. 62 | */ 63 | SearcherDisplay.prototype._displaySearchResults = function(results) { 64 | var resultsHolder = $($("#searchResults").find(".modal-body")), 65 | fragment = document.createDocumentFragment(), 66 | resultsList = document.createElement("ul"); 67 | 68 | resultsHolder.empty(); 69 | 70 | for (var idx = 0; idx < results.length; idx++) { 71 | var result = results[idx], 72 | item = document.createElement("li"), 73 | link = document.createElement("a"); 74 | 75 | link.href = result.id; 76 | link.innerHTML = result.title; 77 | 78 | item.appendChild(link) 79 | resultsList.appendChild(item); 80 | } 81 | 82 | fragment.appendChild(resultsList); 83 | resultsHolder.append(fragment); 84 | 85 | $("#searchResults").modal({"show": true}); 86 | }; 87 | 88 | return new SearcherDisplay(); 89 | })($); 90 | -------------------------------------------------------------------------------- /docs/scripts/fulltext-search.js: -------------------------------------------------------------------------------- 1 | window.Searcher = (function() { 2 | function Searcher() { 3 | this._index = lunr(function () { 4 | this.field('title', {boost: 10}) 5 | this.field('body') 6 | this.ref('id') 7 | }) ; 8 | 9 | this._indexContent = undefined; 10 | } 11 | 12 | Searcher.prototype.init = function() { 13 | var self = this; 14 | 15 | $("script[type='text/x-docstrap-searchdb']").each(function(idx, item) { 16 | self._indexContent = JSON.parse(item.innerHTML); 17 | 18 | for (var entryId in self._indexContent) { 19 | self._index.add(self._indexContent[entryId]); 20 | } 21 | }); 22 | }; 23 | 24 | Searcher.prototype.search = function(searchTerm) { 25 | var results = [], 26 | searchResults = this._index.search(searchTerm); 27 | 28 | for (var idx = 0; idx < searchResults.length; idx++) { 29 | results.push(this._indexContent[searchResults[idx].ref]) 30 | } 31 | 32 | return results; 33 | }; 34 | 35 | return new Searcher(); 36 | })(); -------------------------------------------------------------------------------- /docs/scripts/jaguar.js: -------------------------------------------------------------------------------- 1 | (function(){var e=0;var t;var n=document.getElementById("source-code");if(n){var i=config.linenums;if(i){n=n.getElementsByTagName("ol")[0];t=Array.prototype.slice.apply(n.children);t=t.map(function(t){e++;t.id="line"+e})}else{n=n.getElementsByTagName("code")[0];t=n.innerHTML.split("\n");t=t.map(function(t){e++;return''+t});n.innerHTML=t.join("\n")}}})();$(function(){$("#search").on("keyup",function(e){var t=$(this).val();var n=$(".navigation");if(t){var i=new RegExp(t,"i");n.find("li, .itemMembers").hide();n.find("li").each(function(e,t){var n=$(t);if(n.data("name")&&i.test(n.data("name"))){n.show();n.closest(".itemMembers").show();n.closest(".item").show()}})}else{n.find(".item, .itemMembers").show()}n.find(".list").scrollTop(0)});$(".navigation").on("click",".title",function(e){$(this).parent().find(".itemMembers").toggle()});var e=$(".page-title").data("filename").replace(/\.[a-z]+$/,"");var t=$('.navigation .item[data-name*="'+e+'"]:eq(0)');if(t.length){t.remove().prependTo(".navigation .list").show().find(".itemMembers").show()}var n=function(){var e=$(window).height();var t=$(".navigation");t.height(e).find(".list").height(e-133)};$(window).on("resize",n);n();if(config.disqus){$(window).on("load",function(){var e=config.disqus;var t=document.createElement("script");t.type="text/javascript";t.async=true;t.src="http://"+e+".disqus.com/embed.js";(document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0]).appendChild(t);var n=document.createElement("script");n.async=true;n.type="text/javascript";n.src="http://"+e+".disqus.com/count.js";document.getElementsByTagName("BODY")[0].appendChild(n)})}}); -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /docs/scripts/toc.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | var navbarHeight; 3 | var initialised = false; 4 | var navbarOffset; 5 | 6 | function elOffset($el) { 7 | return $el.offset().top - (navbarHeight + navbarOffset); 8 | } 9 | 10 | function scrollToHash(duringPageLoad) { 11 | var elScrollToId = location.hash.replace(/^#/, ''); 12 | var $el; 13 | 14 | function doScroll() { 15 | var offsetTop = elOffset($el); 16 | window.scrollTo(window.pageXOffset || window.scrollX, offsetTop); 17 | } 18 | 19 | if (elScrollToId) { 20 | $el = $(document.getElementById(elScrollToId)); 21 | 22 | if (!$el.length) { 23 | $el = $(document.getElementsByName(elScrollToId)); 24 | } 25 | 26 | if ($el.length) { 27 | if (duringPageLoad) { 28 | $(window).one('scroll', function() { 29 | setTimeout(doScroll, 100); 30 | }); 31 | } else { 32 | setTimeout(doScroll, 0); 33 | } 34 | } 35 | } 36 | } 37 | 38 | function init(opts) { 39 | if (initialised) { 40 | return; 41 | } 42 | initialised = true; 43 | navbarHeight = $('.navbar').height(); 44 | navbarOffset = opts.navbarOffset; 45 | 46 | // some browsers move the offset after changing location. 47 | // also catch external links coming in 48 | $(window).on("hashchange", scrollToHash.bind(null, false)); 49 | $(scrollToHash.bind(null, true)); 50 | } 51 | 52 | $.catchAnchorLinks = function(options) { 53 | var opts = $.extend({}, jQuery.fn.toc.defaults, options); 54 | init(opts); 55 | }; 56 | 57 | $.fn.toc = function(options) { 58 | var self = this; 59 | var opts = $.extend({}, jQuery.fn.toc.defaults, options); 60 | 61 | var container = $(opts.container); 62 | var tocs = []; 63 | var headings = $(opts.selectors, container); 64 | var headingOffsets = []; 65 | var activeClassName = 'active'; 66 | var ANCHOR_PREFIX = "__anchor"; 67 | var maxScrollTo; 68 | var visibleHeight; 69 | var headerHeight = 10; // so if the header is readable, its counted as shown 70 | init(); 71 | 72 | var scrollTo = function(e) { 73 | e.preventDefault(); 74 | var target = $(e.target); 75 | if (target.prop('tagName').toLowerCase() !== "a") { 76 | target = target.parent(); 77 | } 78 | var elScrollToId = target.attr('href').replace(/^#/, '') + ANCHOR_PREFIX; 79 | var $el = $(document.getElementById(elScrollToId)); 80 | 81 | var offsetTop = Math.min(maxScrollTo, elOffset($el)); 82 | 83 | $('body,html').animate({ scrollTop: offsetTop }, 400, 'swing', function() { 84 | location.hash = '#' + elScrollToId; 85 | }); 86 | 87 | $('a', self).removeClass(activeClassName); 88 | target.addClass(activeClassName); 89 | }; 90 | 91 | var calcHadingOffsets = function() { 92 | maxScrollTo = $("body").height() - $(window).height(); 93 | visibleHeight = $(window).height() - navbarHeight; 94 | headingOffsets = []; 95 | headings.each(function(i, heading) { 96 | var anchorSpan = $(heading).prev("span"); 97 | var top = 0; 98 | if (anchorSpan.length) { 99 | top = elOffset(anchorSpan); 100 | } 101 | headingOffsets.push(top > 0 ? top : 0); 102 | }); 103 | } 104 | 105 | //highlight on scroll 106 | var timeout; 107 | var highlightOnScroll = function(e) { 108 | if (!tocs.length) { 109 | return; 110 | } 111 | if (timeout) { 112 | clearTimeout(timeout); 113 | } 114 | timeout = setTimeout(function() { 115 | var top = $(window).scrollTop(), 116 | highlighted; 117 | for (var i = headingOffsets.length - 1; i >= 0; i--) { 118 | var isActive = tocs[i].hasClass(activeClassName); 119 | // at the end of the page, allow any shown header 120 | if (isActive && headingOffsets[i] >= maxScrollTo && top >= maxScrollTo) { 121 | return; 122 | } 123 | // if we have got to the first heading or the heading is the first one visible 124 | if (i === 0 || (headingOffsets[i] + headerHeight >= top && (headingOffsets[i - 1] + headerHeight <= top))) { 125 | // in the case that a heading takes up more than the visible height e.g. we are showing 126 | // only the one above, highlight the one above 127 | if (i > 0 && headingOffsets[i] - visibleHeight >= top) { 128 | i--; 129 | } 130 | $('a', self).removeClass(activeClassName); 131 | if (i >= 0) { 132 | highlighted = tocs[i].addClass(activeClassName); 133 | opts.onHighlight(highlighted); 134 | } 135 | break; 136 | } 137 | } 138 | }, 50); 139 | }; 140 | if (opts.highlightOnScroll) { 141 | $(window).bind('scroll', highlightOnScroll); 142 | $(window).bind('load resize', function() { 143 | calcHadingOffsets(); 144 | highlightOnScroll(); 145 | }); 146 | } 147 | 148 | return this.each(function() { 149 | //build TOC 150 | var el = $(this); 151 | var ul = $('
'); 152 | 153 | headings.each(function(i, heading) { 154 | var $h = $(heading); 155 | 156 | var anchor = $('').attr('id', opts.anchorName(i, heading, opts.prefix) + ANCHOR_PREFIX).insertBefore($h); 157 | 158 | var span = $('') 159 | .text(opts.headerText(i, heading, $h)); 160 | 161 | //build TOC item 162 | var a = $('') 163 | .append(span) 164 | .attr('href', '#' + opts.anchorName(i, heading, opts.prefix)) 165 | .bind('click', function(e) { 166 | scrollTo(e); 167 | el.trigger('selected', $(this).attr('href')); 168 | }); 169 | 170 | span.addClass(opts.itemClass(i, heading, $h, opts.prefix)); 171 | 172 | tocs.push(a); 173 | 174 | ul.append(a); 175 | }); 176 | el.html(ul); 177 | 178 | calcHadingOffsets(); 179 | }); 180 | }; 181 | 182 | 183 | jQuery.fn.toc.defaults = { 184 | container: 'body', 185 | selectors: 'h1,h2,h3', 186 | smoothScrolling: true, 187 | prefix: 'toc', 188 | onHighlight: function() {}, 189 | highlightOnScroll: true, 190 | navbarOffset: 0, 191 | anchorName: function(i, heading, prefix) { 192 | return prefix+i; 193 | }, 194 | headerText: function(i, heading, $heading) { 195 | return $heading.text(); 196 | }, 197 | itemClass: function(i, heading, $heading, prefix) { 198 | return prefix + '-' + $heading[0].tagName.toLowerCase(); 199 | } 200 | 201 | }; 202 | 203 | })(jQuery); 204 | -------------------------------------------------------------------------------- /docs/styles/jaguar.css: -------------------------------------------------------------------------------- 1 | body,html{font:1em jaf-bernino-sans,"Lucida Grande","Lucida Sans Unicode","Lucida Sans",Geneva,Verdana,sans-serif;background-color:#fff}ol,ul{margin:0;padding:0}li{list-style-type:none}#wrap{position:relative}::-webkit-scrollbar{width:8px;background-color:transparent}::-webkit-scrollbar-thumb{background-color:gray;border-radius:4px}.navigation{position:fixed;float:left;width:250px;height:100%;background-color:#1a1a1a}.navigation .applicationName{margin:0;margin-top:15px;padding:10px 15px;font:700 1.25em Helvetica;color:#fff}.navigation .applicationName a{color:#fff}.navigation .search{padding:10px 15px}.navigation .search input{background-color:#333;color:#fff;border-color:#555}.navigation .list{padding:10px 15px 0 15px;position:relative;overflow:auto;width:100%}.navigation li.item{margin-bottom:8px;padding-bottom:8px;border-bottom:1px solid #333}.navigation li.item a{color:#bbb}.navigation li.item a:hover{color:#fff}.navigation li.item .title{cursor:pointer;position:relative;display:block;font-size:.8em}.navigation li.item .title a{color:#e1e1e1}.navigation li.item .title a:hover{color:#fff}.navigation li.item .title .static{display:block;border-radius:3px;background-color:#779c34;color:#000;font-size:.7em;padding:2px 4px;float:right}.navigation li.item .subtitle{margin-top:10px;font:700 .65em Helvetica;color:#779c34;display:block}.navigation li.item ul>li{font-size:.7em;padding-left:8px;margin-top:2px}.navigation li.item .itemMembers{display:none}.main{padding:20px 20px;margin-left:250px}.main .page-title{display:none}.main h1{font-weight:700;font-size:1.6em;margin:0}.main h2{font-weight:700;font-size:1.5em;margin:0}.main h3{font-weight:700;font-size:12px;margin:5px 0}.main h4{font-weight:700;font-size:1em}.main h5{font-weight:700;font-size:12px}.main dd{font-size:12px}.main h4.name span.type-signature{display:inline-block;border-radius:3px;background-color:gray;color:#fff;font-size:.7em;padding:2px 4px}.main h4.name span.type{margin-left:5px}.main h4.name span.glyphicon{display:inline-block;vertical-align:middle;color:#e1e1e1;margin-left:7px}.main h4.name span.returnType{margin-left:3px;background-color:transparent!important;color:gray!important}.main span.static{display:inline-block;border-radius:3px;background-color:#779c34!important;color:#fff;font-size:.7em;padding:2px 4px;margin-right:8px}.main span.number{background-color:gray!important}.main span.string{background-color:gray!important}.main span.object{background-color:#2a6496!important}.main span.array{background-color:#2a6496!important}.main span.boolean{background-color:#ee7d7d!important}.main .subsection-title{font-size:14px;margin-top:30px;color:#779c34}.main .description{margin-top:10px;font-size:13px}.main .description ol,.main .description ul{margin-bottom:15px}.main .description p{font-size:13px}.main .description h2{margin-top:30px;margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid #efefef}.main .description pre{margin:10px 0}.main .tag-source{font-size:12px}.main dt.tag-source{margin-top:5px}.main dt.tag-todo{font-size:10px;display:inline-block;background-color:#2a6496;color:#fff;padding:2px 4px;border-radius:5px}.main .type-signature{font-size:12px}.main .tag-deprecated{display:inline-block;font-size:10px}.main .important{background-color:#ee7d7d;color:#fff;padding:2px 4px;border-radius:5px}.main .nameContainer{position:relative;margin-top:20px;padding-top:5px;border-top:1px solid #e1e1e1}.main .nameContainer .inherited{display:inline-block;border-radius:3px;background-color:#888!important;font-size:.7em;padding:2px 4px;margin-right:5px}.main .nameContainer .inherited a{color:#fff}.main .nameContainer .tag-source{position:absolute;top:17px;right:0;font-size:10px}.main .nameContainer .tag-source a{color:gray}.main .nameContainer.inherited{color:gray}.main .nameContainer h4{margin-right:150px;line-height:1.3}.main .nameContainer h4 .signature{font-size:13px;font-weight:400;font-family:Menlo,Monaco,Consolas,"Courier New",monospace}.main .nameContainer h4 .type-signature.type a{color:#fff}.main pre{font-size:11px}.main table{width:100%;margin-bottom:15px}.main table th{padding:3px 3px}.main table td{vertical-align:top;padding:5px 3px}.main table .name{width:110px}.main table .type{width:60px;color:#aaa;font-size:11px}.main table .attributes{width:80px;color:#aaa;font-size:11px}.main table .description{font-size:12px}.main table .description p{margin:0}.main table .optional{float:left;border-radius:3px;background-color:#ddd!important;font-size:.7em;padding:2px 4px;margin-right:5px;color:gray}.main .readme p{margin-top:15px;line-height:1.2;font-size:.85em}.main .readme h1{font-size:1.7em}.main .readme h2{margin-top:30px;margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid #e1e1e1}.main .readme li{font-size:.9em;margin-bottom:10px}.main article ol,.main article ul{margin-left:25px}.main article ol>li{list-style-type:decimal;margin-bottom:5px}.main article ul>li{margin-bottom:5px;list-style-type:disc}footer{margin:15px 0;padding-top:15px;border-top:1px solid #e1e1e1;font-family:freight-text-pro,Georgia,Cambria,"Times New Roman",Times,serif;font-size:.8em;color:gray} -------------------------------------------------------------------------------- /docs/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: #006400; 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /docs/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Menlo, Monaco, Consolas, monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /docs/styles/sunlight.dark.css: -------------------------------------------------------------------------------- 1 | /* global styles */ 2 | .sunlight-container { 3 | clear: both !important; 4 | position: relative !important; 5 | margin: 10px 0 !important; 6 | } 7 | .sunlight-code-container { 8 | clear: both !important; 9 | position: relative !important; 10 | border: none; 11 | border-color: #626262 !important; 12 | background-color: #262626 !important; 13 | } 14 | .sunlight-highlighted, .sunlight-container, .sunlight-container textarea { 15 | font-family: Consolas, Inconsolata, Monaco, "Courier New" !important; 16 | font-size: 12px !important; 17 | line-height: 15px !important; 18 | } 19 | .sunlight-highlighted, .sunlight-container textarea { 20 | color: #FFFFFF !important; 21 | margin: 0 !important; 22 | } 23 | .sunlight-container textarea { 24 | padding-left: 0 !important; 25 | margin-left: 0 !important; 26 | margin-right: 0 !important; 27 | padding-right: 0 !important; 28 | } 29 | .sunlight-code-container > .sunlight-highlighted { 30 | white-space: pre; 31 | overflow-x: auto; 32 | overflow-y: hidden; /* ie requires this wtf? */ 33 | } 34 | .sunlight-highlighted { 35 | z-index: 1; 36 | position: relative; 37 | } 38 | .sunlight-highlighted * { 39 | background: transparent; 40 | } 41 | .sunlight-line-number-margin { 42 | float: left !important; 43 | margin-right: 5px !important; 44 | margin-top: 0 !important; 45 | margin-bottom: 0 !important; 46 | padding: 0 !important; 47 | padding-right: 4px !important; 48 | padding-left: 4px !important; 49 | border-right: 1px solid #9A9A9A !important; 50 | background-color: #3E3E3E !important; 51 | color: #9A9A9A !important; 52 | text-align: right !important; 53 | position: relative; 54 | z-index: 3; 55 | } 56 | .sunlight-highlighted a, .sunlight-line-number-margin a { 57 | border: none !important; 58 | text-decoration: none !important; 59 | font-style: normal !important; 60 | padding: 0 !important; 61 | } 62 | .sunlight-line-number-margin a { 63 | color: inherit !important; 64 | } 65 | .sunlight-line-highlight-overlay { 66 | position: absolute; 67 | top: 0; 68 | left: 0; 69 | width: 100%; 70 | z-index: 0; 71 | } 72 | .sunlight-line-highlight-overlay div { 73 | height: 15px; 74 | width: 100%; 75 | } 76 | .sunlight-line-highlight-overlay .sunlight-line-highlight-active { 77 | background-color: #4B4B4B; 78 | } 79 | 80 | /* menu */ 81 | .sunlight-menu { 82 | background-color: #FFFFCC; 83 | color: #000000; 84 | } 85 | .sunlight-menu ul { 86 | margin: 0 !important; 87 | padding: 0 !important; 88 | list-style-type: none !important; 89 | } 90 | .sunlight-menu li { 91 | float: right !important; 92 | margin-left: 5px !important; 93 | } 94 | .sunlight-menu a, .sunlight-menu img { 95 | color: #000099 !important; 96 | text-decoration: none !important; 97 | border: none !important; 98 | } 99 | 100 | 101 | 102 | 103 | .sunlight-string, 104 | .sunlight-char, 105 | .sunlight-heredoc, 106 | .sunlight-heredocDeclaration, 107 | .sunlight-nowdoc, 108 | .sunlight-longString, 109 | .sunlight-rawString, 110 | .sunlight-binaryString, 111 | .sunlight-verbatimString, 112 | .sunlight-rawLongString, 113 | .sunlight-binaryLongString, 114 | .sunlight-diff .sunlight-added { 115 | color: #55EB54 !important; 116 | } 117 | .sunlight-operator, 118 | .sunlight-punctuation, 119 | .sunlight-delimiter { 120 | color: #B1EDEC !important; 121 | } 122 | .sunlight-ident, 123 | .sunlight-diff .sunlight-unchanged { 124 | color: #E0E0E0 !important; 125 | font-weight: bold !important; 126 | } 127 | .sunlight-comment, 128 | .sunlight-xmlDocCommentContent, 129 | .sunlight-nginx .sunlight-ssiCommand, 130 | .sunlight-sln .sunlight-formatDeclaration, 131 | .sunlight-diff .sunlight-mergeHeader, 132 | .sunlight-diff .sunlight-noNewLine { 133 | color: #787D31 !important; 134 | } 135 | .sunlight-number, 136 | .sunlight-cdata, 137 | .sunlight-guid, 138 | .sunlight-diff .sunlight-modified { 139 | color: #F7BA7E !important; 140 | font-weight: bold !important; 141 | } 142 | .sunlight-named-ident, 143 | .sunlight-xml .sunlight-attribute, 144 | .sunlight-constant, 145 | .sunlight-javascript .sunlight-globalVariable, 146 | .sunlight-globalObject, 147 | .sunlight-css .sunlight-id, 148 | .sunlight-python .sunlight-attribute, 149 | .sunlight-nginx .sunlight-context, 150 | .sunlight-httpd .sunlight-context, 151 | .sunlight-lisp .sunlight-declarationSpecifier, 152 | .sunlight-erlang .sunlight-userDefinedFunction, 153 | .sunlight-diff .sunlight-removed { 154 | color: #FBBDEE !important; 155 | font-weight: bold !important; 156 | } 157 | .sunlight-keyword, 158 | .sunlight-languageConstruct, 159 | .sunlight-specialOperator, 160 | .sunlight-xml .sunlight-tagName, 161 | .sunlight-xml .sunlight-operator, 162 | .sunlight-bash .sunlight-command, 163 | .sunlight-erlang .sunlight-moduleAttribute { 164 | color: #A3CCF7 !important; 165 | font-weight: bold !important; 166 | } 167 | .sunlight-shortOpenTag, 168 | .sunlight-openTag, 169 | .sunlight-closeTag, 170 | .sunlight-xmlOpenTag, 171 | .sunlight-xmlCloseTag, 172 | .sunlight-aspOpenTag, 173 | .sunlight-aspCloseTag, 174 | .sunlight-label, 175 | .sunlight-css .sunlight-importantFlag { 176 | background-color: #7373C1 !important; 177 | } 178 | .sunlight-content { 179 | color: #FFFFFF !important; 180 | font-weight: bold !important; 181 | } 182 | .sunlight-function, 183 | .sunlight-globalFunction, 184 | .sunlight-objective-c .sunlight-messageDestination, 185 | .sunlight-ruby .sunlight-specialFunction, 186 | .sunlight-6502asm .sunlight-illegalOpcode, 187 | .sunlight-powershell .sunlight-switch, 188 | .sunlight-lisp .sunlight-macro, 189 | .sunlight-lisp .sunlight-specialForm, 190 | .sunlight-lisp .sunlight-type, 191 | .sunlight-sln .sunlight-sectionName, 192 | .sunlight-diff .sunlight-header { 193 | color: #C8BBF1 !important; 194 | font-weight: bold !important; 195 | } 196 | .sunlight-variable, 197 | .sunlight-environmentVariable, 198 | .sunlight-specialVariable, 199 | .sunlight-objective-c .sunlight-messageArgumentName, 200 | .sunlight-lisp .sunlight-globalVariable, 201 | .sunlight-ruby .sunlight-globalVariable, 202 | .sunlight-ruby .sunlight-instanceVariable { 203 | color: #F5E5B0 !important; 204 | font-weight: bold !important; 205 | } 206 | .sunlight-regexLiteral, 207 | .sunlight-lisp .sunlight-operator, 208 | .sunlight-6502asm .sunlight-pseudoOp, 209 | .sunlight-erlang .sunlight-macro, 210 | .sunlight-diff .sunlight-rangeInfo { 211 | color: #E0F16A !important; 212 | } 213 | .sunlight-specialVariable { 214 | font-style: italic !important; 215 | font-weight: bold !important; 216 | } 217 | .sunlight-csharp .sunlight-pragma, 218 | .sunlight-preprocessorDirective, 219 | .sunlight-vb .sunlight-compilerDirective { 220 | color: #666363 !important; 221 | font-style: italic !important; 222 | } 223 | .sunlight-xmlDocCommentMeta, 224 | .sunlight-java .sunlight-annotation, 225 | .sunlight-scala .sunlight-annotation, 226 | .sunlight-docComment { 227 | color: #666363 !important; 228 | } 229 | .sunlight-quotedIdent, 230 | .sunlight-ruby .sunlight-subshellCommand, 231 | .sunlight-lisp .sunlight-keywordArgument, 232 | .sunlight-haskell .sunlight-infixOperator, 233 | .sunlight-erlang .sunlight-quotedAtom { 234 | color: #F8CA16 !important; 235 | } 236 | 237 | 238 | 239 | 240 | /* html/xml */ 241 | .sunlight-xml .sunlight-tagName, 242 | .sunlight-xml .sunlight-operator, 243 | .sunlight-xml .sunlight-attribute { 244 | font-weight: normal !important; 245 | } 246 | .sunlight-doctype { 247 | color: #DEB9B2 !important; 248 | font-style: italic !important; 249 | } 250 | .sunlight-xml .sunlight-entity { 251 | background-color: #E6E585 !important; 252 | color: #000000 !important; 253 | } 254 | 255 | /* javascript */ 256 | .sunlight-javascript .sunlight-reservedWord { 257 | font-style: italic !important; 258 | } 259 | 260 | /* css */ 261 | .sunlight-css .sunlight-element { 262 | color: #E9EE97 !important; 263 | } 264 | .sunlight-css .sunlight-microsoftFilterPrefix { 265 | color: #C9FF9F !important; 266 | } 267 | .sunlight-css .sunlight-rule { 268 | color: #0099FF !important; 269 | } 270 | .sunlight-css .sunlight-class { 271 | color: #E78282 !important; 272 | } 273 | .sunlight-css .sunlight-pseudoClass, .sunlight-css .sunlight-pseudoElement { 274 | color: #73D693 !important; 275 | } 276 | 277 | /* bash */ 278 | .sunlight-bash .sunlight-hashBang { 279 | color: #FFFF00 !important; 280 | } 281 | 282 | .sunlight-bash .sunlight-verbatimCommand { 283 | color: #BBA4EE !important; 284 | } 285 | .sunlight-bash .sunlight-variable, 286 | .sunlight-bash .sunlight-specialVariable { 287 | color: #ED8585 !important; 288 | } 289 | 290 | /* python */ 291 | .sunlight-python .sunlight-specialMethod { 292 | font-weight: bold !important; 293 | color: #B0A3C2; 294 | } 295 | 296 | /* ruby */ 297 | .sunlight-ruby .sunlight-symbol { 298 | font-weight: bold !important; 299 | color: #90EEA2 !important; 300 | } 301 | 302 | /* brainfuck */ 303 | .sunlight-brainfuck { 304 | font-weight: bold !important; 305 | color: #000000 !important; 306 | } 307 | .sunlight-brainfuck .sunlight-increment { 308 | background-color: #FF9900 !important; 309 | } 310 | .sunlight-brainfuck .sunlight-decrement { 311 | background-color: #FF99FF !important; 312 | } 313 | .sunlight-brainfuck .sunlight-incrementPointer { 314 | background-color: #FFFF99 !important; 315 | } 316 | .sunlight-brainfuck .sunlight-decrementPointer { 317 | background-color: #66CCFF !important; 318 | } 319 | .sunlight-brainfuck .sunlight-read { 320 | background-color: #FFFFFF !important; 321 | } 322 | .sunlight-brainfuck .sunlight-write { 323 | background-color: #99FF99 !important; 324 | } 325 | .sunlight-brainfuck .sunlight-openLoop, .sunlight-brainfuck .sunlight-closeLoop { 326 | background-color: #FFFFFF !important; 327 | } 328 | 329 | /* 6502 asm */ 330 | .sunlight-6502asm .sunlight-label { 331 | background: none !important; 332 | color: #FFFFFF !important; 333 | text-decoration: underline !important; 334 | } 335 | 336 | /* lisp */ 337 | .sunlight-lisp .sunlight-macro { 338 | font-style: italic !important; 339 | } 340 | 341 | /* erlang */ 342 | .sunlight-erlang .sunlight-atom { 343 | color: #FFFFFF !important; 344 | font-weight: bold !important; 345 | } -------------------------------------------------------------------------------- /docs/styles/sunlight.default.css: -------------------------------------------------------------------------------- 1 | /* global styles */ 2 | .sunlight-container { 3 | clear: both !important; 4 | position: relative !important; 5 | margin: 10px 0 !important; 6 | } 7 | .sunlight-code-container { 8 | clear: both !important; 9 | position: relative !important; 10 | border: none; 11 | border-color: #969696 !important; 12 | background-color: #FFFFFF !important; 13 | } 14 | .sunlight-highlighted, .sunlight-container, .sunlight-container textarea { 15 | font-family: Consolas, Inconsolata, Monaco, "Courier New" !important; 16 | font-size: 12px !important; 17 | line-height: 15px !important; 18 | } 19 | .sunlight-highlighted, .sunlight-container textarea { 20 | color: #000000 !important; 21 | margin: 0 !important; 22 | } 23 | .sunlight-container textarea { 24 | padding-left: 0 !important; 25 | margin-left: 0 !important; 26 | margin-right: 0 !important; 27 | padding-right: 0 !important; 28 | } 29 | .sunlight-code-container > .sunlight-highlighted { 30 | white-space: pre; 31 | overflow-x: auto; 32 | overflow-y: hidden; /* ie requires this wtf? */ 33 | } 34 | .sunlight-highlighted { 35 | z-index: 1; 36 | position: relative; 37 | } 38 | .sunlight-highlighted * { 39 | background: transparent; 40 | } 41 | .sunlight-line-number-margin { 42 | float: left !important; 43 | margin-right: 5px !important; 44 | margin-top: 0 !important; 45 | margin-bottom: 0 !important; 46 | padding: 0 !important; 47 | padding-right: 4px !important; 48 | padding-left: 4px !important; 49 | border-right: 1px solid #CCCCCC !important; 50 | background-color: #EEEEEE !important; 51 | color: #848484 !important; 52 | text-align: right !important; 53 | position: relative; 54 | z-index: 3; 55 | } 56 | .sunlight-highlighted a, .sunlight-line-number-margin a { 57 | border: none !important; 58 | text-decoration: none !important; 59 | font-weight: normal !important; 60 | font-style: normal !important; 61 | padding: 0 !important; 62 | } 63 | .sunlight-line-number-margin a { 64 | color: inherit !important; 65 | } 66 | .sunlight-line-highlight-overlay { 67 | position: absolute; 68 | top: 0; 69 | left: 0; 70 | width: 100%; 71 | z-index: 0; 72 | } 73 | .sunlight-line-highlight-overlay div { 74 | height: 15px; 75 | width: 100%; 76 | } 77 | .sunlight-line-highlight-overlay .sunlight-line-highlight-active { 78 | background-color: #E7FCFA; 79 | } 80 | 81 | /* menu */ 82 | .sunlight-menu { 83 | background-color: #FFFFCC; 84 | color: #000000; 85 | } 86 | .sunlight-menu ul { 87 | margin: 0 !important; 88 | padding: 0 !important; 89 | list-style-type: none !important; 90 | } 91 | .sunlight-menu li { 92 | float: right !important; 93 | margin-left: 5px !important; 94 | } 95 | .sunlight-menu a, .sunlight-menu img { 96 | color: #000099 !important; 97 | text-decoration: none !important; 98 | border: none !important; 99 | } 100 | 101 | 102 | 103 | 104 | .sunlight-string, 105 | .sunlight-char, 106 | .sunlight-heredoc, 107 | .sunlight-heredocDeclaration, 108 | .sunlight-nowdoc, 109 | .sunlight-longString, 110 | .sunlight-rawString, 111 | .sunlight-binaryString, 112 | .sunlight-rawLongString, 113 | .sunlight-binaryLongString, 114 | .sunlight-verbatimString, 115 | .sunlight-diff .sunlight-removed { 116 | color: #990000 !important; 117 | } 118 | 119 | .sunlight-ident, 120 | .sunlight-operator, 121 | .sunlight-punctuation, 122 | .sunlight-delimiter, 123 | .sunlight-diff .sunlight-unchanged { 124 | color: #000000 !important; 125 | } 126 | 127 | .sunlight-comment, 128 | .sunlight-xmlDocCommentContent, 129 | .sunlight-nginx .sunlight-ssiCommand, 130 | .sunlight-sln .sunlight-formatDeclaration, 131 | .sunlight-diff .sunlight-added { 132 | color: #009900 !important; 133 | } 134 | .sunlight-number, 135 | .sunlight-guid, 136 | .sunlight-cdata { 137 | color: #CC6600 !important; 138 | } 139 | 140 | .sunlight-named-ident, 141 | .sunlight-constant, 142 | .sunlight-javascript .sunlight-globalVariable, 143 | .sunlight-globalObject, 144 | .sunlight-python .sunlight-attribute, 145 | .sunlight-nginx .sunlight-context, 146 | .sunlight-httpd .sunlight-context, 147 | .sunlight-haskell .sunlight-class, 148 | .sunlight-haskell .sunlight-type, 149 | .sunlight-lisp .sunlight-declarationSpecifier, 150 | .sunlight-erlang .sunlight-userDefinedFunction, 151 | .sunlight-diff .sunlight-header { 152 | color: #2B91AF !important; 153 | } 154 | .sunlight-keyword, 155 | .sunlight-languageConstruct, 156 | .sunlight-css 157 | .sunlight-element, 158 | .sunlight-bash .sunlight-command, 159 | .sunlight-specialOperator, 160 | .sunlight-erlang .sunlight-moduleAttribute, 161 | .sunlight-xml .sunlight-tagName, 162 | .sunlight-xml .sunlight-operator, 163 | .sunlight-diff .sunlight-modified { 164 | color: #0000FF !important; 165 | } 166 | .sunlight-shortOpenTag, 167 | .sunlight-openTag, 168 | .sunlight-closeTag, 169 | .sunlight-xmlOpenTag, 170 | .sunlight-xmlCloseTag, 171 | .sunlight-aspOpenTag, 172 | .sunlight-aspCloseTag, 173 | .sunlight-label, 174 | .sunlight-css .sunlight-importantFlag { 175 | background-color: #FFFF99 !important; 176 | color: #000000 !important; 177 | } 178 | .sunlight-function, 179 | .sunlight-globalFunction, 180 | .sunlight-ruby .sunlight-specialFunction, 181 | .sunlight-objective-c .sunlight-messageDestination, 182 | .sunlight-6502asm .sunlight-illegalOpcode, 183 | .sunlight-powershell .sunlight-switch, 184 | .sunlight-lisp .sunlight-macro, 185 | .sunlight-lisp .sunlight-specialForm, 186 | .sunlight-lisp .sunlight-type, 187 | .sunlight-sln .sunlight-sectionName, 188 | .sunlight-diff .sunlight-rangeInfo { 189 | color: #B069AF !important; 190 | } 191 | 192 | .sunlight-variable, 193 | .sunlight-specialVariable, 194 | .sunlight-environmentVariable, 195 | .sunlight-objective-c .sunlight-messageArgumentName, 196 | .sunlight-lisp .sunlight-globalVariable, 197 | .sunlight-ruby .sunlight-globalVariable, 198 | .sunlight-ruby .sunlight-instanceVariable, 199 | .sunlight-sln .sunlight-operator { 200 | color: #325484 !important; 201 | } 202 | .sunlight-regexLiteral, 203 | .sunlight-lisp .sunlight-operator, 204 | .sunlight-6502asm .sunlight-pseudoOp, 205 | .sunlight-erlang .sunlight-macro { 206 | color: #FF00B2 !important; 207 | } 208 | .sunlight-specialVariable { 209 | font-style: italic !important; 210 | font-weight: bold !important; 211 | } 212 | .sunlight-csharp .sunlight-pragma, 213 | .sunlight-preprocessorDirective, 214 | .sunlight-vb .sunlight-compilerDirective, 215 | .sunlight-diff .sunlight-mergeHeader, 216 | .sunlight-diff .sunlight-noNewLine { 217 | color: #999999 !important; 218 | font-style: italic !important; 219 | } 220 | .sunlight-xmlDocCommentMeta, 221 | .sunlight-java .sunlight-annotation, 222 | .sunlight-scala .sunlight-annotation, 223 | .sunlight-docComment { 224 | color: #808080 !important; 225 | } 226 | .sunlight-quotedIdent, 227 | .sunlight-ruby .sunlight-subshellCommand, 228 | .sunlight-lisp .sunlight-keywordArgument, 229 | .sunlight-haskell .sunlight-infixOperator, 230 | .sunlight-erlang .sunlight-quotedAtom { 231 | color: #999900 !important; 232 | } 233 | 234 | 235 | 236 | /* xml */ 237 | .sunlight-xml .sunlight-string { 238 | color: #990099 !important; 239 | } 240 | .sunlight-xml .sunlight-attribute { 241 | color: #FF0000 !important; 242 | } 243 | .sunlight-xml .sunlight-entity { 244 | background-color: #EEEEEE !important; 245 | color: #000000 !important; 246 | border: 1px solid #000000 !important; 247 | } 248 | .sunlight-xml .sunlight-doctype { 249 | color: #2B91AF !important; 250 | } 251 | 252 | /* javascript */ 253 | .sunlight-javascript .sunlight-reservedWord { 254 | font-style: italic !important; 255 | } 256 | 257 | /* css */ 258 | .sunlight-css .sunlight-microsoftFilterPrefix { 259 | color: #FF00FF !important; 260 | } 261 | .sunlight-css .sunlight-rule { 262 | color: #0099FF !important; 263 | } 264 | .sunlight-css .sunlight-keyword { 265 | color: #4E65B8 !important; 266 | } 267 | .sunlight-css .sunlight-class { 268 | color: #FF0000 !important; 269 | } 270 | .sunlight-css .sunlight-id { 271 | color: #8A8E13 !important; 272 | } 273 | .sunlight-css .sunlight-pseudoClass, 274 | .sunlight-css .sunlight-pseudoElement { 275 | color: #368B87 !important; 276 | } 277 | 278 | /* bash */ 279 | .sunlight-bash .sunlight-hashBang { 280 | color: #3D97F5 !important; 281 | } 282 | .sunlight-bash .sunlight-verbatimCommand { 283 | color: #999900 !important; 284 | } 285 | .sunlight-bash .sunlight-variable, 286 | .sunlight-bash .sunlight-specialVariable { 287 | color: #FF0000 !important; 288 | } 289 | 290 | /* python */ 291 | .sunlight-python .sunlight-specialMethod { 292 | font-weight: bold !important; 293 | color: #A07DD3; 294 | } 295 | 296 | /* ruby */ 297 | .sunlight-ruby .sunlight-symbol { 298 | font-weight: bold !important; 299 | color: #ED7272 !important; 300 | } 301 | 302 | /* brainfuck */ 303 | .sunlight-brainfuck { 304 | font-weight: bold !important; 305 | color: #000000 !important; 306 | } 307 | .sunlight-brainfuck .sunlight-increment { 308 | background-color: #FF9900 !important; 309 | } 310 | .sunlight-brainfuck .sunlight-decrement { 311 | background-color: #FF99FF !important; 312 | } 313 | .sunlight-brainfuck .sunlight-incrementPointer { 314 | background-color: #FFFF99 !important; 315 | } 316 | .sunlight-brainfuck .sunlight-decrementPointer { 317 | background-color: #66CCFF !important; 318 | } 319 | .sunlight-brainfuck .sunlight-read { 320 | background-color: #FFFFFF !important; 321 | } 322 | .sunlight-brainfuck .sunlight-write { 323 | background-color: #99FF99 !important; 324 | } 325 | .sunlight-brainfuck .sunlight-openLoop, .sunlight-brainfuck .sunlight-closeLoop { 326 | background-color: #FFFFFF !important; 327 | } 328 | 329 | /* 6502 asm */ 330 | .sunlight-6502asm .sunlight-label { 331 | font-weight: bold !important; 332 | color: #000000 !important; 333 | background: none !important; 334 | } 335 | 336 | /* lisp */ 337 | .sunlight-lisp .sunlight-macro { 338 | font-style: italic !important; 339 | } 340 | 341 | /* erlang */ 342 | .sunlight-erlang .sunlight-atom { 343 | font-weight: bold !important; 344 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Include required modules 4 | const gulp = require('gulp'); 5 | const babelify = require('babelify'); 6 | const babel = require('gulp-babel'); 7 | const browserify = require('browserify'); 8 | const connect = require('gulp-connect'); 9 | const source = require('vinyl-source-stream'); 10 | const buffer = require('vinyl-buffer'); 11 | const sourcemaps = require('gulp-sourcemaps'); 12 | const eslint = require('gulp-eslint'); 13 | const jsdoc = require('gulp-jsdoc3'); 14 | const uglify = require('gulp-uglify'); 15 | const rename = require('gulp-rename'); 16 | const del = require('del'); 17 | const mochaPhantomJS = require('gulp-mocha-phantomjs'); 18 | 19 | // paths 20 | const NAME = require('./package.json').name; 21 | const DOC_PATH = './src/**/*.js'; 22 | const SRC_FILES = DOC_PATH; 23 | const LIB_PATH = './lib'; 24 | const LINT_PATH = DOC_PATH; 25 | const BUILD_OUTPUT = './dist'; 26 | const DEMOS_OUTPUT = BUILD_OUTPUT + '/demos'; 27 | const THEME_PATH = './styleSheets'; 28 | 29 | // Default task. This will be run when no task is passed in arguments to gulp 30 | gulp.task('default', ['uglify', 'doc']); 31 | 32 | // test 33 | gulp.task('test', ['buildTest'], function() { 34 | return gulp 35 | .src('test/runner.html') 36 | .pipe(mochaPhantomJS({ 37 | reporter: 'spec', 38 | phantomjs: { 39 | useColors: true, 40 | }, 41 | })); 42 | }); 43 | 44 | // clean 45 | gulp.task('clean', function(cb) { 46 | return del(['dist', 'doc', 'lib'], cb); 47 | }); 48 | 49 | // lint with eslint 50 | gulp.task('lint', () => { 51 | return gulp.src([LINT_PATH, '!node_modules/**']) 52 | .pipe(eslint()) 53 | .pipe(eslint.format()) 54 | .pipe(eslint.failAfterError()); 55 | }); 56 | 57 | // Copy static files from html folder to dist folder 58 | gulp.task('copyDemos', ['clean', 'lint'], function() { 59 | return gulp.src('./src/demos/**/*') 60 | .pipe(gulp.dest(DEMOS_OUTPUT)); 61 | }); 62 | 63 | // Copy themes to dist 64 | gulp.task('copyStyles', ['copyDemos'], function() { 65 | return gulp.src(THEME_PATH + '/**/*') 66 | .pipe(gulp.dest(BUILD_OUTPUT + '/styleSheets')); 67 | }); 68 | 69 | // jsdoc 70 | gulp.task('doc', function(cb) { 71 | let config = require('./jsdoc.json'); 72 | gulp.src(['README.md', DOC_PATH], {read: false}) 73 | .pipe(jsdoc(config, cb)); 74 | }); 75 | 76 | gulp.task('buildTest', ()=>{ 77 | return browserify({ 78 | entries: ['./test/index.js'], 79 | debug: true, /* !gulp.env.production,*/ 80 | insertGlobals: true, 81 | }) 82 | .transform(babelify.configure({presets: ['es2015']})) 83 | .bundle() 84 | .pipe(source('test.js')) 85 | .pipe(gulp.dest('./test')) 86 | ; 87 | }); 88 | 89 | // Convert ES6 code in all js files in src/js folder and copy to 90 | // dist folder 91 | gulp.task('build', ['copyStyles'], function() { 92 | return browserify({ 93 | entries: ['./src/index.js'], 94 | debug: false, /* !gulp.env.production,*/ 95 | // insertGlobals: true, 96 | }) 97 | // .pipe(sourcemaps.init()) 98 | .transform(babelify.configure({presets: ['es2015']})) 99 | .bundle() 100 | .pipe(source(NAME + '.js')) 101 | // .pipe(sourcemaps.write(BUILD_OUTPUT)) 102 | .pipe(gulp.dest(BUILD_OUTPUT)) 103 | ; 104 | }); 105 | 106 | // Convert ES6 code in all js files in src/js folder and copy to 107 | // dist folder 108 | gulp.task('npmbuild', ['copyStyles'], function() { 109 | return gulp.src(SRC_FILES) 110 | .pipe(sourcemaps.init()) 111 | .pipe(babel({ 112 | presets: ['es2015'], 113 | })) 114 | .pipe(sourcemaps.write('.')) 115 | .pipe(gulp.dest(LIB_PATH)); 116 | }); 117 | 118 | // uglify 119 | gulp.task('uglify', ['build'], ()=> { 120 | return gulp.src([BUILD_OUTPUT + '/' + NAME + '.js']) 121 | .pipe(sourcemaps.init()) 122 | .pipe(buffer()) 123 | .pipe(uglify({ 124 | compress: { 125 | passes: 10, 126 | sequences: true, 127 | dead_code: true, 128 | conditionals: true, 129 | booleans: true, 130 | unused: true, 131 | if_return: true, 132 | join_vars: true, 133 | drop_console: true, 134 | unsafe: true, 135 | unsafe_proto: true, 136 | evaluate: true, 137 | loops: true, 138 | if_return: true, 139 | join_vars: true, 140 | cascade: true, 141 | collapse_vars: true, 142 | reduce_vars: true, 143 | }, 144 | output: { 145 | beautify: false, 146 | semicolons: false, 147 | quote_style: 1, 148 | }, 149 | mangle: true})) 150 | .pipe(rename(NAME + '.min.js')) 151 | .pipe(sourcemaps.write('.')) 152 | .pipe(gulp.dest(BUILD_OUTPUT)); 153 | }); 154 | 155 | gulp.task('cleanWWW', function(cb) { 156 | return del(['www'], cb); 157 | }); 158 | 159 | gulp.task('precocoon', ['cleanWWW'], function() { 160 | gulp.src('./src/demos/**/*') 161 | .pipe(gulp.dest('./www/')); 162 | gulp.src('./styleSheets/**/*') 163 | .pipe(gulp.dest('./www/')); 164 | gulp.src('./dist/sabertooth.min.js') 165 | .pipe(gulp.dest('./www/')); 166 | }); 167 | 168 | 169 | // Start a test server with doc root at dist folder and 170 | // listening to 9001 port. Home page = http://localhost:9001 171 | gulp.task('startServer', function() { 172 | connect.server({ 173 | root: DEMOS_OUTPUT, 174 | livereload: true, 175 | port: 9001, 176 | }); 177 | }); 178 | -------------------------------------------------------------------------------- /inch.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "included": [ 4 | "src/**/*.js" 5 | ], 6 | "excluded": [ 7 | 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true 4 | }, 5 | 6 | "source":{ 7 | "includePattern": ".+\\.js(doc)?$", 8 | "excludePattern": "(^|\\/|\\\\)_" 9 | }, 10 | 11 | "opts": { 12 | "destination": "./docs", 13 | "recurse": true, 14 | "verbose": true, 15 | "private": true, 16 | "encoding": "utf8", 17 | "lenient": true, 18 | "template": "./node_modules/jaguarjs-jsdoc" 19 | }, 20 | 21 | "plugins": [ 22 | "plugins/markdown", 23 | "./scripts/fixJsdoc.js" 24 | ], 25 | 26 | "templates": { 27 | "cleverLinks": false, 28 | "monospaceLinks": false, 29 | "default": { 30 | "outputSourceFiles": true 31 | }, 32 | "linenums": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sabertooth", 3 | "version": "0.2.1", 4 | "description": "A WebGL UI framework built on pixi.js and inspired by QT", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "test": "gulp test" 8 | }, 9 | "keywords": [ 10 | "WebGL", 11 | "UI", 12 | "user interface", 13 | "pixi", 14 | "2d", 15 | "mobile", 16 | "desktop", 17 | "web", 18 | "apps" 19 | ], 20 | "author": "Zach Moore ", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "github.com/AbydosDigital/SaberTooth/issues", 24 | "email": "AbydosDigital@gmail.com" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/AbydosDigital/SaberTooth.git" 29 | }, 30 | "devDependencies": { 31 | "babel-plugin-version-inline": "^1.0.0", 32 | "babel-preset-es2015": "^6.22.0", 33 | "babelify": "^7.3.0", 34 | "browserify": "^14.1.0", 35 | "chai": "^3.5.0", 36 | "eslint": "^3.14.0", 37 | "eslint-config-google": "^0.7.1", 38 | "eslint-plugin-import": "^2.2.0", 39 | "gulp": "^3.9.1", 40 | "gulp-babel": "^6.1.2", 41 | "gulp-connect": "^5.7.0", 42 | "gulp-eslint": "^3.0.1", 43 | "gulp-inject": "^4.2.0", 44 | "gulp-jsdoc3": "^1.0.1", 45 | "gulp-mocha": "^4.1.0", 46 | "gulp-mocha-phantomjs": "^0.12.1", 47 | "gulp-rename": "^1.2.2", 48 | "gulp-sourcemaps": "^2.4.1", 49 | "gulp-uglify": "^2.1.2", 50 | "jaguarjs-jsdoc": "^1.0.2", 51 | "jsdoc": "^3.4.3", 52 | "mocha": "^3.3.0", 53 | "mocha-phantomjs": "^4.1.0", 54 | "phantomjs": "^1.9.7-15", 55 | "pixi.js": "^4.5.1", 56 | "sinon": "^2.2.0", 57 | "vinyl-buffer": "^1.0.0", 58 | "vinyl-source-stream": "^1.1.0" 59 | }, 60 | "dependencies": { 61 | "babel": "^6.23.0", 62 | "babel-polyfill": "^6.23.0", 63 | "babel-register": "^6.24.0", 64 | "del": "^2.2.2", 65 | "eventemitter3": "^2.0.2", 66 | "pixi-gl-core": "^1.1.0", 67 | "pixi.js": "^4.5.1" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zachacious/Sabertooth/02840b880fbb75f30b993398cd7627af08f911b7/screenshot.jpg -------------------------------------------------------------------------------- /scripts/fixJsdoc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // JSDoc has issues getting the name of `export default class NAME {}` 4 | // this gross regex hacks around that issue until it is fixed. 5 | // See: https://github.com/jsdoc3/jsdoc/issues/1137#issuecomment-174829004 6 | 7 | const rgxGross = /(\/\*{2}[\W\w]+?\*\/)\s*export\s+default\s+class\s+([^\s]*)/g; 8 | const grossReplace = 'export default $2;\n\n$1\nclass $2'; 9 | 10 | // JSDoc has issues with expressing member properties within a class 11 | // this is another terrible hack to address this issue. 12 | // See: https://github.com/jsdoc3/jsdoc/issues/1301 13 | 14 | const rgxMember = /(\@member \{[^\}]+\})(\n[^\/]+\/[\b\s]+)(this\.([^\s]+))/g; 15 | const rgxClassName = /export (default )?class (.+?)\s/; 16 | const rgxNamespace = /\@memberof ([\.a-zA-Z0-9]+)\s/; 17 | 18 | exports.handlers = { 19 | /** 20 | * Called before parsing a file, giving us a change to replace the source. 21 | * 22 | * @param {*} e - The `beforeParse` event data. 23 | * @param {string} e.filename - The name of the file being parsed. 24 | * @param {string} e.source - The source of the file being parsed. 25 | */ 26 | beforeParse(e) { 27 | const namespace = e.source.match(rgxNamespace); 28 | const className = e.source.match(rgxClassName); 29 | 30 | // Fix members not showing up attached to class 31 | if (namespace && className) { 32 | // console.log(`${namespace[1]}.${className[2]}`); 33 | // Replaces "@member {Type}"" with "@member {Type} 34 | // PIXI.ClassName#prop" 35 | e.source = e.source 36 | .replace(rgxMember, `$1 ${namespace[1]}.${className[2]}#$4$2$3`); 37 | } 38 | 39 | e.source = e.source.replace(rgxGross, grossReplace); 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import * as PIXI from 'pixi.js'; 2 | // import {autoDetectRenderer} from 'pixi.js/lib/core'; 3 | import Theme from './Theme'; 4 | import StageWidget from './widgets/StageWidget'; 5 | import BaseWidget from './widgets/BaseWidget'; 6 | import EventEmitter from 'eventemitter3'; 7 | 8 | /** 9 | * App will setup PIXI renderer and root widget automatically. 10 | * Just call App.update() from your render loop 11 | * @class 12 | * @memberof ST 13 | * @extends external:EventEmitter 14 | * 15 | * @example 16 | * let myApp = new ST.App({ 17 | * name: 'My Demo App', 18 | * antialiasing: true, 19 | * }); 20 | * 21 | * let w = new ST.Widgets.Button(myApp.root, {width: 100, height: 30}); 22 | * 23 | * let main = function() { 24 | * myApp.update(); 25 | * requestAnimationFrame(main); 26 | * }; 27 | * 28 | * //call main which will initiate the loop 29 | * main(); 30 | */ 31 | export default class App extends EventEmitter { 32 | /** 33 | * @param {Object} options - Optional parameters 34 | * @param {String} [options.name] - The apps name 35 | * @param {ST.Theme} [options.theme] 36 | * - Theme used by the app - by default creates 37 | * a new Theme with default settings. 38 | * @param {Boolean} [options.transparentBkg=false] - True makes 39 | * background transparent 40 | * @param {Boolean} [options.antialiasing=false] - Sets antialiasing 41 | * @param {Boolean} [options.forceFXAA=false] - Lower quality antialiasing but 42 | * performs better 43 | * @param {Number} [options.resolution=1] - Pixi renderer resolution 44 | * @param {Number} [options.width=800] - Width of app renderer 45 | * @param {Number} [options.height=600] - Height of app renderer 46 | * @param {Boolean} [options.autoResize=true] - True makes app resize with 47 | * browser automatically 48 | */ 49 | constructor(options = {}) { 50 | super(); 51 | const defaults = { 52 | name: 'My App', 53 | theme: new Theme(), 54 | transparentBkg: false, 55 | antialiasing: false, 56 | forceFXAA: false, 57 | resolution: window.devicePixelRatio, 58 | width: 800, 59 | height: 600, 60 | autoResize: true, 61 | forceWebgl: false, 62 | }; 63 | 64 | // fill in missing options with defaults 65 | options = Object.assign(defaults, options); 66 | 67 | /** 68 | * The internal name of the app 69 | * @type {String} 70 | * @private 71 | */ 72 | this._name = options.name; 73 | 74 | // html docs title tag 75 | document.title = this._name; 76 | 77 | const renderOptions = { 78 | resolution: options.resolution, 79 | forceFXAA: options.forceFXAA, 80 | antialias: options.antialiasing, 81 | transparent: options.transparent, 82 | autoResize: true, 83 | roundPixels: true, 84 | }; 85 | 86 | /** 87 | * Points to the correct PIXI renderer. Webgl if possible or if 88 | * forced. 89 | * @member {PIXI.WebglRenderer | PIXI.CanvasRenderer} 90 | */ 91 | if(!options.forceWebgl) { 92 | this.renderer = PIXI.autoDetectRenderer(options.width, options.height, 93 | renderOptions); 94 | } else { 95 | renderOptions.width = options.width; 96 | renderOptions.height = options.height; 97 | this.renderer = new PIXI.WebGLRenderer(renderOptions); 98 | } 99 | 100 | 101 | // Add webgl canvas to the doc. Set padding and margins to 0 102 | this.renderer.view.screencanvas = true; 103 | document.body.appendChild(this.renderer.view); 104 | let newStyle = document.createElement('style'); 105 | let style = '* {padding: 0; margin: 0}'; 106 | newStyle.appendChild(document.createTextNode(style)); 107 | document.head.appendChild(newStyle); 108 | 109 | /** 110 | * Internal theme for app 111 | * @member {ST.Theme} 112 | * @private 113 | */ 114 | this._theme = options.theme; 115 | 116 | /** 117 | * All widgets should be children or children of children of the 118 | * root widget. 119 | * 120 | * @member {ST.Widgets.BaseWidget} 121 | */ 122 | this.root = new StageWidget(); 123 | this.root.padding.setAllTo(0); 124 | 125 | this.renderer.backgroundColor = this._theme.background; 126 | 127 | if(options.autoResize) { 128 | this.resizeToWindow(); 129 | } else { 130 | this.renderer.resize(options.width, options.height); 131 | this.root.width = options.width; 132 | this.root.height = options.height; 133 | } 134 | 135 | /** 136 | * Internal autoResize 137 | * @private 138 | */ 139 | this._autoResize = false; 140 | this.autoResize = options.autoResize; 141 | 142 | // listen for window resize. emit signal 143 | window.addEventListener('resize', (e)=>{ 144 | this.emit('resize'); 145 | }); 146 | // listen for orientation changes. emit signal.(mobile only) 147 | window.addEventListener('orientationchange', (e)=>{ 148 | this.emit('resize'); 149 | }); 150 | 151 | /** 152 | * Fires when the window is resized 153 | * @event ST.App#resize 154 | */ 155 | } 156 | 157 | /** 158 | * Resizes the renderer and root widget to match the browser size 159 | */ 160 | resizeToWindow() { 161 | this.renderer.resize(window.innerWidth, window.innerHeight); 162 | this.root.width = window.innerWidth; 163 | this.root.height = window.innerHeight; 164 | } 165 | 166 | /** 167 | * Renders the root widget 168 | */ 169 | update() { 170 | let focus = BaseWidget.getFocusedWidget(); 171 | if(focus) { 172 | focus.onHasFocus(); 173 | } 174 | 175 | this.renderer.render(this.root); 176 | } 177 | 178 | /** 179 | * The name of the app 180 | * @member {String} 181 | */ 182 | get name() { 183 | return this._name; 184 | } 185 | 186 | set name(val) { // eslint-disable-line require-jsdoc 187 | this._name = val; 188 | document.title = val; // html doc's title tag 189 | } 190 | 191 | /** 192 | * The theme for the app 193 | * @member {ST.Theme} 194 | */ 195 | get theme() { 196 | return this._theme; 197 | } 198 | 199 | set theme(val) { // eslint-disable-line require-jsdoc 200 | this._theme = val; 201 | this.renderer.backgroundColor = this._theme.background; 202 | this.root.theme = this._theme; 203 | } 204 | 205 | /** 206 | *If true the app will auto-size itself to fit the browser window 207 | *@member {Boolean} 208 | */ 209 | get autoResize() { 210 | return this._autoResize; 211 | } 212 | 213 | set autoResize(val) { // eslint-disable-line require-jsdoc 214 | this._autoResize = val; 215 | const listeners = this.listeners('resize'); 216 | 217 | if(val) { 218 | if(listeners.indexOf(this.resizeToWindow) === -1) { 219 | // listener doesnt exist so add it 220 | this.on('resize', this.resizeToWindow, this); 221 | } 222 | } else { 223 | if(listeners.indexOf(this.resizeToWindow) !== -1) { 224 | // listener exist so remove it 225 | this.removeListener('resize', this.resizeToWindow); 226 | } 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/GraphicsGen.js: -------------------------------------------------------------------------------- 1 | import * as PIXI from 'pixi.js'; 2 | // import {Graphics} from 'pixi.js/lib/core'; 3 | /** 4 | * Methods that generate Pixi.Graphics 5 | * @memberof ST 6 | */ 7 | export default class GraphicsGen { 8 | 9 | /** 10 | * Generate a rectangle as a graphic 11 | * @param {Number} width - width of rectangle 12 | * @param {Number} height - height of rectangle 13 | * @param {Number} color - color of rectangle 14 | * @static 15 | * @return {PIXI.Graphics} 16 | */ 17 | static rectangleGraphic(width, height, color) { 18 | let graphic = new PIXI.Graphics(); 19 | graphic.beginFill(color); 20 | graphic.drawRect(0, 0, width, height); 21 | graphic.endFill(); 22 | return graphic; 23 | } 24 | 25 | /** 26 | * Generate a rectangle as a texture 27 | * @param {Number} width - width of rectangle 28 | * @param {Number} height - height of rectangle 29 | * @param {Number} color - color of rectangle 30 | * @static 31 | * @return {PIXI.Texture} 32 | */ 33 | static rectangleTexture(width, height, color) { 34 | let graphic = GraphicsGen.rectangleGraphic(width, height, color); 35 | return graphic.generateCanvasTexture(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Padding.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'eventemitter3'; 2 | 3 | /** 4 | * Padding componenet holds padding data for 4 sides 5 | * @memberof ST 6 | * @extends external:EventEmitter 7 | */ 8 | export default class Padding extends EventEmitter { 9 | /** 10 | * @param {Number} [top=0] 11 | * @param {Number} [left=0] 12 | * @param {Number} [bottom=0] 13 | * @param {Number} [right=0] 14 | */ 15 | constructor(top = 0, left = 0, bottom = 0, right = 0) { 16 | super(); 17 | 18 | /** 19 | * Internal top padding 20 | * @member {Number} 21 | * @private 22 | */ 23 | this._top = top; 24 | 25 | /** 26 | * Internal left padding 27 | * @member {Number} 28 | * @private 29 | */ 30 | this._left = left; 31 | 32 | /** 33 | * Internal bottom padding 34 | * @member {Number} 35 | * @private 36 | */ 37 | this._bottom = bottom; 38 | 39 | /** 40 | * Internal right padding 41 | * @member {Number} 42 | * @private 43 | */ 44 | this._right = right; 45 | 46 | /** 47 | * Fires when one or more of the paddings have changed 48 | * @event ST.Padding#changed 49 | */ 50 | } 51 | 52 | /** 53 | * Convienent way to set all paddings to different values 54 | * @param {Number} [top=0] 55 | * @param {Number} [left=0] 56 | * @param {Number} [bottom=0] 57 | * @param {Number} [right=0] 58 | * @return {Array} - Contains padding values 59 | */ 60 | set(top = 0, left = 0, bottom = 0, right = 0) { 61 | this._top = top; 62 | this._left = left; 63 | this._bottom = bottom; 64 | this._right = right; 65 | this.emit('changed', this); 66 | return [top, left, bottom, right]; 67 | } 68 | 69 | /** 70 | * Set all padding to the same value 71 | * @param {Number} [val=0] - the amount of padding 72 | * @return {Number} - the padding 73 | */ 74 | setAllTo(val = 0) { 75 | this._top = this._bottom = this._left = this._right = val; 76 | this.emit('changed', this); 77 | return val; 78 | } 79 | 80 | /** 81 | * Top padding 82 | * @member {Number} 83 | */ 84 | get top() { 85 | return this._top; 86 | } 87 | 88 | set top(val) { // eslint-disable-line require-jsdoc 89 | this._top = val; 90 | this.emit('changed', this); 91 | } 92 | 93 | /** 94 | * Left padding 95 | * @member {Number} 96 | */ 97 | get left() { 98 | return this._left; 99 | } 100 | 101 | set left(val) { // eslint-disable-line require-jsdoc 102 | this._left = val; 103 | this.emit('changed', this); 104 | } 105 | 106 | /** 107 | * Bottom padding 108 | * @member {Number} 109 | */ 110 | get bottom() { 111 | return this._bottom; 112 | } 113 | 114 | set bottom(val) { // eslint-disable-line require-jsdoc 115 | this._bottom = val; 116 | this.emit('changed', this); 117 | } 118 | /** 119 | * Right padding 120 | * @member {Number} 121 | */ 122 | get right() { 123 | return this._right; 124 | } 125 | 126 | set right(val) { // eslint-disable-line require-jsdoc 127 | this._right = val; 128 | this.emit('changed', this); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Point.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'eventemitter3'; 2 | 3 | /** 4 | * Hold position data. 5 | * Like PIXI.Point but with certain events 6 | * @memberof ST 7 | * @extends external:EventEmitter 8 | */ 9 | export default class Point extends EventEmitter { 10 | /** 11 | * @param {Number} [x=0] - x coordinate 12 | * @param {Number} [y=0] - y coordinate 13 | */ 14 | constructor(x = 0, y = 0) { 15 | super(); 16 | 17 | /** 18 | * Internal x coordinate 19 | * @member {Number} 20 | * @private 21 | */ 22 | this._x = x; 23 | 24 | /** 25 | * Internal y coordinate 26 | * @member {Number} 27 | * @private 28 | */ 29 | this._y = y; 30 | 31 | /** 32 | * Fires when positional data changes 33 | * @event ST.Point#changed 34 | * @param {ST.Point} 35 | */ 36 | } 37 | 38 | /** 39 | * Convient way to set both coordinates 40 | * @param {Number} [x=0] 41 | * @param {Number} [y=0] 42 | */ 43 | set(x = 0, y = 0) { 44 | this._x = x; 45 | this._y = y; 46 | this.emit('changed', this); 47 | } 48 | 49 | /** 50 | * The X coodinate 51 | * @member {Number} 52 | */ 53 | get x() { 54 | return this._x; 55 | } 56 | 57 | set x(val) { // eslint-disable-line require-jsdoc 58 | this._x = val; 59 | this.emit('changed', this); 60 | } 61 | 62 | /** 63 | * The Y coodinate 64 | * @member {Number} 65 | */ 66 | get y() { 67 | return this._y; 68 | } 69 | 70 | set y(val) { // eslint-disable-line require-jsdoc 71 | this._y = val; 72 | this.emit('changed', this); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Stores settings and default values used by the app and widgets. 3 | * Exports a settings object that adds the settings to itself? 4 | * Access like: ST.setting.slider.button.height 5 | * @memberof ST 6 | * @static 7 | */ 8 | class Settings { 9 | /** 10 | * 11 | */ 12 | constructor() {} 13 | 14 | /** 15 | * Adds settings to the global object; 16 | * @param {String} name Name of the settings 17 | * @param {Object} settings Settings to Adds 18 | */ 19 | add(name, settings) { 20 | if(!name || !settings) { 21 | return; 22 | } 23 | 24 | this[name] = settings; 25 | } 26 | } 27 | 28 | let settings = new Settings(); 29 | export {settings}; 30 | -------------------------------------------------------------------------------- /src/Size.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'eventemitter3'; 2 | 3 | /** 4 | * Holds size information 5 | * @memberof ST 6 | * @extends external:EventEmitter 7 | */ 8 | export default class Size extends EventEmitter { 9 | /** 10 | * @param {Number} [width=0] 11 | * @param {Number} [height=0] 12 | */ 13 | constructor(width = 0, height = 0) { 14 | super(); 15 | 16 | /** 17 | * Internal width 18 | * @member {Number} 19 | * @private 20 | */ 21 | this._width = width; 22 | 23 | /** 24 | * Internal height 25 | * @member {Number} 26 | * @private 27 | */ 28 | this._height = height; 29 | 30 | /** 31 | * Fires when size data changes 32 | * @event ST.Size#changed 33 | */ 34 | } 35 | 36 | /** 37 | * Convienece to set both width and height at once 38 | * @param {Number} [width=0] 39 | * @param {Number} [height=0] 40 | */ 41 | set(width = 0, height = 0) { 42 | this._width = width; 43 | this._height = height; 44 | this.emit('changed', this); 45 | } 46 | 47 | /** 48 | * @member {Number} 49 | */ 50 | get width() { 51 | return this._width; 52 | } 53 | 54 | set width(val) { // eslint-disable-line require-jsdoc 55 | this._width = val; 56 | this.emit('changed', this); 57 | } 58 | 59 | /** 60 | * @member {Number} 61 | */ 62 | get height() { 63 | return this._height; 64 | } 65 | 66 | set height(val) { // eslint-disable-line require-jsdoc 67 | this._height = val; 68 | this.emit('changed', this); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Theme.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'eventemitter3'; 2 | import GraphicsGen from './GraphicsGen'; 3 | import Point from './Point'; 4 | 5 | /** 6 | * Themes represent colors and font properties used by the ui. 7 | * @see The example style sheets for reference 8 | * @memberof ST 9 | * @extends external:EventEmitter 10 | * 11 | * @example 12 | * // set only on color for panel 13 | * let myStyle = { 14 | * widgets:{ 15 | * panel: { 16 | * enabled: 0xffffff, 17 | * }, 18 | * }, 19 | * }; 20 | * 21 | * // Will use default styles except for panel.enabled 22 | * let thm = new ST.Theme(myStyle); 23 | * 24 | * // will change the theme for everything parented to root 25 | * app.theme = thm; 26 | * 27 | * // will change theme only for this widget 28 | * widget.theme = thm; 29 | */ 30 | export default class Theme extends EventEmitter { 31 | /** 32 | * @param {Object} [options] Style sheet object 33 | */ 34 | constructor(options = {}) { 35 | super(); 36 | const opts = Object.assign(Theme.defaults, options); 37 | 38 | /** 39 | * Theme colors 40 | * @member {Object} 41 | */ 42 | this.colors = JSON.parse(JSON.stringify(opts.widgets)); 43 | 44 | /** 45 | * Theme font styles 46 | * @member {Object} 47 | */ 48 | this.fontStyles = JSON.parse(JSON.stringify(opts.text)); 49 | 50 | /** 51 | * Frame rects for theme textures 52 | * @member {Object} 53 | */ 54 | this.frames = JSON.parse(JSON.stringify(this.colors)); 55 | 56 | /** 57 | * The base texture for all the themes textures 58 | * @member {PIXI.BaseTexture} 59 | */ 60 | this.baseTexture = this.makeTexture(); 61 | 62 | /** 63 | * Theme textures(frames for the base texture) 64 | * @member {Object} 65 | */ 66 | this.textures = Object.assign(this.frames, {}); 67 | 68 | // Creates the textures from the frames 69 | this.makeTexturesRecursive(this.textures); 70 | 71 | /** 72 | * Renderer background 73 | * @member {Number} 74 | */ 75 | this.background = opts.background; 76 | } 77 | 78 | /** 79 | * Returns the global graphic used by all widgets for clipping 80 | * @static 81 | * @return {PIXI.Graphics} 82 | */ 83 | static getClipGraphic() { 84 | return Theme.clipGraphic; 85 | } 86 | 87 | /** 88 | * Adds the given styles to the global Theme.defaults. 89 | * @param {String} name Name of the widget 90 | * @param {Object} styles A collection of styles for the widget 91 | * @static 92 | */ 93 | static registerDefaultWidgetStyle(name, styles) { 94 | if(!name || !styles) { 95 | return; 96 | } 97 | Theme.defaults.widgets[name] = styles; 98 | } 99 | 100 | /** 101 | *(For internal use only!) 102 | *Makes sure a graphic is created and parented to a base graphic 103 | *for every color listed in the widgets section of the theme. 104 | *graphics are positioned with 1 pixel of spacing. 105 | *@param {Object} frames containes widget section of theme object 106 | *@param {ST.Point} pos position object to be 107 | *continuous throughout recursion 108 | *@param {PIXI.Graphics} graphic base graphic 109 | *that theme graphics are added to 110 | *@private 111 | */ 112 | makeGraphicsRecursive(frames, pos, graphic) { 113 | let keys = Object.keys(frames); 114 | 115 | for (let i = 0; i < keys.length; i++) { 116 | if(frames[keys[i]] instanceof PIXI.Rectangle) continue; 117 | if(typeof frames[keys[i]] === 'number') { 118 | let g = GraphicsGen 119 | .rectangleGraphic(4, 4, frames[keys[i]]); 120 | graphic.addChild(g); 121 | g.position.set(pos.x, pos.y); 122 | frames[keys[i]] = new PIXI.Rectangle(pos.x+1, pos.y+1, 1, 1); 123 | pos.x += 6; 124 | } else { 125 | this.makeGraphicsRecursive(frames[keys[i]], pos, graphic); 126 | } 127 | } 128 | } 129 | 130 | /** 131 | * Makes the base texture used to color the widgets 132 | * @private 133 | * @return {PIXI.Texture} 134 | */ 135 | makeTexture() { 136 | let baseGraphic = GraphicsGen.rectangleGraphic(1024, 8, 0x000000); 137 | let pos = new Point(); 138 | 139 | /* make this.frames = the widgets portion of the theme Object. 140 | The colors in this.frames will be replaced by PIXI.Rectangles 141 | that are the texture frames*/ 142 | this.frames = JSON.parse(JSON.stringify(this.colors)); 143 | 144 | this.makeGraphicsRecursive(this.frames, pos, baseGraphic); 145 | 146 | return baseGraphic.generateCanvasTexture().baseTexture; 147 | } 148 | 149 | /** 150 | * Makes all the textures needed by the theme. BaseTextures 151 | * contain the actual image. Textures serve as frames for the 152 | * sprites. 153 | * @param {PIXI.BaseTexture} tex 154 | * @private 155 | */ 156 | makeTexturesRecursive(tex = this.textures) { 157 | let keys = Object.keys(tex); 158 | 159 | for(let i = 0; i < keys.length; i++) { 160 | if(tex[keys[i]] instanceof PIXI.Texture) continue; 161 | if(tex[keys[i]] instanceof PIXI.Rectangle) { 162 | tex[keys[i]] = 163 | new PIXI.Texture(this.baseTexture, tex[keys[i]]); 164 | } else { 165 | this.makeTexturesRecursive(tex[keys[i]]); 166 | } 167 | } 168 | } 169 | } 170 | 171 | // Global Theme defaults 172 | Theme.defaults = { 173 | background: 0x222222, 174 | widgets: { 175 | 176 | }, 177 | text: 178 | { 179 | enabled: { 180 | fontFamily: 'Arial', 181 | fontSize: 12, 182 | fill: 0x959595, 183 | }, 184 | disabled: { 185 | fontFamily: 'Arial', 186 | fontSize: 12, 187 | fill: 0x555555, 188 | }, 189 | hover: { 190 | fontFamily: 'Arial', 191 | fontSize: 12, 192 | fill: 0x85ad85, 193 | }, 194 | click: { 195 | fontFamily: 'Arial', 196 | fontSize: 12, 197 | fill: 0x99c799, 198 | }, 199 | }, 200 | }; 201 | 202 | // Global clip graphic 203 | Theme.clipGraphic = GraphicsGen.rectangleGraphic(1, 1, 0x000000); 204 | -------------------------------------------------------------------------------- /src/const.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {Number} 3 | * @memberof ST 4 | * @const 5 | * @readonly 6 | */ 7 | export const HORIZONTAL = 0; 8 | 9 | /** 10 | * @type {Number} 11 | * @memberof ST 12 | * @const 13 | * @readonly 14 | */ 15 | export const VERTICAL = 1; 16 | -------------------------------------------------------------------------------- /src/demos/demos.js: -------------------------------------------------------------------------------- 1 | /* 2 | This demonstrates one way to create an app with 3 | SaberTooth by creating the app as a subclass of 4 | ST.App( prototypal inheritance ) and then 5 | instantiating it in index.html. For simple apps 6 | you could just as well use a script tag in index.html 7 | and just create instances of SaberTooth objects 8 | without subclassing. 9 | 10 | One could also install & use SaberTooth via npm. 11 | (npm install sabertooth --save-dev) 12 | */ 13 | 14 | // Cocoon canvas+ doesn't like use strict ??? 15 | // 'use strict'; 16 | 17 | // SaberTooth should be loaded in index.html before this file. 18 | // Themes should be loaded in index.html before this file. 19 | 20 | // Create a subclass of ST.App 21 | var DemoApp = function(theme) { //eslint-disable-line 22 | // call parent constructor 23 | ST.App.call(this, { 24 | name: 'Demos', 25 | theme: theme, // must be loaded before this file 26 | autoResize: true, 27 | forceWebgl: true, 28 | }); 29 | 30 | // hack the sprite renderer to get a draw count since PIXI.js no longer 31 | // supports draw counts. 32 | ST.hackSpriteRendererDrawCounter(this.renderer.plugins.sprite); 33 | this.root.layout = new ST.Layouts.VBoxLayout(this.root); 34 | 35 | this.createDebugPanel(); 36 | this.createSliderPanel(); 37 | }; 38 | 39 | // Inherit from ST.App 40 | DemoApp.prototype = Object.create(ST.App.prototype); 41 | // Set constructor back to DemoApp 42 | DemoApp.prototype.constructor = DemoApp; 43 | 44 | // METHODS =========================================== 45 | 46 | // Begins the main application loop 47 | DemoApp.prototype.begin = function() { 48 | var _this = this; //eslint-disable-line 49 | 50 | this.main = function() { 51 | // reset the draw count for the next frame 52 | _this.renderer.plugins.sprite.draws = 0; 53 | 54 | _this.update(); // updates the app 55 | 56 | _this.dpDrawCountLabel.beginBypassUpdate(); // prevent a layout update 57 | _this.dpDrawCountLabel.text = _this.renderer.plugins.sprite.draws; 58 | _this.dpDrawCountLabel.endBypassUpdate(); 59 | 60 | requestAnimationFrame(_this.main); // rinse, repeat 61 | }; 62 | 63 | this.main(); 64 | }; 65 | 66 | // Creates a simple debug panel with draw counter. 67 | // TODO: add fps 68 | DemoApp.prototype.createDebugPanel = function() { 69 | // the panel itself with a vertical box layout 70 | this.debugPanel = new ST.Widgets.Panel(this.root, { 71 | width: 300, 72 | height: 150, 73 | }); 74 | this.debugPanel.layout = new ST.Layouts.VBoxLayout(this.debugPanel); 75 | 76 | // creates a label at the top with the apps name 77 | this.dpTitle = new ST.Widgets.Label(this.debugPanel, {text: this.name}); 78 | 79 | // creates a container with hbox layout below the title 80 | this.dpDrawCountSet 81 | = new ST.Widgets.Container(this.debugPanel, {height: 30}); 82 | this.dpDrawCountSet.layout = new ST.Layouts.HBoxLayout(this.dpDrawCountSet); 83 | this.dpDrawCountSet.hPolicy 84 | = new ST.SizePolicies.ExpandingPolicy(this.dpDrawCountSet); 85 | 86 | // sets 'Draw Count: ' on the left side of this.dpDrawCountSet 87 | this.dpDrawCountTitle 88 | = new ST.Widgets.Label(this.dpDrawCountSet, {text: 'Draw Count: '}); 89 | 90 | // set the draw count label to the right of 'Draw Count: ' 91 | this.dpDrawCountLabel 92 | = new ST.Widgets.Label(this.dpDrawCountSet, {text: '0'}); 93 | }; 94 | 95 | DemoApp.prototype.createSliderPanel = function() { 96 | this.slPanel = new ST.Widgets.Panel(this.root, { 97 | width: 400, 98 | height: 100, 99 | x: 200, 100 | y: 200, 101 | }); 102 | this.slPanel.layout = new ST.Layouts.VBoxLayout(this.slPanel); 103 | 104 | this.sl1 = new ST.Widgets.Slider(this.slPanel, { 105 | width: 300, 106 | height: 30, 107 | }); 108 | this.sl1.hPolicy 109 | = new ST.SizePolicies.ExpandingPolicy(this.sl1); 110 | 111 | this.b1 = new ST.Widgets.TextButton(this.slPanel, { 112 | text: 'Button 1', 113 | width: 100, 114 | height: 30, 115 | }); 116 | 117 | this.b2 = new ST.Widgets.TextButton(this.slPanel, { 118 | text: 'Another Button', 119 | width: 100, 120 | height: 30, 121 | }); 122 | 123 | this.b3 = new ST.Widgets.TextButton(this.slPanel, { 124 | text: 'Button Three', 125 | width: 100, 126 | height: 100, 127 | }); 128 | }; 129 | -------------------------------------------------------------------------------- /src/demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The EventEmitter namespace 3 | * @external EventEmitter 4 | * @see https://github.com/primus/eventemitter3 5 | */ 6 | 7 | /** 8 | * The PIXI namespace 9 | * @external PIXI 10 | * @see http://pixijs.download/release/docs/index.html 11 | */ 12 | 13 | /** 14 | * PIXI.TransformStatic 15 | * @external TransformStatic 16 | * @see http://pixijs.download/release/docs/PIXI.TransformStatic.html 17 | */ 18 | 19 | /** 20 | * PIXI.Container 21 | * @external Container 22 | * @see http://pixijs.download/release/docs/PIXI.Container.html 23 | */ 24 | 25 | import App from './App'; 26 | import {HORIZONTAL, VERTICAL} from './const'; 27 | import GraphicsGen from './GraphicsGen'; 28 | import Padding from './Padding'; 29 | import Point from './Point'; 30 | import Size from './Size'; 31 | import Theme from './Theme'; 32 | import {hackSpriteRendererDrawCounter} from './drawcount'; 33 | import {settings} from './Settings'; 34 | 35 | import * as Widgets from './widgets'; 36 | 37 | import {Layouts, SizePolicies, Alignment, 38 | LayoutManager, SizeManager} from './layoutSys'; 39 | 40 | export { 41 | HORIZONTAL, 42 | VERTICAL, 43 | Alignment, 44 | Widgets, 45 | Layouts, 46 | SizePolicies, 47 | LayoutManager, 48 | SizeManager, 49 | GraphicsGen, 50 | Padding, 51 | Point, 52 | Size, 53 | Theme, 54 | App, 55 | hackSpriteRendererDrawCounter, 56 | settings, 57 | }; 58 | 59 | global.ST = exports; // eslint-disable-line 60 | -------------------------------------------------------------------------------- /src/layoutSys/Alignment.js: -------------------------------------------------------------------------------- 1 | import Point from '.././Point'; 2 | import EventEmitter from 'eventemitter3'; 3 | 4 | /** 5 | * Holds alignment policies for horizontal 6 | * and vertical alignments - alignments are static callback 7 | * methods that return a relative position offset for the widget to 8 | * its parent. 9 | * @memberof ST 10 | * @extends external:EventEmitter 11 | * 12 | * @example 13 | * let widget = new ST.Widgets.Button(myApp.root, { 14 | * width: 100, 15 | * height: 30, 16 | * }); 17 | * 18 | * widget.layout = new ST.Layouts.VBoxLayout(widget); 19 | * 20 | * widget.layout.alignment.hAlign = ST.Alignment.right; 21 | * widget.layout.alignment.vAlign = ST.Alignment.middle; 22 | */ 23 | export default class Alignment extends EventEmitter { 24 | /** 25 | * @param {Function} [hAlign=Alignment.left] Horizontal alignment 26 | * @param {Function} [vAlign=Alignment.top] Vertical alignment 27 | */ 28 | constructor(hAlign = Alignment.left, vAlign = Alignment.top) { 29 | super(); 30 | /** 31 | * Holds the HORIZONTAL alignment 32 | * @member {Function} 33 | */ 34 | this.hAlign = hAlign; 35 | 36 | /** 37 | * Holds the VERTICAL alignment 38 | * @member {Function} 39 | */ 40 | this.vAlign = vAlign; 41 | } 42 | 43 | /** 44 | * Get a position offset from alignments 45 | * @param {ST.Widgets.BaseWidget} parent The parent widget 46 | * @param {Number} targetWidth The widgets width 47 | * @param {Number} targetHeight The widgets height 48 | * @return {Point} The calculated offset relative to the parent 49 | */ 50 | getOffset(parent, targetWidth, targetHeight) { 51 | let offset = new Point(); 52 | offset.x = this.hAlign(targetWidth, parent); 53 | offset.y = this.vAlign(targetHeight, parent); 54 | return offset; 55 | } 56 | 57 | // ALIGNMENTS --- 58 | 59 | /** 60 | * Left Alignment 61 | * @static 62 | * @param {Number} targetWidth The widgets width 63 | * @param {ST.Widgets.BaseWidget} parent The parent widget 64 | * @return {Number} Horizontal offset 65 | */ 66 | static left(targetWidth, parent) { 67 | return parent.padding.left; 68 | } 69 | 70 | /** 71 | * Center Alignment 72 | * @static 73 | * @param {Number} targetWidth The widgets width 74 | * @param {ST.Widgets.BaseWidget} parent The parent widget 75 | * @return {Number} Horizontal offset 76 | */ 77 | static center(targetWidth, parent) { 78 | return ((parent.width / 2) - (targetWidth / 2));// - pad; 79 | } 80 | 81 | /** 82 | * Right Alignment 83 | * @static 84 | * @param {Number} targetWidth The widgets width 85 | * @param {ST.Widgets.BaseWidget} parent The parent widget 86 | * @return {Number} Horizontal offset 87 | */ 88 | static right(targetWidth, parent) { 89 | return (parent.width - targetWidth); 90 | } 91 | 92 | /** 93 | * Top Alignment 94 | * @static 95 | * @param {Number} targetHeight The widgets width 96 | * @param {ST.Widgets.BaseWidget} parent The parent widget 97 | * @return {Number} Vertical offset 98 | */ 99 | static top(targetHeight, parent) { 100 | return parent.padding.top; 101 | } 102 | 103 | /** 104 | * Middle Alignment 105 | * @static 106 | * @param {Number} targetHeight The widgets width 107 | * @param {ST.Widgets.BaseWidget} parent The parent widget 108 | * @return {Number} Vertical offset 109 | */ 110 | static middle(targetHeight, parent) { 111 | return ((parent.height / 2) - (targetHeight / 2));// - pad; 112 | } 113 | 114 | /** 115 | * Bottom Alignment 116 | * @static 117 | * @param {Number} targetHeight The widgets width 118 | * @param {ST.Widgets.BaseWidget} parent The parent widget 119 | * @return {Number} Vertical offset 120 | */ 121 | static bottom(targetHeight, parent) { 122 | return (parent.height - targetHeight); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/layoutSys/index.js: -------------------------------------------------------------------------------- 1 | import * as Layouts from './layouts'; 2 | import * as SizePolicies from './sizePolicies'; 3 | import Alignment from './Alignment'; 4 | 5 | export{ 6 | Layouts, 7 | SizePolicies, 8 | Alignment, 9 | }; 10 | -------------------------------------------------------------------------------- /src/layoutSys/layouts/BaseLayout.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'eventemitter3'; 2 | import Alignment from '.././Alignment'; 3 | import BaseWidget from '../../widgets/BaseWidget'; 4 | 5 | /** 6 | * The BaseLayout class is the abstract base class for all layouts. 7 | * @memberof ST.Layouts 8 | * @abstract 9 | */ 10 | export default class BaseLayout extends EventEmitter { 11 | /** 12 | * @param {ST.Widgets.BaseWidget} hostWidget 13 | * - Widget this layout belongs to 14 | */ 15 | constructor(hostWidget) { 16 | super(); 17 | // make sure we have a host widget 18 | if(!hostWidget) { 19 | throw new Error('Layouts must be created ' + 20 | 'with a valid host widget parameter'); 21 | } 22 | 23 | /** 24 | * The internal reference to the host widget 25 | * @member {ST.Widgets.BaseWidget} 26 | * @private 27 | */ 28 | this._host = hostWidget; 29 | 30 | /** 31 | * Holds the Alignment object 32 | * @member {ST.Alignment} 33 | */ 34 | this.alignment = new Alignment(); 35 | 36 | /** 37 | * If set to true then the layout will update on host widget 38 | * changes in position and size. 39 | * @member {Boolean} 40 | * @default false 41 | */ 42 | this.updateOnHostChanges = false; 43 | 44 | /** 45 | * Fired when layout has finished updating 46 | * @event ST.Layouts.BaseLayout#updated 47 | */ 48 | } 49 | 50 | /** 51 | *Override in layouts if they need to act upon the addition or 52 | *removal of children from the host widget. 53 | *@virtual 54 | *@private 55 | */ 56 | hostChildrenChanged() {} 57 | 58 | /** 59 | * Overide in layouts - code that runs before the iteration 60 | * of child layouts 61 | * @virtual 62 | * @private 63 | */ 64 | beginIteration() {} 65 | 66 | /** 67 | * Overide in layouts - code that runs after the iteration 68 | * of child layouts 69 | * @virtual 70 | * @private 71 | */ 72 | endIteration() {} 73 | 74 | /** 75 | * Overide in layouts - code that sets the position of 76 | * each child widget. 77 | * @param {ST.Widgets.BaseWidget} child the child widget in question 78 | * @virtual 79 | * @private 80 | */ 81 | setChildPos(child) {} 82 | 83 | /** 84 | * Execute the layout update - iterates over child widgets 85 | * and executes their layouts before positioning them. 86 | * @private 87 | */ 88 | exec() { 89 | this.beginIteration(); 90 | const w = this._host; 91 | const len = w.children.length; 92 | let i = len; 93 | while(i--) { 94 | const child = w.children[len - i]; // iterate in forward order 95 | if(child instanceof BaseWidget) { 96 | child.beginBypassUpdate(); // prevents infinite loop 97 | 98 | this.setChildPos(child); 99 | child.layout.exec(); 100 | 101 | child.endBypassUpdate(); 102 | } 103 | } 104 | 105 | /* layouts execute after size policies therefor 106 | update is complete 107 | */ 108 | w.validate(); // make sure updates stop on next loop 109 | w._updateClipGraphic(); // because things have changed 110 | 111 | w.emit('updated'); 112 | 113 | this.endIteration(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/layoutSys/layouts/BoxLayout.js: -------------------------------------------------------------------------------- 1 | import BaseLayout from './BaseLayout'; 2 | import Point from '../.././Point'; 3 | import {HORIZONTAL} from '../.././const'; 4 | import BaseWidget from '../.././widgets/BaseWidget'; 5 | 6 | /** 7 | * Arrange out widgets in a straight line in one direction 8 | * @memberof ST.Layouts 9 | * @extends ST.Layouts.BaseLayout 10 | * 11 | * @example 12 | * let widget = new ST.Widgets.Button(myApp.root, { 13 | * width: 100, 14 | * height: 30, 15 | * }); 16 | * 17 | * widget.layout = new ST.Layouts.BoxLayout(widget, ST.HORIZONTAL); 18 | */ 19 | export default class BoxLayout extends BaseLayout { 20 | /** 21 | *@param {ST.Widgets.BaseWidget} hostWidget 22 | *The widget this layout belongs to 23 | *@param {number} [orientation=HORIZONTAL] Orientation of layout 24 | *@param {number} [spacing=4] the space between widgets in pixels 25 | */ 26 | constructor(hostWidget, orientation = HORIZONTAL, spacing = 4) { 27 | super(hostWidget); 28 | 29 | /** 30 | * Orientation of the layout 31 | * @member {number} 32 | */ 33 | this.orientation = orientation; 34 | 35 | /** 36 | * The space between widgets in pixels 37 | * @member {number} 38 | */ 39 | this.spacing = spacing; 40 | 41 | /** 42 | * Holds the offset applied to each 43 | * consecutive child widget's position 44 | * @member {Point} 45 | * @private 46 | */ 47 | this.posOffset = new Point(); 48 | 49 | /** 50 | * Total width of child widgets combined 51 | * @member {Number} 52 | * @private 53 | */ 54 | this._totalChildrenWidth = 0; 55 | 56 | /** 57 | * Total height of child widgets combined 58 | * @member {Number} 59 | * @private 60 | */ 61 | this._totalChildrenHeight = 0; 62 | 63 | /** 64 | * Result of the old calculation. This is the value used. 65 | * @member {Number} 66 | * @private 67 | */ 68 | this._totalChildrenWidthOld = 0; 69 | 70 | /** 71 | * Result of the old calculation. This is the value used. 72 | * @member {Number} 73 | * @private 74 | */ 75 | this._totalChildrenHeightOld = 0; 76 | 77 | /** 78 | * Wether child sizes have been calculted 79 | * @member {Boolean} 80 | * @private 81 | */ 82 | this._totalChildrenSizeInitialized = false; 83 | 84 | /** 85 | * Fires when layout is finished 86 | * @event ST.Layouts.BoxLayout#finished 87 | */ 88 | } 89 | 90 | /** 91 | * Calculates the total combined size of children 92 | * @private 93 | */ 94 | initTotalChildrenSize() { 95 | const children = this._host.children; 96 | let i = children.length; 97 | while(i--) { 98 | const child = children[i]; 99 | if(child instanceof BaseWidget) { 100 | this._totalChildrenWidth += child.width + this.spacing; 101 | this._totalChildrenHeight += child.height + this.spacing; 102 | } 103 | } 104 | 105 | this._totalChildrenSizeInitialized = true; 106 | } 107 | 108 | /** 109 | * Prepare for iteration 110 | * @override 111 | * @private 112 | */ 113 | beginIteration() { 114 | if(!this._totalChildrenSizeInitialized) { 115 | this.initTotalChildrenSize(); 116 | }; 117 | // cache the old sizes 118 | this._totalChildrenWidthOld = this._totalChildrenWidth; 119 | this._totalChildrenHeightOld = this._totalChildrenHeight; 120 | 121 | // reset totals 122 | this._totalChildrenWidth = 0; 123 | this._totalChildrenHeight = 0; 124 | 125 | this.posOffset.set(0, 0); 126 | } 127 | 128 | /** 129 | * Sets childs position and calculate next offset 130 | * @param {ST.Widgets.BaseWidget} child - the child in question 131 | * @override 132 | */ 133 | setChildPos(child) { 134 | let alignmentOffset; 135 | 136 | // update should already be bypassed on host so this 137 | // shouldn't cause an update / infinite loop 138 | child.position.set(this.posOffset.x, this.posOffset.y); 139 | 140 | // add spacing to the totals for the next one 141 | this._totalChildrenWidth += child.width + this.spacing; 142 | this._totalChildrenHeight += child.height + this.spacing; 143 | 144 | if(this.orientation === HORIZONTAL) { 145 | alignmentOffset = this.alignment.getOffset(this._host, 146 | this._totalChildrenWidthOld, child.height); 147 | 148 | // set posOffset for the next one 149 | this.posOffset.x += child.width + this.spacing; 150 | } else { // VERTICAL 151 | alignmentOffset = this.alignment.getOffset(this._host, 152 | child.width, this._totalChildrenHeightOld); 153 | 154 | // set posOffset for the next one 155 | this.posOffset.y += child.height + this.spacing; 156 | } 157 | 158 | child.x += alignmentOffset.x; 159 | child.y += alignmentOffset.y; 160 | 161 | // set the real position from the virtual 162 | child.applyPosition(); 163 | } 164 | 165 | /** 166 | * Emits finished event 167 | * @override 168 | */ 169 | endIteration() { 170 | this.emit('finished'); 171 | } 172 | 173 | /** 174 | * When host widgets children change this will re-calculate the 175 | * total size of the children 176 | * @override 177 | */ 178 | hostChildrenChanged() { 179 | this._totalChildrenSizeInitialized = false; 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /src/layoutSys/layouts/FixedLayout.js: -------------------------------------------------------------------------------- 1 | import BaseLayout from './BaseLayout'; 2 | 3 | /** 4 | * Arranges widgets according to their user defined position. 5 | * @memberof ST.Layouts 6 | * @extends BaseLayout 7 | * 8 | * @example 9 | * let widget = new ST.Widgets.Button(myApp.root, { 10 | * width: 100, 11 | * height: 30, 12 | * }); 13 | * 14 | * widget.layout = new ST.Layouts.FixedLayout(widget); 15 | */ 16 | export default class FixedLayout extends BaseLayout { 17 | /** 18 | * @param {ST.Widgets.BaseWidget} hostWidget 19 | * The widget this layout belongs to 20 | */ 21 | constructor(hostWidget) { 22 | super(hostWidget); 23 | 24 | this.updateOnHostChanges = true; 25 | } 26 | 27 | 28 | /** 29 | * Sets the widgets position with offset 30 | * @param {ST.Widgets.BaseWidget} child - the child in question 31 | * @override 32 | */ 33 | setChildPos(child) { 34 | const alignOffset = this.alignment.getOffset(this._host, 35 | child.width, child.height); 36 | 37 | child.applyPosition(alignOffset.x, alignOffset.y); 38 | } 39 | 40 | /** @inheritdoc */ 41 | endIteration() { 42 | this.emit('finished'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/layoutSys/layouts/HBoxLayout.js: -------------------------------------------------------------------------------- 1 | import BoxLayout from './BoxLayout'; 2 | import {HORIZONTAL} from '../.././const'; 3 | 4 | /** 5 | * Arranges widgets horizontally stacked beside each other. 6 | * (Same as box layout with orientation = ST.HORIZONTAL) 7 | * @memberof ST.Layouts 8 | * @extends ST.Layouts.BoxLayout 9 | * 10 | * @example 11 | * let widget = new ST.Widgets.Button(myApp.root, { 12 | * width: 100, 13 | * height: 30, 14 | * }); 15 | * 16 | * widget.layout = new ST.Layouts.HBoxLayout(widget); 17 | */ 18 | export default class HBoxLayout extends BoxLayout { 19 | /** 20 | * @param {ST.Widgets.BaseWidget} hostWidget 21 | * The widget this layout belongs to 22 | * @param {Number} [spacing=4] The space in pixels between widgets 23 | */ 24 | constructor(hostWidget, spacing = 4) { 25 | super(hostWidget, HORIZONTAL, spacing); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/layoutSys/layouts/VBoxLayout.js: -------------------------------------------------------------------------------- 1 | import BoxLayout from './BoxLayout'; 2 | import {VERTICAL} from '../.././const'; 3 | 4 | /** 5 | * Arranges widgets vertically stacked on top of each other. 6 | * (Same as box layout with orientation = ST.VERTICAL) 7 | * @memberof ST.Layouts 8 | * @extends ST.Layouts.BoxLayout 9 | * 10 | * @example 11 | * let widget = new ST.Widgets.Button(myApp.root, { 12 | * width: 100, 13 | * height: 30, 14 | * }); 15 | * 16 | * widget.layout = new ST.Layouts.VBoxLayout(widget); 17 | */ 18 | export default class VBoxLayout extends BoxLayout { 19 | /** 20 | * @param {ST.Widgets.BaseWidget} hostWidget 21 | * The widget this layout belongs to 22 | * @param {Number} [spacing=4] The space in pixels between widgets 23 | */ 24 | constructor(hostWidget, spacing = 4) { 25 | super(hostWidget, VERTICAL, spacing); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/layoutSys/layouts/index.js: -------------------------------------------------------------------------------- 1 | import BaseLayout from './BaseLayout'; 2 | import FixedLayout from './FixedLayout'; 3 | import BoxLayout from './BoxLayout'; 4 | import HBoxLayout from './HBoxLayout'; 5 | import VBoxLayout from './VBoxLayout'; 6 | 7 | export { 8 | BaseLayout, 9 | FixedLayout, 10 | BoxLayout, 11 | HBoxLayout, 12 | VBoxLayout, 13 | }; 14 | -------------------------------------------------------------------------------- /src/layoutSys/sizePolicies/BasePolicy.js: -------------------------------------------------------------------------------- 1 | import BaseWidget from '../../widgets/BaseWidget'; 2 | import EventEmitter from 'eventemitter3'; 3 | import {HORIZONTAL} from '../.././const'; 4 | 5 | /** 6 | * Abstract base class for all size policies 7 | * @memberof ST.SizePolicies 8 | * @abstract 9 | */ 10 | export default class BasePolicy extends EventEmitter { 11 | /** 12 | * @param {ST.Widgets.BaseWidget} hostWidget 13 | * The widget this policy belongs to 14 | * @param {number} [orientation=HORIZONTAL] The orientation of the policy 15 | */ 16 | constructor(hostWidget, orientation = HORIZONTAL) { 17 | super(); 18 | // make sure we have a host 19 | if(!hostWidget) { 20 | throw new Error('SizePolicy must be created ' + 21 | 'with a valid host widget parameter'); 22 | } 23 | 24 | /** 25 | * The orientation of the policy 26 | * @member {number} 27 | */ 28 | this.orientation = orientation; 29 | 30 | /** 31 | * The widget this policy belongs to 32 | * @member {ST.Widgets.BaseWidget} 33 | * @private 34 | */ 35 | this._host = hostWidget; 36 | 37 | /** 38 | * Combined size of child widgets who have finished 39 | * executing their size policy 40 | * @member {Number} 41 | * @private 42 | */ 43 | this.totalChildrenFinishedSize = 0; 44 | 45 | /** 46 | * Count of child widgets who have finished 47 | * executing their size policy 48 | * @member {Number} 49 | * @private 50 | */ 51 | this.totalChildrenFinished = 0; 52 | 53 | /** 54 | * If set to true the policy will update on changes to 55 | * the parent widgets size and position 56 | * @member {Boolean} 57 | * @default false 58 | * @private 59 | */ 60 | this.updateOnHostChanges = false; 61 | 62 | /** 63 | * Total space used by children widgets 64 | * @member {Number} 65 | * @private 66 | */ 67 | this.usedSpace = 0; 68 | 69 | /** 70 | * Keeps track of the spacing in the current layout 71 | * @member {Number} 72 | * @private 73 | */ 74 | this.layoutSpacing = 0; 75 | 76 | /** 77 | * Fires when iteration of children begins 78 | * @event ST.SizePolicies.BasePolicy#preIteration 79 | */ 80 | 81 | /** 82 | * Fires when iteration of children ends 83 | * @event ST.SizePolicies.BasePolicy#postIteration 84 | */ 85 | } 86 | 87 | /** 88 | * Handles sizing the widget if HORIZONTAL orientation 89 | * @virtual 90 | */ 91 | sizeWidgetHorizontal() {} 92 | 93 | /** 94 | * Handles sizing the widget if VERTICAL orientation 95 | * @virtual 96 | */ 97 | sizeWidgetVertical() {} 98 | 99 | /** 100 | * As each child widgets policy finishes we count them and 101 | * add their sizes to the collective 102 | * @param {number} size - Size of the child passed in 103 | * @private 104 | */ 105 | childPolicyFinished(size = 0) { 106 | this.totalChildrenFinishedSize += size + this.layoutSpacing; 107 | this.usedSpace += size + this.layoutSpacing; 108 | this.totalChildrenFinished -= 1; 109 | } 110 | 111 | /** 112 | * Execute the policy update - iterate over and execute 113 | * children widgets policies. 114 | * @private 115 | */ 116 | exec() { 117 | this.emit('preIteration'); 118 | let w = this._host; 119 | let len = w.children.length; 120 | let i = len; 121 | // because some layouts don't have spacing (FixedLayout) 122 | if('spacing' in this._host.layout) { 123 | this.layoutSpacing = this._host.layout.spacing; 124 | } 125 | this.usedSpace = 0 - this.layoutSpacing; 126 | // counts down instead of up 127 | this.totalChildrenFinished = len - 1; 128 | this.totalChildrenFinishedSize = 0 - this.layoutSpacing; 129 | 130 | while(i--) { 131 | const child = w.children[i]; 132 | if(this.orientation === HORIZONTAL) { 133 | if(child instanceof BaseWidget) { 134 | // prevents infinite loop caused by updating each loop 135 | child.beginBypassUpdate(); 136 | child.hPolicy.once('finished', 137 | this.childPolicyFinished, this); 138 | child.hPolicy.exec(); 139 | child.endBypassUpdate(); 140 | } 141 | } else { 142 | if(child instanceof BaseWidget) { 143 | // prevents infinite loop caused by updating each loop 144 | child.beginBypassUpdate(); 145 | child.vPolicy.once('finished', 146 | this.childPolicyFinished, this); 147 | child.vPolicy.exec(); 148 | child.endBypassUpdate(); 149 | } 150 | } 151 | } 152 | 153 | 154 | if(this.orientation === HORIZONTAL) { 155 | this.sizeWidgetHorizontal(); 156 | } else { // VERTICAL 157 | this.sizeWidgetVertical(); 158 | } 159 | 160 | this.emit('postIteration'); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/layoutSys/sizePolicies/ExpandingPolicy.js: -------------------------------------------------------------------------------- 1 | import BasePolicy from './BasePolicy'; 2 | import {HORIZONTAL} from '../.././const'; 3 | 4 | /** 5 | * Expanding Policy causes children to expand in the given direction 6 | * filling the parent. 7 | * This Policy doesn't respect siblings - best for widgets with 8 | * a sigle child, or for example the hPolicy of a widget in a vertical layout. 9 | * @memberof ST.SizePolicies 10 | * @extends ST.SizePolicies.BasePolicy 11 | * 12 | * @example 13 | * let widget = new ST.Widgets.Button(myApp.root, { 14 | * width: 100, 15 | * height: 30, 16 | * }); 17 | * 18 | * // will fill the entire parent regardless of sibling widgets 19 | * widget.hPolicy = new ST.SizePolicies.ExpandingPolicy(widget, ST.HORIZONTAL); 20 | * widget.vPolicy = new ST.SizePolicies.ExpandingPolicy(widget, ST.VERTICAL); 21 | */ 22 | export default class ExpandingPolicy extends BasePolicy { 23 | /** 24 | * @param {ST.Widgets.BaseWidget} hostWidget 25 | * The widget this policy belongs to 26 | * @param {number} [orientation=HORIZONTAL] The orientation of the policy 27 | */ 28 | constructor(hostWidget, orientation = HORIZONTAL) { 29 | super(hostWidget, orientation); 30 | 31 | this.updateOnHostChanges = false; 32 | 33 | /** 34 | * Fires after size is set 35 | * @event ST.SizePolicies.ExpandingPolicy#finished 36 | * @param {Number} size the size of the widget 37 | */ 38 | } 39 | 40 | /** 41 | * Make connection so that the widget 42 | * only gets sized after the parent is sized. 43 | * @override 44 | */ 45 | sizeWidgetHorizontal() { 46 | // connect slot each iteration because they are use once 47 | // this prevents problems when host widget changes parent 48 | if(this._host.parent) { 49 | this._host.parent.hPolicy.once('finished', 50 | this.parentReadyH, this); 51 | } 52 | } 53 | 54 | /** 55 | * Make connection so that the widget 56 | * only gets sized after the parent is sized. 57 | * @override 58 | */ 59 | sizeWidgetVertical() { 60 | if(this._host.parent) { 61 | this._host.parent.vPolicy.once('finished', 62 | this.parentReadyV, this); 63 | } 64 | } 65 | 66 | /** 67 | * Size widget after parent widget is finished. 68 | * When parent is finished we can size the widget and 69 | * know that parents size wont change afterward. 70 | */ 71 | parentReadyH() { 72 | const w = this._host; 73 | const p = this._host.parent; 74 | const pad = p.padding.left + p.padding.right; 75 | 76 | w.width = p.width - pad; 77 | w.validateWidth(); // make it obey widet.min and max 78 | this.emit('finished', w.width); 79 | } 80 | 81 | /** 82 | * Size widget after parent widget is finished. 83 | * When parent is finished we can size the widget and 84 | * know that parents size wont change afterward. 85 | */ 86 | parentReadyV() { 87 | const w = this._host; 88 | const p = this._host.parent; 89 | const pad = p.padding.top + p.padding.bottom; 90 | 91 | w.height = p.height - pad; 92 | w.validateHeight(); // make it obey widet.min and max 93 | this.emit('finished', w.height); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/layoutSys/sizePolicies/FixedPolicy.js: -------------------------------------------------------------------------------- 1 | import {HORIZONTAL} from '../.././const'; 2 | import BasePolicy from './BasePolicy'; 3 | 4 | /** 5 | * Sets the user defined size 6 | * @memberof ST.SizePolicies 7 | * @extends ST.SizePolicies.BasePolicy 8 | * 9 | * @example 10 | * let widget = new ST.Widgets.Button(myApp.root, { 11 | * width: 100, 12 | * height: 30, 13 | * }); 14 | * 15 | * widget.hPolicy = new ST.SizePolicies.FixedPolicy(widget, ST.HORIZONTAL); 16 | * widget.vPolicy = new ST.SizePolicies.FixedPolicy(widget, ST.VERTICAL); 17 | */ 18 | export default class FixedPolicy extends BasePolicy { 19 | /** 20 | * @param {ST.Widgets.BaseWidget} hostWidget 21 | * The widget this policy belongs to 22 | * @param {number} [orientation=HORIZONTAL] The orientation of the policy 23 | */ 24 | constructor(hostWidget, orientation = HORIZONTAL) { 25 | super(hostWidget, orientation); 26 | 27 | this.updateOnHostChanges = true; 28 | 29 | /** 30 | * Fires after size is set 31 | * @event ST.SizePolicies.FixedPolicy#finished 32 | * @param {Number} size the size of the widget 33 | */ 34 | } 35 | 36 | /** 37 | * Validates size 38 | * @override 39 | */ 40 | sizeWidgetHorizontal() { 41 | this._host.validateWidth(); // make obey widget.min and max 42 | this.emit('finished', this._host.width); 43 | } 44 | 45 | /** 46 | * Validates size 47 | * @override 48 | */ 49 | sizeWidgetVertical() { 50 | this._host.validateHeight(); // make obey widget.min and max 51 | this.emit('finished', this._host.height); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/layoutSys/sizePolicies/SharedExpandingPolicy.js: -------------------------------------------------------------------------------- 1 | import ExpandingPolicy from './ExpandingPolicy'; 2 | import {HORIZONTAL, VERTICAL} from '../.././const'; 3 | 4 | /** 5 | * Expands widgets into their parent while sharing space with their siblings. 6 | * @memberof ST.SizePolicies 7 | * @extends ST.SizePolicies.ExpandingPolicy 8 | * 9 | * @example 10 | * let widget = new ST.Widgets.Button(myApp.root, { 11 | * width: 100, 12 | * height: 30, 13 | * }); 14 | * 15 | * widget.hPolicy = new ST.SizePolicies 16 | * .SharedExpandingPolicy(widget, ST.HORIZONTAL); 17 | * 18 | * widget.vPolicy = new ST.SizePolicies 19 | * .SharedExpandingPolicy(widget, ST.VERTICAL); 20 | */ 21 | export default class SharedExpandingPolicy extends ExpandingPolicy { 22 | /** 23 | * @param {ST.Widgets.BaseWidget} hostWidget 24 | * The widget this policy belongs to 25 | * @param {number} [orientation=HORIZONTAL] The orientation of the policy 26 | */ 27 | constructor(hostWidget, orientation = HORIZONTAL) { 28 | super(hostWidget, orientation); 29 | 30 | /** 31 | * Fires after size has been set 32 | * @event ST.SizePolicies.SharedExpandingPolicy#finished 33 | * @param {Number} size the size of the widget 34 | */ 35 | } 36 | 37 | /** 38 | * Size widget after parent widget is finished. 39 | * Note: When parent is finished we can size the widget and 40 | * know that parents size wont change afterward. 41 | * @override 42 | */ 43 | parentReadyH() { 44 | const w = this._host; 45 | const parent = w.parent; 46 | const pad = parent.padding.left + parent.padding.right; 47 | const p = parent.hPolicy; 48 | const remaining = p.totalChildrenFinished - 1; 49 | const availableSpace 50 | = (parent.width - p.totalChildrenFinishedSize) - pad; 51 | let size; 52 | 53 | // because some layouts dont need an orientation 54 | if('orientation' in parent.layout && 55 | parent.layout.orientation === VERTICAL) { 56 | size = parent.width - pad; 57 | w.width = size; 58 | w.validateWidth(); // obey widget min and max size 59 | } else { 60 | size = availableSpace / remaining; 61 | w.width = size; 62 | // obey widget min and max size 63 | let vWidth = w.validateWidth(); 64 | if(vWidth === w.width) { 65 | p.once('postIteration', this.consumeUnusedSpaceH, this); 66 | } 67 | } 68 | 69 | this.emit('finished', w.width); 70 | } 71 | 72 | /** 73 | * Size widget after parent widget is finished. 74 | * Note: When parent is finished we can size the widget and 75 | * know that parents size wont change afterward. 76 | * @override 77 | */ 78 | parentReadyV() { 79 | const w = this._host; 80 | const parent = w.parent; 81 | const pad = parent.padding.top + parent.padding.bottom; 82 | const p = parent.vPolicy; 83 | const remaining = p.totalChildrenFinished - 1; 84 | const availableSpace = 85 | (parent.height - p.totalChildrenFinishedSize ) - pad; 86 | let size; 87 | 88 | // because some layouts dont need an orientation 89 | if('orientation' in parent.layout && 90 | parent.layout.orientation === HORIZONTAL) { 91 | size = parent.height - pad; 92 | w.height = size; 93 | w.validateHeight(); // obey widget min and max size 94 | } else { 95 | size = availableSpace / remaining; 96 | w.height = size; 97 | // obey widget min and max size 98 | let vHeight = w.validateHeight(); 99 | if(vHeight === w.height) { 100 | p.once('postIteration', this.consumeUnusedSpaceV, this); 101 | } 102 | } 103 | this.emit('finished', w.height); 104 | } 105 | 106 | /** 107 | * Make sure widget gets its share of unused space after all of siblings 108 | * have been sized 109 | * @callback 110 | */ 111 | consumeUnusedSpaceH() { 112 | const w = this._host; 113 | const parent = w.parent; 114 | const unusedSpace = parent.width - parent.hPolicy.usedSpace; 115 | 116 | // every widget listens to the postIteration event so use that 117 | // to get a count on remaining widgets 118 | const remaining = parent.hPolicy.listeners('postIteration') 119 | .filter((value)=>{ 120 | return value.name === this.consumeUnusedSpaceH.name; 121 | }); 122 | const pad = parent.padding.left + parent.padding.right; 123 | const relSize = (unusedSpace / (remaining.length+1)) - pad; 124 | 125 | // set the newly adjusted size 126 | w.width += relSize; 127 | w.validateWidth(); // obey widget min and max size 128 | 129 | // add used space back 130 | parent.hPolicy.usedSpace += relSize; 131 | } 132 | 133 | /** 134 | * Make sure widget gets its share of unused space after all of siblings 135 | * have been sized 136 | * @callback 137 | */ 138 | consumeUnusedSpaceV() { 139 | const w = this._host; 140 | const parent = w.parent; 141 | const unusedSpace = parent.height - parent.vPolicy.usedSpace; 142 | 143 | // every widget listens to the postIteration event so use that 144 | // to get a count on remaining widgets 145 | const remaining = parent.vPolicy.listeners('postIteration') 146 | .filter((value)=>{ 147 | return value.name === this.consumeUnusedSpaceV.name; 148 | }); 149 | const pad = parent.padding.top + parent.padding.bottom; 150 | const relSize = (unusedSpace / (remaining.length+1)) - pad; 151 | 152 | // set the newly adjusted size 153 | w.height += relSize; 154 | w.validateHeight();// obey widget min and max size 155 | 156 | // add used space back 157 | parent.vPolicy.usedSpace += relSize; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/layoutSys/sizePolicies/index.js: -------------------------------------------------------------------------------- 1 | import BasePolicy from './BasePolicy'; 2 | import FixedPolicy from './FixedPolicy'; 3 | import ExpandingPolicy from './ExpandingPolicy'; 4 | import SharedExpandingPolicy from './SharedExpandingPolicy'; 5 | 6 | export { 7 | BasePolicy, 8 | FixedPolicy, 9 | ExpandingPolicy, 10 | SharedExpandingPolicy, 11 | }; 12 | -------------------------------------------------------------------------------- /src/widgets/Button.js: -------------------------------------------------------------------------------- 1 | import Panel from './Panel'; 2 | import Alignment from '../layoutSys/Alignment'; 3 | import Theme from '.././Theme'; 4 | 5 | /* Add widget style to ST.Theme.defaults. This way the widget 6 | will always have a style even if the given theme doesn't have one 7 | specifically for it. All widgets that have themable elements 8 | should call this method before describing their class.*/ 9 | Theme.registerDefaultWidgetStyle('button', { 10 | enabled: 0x363636, 11 | disabled: 0x2e2e2e, 12 | hover: 0x264e26, 13 | click: 0x3a723a, 14 | }); 15 | 16 | /** 17 | * A simple button widget that is the base class for other button types. 18 | * @extends ST.Widgets.Panel 19 | * @memberof ST.Widgets 20 | * 21 | * @example 22 | * let widget = new ST.Widgets.Button(myApp.root, { 23 | * width: 100, 24 | * height: 30, 25 | * }); 26 | */ 27 | export default class Button extends Panel { 28 | /** 29 | * @param {ST.Widgets.BaseWidget} parent The widgets parent 30 | * @param {Object} [options = Object] See {@link ST.Widgets.BaseWidget} 31 | */ 32 | constructor(parent, options = {}) { 33 | super(parent, options); 34 | 35 | this.interactive = true; 36 | 37 | // make alignment default to center 38 | this.layout.alignment.vAlign = Alignment.middle; 39 | this.layout.alignment.hAlign = Alignment.center; 40 | } 41 | 42 | /** @inheritdoc */ 43 | paintDefault() { 44 | if(this._bkgObj) { 45 | this._bkgObj.texture = this.theme.textures.button.enabled; 46 | } 47 | } 48 | 49 | /** @inheritdoc */ 50 | paintDown() { 51 | if(this._bkgObj) { 52 | this._bkgObj.texture = this.theme.textures.button.click; 53 | } 54 | } 55 | 56 | /** @inheritdoc */ 57 | paintHover() { 58 | if(this._bkgObj) { 59 | this._bkgObj.texture = this.theme.textures.button.hover; 60 | } 61 | } 62 | 63 | /** @inheritdoc */ 64 | paintDisabled() { 65 | if(this._bkgObj) { 66 | this._bkgObj.texture = this.theme.textures.button.disabled; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/widgets/Container.js: -------------------------------------------------------------------------------- 1 | import Panel from './Panel'; 2 | 3 | /** 4 | * Serves as a container for other widgets. Can be used to layout 5 | * widgets without having a background. 6 | * @extends ST.Widgets.Panel 7 | * @memberof ST.Widgets 8 | * 9 | * @example 10 | * let widget = new ST.Widgets.Container(myApp.root, { 11 | * width: 100, 12 | * height: 30, 13 | * }); 14 | */ 15 | export default class Container extends Panel { 16 | /** 17 | * @param {ST.Widgets.BaseWidget} parent parent widget 18 | * @param {Object} [options = Object] See {@link ST.Widgets.BaseWidget} 19 | */ 20 | constructor(parent, options = {}) { 21 | super(parent, options); 22 | // Containers have nothing to interact with 23 | this.interactive = false; 24 | this.sizeProxy.renderable = false; 25 | this.padding.setAllTo(0); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/widgets/Image.js: -------------------------------------------------------------------------------- 1 | import BaseWidget from './BaseWidget'; 2 | 3 | /** 4 | * Wraps PIXI.Sprite and allows images/ textures to become widgets 5 | * @extends ST.Widgets.BaseWidget 6 | * @memberof ST.Widgets 7 | * 8 | * @example 9 | * // load texture with PIXI first, then... 10 | * 11 | * let widget = new ST.Widgets.Image(myApp.root, {texture: myTexture}); 12 | */ 13 | export default class Image extends BaseWidget { 14 | /** 15 | *@param {ST.Widgets.BaseWidget} parent Widgets parent 16 | *@param {Object} [options = Object] See {@link ST.Widgets.BaseWidget} 17 | *@param {PIXI.Texture} [options.texture = null] The texture for the Image 18 | */ 19 | constructor(parent, options = {}) { 20 | super(parent, options); 21 | // default options 22 | const defaults = { 23 | texture: null, 24 | }; 25 | 26 | // fill in missing options with defaults 27 | options = Object.assign(defaults, options); 28 | 29 | // Images could be interactive but most will not 30 | this.interactive = false; 31 | 32 | /** 33 | * Holds the sprite internally 34 | * @member {PIXI.Sprite} 35 | * @private 36 | */ 37 | this._sprite = new PIXI.Sprite(); 38 | if(options.texture) { 39 | this._sprite.texture = options.texture; 40 | } 41 | this.addChild(this._sprite); 42 | this._sprite.width = this.width; 43 | this._sprite.height = this.height; 44 | 45 | this.sizeProxy = this._sprite; 46 | } 47 | 48 | /** 49 | * The PIXI.Sprite used internally 50 | * @member {PIXI.Sprite} 51 | */ 52 | get sprite() { 53 | return this._sprite; 54 | } 55 | 56 | set sprite(val) { // eslint-disable-line require-jsdoc 57 | if(val instanceof PIXI.Sprite) { 58 | this._sprite = val; 59 | } 60 | } 61 | 62 | /** 63 | *The sprites texture 64 | *@member {PIXI.Texture} 65 | */ 66 | get texture() { 67 | return this._sprite.texture; 68 | } 69 | 70 | set texture(val) { // eslint-disable-line require-jsdoc 71 | if(val instanceof PIXI.Texture) { 72 | this._sprite.texture = val; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/widgets/Label.js: -------------------------------------------------------------------------------- 1 | import * as PIXI from 'pixi.js'; 2 | import BaseWidget from './BaseWidget'; 3 | 4 | /** 5 | * A simple text label 6 | * @memberof ST.Widgets 7 | * @extends ST.Widgets.BaseWidget 8 | * 9 | * @example 10 | * let widget = new ST.Widgets.Label(myApp.root, {text: 'My Text'}); 11 | */ 12 | export default class Label extends BaseWidget { 13 | /** 14 | * @param {ST.BaseWidget} parent Widgets parent 15 | * @param {Object} [options = Object] See {@link ST.Widgets.BaseWidget} 16 | * @param {String} [options.text] The text presented on the label 17 | */ 18 | constructor(parent, options = {}) { 19 | super(parent, options); 20 | // default options 21 | const defaults = { 22 | text: '', 23 | }; 24 | 25 | options = Object.assign(defaults, options); 26 | 27 | // this is mostly un-needed for labels 28 | this.interactive = false; 29 | 30 | /** 31 | * Internal PIXI.Text object 32 | * @member {PIXI.Text} 33 | * @private 34 | */ 35 | this._textObj = new PIXI.Text(); 36 | this.addChild(this._textObj); 37 | this._textObj.mask = null; 38 | this._clipGraphic.renderable = false; 39 | this.paintDefault(); 40 | this.text = options.text; 41 | 42 | /** 43 | * Fires when the text is changed 44 | * @event ST.Widgets.Label#textChanged 45 | */ 46 | } 47 | 48 | /** @inheritdoc */ 49 | paintDefault() { 50 | if(this._textObj) { 51 | this._textObj.style = this.theme.fontStyles.enabled; 52 | } 53 | } 54 | 55 | /** @inheritdoc */ 56 | paintDown() { 57 | if(this._textObj) { 58 | this._textObj.style = this.theme.fontStyles.click; 59 | } 60 | } 61 | 62 | /** @inheritdoc */ 63 | paintHover() { 64 | if(this._textObj) { 65 | this._textObj.style = this.theme.fontStyles.hover; 66 | } 67 | } 68 | 69 | /** @inheritdoc */ 70 | paintDisabled() { 71 | if(this._textObj) { 72 | this._textObj.style = this.theme.fontStyles.disabled; 73 | } 74 | } 75 | 76 | /** 77 | * The text presented by the label 78 | * @member {String} 79 | */ 80 | get text() { 81 | return this._textObj.text; 82 | } 83 | 84 | set text(val) { // eslint-disable-line require-jsdoc 85 | this._textObj.text = val; 86 | this.width = this._textObj.width; 87 | this.height = this._textObj.height; 88 | this._updateClipGraphic(); 89 | this.emit('textChanged', val); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/widgets/Panel.js: -------------------------------------------------------------------------------- 1 | import BaseWidget from './BaseWidget'; 2 | import Theme from '.././Theme'; 3 | 4 | /* Add widget style to ST.Theme.defaults. This way the widget 5 | will always have a style even if the given theme doesn't have one 6 | specifically for it. All widgets that have themable elements 7 | should call this method before describing their class.*/ 8 | Theme.registerDefaultWidgetStyle('panel', { 9 | enabled: 0x2b2b2b, 10 | disabled: 0x272727, 11 | hover: 0x244c24, 12 | click: 0x387038, 13 | }); 14 | 15 | /** 16 | * Panels are simple rectangle widgets that contain other widgets. 17 | * @memberof ST.Widgets 18 | * @extends ST.Widgets.BaseWidget 19 | * 20 | * @example 21 | * let widget = new ST.Widgets.Panel(myApp.root, { 22 | * width: 400, 23 | * height: 400, 24 | * }); 25 | */ 26 | export default class Panel extends BaseWidget { 27 | /** 28 | * @param {ST.Widgets.BaseWidget} parent The widgets parent 29 | * @param {Object} [options] See {@link ST.Widgets.BaseWidget} 30 | */ 31 | constructor(parent, options) { 32 | super(parent, options); 33 | 34 | // Most panels will be static, so interactivity is not needed 35 | // Remember to set true for subclasses that are. 36 | this.interactive = false; 37 | 38 | /** 39 | * Internal background sprite 40 | * @member {PIXI.Sprite} 41 | * @private 42 | */ 43 | this._bkgObj = new PIXI.Sprite(this.theme.texture); 44 | this.addChild(this._bkgObj); 45 | this._bkgObj.width = this.width; 46 | this._bkgObj.height = this.height; 47 | 48 | this.sizeProxy = this._bkgObj; 49 | 50 | this.paintDefault(); 51 | } 52 | 53 | /** @inheritdoc */ 54 | paintDefault() { 55 | if(this._bkgObj) { 56 | this._bkgObj.texture = this.theme.textures.panel.enabled; 57 | } 58 | } 59 | 60 | /** @inheritdoc */ 61 | paintDisabled() { 62 | if(this._bkgObj) { 63 | this._bkgObj.texture = this.theme.textures.panel.disabled; 64 | } 65 | } 66 | 67 | /** @inheritdoc */ 68 | paintHover() { 69 | if(this._bkgObj) { 70 | this._bkgObj.texture = this.theme.textures.panel.hover; 71 | } 72 | } 73 | 74 | /** @inheritdoc */ 75 | paintDown() { 76 | if(this._bkgObj) { 77 | this._bkgObj.texture = this.theme.textures.panel.click; 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/widgets/SITransform.js: -------------------------------------------------------------------------------- 1 | import * as PIXI from 'pixi.js'; 2 | 3 | /** 4 | * Transform that makes a PIXI.Container size independent 5 | * from its parent. ( FOR INTERNAL USE ONLY! ) 6 | * @memberof ST.Widgets 7 | * @extends external:TransformStatic 8 | */ 9 | export default class SITransform extends PIXI.TransformStatic { 10 | /** 11 | * 12 | */ 13 | constructor() { 14 | super(); 15 | } 16 | 17 | /** 18 | * @inheritDoc 19 | */ 20 | updateTransform(parentTransform) { 21 | const lt = this.localTransform; 22 | if (this._localID !== this._currentLocalID) { 23 | // get the matrix values of the displayobject 24 | // based on its transform properties.. 25 | lt.a = this._cx * this.scale._x; 26 | lt.b = this._sx * this.scale._x; 27 | lt.c = this._cy * this.scale._y; 28 | lt.d = this._sy * this.scale._y; 29 | lt.tx = this.position._x - ((this.pivot._x * lt.a) + 30 | (this.pivot._y * lt.c)); 31 | lt.ty = this.position._y - ((this.pivot._x * lt.b) + 32 | (this.pivot._y * lt.d)); 33 | this._currentLocalID = this._localID; 34 | // force an update.. 35 | this._parentID = -1; 36 | } 37 | if (this._parentID !== parentTransform._worldID) { 38 | // concat the parent matrix with the objects transform. 39 | const pt = parentTransform.worldTransform; 40 | const wt = this.worldTransform; 41 | 42 | /* I believe that removing the folowing prevent size modification 43 | from the parent*/ 44 | 45 | // wt.a = (lt.a * pt.a) + (lt.b * pt.c); 46 | // wt.b = (lt.a * pt.b) + (lt.b * pt.d); 47 | // wt.c = (lt.c * pt.a) + (lt.d * pt.c); 48 | // wt.d = (lt.c * pt.b) + (lt.d * pt.d); 49 | // 50 | wt.tx = (lt.tx * pt.a) + (lt.ty * pt.c) + pt.tx; 51 | wt.ty = (lt.tx * pt.b) + (lt.ty * pt.d) + pt.ty; 52 | this._parentID = parentTransform._worldID; 53 | // update the id of the transform.. 54 | this._worldID ++; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/widgets/StageWidget.js: -------------------------------------------------------------------------------- 1 | import BaseWidget from './BaseWidget'; 2 | 3 | /** 4 | * Container widget that keeps a fixed width and height. Probably should only 5 | * be used for full screen widgets such as the apps root widget. 6 | * @memberof ST.Widgets 7 | * @extends ST.Widgets.BaseWidget 8 | * 9 | * @example 10 | * let widget = new ST.Widgets.StageWidget(myApp.root, { 11 | * width: 100, 12 | * height: 300, 13 | * }); 14 | */ 15 | export default class StageWidget extends BaseWidget { 16 | /** 17 | * @param {ST.Widgets.BaseWidget} parent The widgets parent 18 | * @param {Object} [options] See {@link ST.Widgets.BaseWidget} 19 | */ 20 | constructor(parent, options = {}) { 21 | super(parent, options); 22 | // Nothing to interact with 23 | this.interactive = false; 24 | } 25 | 26 | /** 27 | * Calculate bounds to remain at the user 28 | * defined width and height of the widget as it 29 | * count the user defined size into the bounds. 30 | * @private 31 | * @override 32 | */ 33 | _calculateBounds() { 34 | this._bounds.minX = 0; 35 | this._bounds.minY = 0; 36 | this._bounds.maxX = this._width; 37 | this._bounds.maxY = this._height; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/widgets/TextButton.js: -------------------------------------------------------------------------------- 1 | import Button from './Button'; 2 | import Label from './Label'; 3 | 4 | /** 5 | * Button with a text label 6 | * @memberof ST.Widgets 7 | * @extends ST.Widgets.Button 8 | * 9 | * @example 10 | * let widget = new ST.Widgets.TextButton(myApp.root, { 11 | * width: 100, 12 | * height: 30, 13 | * text: 'My Text Button', 14 | * }); 15 | */ 16 | export default class TextButton extends Button { 17 | /** 18 | * @param {ST.BaseWidget} parent The widgets parent 19 | * @param {Object} [options] See {@link ST.Widgets.BaseWidget} 20 | * @param {String} [options.text] The labels text 21 | */ 22 | constructor(parent, options = {}) { 23 | super(parent, options); 24 | 25 | const defaults = { 26 | text: '', 27 | }; 28 | 29 | options = Object.assign(defaults, options); 30 | 31 | /** 32 | * The internal label 33 | * @member {ST.Widgets.Label} 34 | * @private 35 | */ 36 | this.label = new Label(this, {text: options.text}); 37 | } 38 | 39 | /** 40 | * The buttons text 41 | * @member {String} 42 | */ 43 | get text() { 44 | return this.label.text; 45 | } 46 | 47 | set text(val) { // eslint-disable-line require-jsdoc 48 | this.label.text = val; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/widgets/index.js: -------------------------------------------------------------------------------- 1 | import BaseWidget from './BaseWidget'; 2 | import StageWidget from './StageWidget'; 3 | import Label from './Label'; 4 | import Panel from './Panel'; 5 | import Button from './Button'; 6 | import Slider from './Slider'; 7 | import TextButton from './TextButton'; 8 | import SITransform from './SITransform'; 9 | import Container from './Container'; 10 | import Image from './Image'; 11 | 12 | export { 13 | BaseWidget, 14 | StageWidget, 15 | Label, 16 | Panel, 17 | Button, 18 | Slider, 19 | TextButton, 20 | SITransform, 21 | Container, 22 | Image, 23 | }; 24 | -------------------------------------------------------------------------------- /styleSheets/greyToadTheme.js: -------------------------------------------------------------------------------- 1 | greyToadTheme = { 2 | background: 0x333333, 3 | widgets: { 4 | panel: { 5 | enabled: 0x2b2b2b, 6 | disabled: 0x272727, 7 | hover: 0x244c24, 8 | click: 0x387038, 9 | }, 10 | button: { 11 | enabled: 0x363636, 12 | disabled: 0x2e2e2e, 13 | hover: 0x264e26, 14 | click: 0x3a723a, 15 | }, 16 | slider: { 17 | track: { 18 | enabled: 0x303030, 19 | disabled: 0x2e2e2e, 20 | }, 21 | button: { 22 | enabled: 0x284328, 23 | disabled: 0x303030, 24 | hover: 0x264e26, 25 | click: 0x3a723a, 26 | }, 27 | }, 28 | }, 29 | text: { 30 | enabled: { 31 | fontFamily: 'Arial', 32 | fontSize: 12, 33 | fill: 0x959595, 34 | }, 35 | disabled: { 36 | fontFamily: 'Arial', 37 | fontSize: 12, 38 | fill: 0x555555, 39 | }, 40 | hover: { 41 | fontFamily: 'Arial', 42 | fontSize: 12, 43 | fill: 0x85ad85, 44 | }, 45 | click: { 46 | fontFamily: 'Arial', 47 | fontSize: 12, 48 | fill: 0x99c799, 49 | }, 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /styleSheets/holyStyle.js: -------------------------------------------------------------------------------- 1 | holyStyle = { 2 | background: 0xffffff, 3 | widgets: { 4 | panel: { 5 | enabled: 0xffffff, 6 | disabled: 0xffffff, 7 | hover: 0xffffff, 8 | click: 0xffffff, 9 | }, 10 | button: { 11 | enabled: 0xbdbdbd, 12 | disabled: 0xb4b4b4, 13 | hover: 0xb20000, 14 | click: 0xc00000, 15 | }, 16 | slider: { 17 | track: { 18 | enabled: 0xb4b4b4, 19 | disabled: 0xb4b4b4, 20 | }, 21 | button: { 22 | enabled: 0xb20000, 23 | disabled: 0xbdbdbd, 24 | hover: 0xbb0000, 25 | click: 0xc00000, 26 | }, 27 | }, 28 | }, 29 | text: { 30 | enabled: { 31 | fontFamily: 'Arial', 32 | fontSize: 12, 33 | fill: 0x000000, 34 | }, 35 | disabled: { 36 | fontFamily: 'Arial', 37 | fontSize: 12, 38 | fill: 0xe5e5e5, 39 | }, 40 | hover: { 41 | fontFamily: 'Arial', 42 | fontSize: 12, 43 | fill: 0xc00000, 44 | }, 45 | click: { 46 | fontFamily: 'Arial', 47 | fontSize: 12, 48 | fill: 0xc00000, 49 | }, 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('ST', ()=>{ 4 | it('should exist as a global object', ()=>{ 5 | expect(ST).to.be.an('object'); 6 | }); 7 | require('./spec'); 8 | }); 9 | -------------------------------------------------------------------------------- /test/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 23 | 24 | 25 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/spec/App-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('App', ()=>{ 4 | let app = new ST.App(); 5 | 6 | it('should create a pixi canvas on the page', ()=>{ 7 | expect(document.body.contains(app.renderer.view)).to.be.true; 8 | }); 9 | 10 | it('should have a valid theme', ()=>{ 11 | expect(app.theme).to.be.an.instanceof(ST.Theme); 12 | }); 13 | 14 | it('should have a valid root widget', ()=>{ 15 | expect(app.root).to.be.an.instanceof(ST.Widgets.BaseWidget); 16 | }); 17 | 18 | it('should resize the root widget to match the window', ()=>{ 19 | window.resizeBy(300, 200); 20 | expect(app.root.width).to.equal(window.innerWidth); 21 | expect(app.root.height).to.equal(window.innerHeight); 22 | }); 23 | 24 | describe('#name', ()=>{ 25 | it('should change the page title', ()=>{ 26 | app.name = 'SaberTooth Test'; 27 | expect(document.title).to.equal('SaberTooth Test'); 28 | }); 29 | }); 30 | 31 | describe('#autoResize', ()=>{ 32 | it('should add a listener to resize event if set to true and ' + 33 | 'one doesnt already exist', ()=>{ 34 | app.autoResize = true; 35 | let listeners = app.listeners('resize'); 36 | expect(listeners.indexOf(app.resizeToWindow)).to.not.equal(-1); 37 | }); 38 | 39 | it('should not add more than one listener', ()=>{ 40 | app.autoResize = true; 41 | let listeners = app.listeners('resize'); 42 | expect(listeners.length).to.equal(1); 43 | }); 44 | 45 | it('should remove the listener from resize if set to false', ()=>{ 46 | app.autoResize = false; 47 | let listeners = app.listeners('resize'); 48 | expect(listeners.indexOf(app.resizeToWindow)).to.equal(-1); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/spec/GraphicsGen-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('GraphicsGen', ()=>{ 4 | describe('#rectangleGraphic()', ()=>{ 5 | it('should return a Pixi.Graphics with a ' 6 | + 'width of 80 and a height of 100', ()=>{ 7 | let rectGraphic = 8 | ST.GraphicsGen.rectangleGraphic(80, 100, 0x000000); 9 | 10 | expect(rectGraphic).to.be.an.instanceof(PIXI.Graphics); 11 | expect(rectGraphic.width).to.equal(80); 12 | expect(rectGraphic.height).to.equal(100); 13 | }); 14 | }); 15 | 16 | describe('#rectangleTexture()', ()=>{ 17 | it('should return a PIXI.Texture with a' 18 | + ' width of 100 and a height of 80', ()=>{ 19 | let rectTex = 20 | ST.GraphicsGen.rectangleTexture(100, 80, 0x000000); 21 | 22 | expect(rectTex).to.be.an.instanceof(PIXI.Texture); 23 | expect(rectTex.width).to.equal(100); 24 | expect(rectTex.height).to.equal(80); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/spec/Padding-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Padding', ()=>{ 4 | let pad = new ST.Padding(); 5 | it('should be created with a default of 0', ()=>{ 6 | expect(pad.left).to.equal(0); 7 | expect(pad.top).to.equal(0); 8 | expect(pad.right).to.equal(0); 9 | expect(pad.bottom).to.equal(0); 10 | }); 11 | 12 | describe('#set()', ()=>{ 13 | it('should set each padding to the values of 1,2,3,4', ()=>{ 14 | pad.set(1, 2, 3, 4); 15 | expect(pad.left).to.equal(2); 16 | expect(pad.top).to.equal(1); 17 | expect(pad.right).to.equal(4); 18 | expect(pad.bottom).to.equal(3); 19 | }); 20 | }); 21 | 22 | describe('#setAllTo()', ()=>{ 23 | it('should set all padding values to 10', ()=>{ 24 | pad.setAllTo(10); 25 | expect(pad.left).to.equal(10); 26 | expect(pad.top).to.equal(10); 27 | expect(pad.right).to.equal(10); 28 | expect(pad.bottom).to.equal(10); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/spec/Point-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Point', ()=>{ 4 | let p = new ST.Point(); 5 | it('should create with default x and y of 0', ()=>{ 6 | expect(p.x).to.equal(0); 7 | expect(p.y).to.equal(0); 8 | }); 9 | describe('#set()', ()=>{ 10 | it('should set x and y to 20, 80', ()=>{ 11 | p.set(20, 80); 12 | expect(p.x).to.equal(20); 13 | expect(p.y).to.equal(80); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/spec/Settings-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Settings', ()=>{ 4 | it('should have a global instance', ()=>{ 5 | expect(ST.settings).to.not.be.unidentified; 6 | }); 7 | 8 | describe('#add()', ()=>{ 9 | it('should add the given object as a property of itself', ()=>{ 10 | let obj = { 11 | truck: { 12 | size: 'big', 13 | weight: 'heavy', 14 | }, 15 | }; 16 | 17 | ST.settings.add('vehicles', obj); 18 | 19 | expect(ST.settings.vehicles.truck.size).to.equal('big'); 20 | expect(ST.settings.vehicles.truck.weight).to.equal('heavy'); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/spec/Size-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Size', ()=>{ 4 | let s = new ST.Size(); 5 | it('should create with default size of 0', ()=>{ 6 | expect(s.width).to.equal(0); 7 | expect(s.height).to.equal(0); 8 | }); 9 | describe('#set()', ()=>{ 10 | it('should set width and height to 20, 80', ()=>{ 11 | s.set(20, 80); 12 | expect(s.width).to.equal(20); 13 | expect(s.height).to.equal(80); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/spec/Theme-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Theme', ()=>{ 4 | // let greyToadTheme = require('../../themes/greyToadTheme'); 5 | // let change one style because this theme is the same as the defaults 6 | greyToadTheme.background = 0xffffff; 7 | greyToadTheme.widgets.button.hover = 0xff00ff; 8 | let theme = new ST.Theme(greyToadTheme); 9 | let defTheme = new ST.Theme(); 10 | 11 | it('should load the given styles when created', ()=>{ 12 | expect(theme.background).to.equal(0xffffff); 13 | expect(theme.colors.button.hover).to.equal(0xff00ff); 14 | // not gonna check each one. This should be enough 15 | }); 16 | 17 | it('should load the default styles if no style is given', ()=>{ 18 | expect(defTheme.background).to.equal(ST.Theme.defaults.background); 19 | expect(defTheme.colors.button.hover).to.equal(ST.Theme.defaults 20 | .widgets.button.hover); 21 | // not gonna check each one. This should be enough 22 | }); 23 | 24 | describe('#colors', ()=>{ 25 | it('should equal the widgets portion of the styles', ()=>{ 26 | expect(theme.colors).to.deep.equal(greyToadTheme.widgets); 27 | }); 28 | }); 29 | 30 | describe('#fontStyles', ()=>{ 31 | it('should equal the text portion of the styles', ()=>{ 32 | expect(theme.fontStyles).to.deep.equal(greyToadTheme.text); 33 | }); 34 | }); 35 | 36 | describe('#frames', ()=>{ 37 | it('should contain rects in the structure of the widgets' + 38 | ' portion of the styles', ()=>{ 39 | // textures becomes an alias for frames therefor 40 | // see #textures 41 | 42 | // try a few of them 43 | // expect(theme.frames.button.enabled).to.be.an 44 | // .instanceof(PIXI.Rectangle); 45 | // expect(theme.frames.panel.hover).to.be.an 46 | // .instanceof(PIXI.Rectangle); 47 | // expect(theme.frames.slider.button.enabled).to.be.an 48 | // .instanceof(PIXI.Rectangle); 49 | }); 50 | }); 51 | 52 | describe('#baseTexture', ()=>{ 53 | it('should be a PIXI.BaseTexture', ()=>{ 54 | expect(theme.baseTexture).to.be.an.instanceof(PIXI.BaseTexture); 55 | }); 56 | }); 57 | 58 | describe('#textures', ()=>{ 59 | it('should contain textures in the structure of the widgets' + 60 | ' portion of the styles', ()=>{ 61 | // try a few of them 62 | expect(theme.textures.button.enabled).to.be.an 63 | .instanceof(PIXI.Texture); 64 | expect(theme.textures.panel.hover).to.be.an 65 | .instanceof(PIXI.Texture); 66 | expect(theme.textures.slider.button.enabled).to.be.an 67 | .instanceof(PIXI.Texture); 68 | }); 69 | }); 70 | 71 | describe('#background', ()=>{ 72 | it('should equal the color given in the style', ()=>{ 73 | expect(theme.background).to.equal(0xffffff); 74 | }); 75 | }); 76 | 77 | describe('#getClipGraphic() (STATIC)', ()=>{ 78 | it('should return the global clipGraphic', ()=>{ 79 | expect(ST.Theme.getClipGraphic()).to.be.an 80 | .instanceof(PIXI.Graphics); 81 | }); 82 | }); 83 | 84 | describe('#registerDefaultWidgetStyle() (STATIC)', ()=>{ 85 | it('should add the given style to theme.defaults(global)', ()=>{ 86 | let style = { 87 | knob: { 88 | enabled: 0x000000, 89 | disabled: 0xffffff, 90 | }, 91 | }; 92 | 93 | ST.Theme.registerDefaultWidgetStyle('pane', style); 94 | 95 | expect(ST.Theme.defaults.widgets.pane.knob.enabled) 96 | .to.equal(0x000000); 97 | 98 | expect(ST.Theme.defaults.widgets.pane.knob.disabled) 99 | .to.equal(0xffffff); 100 | }); 101 | }); 102 | 103 | describe('#makeGraphicsRecursive()', ()=>{ 104 | // not needed if frames test above passes 105 | }); 106 | 107 | describe('#makeTexture()', ()=>{ 108 | // not needed if baseTexture test above passes 109 | }); 110 | 111 | describe('#makeTexturesRecursive()', ()=>{ 112 | // not needed if textures text above passes 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/spec/index.js: -------------------------------------------------------------------------------- 1 | require('./Theme-spec'); 2 | require('./Size-spec'); 3 | require('./Point-spec'); 4 | require('./Padding-spec'); 5 | require('./GraphicsGen-spec'); 6 | require('./App-spec'); 7 | require('./widgets'); 8 | require('./layoutSys'); 9 | require('./Settings-spec'); 10 | -------------------------------------------------------------------------------- /test/spec/layoutSys/Alignment-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Alignment', ()=>{ 4 | let widget0 = new ST.Widgets.BaseWidget(); 5 | widget0.width = 400; widget0.height = 400; 6 | let widget1 = new ST.Widgets.BaseWidget(widget0); 7 | widget1.width = 200; widget1.height = 200; 8 | 9 | describe('#getOffset()', ()=>{ 10 | it('should return a relative offset base on the given horizontal' 11 | + ' and vertical alignment', ()=>{ 12 | widget0.layout.alignment.hAlign = ST.Alignment.center; 13 | widget0.layout.alignment.vAlign = ST.Alignment.middle; 14 | let off = new ST.Point(); 15 | off = widget0.layout.alignment.getOffset(widget0, 16 | widget1.width, widget1.height); 17 | // (widget0.width/2) - (widget1.width/2) 18 | expect(off.x).to.equal(100); 19 | expect(off.y).to.equal(100); 20 | }); 21 | }); 22 | 23 | describe('#left', ()=>{ 24 | it('should return relative position for left alignment', ()=>{ 25 | let off = ST.Alignment.left(400, widget0); 26 | expect(off).to.equal(4); 27 | }); 28 | }); 29 | 30 | describe('#center', ()=>{ 31 | it('should return relative position for center alignment', ()=>{ 32 | let off = ST.Alignment.center(100, widget0); 33 | expect(off).to.equal(150); 34 | }); 35 | }); 36 | 37 | describe('#right', ()=>{ 38 | it('should return relative position for right alignment', ()=>{ 39 | let off = ST.Alignment.right(100, widget0); 40 | expect(off).to.equal(300); 41 | }); 42 | }); 43 | 44 | describe('#top', ()=>{ 45 | it('should return relative position for top alignment', ()=>{ 46 | let off = ST.Alignment.top(100, widget0); 47 | expect(off).to.equal(4); 48 | }); 49 | }); 50 | 51 | describe('#middle', ()=>{ 52 | it('should return relative position for middle alignment', ()=>{ 53 | let off = ST.Alignment.middle(100, widget0); 54 | expect(off).to.equal(150); 55 | }); 56 | }); 57 | 58 | describe('#bottom', ()=>{ 59 | it('should return relative position for bottom alignment', ()=>{ 60 | let off = ST.Alignment.bottom(100, widget0); 61 | expect(off).to.equal(300); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/spec/layoutSys/index.js: -------------------------------------------------------------------------------- 1 | require('./Alignment-spec'); 2 | require('./sizePolicies'); 3 | require('./layouts'); 4 | -------------------------------------------------------------------------------- /test/spec/layoutSys/layouts/BaseLayout-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('BaseLayout', ()=>{ 4 | let widget0 = new ST.Widgets.Panel(null, {width: 100, height: 100}); 5 | let widget1 = new ST.Widgets.Button(widget0, {width: 20, height: 20}); 6 | widget1.position.set(20, 20); 7 | 8 | describe('#exec', ()=>{ 9 | it('should set the position of its children and exec their layours', 10 | ()=>{ 11 | // real position isnt set until layout executes 12 | expect(widget1.transform.position.x).to.equal(0); 13 | expect(widget1.transform.position.y).to.equal(0); 14 | 15 | let spy = sinon.spy(widget1.layout, 'exec'); 16 | 17 | widget0.layout.exec(); 18 | 19 | expect(spy.called).to.be.true; 20 | 21 | widget1.layout.exec.restore(); 22 | 23 | expect(widget1.transform.position.x).to.equal(24); 24 | expect(widget1.transform.position.y).to.equal(24); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/spec/layoutSys/layouts/BoxLayout-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('BoxLayout', ()=>{ 4 | let w0 = new ST.Widgets.Panel(null, {width: 400, height: 400}); 5 | w0.layout = new ST.Layouts.BoxLayout(w0, ST.VERTICAL); 6 | let w1 = new ST.Widgets.Button(w0, {width: 20, height: 20}); 7 | let w2 = new ST.Widgets.Button(w0, {width: 20, height: 20}); // eslint-disable-line 8 | let w3 = new ST.Widgets.Button(w0, {width: 20, height: 20}); // eslint-disable-line 9 | 10 | describe('#initTotalChildrenSize()', ()=>{ 11 | it('should calculate the total size of the children', ()=>{ 12 | expect(w0.layout._totalChildrenWidth).to.equal(0); 13 | expect(w0.layout._totalChildrenHeight).to.equal(0); 14 | 15 | w0.layout.initTotalChildrenSize(); 16 | 17 | // add together + spacing 18 | expect(w0.layout._totalChildrenWidth).to.equal(72); 19 | expect(w0.layout._totalChildrenHeight).to.equal(72); 20 | }); 21 | }); 22 | 23 | describe('#beginIteration()', ()=>{ 24 | it('should prepare variables before iteration', ()=>{ 25 | let wLayout = w0.layout; 26 | wLayout.beginIteration(); 27 | 28 | expect(wLayout._totalChildrenHeight).to.equal(0); 29 | expect(wLayout._totalChildrenWidth).to.equal(0); 30 | 31 | expect(wLayout.posOffset.x).to.equal(0); 32 | expect(wLayout.posOffset.y).to.equal(0); 33 | }); 34 | }); 35 | 36 | describe('#setChildPos()', ()=>{ 37 | it('should set the position of the child', ()=>{ 38 | let wLayout = w0.layout; 39 | wLayout.setChildPos(w1); 40 | 41 | expect(wLayout._totalChildrenWidth).to.equal(24); 42 | expect(wLayout._totalChildrenHeight).to.equal(24); 43 | 44 | expect(w1.transform.position.x).to.equal(4); 45 | expect(w1.transform.position.y).to.equal(4); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/spec/layoutSys/layouts/FixedLayout-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('FixedLayout', ()=>{ 4 | let w0 = new ST.Widgets.Panel(null, {width: 400, height: 400}); 5 | let w1 = new ST.Widgets.Button(w0); 6 | w1.position.set(12, 12); 7 | 8 | describe('#setChildPos()', ()=>{ 9 | it('should set the childs position', ()=>{ 10 | expect(w1.transform.position.x).to.equal(0); 11 | expect(w1.transform.position.y).to.equal(0); 12 | 13 | w0.layout.setChildPos(w1); 14 | 15 | expect(w1.transform.position.x).to.equal(16); 16 | expect(w1.transform.position.y).to.equal(16); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/spec/layoutSys/layouts/HBoxLayout-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('HBoxLayout', ()=>{ 4 | it('should construct a BoxLayout with HORIZONTAL orientation', ()=>{ 5 | let w1 = new ST.Widgets.Panel(); 6 | 7 | w1.layout = new ST.Layouts.HBoxLayout(w1); 8 | expect(w1.layout.orientation).to.equal(ST.HORIZONTAL); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/spec/layoutSys/layouts/VBoxLayout-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('VBoxLayout', ()=>{ 4 | it('should construct a BoxLayout with VERTICAL orientation', ()=>{ 5 | let w1 = new ST.Widgets.Panel(); 6 | 7 | w1.layout = new ST.Layouts.VBoxLayout(w1); 8 | expect(w1.layout.orientation).to.equal(ST.VERTICAL); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/spec/layoutSys/layouts/index.js: -------------------------------------------------------------------------------- 1 | require('./BaseLayout-spec'); 2 | require('./BoxLayout-spec'); 3 | require('./FixedLayout-spec'); 4 | require('./HBoxLayout-spec'); 5 | require('./VBoxLayout-spec'); 6 | -------------------------------------------------------------------------------- /test/spec/layoutSys/sizePolicies/BasePolicy-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('BasePolicy', ()=>{ 4 | let widget = new ST.Widgets.Button(); 5 | 6 | describe('#childPolicyFinished()', ()=>{ 7 | it('should add each childs size to totalChildrenFinishedSize and' 8 | + ' substract 1 from totalChildrenFinished', ()=>{ 9 | widget.hPolicy.totalChildrenFinished = 1; 10 | widget.hPolicy.totalChildrenFinishedSize = 100; 11 | widget.hPolicy.childPolicyFinished(400); 12 | 13 | expect(widget.hPolicy.totalChildrenFinished).to.equal(0); 14 | expect(widget.hPolicy.totalChildrenFinishedSize).to.equal(500); 15 | }); 16 | }); 17 | 18 | describe('#exec()', ()=>{ 19 | let widget2 = new ST.Widgets.Button(widget); 20 | 21 | it('should add a one time event' 22 | + '( register size with childPolicyFinished ) and exec the ' 23 | + 'HORIZONTAL size policy for each child ' 24 | + 'that is HORIZONTAL and an instanceof ST.Widgets.BaseWidget', ()=>{ 25 | let spy = sinon.spy(widget2.hPolicy, 'exec'); 26 | expect(widget2.hPolicy.listeners('finished', true)).to.be.false; 27 | expect(widget2).to.be.an.instanceof(ST.Widgets.BaseWidget); 28 | expect(widget.hPolicy.orientation).to.equal(ST.HORIZONTAL); 29 | widget.hPolicy.exec(); 30 | 31 | expect(spy.called).to.be.true; 32 | widget2.hPolicy.exec.restore(); 33 | }); 34 | 35 | it('should add a one time event' 36 | + '( register size with childPolicyFinished ) and exec the ' 37 | + 'VERTICAL size policy for each child ' 38 | + 'that is VERTICAL and an instanceof ST.Widgets.BaseWidget', ()=>{ 39 | let spy = sinon.spy(widget2.vPolicy, 'exec'); 40 | expect(widget2.vPolicy.listeners('finished', true)).to.be.false; 41 | expect(widget2).to.be.an.instanceof(ST.Widgets.BaseWidget); 42 | expect(widget.vPolicy.orientation).to.equal(ST.VERTICAL); 43 | widget.vPolicy.exec(); 44 | 45 | expect(spy.called).to.be.true; 46 | widget2.vPolicy.exec.restore(); 47 | }); 48 | 49 | it('should call sizeWidgetHorizontal() if the widget is HORIZONTAL', 50 | ()=>{ 51 | let spy = sinon.spy(widget.hPolicy, 'sizeWidgetHorizontal'); 52 | widget.hPolicy.exec(); 53 | expect(spy.called).to.be.true; 54 | widget.hPolicy.sizeWidgetHorizontal.restore(); 55 | }); 56 | 57 | it('should call sizeWidgetVertical() if the widget is VERTICAL', 58 | ()=>{ 59 | let spy = sinon.spy(widget.vPolicy, 'sizeWidgetVertical'); 60 | widget.vPolicy.exec(); 61 | expect(spy.called).to.be.true; 62 | widget.vPolicy.sizeWidgetVertical.restore(); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/spec/layoutSys/sizePolicies/ExpandingPolicy-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('ExpandingPolicy', ()=>{ 4 | let widget0 = new ST.Widgets.Button(); 5 | widget0.width = 200; widget0.height = 200; 6 | let widget1 = new ST.Widgets.Button(widget0); 7 | widget1.hPolicy = new ST.SizePolicies.ExpandingPolicy(widget1); 8 | widget1.vPolicy = new ST.SizePolicies.ExpandingPolicy(widget1); 9 | widget1.width = 100; widget1.height = 100; 10 | 11 | describe('#parentReadyH', ()=>{ 12 | it('should size the widget to match its parents width - padding', ()=>{ 13 | expect(widget1.width).to.equal(100); 14 | widget1.hPolicy.parentReadyH(); 15 | expect(widget1.width).to.equal(192); 16 | }); 17 | }); 18 | 19 | describe('#parentReadyV', ()=>{ 20 | it('should size the widget to match its parents height - padding', ()=>{ 21 | expect(widget1.height).to.equal(100); 22 | widget1.vPolicy.parentReadyV(); 23 | expect(widget1.height).to.equal(192); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/spec/layoutSys/sizePolicies/FixedPolicy-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('FixedPolicy', ()=>{ 4 | let widget = new ST.Widgets.Button(); 5 | 6 | describe('#sizeWidgetHorizontal', ()=>{ 7 | it('should validate the width of the widget', ()=>{ 8 | let spy = sinon.spy(widget, 'validateWidth'); 9 | widget.hPolicy.sizeWidgetHorizontal(); 10 | expect(spy.called).to.be.true; 11 | widget.validateWidth.restore(); 12 | }); 13 | }); 14 | 15 | describe('#sizeWidgetVertical', ()=>{ 16 | it('should validate the height of the widget', ()=>{ 17 | let spy = sinon.spy(widget, 'validateHeight'); 18 | widget.vPolicy.sizeWidgetVertical(); 19 | expect(spy.called).to.be.true; 20 | widget.validateHeight.restore(); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/spec/layoutSys/sizePolicies/SharedExpandingPolicy-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('SharedExpandingPolicy', ()=>{ 4 | let widget0 = new ST.Widgets.Panel(null, {width: 400, height: 400}); 5 | widget0.layout = new ST.Layouts.HBoxLayout(widget0); 6 | 7 | let widget1 = new ST.Widgets.Button(widget0); 8 | widget1.hPolicy = new ST.SizePolicies.SharedExpandingPolicy(widget1); 9 | widget1.vPolicy 10 | = new ST.SizePolicies.SharedExpandingPolicy(widget1, ST.VERTICAL); 11 | 12 | let widget2 = new ST.Widgets.Button(widget0); 13 | widget2.hPolicy = new ST.SizePolicies.SharedExpandingPolicy(widget2); 14 | widget2.vPolicy 15 | = new ST.SizePolicies.SharedExpandingPolicy(widget2, ST.VERTICAL); 16 | 17 | describe('#parentReadyH', ()=>{ 18 | it('should size the widgets equal size if all have shared policy', ()=>{ 19 | widget0.hPolicy.exec(); 20 | expect(widget1.width).to.equal(194); 21 | expect(widget2.width).to.equal(194); 22 | }); 23 | 24 | it('should handle widgets that arent of shared policy', ()=>{ 25 | let nullwidget = new ST.Widgets.Panel(); 26 | let widget3 27 | = new ST.Widgets.Button(widget0, {width: 100, height: 100}); 28 | widget3.hPolicy = new ST.SizePolicies.FixedPolicy(widget3); 29 | widget0.hPolicy.exec(); 30 | expect(widget1.width).to.equal(142); 31 | expect(widget2.width).to.equal(142); 32 | expect(widget3.width).to.equal(100); 33 | nullwidget.addChild(widget3); 34 | }); 35 | }); 36 | 37 | describe('#parentReadyV', ()=>{ 38 | it('should size the widgets equal size if all have shared policy', ()=>{ 39 | /* 40 | same as above test. 41 | */ 42 | }); 43 | 44 | it('should handle widgets that arent of shared policy', ()=>{ 45 | /* 46 | same as above test. 47 | */ 48 | }); 49 | }); 50 | 51 | describe('#consumeUnusedSpaceH()', ()=>{ 52 | // TODO 53 | }); 54 | 55 | describe('#consumeUnusedSpaceV()', ()=>{ 56 | // TODO 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/spec/layoutSys/sizePolicies/index.js: -------------------------------------------------------------------------------- 1 | require('./BasePolicy-spec'); 2 | require('./ExpandingPolicy-spec'); 3 | require('./FixedPolicy-spec'); 4 | require('./SharedExpandingPolicy-spec'); 5 | -------------------------------------------------------------------------------- /test/spec/widgets/BaseWidget-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | TODO: 5 | test things that should happen when events are fired. 6 | eg. when padding changes _updateClipGraphic should be called. 7 | */ 8 | 9 | describe('BaseWidget', ()=>{ 10 | let widget0 = new ST.Widgets.Panel(); 11 | let widget1 = new ST.Widgets.Panel(widget0); 12 | let widget2 = new ST.Widgets.Panel(widget1); 13 | 14 | beforeEach(()=>{ 15 | widget1.layout 16 | = new ST.Layouts.FixedLayout(widget1); 17 | widget2.hPolicy 18 | = new ST.SizePolicies.FixedPolicy(widget2); 19 | widget0.hPolicy 20 | = new ST.SizePolicies.FixedPolicy(widget0); 21 | widget1.validate(); 22 | widget0.validate(); 23 | widget2.validate(); 24 | }); 25 | 26 | it('should parent one widget to another', ()=>{ 27 | expect(widget2.parent).to.equal(widget1); 28 | }); 29 | 30 | describe('#beginBypassUpdate()', ()=>{ 31 | 32 | }); 33 | 34 | describe('#endBypassUpdate()', ()=>{ 35 | 36 | }); 37 | 38 | describe('#validateWidth()', ()=>{ 39 | it('should keep widgets width within min/max', ()=>{ 40 | widget2.min.width = 10; 41 | widget2.max.width = 80; 42 | widget2.width = 100; 43 | widget2.validateWidth(); 44 | expect(widget2.width).to.equal(80); 45 | widget2.width = 5; 46 | widget2.validateWidth(); 47 | expect(widget2.width).to.equal(10); 48 | }); 49 | }); 50 | 51 | describe('#validateHeight()', ()=>{ 52 | it('should keep widgets height within min/max', ()=>{ 53 | widget2.min.height = 10; 54 | widget2.max.height = 80; 55 | widget2.height = 100; 56 | widget2.validateHeight(); 57 | expect(widget2.height).to.equal(80); 58 | widget2.height = 5; 59 | widget2.validateHeight(); 60 | expect(widget2.height).to.equal(10); 61 | }); 62 | }); 63 | 64 | describe('#update()', ()=>{ 65 | 66 | }); 67 | 68 | describe('#validate()', ()=>{ 69 | 70 | }); 71 | 72 | describe('#invalidate()', ()=>{ 73 | 74 | }); 75 | 76 | describe('#routeInvalidation()', ()=>{ 77 | it('should invalidate the highest parent', ()=>{ 78 | widget1.layout 79 | = new ST.Layouts.HBoxLayout(widget1); 80 | widget2.layout 81 | = new ST.Layouts.HBoxLayout(widget2); 82 | widget2.routeInvalidation(); 83 | expect(widget0.valid).to.be.false; 84 | expect(widget1.valid).to.be.true; 85 | expect(widget2.valid).to.be.true; 86 | }); 87 | 88 | it('should invalidate the first parent with a fixed size policy', ()=>{ 89 | widget2.routeInvalidation(); 90 | expect(widget0.valid).to.be.true; 91 | expect(widget1.valid).to.be.false; 92 | expect(widget2.valid).to.be.true; 93 | }); 94 | }); 95 | 96 | describe('#recursiveRouteUpdate()', ()=>{ 97 | it('should route update to itself if no parent exist', ()=>{ 98 | let updateSpy = sinon.spy(widget0, 'update'); 99 | widget0.recursiveRouteUpdate(); 100 | expect(updateSpy.called).to.be.true; 101 | widget0.update.restore(); 102 | }); 103 | 104 | it('should recursivley run this function if the parent is valid', ()=>{ 105 | widget1.invalidate(); 106 | let spy = sinon.spy(widget1, 'recursiveRouteUpdate'); 107 | widget2.recursiveRouteUpdate(); 108 | expect(spy.called).to.be.true; 109 | widget1.recursiveRouteUpdate.restore(); 110 | }); 111 | 112 | it('should route update to itself if parent is valid', ()=>{ 113 | let updateSpy = sinon.spy(widget0, 'update'); 114 | widget0.recursiveRouteUpdate(); 115 | expect(updateSpy.called).to.be.true; 116 | widget0.update.restore(); 117 | }); 118 | }); 119 | 120 | describe('#renderCanvas()', ()=>{ 121 | 122 | }); 123 | 124 | describe('#renderWebGL()', ()=>{ 125 | 126 | }); 127 | 128 | describe('#setParent()', ()=>{ 129 | 130 | }); 131 | 132 | describe('#addChild', ()=>{ 133 | it('should set each PIXI.Containers mask to null', ()=>{ 134 | let pc = new PIXI.Container(); 135 | widget2.addChild(pc); 136 | expect(pc.mask).to.be.null; 137 | }); 138 | 139 | it('should add its theme to each BaseWidget child added', ()=>{ 140 | expect(widget2.theme).to.equal(widget1.theme); 141 | }); 142 | 143 | it('should set mask to null if child has updateOnHostChanges = false ' 144 | + 'for both size policies', ()=>{ 145 | // should set to parent 146 | widget1.layout = new ST.Layouts.VBoxLayout(widget1); 147 | widget2.hPolicy 148 | = new ST.SizePolicies.FixedPolicy(widget2); 149 | 150 | expect(widget2.sizeProxy.mask).to.equal(widget1.clipGraphic); 151 | 152 | widget2.hPolicy 153 | = new ST.SizePolicies.ExpandingPolicy(widget2, ST.HORIZONTAL); 154 | widget2.vPolicy 155 | = new ST.SizePolicies.ExpandingPolicy(widget2, ST.VERTICAL); 156 | 157 | expect(widget2.sizeProxy.mask).to.be.null; 158 | }); 159 | 160 | it('should mask children if its layout has updateOnHostChanges = true', 161 | ()=>{ 162 | widget1.layout = new ST.Layouts.FixedLayout(widget1); 163 | expect(widget2.sizeProxy.mask).to.equal(widget1.clipGraphic); 164 | }); 165 | 166 | // it('should add its clipGraphic to each BaseWidget child addeds' 167 | // + ' size proxy', ()=>{ 168 | // expect(widget2.sizeProxy.mask).to.equal(widget1.clipGraphic); 169 | // }); 170 | }); 171 | 172 | describe('#addChildAt()', ()=>{ 173 | // same as #addChild 174 | }); 175 | 176 | describe('#onChildrenChange()', ()=>{ 177 | 178 | }); 179 | 180 | describe('#applyPosition()', ()=>{ 181 | 182 | }); 183 | 184 | describe('_updateClipGraphic()', ()=>{ 185 | it('should set to size of widget - padding', ()=>{ 186 | widget2.vPolicy 187 | = new ST.SizePolicies.FixedPolicy(widget2); 188 | widget2.max.width = 1000; 189 | widget2.max.height = 1000; 190 | widget2.width = 400; 191 | widget2.height = 400; 192 | widget1.update(); // should call _updateClipGraphic() 193 | expect(widget2.clipGraphic.width).to.equal(392); 194 | expect(widget2.clipGraphic.height).to.equal(392); 195 | }); 196 | 197 | it('should set the pos to the top left padding values', ()=>{ 198 | expect(widget2.clipGraphic.x).to.equal(4); 199 | expect(widget2.clipGraphic.y).to.equal(4); 200 | }); 201 | 202 | it('should set renderable to false', ()=>{ 203 | expect(widget2.clipGraphic.renderable).to.be.false; 204 | }); 205 | }); 206 | 207 | describe('#theme', ()=>{ 208 | let aThm = new ST.Theme(); 209 | it('should apply the set theme to children recursively', ()=>{ 210 | widget0.theme = aThm; 211 | expect(widget1.theme).to.equal(widget0.theme); 212 | expect(widget2.theme).to.equal(widget1.theme); 213 | }); 214 | }); 215 | 216 | describe('#disabled', ()=>{ 217 | it('should disable itself and its children when set to false', ()=>{ 218 | widget0.disabled = true; 219 | expect(widget0.disabled).to.be.true; 220 | expect(widget1.disabled).to.be.true; 221 | expect(widget2.disabled).to.be.true; 222 | }); 223 | 224 | it('should enable itself and its children when set to true', ()=>{ 225 | widget0.disabled = false; 226 | expect(widget0.disabled).to.be.false; 227 | expect(widget1.disabled).to.be.false; 228 | expect(widget2.disabled).to.be.false; 229 | }); 230 | }); 231 | 232 | describe('#_evaluateMask()', ()=>{ 233 | it('should mask all children if layout.updateOnHostChanges = true', 234 | ()=>{ 235 | widget0.layout = new ST.Layouts.FixedLayout(widget0); 236 | expect(widget1.mask).to.equal(widget0.clipGraphic); 237 | }); 238 | 239 | it('should mask child if either policy has updateOnHostChanges true' 240 | + ' and the parents layout has updateOnHostChanges false', ()=>{ 241 | widget0.layout = new ST.Layouts.VBoxLayout(widget0, ST.VERTICAL); 242 | widget1.hPolicy = new ST.SizePolicies.FixedPolicy(widget1); 243 | expect(widget1.mask).to.equal(widget0.clipGraphic); 244 | }); 245 | 246 | it('should set mask to null if layout has updateOnHostChanges false ' 247 | + 'and childs policies have updateOnHostChanges false', ()=>{ 248 | widget0.layout = new ST.Layouts.VBoxLayout(widget0, ST.VERTICAL); 249 | widget1.hPolicy 250 | = new ST.SizePolicies.ExpandingPolicy(widget1, ST.HORIZONTAL); 251 | widget1.vPolicy 252 | = new ST.SizePolicies.ExpandingPolicy(widget1, ST.VERTICAL); 253 | expect(widget1.mask).to.be.null; 254 | }); 255 | 256 | it('should set mask to null if child is not a widget', ()=>{ 257 | let pc = new PIXI.Container(); 258 | widget0.addChild(pc); 259 | widget0._evaluateMask(); 260 | expect(pc.mask).to.be.null; 261 | }); 262 | }); 263 | 264 | describe('#getFocusedWidget', ()=>{ 265 | it('should return the widget currently in focus', ()=>{ 266 | widget1.focus(); 267 | expect(ST.Widgets.BaseWidget.getFocusedWidget()).to.equal(widget1); 268 | }); 269 | }); 270 | }); 271 | -------------------------------------------------------------------------------- /test/spec/widgets/Button-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Button', ()=>{ 4 | 5 | }); 6 | -------------------------------------------------------------------------------- /test/spec/widgets/Image-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Image', ()=>{ 4 | let sprite = new PIXI.Sprite(); 5 | let texture = new PIXI.Texture(); 6 | let image = new ST.Widgets.Image(); 7 | 8 | describe('#sprite', ()=>{ 9 | it('should set the sprite for the image', ()=>{ 10 | image.sprite = sprite; 11 | expect(image._sprite).to.equal(sprite); 12 | 13 | // shouldnt set texture to sprite 14 | image.sprite = texture; 15 | expect(image._sprite).to.equal(sprite); 16 | }); 17 | }); 18 | 19 | describe('#texture', ()=>{ 20 | it('should set the sprites texture', ()=>{ 21 | image.texture = texture; 22 | expect(image._sprite.texture).to.equal(texture); 23 | 24 | // shouldnt set sprite for texture 25 | image.texture = sprite; 26 | expect(image._sprite.texture).to.equal(texture); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/spec/widgets/Label-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Label', ()=>{ 4 | let label = new ST.Widgets.Label(null, {text: 'test'}); 5 | 6 | it('should set text from option if set', ()=>{ 7 | expect(label.text).to.equal('test'); 8 | }); 9 | 10 | describe('#_textObj', ()=>{ 11 | it('should have a null mask', ()=>{ 12 | expect(label._textObj.mask).to.equal(null); 13 | }); 14 | }); 15 | 16 | describe('#_clipGraphic', ()=>{ 17 | it('should be unrenderable', ()=>{ 18 | expect(label._clipGraphic.renderable).to.equal(false); 19 | }); 20 | }); 21 | 22 | describe('#text', ()=>{ 23 | it('should update _textObjs text', ()=>{ 24 | label.text = 'update'; 25 | expect(label._textObj.text).to.equal('update'); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/spec/widgets/Panel-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Panel', ()=>{ 4 | let panel = new ST.Widgets.Panel(); 5 | 6 | describe('#_bkgObj', ()=>{ 7 | it('should be a sprite', ()=>{ 8 | expect(panel._bkgObj).to.be.an.instanceof(PIXI.Sprite); 9 | }); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test/spec/widgets/SITransform-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('SITransform', ()=>{ 4 | it('should allow a widget to transform its dimensions' 5 | + ' independent from its parent', ()=>{ 6 | let widget0 = new ST.Widgets.Panel(); 7 | widget0.width = 1000; 8 | let widget1 = new ST.Widgets.Panel(); 9 | widget1.width = 200; 10 | widget0.addChild(widget1); 11 | widget0.width = 500; 12 | expect(widget1.width).to.equal(200); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/spec/widgets/Slider-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // TODO: Would like to get more test here at some point 4 | 5 | describe('Slider', ()=>{ 6 | let sl = new ST.Widgets.Slider(null, {width: 200, height: 30}); 7 | sl.update(); 8 | 9 | describe('#track', ()=>{ 10 | it('should be an image widget', ()=>{ 11 | expect(sl.track).to.be.an.instanceof(ST.Widgets.Image); 12 | }); 13 | }); 14 | 15 | describe('#trackHitRect', ()=>{ 16 | it('should be a rectangle', ()=>{ 17 | expect(sl.trackHitRect).to.be.an.instanceof(PIXI.Rectangle); 18 | }); 19 | }); 20 | 21 | describe('#button', ()=>{ 22 | it('should be an image widget', ()=>{ 23 | expect(sl.button).to.be.an.instanceof(ST.Widgets.Image); 24 | }); 25 | }); 26 | 27 | describe('#updateTrackHitRect()', ()=>{ 28 | it('should set the size of the clickable area of the track', ()=>{ 29 | expect(sl.trackHitRect.x).to.equal(0); 30 | expect(sl.trackHitRect.y).to.equal(-5); 31 | expect(sl.trackHitRect.width).to.equal(200); 32 | expect(sl.trackHitRect.height).to.equal(sl.track.height + 10); 33 | }); 34 | }); 35 | 36 | describe('#value', ()=>{ 37 | it('should set the buttons pos when set', ()=>{ 38 | sl.value = 0.5; 39 | const actual = 90; 40 | expect(sl.button.x).to.equal(actual); 41 | }); 42 | 43 | it('should return the value from the position', ()=>{ 44 | const actual = 0.5; 45 | expect(sl.value).to.equal(actual); 46 | }); 47 | }); 48 | 49 | describe('#orientation', ()=>{ 50 | sl.orientation = ST.HORIZONTAL; 51 | 52 | it('should set the min and max heights when set', ()=>{ 53 | expect(sl.min.height).to.equal(sl.button.height); 54 | expect(sl.min.width).to.equal(sl.button.width*2); 55 | expect(sl.max.height).to.equal(sl.button.height); 56 | expect(sl.max.width).to.equal(10000); 57 | }); 58 | 59 | it('should set the track size', ()=>{ 60 | expect(sl.track.width).to.equal(sl.width); 61 | expect(sl.track.height).to.equal(5); 62 | }); 63 | 64 | it('should set alignments', ()=>{ 65 | expect(sl.layout.alignment.hAlign).to.equal(ST.Alignment.left); 66 | expect(sl.layout.alignment.vAlign).to.equal(ST.Alignment.middle); 67 | }); 68 | 69 | it('should set the tracks size policies', ()=>{ 70 | expect(sl.track.hPolicy).to.be.an 71 | .instanceof(ST.SizePolicies.ExpandingPolicy); 72 | 73 | expect(sl.track.vPolicy).to.be.an 74 | .instanceof(ST.SizePolicies.FixedPolicy); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/spec/widgets/StageWidget-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('StageWidget', ()=>{ 4 | it('Should have a bounds as big as the user defined size', ()=>{ 5 | let sw = new ST.Widgets.StageWidget(null, {width: 600, height: 800}); 6 | let b1 = new ST.Widgets.Button(sw, {width: 900, height: 900}); //eslint-disable-line 7 | let b2 = new ST.Widgets.Panel(sw, {width: 100, height: 100, x: 700}); // eslint-disable-line 8 | let bounds = sw.getBounds(); 9 | 10 | expect(bounds.width).to.equal(600); 11 | expect(bounds.height).to.equal(800); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/spec/widgets/TextButton-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('TextButton', ()=>{ 4 | describe('#Text', ()=>{ 5 | it('should set the text for the contained label', ()=>{ 6 | let tb = new ST.Widgets.TextButton(); 7 | tb.text = 'meow'; 8 | expect(tb.label.text).to.equal('meow'); 9 | }); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test/spec/widgets/index.js: -------------------------------------------------------------------------------- 1 | require('./BaseWidget-spec'); 2 | require('./Button-spec'); 3 | require('./Label-spec'); 4 | require('./Panel-spec'); 5 | require('./SITransform-spec'); 6 | require('./StageWidget-spec'); 7 | require('./TextButton-spec'); 8 | require('./Slider-spec'); 9 | --------------------------------------------------------------------------------