├── .editorconfig ├── .github └── workflows │ └── main.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── app.qml ├── build ├── compiler ├── __init__.py ├── doc │ ├── __init__.py │ └── json.py ├── gcc │ ├── COPYING │ ├── README.md │ └── compiler.jar ├── grammar.py ├── js │ ├── __init__.py │ ├── code.py │ ├── component.py │ └── generator.py ├── lang.py ├── manifest.py └── ts.py ├── core ├── .manifest ├── Anchors.qml ├── Animation.qml ├── BaseLayout.qml ├── BaseLayoutContentPadding.qml ├── BaseMixin.qml ├── BaseMouseMixin.qml ├── BaseView.qml ├── BaseViewContent.qml ├── Binding.qml ├── Border.qml ├── BorderSide.qml ├── ClickMixin.qml ├── ColorAnimation.qml ├── Column.qml ├── Component.qml ├── Connection.qml ├── ContentMargin.qml ├── Context.qml ├── ContextMenuMixin.qml ├── Device.qml ├── Effects.qml ├── EventEmitter.qml ├── Font.qml ├── Gamepad.qml ├── GamepadManager.qml ├── Gradient.qml ├── GradientStop.qml ├── Grid.qml ├── GridView.qml ├── HoverMixin.qml ├── Image.qml ├── Item.qml ├── Layout.qml ├── ListModel.qml ├── ListView.qml ├── Loader.qml ├── LocalStorage.qml ├── Location.qml ├── Model.qml ├── MouseArea.qml ├── MouseMoveMixin.qml ├── MousePressMixin.qml ├── Object.qml ├── PageStack.qml ├── PropertyStorage.qml ├── ProxyModel.qml ├── RAIIEventEmitter.qml ├── Radius.qml ├── Rectangle.qml ├── Repeater.qml ├── Request.qml ├── Row.qml ├── ScrollView.qml ├── SequentialAnimation.qml ├── Shadow.qml ├── System.qml ├── TableView.qml ├── Text.qml ├── Timer.qml ├── Transform.qml ├── VideoPlayer.qml ├── WheelMixin.qml ├── core.js ├── gradient.js ├── model.js └── transform.js ├── generate-gamepad-mappings ├── package-lock.json ├── package.json ├── partners.json ├── platform ├── android │ ├── .core.js │ ├── .manifest │ ├── build.py │ ├── device.js │ └── dist │ │ ├── androidIcon.png │ │ ├── config.xml │ │ └── index.html ├── commercial │ ├── .core.js │ ├── .manifest │ ├── PureQmlSplash.qml │ └── dist │ │ └── res │ │ ├── pureqml-splash-logo.png │ │ └── pureqml-splash-shadow.png ├── core │ └── .manifest ├── debug.console.re │ ├── .core.js │ ├── .manifest │ └── dist │ │ └── index.html ├── electronjs │ ├── .core.js │ ├── .manifest │ ├── device.js │ └── dist │ │ ├── main.js │ │ ├── package.json │ │ ├── preload.js │ │ └── renderer.js ├── html5.canvas │ ├── .core.js │ ├── .manifest │ └── backend.js ├── html5.fingerprint │ ├── .manifest │ ├── fingerprint.js │ └── sha1.js ├── html5 │ ├── .core.js │ ├── .manifest │ ├── Stylesheet.qml │ ├── cache.js │ ├── dist │ │ ├── index.html │ │ └── modernizr-custom.js │ ├── html.js │ ├── localstorage.js │ └── location.js ├── ios │ ├── .core.js │ ├── .manifest │ ├── build.py │ ├── device.js │ ├── dist │ │ ├── config.xml │ │ ├── icons │ │ │ ├── icon-1024.png │ │ │ ├── icon-20.png │ │ │ ├── icon-20@2x.png │ │ │ ├── icon-20@3x.png │ │ │ ├── icon-24@2x.png │ │ │ ├── icon-27.5@2x.png │ │ │ ├── icon-29.png │ │ │ ├── icon-29@2x.png │ │ │ ├── icon-29@3x.png │ │ │ ├── icon-40.png │ │ │ ├── icon-40@2x.png │ │ │ ├── icon-44@2x.png │ │ │ ├── icon-50.png │ │ │ ├── icon-50@2x.png │ │ │ ├── icon-60@2x.png │ │ │ ├── icon-60@3x.png │ │ │ ├── icon-72.png │ │ │ ├── icon-72@2x.png │ │ │ ├── icon-76.png │ │ │ ├── icon-76@2x.png │ │ │ ├── icon-83.5@2x.png │ │ │ ├── icon-86@2x.png │ │ │ ├── icon-98@2x.png │ │ │ ├── icon.png │ │ │ ├── icon@2x.png │ │ │ └── splash │ │ │ │ ├── Default@2x~ipad~anyany.png │ │ │ │ ├── Default@2x~ipad~comany.png │ │ │ │ ├── Default@2x~iphone~anyany.png │ │ │ │ ├── Default@2x~iphone~comany.png │ │ │ │ ├── Default@2x~iphone~comcom.png │ │ │ │ ├── Default@3x~iphone~anyany.png │ │ │ │ ├── Default@3x~iphone~anycom.png │ │ │ │ └── Default@3x~iphone~comany.png │ │ └── index.html │ └── video.js ├── loggerpanel │ ├── .core.js │ └── .manifest ├── pure.blessed │ ├── .core.js │ ├── .manifest │ └── backend.js ├── pure.femto │ ├── .core.js │ ├── .manifest │ ├── backend.js │ ├── build-android-native.sh │ ├── device.js │ ├── location.js │ ├── storage.js │ └── video.js ├── pure.void │ ├── .core.js │ ├── .manifest │ └── backend.js ├── pure │ ├── .core.js │ ├── .manifest │ ├── Stylesheet.qml │ └── runtime.js ├── video.dashjs │ ├── .core.js │ ├── .manifest │ ├── backend.js │ └── dist │ │ ├── dash.all.min.js │ │ └── index.html ├── video.html5 │ ├── .core.js │ ├── .manifest │ └── backend.js ├── video.jsmpeg │ ├── .core.js │ ├── .manifest │ ├── backend.js │ └── dist │ │ ├── index.html │ │ ├── jsmpeg.min.js │ │ └── m3u8-parser.min.js ├── video.shaka │ ├── .core.js │ ├── .manifest │ ├── backend.js │ └── dist │ │ ├── index.html │ │ └── shaka-player.compiled.min.js ├── video.videojs │ ├── .core.js │ ├── .manifest │ ├── backend.js │ └── dist │ │ ├── index.html │ │ └── video.min.js ├── web.pwa │ ├── .core.js │ ├── .manifest │ └── dist │ │ └── sw.js ├── web │ ├── .core.js │ ├── .manifest │ └── device.js └── webextension │ ├── .manifest │ └── dist │ ├── icon128.png │ ├── icon16.png │ ├── icon32.png │ ├── icon48.png │ ├── index.html │ └── manifest.json ├── requirements.txt ├── test ├── Makefile ├── lit.cfg.py ├── lit.py ├── lit │ ├── BooleanExpression.py │ ├── LitConfig.py │ ├── LitTestCase.py │ ├── ProgressBar.py │ ├── ShCommands.py │ ├── ShUtil.py │ ├── Test.py │ ├── TestRunner.py │ ├── TestingConfig.py │ ├── __init__.py │ ├── builtin_commands │ │ ├── __init__.py │ │ ├── cat.py │ │ └── diff.py │ ├── cl_arguments.py │ ├── discovery.py │ ├── display.py │ ├── formats │ │ ├── __init__.py │ │ ├── base.py │ │ ├── googletest.py │ │ └── shtest.py │ ├── llvm │ │ ├── __init__.py │ │ ├── config.py │ │ └── subst.py │ ├── main.py │ ├── reports.py │ ├── run.py │ ├── util.py │ └── worker.py ├── model.js ├── qml │ ├── enum_and_ids_in_string_context.qml │ ├── expr.qml │ ├── float_parse.qml │ ├── gh172-a.qml │ ├── gh172-b.qml │ ├── gh205.qml │ ├── model_row.qml │ └── underscore_in_property_type.qml ├── update.js └── view.js └── update-ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = false 5 | 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | charset = utf-8 10 | indent_style = tab 11 | indent_size = 4 12 | trim_trailing_whitespace = true 13 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the workflow will run 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v4 27 | 28 | # Runs a single command using the runners shell 29 | - name: Run lit tests 30 | run: | 31 | pip install -r requirements.txt 32 | make -C test 33 | 34 | - name: Run mocha tests 35 | run: | 36 | npm install 37 | npm test 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.pyc 4 | *.pyo 5 | app/qml.js 6 | app/qml.min.js 7 | .cache/* 8 | node_modules 9 | .idea/ 10 | test/qml/Output 11 | test/build.* 12 | test/.cache 13 | 14 | -------------------------------------------------------------------------------- /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 contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at team@pureqml.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-2020 PureQML Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app.qml: -------------------------------------------------------------------------------- 1 | Text { 2 | anchors.fill: context; 3 | 4 | text: 'Hello, world!'; 5 | font.pixelSize: 32; 6 | verticalAlignment: Text.AlignVCenter; 7 | horizontalAlignment: Text.AlignHCenter; 8 | } 9 | -------------------------------------------------------------------------------- /compiler/doc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/compiler/doc/__init__.py -------------------------------------------------------------------------------- /compiler/gcc/compiler.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/compiler/gcc/compiler.jar -------------------------------------------------------------------------------- /compiler/js/__init__.py: -------------------------------------------------------------------------------- 1 | from builtins import map 2 | 3 | import re 4 | 5 | def split_name(name): 6 | dot = name.rfind('.') 7 | if dot >= 0: 8 | return name[:dot], name[dot + 1:] 9 | else: 10 | return '', name 11 | 12 | def get_package(name): 13 | return split_name(name)[0] 14 | 15 | _escape_re = re.compile(r'\W') 16 | def escape(name): 17 | return _escape_re.sub('_', name) 18 | 19 | _id_re = re.compile(r'[^a-zA-Z0-9_]') 20 | 21 | def escape_id(name): 22 | return _id_re.sub('_', name) 23 | 24 | def escape_package(name): 25 | package = name.split('.') 26 | return ".".join(map(escape_id, package)) 27 | 28 | def mangle_package(name): 29 | package = name.split('.') 30 | package = list(map(escape_id, package)) 31 | if package[0] == '_globals': 32 | package.pop(0) 33 | package = [''] + package 34 | return "$".join(package) 35 | 36 | class Error(Exception): 37 | def __init__(self, message, loc): 38 | if loc: 39 | super(Error, self).__init__("{}: {}".format(loc, message)) 40 | else: 41 | super(Error, self).__init__(message) 42 | 43 | from compiler.js.component import component_generator 44 | from compiler.js.generator import generator 45 | -------------------------------------------------------------------------------- /compiler/manifest.py: -------------------------------------------------------------------------------- 1 | from builtins import object 2 | 3 | import json 4 | 5 | def _get_property(obj, name, default = None): 6 | if '.' in name: 7 | path = name.split('.') 8 | for k in path[:-1]: 9 | obj = obj.get(k, {}) 10 | return obj.get(path[-1], default) 11 | else: 12 | return obj.get(name, default) 13 | 14 | def _set_property(obj, name, value): 15 | if '.' in name: 16 | path = name.split('.') 17 | current = obj 18 | for p in path[:-1]: 19 | current = current.setdefault(p, {}) 20 | current[path[-1]] = value 21 | else: 22 | obj[name] = value 23 | 24 | def _pair_hook(pairs): 25 | obj = {} 26 | for k, v in pairs: 27 | _set_property(obj, k, v) 28 | return obj 29 | 30 | def merge_properties(dst, src): 31 | src = _pair_hook(list(src.items())) 32 | for key, value in src.items(): 33 | if key.find('.') >= 0: 34 | raise Exception('dot found in key') 35 | if isinstance(value, dict): 36 | node = dst.setdefault(key, {}) 37 | merge_properties(node, value) 38 | else: 39 | dst[key] = value 40 | 41 | return dst 42 | 43 | class Manifest(object): 44 | def __init__(self, data = None): 45 | if data is None: 46 | data = {} 47 | self.data = data 48 | 49 | @property 50 | def source_dir(self): 51 | return self.data.get('sources', 'src') 52 | 53 | @property 54 | def apps(self): 55 | return self.data.get('apps', []) 56 | 57 | @property 58 | def web_prefix(self): 59 | return self.data.get('web-prefix', '') 60 | 61 | @property 62 | def strict(self): 63 | return self.data.get('strict', True) 64 | 65 | @property 66 | def standalone(self): 67 | return self.data.get('standalone', True) 68 | 69 | @property 70 | def requires(self): 71 | return self.data.get('requires', []) 72 | 73 | @property 74 | def use_only_for(self): 75 | return self.data.get('use-only-for', []) 76 | 77 | def platform_requires(self, platform): 78 | return _get_property(self.data, 'platform.%s.requires' %platform, []) 79 | 80 | @property 81 | def minify(self): 82 | return self.data.get('minify', False) 83 | 84 | @property 85 | def templater(self): 86 | return self.data.get('templater', 'simple') 87 | 88 | @property 89 | def languages(self): 90 | return self.data.get('languages', []) 91 | 92 | @property 93 | def platforms(self): 94 | return self.data.get('platforms', []) 95 | 96 | @property 97 | def package(self): 98 | return self.data.get('package', '') 99 | 100 | @property 101 | def public(self): 102 | return self.data.get('public', False) 103 | 104 | @property 105 | def templates(self): 106 | return self.data.get('templates', ['*.html']) 107 | 108 | @property 109 | def properties(self): 110 | return self.data.setdefault('properties', {}) 111 | 112 | def set_property(self, name, value): 113 | return _set_property(self.properties, name, value) 114 | 115 | @property 116 | def partner(self): 117 | return self.data.get('partner', 'free') 118 | 119 | @property 120 | def export_module(self): 121 | return self.data.get('export_module', False) 122 | 123 | def load(f): 124 | return Manifest(json.load(f, object_pairs_hook = _pair_hook)) 125 | 126 | def loads(s): 127 | return Manifest(json.loads(s, object_pairs_hook = _pair_hook)) 128 | -------------------------------------------------------------------------------- /core/.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "package": "core", 3 | "properties": { 4 | "resolutionWidth": 0, 5 | "resolutionHeight": 0, 6 | "useNativeFocusForInput": true, 7 | "emulateRemoteKeys": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /core/Animation.qml: -------------------------------------------------------------------------------- 1 | /** 2 | controls property animation behavior in declarative way 3 | Animation class works only for integral types, please use ColorAnimation for animating color properties 4 | */ 5 | Object { 6 | property int delay: 0; ///< delay in ms 7 | property int duration: 200; ///< duration in ms 8 | property bool cssTransition: true; ///< use css transition if possible 9 | property bool running: false; ///< currently running 10 | property string easing: "ease"; ///< easing function 11 | property Object target; ///< target object 12 | property string property; ///< target object property 13 | property variant from; ///< used in declarative animation inside SequentialAnimation, starting value 14 | property variant to; ///< used in declarative animation inside SequentialAnimation, destination value 15 | 16 | constructor: { this._disabled = 0; this._native = false } 17 | /// disable animation. 18 | disable: { ++this._disabled; this._updateAnimation() } 19 | /// enable animation. 20 | enable: { --this._disabled; this._updateAnimation() } 21 | /// returns true if animation is enabled 22 | enabled: { return this._disabled === 0 } 23 | 24 | /// @private 25 | onDelayChanged, 26 | onDurationChanged, 27 | onCssTransitionChanged, 28 | onRunningChanged, 29 | onEasingChanged: { this._updateAnimation() } 30 | 31 | function active() { 32 | return this.enabled() && this.duration > 0 33 | } 34 | 35 | function _updateAnimation() { 36 | if (this.target) 37 | this.target.updateAnimation(this.property, this) 38 | } 39 | 40 | /// @private 41 | function interpolate(dst, src, t) { 42 | return t * (dst - src) + src; 43 | } 44 | 45 | /// @private 46 | function complete() { } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /core/BaseLayout.qml: -------------------------------------------------------------------------------- 1 | /// base class for BaseView and Layout 2 | Item { 3 | property int count; ///< number of children elements 4 | property bool trace; ///< output debug info in logs: layouts, item positioning 5 | 6 | property int spacing; ///< spacing between adjanced items, pixels 7 | property int currentIndex; ///< index of current focused item 8 | property int contentWidth; ///< content width 9 | property int contentHeight; ///< content height 10 | property bool keyNavigationWraps; ///< key navigation wraps from first to last and vise versa 11 | property bool handleNavigationKeys; ///< handle navigation keys, move focus 12 | property int layoutDelay: -1; ///< <0 - end of the tick (default), 0 - request animation frame, >0 - delay in ms 13 | property int prerenderDelay: -1; ///< <0 - end of the tick (default), 0 - request animation frame, >0 - delay in ms 14 | property bool offlineLayout; ///< layout delegates even if view's invisible 15 | property lazy padding: BaseLayoutContentPadding { } //< padding for content 16 | 17 | ///@private 18 | constructor: { 19 | this._skipPositioning = false 20 | this._padding = {} 21 | } 22 | 23 | /// @private 24 | function _attach() { } 25 | 26 | ///@private 27 | function _scheduleLayout(skipPositioning) { 28 | if (!this.recursiveVisible && !this.offlineLayout) 29 | return 30 | 31 | if (skipPositioning) 32 | this._skipPositioning = true 33 | 34 | if (this.prerenderDelay >= 0 && this.layoutDelay >= 0 && this.layoutDelay < this.prerenderDelay) { 35 | this._context.delayedAction('layout', this, this._doLayoutNP, this.layoutDelay) 36 | this._context.delayedAction('prerender', this, this._doLayout, this.prerenderDelay) 37 | } else 38 | this._context.delayedAction('layout', this, this._doLayout, this.layoutDelay) 39 | } 40 | 41 | ///@private 42 | function _doLayout() { 43 | this._attach() 44 | this._processUpdates() 45 | this._layout() 46 | this._skipPositioning = false 47 | } 48 | 49 | ///@private 50 | function _doLayoutNP() { 51 | this._attach() 52 | this._processUpdates() 53 | this._layout(true) 54 | this._skipPositioning = false 55 | } 56 | 57 | ///@private 58 | function _processUpdates() { } 59 | 60 | onSpacingChanged, 61 | onRecursiveVisibleChanged: { 62 | this._scheduleLayout() 63 | } 64 | 65 | ///@private 66 | onCompleted: { this._scheduleLayout() } 67 | } 68 | -------------------------------------------------------------------------------- /core/BaseLayoutContentPadding.qml: -------------------------------------------------------------------------------- 1 | /// class controlling internal paddings of BaseLayout content 2 | Object { 3 | property int top: all; ///< top padding 4 | property int left: all; ///< left padding 5 | property int right: all; ///< right padding 6 | property int bottom: all; ///< bottom padding 7 | property int all; ///< a value for all sides 8 | 9 | prototypeConstructor: { 10 | BaseLayoutContentPaddingPrototype.defaultProperty = 'all' 11 | } 12 | 13 | constructor: { 14 | this.parent._padding = this 15 | } 16 | 17 | onTopChanged, 18 | onLeftChanged, 19 | onRightChanged, 20 | onBottomChanged: { this.parent._scheduleLayout(); } 21 | } 22 | -------------------------------------------------------------------------------- /core/BaseMixin.qml: -------------------------------------------------------------------------------- 1 | Object { 2 | property bool enabled: true; ///< enable/disable mixin 3 | 4 | constructor: { 5 | this.element = this.parent.element 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /core/BaseMouseMixin.qml: -------------------------------------------------------------------------------- 1 | BaseMixin { 2 | property string cursor; ///< mouse cursor 3 | 4 | ///@private 5 | constructor: { 6 | this._touchEvent = false 7 | if (this.cursor) 8 | this.parent.style('cursor', this.cursor) 9 | } 10 | 11 | function _setTouchEvent() { 12 | this._touchEvent = true 13 | } 14 | 15 | function _resetTouchEvent() { 16 | this._touchEvent = false 17 | } 18 | 19 | /// @private pops touch event flag to skip mouse over later 20 | function _trueUnlessTouchEvent() { 21 | return !this._touchEvent 22 | } 23 | 24 | onCursorChanged: { 25 | this.parent.style('cursor', value) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/BaseViewContent.qml: -------------------------------------------------------------------------------- 1 | /// @private content for base view scrolling area 2 | Item { 3 | onXChanged: { this.parent._scheduleLayout() } //fixme: if you need sync _layout here, please note that discarding delegate can result in recursive createDelegate() call from _layout, do not change it without fixing that first. 4 | onYChanged: { this.parent._scheduleLayout() } 5 | 6 | constructor: { 7 | this.style('will-change', 'scroll-position, transform, left, top') 8 | } 9 | 10 | ///@private silently updates scroll positions, because browser animates scroll 11 | function _updateScrollPositions(x, y, layout) { 12 | this._setProperty('x', -x) 13 | this._setProperty('y', -y) 14 | if (layout === undefined || layout) //default true 15 | this.parent._scheduleLayout(true) //schedule layout but skip positioning 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/Binding.qml: -------------------------------------------------------------------------------- 1 | Object { 2 | property bool delayed; ///< delays update to the next tick 3 | property string property; ///< target property 4 | property Object target; ///< target object 5 | property bool when: true; ///< assign value to target when this condition met 6 | property var value; ///< any value 7 | 8 | function _updateTarget() { 9 | if (this.delayed) 10 | this._context.delayedAction("binding:update", this, this._updateTargetImpl) 11 | else 12 | this._updateTargetImpl() 13 | } 14 | 15 | function _updateTargetImpl() { 16 | $core.assign(this.target, this.property, this.value) 17 | } 18 | 19 | onTargetChanged, onValueChanged, onWhenChanged: { 20 | if (this.target && this.when && this.property) 21 | this._updateTarget() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/Border.qml: -------------------------------------------------------------------------------- 1 | /// class controlling border rendering 2 | Object { 3 | property int width; ///< width of the border 4 | property color color: "black"; ///< color of the border 5 | property enum style { None, Hidden, Dotted, Dashed, Solid, Double, Groove, Ridge, Inset, Outset }: Solid; ///< style of the border 6 | property enum type { Inner, Outer, Center }; ///< whether box is inside bounding rect or not 7 | 8 | property lazy left: BorderSide { name: "left"; } ///< left border side 9 | property lazy right: BorderSide { name: "right"; } ///< right border side 10 | property lazy top: BorderSide { name: "top"; } ///< top border side 11 | property lazy bottom: BorderSide { name: "bottom"; } ///< bottom border side 12 | 13 | function _update() { 14 | var parent = this.parent 15 | var value = this.width 16 | parent.style('border-width', value) 17 | switch(this.type) { 18 | case this.Inner: 19 | parent._borderXAdjust = 0 20 | parent._borderYAdjust = 0 21 | parent._borderInnerWidthAdjust = -2 * value 22 | parent._borderInnerHeightAdjust = -2 * value 23 | parent._setSizeAdjust() 24 | break 25 | case this.Outer: 26 | parent._borderXAdjust = -value 27 | parent._borderYAdjust = -value 28 | parent._borderWidthAdjust = 0 29 | parent._borderHeightAdjust = 0 30 | parent._setSizeAdjust() 31 | break 32 | case this.Center: 33 | parent._borderXAdjust = -value / 2 34 | parent._borderYAdjust = -value / 2 35 | parent._borderWidthAdjust = -value 36 | parent._borderHeightAdjust = -value 37 | parent._setSizeAdjust() 38 | break 39 | } 40 | } 41 | 42 | onWidthChanged: { 43 | this._update() 44 | } 45 | 46 | onTypeChanged: { 47 | var style 48 | switch(value) { 49 | case this.Inner: 50 | style = 'border-box'; break; 51 | case this.Outer: 52 | case this.Center: 53 | style = 'content-box'; break; 54 | } 55 | this.parent.style('box-sizing', style) 56 | this._update() 57 | } 58 | 59 | onColorChanged: { 60 | var newColor = $core.Color.normalize(this.color) 61 | this.parent.style('border-color', newColor) 62 | } 63 | 64 | onStyleChanged: { 65 | var styleName 66 | switch(value) { 67 | case this.None: styleName = 'none'; break 68 | case this.Hidden: styleName = 'hidden'; break 69 | case this.Dotted: styleName = 'dotted'; break 70 | case this.Dashed: styleName = 'dashed'; break 71 | case this.Solid: styleName = 'solid'; break 72 | case this.Double: styleName = 'double'; break 73 | case this.Groove: styleName = 'groove'; break 74 | case this.Ridge: styleName = 'ridge'; break 75 | case this.Inset: styleName = 'inset'; break 76 | case this.Outset: styleName = 'outset'; break 77 | } 78 | 79 | this.parent.style('border-style', styleName) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /core/BorderSide.qml: -------------------------------------------------------------------------------- 1 | /** 2 | @private 3 | Component aimed to adjust individual preferences of each border side 4 | */ 5 | 6 | Object { 7 | // property enum type { Inner, Outer, Center }; ///< whether box is inside bounding rect or not 8 | property string name; 9 | property int width: parent.width; 10 | property color color: parent.color; 11 | property int style: parent.style; 12 | property int type: parent.type; 13 | 14 | ///@private 15 | function _updateStyle() { 16 | if (!this.parent || !this.parent.parent || !this.name) 17 | return 18 | 19 | var Border = $core.Border 20 | var styleName 21 | switch(this.style) { 22 | case Border.None: styleName = 'none'; break 23 | case Border.Hidden: styleName = 'hidden'; break 24 | case Border.Dotted: styleName = 'dotted'; break 25 | case Border.Dashed: styleName = 'dashed'; break 26 | case Border.Solid: styleName = 'solid'; break 27 | case Border.Double: styleName = 'double'; break 28 | case Border.Groove: styleName = 'groove'; break 29 | case Border.Ridge: styleName = 'ridge'; break 30 | case Border.Inset: styleName = 'inset'; break 31 | case Border.Outset: styleName = 'outset'; break 32 | } 33 | 34 | var borderCss = this.width + "px " + styleName + " " + $core.Color.normalize(this.color) 35 | this.parent.parent.style('border-' + this.name, borderCss) 36 | } 37 | 38 | ///@private 39 | onWidthChanged, 40 | onColorChanged, 41 | onStyleChanged: { this._updateStyle() } 42 | } 43 | -------------------------------------------------------------------------------- /core/ClickMixin.qml: -------------------------------------------------------------------------------- 1 | /// This mixin provides mouse click event detecting 2 | BaseMouseMixin { 3 | ///@private 4 | constructor: { 5 | this._bindClick(this.enabled) 6 | } 7 | 8 | ///@private 9 | function _bindClick(value) { 10 | if (value && !this._cmClickBinder) { 11 | this._cmClickBinder = new $core.EventBinder(this.element) 12 | this._cmClickBinder.on('click', $core.createSignalForwarder(this.parent, 'clicked').bind(this)) 13 | } 14 | if (this._cmClickBinder) 15 | this._cmClickBinder.enable(value) 16 | } 17 | 18 | ///@private 19 | onEnabledChanged: { this._bindClick(value) } 20 | } 21 | -------------------------------------------------------------------------------- /core/ColorAnimation.qml: -------------------------------------------------------------------------------- 1 | ///Animation for color-typed properties 2 | Animation { 3 | ///@private 4 | function interpolate(dst, src, t) { 5 | return $core.Color.interpolate(dst, src, t) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /core/Column.qml: -------------------------------------------------------------------------------- 1 | /// Layout for vertical oriented content 2 | Layout { 3 | ///@private 4 | onKeyPressed: { 5 | if (!this.handleNavigationKeys) 6 | return false; 7 | 8 | switch (key) { 9 | case 'Up': return this.focusPrevChild() 10 | case 'Down': return this.focusNextChild() 11 | } 12 | } 13 | 14 | ///@private 15 | function _layout() { 16 | if (!this.recursiveVisible && !this.offlineLayout) 17 | return 18 | 19 | var children = this.children; 20 | var p = 0 21 | var w = 0 22 | this.count = children.length 23 | 24 | for(var i = 0; i < children.length; ++i) { 25 | var c = children[i] 26 | if (!('height' in c)) 27 | continue 28 | 29 | var tm = c.anchors.topMargin || c.anchors.margins 30 | var bm = c.anchors.bottomMargin || c.anchors.margins 31 | 32 | var r = c.x + c.width 33 | if (r > w) 34 | w = r 35 | c.viewY = p + tm 36 | if (c.visible && c.height > 0) 37 | p += c.height + tm + bm + this.spacing 38 | } 39 | if (p > 0) 40 | p -= this.spacing 41 | this.contentWidth = w 42 | this.contentHeight = p 43 | } 44 | 45 | ///@private 46 | function addChild(child) { 47 | $core.Item.prototype.addChild.apply(this, arguments) 48 | 49 | if (!('height' in child)) 50 | return 51 | 52 | var update = this._scheduleLayout.bind(this) 53 | child.onChanged('height', update) 54 | child.onChanged('recursiveVisible', update) 55 | child.on('anchorsMarginsUpdated', update) 56 | this._scheduleLayout() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /core/Component.qml: -------------------------------------------------------------------------------- 1 | /// Semi-stub to allow loading of component via Loader.sourceComponent 2 | Object { 3 | property Item delegate; ///< delegate - template object 4 | } 5 | -------------------------------------------------------------------------------- /core/Connection.qml: -------------------------------------------------------------------------------- 1 | /// Describes generalized connections to signals. However it's preferrable to use extended syntax, e.g. `myButton.onClicked: { }` instead of dynamic Connection. This class is useful if you want to switch your targets on the fly 2 | Object { 3 | property Object target; ///< target object to connect to 4 | 5 | constructor: { 6 | this._declaredOnConnections = [] 7 | this._declaredOnChangedConnections = [] 8 | } 9 | 10 | /// @private 11 | function _disconnect() { 12 | this.removeAllOn() 13 | this.removeAllOnChanged() 14 | } 15 | 16 | /// @private 17 | function _reconnect() { 18 | this._disconnect() 19 | 20 | var target = this.target 21 | if (!target) 22 | return 23 | 24 | if (!(target instanceof $core.CoreObject)) 25 | throw new Error("You can only assign qml objects to target, got: " + target + " of type " + typeof target + " to " + this.getComponentPath()) 26 | 27 | //reconnect onTargetChanged 28 | this.connectOnChanged(this, 'target', this._scheduleReconnect.bind(this)) //restore target connection 29 | 30 | 31 | var ons = this._declaredOnConnections 32 | for(var i = 0, n = ons.length; i < n; i += 2) { 33 | this.connectOn(this.target, ons[i], ons[i + 1]) 34 | } 35 | ons = this._declaredOnChangedConnections 36 | for(var i = 0, n = ons.length; i < n; i += 2) { 37 | this.connectOnChanged(this.target, ons[i], ons[i + 1]) 38 | } 39 | } 40 | 41 | /// @private 42 | function _scheduleReconnect() { 43 | this._context.delayedAction('reconnect', this, this._reconnect) 44 | } 45 | 46 | /// @private 47 | function on (name, callback) { 48 | if (name === 'target') 49 | return 50 | 51 | if (name === '') 52 | throw new Error('empty listener name') 53 | 54 | this._declaredOnConnections.push(name, callback) 55 | if (this.target) { 56 | this.connectOn(this.target, name, callback) 57 | } 58 | } 59 | 60 | /// @private 61 | function onChanged (name, callback) { 62 | if (name === 'target') 63 | return 64 | 65 | if (name === '') 66 | throw new Error('empty listener name') 67 | 68 | this._declaredOnChangedConnections.push(name, callback) 69 | if (this.target) 70 | this.connectOnChanged(this.target, name, callback) 71 | } 72 | 73 | onTargetChanged, 74 | onCompleted: 75 | { this._scheduleReconnect(); } 76 | } 77 | -------------------------------------------------------------------------------- /core/ContentMargin.qml: -------------------------------------------------------------------------------- 1 | Object { 2 | property int top; 3 | property int left; 4 | property int right; 5 | property int bottom; 6 | } 7 | -------------------------------------------------------------------------------- /core/ContextMenuMixin.qml: -------------------------------------------------------------------------------- 1 | /// This mixin provides mouse click event detecting 2 | BaseMixin { 3 | ///@private 4 | constructor: { 5 | this._bindClick(this.enabled) 6 | } 7 | 8 | ///@private 9 | function _bindClick(value) { 10 | if (value && !this._cmClickBinder) { 11 | this._cmClickBinder = new $core.EventBinder(this.element) 12 | this._cmClickBinder.on('contextmenu', $core.createSignalForwarder(this.parent, 'contextMenu').bind(this)) 13 | } 14 | if (this._cmClickBinder) 15 | this._cmClickBinder.enable(value) 16 | } 17 | 18 | ///@private 19 | onEnabledChanged: { this._bindClick(value) } 20 | } 21 | -------------------------------------------------------------------------------- /core/Device.qml: -------------------------------------------------------------------------------- 1 | /// Object which contains device specific information like model name. MAC address etc. 2 | Object { 3 | signal propertyUpdated; ///< this signal is emited every time when one of the property was changed 4 | property string macAddress; ///< device MAC address if its available for current platform 5 | property string modelName; ///< device model name 6 | property string deviceId; ///< unique device id or random generated id if its not supported 7 | property string firmware; ///< firmware version 8 | property string language; ///< device langgcode 9 | property string country; ///< device country code 10 | property string sdk; ///< SDK version 11 | property string ip; ///< device IP address if its available 12 | property string runtime; ///< pureqml specific runtime provider, cordova/native 13 | 14 | property bool standByMode; ///< Stand by mode flag (if its supported) 15 | property bool supportingUhd; ///< UHD (4K) supporting flag 16 | property bool supportingHdr; ///< HDR supporting flag 17 | property bool supporting3d; ///< 3D Video supporting flag 18 | 19 | constructor: { 20 | var backend = $core.__deviceBackend 21 | if (!backend) 22 | throw new Error('no backend found') 23 | 24 | this.impl = backend().createDevice(this) 25 | } 26 | 27 | onSdkChanged, 28 | onDeviceIdChanged, 29 | onFirmwareChanged, 30 | onMacAddessChanged, 31 | onModelNameChanged, 32 | onSupporting3dChanged, 33 | onSupportingUhdChanged: { this.propertyUpdated() } 34 | 35 | onStandByModeChanged: { 36 | if (!this.impl) { 37 | throw new Error('toggleStandByMode: no backend found') 38 | return 39 | } 40 | 41 | this.impl.toggleStandByMode() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /core/Effects.qml: -------------------------------------------------------------------------------- 1 | ///provides various visual effects 2 | Object { 3 | property real blur; ///< applies a blur effect to the image 4 | property real grayscale; ///< converts the image to grayscale 5 | property real sepia; ///< converts the image to sepia 6 | property real brightness; ///< adjusts the brightness of the image 7 | property real contrast; ///< adjusts the contrast of the image 8 | property real hueRotate; ///< applies a hue rotation on the image. The value defines the number of degrees around the color circle the image samples will be adjusted 9 | property real invert; ///< inverts the samples in the image 10 | property real saturate; ///< saturates the image 11 | property lazy shadow : Shadow { } ///< object property for the shadow adjusting 12 | 13 | /// @private 14 | function _addStyle(array, property, style, units) { 15 | var value = this[property] 16 | if (value) 17 | array.push((style || property) + '(' + value + (units || '') + ') ') 18 | } 19 | 20 | /// @private 21 | function _getFilterStyle() { 22 | var style = [] 23 | this._addStyle(style, 'blur', 'blur', 'px') 24 | this._addStyle(style, 'grayscale') 25 | this._addStyle(style, 'sepia') 26 | this._addStyle(style, 'brightness') 27 | this._addStyle(style, 'contrast') 28 | this._addStyle(style, 'hueRotate', 'hue-rotate', 'deg') 29 | this._addStyle(style, 'invert') 30 | this._addStyle(style, 'saturate') 31 | return style 32 | } 33 | 34 | /// @private 35 | function _updateStyle(updateShadow) { 36 | var filterStyle = this._getFilterStyle().join('') 37 | var parent = this.parent 38 | var style = {} 39 | 40 | //chromium bug 41 | //https://github.com/Modernizr/Modernizr/issues/981 42 | style['-webkit-filter'] = filterStyle 43 | style['filter'] = filterStyle 44 | 45 | if (this.shadow && (!this.shadow._empty() || updateShadow)) 46 | style['box-shadow'] = this.shadow._getFilterStyle() 47 | 48 | parent.style(style) 49 | } 50 | 51 | onBlurChanged, onGrayscaleChanged, 52 | onSepiaChanged, onBrightnessChanged, 53 | onContrastChanged, onHueRotateChanged, 54 | onInvertChanged, onSaturateChanged: { this._updateStyle() } 55 | } 56 | -------------------------------------------------------------------------------- /core/EventEmitter.qml: -------------------------------------------------------------------------------- 1 | ///@private 2 | CoreObject { 3 | constructor: { 4 | this._eventHandlers = {} 5 | this._onConnections = [] 6 | } 7 | 8 | /// @private removes all on(signal) connections 9 | function removeAllOn() { 10 | var connections = this._onConnections 11 | for(var i = 0, n = connections.length; i < n; i += 3) 12 | connections[i].removeListener(connections[i + 1], connections[i + 2]) 13 | this._onConnections = [] 14 | } 15 | 16 | function discard() { 17 | this.removeAllOn() 18 | for(var name in this._eventHandlers) 19 | this.removeAllListeners(name) 20 | } 21 | 22 | function on (name, callback) { 23 | if (name === '') 24 | throw new Error('empty listener name') 25 | 26 | var storage = this._eventHandlers 27 | var handlers = storage[name] 28 | if (handlers !== undefined) 29 | handlers.push(callback) 30 | else { 31 | storage[name] = [callback] 32 | } 33 | } 34 | 35 | function connectOn(target, name, callback) { 36 | target.on(name, callback) 37 | this._onConnections.push(target, name, callback) 38 | } 39 | 40 | function emit (name) { 41 | if (name === '') 42 | throw new Error('empty listener name') 43 | 44 | var proto_callback = this['__on__' + name] 45 | var handlers = this._eventHandlers[name] 46 | 47 | if (proto_callback === undefined && handlers === undefined) 48 | return 49 | 50 | COPY_ARGS(args, 1) 51 | 52 | var invoker = $core.safeCall( 53 | this, args, 54 | function(ex) { log("event/signal " + name + " handler failed:", ex, ex.stack) } 55 | ) 56 | 57 | if (proto_callback !== undefined) 58 | proto_callback.forEach(invoker) 59 | 60 | if (handlers !== undefined) 61 | handlers.forEach(invoker) 62 | } 63 | 64 | function emitWithArgs (name, args) { 65 | if (name === '') 66 | throw new Error('empty listener name') 67 | 68 | var proto_callback = this['__on__' + name] 69 | var handlers = this._eventHandlers[name] 70 | 71 | if (proto_callback === undefined && handlers === undefined) 72 | return 73 | 74 | var invoker = $core.safeCall( 75 | this, args, 76 | function(ex) { log("event/signal " + name + " handler failed:", ex, ex.stack) } 77 | ) 78 | 79 | if (proto_callback !== undefined) 80 | proto_callback.forEach(invoker) 81 | 82 | if (handlers !== undefined) 83 | handlers.forEach(invoker) 84 | } 85 | 86 | function removeAllListeners(name) { 87 | delete this._eventHandlers[name] 88 | } 89 | 90 | function removeListener (name, callback) { 91 | if (!(name in this._eventHandlers) || callback === undefined || callback === null || name === '') { 92 | if ($manifest$trace$listeners) 93 | log('invalid removeListener(' + name + ', ' + callback + ') invocation', new Error().stack) 94 | return 95 | } 96 | 97 | var handlers = this._eventHandlers[name] 98 | var idx = handlers.indexOf(callback) 99 | if (idx >= 0) 100 | handlers.splice(idx, 1) 101 | else if ($manifest$trace$listeners) 102 | log('failed to remove listener for', name, 'from', this) 103 | 104 | if (!handlers.length) 105 | this.removeAllListeners(name) 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /core/Font.qml: -------------------------------------------------------------------------------- 1 | /// adjusts text font properties 2 | Object { 3 | property string family: manifest.style.font.family; ///< font family 4 | property bool italic; ///< applies italic style 5 | property bool bold; ///< applies bold style 6 | property bool underline; ///< applies underline style 7 | property bool overline; ///< applies overline style 8 | property bool strike; ///< line throw text flag 9 | property bool strikeout; ///< line throw text flag for compatibility with QML 10 | property real letterSpacing; ///< spacing between letters 11 | property real wordSpacing; ///< spacing between words 12 | property int pixelSize: manifest.style.font.pixelSize; ///< font size in pixels 13 | property int pointSize; ///< font size in points 14 | property real lineHeight: manifest.style.font.lineHeight; ///< font line height in font heights 15 | property int weight; ///< font weight value 16 | property enum capitalization { MixedCase, AllUppercase, AllLowercase, SmallCaps, Capitalize }; 17 | 18 | ///@private 19 | function _updateTextDecoration() { 20 | var decoration = (this.underline ? ' underline' : '') 21 | + (this.overline ? ' overline' : '') 22 | + (this.strike || this.strikeout ? ' line-through' : '') 23 | this.parent.style('text-decoration', decoration) 24 | this.parent._updateSize() 25 | } 26 | 27 | onFamilyChanged: { this.parent.style('font-family', value); this.parent._updateSize() } 28 | onPointSizeChanged: { if (value > 0) this.pixelSize = 0; this.parent.style('font-size', value > 0? value + 'pt': ''); this.parent._updateSize() } 29 | onPixelSizeChanged: { if (value > 0) this.pointSize = 0; this.parent.style('font-size', value > 0? value + 'px': ''); this.parent._updateSize() } 30 | onItalicChanged: { this.parent.style('font-style', value? 'italic': 'normal'); this.parent._updateSize() } 31 | onBoldChanged: { this.parent.style('font-weight', value? 'bold': 'normal'); this.parent._updateSize() } 32 | onUnderlineChanged: { this._updateTextDecoration() } 33 | onOverlineChanged: { this._updateTextDecoration() } 34 | onStrikeChanged, 35 | onStrikeoutChanged: { this._updateTextDecoration() } 36 | onLineHeightChanged: { this.parent.style('line-height', value); this.parent._updateSize() } 37 | onWeightChanged: { this.parent.style('font-weight', value); this.parent._updateSize() } 38 | onLetterSpacingChanged: { this.parent.style('letter-spacing', value + "px"); this.parent._updateSize() } 39 | onWordSpacingChanged: { this.parent.style('word-spacing', value + "px"); this.parent._updateSize() } 40 | onCapitalizationChanged: { 41 | this.parent.style('text-transform', 'none'); 42 | this.parent.style('font-variant', 'normal'); 43 | switch(value) { 44 | case this.AllUppercase: this.parent.style('text-transform', 'uppercase'); break 45 | case this.AllLowercase: this.parent.style('text-transform', 'lowercase'); break 46 | case this.SmallCaps: this.parent.style('font-variant', 'small-caps'); break 47 | case this.Capitalize: this.parent.style('text-transform', 'capitalize'); break 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/GamepadManager.qml: -------------------------------------------------------------------------------- 1 | ///gamepad manager item holds Gamepad items and provide common API 2 | Item { 3 | signal connected; ///< emitted when any gamepad is connected 4 | signal disconnected; ///< emitted when gamepad is disconnected 5 | 6 | property variant _gamepads; ///< @private 7 | property int count: 0; ///< count of the all connected gamepad devices 8 | property int gamepadChildrensCount: 0; ///< count of Gamepad instances inside scope 9 | property int gamepadPollingInterval: 1000; ///< startup delay before gamepads polling because there is no gamepad events 10 | property int eventPollingInterval: 8; ///< gamepad event polling timer interval default value is 8ms (for 120fps) because there is no gamepad events 11 | 12 | Timer { 13 | id: startupTimer; 14 | interval: parent.gamepadPollingInterval; 15 | repeat: false; 16 | triggeredOnStart: true; 17 | 18 | onTriggered: { this.parent.pollGamepads() } 19 | } 20 | 21 | Timer { 22 | interval: parent.eventPollingInterval; 23 | repeat: true; 24 | running: parent.gamepadChildrensCount; 25 | triggeredOnStart: true; 26 | 27 | onTriggered: { this.parent.gpButtonCheckLoop() } 28 | } 29 | 30 | /// @private 31 | pollGamepads: { 32 | clearInterval(this._gpPollInterval) 33 | var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []); 34 | for (var i = 0; i < gamepads.length; ++i) { 35 | var gamepad = gamepads[i] 36 | if (gamepad) 37 | this.gamepadConnectedHandler({ 'gamepad': gamepad }) 38 | } 39 | } 40 | 41 | /// @private 42 | gpButtonCheckLoop: { 43 | clearInterval(this._gpButtonsPollInterval); 44 | var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []); 45 | for (var i in gamepads) { 46 | if (!gamepads[i] || !gamepads[i].buttons) 47 | continue 48 | 49 | var gp = gamepads[i] 50 | var gpItem 51 | 52 | for (var i = 0; i < this.children.length; ++i) { 53 | var c = this.children[i] 54 | if (c instanceof $core.Gamepad && c.connected && c.index === gp.index) { 55 | gpItem = c 56 | break 57 | } 58 | } 59 | 60 | if (!gp || !gpItem) 61 | continue 62 | 63 | gpItem.poll(gp) 64 | } 65 | } 66 | 67 | /// @private 68 | gamepadConnectedHandler(event): { 69 | log('connected', event.gamepad.id) 70 | this.connected(event.gamepad) 71 | 72 | if (!this._gamepads) 73 | this._gamepads = {} 74 | this._gamepads[event.gamepad.index] = event.gamepad 75 | ++this.count 76 | 77 | if (!$core.Gamepad) { 78 | log("No 'Gamepad' instance found, add at least one 'Gamepad' item inside 'GamepadManager' scope.") 79 | return 80 | } 81 | 82 | var vendorRegExp = /vendor.*?\d{1,4}/g 83 | var productRegExp = /product.*?\d{1,4}/g 84 | var digits = /\d{1,4}/g 85 | 86 | var idStr = event.gamepad.id.toLowerCase() 87 | var match = vendorRegExp.exec(idStr) 88 | match = digits.exec(match) 89 | var vendorId 90 | if (match && match.length) 91 | vendorId = match[0] 92 | 93 | match = productRegExp.exec(idStr) 94 | match = digits.exec(match) 95 | var productId 96 | if (match && match.length) 97 | productId = match[0] 98 | 99 | var children = this.children 100 | var g = event.gamepad 101 | for (var i = 0; i < children.length; ++i) { 102 | var c = children[i] 103 | if (c instanceof $core.Gamepad && !c.connected) { 104 | c.index = g.index 105 | c.connected = true 106 | c.deviceInfo = g.id 107 | c.buttonsCount = g.buttons.length 108 | c.axesCount = g.axes.length 109 | if (vendorId) 110 | c.vendorId = vendorId 111 | if (productId) 112 | c.productId = productId 113 | c.standartMapping = g.mapping === "standard" 114 | ++this.gamepadChildrensCount 115 | break 116 | } 117 | } 118 | } 119 | 120 | /// @private 121 | gamepadDisconnectedHandler(event): { 122 | this.disconnected(event.gamepad) 123 | delete this._gamepads[event.gamepad.index] 124 | --this.count 125 | 126 | if (!$core.Gamepad) { 127 | log("No 'Gamepad' instance found, add at least one 'Gamepad' item inside 'GamepadManager' scope.") 128 | return 129 | } 130 | 131 | var g = event.gamepad 132 | var children = this.children 133 | 134 | for (var i = 0; i < children.length; ++i) { 135 | var c = children[i] 136 | if (c instanceof $core.Gamepad && c.index === g.index) { 137 | c.index = -1 138 | c.connected = false 139 | c.deviceInfo = "" 140 | c.buttonsCount = 0 141 | c.axesCount = 0 142 | --this.gamepadChildrensCount 143 | break 144 | } 145 | } 146 | } 147 | 148 | /// @private 149 | onCompleted: { 150 | this._gpButtonsPollInterval = {} 151 | this._gpPollInterval = {} 152 | 153 | startupTimer.restart() 154 | 155 | var ctx = this._context 156 | ctx.window.on('gamepadconnected', this.gamepadConnectedHandler.bind(this)) 157 | ctx.window.on('gamepaddisconnected', this.gamepadDisconnectedHandler.bind(this)) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /core/Gradient.qml: -------------------------------------------------------------------------------- 1 | /// gradient filled area, just place GradientStop in its scope 2 | Object { 3 | property enum orientation { Vertical, Horizontal, BottomRight, TopRight, Custom }; ///< gradient direction enumaration 4 | property real angle; ///< angle for custom orientated gradient 5 | property enum type { Linear, Conical }: Linear; /// < Linear by default to preserve backward compatibility 6 | 7 | ///@private 8 | constructor: { 9 | this.stops = [] 10 | } 11 | 12 | ///@private 13 | function addChild(child) { 14 | $core.Object.prototype.addChild.apply(this, arguments) 15 | if (child instanceof $core.GradientStop) { 16 | this.stops.push(child) 17 | this.stops.sort(function(a, b) { return a.position > b.position; }) 18 | this._updateStyle() 19 | } 20 | } 21 | 22 | ///@private 23 | function _updateStyle() { 24 | var decl = this._getDeclaration() 25 | if (decl) 26 | this.parent.style({ 'background-color': '', 'background': decl }) 27 | } 28 | 29 | ///@private 30 | function _getDeclaration() { 31 | var stops = this.stops 32 | var n = stops.length 33 | if (n < 2) 34 | return 35 | 36 | var orientation 37 | if (this.type == this.Linear) { 38 | switch(this.orientation) { 39 | default: 40 | case this.Vertical: orientation = 'to bottom'; break 41 | case this.Horizontal: orientation = 'to left'; break 42 | case this.BottomRight: orientation = 'to bottom right'; break 43 | case this.TopRight: orientation = 'to top right'; break 44 | case this.Custom: orientation = this.angle + 'deg'; break 45 | } 46 | } 47 | else if (this.type == this.Conical) { 48 | orientation = this.angle + 'deg at 50% 50%'; // TODO parameterize the center 49 | } 50 | 51 | var grad = new $core.gradient.Gradient(orientation, this.type) 52 | 53 | for(var i = 0; i < n; ++i) { 54 | var stop = stops[i] 55 | grad.add(stop._getDeclaration()) 56 | } 57 | 58 | return grad 59 | } 60 | 61 | ///@private 62 | onCompleted: { 63 | this._updateStyle() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /core/GradientStop.qml: -------------------------------------------------------------------------------- 1 | /// this object is used for customizing Gradient 2 | Object { 3 | property real position; ///< relative position of the stop must be in range [0:1] 4 | property color color; ///< color of this stop 5 | 6 | ///@private 7 | onPositionChanged, onColorChanged: { 8 | this.parent._updateStyle() 9 | } 10 | 11 | ///@private 12 | function _getDeclaration() { 13 | return new $core.gradient.GradientStop(this.color, this.position) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /core/HoverMixin.qml: -------------------------------------------------------------------------------- 1 | /// this mixin provides mouse hover events handling 2 | BaseMouseMixin { 3 | property bool value; ///< is 'true' if item if hovered, 'false' otherwise 4 | 5 | ///@private 6 | constructor: { 7 | this.element = this.parent.element; 8 | this.parent.style('cursor', this.cursor) 9 | this._bindHover(this.enabled) 10 | if (!exports.hasProperty(parent, "hovered")) 11 | exports.addProperty(parent, "bool", "hovered", false) 12 | } 13 | 14 | ///@private 15 | function _bindHover(value) { 16 | if (value && !this._hmHoverBinder) { 17 | this._hmHoverBinder = new $core.EventBinder(this.parent.element) 18 | 19 | if (this._context.backend.capabilities.mouseEnterLeaveSupported) { 20 | this._hmHoverBinder.on('mouseenter', function() { this.value = this._trueUnlessTouchEvent() }.bind(this)) 21 | this._hmHoverBinder.on('mouseleave', function() { this.value = false }.bind(this)) 22 | } else { 23 | this._hmHoverBinder.on('mouseover', function() { this.value = this._trueUnlessTouchEvent() }.bind(this)) 24 | this._hmHoverBinder.on('mouseout', function() { this.value = false }.bind(this)) 25 | } 26 | this._hmHoverBinder.on('touchstart', this._setTouchEvent.bind(this)) 27 | this._hmHoverBinder.on('mouseup', this._resetTouchEvent.bind(this)) 28 | } 29 | if (this._hmHoverBinder) 30 | this._hmHoverBinder.enable(value) 31 | } 32 | 33 | onValueChanged: { this.parent.hovered = value } 34 | onEnabledChanged: { this._bindHover(value) } 35 | } 36 | -------------------------------------------------------------------------------- /core/Layout.qml: -------------------------------------------------------------------------------- 1 | /// base layout component. 2 | BaseLayout { 3 | width: contentWidth; ///< inner content width 4 | height: contentHeight; ///< inner content height 5 | handleNavigationKeys: true; ///< handle navigation keys, move focus 6 | keyNavigationWraps: true; ///< key navigation wraps from first to last and vise versa 7 | 8 | ///move focus to the next child 9 | focusNextChild: { 10 | var idx = 0; 11 | var children = this.children 12 | if (this.focusedChild) 13 | idx = children.indexOf(this.focusedChild) 14 | 15 | for (var i = idx + 1; i < children.length; ++i) { 16 | if (children[i]._tryFocus()) { 17 | this.currentIndex = i 18 | this.focusChild(this.children[i]) 19 | return true 20 | } 21 | } 22 | 23 | if (!this.keyNavigationWraps) 24 | return false 25 | 26 | for (var i = 0; i <= idx; ++i) { 27 | if (children[i]._tryFocus()) { 28 | this.currentIndex = i 29 | this.focusChild(this.children[i]) 30 | return true 31 | } 32 | } 33 | 34 | return false 35 | } 36 | 37 | ///move focus to the previous child 38 | focusPrevChild: { 39 | var idx = 0; 40 | var children = this.children 41 | if (this.focusedChild) 42 | idx = children.indexOf(this.focusedChild) 43 | 44 | for (var i = idx - 1; i >= 0; --i) { 45 | if (children[i]._tryFocus()) { 46 | this.currentIndex = i 47 | this.focusChild(this.children[i]) 48 | return true 49 | } 50 | } 51 | 52 | if (!this.keyNavigationWraps) 53 | return false 54 | 55 | var last = children.length - 1 56 | for (var i = last; i >= idx; --i) { 57 | if (children[i]._tryFocus()) { 58 | this.currentIndex = i 59 | this.focusChild(this.children[i]) 60 | return true 61 | } 62 | } 63 | 64 | return false 65 | 66 | } 67 | 68 | onCurrentIndexChanged: { 69 | if (value >= 0 && value < this.children.length) 70 | this.focusChild(this.children[value]) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /core/Loader.qml: -------------------------------------------------------------------------------- 1 | /// object that helps loading components dynamically 2 | Item { 3 | signal loaded; ///< signals loaded component (first argument) 4 | signal itemCompleted; ///< fires after item onCompleted has been fired and item is fully constructed 5 | property string source; ///< component's URL 6 | property Component sourceComponent; ///< loads delegate from component. 7 | property Object item; ///< item for storing requested component 8 | property bool trace; ///< log loading objects 9 | 10 | ///@private 11 | function discardItem() { 12 | this._itemCompleted = false 13 | var item = this.item 14 | if (item) { 15 | item.discard() 16 | this.item = null 17 | } 18 | } 19 | 20 | ///@private 21 | function discard() { 22 | this.discardItem() 23 | $core.Item.prototype.discard.call(this) 24 | } 25 | 26 | ///@internal 27 | onSourceChanged: { 28 | this.discardItem() 29 | this._load() 30 | } 31 | onSourceComponentChanged: { 32 | this.discardItem() 33 | this._load() 34 | } 35 | 36 | function _loadSource() { 37 | var source = this.source 38 | if (this.trace) 39 | log('loading ' + source + '…') 40 | var path = source.split('.') 41 | var ctor = _globals 42 | while(path.length) { 43 | var ns = path.shift() 44 | ctor = ctor[ns] 45 | if (ctor === undefined) 46 | throw new Error('unknown component used: ' + source) 47 | } 48 | return new ctor(this) 49 | } 50 | 51 | ///@internal 52 | function _load() { 53 | var item 54 | if (this.source) { 55 | item = this._loadSource() 56 | $core.core.createObject(item) 57 | } else if (this.sourceComponent) { 58 | if (!(this.sourceComponent instanceof $core.Component)) 59 | throw new Error("sourceComponent assigned to Loader " + this.getComponentPath() + " is not an instance of Component") 60 | if (this.trace) 61 | log('loading component ' + this.sourceComponent.getComponentPath()) 62 | var row = this.parent._get('model', true) 63 | item = this.sourceComponent.delegate(this, row? row: {}) 64 | this._updateVisibilityForChild(item, this.recursiveVisible) 65 | this._tryFocus() 66 | } else 67 | return 68 | 69 | this.item = item 70 | 71 | this.loaded(item) 72 | 73 | var hasOnCompleted = item.__complete !== $core.CoreObject.prototype.__complete 74 | if (hasOnCompleted) { 75 | // Schedule dummy object which calls itemCompleted(item) 76 | // It's guaranteed to execute after possibly delayed item.onComplete handler. 77 | var complete = function() { 78 | //check that item hasn't been changed. 79 | if (this.item === item) { 80 | this._itemCompleted = true 81 | this.itemCompleted(item) 82 | } 83 | }.bind(this) 84 | 85 | this._context.__onCompleted({ 86 | __complete: complete 87 | }) 88 | } else { 89 | this._itemCompleted = true 90 | this.itemCompleted(item) 91 | } 92 | } 93 | 94 | onRecursiveVisibleChanged: { 95 | if (this.item) 96 | this._updateVisibilityForChild(this.item, value) 97 | } 98 | 99 | /// @private 100 | function on (name, callback) { 101 | $core.Item.prototype.on.apply(this, arguments) 102 | if (name === 'loaded') { 103 | if (this.item) 104 | callback(this.item) 105 | } 106 | if (name === 'itemCompleted') { 107 | if (this._itemCompleted) 108 | callback(this.item) 109 | } 110 | } 111 | 112 | ///@internal 113 | onCompleted: { 114 | if (!this.item && (this.source || this.sourceComponent)) 115 | this._load() 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /core/LocalStorage.qml: -------------------------------------------------------------------------------- 1 | /// simple proxy to underlying storage 2 | Object { 3 | constructor: { 4 | var backend = $core.__localStorageBackend 5 | this.impl = backend().createLocalStorage(this) 6 | } 7 | 8 | ///@private 9 | _checkNameValid(name): { 10 | if (!name) throw new Error("empty name") 11 | } 12 | 13 | ///@private 14 | _ensureCallback(cb, name): { 15 | return cb || function(val) { log("ignore value of", name, "gotten from storage:", val) } 16 | } 17 | 18 | ///@private 19 | _ensureErrCallback(cb): { 20 | return cb || function(err) { log(err.message) } 21 | } 22 | 23 | /** 24 | * Return stored item by name 25 | * @param {string} name - stored item name 26 | * @param {function} callback - callback to return value 27 | * @param {function} error - callback to report non-existing value or some kind of error 28 | */ 29 | get(name, callback, error): { 30 | this._checkNameValid(name) 31 | this.impl.get(name, this._ensureCallback(callback, name), this._ensureErrCallback(error), this) 32 | } 33 | 34 | /** 35 | * Return stored item by name or default value if not exists 36 | * @param {string} name - stored item name 37 | * @param {function} callback - callback to return value 38 | * @param {Object} defaultValue - default value 39 | */ 40 | getOrDefault(name, callback, defaultValue): { 41 | this._checkNameValid(name) 42 | callback = this._ensureCallback(callback, name) 43 | this.impl.get(name, callback, function() { callback(defaultValue) }, this) 44 | } 45 | 46 | /** 47 | * Save named item 48 | * @param {string} name - item name 49 | * @param {string} value - item value 50 | * @param {function} error - callback to report error 51 | */ 52 | set(name, value, error): { 53 | this._checkNameValid(name) 54 | this.impl.set(name, value, this._ensureErrCallback(error), this) 55 | } 56 | 57 | /** 58 | * Remove item 59 | * @param {string} name - item name 60 | * @param {function} error - callback to report error 61 | */ 62 | erase(name, error): { 63 | this._checkNameValid(name) 64 | this.impl.erase(name, this._ensureErrCallback(error), this) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /core/Location.qml: -------------------------------------------------------------------------------- 1 | /// Window location object 2 | Object { 3 | property string hash; ///< contains current hash value (after '#' charachter) 4 | property string host; ///< current host with port number 5 | property string href; ///< whole current URL 6 | property string port; ///< current port number 7 | property string origin; ///< current protocol, hostname and port number of a URL 8 | property string hostname; ///< current host name 9 | property string pathname; ///< path name of the current URL 10 | property string protocol; ///< current protocol 11 | property string search; ///< query string of the URL 12 | property Object state; ///< current history state 13 | 14 | ///@private 15 | constructor: { 16 | var backend = $core.__locationBackend 17 | if (!backend) 18 | throw new Error('no backend found') 19 | this.impl = backend().createLocation(this) 20 | } 21 | 22 | pushState(state, title, url): { 23 | this.impl.pushState(state, title, url) 24 | } 25 | 26 | changeHref(href): { 27 | this.impl.changeHref(href) 28 | } 29 | 30 | function reload() { 31 | this.impl.reload() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/Model.qml: -------------------------------------------------------------------------------- 1 | Object { 2 | signal reset; ///< model reset signal 3 | signal rowsInserted; ///< rows inserted signal 4 | signal rowsChanged; ///< rows changed signal 5 | signal rowsRemoved; ///< rows removed signal 6 | 7 | property int count; ///< model rows count. Please note that you can't directly/indirectly modify model from onChanged handler. Use view.onCountChanged instead 8 | 9 | /// @private 10 | function attachTo(object) { 11 | if (object._modelAttached) 12 | object._modelAttached.detachFrom(object) 13 | if (!object._modelReset) 14 | object._modelReset = object._onReset.bind(object) 15 | if (!object._modelRowsInserted) 16 | object._modelRowsInserted = object._onRowsInserted.bind(object) 17 | if (!object._modelRowsChanged) 18 | object._modelRowsChanged = object._onRowsChanged.bind(object) 19 | if (!object._modelRowsRemoved) 20 | object._modelRowsRemoved = object._onRowsRemoved.bind(object) 21 | 22 | var Model = $core.Model 23 | var model = this 24 | var modelType = typeof model 25 | if ((Model !== undefined) && (model instanceof Model)) { 26 | } else if (Array.isArray(model)) { 27 | model = new $core.model.ArrayModelWrapper(model) 28 | } else if (modelType === 'number') { 29 | var data = [] 30 | for(var i = 0; i < model; ++i) 31 | data.push({}) 32 | model = new $core.model.ArrayModelWrapper(data) 33 | } else 34 | throw new Error("unknown value of type '" + (typeof model) + "', attached to model property: " + model + ((modelType === 'object') && ('componentName' in model)? ', component name: ' + model.componentName: '')) 35 | 36 | model.on('reset', object._modelReset) 37 | model.on('rowsInserted', object._modelRowsInserted) 38 | model.on('rowsChanged', object._modelRowsChanged) 39 | model.on('rowsRemoved', object._modelRowsRemoved) 40 | 41 | object._modelAttached = model 42 | object._onReset() 43 | } 44 | 45 | /// @private 46 | function detachFrom(object) { 47 | var model = object._modelAttached 48 | if (!model) 49 | return 50 | 51 | object._modelAttached = null 52 | 53 | model.removeListener('reset', object._modelReset) 54 | model.removeListener('rowsInserted', object._modelRowsInserted) 55 | model.removeListener('rowsChanged', object._modelRowsChanged) 56 | model.removeListener('rowsRemoved', object._modelRowsRemoved) 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /core/MouseMoveMixin.qml: -------------------------------------------------------------------------------- 1 | /// this mixin provides mouse press events handling 2 | BaseMouseMixin { 3 | property int mouseX; 4 | property int mouseY; 5 | property int clientX; 6 | property int clientY; 7 | property int screenX; 8 | property int screenY; 9 | signal mouseMove; 10 | 11 | ///@private 12 | constructor: { 13 | this._bindMove(this.enabled) 14 | } 15 | 16 | /// @private 17 | function _updatePosition(event) { 18 | var parent = this.parent 19 | var touchEvent = event.type === 'touchmove' 20 | var x, y 21 | if (touchEvent) { 22 | var touch = event.touches[0] 23 | this.screenX = touch.screenX; 24 | this.screenY = touch.screenY; 25 | this.clientX = touch.clientX; 26 | this.clientY = touch.clientY; 27 | var screenPos = this.parent.toScreen() 28 | x = touch.clientX - screenPos[0] 29 | y = touch.clientY - screenPos[1] 30 | } else { 31 | this.screenX = event.screenX; 32 | this.screenY = event.screenY; 33 | this.clientX = event.clientX; 34 | this.clientY = event.clientY; 35 | x = event.offsetX 36 | y = event.offsetY 37 | } 38 | if (x >= 0 && y >= 0 && x < parent.width && y < parent.height) { 39 | this.mouseX = x 40 | this.mouseY = y 41 | this.mouseMove(x, y, event) 42 | return true 43 | } 44 | else 45 | return false 46 | } 47 | 48 | /// @private 49 | function _bindMove(value) { 50 | if (value && !this._mouseMoveBinder) { 51 | this._mouseMoveBinder = new $core.EventBinder(this.element) 52 | var handler = function(event) { 53 | if (this._updatePosition(event) && event.type !== 'touchmove') 54 | $core.callMethod(event, 'preventDefault') 55 | }.bind(this) 56 | this._mouseMoveBinder.on('mousemove', handler) 57 | this._mouseMoveBinder.on('touchmove', handler) 58 | } 59 | if (this._mouseMoveBinder) 60 | this._mouseMoveBinder.enable(value) 61 | } 62 | 63 | onEnabledChanged: { 64 | this._bindMove(value) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /core/MousePressMixin.qml: -------------------------------------------------------------------------------- 1 | /// this mixin provides mouse press events handling 2 | BaseMouseMixin { 3 | property bool pressed; ///< true if any buttons pressed 4 | 5 | ///@private 6 | constructor: { 7 | this._bindPress(this.enabled) 8 | } 9 | 10 | /// @private 11 | function _bindPress(value) { 12 | if (value && !this._mpmPressBinder) { 13 | this._mpmPressBinder = new $core.EventBinder(this.element) 14 | this._mpmPressBinder.on('mousedown', function() { this.pressed = true }.bind(this)) 15 | this._mpmPressBinder.on('mouseup', function() { this.pressed = false }.bind(this)) 16 | } 17 | if (this._mpmPressBinder) 18 | this._mpmPressBinder.enable(value) 19 | } 20 | 21 | /// @private 22 | onEnabledChanged: { 23 | this._bindPress(value) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/PageStack.qml: -------------------------------------------------------------------------------- 1 | ///layout for displaying one of its children at the time 2 | Layout { 3 | property int currentIndex: 0; ///< index of displaying child 4 | property int count: 0; ///< childrens count 5 | clip: true; ///@private 6 | 7 | /// @private 8 | onCurrentIndexChanged: { 9 | if (this.currentIndex < 0) 10 | this.currentIndex = 0; 11 | else if (this.children.length > 0 && this.currentIndex >= this.children.length) 12 | this.currentIndex = this.children.length - 1; 13 | 14 | this._scheduleLayout() 15 | } 16 | 17 | /// @private 18 | onActiveFocusChanged: { 19 | if (value && this.count) 20 | this.children[this.currentIndex].setFocus() 21 | } 22 | 23 | /// @private 24 | function _layout() { 25 | this.count = this.children.length; 26 | if (this.trace) 27 | log('laying out ' + this.count + ' children in ' + this.width + 'x' + this.height) 28 | 29 | for (var i = 0; i < this.count; ++i) 30 | this.children[i].visibleInView = (i === this.currentIndex); 31 | 32 | var c = this.children[this.currentIndex]; 33 | if (!c) 34 | return 35 | 36 | this.contentHeight = c.height; 37 | this.contentWidth = c.width; 38 | } 39 | 40 | /// @private 41 | function addChild(child) { 42 | $core.Layout.prototype.addChild.apply(this, arguments) 43 | var children = this.children 44 | var index = children.length - 1 45 | var lastChild = children[index] 46 | lastChild.visibleInView = (index === this.currentIndex) 47 | var update = this._scheduleLayout.bind(this) 48 | child.onChanged('height', update) 49 | child.onChanged('recursiveVisible', update) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/PropertyStorage.qml: -------------------------------------------------------------------------------- 1 | /// object to hold named property synced with underlying storage 2 | LocalStorage { 3 | signal ready; ///< the value now in a sync state with underlying storage 4 | property string name; ///< stored property key name 5 | property string value; ///< stored property value 6 | property string defaultValue; ///< default init value 7 | 8 | ///@private 9 | _checkNameValid: { 10 | if (!this.name) 11 | throw new Error('empty property name') 12 | } 13 | 14 | ///@private 15 | _read: { 16 | this._checkNameValid() 17 | this.getOrDefault(this.name, function(value) { 18 | this._setProperty('value', value) 19 | this.ready() 20 | }.bind(this), this.defaultValue) 21 | } 22 | 23 | ///@private 24 | _write: { 25 | this._checkNameValid() 26 | if (this.value) 27 | this.set(this.name, this.value) 28 | else 29 | this.erase(this.name) 30 | } 31 | 32 | ///@private 33 | onValueChanged: { this._write() } 34 | 35 | ///@private 36 | onNameChanged: { 37 | this._setProperty('value', '') 38 | this._read() 39 | } 40 | 41 | onCompleted: { 42 | if (this.value) { 43 | this._setProperty('value', this.value) 44 | this.ready() 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/RAIIEventEmitter.qml: -------------------------------------------------------------------------------- 1 | ///@private 2 | EventEmitter { 3 | ///@private 4 | constructor: { 5 | this._onListener = {} 6 | } 7 | 8 | ///@private 9 | function on (name, callback) { 10 | if (!(name in this._eventHandlers)) { 11 | if (name in this._onListener) { 12 | //log('first listener to', name) 13 | this._onListener[name][0](name) 14 | } else if ('' in this._onListener) { 15 | //log('first listener to', name) 16 | this._onListener[''][0](name) 17 | } 18 | if (this._eventHandlers[name]) 19 | throw new Error('listener callback added event handler') 20 | } 21 | $core.EventEmitter.prototype.on.call(this, name, callback) 22 | } 23 | 24 | ///@private 25 | function onListener (name, first, last) { 26 | this._onListener[name] = [first, last] 27 | } 28 | 29 | ///@private 30 | function removeAllListeners(name) { 31 | $core.EventEmitter.prototype.removeAllListeners.call(this, name) 32 | if (name in this._onListener) 33 | this._onListener[name][1](name) 34 | else if ('' in this._onListener) { 35 | //log('first listener to', name) 36 | this._onListener[''][1](name) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /core/Radius.qml: -------------------------------------------------------------------------------- 1 | Object { 2 | property real radius; ///< radius for all corners 3 | property real topLeft; ///< top left corner radius 4 | property real topRight; ///< top right corner radius 5 | property real bottomLeft; ///< bottom left corner radius 6 | property real bottomRight; ///< bottom right corner radius 7 | 8 | prototypeConstructor: { 9 | RadiusPrototype.defaultProperty = 'radius'; 10 | } 11 | 12 | _updateValue: { 13 | var radius = this.radius 14 | var tl = this.topLeft || radius 15 | var tr = this.topRight || radius 16 | var bl = this.bottomLeft || radius 17 | var br = this.bottomRight || radius 18 | if (tl == tr && bl == br && tl == bl) 19 | this.parent.style('border-radius', tl) 20 | else 21 | this.parent.style('border-radius', tl + 'px ' + tr + 'px ' + br + 'px ' + bl + 'px') 22 | } 23 | 24 | onRadiusChanged, 25 | onTopLeftChanged, 26 | onTopRightChanged, 27 | onBottomLeftChanged, 28 | onBottomRightChanged: { 29 | this._updateValue() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /core/Rectangle.qml: -------------------------------------------------------------------------------- 1 | /// Colored rectangle with optional rounded corners, border and/or gradient. 2 | Item { 3 | property color color: "#0000"; ///< rectangle background color 4 | property lazy border: Border {} ///< object holding properties of the border 5 | property Gradient gradient; ///< if gradient object was set, it displays gradient instead of solid color 6 | constructor : { 7 | this._context.backend.initRectangle(this) 8 | } 9 | 10 | onColorChanged: { 11 | this.style('background-color', $core.Color.normalize(value)) 12 | } 13 | 14 | prototypeConstructor: { 15 | var styleMap = RectanglePrototype._propertyToStyle = Object.create(RectangleBasePrototype._propertyToStyle) 16 | styleMap['color'] = 'background-color' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/Repeater.qml: -------------------------------------------------------------------------------- 1 | ///The simplest view implementation, creates elements without positioning 2 | BaseView { 3 | 4 | ///@private 5 | function positionViewAtIndex() { } 6 | 7 | ///@private 8 | function _layout() { 9 | if (!this.recursiveVisible && !this.offlineLayout) { 10 | this.layoutFinished() 11 | return 12 | } 13 | 14 | var model = this._modelAttached; 15 | if (!model) { 16 | this.layoutFinished() 17 | return 18 | } 19 | 20 | var created = false; 21 | var n = this.count = model.count 22 | var items = this._items 23 | for(var i = 0; i < n; ++i) { 24 | var item = items[i] 25 | if (!item) { 26 | item = this._createDelegate(i) 27 | created = true 28 | } 29 | } 30 | this.layoutFinished() 31 | if (created) 32 | this._context.scheduleComplete() 33 | } 34 | 35 | /// @private creates delegate in given item slot 36 | function _createDelegate(idx) { 37 | var delegate = $core.BaseView.prototype._createDelegate.call(this, idx, function(delegate) { 38 | var parent = this.parent 39 | parent.element.append(delegate.element) 40 | parent.addChild(delegate) 41 | }.bind(this)) 42 | return delegate 43 | } 44 | 45 | function _discardItem(item) { 46 | this.parent.removeChild(item) 47 | $core.BaseView.prototype._discardItem.apply(this, arguments) 48 | } 49 | 50 | onLayoutFinished: { 51 | //request layout from parent if it's layout 52 | var parent = this.parent 53 | if (parent && parent._scheduleLayout) { 54 | parent._scheduleLayout() 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /core/Request.qml: -------------------------------------------------------------------------------- 1 | ///object for handling XML/HTTP requests 2 | Object { 3 | property bool loading: false; ///< loading flag, is true when request was send and false when answer was recieved or error occured 4 | 5 | /**@param request:Object request object 6 | send request using 'XMLHttpRequest' object*/ 7 | function ajax(request) { 8 | if (request.done) 9 | request.done = this._context.wrapNativeCallback(request.done) 10 | if (request.error) 11 | request.error = this._context.wrapNativeCallback(request.error) 12 | this._context.backend.ajax(this, request) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/Row.qml: -------------------------------------------------------------------------------- 1 | /// layout for horizontal oriented content 2 | Layout { 3 | ///@private 4 | onKeyPressed: { 5 | if (!this.handleNavigationKeys) 6 | return false; 7 | 8 | switch (key) { 9 | case 'Left': return this.focusPrevChild() 10 | case 'Right': return this.focusNextChild() 11 | } 12 | } 13 | 14 | ///@private 15 | function _layout() { 16 | if (!this.recursiveVisible && !this.offlineLayout) 17 | return 18 | 19 | var children = this.children; 20 | var p = 0 21 | var h = 0 22 | this.count = children.length 23 | 24 | for(var i = 0; i < children.length; ++i) { 25 | var c = children[i] 26 | if (!('width' in c)) 27 | continue 28 | 29 | var rm = c.anchors.rightMargin || c.anchors.margins 30 | var lm = c.anchors.leftMargin || c.anchors.margins 31 | 32 | var b = c.y + c.height 33 | if (b > h) 34 | h = b 35 | c.viewX = p + rm 36 | if (c.visible && c.width > 0) 37 | p += c.width + this.spacing + rm + lm 38 | } 39 | if (p > 0) 40 | p -= this.spacing 41 | this.contentWidth = p 42 | this.contentHeight = h 43 | } 44 | 45 | ///@private 46 | function addChild(child) { 47 | $core.Item.prototype.addChild.apply(this, arguments) 48 | 49 | if (!('width' in child)) 50 | return 51 | 52 | var update = this._scheduleLayout.bind(this) 53 | child.onChanged('recursiveVisible', update) 54 | child.onChanged('width', update) 55 | child.on('anchorsMarginsUpdated', update) 56 | this._scheduleLayout() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /core/ScrollView.qml: -------------------------------------------------------------------------------- 1 | Item { 2 | property enum horizontalScrollBarPolicy { ScrollBarAsNeeded, ScrollBarAlwaysOff, ScrollBarAlwaysOn}: ScrollBarAlwaysOff; 3 | property enum verticalScrollBarPolicy { ScrollBarAsNeeded, ScrollBarAlwaysOff, ScrollBarAlwaysOn}; 4 | cssPointerTouchEvents: true; 5 | 6 | constructor: { 7 | this.style({ 'overflow-x': 'hidden', 'overflow-y': 'auto' }) 8 | } 9 | 10 | ///@private 11 | function _setStyle(style, value) { 12 | switch(value) { 13 | case ScrollViewPrototype.ScrollBarAsNeeded: 14 | this.style(style, 'auto') 15 | break 16 | case ScrollViewPrototype.ScrollBarAlwaysOn: 17 | this.style(style, 'visible') 18 | break 19 | case ScrollViewPrototype.ScrollBarAlwaysOff: 20 | this.style(style, 'hidden') 21 | break 22 | } 23 | } 24 | 25 | onHorizontalScrollBarPolicyChanged: { 26 | this._setStyle('overflow-x', value) 27 | } 28 | 29 | onVerticalScrollBarPolicyChanged: { 30 | this._setStyle('overflow-y', value) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/SequentialAnimation.qml: -------------------------------------------------------------------------------- 1 | Object { 2 | property bool running: false; 3 | property bool trace: false; 4 | 5 | constructor: { 6 | this._sequence = [] 7 | this._currentTarget = null 8 | this._currentProperty = null 9 | } 10 | 11 | onRunningChanged: { 12 | if (value) 13 | { 14 | if ($manifest$disableAnimations || this._sequence.length === 0) { 15 | this.running = false 16 | return 17 | } 18 | this._start(0) 19 | } 20 | } 21 | 22 | function _start(idx) { 23 | for(var i = idx, n = this._sequence.length; i < n; ++i) { 24 | var animation = this._sequence[i] 25 | var target = animation.target 26 | var property = animation.property 27 | var to = animation.to 28 | if (!target || !property || to === undefined) { 29 | log('invalid animation ', this.getComponentPath(), 'without target/property or to') 30 | continue 31 | } 32 | var from = animation.from 33 | if (from !== undefined) { 34 | if (this.trace) 35 | log('animation #' + idx + 'setting initial property value to', from) 36 | target[property] = from 37 | } 38 | 39 | if (target[property] === to) { 40 | if (this.trace) 41 | log('skipping animation #' + idx + ', same value') 42 | continue 43 | } 44 | if (this.trace) 45 | log('starting animation #' + idx, 'target', target.getComponentPath(), 'property', property, 'to', to) 46 | this._currentTarget = target 47 | this._currentProperty = property 48 | target.setAnimation(property, animation) 49 | target[property] = to 50 | return; 51 | } 52 | if (this.trace) 53 | log('animation sequence ', this.getComponentPath(), 'finished') 54 | this.running = false //no valid animation found 55 | return 56 | } 57 | 58 | function _onAnimationRunningChanged(animation, running) { 59 | if (this.trace) 60 | log('animation', animation.getComponentPath(), 'changed running to', running) 61 | if (!running) { 62 | this._currentTarget.resetAnimation(this._currentProperty) 63 | this._currentTarget = this._currentProperty = null 64 | var idx = this._sequence.indexOf(animation) 65 | this._start(idx + 1) 66 | } 67 | } 68 | 69 | function addChild (animation) { 70 | if (animation instanceof $core.Animation) 71 | { 72 | animation.cssTransition = false //we will add keyframe mode here later 73 | this._sequence.push(animation) 74 | this.connectOnChanged(animation, 'running', function(value) { 75 | this._onAnimationRunningChanged(animation, value) 76 | }.bind(this)) 77 | } 78 | else 79 | $core.Object.prototype.addChild.call(this, animation) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /core/Shadow.qml: -------------------------------------------------------------------------------- 1 | /// provides shadow properties adjustment 2 | Object { 3 | property real x; ///< x coordinate 4 | property real y; ///< y coordinate 5 | property color color: "black"; ///< color of the shadow 6 | property real blur; ///< applies a blur effect on the shadow 7 | property real spread; ///< adjusts a spread distance of the shadow 8 | 9 | onXChanged, onYChanged, 10 | onColorChanged, onBlurChanged, onSpreadChanged: { 11 | this.parent._updateStyle(true) 12 | } 13 | 14 | /// @private 15 | function _empty() { 16 | return !this.x && !this.y && !this.blur && !this.spread; 17 | } 18 | 19 | /// @private 20 | function _getFilterStyle() { 21 | var style = this.x + "px " + this.y + "px " + this.blur + "px " 22 | if (this.spread > 0) 23 | style += this.spread + "px " 24 | style += $core.Color.normalize(this.color) 25 | return style 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/System.qml: -------------------------------------------------------------------------------- 1 | ///object for storing general info about device and platform 2 | Object { 3 | property string userAgent; ///< browser userAgent value 4 | property string language; ///< platform language 5 | property string browser; ///< browser name 6 | property string vendor; ///< current vendor name 7 | property string os; ///< operation system name 8 | property bool webkit; ///< webkit supported flag 9 | property bool support3dTransforms; ///< CSS transforms3d supported flag 10 | property bool supportTransforms; ///< CSS transforms supported flag 11 | property bool supportTransitions; ///< CSS transitions supported flag 12 | property bool portrait: parent.width < parent.height; ///< portrait oriented screen flag 13 | property bool landscape: !portrait; ///< landscape oriented screen flag 14 | property bool pageActive: true; ///< page active flag 15 | property int screenWidth; ///< device screen width value 16 | property int screenHeight; ///< device screen height value 17 | property int contextWidth: context.width; ///< @private 18 | property int contextHeight: context.height; ///< @private 19 | property int resolutionWidth; ///< app screen width from manifest 20 | property int resolutionHeight; ///< app screen height from manifest 21 | property enum device { Desktop, Tv, Mobile }; ///< device type enumeration, values: Desktop, Tv or Mobile 22 | property enum layoutType { MobileS, MobileM, MobileL, Tablet, Laptop, LaptopL, Laptop4K }; ///< layout type enumeration, values: MobileS, MobileM, MobileL, Tablet, Laptop, LaptopL and Laptop4K 23 | property bool virtualKeyboard: device === System.Tv || device === System.Mobile; ///< used to tweak components which use on-screen keyboard 24 | property bool tapHighlighted: os === "android" || os === "androidttk" || os === "hisense" || os === "ekt" || os === "sagem"; 25 | property bool selectByMouse: !(tapHighlighted || os === "webos"); 26 | 27 | /// @private 28 | onContextWidthChanged: { this._updateLayoutType() } 29 | /// @private 30 | onContextHeightChanged: { this._updateLayoutType() } 31 | 32 | /// @private 33 | _updateLayoutType: { 34 | if (!this.contextWidth || !this.contextHeight) 35 | return 36 | var min = this.contextWidth;// < this.contextHeight ? this.contextWidth : this.contextHeight 37 | 38 | if (min <= 320) 39 | this.layoutType = this.MobileS 40 | else if (min <= 375) 41 | this.layoutType = this.MobileM 42 | else if (min <= 425) 43 | this.layoutType = this.MobileL 44 | else if (min <= 768) 45 | this.layoutType = this.Tablet 46 | else if (this.contextWidth <= 1024) 47 | this.layoutType = this.Laptop 48 | else if (this.contextWidth <= 1440) 49 | this.layoutType = this.LaptopL 50 | else 51 | this.layoutType = this.Laptop4K 52 | } 53 | 54 | /// @private 55 | constructor: { 56 | this.vendor = $core.vendor 57 | this.device = $core.device 58 | this.os = $core.os 59 | 60 | this.browser = $core.browser 61 | this.userAgent = $core.userAgent 62 | this.language = $core.language 63 | 64 | var ctx = this._context 65 | ctx.language = this.language.replace('-', '_') 66 | this.webkit = this.userAgent.toLowerCase().indexOf('webkit') >= 0 67 | 68 | this.support3dTransforms = ctx.backend.capabilities.csstransforms3d || false 69 | this.supportTransforms = ctx.backend.capabilities.csstransforms || false 70 | this.supportTransitions = ctx.backend.capabilities.csstransitions || false 71 | 72 | this.resolutionWidth = $manifest$resolutionWidth 73 | this.resolutionHeight = $manifest$resolutionHeight 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /core/Text.qml: -------------------------------------------------------------------------------- 1 | /// item with text 2 | Item { 3 | property string text; ///< text to be displayed 4 | property color color; ///< color of the text 5 | property lazy shadow: Shadow { } ///< text shadow object 6 | property lazy font: Font { } ///< text font object 7 | property enum horizontalAlignment { AlignLeft, AlignRight, AlignHCenter, AlignJustify }; ///< text horizontal alignment 8 | property enum verticalAlignment { AlignTop, AlignBottom, AlignVCenter }; ///< text vertical alignment 9 | property enum wrapMode { NoWrap, WordWrap, WrapAnywhere, Wrap }; ///< multiline text wrap mode 10 | property enum textFormat { Html, Text }; ///< only html or text for now 11 | property int paintedWidth; ///< real width of the text without any layout applied 12 | property int paintedHeight; ///< real height of this text without any layout applied 13 | width: paintedWidth; 14 | height: paintedHeight; 15 | property bool offlineLayout; 16 | 17 | ///@private 18 | constructor: { 19 | this._context.backend.initText(this) 20 | if (this.text.length > 0) 21 | this._setText(this.text) 22 | } 23 | 24 | function getClass() { return 'core-text' } 25 | 26 | function registerStyle(style, tag) { 27 | style.addRule(tag, {'width': 'auto', 'height': 'auto'}) 28 | } 29 | 30 | ///@private 31 | function _scheduleUpdateSize() { 32 | this._context.delayedAction('text:update-size', this, this._updateSizeImpl) 33 | } 34 | 35 | ///@private 36 | function _setText(html) { 37 | this._context.backend.setText(this, html) 38 | } 39 | 40 | ///@private 41 | function onChanged (name, callback) { 42 | if (!this._updateSizeNeeded) { 43 | switch(name) { 44 | case "right": 45 | case "width": 46 | case "bottom": 47 | case "height": 48 | case "verticalCenter": 49 | case "horizontalCenter": 50 | this._enableSizeUpdate() 51 | } 52 | } 53 | $core.Item.prototype.onChanged.apply(this, arguments); 54 | } 55 | 56 | ///@private 57 | function on(name, callback) { 58 | if (!this._updateSizeNeeded) { 59 | if (name === 'newBoundingBox') 60 | this._enableSizeUpdate() 61 | } 62 | $core.Item.prototype.on.apply(this, arguments) 63 | } 64 | 65 | ///@private 66 | function _updateStyle() { 67 | if (this.shadow && !this.shadow._empty()) 68 | this.style('text-shadow', this.shadow._getFilterStyle()) 69 | else 70 | this.style('text-shadow', '') 71 | $core.Item.prototype._updateStyle.apply(this, arguments) 72 | } 73 | 74 | ///@private 75 | function _enableSizeUpdate() { 76 | this._updateSizeNeeded = true 77 | this._updateSize() 78 | } 79 | 80 | onRecursiveVisibleChanged: { 81 | if (value && !this.offlineLayout) 82 | this._updateSize() 83 | } 84 | 85 | ///@private 86 | function _updateSize() { 87 | if ((this.offlineLayout || this.recursiveVisible) && (this._updateSizeNeeded || this.clip)) 88 | this._scheduleUpdateSize() 89 | } 90 | 91 | ///@private 92 | function _updateSizeImpl() { 93 | if (this.text.length === 0) { 94 | this.paintedWidth = 0 95 | this.paintedHeight = 0 96 | return 97 | } 98 | 99 | this._context.backend.layoutText(this) 100 | } 101 | 102 | onTextChanged: { this._setText(value); this._updateSize() } 103 | onColorChanged: { this.style('color', $core.Color.normalize(value)) } 104 | onWidthChanged: { this._updateSize() } 105 | onHeightChanged: { this._updateSize() } 106 | 107 | onVerticalAlignmentChanged: { 108 | this._enableSizeUpdate() 109 | if ($manifest$requireVerticalTextAlignmentStyle) { 110 | switch(value) { 111 | case this.AlignTop: this.style('-pure-text-vertical-align', 'top'); break 112 | case this.AlignVCenter: this.style('-pure-text-vertical-align', 'middle'); break 113 | case this.AlignBottom: this.style('-pure-text-vertical-align', 'bottom'); break 114 | } 115 | } 116 | } 117 | 118 | onHorizontalAlignmentChanged: { 119 | switch(value) { 120 | case this.AlignLeft: this.style('text-align', 'left'); break 121 | case this.AlignRight: this.style('text-align', 'right'); break 122 | case this.AlignHCenter: this.style('text-align', 'center'); break 123 | case this.AlignJustify: this.style('text-align', 'justify'); break 124 | } 125 | } 126 | 127 | function _updateWSHandling() { 128 | var text = this.textFormat === this.Text 129 | switch(this.wrapMode) { 130 | case this.NoWrap: 131 | this.style({'white-space': text? 'pre': 'nowrap', 'word-break': '' }) 132 | break 133 | case this.Wrap: 134 | case this.WordWrap: 135 | this.style({'white-space': text? 'pre-wrap': 'normal', 'word-break': '' }) 136 | break 137 | case this.WrapAnywhere: 138 | this.style({ 'white-space': text? 'pre-wrap': 'normal', 'word-break': 'break-all' }) 139 | break 140 | } 141 | this._updateSize(); 142 | } 143 | 144 | onTextFormatChanged: { 145 | this._updateWSHandling() 146 | } 147 | 148 | onWrapModeChanged: { 149 | this._updateWSHandling() 150 | } 151 | 152 | onOfflineLayoutChanged: { 153 | if (value) 154 | this._enableSizeUpdate() 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /core/Timer.qml: -------------------------------------------------------------------------------- 1 | /// class handles periodic tasks 2 | Object { 3 | signal triggered; ///< this signal triggered when timer fires 4 | property int interval: 1000; ///< interval, ms 5 | property bool repeat; ///< makes this timer periodic 6 | property bool running; ///< current timer status, true - running, false - paused 7 | property bool triggeredOnStart; ///< fire timer's signal on start or activation 8 | 9 | constructor: { 10 | this._trigger = this._context.wrapNativeCallback(this.triggered.bind(this)) 11 | } 12 | 13 | /// restart timer, activate if stopped 14 | restart: { this.stop(); this.start(); } 15 | 16 | /// stops timer 17 | stop: { this.running = false } 18 | 19 | /// starts timer 20 | start: { this.running = true } 21 | 22 | /// @private 23 | onTriggered: { 24 | if (!this.repeat && (!this.triggeredOnStart || this._triggered)) 25 | this.running = false 26 | this._triggered = true 27 | } 28 | 29 | /// @private 30 | onCompleted: { 31 | if (this.running && this.triggeredOnStart) 32 | this.triggered() 33 | } 34 | 35 | onRunningChanged: { 36 | this._restart() 37 | if (value && this.triggeredOnStart) { 38 | this._triggered = false 39 | this.triggered() 40 | } 41 | } 42 | 43 | onIntervalChanged, 44 | onRepeatChanged: { this._restart() } 45 | 46 | /// @private 47 | function discard() { 48 | this._clear() 49 | $core.Object.prototype.discard.apply(this) 50 | } 51 | 52 | /// @private 53 | function _clear() { 54 | if (this._timeout) { 55 | clearTimeout(this._timeout); 56 | this._timeout = undefined; 57 | } 58 | if (this._interval) { 59 | clearInterval(this._interval); 60 | this._interval = undefined; 61 | } 62 | } 63 | 64 | /// @private 65 | function _restart() { 66 | this._clear() 67 | 68 | if (!this.running) 69 | return; 70 | 71 | //log("starting timer", this.interval, this.repeat); 72 | var self = this 73 | var context = self._context 74 | if (this.repeat) 75 | this._interval = setInterval(this._trigger, this.interval); 76 | else 77 | this._timeout = setTimeout(this._trigger, this.interval); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /core/Transform.qml: -------------------------------------------------------------------------------- 1 | /// class controlling object transformation 2 | Object { 3 | property int perspective; ///< perspective transformation 4 | property int translateX; ///< x-translate 5 | property int translateY; ///< y-translate 6 | property int translateZ; ///< z-translate 7 | property real rotateX; ///< x-axis rotation 8 | property real rotateY; ///< y-axis rotation 9 | property real rotateZ; ///< z-axis rotation 10 | property real rotate; ///< rotate relative transform point 11 | property real scaleX; ///< horizontal scale 12 | property real scaleY; ///< vertical scale 13 | property real skewX; ///< horizontal skew 14 | property real skewY; ///< vertical skew 15 | 16 | ///@private 17 | constructor: { this._transforms = new $core.transform.Transform() } 18 | 19 | onPerspectiveChanged: { this._transforms.add('perspective', value, 'px'); this._updateTransform() } 20 | onTranslateXChanged: { this._transforms.add('translateX', value, 'px'); this._updateTransform() } 21 | onTranslateYChanged: { this._transforms.add('translateY', value, 'px'); this._updateTransform() } 22 | onTranslateZChanged: { this._transforms.add('translateZ', value, 'px'); this._updateTransform() } 23 | onRotateXChanged: { this._transforms.add('rotateX', value, 'deg'); this._updateTransform() } 24 | onRotateYChanged: { this._transforms.add('rotateY', value, 'deg'); this._updateTransform() } 25 | onRotateZChanged: { this._transforms.add('rotateZ', value, 'deg'); this._updateTransform() } 26 | onRotateChanged: { this._transforms.add('rotate', value, 'deg'); this._updateTransform() } 27 | onScaleXChanged: { this._transforms.add('scaleX', value); this._updateTransform() } 28 | onScaleYChanged: { this._transforms.add('scaleY', value); this._updateTransform() } 29 | onSkewXChanged: { this._transforms.add('skewX', value, 'deg'); this._updateTransform() } 30 | onSkewYChanged: { this._transforms.add('skewY', value, 'deg'); this._updateTransform() } 31 | 32 | function _updateTransform() { 33 | this.parent.style('transform', this._transforms) 34 | } 35 | 36 | ///@private instead of animating transform property in Item, animate each property in Transform object 37 | ///unfortunately animations are not const, though it's a good idea to make them so (and save on animation instances) 38 | ///this function is meant to be called from non-html backends to animate transformations. 39 | ///see backend.js in platform/pure.femto for details 40 | function _animateAll(animation) { 41 | var transform = this 42 | var transform_properties = [ 43 | 'perspective', 44 | 'translateX', 'translateY', 'translateZ', 45 | 'rotateX', 'rotateY', 'rotateZ', 'rotate', 46 | 'scaleX', 'scaleY', 47 | 'skewX', 'skewY' 48 | ] 49 | transform_properties.forEach(function(transform_property) { 50 | var property_animation = new $core.Animation(transform) 51 | $core.core.createObject(property_animation) 52 | property_animation.delay = animation.delay 53 | property_animation.duration = animation.duration 54 | property_animation.easing = animation.easing 55 | 56 | transform.setAnimation(transform_property, property_animation) 57 | }) 58 | } 59 | 60 | ///@private 61 | function getAnimation(name) { 62 | var animation = $core.Object.prototype.getAnimation.call(this, name) 63 | if (!animation) { 64 | animation = $core.Object.prototype.getAnimation.call(this.parent, 'transform') 65 | } 66 | return animation 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /core/WheelMixin.qml: -------------------------------------------------------------------------------- 1 | /// mixin for wheel events 2 | BaseMixin { 3 | ///@private 4 | constructor: { 5 | this._bindWheel(this.enabled) 6 | } 7 | 8 | ///@private 9 | function _bindWheel(value) { 10 | if (value && !this._wheelBinder) { 11 | this._wheelBinder = new $core.EventBinder(this.parent.element) 12 | this._wheelBinder.on('wheel', $core.createSignalForwarder(this.parent, 'wheel').bind(this)) 13 | this._wheelBinder.on('mousewheel', $core.createSignalForwarder(this.parent, 'wheel').bind(this)) 14 | } 15 | if (this._wheelBinder) 16 | this._wheelBinder.enable(value) 17 | } 18 | 19 | ///@private 20 | onEnabledChanged: { this._bindWheel(value) } 21 | } 22 | -------------------------------------------------------------------------------- /core/gradient.js: -------------------------------------------------------------------------------- 1 | var GradientStop = function(color, position) { 2 | this.color = $core.Color.normalize(color) 3 | this.position = position 4 | } 5 | 6 | var GradientStopPrototype = GradientStop.prototype 7 | GradientStopPrototype.constructor = GradientStop 8 | 9 | GradientStopPrototype.toString = function() { 10 | return this.color + " " + Math.floor(100 * this.position) + "%" 11 | } 12 | 13 | var Gradient = function(orientation, type) { 14 | this.orientation = orientation 15 | this.stops = [] 16 | this.type = type 17 | } 18 | 19 | var GradientPrototype = Gradient.prototype 20 | GradientPrototype.constructor = Gradient 21 | 22 | GradientPrototype.add = function(stop) { 23 | this.stops.push(stop) 24 | } 25 | 26 | GradientPrototype.toString = function() { 27 | if (this.type === $core.Gradient.Linear) { 28 | return 'linear-gradient(' + this.orientation + ',' + this.stops.join() + ')' 29 | } 30 | else if (this.type === $core.Gradient.Conical) { 31 | var gradientString = 'conic-gradient(from ' + this.orientation 32 | for(var i = 0; i < this.stops.length; ++i) { 33 | var stop = this.stops[i] 34 | gradientString += ', ' + stop.color.hex() + ' ' + 2 * Math.PI * stop.position + 'rad' 35 | } 36 | gradientString += ')' 37 | return gradientString; 38 | } 39 | 40 | throw new Error("Gradient Type " + this.type + " is not supported") 41 | return ""; 42 | } 43 | 44 | exports.GradientStop = GradientStop 45 | exports.Gradient = Gradient 46 | -------------------------------------------------------------------------------- /core/model.js: -------------------------------------------------------------------------------- 1 | var ModelUpdateNothing = 0 2 | var ModelUpdateInsert = 1 3 | var ModelUpdateRemove = 2 4 | var ModelUpdateUpdate = 3 5 | var ModelUpdateFinish = 4 6 | 7 | exports.ModelUpdate = function() { 8 | this.rows = [] 9 | } 10 | exports.ModelUpdate.prototype.constructor = exports.ModelUpdate 11 | 12 | exports.ModelUpdate.prototype.reset = function(model) { 13 | var n = model.count 14 | var rows = this.rows = new Array(n) 15 | for (var i = 0; i < n; ++i) 16 | rows[i] = [i, true] 17 | } 18 | 19 | exports.ModelUpdate.prototype.insert = function(model, begin, end) { 20 | if (begin >= end) 21 | return 22 | 23 | var d = end - begin 24 | var rows = this.rows 25 | var args = [begin, 0] 26 | for(var i = 0; i < d; ++i) 27 | args.push([begin + i, true]) 28 | rows.splice.apply(rows, args) 29 | if (rows.length != model.count) 30 | throw new Error('unbalanced insert ' + rows.length + ' + [' + begin + '-' + end + '], model reported ' + model.count) 31 | } 32 | 33 | exports.ModelUpdate.prototype.remove = function(model, begin, end) { 34 | if (begin >= end) 35 | return 36 | 37 | var d = end - begin 38 | var rows = this.rows 39 | rows.splice(begin, d) 40 | if (rows.length != model.count) 41 | throw new Error('unbalanced remove ' + rows.length + ' + [' + begin + '-' + end + '], model reported ' + model.count) 42 | } 43 | 44 | exports.ModelUpdate.prototype.update = function(model, begin, end) { 45 | if (begin >= end) 46 | return 47 | 48 | var rows = this.rows; 49 | for(var i = begin; i < end; ++i) 50 | rows[i][1] = true 51 | } 52 | 53 | exports.ModelUpdate.prototype.clear = function() { 54 | this.rows = [] 55 | } 56 | 57 | exports.ModelUpdate.prototype.apply = function(view, skipCheck) { 58 | var rows = this.rows 59 | var currentRange = ModelUpdateNothing 60 | var currentRangeStartedAt = 0 61 | var currentRangeSize = 0 62 | var updated = false 63 | 64 | //log("APPLY ", rows) 65 | 66 | var apply = function(range, index, size) { 67 | if (size === undefined) 68 | size = 1 69 | 70 | if (currentRange === range) { 71 | currentRangeSize += size 72 | return 73 | } 74 | 75 | if (currentRangeSize > 0) { 76 | switch(currentRange) { 77 | case ModelUpdateNothing: 78 | break 79 | case ModelUpdateUpdate: 80 | updated = true 81 | view._updateItems(currentRangeStartedAt, currentRangeStartedAt + currentRangeSize) 82 | break 83 | case ModelUpdateInsert: 84 | updated = true 85 | view._insertItems(currentRangeStartedAt, currentRangeStartedAt + currentRangeSize) 86 | break 87 | case ModelUpdateRemove: 88 | updated = true 89 | view._removeItems(currentRangeStartedAt, currentRangeStartedAt + currentRangeSize) 90 | break 91 | } 92 | } 93 | 94 | currentRange = range 95 | currentRangeStartedAt = index 96 | currentRangeSize = size 97 | } 98 | 99 | var src_n = rows.length 100 | var dst_n = view._items.length 101 | var offset = 0 102 | for(var src = 0; src < src_n; ) { 103 | var row = rows[src] 104 | var dst = row[0] + offset 105 | if (src >= dst_n) { 106 | apply(ModelUpdateInsert, src, src_n - dst_n) 107 | break 108 | } else if (dst === src) { 109 | apply(row[1] || offset !== 0? ModelUpdateUpdate: ModelUpdateNothing, src) 110 | if (offset !== 0) 111 | view._updateDelegateIndex(src) 112 | ++src 113 | ++dst 114 | } else if (dst > src) { 115 | //removing gap 116 | var d = dst - src 117 | apply(ModelUpdateRemove, src, d) 118 | offset += -d 119 | } else { //dst < src 120 | var d = src - dst 121 | if (currentRange === ModelUpdateUpdate && d == currentRangeSize) { 122 | //check here if we have an equivalent range of update, 123 | //signal insert first instead of update (on the next loop iteration) 124 | offset += d 125 | currentRange = ModelUpdateInsert 126 | } else { 127 | offset += d 128 | src += d 129 | apply(ModelUpdateInsert, dst + d, d) 130 | } 131 | } 132 | } 133 | apply(ModelUpdateFinish, dst_n) 134 | 135 | dst_n = view._items.length //update length 136 | if (dst_n > src_n) { 137 | view._removeItems(src_n, dst_n) 138 | } else if (src_n > dst_n) { 139 | view._insertItems(dst_n, src_n) 140 | } 141 | if (!skipCheck && view._items.length != src_n ) 142 | throw new Error('unbalanced items update, view: ' + view._items.length + ', update:' + src_n) 143 | 144 | for(var i = 0; i < src_n; ++i) { 145 | var row = rows[i] 146 | row[0] = i 147 | row[1] = false 148 | } 149 | return updated 150 | } 151 | 152 | var ArrayModelWrapper = exports.ArrayModelWrapper = function (data) { this.data = data; this.count = data.length } 153 | ArrayModelWrapper.prototype.get = function(idx) { return { value: this.data[idx] } } 154 | ArrayModelWrapper.prototype.on = function() { } 155 | ArrayModelWrapper.prototype.removeListener = function() { } 156 | -------------------------------------------------------------------------------- /core/transform.js: -------------------------------------------------------------------------------- 1 | var Value = function(value, unit) { 2 | this.value = value 3 | this.unit = unit 4 | } 5 | 6 | var ValuePrototype = Value.prototype 7 | ValuePrototype.constructor = Value 8 | 9 | ValuePrototype.toString = function() { 10 | var unit = this.unit 11 | return unit != undefined? this.value + unit: this.value 12 | } 13 | 14 | var Transform = function() { 15 | this.transforms = {} 16 | } 17 | 18 | var TransformPrototype = Transform.prototype 19 | TransformPrototype.constructor = Transform 20 | 21 | TransformPrototype.add = function(name, value, unit) { 22 | this.transforms[name] = new Value(value, unit) 23 | } 24 | 25 | TransformPrototype.toString = function() { 26 | var transforms = this.transforms 27 | var str = '' 28 | for(var name in transforms) { 29 | var value = transforms[name] 30 | str += name + '(' + value + ') ' 31 | } 32 | return str 33 | } 34 | 35 | exports.Transform = Transform 36 | 37 | -------------------------------------------------------------------------------- /generate-gamepad-mappings: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.7 2 | 3 | import argparse 4 | import json 5 | 6 | 7 | def get_max_size(val): 8 | return { 9 | 'head': 4, 10 | 'axes': 13, 11 | 'button': 45 12 | }.get(val, 0) 13 | 14 | 15 | def get_key_name(val): 16 | return { 17 | 'b': 'button', 18 | 'a': 'axes', 19 | 'h': 'head' 20 | }.get(val, val) 21 | 22 | 23 | def get_button_name(name): 24 | return { 25 | "dpup": "up", 26 | "dpright": "right", 27 | "dpleft": "left", 28 | "dpdown": "down", 29 | "lefty": "leftStickY", 30 | "righty": "rightStickY", 31 | "leftx": "leftStickX", 32 | "rightx": "rightStickX", 33 | "righttrigger": "rightTrigger", 34 | "leftshoulder": "leftBumper", 35 | "rightshoulder": "rightBumper", 36 | "rightstick": "rightStick", 37 | "lefttrigger": "leftTrigger", 38 | "leftstick": "leftStick" 39 | }.get(name, name) 40 | 41 | 42 | def save(filename, data): 43 | with open(filename, 'w') as file_: 44 | file_.write(data) 45 | 46 | 47 | def change_endian(hexString): 48 | return ''.join(sum([(c,d,a,b) for a,b,c,d in zip(*[iter(hexString)]*4)], ())) 49 | 50 | 51 | def parse_file(filePath, resPath): 52 | with open(filePath) as f: 53 | content = f.readlines() 54 | result = {} 55 | 56 | platform = "" 57 | for line in content: 58 | line = line.strip(" \t\r\n") 59 | 60 | if len(line) == 0: 61 | continue 62 | 63 | if line[0] == '#': # skip OS lines 64 | platform = line[2:] 65 | continue 66 | 67 | tokens = line.split(',') 68 | if len(tokens) <= 1: 69 | continue 70 | 71 | gamepad = {} 72 | vendor = tokens[0][:16] 73 | product = tokens[0][16:32] 74 | if platform.lower() == "linux": 75 | vendor = str(int(change_endian(vendor)[8:12], 16)) 76 | product = str(int(change_endian(product)[8:12], 16)) 77 | else: 78 | vendor = str(int(change_endian(vendor)[0:4], 16)) 79 | product = str(int(change_endian(product)[0:4], 16)) 80 | id = vendor + ":" + product 81 | 82 | gamepad['name'] = tokens[1] 83 | gamepad['mapping'] = {} 84 | gamepad['mapping']['button'] = [0] * 45 # 45 - max button number 85 | gamepad['mapping']['axes'] = [0] * 13 # 13 - max axes count 86 | max_button = 0 87 | max_axes = 0 88 | for t in tokens: 89 | if len(t) == 0: 90 | continue 91 | 92 | item = t.split(':') 93 | if len(item) <= 1 or item[0] == "platform" or len(item[0]) == 0 or len(item[1]) == 0: 94 | continue 95 | 96 | key = get_key_name(item[1][0]) 97 | if key == "head": 98 | continue 99 | 100 | val = get_button_name(item[0]) 101 | idx = int(item[1][1:]) 102 | gamepad['mapping'][key][idx] = val 103 | if key == "button": 104 | if max_button < idx: 105 | max_button = idx 106 | elif key == "axes": 107 | if max_axes < idx: 108 | max_axes = idx 109 | 110 | gamepad['mapping']['button'] = gamepad['mapping']['button'][:max_button + 1] if max_button > 0 else [] 111 | gamepad['mapping']['axes'] = gamepad['mapping']['axes'][:max_axes + 1] if max_axes > 0 else [] 112 | 113 | result[id] = gamepad 114 | save(resPath, json.dumps(result, sort_keys=True)) 115 | 116 | 117 | if __name__ == '__main__': 118 | parser = argparse.ArgumentParser() 119 | 120 | # input file example 121 | # https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt 122 | parser.add_argument('file') 123 | parser.add_argument('output') 124 | args = parser.parse_args() 125 | parse_file(args.file, args.output) 126 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pureqml-core", 3 | "version": "1.2.0", 4 | "description": "QML to HTML5 translator, both for mobile and desktop targets.", 5 | "keywords": [ 6 | "qml", 7 | "transpiler", 8 | "translator", 9 | "compiler", 10 | "application", 11 | "mobile", 12 | "templater", 13 | "ui", 14 | "ux", 15 | "cross-platform" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/pureqml/qmlcore" 20 | }, 21 | "bin": { 22 | "pureqml-build": "build" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/pureqml/qmlcore/issues" 26 | }, 27 | "scripts": { 28 | "test": "mocha" 29 | }, 30 | "author": "PureQML team", 31 | "license": "MIT", 32 | "dependencies": { 33 | "mocha": "^11.0.1", 34 | "sinon": "^12.0.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /partners.json: -------------------------------------------------------------------------------- 1 | { 2 | "pureqml-team": { 3 | "name": "PureQML Team", 4 | "engine": "Octobears Private", 5 | "status": "paper" 6 | }, 7 | 8 | "free": { 9 | "name": "General Public", 10 | "engine": "No-Partnership" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /platform/android/.core.js: -------------------------------------------------------------------------------- 1 | log = console.log.bind(console) 2 | 3 | if (navigator.userAgent.indexOf('Android') >= 0) { 4 | _globals.core.__deviceBackend = function() { return _globals.android.device } 5 | 6 | log("Android detected") 7 | exports.core.vendor = "google" 8 | exports.core.device = 2 9 | exports.core.os = "android" 10 | 11 | exports.core.keyCodes = { 12 | 4: 'Back', 13 | 13: 'Select', 14 | 32: 'Space', 15 | 33: 'PageUp', 16 | 34: 'PageDown', 17 | 37: 'Left', 18 | 38: 'Up', 19 | 39: 'Right', 20 | 40: 'Down', 21 | 48: '0', 22 | 49: '1', 23 | 50: '2', 24 | 51: '3', 25 | 52: '4', 26 | 53: '5', 27 | 54: '6', 28 | 55: '7', 29 | 56: '8', 30 | 57: '9', 31 | 179: 'Pause', 32 | 112: 'Red', 33 | 113: 'Green', 34 | 114: 'Yellow', 35 | 115: 'Blue' 36 | } 37 | 38 | if (window.cordova) { 39 | document.addEventListener("backbutton", function(e) { 40 | var event = new KeyboardEvent("keydown", { bubbles : true }); 41 | Object.defineProperty(event, 'keyCode', { get : function() { return 4; } }) 42 | document.dispatchEvent(event); 43 | }, false); 44 | } else { 45 | log("'cordova' not defined. 'Back' button will be unhandable. It looks like you forget to include 'cordova.js'") 46 | } 47 | 48 | document.addEventListener("deviceready", function() { 49 | _globals._context.system.vendor = device.manufacturer 50 | }, false); 51 | 52 | log("Android initialized") 53 | 54 | exports.closeApp = function() { 55 | navigator.app.exitApp(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /platform/android/.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "requires": ["html5", "web", "video.html5"], 3 | "templates": ["*.html", "config.xml"], 4 | "strict": false 5 | } 6 | -------------------------------------------------------------------------------- /platform/android/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | 5 | import argparse 6 | import os 7 | 8 | 9 | def build(app, title, release): 10 | os.system('rm -rf %s' %app) 11 | res = os.system('cordova create %s com.%s.app %s' %(app, app, title)) 12 | if res != 0: 13 | print("Failed to create android app") 14 | return 15 | os.system('rsync -a ./ %s/www --exclude=%s ' %(app,app)) 16 | os.system('cp androidIcon.png %s' %(app)) 17 | os.system('cp config.xml %s' %(app)) 18 | os.chdir(app) 19 | 20 | os.system('cordova platform add android') 21 | os.system('cordova plugin add cordova-plugin-streaming-media') 22 | os.system('cordova plugin add cordova-plugin-device') 23 | os.system('cordova plugin add cordova-plugin-screen-orientation') 24 | 25 | if release: 26 | build = 'cordova build android --release -- ' 27 | # TODO: pass release parameters 28 | # os.system(build + '--keystore={{androidBuild.keystore}} --storePassword={{androidBuild.storePassword}} --alias={{androidBuild.alias}} --password={{androidBuild.password}}') 29 | else: 30 | os.system('cordova build android') 31 | 32 | os.chdir('..') 33 | 34 | 35 | parser = argparse.ArgumentParser('pureqml cordova android build tool') 36 | parser.add_argument('--app', '-a', help='application name', default="app") 37 | parser.add_argument('--title', '-t', help='application title', default="App") 38 | parser.add_argument('--release', '-r', help='build release apk', default=False) 39 | args = parser.parse_args() 40 | 41 | 42 | res = os.system('cordova --version') 43 | if res == 0: 44 | build(args.app, args.title, args.release) 45 | else: 46 | print('Install "cordova" first: https://cordova.apache.org/docs/en/latest/guide/cli/') 47 | -------------------------------------------------------------------------------- /platform/android/device.js: -------------------------------------------------------------------------------- 1 | var Device = function(ui) { 2 | function onDeviceReady() { 3 | ui.deviceId = device.uuid 4 | ui.modelName = device.model 5 | ui.firmware = device.version 6 | ui.runtime = 'cordova' 7 | } 8 | ui._context.document.on('deviceready', onDeviceReady); 9 | 10 | var screen = window.screen 11 | if (screen) { 12 | ui.lockOrientation = function(orientation) { screen.orientation.lock(orientation) }.bind(ui) 13 | ui.unlockOrientation = function() { screen.orientation.unlock() }.bind(ui) 14 | } else { 15 | log("'screen' is undefined, add 'cordova-plugin-screen-orientation' plugin") 16 | } 17 | } 18 | 19 | exports.createDevice = function(ui) { 20 | return new Device(ui) 21 | } 22 | 23 | exports.Device = Device 24 | -------------------------------------------------------------------------------- /platform/android/dist/androidIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/android/dist/androidIcon.png -------------------------------------------------------------------------------- /platform/android/dist/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ title }} 4 | 5 | {{ description | default('Override app description in manifest') }} 6 | 7 | 8 | {{ author.name | default('John Doe') if author is defined else 'John Doe' }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /platform/android/dist/index.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block head %} 3 | 4 | 5 | 6 | {{ super() }} 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /platform/commercial/.core.js: -------------------------------------------------------------------------------- 1 | /*** @using { commercial.PureQmlSplash } **/ 2 | -------------------------------------------------------------------------------- /platform/commercial/.manifest: -------------------------------------------------------------------------------- 1 | { "standalone": false } 2 | -------------------------------------------------------------------------------- /platform/commercial/PureQmlSplash.qml: -------------------------------------------------------------------------------- 1 | Rectangle { 2 | property bool showGlow; 3 | anchors.fill: context; 4 | color: "#212121"; 5 | 6 | Text { 7 | anchors.bottom: logo.top; 8 | anchors.horizontalCenter: parent.horizontalCenter; 9 | text: "powered by"; 10 | font.weight: 300; 11 | font.pixelSize: 36; 12 | color: "#eee"; 13 | } 14 | 15 | Image { 16 | anchors.centerIn: parent; 17 | source: "res/pureqml-splash-shadow.png"; 18 | opacity: parent.showGlow ? 1.0 : 0.0; 19 | 20 | Behavior on opacity { Animation { duration: 3000; } } 21 | } 22 | 23 | Image { 24 | id: logo; 25 | anchors.centerIn: parent; 26 | source: "res/pureqml-splash-logo.png"; 27 | } 28 | 29 | Timer { 30 | interval: 3000; 31 | triggeredOnStart: true; 32 | running: true; 33 | repeat: true; 34 | 35 | onTriggered: { this.parent.showGlow = !this.parent.showGlow } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /platform/commercial/dist/res/pureqml-splash-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/commercial/dist/res/pureqml-splash-logo.png -------------------------------------------------------------------------------- /platform/commercial/dist/res/pureqml-splash-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/commercial/dist/res/pureqml-splash-shadow.png -------------------------------------------------------------------------------- /platform/core/.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "standalone": false, 3 | "properties": { 4 | "virtual": { 5 | "width": 1920, 6 | "height": 1080 7 | }, 8 | 9 | "disableAnimations": false, 10 | "requireExplicitRecursiveVisibilityStyle": true, 11 | "requireVerticalTextAlignmentStyle": true, 12 | 13 | "html5.prefix": "", 14 | 15 | "style.font" : { 16 | "family": "Arial", 17 | "pixelSize": 16, 18 | "pointSize": 0, 19 | "lineHeight": 1.2, 20 | "weight": 400 21 | }, 22 | 23 | "trace" : { 24 | "keys" : false, 25 | "focus": false, 26 | "listeners": false 27 | }, 28 | 29 | "log": { "disable": false }, 30 | 31 | "system.fingerprint": false 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /platform/debug.console.re/.core.js: -------------------------------------------------------------------------------- 1 | log = console.re.log.bind(console.re) 2 | -------------------------------------------------------------------------------- /platform/debug.console.re/.manifest: -------------------------------------------------------------------------------- 1 | { "requires": ["html5"] } 2 | -------------------------------------------------------------------------------- /platform/debug.console.re/dist/index.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block head %} 3 | 4 | {{ super() }} 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /platform/electronjs/.core.js: -------------------------------------------------------------------------------- 1 | exports.core.device = 0 2 | var userAgent = exports.core.userAgent.toLowerCase() 3 | if (userAgent.indexOf("linux")) { 4 | exports.core.os = "Linux" 5 | } else if (userAgent.indexOf("windows")) { 6 | exports.core.os = "Windows" 7 | } else if (userAgent.indexOf("mac")) { 8 | exports.core.os = "MacOS" 9 | exports.core.vendor = "Apple" 10 | } else { 11 | exports.core.os = "electronjs" 12 | } 13 | 14 | _globals.core.__deviceBackend = function() { return _globals.electronjs.device } 15 | -------------------------------------------------------------------------------- /platform/electronjs/.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "requires": ["html5", "web"], 3 | "templates": ["*.html", "main.js", "package.json"] 4 | } 5 | -------------------------------------------------------------------------------- /platform/electronjs/device.js: -------------------------------------------------------------------------------- 1 | var Device = function(ui) { 2 | var userAgent = _globals.core.userAgent.toLowerCase() 3 | var osToken = userAgent.substring(userAgent.indexOf("(") + 1, userAgent.indexOf(")")) 4 | var tokens = osToken.split(";") 5 | if (tokens.length > 0) 6 | ui.modelName = tokens[0] 7 | if (tokens.length > 1) 8 | ui.sdk = tokens[1] 9 | } 10 | 11 | exports.createDevice = function(ui) { 12 | return new Device(ui) 13 | } 14 | 15 | exports.Device = Device 16 | -------------------------------------------------------------------------------- /platform/electronjs/dist/main.js: -------------------------------------------------------------------------------- 1 | const {app, BrowserWindow} = require('electron') 2 | const path = require('path') 3 | 4 | let win 5 | 6 | function createWindow () { 7 | // Create the browser window. 8 | win = new BrowserWindow({ 9 | width: {{ resolutionWidth | default(1280) }}, 10 | height: {{ resolutionHeight | default(720) }}, 11 | webPreferences: { 12 | preload: path.join(__dirname, 'preload.js') 13 | } 14 | }) 15 | 16 | win.loadFile('index.html') 17 | 18 | // TODO: uncomment for debug 19 | // win.webContents.openDevTools() 20 | 21 | win.on('closed', function () { win = null }) 22 | } 23 | 24 | app.on('ready', createWindow) 25 | 26 | app.on('window-all-closed', function () { 27 | if (process.platform !== 'darwin') app.quit() 28 | }) 29 | 30 | app.on('activate', function () { 31 | if (win === null) 32 | createWindow() 33 | }) 34 | -------------------------------------------------------------------------------- /platform/electronjs/dist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ title | default('pureqml') }}", 3 | "version": "{{ version | default('1.0.0') }}", 4 | "description": "{{ description | default('') }}", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron ." 8 | }, 9 | "repository": "https://github.com/electron/electron-quick-start", 10 | "keywords": [ 11 | "Electron", 12 | "quick", 13 | "start", 14 | "tutorial", 15 | "demo" 16 | ], 17 | "author": "{{ author.name | default('Jhon Doe') if author is defined else 'Jhon Doe' }}", 18 | "license": "{{ license | default('MIT') }}", 19 | "devDependencies": { 20 | "electron": "^7.1.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /platform/electronjs/dist/preload.js: -------------------------------------------------------------------------------- 1 | // All of the Node.js APIs are available in the preload process. 2 | // It has the same sandbox as a Chrome extension. 3 | window.addEventListener('DOMContentLoaded', () => { 4 | const replaceText = (selector, text) => { 5 | const element = document.getElementById(selector) 6 | if (element) element.innerText = text 7 | } 8 | 9 | for (const type of ['chrome', 'node', 'electron']) { 10 | replaceText(`${type}-version`, process.versions[type]) 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /platform/electronjs/dist/renderer.js: -------------------------------------------------------------------------------- 1 | // This file is required by the index.html file and will 2 | // be executed in the renderer process for that window. 3 | // No Node.js APIs are available in this process because 4 | // `nodeIntegration` is turned off. Use `preload.js` to 5 | // selectively enable features needed in the rendering 6 | // process. 7 | -------------------------------------------------------------------------------- /platform/html5.canvas/.core.js: -------------------------------------------------------------------------------- 1 | _globals._backend = function() { return _globals.html5.canvas.backend } 2 | -------------------------------------------------------------------------------- /platform/html5.canvas/.manifest: -------------------------------------------------------------------------------- 1 | { "requires": ["html5", "pure"], "standalone": false } 2 | -------------------------------------------------------------------------------- /platform/html5.canvas/backend.js: -------------------------------------------------------------------------------- 1 | exports.capabilities = { } 2 | 3 | var html = null 4 | var runtime = null 5 | var rootItem = null 6 | var canvas = null 7 | 8 | var proxy = [ 9 | 'requestAnimationFrame', 'cancelAnimationFrame', 10 | 'enterFullscreenMode', 'exitFullscreenMode', 'inFullscreenMode', 11 | 'loadImage', 12 | //'initText', 'layoutText', //this should not be proxy, implement this backend methods 13 | ] 14 | 15 | exports.initImage = function(image) { 16 | html.initImage(image) 17 | image.element.ui = image 18 | image.element._image = image._image 19 | image.onChanged('status', function() { image.element.update() } ) 20 | } 21 | 22 | var Renderer = function(canvas) { 23 | this.canvas = canvas 24 | } 25 | 26 | Renderer.prototype.constructor = Renderer 27 | 28 | Renderer.prototype.setClip = function(rect) { 29 | this.canvas.rect(rect.l, rect.t, rect.width(), rect.height()) 30 | this.canvas.clip() 31 | } 32 | 33 | Renderer.prototype.fillRect = function(rect, color) { 34 | if (color.a < 1) 35 | return 36 | 37 | this.canvas.globalAlpha = color.a / 255 38 | this.canvas.fillStyle = color.rgba() 39 | this.canvas.fillRect(rect.l, rect.t, rect.width(), rect.height()) 40 | } 41 | 42 | Renderer.prototype.drawImage = function(rect, image, el) { 43 | var ui = el.ui 44 | this.canvas.globalAlpha = ui.opacity 45 | if (ui.sourceHeight > 0 && ui.sourceWidth > 0) { 46 | this.canvas.drawImage(image, 47 | 0, 0, ui.sourceWidth, ui.sourceHeight, 48 | rect.l, rect.t, ui.width, ui.height) 49 | } 50 | } 51 | 52 | exports.init = function(ctx) { 53 | log('init') 54 | html = _globals.html5.html 55 | runtime = _globals.pure.runtime 56 | 57 | proxy.forEach(function(name) { 58 | exports[name] = html[name] 59 | }) 60 | 61 | ctx.options.tag = 'canvas' 62 | html.init(ctx) 63 | ctx.canvasElement = ctx.element 64 | ctx.canvas = ctx.element.dom 65 | ctx._updatedItems = [] 66 | ctx.element = exports.createElement(ctx, ctx.getTag()) 67 | 68 | var resizeCanvas = function() { 69 | log('resizing canvas') 70 | var canvas = ctx.canvas 71 | canvas.setAttribute('width', ctx.width) 72 | canvas.setAttribute('height', ctx.height) 73 | ctx.renderer = new Renderer(canvas.getContext("2d")) 74 | runtime.renderFrame(ctx) 75 | } 76 | resizeCanvas() 77 | ctx.onChanged('width', resizeCanvas) 78 | ctx.onChanged('height', resizeCanvas) 79 | 80 | { 81 | var Element = runtime.Element 82 | Element.prototype.setHtml = function(html) { 83 | //log('setHtml stub') 84 | this.layoutText() 85 | } 86 | 87 | Element.prototype.layoutText = function() { 88 | //log('layout text stub') 89 | } 90 | } 91 | } 92 | 93 | exports.run = function(ctx) { 94 | log('calling redraw') 95 | ctx.canvasElement.style('visibility', 'visible') 96 | runtime.renderFrame(ctx) 97 | } 98 | 99 | exports.createElement = function(ctx, tag) { 100 | if (runtime === null) 101 | runtime = _globals.pure.runtime //fixme: this is called from StyleSheet too early (ctor?), fix initialisation order! 102 | if (html === null) 103 | html = _globals.html5.html 104 | 105 | if (tag === 'style') 106 | return new html.Element(ctx, tag) 107 | else 108 | return new runtime.Element(ctx, tag) 109 | } 110 | 111 | exports.initText = function(text) { 112 | var element = text.element 113 | element._offsetX = 0 114 | element._offsetY = 0 115 | element.ui = text 116 | element.update() 117 | } 118 | 119 | exports.layoutText = function(text) { 120 | //log('layoutText stub') 121 | } 122 | -------------------------------------------------------------------------------- /platform/html5.fingerprint/.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "standalone": false, 3 | "requires": ["html5"], 4 | "properties": { "system.fingerprint": true } 5 | } 6 | -------------------------------------------------------------------------------- /platform/html5.fingerprint/fingerprint.js: -------------------------------------------------------------------------------- 1 | var Fingerprint = function() { 2 | log("creating fingerprint") 3 | this.__visited = [] 4 | this.__text = '' 5 | } 6 | 7 | var FingerprintPrototype = Fingerprint.prototype 8 | 9 | FingerprintPrototype._update = function(value, name) { 10 | if (!value || this.__visited.indexOf(value) >= 0) 11 | return 12 | 13 | this.__visited.push(value) 14 | var type = typeof value 15 | if (type === 'object') { 16 | if ('length' in value) { 17 | for(var i = 0; i < value.length; ++i) { 18 | this._update(value[i]) 19 | } 20 | } else { 21 | for (var k in value) { 22 | this._update(value[k], k) 23 | } 24 | } 25 | return 26 | } else if (type === 'function') { 27 | return 28 | } 29 | 30 | //log("fingerprint update", value, name !== undefined? name: '') 31 | if (value !== undefined && value !== null) 32 | this.__text += String(value) 33 | this.__text += "\0" 34 | } 35 | 36 | FingerprintPrototype.update = function() { 37 | for(var i = 0; i < arguments.length; ++i) { 38 | this._update(arguments[i]) 39 | } 40 | } 41 | 42 | FingerprintPrototype.finalize = function() { 43 | var r = $html5.fingerprint.sha1.sha1(this.__text) 44 | this.__text = '' 45 | return r 46 | } 47 | 48 | exports.Fingerprint = Fingerprint 49 | -------------------------------------------------------------------------------- /platform/html5.fingerprint/sha1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Secure Hash Algorithm (SHA1) 4 | * http://www.webtoolkit.info/ 5 | * 6 | **/ 7 | 8 | var sha1 = function (msg) { 9 | function rotate_left(n,s) { 10 | var t4 = ( n<>>(32-s)); 11 | return t4; 12 | }; 13 | 14 | function lsb_hex(val) { 15 | var str=""; 16 | var i; 17 | var vh; 18 | var vl; 19 | 20 | for( i=0; i<=6; i+=2 ) { 21 | vh = (val>>>(i*4+4))&0x0f; 22 | vl = (val>>>(i*4))&0x0f; 23 | str += vh.toString(16) + vl.toString(16); 24 | } 25 | return str; 26 | }; 27 | 28 | function cvt_hex(val) { 29 | var str=""; 30 | var i; 31 | var v; 32 | 33 | for( i=7; i>=0; i-- ) { 34 | v = (val>>>(i*4))&0x0f; 35 | str += v.toString(16); 36 | } 37 | return str; 38 | }; 39 | 40 | 41 | function Utf8Encode(string) { 42 | string = string.replace(/\r\n/g,"\n"); 43 | var utftext = ""; 44 | 45 | for (var n = 0; n < string.length; n++) { 46 | 47 | var c = string.charCodeAt(n); 48 | 49 | if (c < 128) { 50 | utftext += String.fromCharCode(c); 51 | } 52 | else if((c > 127) && (c < 2048)) { 53 | utftext += String.fromCharCode((c >> 6) | 192); 54 | utftext += String.fromCharCode((c & 63) | 128); 55 | } 56 | else { 57 | utftext += String.fromCharCode((c >> 12) | 224); 58 | utftext += String.fromCharCode(((c >> 6) & 63) | 128); 59 | utftext += String.fromCharCode((c & 63) | 128); 60 | } 61 | 62 | } 63 | 64 | return utftext; 65 | }; 66 | 67 | var blockstart; 68 | var i, j; 69 | var W = new Array(80); 70 | var H0 = 0x67452301; 71 | var H1 = 0xEFCDAB89; 72 | var H2 = 0x98BADCFE; 73 | var H3 = 0x10325476; 74 | var H4 = 0xC3D2E1F0; 75 | var A, B, C, D, E; 76 | var temp; 77 | 78 | msg = Utf8Encode(msg); 79 | 80 | var msg_len = msg.length; 81 | 82 | var word_array = new Array(); 83 | for( i=0; i>>29 ); 111 | word_array.push( (msg_len<<3)&0x0ffffffff ); 112 | 113 | 114 | for ( blockstart=0; blockstart= 0) 28 | exports.core.browser = "Chromium" 29 | else if (exports.core.userAgent.indexOf('Chrome') >= 0) 30 | exports.core.browser = "Chrome" 31 | else if (exports.core.userAgent.indexOf('Opera') >= 0) 32 | exports.core.browser = "Opera" 33 | else if (exports.core.userAgent.indexOf('Firefox') >= 0) 34 | exports.core.browser = "Firefox" 35 | else if (exports.core.userAgent.indexOf('Safari') >= 0) 36 | exports.core.browser = "Safari" 37 | else if (exports.core.userAgent.indexOf('MSIE') >= 0) 38 | exports.core.browser = "IE" 39 | else if (exports.core.userAgent.indexOf('YaBrowser') >= 0) 40 | exports.core.browser = "Yandex" 41 | else 42 | exports.core.browser = '' 43 | 44 | 45 | _globals._backend = function() { return _globals.html5.html } 46 | _globals.core.__locationBackend = function() { return _globals.html5.location } 47 | _globals.core.__localStorageBackend = function() { return _globals.html5.localstorage } 48 | -------------------------------------------------------------------------------- /platform/html5/.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "standalone": false, 3 | "requires": ["core"], 4 | "properties": { 5 | "cssAutoClassificator": false, 6 | "cssDisableTransitions": false, 7 | "cssDisableTransformations": false, 8 | "expectRunContextEvent": false, 9 | "requireExplicitRecursiveVisibilityStyle": false, 10 | "requireVerticalTextAlignmentStyle": false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /platform/html5/Stylesheet.qml: -------------------------------------------------------------------------------- 1 | Object { 2 | constructor: { 3 | var context = this._context 4 | 5 | var style = this.style = context.createElement('style') 6 | style.dom.type = 'text/css' 7 | 8 | var divId = context.options.id 9 | 10 | var div = document.getElementById(context, divId) 11 | var topLevel = div === null 12 | 13 | var tapHighlighted = context.system.tapHighlighted 14 | 15 | //var textAdjust = window.Modernizr.prefixedCSS('text-size-adjust') + ": 100%; " 16 | style.setHtml( 17 | //"html { " + textAdjust + " }" + 18 | "div#" + divId + " { position: absolute; visibility: hidden; left: 0px; top: 0px; }" + 19 | (tapHighlighted ? this.mangleRule('div', "{ -webkit-tap-highlight-color: rgba(255, 255, 255, 0); -webkit-focus-ring-color: rgba(255, 255, 255, 0); outline: none; }") : "") + 20 | (topLevel? "body { padding: 0; margin: 0; border: 0px; overflow: hidden; }": "") + //fixme: do we need style here in non-top-level mode? 21 | this.mangleRule('video', "{ position: absolute; }") + //fixme: do we need position rule if it's item? 22 | this.mangleRule('img', "{ position: absolute; -webkit-touch-callout: none; }") 23 | ) 24 | var head = _globals.html5.html.getElement(context, 'head') 25 | head.append(style) 26 | head.updateStyle() 27 | 28 | this._addRule = _globals.html5.html.createAddRule(style.dom).bind(this) 29 | this._lastId = 0 30 | } 31 | 32 | function allocateClass(prefix) { 33 | var globalPrefix = $manifest$html5$prefix 34 | return (globalPrefix? globalPrefix: '') + prefix + '-' + this._lastId++ 35 | } 36 | 37 | function mangleSelector(selector) { 38 | var prefix = $manifest$html5$prefix 39 | if (prefix) 40 | return selector + '.' + prefix + 'core-item' 41 | else 42 | return selector 43 | } 44 | 45 | function mangleRule(selector, rule) { 46 | return this.mangleSelector(selector) + ' ' + rule + ' ' 47 | } 48 | 49 | function addRule(selector, rule) { 50 | this._addRule(selector, rule) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /platform/html5/cache.js: -------------------------------------------------------------------------------- 1 | var getTime = function() { return Math.floor(new Date().getTime() / 1000) } 2 | 3 | var Entry = function() { 4 | this.created = getTime() 5 | this.waiters = [] 6 | this.invoker = null 7 | } 8 | 9 | Entry.prototype.expired = function(ttl) { 10 | return getTime() - this.created >= ttl 11 | } 12 | 13 | Entry.prototype.set = function(result) { 14 | this.created = getTime() 15 | var invoker = this.invoker = _globals.core.safeCall(null, [result], function(ex) { log("cache entry callback failed: ", ex, ex.stack) }) 16 | while(this.waiters.length) { 17 | var waiters = this.waiters 18 | this.waiters = [] 19 | waiters.forEach(invoker) 20 | } 21 | this.waiters = null 22 | } 23 | 24 | Entry.prototype.wait = function(callback) { 25 | if (this.invoker !== null) 26 | this.invoker(callback) 27 | else 28 | this.waiters.push(callback) 29 | } 30 | 31 | var Cache = function(create, ttl) { 32 | if (!create) 33 | throw new Error("create callback is required") 34 | this._create = create 35 | this._ttl = ttl || 3600 36 | this._cache = {} 37 | setInterval(this.cleanup.bind(this), this._ttl / 2 * 1000) 38 | } 39 | 40 | Cache.prototype.get = function(key, callback) { 41 | var entry = this._cache[key] 42 | if (entry === undefined || entry.expired(this._ttl)) { 43 | this._cache[key] = entry = new Entry() 44 | this._create(key, function(result) { 45 | entry.set(result) 46 | }) 47 | } 48 | entry.wait(callback) 49 | } 50 | 51 | Cache.prototype.cleanup = function() { 52 | for(var k in this._cache) { 53 | var entry = this._cache[k] 54 | if (entry.expired(this._ttl)) { 55 | delete this._cache[k] 56 | } 57 | } 58 | } 59 | 60 | exports.Cache = Cache 61 | -------------------------------------------------------------------------------- /platform/html5/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block title %}{{ title }}{% endblock %} 5 | 6 | 7 | {% block head %}{% endblock %} 8 | 9 | 10 | 11 | {% block content %}{% endblock %} 12 | {% block mainscript %} 13 | 14 | {% endblock %} 15 | {% block footer %}{% endblock %} 16 | 17 | 18 | -------------------------------------------------------------------------------- /platform/html5/localstorage.js: -------------------------------------------------------------------------------- 1 | var LocalStorage = function(parent) { 2 | if (parent && parent.name !== undefined) { 3 | // TODO: implement properties sunchronization using parent._setProperty() and parent.ready() 4 | } 5 | this._storage = window.localStorage; 6 | if (!this._storage) 7 | throw new Error("no local storage support") 8 | } 9 | 10 | LocalStorage.prototype.get = function(name, callback, error) { 11 | var value = this._storage.getItem(name) 12 | if (value !== null) 13 | callback(value) 14 | else 15 | error(new Error('no item with name ' + name)) 16 | } 17 | 18 | LocalStorage.prototype.set = function(name, value) { 19 | this._storage.setItem(name, value) 20 | } 21 | 22 | LocalStorage.prototype.erase = function(name, error) { 23 | this._storage.removeItem(name) 24 | } 25 | 26 | exports.createLocalStorage = function(parent) { 27 | return new LocalStorage(parent) 28 | } 29 | 30 | exports.LocalStorage = LocalStorage 31 | -------------------------------------------------------------------------------- /platform/html5/location.js: -------------------------------------------------------------------------------- 1 | var Location = function(ui) { 2 | this._ui = ui 3 | var location = window.location 4 | this.updateActualValues() 5 | var self = this 6 | var context = ui._context 7 | context.window.on("hashchange", function() { self._ui.hash = location.hash }.bind(this)) 8 | context.window.on("popstate", function() { self.updateActualValues() }.bind(this)) 9 | } 10 | 11 | Location.prototype.updateActualValues = function() { 12 | var ui = this._ui 13 | var windowContext = ui._context.window.dom 14 | ui.hash = windowContext.location.hash 15 | ui.href = windowContext.location.href 16 | ui.port = windowContext.location.port 17 | ui.host = windowContext.location.host 18 | ui.origin = windowContext.location.origin 19 | ui.hostname = windowContext.location.hostname 20 | ui.pathname = windowContext.location.pathname 21 | ui.protocol = windowContext.location.protocol 22 | ui.search = windowContext.location.search 23 | ui.state = windowContext.history.state 24 | } 25 | 26 | Location.prototype.changeHref = function(href) { 27 | this._ui._context.window.dom.location.href = href 28 | this.updateActualValues() 29 | } 30 | 31 | Location.prototype.reload = function() { 32 | this._ui._context.window.dom.location.reload() 33 | } 34 | 35 | Location.prototype.pushState = function(state, title, url) { 36 | var ui = this._ui 37 | var windowContext = ui._context.window.dom 38 | if (windowContext.location.hostname) { 39 | windowContext.history.pushState(state, title, url) 40 | this.updateActualValues() 41 | } else { 42 | ui._context.document.title = title 43 | this._ui.state = state 44 | } 45 | } 46 | 47 | exports.createLocation = function(ui) { 48 | return new Location(ui) 49 | } 50 | 51 | exports.Location = Location 52 | -------------------------------------------------------------------------------- /platform/ios/.core.js: -------------------------------------------------------------------------------- 1 | log = console.log.bind(console) 2 | 3 | _globals.core.__deviceBackend = function() { return _globals.ios.device } 4 | _globals.core.__videoBackends.ios = function() { return _globals.ios.video } 5 | 6 | log("iOS detected") 7 | exports.core.vendor = "apple" 8 | exports.core.device = 2 9 | exports.core.os = "ios" 10 | 11 | exports.closeApp = function() { 12 | navigator.app.exitApp(); 13 | } 14 | -------------------------------------------------------------------------------- /platform/ios/.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "requires": ["video.html5"], 3 | "templates": ["*.html", "config.xml", "build.py"] 4 | } 5 | -------------------------------------------------------------------------------- /platform/ios/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | 5 | import argparse 6 | import os 7 | 8 | 9 | def build(app, title): 10 | os.system('ls ./') 11 | os.system('rm -rf %s' %app) 12 | res = os.system('cordova create %s com.%s.app %s' %(title, app, title)) 13 | if res != 0: 14 | print("Failed to create ios app") 15 | return 16 | os.system('rsync -a ./ %s/www --exclude=%s ' %(app,app)) 17 | os.system('cp config.xml %s' %(app)) 18 | 19 | os.chdir(app) 20 | os.system('cordova platform add ios@5.1.0') 21 | os.system('cordova plugin add cordova-plugin-device') 22 | os.system('cordova plugin add cordova-plugin-screen-orientation') 23 | os.system('cordova plugin add cordova-plugin-wkwebview-engine') 24 | os.system('cordova build ios') 25 | os.system('cp -r ../icons/* ./platforms/ios/%s/Images.xcassets/AppIcon.appiconset' %(title)) 26 | os.system('cordova run --ios') 27 | os.chdir('..') 28 | 29 | 30 | parser = argparse.ArgumentParser('qmlcore build tool') 31 | parser.add_argument('--app', '-a', help='application name', default="app") 32 | parser.add_argument('--title', '-t', help='application title', default="App") 33 | args = parser.parse_args() 34 | 35 | 36 | res = os.system('cordova --version') 37 | if res == 0: 38 | build(args.app, args.title) 39 | else: 40 | print('Install "cordova" first: https://cordova.apache.org/docs/en/latest/guide/cli/') 41 | -------------------------------------------------------------------------------- /platform/ios/device.js: -------------------------------------------------------------------------------- 1 | var Device = function(ui) { 2 | function onDeviceReady() { 3 | ui.deviceId = device.uuid 4 | ui.modelName = device.model 5 | ui.firmware = device.version 6 | ui.sdk = device.version 7 | if (clientInformation && clientInformation.language) 8 | ui.language = device.version 9 | } 10 | ui._context.document.on('deviceready', onDeviceReady); 11 | 12 | var screen = window.screen 13 | if (screen) { 14 | ui.lockOrientation = function(orientation) { screen.orientation.lock(orientation) }.bind(ui) 15 | ui.unlockOrientation = function() { screen.orientation.unlock() }.bind(ui) 16 | } else { 17 | log("'screen' is undefined, add 'cordova-plugin-screen-orientation' plugin") 18 | } 19 | } 20 | 21 | exports.createDevice = function(ui) { 22 | return new Device(ui) 23 | } 24 | 25 | exports.Device = Device 26 | -------------------------------------------------------------------------------- /platform/ios/dist/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ title }} 4 | 5 | {{ description | default('Override app description in manifest') }} 6 | 7 | 8 | {{ author.name | default('John Doe') if author is defined else 'John Doe' }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-1024.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-20.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-20@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-20@3x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-24@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-24@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-27.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-27.5@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-29.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-29@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-29@3x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-40.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-40@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-44@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-44@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-50.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-50@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-60@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-60@3x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-72.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-72@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-76.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-76@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-83.5@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-86@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-86@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon-98@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon-98@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/icon@2x.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/splash/Default@2x~ipad~anyany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/splash/Default@2x~ipad~anyany.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/splash/Default@2x~ipad~comany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/splash/Default@2x~ipad~comany.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/splash/Default@2x~iphone~anyany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/splash/Default@2x~iphone~anyany.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/splash/Default@2x~iphone~comany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/splash/Default@2x~iphone~comany.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/splash/Default@2x~iphone~comcom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/splash/Default@2x~iphone~comcom.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/splash/Default@3x~iphone~anyany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/splash/Default@3x~iphone~anyany.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/splash/Default@3x~iphone~anycom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/splash/Default@3x~iphone~anycom.png -------------------------------------------------------------------------------- /platform/ios/dist/icons/splash/Default@3x~iphone~comany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/ios/dist/icons/splash/Default@3x~iphone~comany.png -------------------------------------------------------------------------------- /platform/ios/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block title %}{{ title }}{% endblock %} 5 | 6 | {% block head %}{% endblock %} 7 | 8 | 9 | 10 | 11 | {% block content %}{% endblock %} 12 | {% block mainscript %} 13 | 14 | {% endblock %} 15 | {% block footer %}{% endblock %} 16 | 17 | 18 | -------------------------------------------------------------------------------- /platform/ios/video.js: -------------------------------------------------------------------------------- 1 | var Player = function(ui) { 2 | var player = ui._context.createElement('video') 3 | this.element = player 4 | this.ui = ui 5 | this.setEventListeners() 6 | this.element.style("pointer-events", "none") 7 | this.element.dom.setAttribute("webkit-playsinline", "") 8 | this.element.dom.setAttribute("playsinline", "") 9 | 10 | ui.element.remove() 11 | ui.element = player 12 | ui.parent.element.append(ui.element) 13 | } 14 | 15 | Player.prototype = Object.create(_globals.video.html5.backend.Player.prototype) 16 | 17 | exports.createPlayer = function(ui) { 18 | return new Player(ui) 19 | } 20 | 21 | exports.probeUrl = function(url) { 22 | return 150 23 | } 24 | 25 | exports.Player = Player 26 | -------------------------------------------------------------------------------- /platform/loggerpanel/.core.js: -------------------------------------------------------------------------------- 1 | var logPanelFlag = window["$manifest$logPanel"] 2 | var logMessagesBuffer = [] 3 | 4 | if (logPanelFlag) { 5 | log = function(dummy) { 6 | var args = Array.prototype.slice.call(arguments) 7 | var logger = document.getElementById("logger") || undefined 8 | if (logger) { 9 | if (logMessagesBuffer.length > 0) { 10 | for (var i = 0; i < logMessagesBuffer.length; ++i) 11 | logger.innerHTML += logMessagesBuffer[i] + "
" 12 | logMessagesBuffer = [] 13 | } 14 | logger.innerHTML += args.join(" ") + "
" 15 | } else { 16 | logMessagesBuffer.push(args.join(" ")) 17 | } 18 | } 19 | } else { 20 | log = console.log.bind(console) 21 | } 22 | -------------------------------------------------------------------------------- /platform/loggerpanel/.manifest: -------------------------------------------------------------------------------- 1 | { "requires": ["html5"] } 2 | -------------------------------------------------------------------------------- /platform/pure.blessed/.core.js: -------------------------------------------------------------------------------- 1 | if ((typeof process !== 'undefined') && (process.release.name === 'node')) { 2 | exports.core.os = process.platform 3 | exports.core.userAgent = process.release.name 4 | } 5 | 6 | _globals._backend = function() { return _globals.pure.blessed.backend } 7 | _globals.core.__locationBackend = function() { return _globals.pure.blessed.backend } 8 | _globals.core.__deviceBackend = function() { return _globals.pure.blessed.backend } 9 | _globals.core.__localStorageBackend = function() { return _globals.pure.blessed.backend } 10 | 11 | _globals.core.__videoBackends.void = function() { return _globals.pure.blessed.backend } -------------------------------------------------------------------------------- /platform/pure.blessed/.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "requires": ["pure"], 3 | "properties": { 4 | "requireExplicitRecursiveVisibilityStyle": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /platform/pure.femto/.core.js: -------------------------------------------------------------------------------- 1 | _globals._backend = function() { return _globals.pure.femto.backend } 2 | _globals.core.__deviceBackend = function() { return _globals.pure.femto.device } 3 | _globals.core.__locationBackend = function() { return _globals.pure.femto.location } 4 | _globals.core.__videoBackends.femto = function() { return _globals.pure.femto.video } 5 | _globals.core.__localStorageBackend = function() { return _globals.pure.femto.storage } 6 | 7 | _globals.core.os = 'android' 8 | _globals.core.browser = 'PureQML' 9 | -------------------------------------------------------------------------------- /platform/pure.femto/.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "requires": ["pure"], 3 | "export_module": true, 4 | 5 | "properties": { 6 | "style.font" : { 7 | "family": "Roboto Medium", 8 | "pixelSize": 12 9 | }, 10 | "cssDisableTransformations": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /platform/pure.femto/backend.js: -------------------------------------------------------------------------------- 1 | exports.capabilities = {} 2 | exports._deviceInfo = {} 3 | 4 | _globals.closeApp = function() { 5 | log("closeApp") 6 | fd.closeApp() 7 | } 8 | 9 | exports.init = function(ctx) { 10 | log('backend initialization...') 11 | var options = ctx.options 12 | var nativeContext = options.nativeContext 13 | 14 | var oldOn = fd.Object.prototype.on 15 | fd.Element.prototype.on = function(name, callback) { 16 | oldOn.call(this, name, ctx.wrapNativeCallback(callback)) 17 | } 18 | 19 | ctx._attachElement(nativeContext) 20 | ctx.width = nativeContext.width 21 | ctx.height = nativeContext.height 22 | nativeContext.on('resize', function(w, h) { 23 | log("resizing context to " + w + 'x' + h) 24 | ctx.system.resolutionWidth = w 25 | ctx.system.resolutionHeight = h 26 | ctx.width = w 27 | ctx.height = h 28 | }) 29 | nativeContext.on('keydown', ctx.wrapNativeCallback(function(key) { 30 | var event = { 31 | timestamp: new Date().getTime() 32 | } 33 | return ctx.processKey(key, event) 34 | })) 35 | log('window size', ctx.width, ctx.height) 36 | exports._deviceInfo = fd.getDeviceInfo() 37 | } 38 | 39 | 40 | exports.run = function(ctx, callback) { 41 | ctx.system.device = exports._deviceInfo.device 42 | //schedule onload event 43 | callback() 44 | } 45 | 46 | exports.initSystem = function(system) { 47 | } 48 | 49 | exports.createElement = function(ctx, tag, cls) { 50 | var el 51 | switch(tag) { 52 | case 'input': 53 | el = new fd.Input() 54 | break 55 | case 'spinner': 56 | el = new fd.Spinner() 57 | break 58 | default: 59 | el = new fd.Element() 60 | break 61 | } 62 | if (cls) 63 | el.addClass(cls) 64 | return el 65 | } 66 | 67 | exports.initRectangle = function(rect) { 68 | rect._attachElement(new fd.Rectangle()) 69 | } 70 | 71 | exports.initImage = function(image) { 72 | image._attachElement(new fd.Image()) 73 | } 74 | 75 | var ImageStatusNull = 0 76 | var ImageStatusLoaded = 1 77 | var ImageStatusUnloaded = 2 78 | var ImageStatusError = 3 79 | 80 | 81 | exports.loadImage = function(image, callback) { 82 | image.status = ImageStatusNull 83 | image.element.load(image.source, callback) 84 | } 85 | 86 | exports.initText = function(text) { 87 | text._attachElement(new fd.Text()) 88 | } 89 | 90 | exports.setText = function(text, html) { 91 | text.element.setText(html) 92 | } 93 | 94 | exports.layoutText = function(text) { 95 | text.element.layoutText(function(metrics) { 96 | if (metrics !== null) { 97 | text.paintedWidth = metrics.width 98 | text.paintedHeight = metrics.height 99 | } else 100 | console.log('failed to layout text', text.text) 101 | }) 102 | } 103 | 104 | exports.setAnimation = function (component, name, animation) { 105 | return false 106 | } 107 | 108 | exports.requestAnimationFrame = function(callback) { 109 | return setTimeout(callback, 0) 110 | } 111 | 112 | exports.cancelAnimationFrame = function (timer) { 113 | clearTimeout(timer) 114 | } 115 | 116 | exports.tick = function(ctx) { } 117 | 118 | exports.ajax = function(ui, request) { 119 | var error = request.error, done = request.done 120 | var ctx = ui._context 121 | if (error) 122 | request.error = ctx.wrapNativeCallback(function(event) { ui.loading = false; log("Error", event); error(event); }) 123 | if (done) 124 | request.done = ctx.wrapNativeCallback(function(event) { ui.loading = false; done(event); }) 125 | 126 | ui.loading = true 127 | return fd.httpRequest(request) 128 | } 129 | 130 | exports.addRule = function(selector, rules) { 131 | var type = typeof rules 132 | if (type !== 'object') { 133 | log("WARNING: Stylesheet.addRule uses text based rules, it's unsupported in native clients, selector: " + selector + ", rules: " + rules) 134 | return 135 | } 136 | if ('style' in fd) 137 | fd.style(selector, rules) 138 | } 139 | -------------------------------------------------------------------------------- /platform/pure.femto/build-android-native.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | MAIN=$0 6 | 7 | die() { echo "$*" 1>&2 ; exit 1; } 8 | 9 | if [ -z "$ANDROID_HOME" ]; then 10 | die "environment variable ANDROID_HOME=~/location/to/your/android/sdk required" 11 | fi 12 | 13 | BUILD_DIR=${PWD}/build.pure.femto.app 14 | APP_NAME='' 15 | 16 | while getopts ":a:" OPTNAME; do 17 | case ${OPTNAME} in 18 | a) 19 | APP_NAME="${OPTARG}" 20 | ;; 21 | :) 22 | die "Error: -${OPTARG} requires an argument." 23 | ;; 24 | ?) 25 | die "${MAIN} -a specify app to bundle" 26 | exit 0 27 | ;; 28 | *) 29 | echo "Invalid option ${OPTNAME}" 30 | exit 1 31 | ;; 32 | esac 33 | done 34 | 35 | export PATH=$PATH:$ANDROID_HOME/tools/ 36 | export PATH=$PATH:$ANDROID_HOME/platform-tools/ 37 | 38 | APP_DIR=${BUILD_DIR}/qmlcore-android 39 | if [ ! -d ${APP_DIR} ]; then 40 | echo "installing android runtime..." 41 | mkdir -p ${BUILD_DIR} 42 | pushd ${BUILD_DIR} 43 | git clone --depth=1 https://github.com/pureqml/qmlcore-android.git 44 | popd 45 | else 46 | pushd ${BUILD_DIR} 47 | git -C qmlcore-android pull --depth=1 48 | popd 49 | fi 50 | 51 | echo "compiling app..." 52 | SRC_DIR=${PWD}/build.pure.femto 53 | rm -rf ${SRC_DIR} 54 | ./qmlcore/build -j -p pure.femto ${APP_NAME} 55 | if [ ! -d ${SRC_DIR} ]; then 56 | echo "could not find project in ${SRC_DIR}" 57 | exit 1 58 | fi 59 | 60 | APP_TITLE=$(./qmlcore/build -j -p pure.femto -P title ${APP_NAME} 2>/dev/null) || die "you have to specify application title in .manifest/properties.title" 61 | APP_DOMAIN=$(./qmlcore/build -j -p pure.femto -P domain ${APP_NAME} 2>/dev/null) || die "you have to specify application domain/package in .manifest/properties.domain" 62 | APP_ICON_COLOR=$(./qmlcore/build -j -p pure.femto -P iconColor ${APP_NAME} 2>/dev/null) || APP_ICON_COLOR="" 63 | APP_SDK_VERSION=$(./qmlcore/build -j -p pure.femto -P androidtargetSdkVersion ${APP_NAME} 2>/dev/null) || APP_SDK_VERSION="29" 64 | 65 | if [ -n "${APP_NAME}" ]; then 66 | echo "using app name ${APP_NAME}..." 67 | APP_DOMAIN="${APP_DOMAIN}.${APP_NAME}" 68 | SRC_DIR="${SRC_DIR}/${APP_NAME}" 69 | else 70 | echo "using top-level build dir..." 71 | fi 72 | echo "app domain: ${APP_DOMAIN}, title: ${APP_TITLE}" 73 | 74 | echo "bundling..." 75 | DST_DIR=${BUILD_DIR}/app 76 | 77 | rm -rf ${DST_DIR} 78 | mkdir -p ${DST_DIR} 79 | git -C ${APP_DIR} checkout-index -a --prefix=${DST_DIR}/ 80 | 81 | ASSETS_DIR="${DST_DIR}/app/src/main/assets" 82 | rm "${ASSETS_DIR}/.keep" 83 | 84 | pushd ${ASSETS_DIR} 85 | echo "using ${SRC_DIR} as source directory" 86 | cp -a ${SRC_DIR}/* . 87 | mv qml.*.js main.js 2>/dev/null || die "Could not find qml.*.js in build directory, in case your project has multiple app, specify the name with -a, e.g -a " 88 | if [ -e ${SRC_DIR}/icons ]; then 89 | cp -r ${SRC_DIR}/icons/* ../res/. 90 | fi 91 | popd 92 | 93 | pushd ${DST_DIR} 94 | P="s/package=\"com.pureqml.android\"/package=\"${APP_DOMAIN}\"/" 95 | sed -i "${P}" app/src/main/AndroidManifest.xml 96 | P="s/QMLCoreAndroidRuntime<\\/string>/${APP_TITLE}<\\/string>/" 97 | sed -i "${P}" app/src/main/res/values/strings.xml 98 | P="s/-keep public com\\.pureqml\\.android\\./-keep public ${APP_DOMAIN}./g" 99 | sed -i "${P}" app/proguard-rules.pro 100 | P="s/applicationId \"com\\.pureqml\\.qmlcore\\.runtime\\.android\"/applicationId \"${APP_DOMAIN}\"/" 101 | sed -i "${P}" app/build.gradle 102 | P="s/targetSdkVersion 29/targetSdkVersion ${APP_SDK_VERSION}/" 103 | sed -i "${P}" app/build.gradle 104 | P="s/compileSdkVersion 29/compileSdkVersion ${APP_SDK_VERSION}/" 105 | sed -i "${P}" app/build.gradle 106 | P="s/namespace 'com.pureqml.android'/namespace '${APP_DOMAIN}'/" 107 | sed -i "${P}" app/build.gradle 108 | 109 | 110 | if [ ! -z "$APP_ICON_COLOR" ]; then 111 | P="s/#FFFFFF/${APP_ICON_COLOR}/" 112 | sed -i "${P}" app/src/main/res/values/ic_launcher_background.xml 113 | fi 114 | 115 | JAVA_SRC=app/src/main/java/$(echo "${APP_DOMAIN}" | tr '.' '/') 116 | mkdir -p ${JAVA_SRC} 117 | mv app/src/main/java/com/pureqml/android/* ${JAVA_SRC}/ 118 | rm -rf app/src/main/java/com/pureqml/android 119 | rmdir -p app/src/main/java/com/pureqml || true 120 | for J in $(find -name '*.java'); do 121 | sed -i "s/package com\\.pureqml\\.android/package ${APP_DOMAIN}/" $J 122 | sed -i "s/import com\\.pureqml\\.android/import ${APP_DOMAIN}/g" $J 123 | sed -i "s/import static com\\.pureqml\\.android/import static ${APP_DOMAIN}/g" $J 124 | done 125 | popd 126 | 127 | echo "building" 128 | pushd ${DST_DIR} 129 | TERM=xterm-color ./gradlew build #workaround grandle bug 130 | popd 131 | 132 | echo 133 | echo 134 | echo "build finished, outputting apk locations:" 135 | echo 136 | find ${BUILD_DIR} -iname '*.apk' 137 | 138 | -------------------------------------------------------------------------------- /platform/pure.femto/device.js: -------------------------------------------------------------------------------- 1 | exports.createDevice = function(ui) { 2 | var info = ui._context.backend._deviceInfo 3 | Object.assign(ui, info) 4 | 5 | ui.lockOrientation = function(orientation) { 6 | fd.setDeviceFeature('orientation', orientation) 7 | } 8 | 9 | ui.keepScreenOn = function(enable) { 10 | fd.setDeviceFeature('keep-screen-on', enable) 11 | } 12 | 13 | ui.setFullScreen = function(enable) { 14 | fd.setDeviceFeature('fullscreen', enable) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /platform/pure.femto/location.js: -------------------------------------------------------------------------------- 1 | var Location = function() { } 2 | 3 | exports.createLocation = function(ui) { return new Location } 4 | 5 | -------------------------------------------------------------------------------- /platform/pure.femto/storage.js: -------------------------------------------------------------------------------- 1 | exports.createLocalStorage = function() { 2 | return new fd.LocalStorage() 3 | } 4 | -------------------------------------------------------------------------------- /platform/pure.femto/video.js: -------------------------------------------------------------------------------- 1 | exports.createPlayer = function(ui) { 2 | log('video.createPlayer') 3 | 4 | var player = new fd.VideoPlayer() 5 | 6 | var resetState = function() { 7 | ui.ready = false 8 | ui.paused = false 9 | ui.waiting = false 10 | ui.seeking = false 11 | ui.stalled = false 12 | } 13 | 14 | player.on('stateChanged', function(state) { 15 | log('VideoPlayer: stateChanged ' + state) 16 | switch(state) { 17 | case 1: 18 | log("VideoPlayer: STATE_IDLE") 19 | resetState() 20 | break; 21 | case 2: 22 | log("VideoPlayer: STATE_BUFFERING") 23 | if (!ui.paused) 24 | ui.waiting = true 25 | break; 26 | case 3: 27 | log("VideoPlayer: STATE_READY") 28 | ui.waiting = false 29 | ui.ready = true 30 | break; 31 | case 4: 32 | log("VideoPlayer: STATE_ENDED") 33 | ui.finished() 34 | resetState() 35 | break; 36 | default: 37 | log("VideoPlayer: unhandled state", typeof state, state) 38 | } 39 | }) 40 | 41 | player.on('seeked', function() { 42 | ui.waiting = false 43 | ui.seeking = false 44 | }) 45 | 46 | player.on('error', function(err) { 47 | log('VideoPlayer: error: ', err) 48 | resetState(ui); 49 | ui.error({ message: err }) 50 | }) 51 | 52 | player.on('pause', function(isPaused) { 53 | ui.paused = isPaused 54 | log('VideoPlayer: paused: ', isPaused) 55 | }) 56 | 57 | player.on('timeupdate', function(position) { 58 | ui.waiting = false 59 | ui.stalled = false 60 | if (!ui.seeking) 61 | ui.progress = position; 62 | }.bind(ui)) 63 | 64 | player.on('durationchange', function(duration) { 65 | ui.duration = duration 66 | }.bind(ui)) 67 | 68 | 69 | return player 70 | } 71 | 72 | exports.probeUrl = function(url) { 73 | log('video.probeUrl', url) 74 | return 150 75 | } 76 | -------------------------------------------------------------------------------- /platform/pure.void/.core.js: -------------------------------------------------------------------------------- 1 | if ((typeof process !== 'undefined') && (process.release.name === 'node')) { 2 | exports.core.os = process.platform 3 | exports.core.userAgent = process.release.name 4 | } 5 | 6 | _globals._backend = function() { return _globals.pure.void.backend } 7 | _globals.core.__locationBackend = function() { return _globals.pure.void.backend } 8 | _globals.core.__deviceBackend = function() { return _globals.pure.void.backend } 9 | _globals.core.__localStorageBackend = function() { return _globals.pure.void.backend } 10 | 11 | _globals.core.__videoBackends.void = function() { return _globals.pure.void.backend } -------------------------------------------------------------------------------- /platform/pure.void/.manifest: -------------------------------------------------------------------------------- 1 | { "requires": ["pure"] } 2 | -------------------------------------------------------------------------------- /platform/pure/.core.js: -------------------------------------------------------------------------------- 1 | _globals.core.os = 'linux' 2 | _globals.core.userAgent = 'pure-native' 3 | _globals.core.language = 'en' 4 | -------------------------------------------------------------------------------- /platform/pure/.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "standalone": false, 3 | "requires": ["core"], 4 | "properties": { 5 | "cssDisableTransformations": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /platform/pure/Stylesheet.qml: -------------------------------------------------------------------------------- 1 | Object { 2 | constructor: { 3 | } 4 | 5 | function addRule(selector, rule) { 6 | $pure.femto.backend.addRule(selector, rule) 7 | } 8 | 9 | function allocateClass(prefix) { 10 | return prefix 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /platform/video.dashjs/.core.js: -------------------------------------------------------------------------------- 1 | _globals.core.__videoBackends.dashjs = function() { return _globals.video.dashjs.backend } 2 | -------------------------------------------------------------------------------- /platform/video.dashjs/.manifest: -------------------------------------------------------------------------------- 1 | { "requires": ["video.html5"] } 2 | -------------------------------------------------------------------------------- /platform/video.dashjs/backend.js: -------------------------------------------------------------------------------- 1 | var Player = function(ui) { 2 | var player = ui._context.createElement('video') 3 | player.dom.preload = "metadata" 4 | player.setAttribute("data-dashjs-player", "") 5 | 6 | this.element = player 7 | this.ui = ui 8 | this.setEventListeners() 9 | 10 | ui.element.remove() 11 | ui.element = player 12 | ui.parent.element.append(ui.element) 13 | 14 | this.dash = dashjs.MediaPlayer().create(); 15 | this.dash.initialize(player.dom) 16 | } 17 | 18 | Player.prototype = Object.create(_globals.video.html5.backend.Player.prototype) 19 | 20 | Player.prototype.setSource = function(url) { 21 | log("dashjs::setSource", url) 22 | this.ui.ready = false 23 | this.dash.attachSource(url) 24 | 25 | if (this.ui.autoPlay) 26 | this.play() 27 | } 28 | 29 | Player.prototype.setupDrm = function(type, options, callback, error) { 30 | var drmConfig = {} 31 | if (type === "widevine") { 32 | drmConfig["com.widevine.alpha"] = { 33 | "serverURL": options.laServer, 34 | "systemStringPriority": ["com.widevine.something", "com.widevine.alpha"], 35 | "priority": 1 36 | } 37 | } else if (type === "playready") { 38 | drmConfig["com.microsoft.playready"] = { 39 | "serverURL": options.laServer, 40 | "priority": 1, 41 | "systemStringPriority": ["com.microsoft.playready.something", "com.microsoft.playready.recommendation", "com.microsoft.playready.hardware", "com.microsoft.playready"] 42 | } 43 | } else { 44 | error ? error(new Error("Unknown or not supported DRM type " + type)) : log("Unknown or not supported DRM type " + type) 45 | } 46 | 47 | this.dash.setProtectionData(drmConfig) 48 | if (callback) 49 | callback() 50 | } 51 | 52 | 53 | exports.createPlayer = function(ui) { 54 | return new Player(ui) 55 | } 56 | 57 | exports.probeUrl = function(url) { 58 | return 10 59 | } 60 | 61 | Player.prototype.dispose = function() { 62 | _globals.video.html5.backend.Player.prototype.dispose.apply(this) 63 | this.dash.reset() 64 | this.dash = null 65 | } 66 | 67 | exports.Player = Player 68 | -------------------------------------------------------------------------------- /platform/video.dashjs/dist/index.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block head %} 3 | {{ super() }} 4 | 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /platform/video.html5/.core.js: -------------------------------------------------------------------------------- 1 | _globals.core.__videoBackends.html5 = function() { return _globals.video.html5.backend } 2 | -------------------------------------------------------------------------------- /platform/video.html5/.manifest: -------------------------------------------------------------------------------- 1 | { "requires": ["html5"], "standalone": false } 2 | -------------------------------------------------------------------------------- /platform/video.jsmpeg/.core.js: -------------------------------------------------------------------------------- 1 | _globals.core.__videoBackends.jsmpeg = function() { return _globals.video.jsmpeg.backend } 2 | -------------------------------------------------------------------------------- /platform/video.jsmpeg/.manifest: -------------------------------------------------------------------------------- 1 | { "requires": ["html5"] } 2 | -------------------------------------------------------------------------------- /platform/video.jsmpeg/dist/index.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block head %} 3 | {{ super() }} 4 | 5 | 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /platform/video.shaka/.core.js: -------------------------------------------------------------------------------- 1 | shaka.polyfill.installAll(); 2 | if (shaka.Player.isBrowserSupported()) { 3 | _globals.core.__videoBackends.shaka = function() { return _globals.video.shaka.backend } 4 | } 5 | -------------------------------------------------------------------------------- /platform/video.shaka/.manifest: -------------------------------------------------------------------------------- 1 | { "requires": ["video.html5"] } 2 | -------------------------------------------------------------------------------- /platform/video.shaka/dist/index.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block head %} 3 | {{ super() }} 4 | 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /platform/video.videojs/.core.js: -------------------------------------------------------------------------------- 1 | _globals.core.__videoBackends.videojs = function() { return _globals.video.videojs.backend } 2 | -------------------------------------------------------------------------------- /platform/video.videojs/.manifest: -------------------------------------------------------------------------------- 1 | { "requires": ["video.html5"] } 2 | -------------------------------------------------------------------------------- /platform/video.videojs/backend.js: -------------------------------------------------------------------------------- 1 | var Player = function(ui) { 2 | var player = ui._context.createElement('video') 3 | player.dom.preload = "metadata" 4 | 5 | player.setAttribute('preload', 'auto') 6 | player.setAttribute('data-setup', '{}') 7 | player.setAttribute('class', 'video-js') 8 | 9 | this.element = player 10 | this.ui = ui 11 | this.setEventListeners() 12 | 13 | var uniqueId = 'videojs' + this.element._uniqueId 14 | player.setAttribute('id', uniqueId) 15 | 16 | if (ui.element) 17 | ui.element.remove() 18 | ui.element = player 19 | ui.parent.element.append(ui.element) 20 | 21 | this.videojs = window.videojs(uniqueId, { "textTrackSettings": false }) 22 | 23 | this.videojs.width = 'auto' 24 | this.videojs.height = 'auto' 25 | 26 | var errorDisplay = document.getElementsByClassName("vjs-error-display") 27 | if (errorDisplay && errorDisplay.length) { 28 | for (var index = 0; index < errorDisplay.length; ++index) { 29 | errorDisplay[index].style.display = 'none' 30 | } 31 | } 32 | 33 | var videojsSpinner = document.getElementsByClassName("vjs-loading-spinner") 34 | if (videojsSpinner && videojsSpinner.length) { 35 | for (var index = 0; index < videojsSpinner.length; ++index) { 36 | videojsSpinner[index].style.display = 'none' 37 | } 38 | } 39 | 40 | var videojsControllButton = document.getElementsByClassName("vjs-control-bar") 41 | if (videojsControllButton && videojsControllButton.length) { 42 | for (var index = 0; index < videojsControllButton.length; ++index) { 43 | videojsControllButton[index].style.display = 'none' 44 | } 45 | } 46 | 47 | var videojsBigPlayButton = document.getElementsByClassName("vjs-big-play-button") 48 | if (videojsBigPlayButton && videojsBigPlayButton.length) { 49 | for (var index = 0; index < videojsBigPlayButton.length; ++index) { 50 | videojsBigPlayButton[index].style.display = 'none' 51 | } 52 | } 53 | 54 | this.videojsContaner = document.getElementById(uniqueId) 55 | this.videojsContaner.style.zindex = -1 56 | } 57 | 58 | Player.prototype = Object.create(_globals.video.html5.backend.Player.prototype) 59 | 60 | Player.prototype.dispose = function() { 61 | window.videojs(this.videojsContaner).dispose() 62 | this.videojs.dispose() 63 | this.videojs = null 64 | _globals.video.html5.backend.Player.prototype.dispose.apply(this) 65 | } 66 | 67 | Player.prototype.setSource = function(url) { 68 | var media = { 'src': url } 69 | log("SetSource", url) 70 | if (url) { 71 | var urlLower = url.toLowerCase() 72 | var querryIndex = url.indexOf("?") 73 | if (querryIndex >= 0) 74 | urlLower = urlLower.substring(0, querryIndex) 75 | var extIndex = urlLower.lastIndexOf(".") 76 | var extension = urlLower.substring(extIndex, urlLower.length) 77 | if (extension === ".m3u8" || extension === ".m3u") 78 | media.type = 'application/x-mpegURL' 79 | else if (extension === ".mpd") 80 | media.type = 'application/dash+xml' 81 | } 82 | this.videojs.src(media, { html5: { hls: { withCredentials: true } }, fluid: true, preload: 'none', techOrder: ["html5"] }) 83 | if (this.ui.autoPlay) 84 | this.play() 85 | } 86 | 87 | Player.prototype.play = function() { 88 | var playPromise = this.element.dom.play() 89 | if (playPromise !== undefined) { 90 | playPromise.catch(function(e) { 91 | log('play error:', e) 92 | if (this.ui.autoPlay && e.code === DOMException.ABORT_ERR) 93 | this.element.dom.play() 94 | }.bind(this)) 95 | } 96 | } 97 | 98 | Player.prototype.setRect = function(l, t, r, b) { 99 | this.videojsContaner.style.width = (r - l) + "px" 100 | this.videojsContaner.style.height = (b - t) + "px" 101 | } 102 | 103 | exports.createPlayer = function(ui) { 104 | return new Player(ui) 105 | } 106 | 107 | exports.probeUrl = function(url) { 108 | return window.videojs ? 60 : 0 109 | } 110 | -------------------------------------------------------------------------------- /platform/video.videojs/dist/index.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block head %} 3 | {{ super() }} 4 | 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /platform/web.pwa/.core.js: -------------------------------------------------------------------------------- 1 | if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) { 2 | navigator.serviceWorker.register('./sw.js', {}) 3 | .then((reg) => { 4 | // registration worked 5 | console.log('Registration succeeded. Scope is ' + reg.scope); 6 | }).catch((error) => { 7 | // registration failed 8 | console.log('Registration failed with ' + error); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /platform/web.pwa/.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "requires": ["web"], 3 | "templates": ["*.html", "sw.js"], 4 | "templater": "jinja2" 5 | } 6 | 7 | -------------------------------------------------------------------------------- /platform/web.pwa/dist/sw.js: -------------------------------------------------------------------------------- 1 | var CACHE_NAME = 'pureqml-cache-v1'; 2 | var urlsToCache = {{ installed_files | tojson }}; 3 | urlsToCache.push('/') 4 | 5 | self.addEventListener('install', function(event) { 6 | event.waitUntil( 7 | caches.open(CACHE_NAME) 8 | .then(function(cache) { 9 | console.log('Opened cache, adding cached urls: ' + urlsToCache.length); 10 | return cache.addAll(urlsToCache); 11 | }) 12 | ); 13 | }); 14 | 15 | self.addEventListener('fetch', function(event) { 16 | event.respondWith( 17 | caches.match(event.request) 18 | .then(function(response) { 19 | if (response) { 20 | return response; 21 | } 22 | 23 | return fetch(event.request, {credentials: 'include'}).then( 24 | function(response) { 25 | if(!response || response.status !== 200 || response.type !== 'basic') { 26 | return response; 27 | } 28 | 29 | var responseToCache = response.clone(); 30 | 31 | caches.open(CACHE_NAME) 32 | .then(function(cache) { 33 | cache.put(event.request, responseToCache); 34 | }); 35 | 36 | return response; 37 | }); 38 | }) 39 | ); 40 | }); 41 | -------------------------------------------------------------------------------- /platform/web/.core.js: -------------------------------------------------------------------------------- 1 | _globals.core.__deviceBackend = function() { return _globals.web.device } 2 | 3 | var keyCodes = { 4 | 13: 'Select', 5 | 16: 'Shift', 6 | 17: 'Ctrl', 7 | 18: 'LeftAlt', 8 | 27: 'Back', 9 | 37: 'Left', 10 | 32: 'Space', 11 | 33: 'PageUp', 12 | 34: 'PageDown', 13 | 36: 'Menu', 14 | 38: 'Up', 15 | 39: 'Right', 16 | 40: 'Down', 17 | 48: '0', 18 | 49: '1', 19 | 50: '2', 20 | 51: '3', 21 | 52: '4', 22 | 53: '5', 23 | 54: '6', 24 | 55: '7', 25 | 56: '8', 26 | 57: '9', 27 | 65: 'A', 28 | 66: 'B', 29 | 67: 'C', 30 | 68: 'D', 31 | 69: 'E', 32 | 70: 'F', 33 | 71: 'G', 34 | 72: 'H', 35 | 73: 'I', 36 | 74: 'J', 37 | 75: 'K', 38 | 76: 'L', 39 | 77: 'M', 40 | 78: 'N', 41 | 79: 'O', 42 | 80: 'P', 43 | 81: 'Q', 44 | 82: 'R', 45 | 83: 'S', 46 | 84: 'T', 47 | 85: 'U', 48 | 86: 'V', 49 | 87: 'W', 50 | 88: 'X', 51 | 89: 'Y', 52 | 90: 'Z', 53 | // NumPad 54 | 96: '0', 55 | 97: '1', 56 | 98: '2', 57 | 99: '3', 58 | 100: '4', 59 | 101: '5', 60 | 102: '6', 61 | 103: '7', 62 | 104: '8', 63 | 105: '9', 64 | } 65 | 66 | if ($manifest$emulateRemoteKeys) { 67 | var emulatedKeys = { 68 | 112: 'Red', 69 | 113: 'Green', 70 | 114: 'Yellow', 71 | 115: 'Blue', 72 | 219: 'Red', // [ 73 | 221: 'Green', // ] 74 | 186: 'Yellow', // ; 75 | 222: 'Blue', // ' 76 | 230: 'RightAlt', 77 | 187: 'VolumeUp', 78 | 189: 'VolumeDown', 79 | 191: 'Mute', 80 | // NumPad 81 | 107: 'VolumeUp', 82 | 109: 'VolumeDown', 83 | 111: 'Mute', 84 | } 85 | for(var code in emulatedKeys) { 86 | keyCodes[code] = emulatedKeys[code] 87 | } 88 | } 89 | 90 | exports.core.keyCodes = keyCodes 91 | 92 | exports.closeApp = function() { 93 | window.close() 94 | } 95 | -------------------------------------------------------------------------------- /platform/web/.manifest: -------------------------------------------------------------------------------- 1 | { "requires": ["video.html5"] } 2 | -------------------------------------------------------------------------------- /platform/web/device.js: -------------------------------------------------------------------------------- 1 | var Device = function(ui) { 2 | var context = ui._context 3 | if ($manifest$system$fingerprint) { 4 | var fingerprint = new $html5.fingerprint.fingerprint.Fingerprint() 5 | context.backend.fingerprint(context, fingerprint) 6 | ui.deviceId = fingerprint.finalize() 7 | log("deviceId", ui.deviceId) 8 | } else { 9 | var deviceString = context.system.os + "_" + context.system.browser 10 | deviceString = deviceString.replace(/\s/g, '') 11 | ui.deviceId = deviceString + "_" + Math.random().toString(36).substr(2, 9) 12 | } 13 | } 14 | 15 | exports.createDevice = function(ui) { 16 | return new Device(ui) 17 | } 18 | 19 | exports.Device = Device 20 | -------------------------------------------------------------------------------- /platform/webextension/.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "requires": ["html5"], 3 | "templates": ["*.html", "manifest.json"] 4 | } 5 | -------------------------------------------------------------------------------- /platform/webextension/dist/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/webextension/dist/icon128.png -------------------------------------------------------------------------------- /platform/webextension/dist/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/webextension/dist/icon16.png -------------------------------------------------------------------------------- /platform/webextension/dist/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/webextension/dist/icon32.png -------------------------------------------------------------------------------- /platform/webextension/dist/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/platform/webextension/dist/icon48.png -------------------------------------------------------------------------------- /platform/webextension/dist/index.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block head %} 3 | 9 | {{ super() }} 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /platform/webextension/dist/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ title | default('pureqml') }}", 3 | "version": "{{ version | default('1.0.0') }}", 4 | "description": "{{ description | default('') }}", 5 | "icons": { 6 | "16": "icon16.png", 7 | "32": "icon32.png", 8 | "48": "icon48.png", 9 | "128": "icon128.png" 10 | }, 11 | "browser_action": { 12 | "default_popup": "index.html" 13 | }, 14 | "manifest_version": 2 15 | } 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | future>=0.17.1 2 | inotify>=0.2.10 3 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: check 3 | check: 4 | @./lit.py . 5 | 6 | .PHONY: clean 7 | clean: 8 | rm -rf build.* qml/Output 9 | .PHONY: superclean 10 | superclean: 11 | rm -rf .cache build.* qml/Output 12 | -------------------------------------------------------------------------------- /test/lit.cfg.py: -------------------------------------------------------------------------------- 1 | import lit.formats 2 | 3 | config.name = "Compiler Tests" 4 | config.test_format = lit.formats.ShTest(True) 5 | 6 | config.suffixes = ['.qml'] 7 | 8 | config.test_source_root = os.path.dirname(__file__) 9 | config.my_obj_root = os.path.join(config.test_source_root, "..") 10 | config.test_exec_root = os.path.join(config.my_obj_root, 'test') 11 | 12 | build_path = os.path.join(config.my_obj_root, 'build') 13 | cache_dir = os.path.join(config.test_exec_root, '.cache') 14 | manifest = '{"sources":"qml","apps":["%noext_basename_s"],"package":"test"}' 15 | config.substitutions.append(("%out","%S/../build.%basename_t")) 16 | config.substitutions.append(('%build', ": rm -f %s/test.%%noext_basename_s && rm -rf build.%%basename_t && cd %s && %s -v --build-dir=build.%%basename_t --cache-dir=%s --inline-manifest=\'%s\'" % (cache_dir, config.test_exec_root, build_path, cache_dir, manifest))) 17 | 18 | -------------------------------------------------------------------------------- /test/lit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # https://medium.com/@mshockwave/using-llvm-lit-out-of-tree-5cddada85a78 4 | 5 | # To run lit-based test suite: 6 | # cd xyz/qmlcore/test && ./lit.py -va . 7 | 8 | from lit.main import main 9 | import os 10 | 11 | if __name__ == '__main__': 12 | if not os.path.exists(".cache/core.Item"): 13 | print("Note that first run may take quite a while .cache/core.* is populated...") 14 | main() 15 | -------------------------------------------------------------------------------- /test/lit/LitTestCase.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import lit.discovery 4 | import lit.LitConfig 5 | import lit.worker 6 | 7 | """ 8 | TestCase adaptor for providing a Python 'unittest' compatible interface to 'lit' 9 | tests. 10 | """ 11 | 12 | 13 | class UnresolvedError(RuntimeError): 14 | pass 15 | 16 | 17 | class LitTestCase(unittest.TestCase): 18 | def __init__(self, test, lit_config): 19 | unittest.TestCase.__init__(self) 20 | self._test = test 21 | self._lit_config = lit_config 22 | 23 | def id(self): 24 | return self._test.getFullName() 25 | 26 | def shortDescription(self): 27 | return self._test.getFullName() 28 | 29 | def runTest(self): 30 | # Run the test. 31 | result = lit.worker._execute(self._test, self._lit_config) 32 | 33 | # Adapt the result to unittest. 34 | if result.code is lit.Test.UNRESOLVED: 35 | raise UnresolvedError(result.output) 36 | elif result.code.isFailure: 37 | self.fail(result.output) 38 | 39 | 40 | def load_test_suite(inputs): 41 | import platform 42 | windows = platform.system() == 'Windows' 43 | 44 | # Create the global config object. 45 | lit_config = lit.LitConfig.LitConfig( 46 | progname='lit', 47 | path=[], 48 | quiet=False, 49 | useValgrind=False, 50 | valgrindLeakCheck=False, 51 | valgrindArgs=[], 52 | noExecute=False, 53 | debug=False, 54 | isWindows=windows, 55 | params={}) 56 | 57 | # Perform test discovery. 58 | tests = lit.discovery.find_tests_for_inputs(lit_config, inputs, False) 59 | test_adaptors = [LitTestCase(t, lit_config) for t in tests] 60 | 61 | # Return a unittest test suite which just runs the tests in order. 62 | return unittest.TestSuite(test_adaptors) 63 | -------------------------------------------------------------------------------- /test/lit/ShCommands.py: -------------------------------------------------------------------------------- 1 | class Command: 2 | def __init__(self, args, redirects): 3 | self.args = list(args) 4 | self.redirects = list(redirects) 5 | 6 | def __repr__(self): 7 | return 'Command(%r, %r)' % (self.args, self.redirects) 8 | 9 | def __eq__(self, other): 10 | if not isinstance(other, Command): 11 | return False 12 | 13 | return ((self.args, self.redirects) == 14 | (other.args, other.redirects)) 15 | 16 | def toShell(self, file): 17 | for arg in self.args: 18 | if "'" not in arg: 19 | quoted = "'%s'" % arg 20 | elif '"' not in arg and '$' not in arg: 21 | quoted = '"%s"' % arg 22 | else: 23 | raise NotImplementedError('Unable to quote %r' % arg) 24 | file.write(quoted) 25 | 26 | # For debugging / validation. 27 | import ShUtil 28 | dequoted = list(ShUtil.ShLexer(quoted).lex()) 29 | if dequoted != [arg]: 30 | raise NotImplementedError('Unable to quote %r' % arg) 31 | 32 | for r in self.redirects: 33 | if len(r[0]) == 1: 34 | file.write("%s '%s'" % (r[0][0], r[1])) 35 | else: 36 | file.write("%s%s '%s'" % (r[0][1], r[0][0], r[1])) 37 | 38 | class GlobItem: 39 | def __init__(self, pattern): 40 | self.pattern = pattern 41 | 42 | def __repr__(self): 43 | return self.pattern 44 | 45 | def __eq__(self, other): 46 | if not isinstance(other, Command): 47 | return False 48 | 49 | return (self.pattern == other.pattern) 50 | 51 | def resolve(self, cwd): 52 | import glob 53 | import os 54 | if os.path.isabs(self.pattern): 55 | abspath = self.pattern 56 | else: 57 | abspath = os.path.join(cwd, self.pattern) 58 | results = glob.glob(abspath) 59 | return [self.pattern] if len(results) == 0 else results 60 | 61 | class Pipeline: 62 | def __init__(self, commands, negate=False, pipe_err=False): 63 | self.commands = commands 64 | self.negate = negate 65 | self.pipe_err = pipe_err 66 | 67 | def __repr__(self): 68 | return 'Pipeline(%r, %r, %r)' % (self.commands, self.negate, 69 | self.pipe_err) 70 | 71 | def __eq__(self, other): 72 | if not isinstance(other, Pipeline): 73 | return False 74 | 75 | return ((self.commands, self.negate, self.pipe_err) == 76 | (other.commands, other.negate, self.pipe_err)) 77 | 78 | def toShell(self, file, pipefail=False): 79 | if pipefail != self.pipe_err: 80 | raise ValueError('Inconsistent "pipefail" attribute!') 81 | if self.negate: 82 | file.write('! ') 83 | for cmd in self.commands: 84 | cmd.toShell(file) 85 | if cmd is not self.commands[-1]: 86 | file.write('|\n ') 87 | 88 | class Seq: 89 | def __init__(self, lhs, op, rhs): 90 | assert op in (';', '&', '||', '&&') 91 | self.op = op 92 | self.lhs = lhs 93 | self.rhs = rhs 94 | 95 | def __repr__(self): 96 | return 'Seq(%r, %r, %r)' % (self.lhs, self.op, self.rhs) 97 | 98 | def __eq__(self, other): 99 | if not isinstance(other, Seq): 100 | return False 101 | 102 | return ((self.lhs, self.op, self.rhs) == 103 | (other.lhs, other.op, other.rhs)) 104 | 105 | def toShell(self, file, pipefail=False): 106 | self.lhs.toShell(file, pipefail) 107 | file.write(' %s\n' % self.op) 108 | self.rhs.toShell(file, pipefail) 109 | -------------------------------------------------------------------------------- /test/lit/__init__.py: -------------------------------------------------------------------------------- 1 | """'lit' Testing Tool""" 2 | 3 | __author__ = 'Daniel Dunbar' 4 | __email__ = 'daniel@minormatter.com' 5 | __versioninfo__ = (12, 0, 0) 6 | __version__ = '.'.join(str(v) for v in __versioninfo__) + 'dev' 7 | 8 | __all__ = [] 9 | -------------------------------------------------------------------------------- /test/lit/builtin_commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pureqml/qmlcore/4b1eff3a3478ba8ddc51e363e99c6808212094db/test/lit/builtin_commands/__init__.py -------------------------------------------------------------------------------- /test/lit/builtin_commands/cat.py: -------------------------------------------------------------------------------- 1 | import getopt 2 | import sys 3 | try: 4 | from StringIO import StringIO 5 | except ImportError: 6 | from io import StringIO 7 | 8 | def convertToCaretAndMNotation(data): 9 | newdata = StringIO() 10 | if isinstance(data, str): 11 | data = bytearray(data) 12 | 13 | for intval in data: 14 | if intval == 9 or intval == 10: 15 | newdata.write(chr(intval)) 16 | continue 17 | if intval > 127: 18 | intval = intval -128 19 | newdata.write("M-") 20 | if intval < 32: 21 | newdata.write("^") 22 | newdata.write(chr(intval+64)) 23 | elif intval == 127: 24 | newdata.write("^?") 25 | else: 26 | newdata.write(chr(intval)) 27 | 28 | return newdata.getvalue().encode() 29 | 30 | 31 | def main(argv): 32 | arguments = argv[1:] 33 | short_options = "v" 34 | long_options = ["show-nonprinting"] 35 | show_nonprinting = False; 36 | 37 | try: 38 | options, filenames = getopt.gnu_getopt(arguments, short_options, long_options) 39 | except getopt.GetoptError as err: 40 | sys.stderr.write("Unsupported: 'cat': %s\n" % str(err)) 41 | sys.exit(1) 42 | 43 | for option, value in options: 44 | if option == "-v" or option == "--show-nonprinting": 45 | show_nonprinting = True; 46 | 47 | writer = getattr(sys.stdout, 'buffer', None) 48 | if writer is None: 49 | writer = sys.stdout 50 | if sys.platform == "win32": 51 | import os, msvcrt 52 | msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY) 53 | for filename in filenames: 54 | try: 55 | fileToCat = open(filename,"rb") 56 | contents = fileToCat.read() 57 | if show_nonprinting: 58 | contents = convertToCaretAndMNotation(contents) 59 | writer.write(contents) 60 | sys.stdout.flush() 61 | fileToCat.close() 62 | except IOError as error: 63 | sys.stderr.write(str(error)) 64 | sys.exit(1) 65 | 66 | if __name__ == "__main__": 67 | main(sys.argv) 68 | -------------------------------------------------------------------------------- /test/lit/display.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def create_display(opts, tests, total_tests, workers): 5 | if opts.quiet: 6 | return NopDisplay() 7 | 8 | of_total = (' of %d' % total_tests) if (tests != total_tests) else '' 9 | header = '-- Testing: %d%s tests, %d workers --' % (tests, of_total, workers) 10 | 11 | progress_bar = None 12 | if opts.succinct and opts.useProgressBar: 13 | import lit.ProgressBar 14 | try: 15 | tc = lit.ProgressBar.TerminalController() 16 | progress_bar = lit.ProgressBar.ProgressBar(tc, header) 17 | header = None 18 | except ValueError: 19 | progress_bar = lit.ProgressBar.SimpleProgressBar('Testing: ') 20 | 21 | return Display(opts, tests, header, progress_bar) 22 | 23 | 24 | class NopDisplay(object): 25 | def print_header(self): pass 26 | def update(self, test): pass 27 | def clear(self, interrupted): pass 28 | 29 | 30 | class Display(object): 31 | def __init__(self, opts, tests, header, progress_bar): 32 | self.opts = opts 33 | self.tests = tests 34 | self.header = header 35 | self.progress_bar = progress_bar 36 | self.completed = 0 37 | 38 | def print_header(self): 39 | if self.header: 40 | print(self.header) 41 | if self.progress_bar: 42 | self.progress_bar.update(0.0, '') 43 | 44 | def update(self, test): 45 | self.completed += 1 46 | 47 | show_result = test.isFailure() or \ 48 | self.opts.showAllOutput or \ 49 | (not self.opts.quiet and not self.opts.succinct) 50 | if show_result: 51 | if self.progress_bar: 52 | self.progress_bar.clear(interrupted=False) 53 | self.print_result(test) 54 | 55 | if self.progress_bar: 56 | if test.isFailure(): 57 | self.progress_bar.barColor = 'RED' 58 | percent = float(self.completed) / self.tests 59 | self.progress_bar.update(percent, test.getFullName()) 60 | 61 | def clear(self, interrupted): 62 | if self.progress_bar: 63 | self.progress_bar.clear(interrupted) 64 | 65 | def print_result(self, test): 66 | # Show the test result line. 67 | test_name = test.getFullName() 68 | print('%s: %s (%d of %d)' % (test.result.code.name, test_name, 69 | self.completed, self.tests)) 70 | 71 | # Show the test failure output, if requested. 72 | if (test.isFailure() and self.opts.showOutput) or \ 73 | self.opts.showAllOutput: 74 | if test.isFailure(): 75 | print("%s TEST '%s' FAILED %s" % ('*'*20, test.getFullName(), 76 | '*'*20)) 77 | out = test.result.output 78 | # Encode/decode so that, when using Python 3.6.5 in Windows 10, 79 | # print(out) doesn't raise UnicodeEncodeError if out contains 80 | # special characters. However, Python 2 might try to decode 81 | # as part of the encode call if out is already encoded, so skip 82 | # encoding if it raises UnicodeDecodeError. 83 | if sys.stdout.encoding: 84 | try: 85 | out = out.encode(encoding=sys.stdout.encoding, 86 | errors="replace") 87 | except UnicodeDecodeError: 88 | pass 89 | # Python 2 can raise UnicodeDecodeError here too in cases 90 | # where the stdout encoding is ASCII. Ignore decode errors 91 | # in this case. 92 | out = out.decode(encoding=sys.stdout.encoding, errors="ignore") 93 | print(out) 94 | print("*" * 20) 95 | 96 | # Report test metrics, if present. 97 | if test.result.metrics: 98 | print("%s TEST '%s' RESULTS %s" % ('*'*10, test.getFullName(), 99 | '*'*10)) 100 | items = sorted(test.result.metrics.items()) 101 | for metric_name, value in items: 102 | print('%s: %s ' % (metric_name, value.format())) 103 | print("*" * 10) 104 | 105 | # Report micro-tests, if present 106 | if test.result.microResults: 107 | items = sorted(test.result.microResults.items()) 108 | for micro_test_name, micro_test in items: 109 | print("%s MICRO-TEST: %s" % 110 | ('*'*3, micro_test_name)) 111 | 112 | if micro_test.metrics: 113 | sorted_metrics = sorted(micro_test.metrics.items()) 114 | for metric_name, value in sorted_metrics: 115 | print(' %s: %s ' % (metric_name, value.format())) 116 | 117 | # Ensure the output is flushed. 118 | sys.stdout.flush() 119 | -------------------------------------------------------------------------------- /test/lit/formats/__init__.py: -------------------------------------------------------------------------------- 1 | from lit.formats.base import ( # noqa: F401 2 | TestFormat, 3 | FileBasedTest, 4 | OneCommandPerFileTest, 5 | ExecutableTest 6 | ) 7 | 8 | from lit.formats.googletest import GoogleTest # noqa: F401 9 | from lit.formats.shtest import ShTest # noqa: F401 10 | -------------------------------------------------------------------------------- /test/lit/formats/base.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import os 3 | 4 | import lit.Test 5 | import lit.util 6 | 7 | class TestFormat(object): 8 | pass 9 | 10 | ### 11 | 12 | class FileBasedTest(TestFormat): 13 | def getTestsInDirectory(self, testSuite, path_in_suite, 14 | litConfig, localConfig): 15 | source_path = testSuite.getSourcePath(path_in_suite) 16 | for filename in os.listdir(source_path): 17 | # Ignore dot files and excluded tests. 18 | if (filename.startswith('.') or 19 | filename in localConfig.excludes): 20 | continue 21 | 22 | filepath = os.path.join(source_path, filename) 23 | if not os.path.isdir(filepath): 24 | base,ext = os.path.splitext(filename) 25 | if ext in localConfig.suffixes: 26 | yield lit.Test.Test(testSuite, path_in_suite + (filename,), 27 | localConfig) 28 | 29 | ### 30 | 31 | import re 32 | import tempfile 33 | 34 | class OneCommandPerFileTest(TestFormat): 35 | # FIXME: Refactor into generic test for running some command on a directory 36 | # of inputs. 37 | 38 | def __init__(self, command, dir, recursive=False, 39 | pattern=".*", useTempInput=False): 40 | if isinstance(command, str): 41 | self.command = [command] 42 | else: 43 | self.command = list(command) 44 | if dir is not None: 45 | dir = str(dir) 46 | self.dir = dir 47 | self.recursive = bool(recursive) 48 | self.pattern = re.compile(pattern) 49 | self.useTempInput = useTempInput 50 | 51 | def getTestsInDirectory(self, testSuite, path_in_suite, 52 | litConfig, localConfig): 53 | dir = self.dir 54 | if dir is None: 55 | dir = testSuite.getSourcePath(path_in_suite) 56 | 57 | for dirname,subdirs,filenames in os.walk(dir): 58 | if not self.recursive: 59 | subdirs[:] = [] 60 | 61 | subdirs[:] = [d for d in subdirs 62 | if (d != '.svn' and 63 | d not in localConfig.excludes)] 64 | 65 | for filename in filenames: 66 | if (filename.startswith('.') or 67 | not self.pattern.match(filename) or 68 | filename in localConfig.excludes): 69 | continue 70 | 71 | path = os.path.join(dirname,filename) 72 | suffix = path[len(dir):] 73 | if suffix.startswith(os.sep): 74 | suffix = suffix[1:] 75 | test = lit.Test.Test( 76 | testSuite, path_in_suite + tuple(suffix.split(os.sep)), 77 | localConfig) 78 | # FIXME: Hack? 79 | test.source_path = path 80 | yield test 81 | 82 | def createTempInput(self, tmp, test): 83 | raise NotImplementedError('This is an abstract method.') 84 | 85 | def execute(self, test, litConfig): 86 | if test.config.unsupported: 87 | return (lit.Test.UNSUPPORTED, 'Test is unsupported') 88 | 89 | cmd = list(self.command) 90 | 91 | # If using temp input, create a temporary file and hand it to the 92 | # subclass. 93 | if self.useTempInput: 94 | tmp = tempfile.NamedTemporaryFile(suffix='.cpp') 95 | self.createTempInput(tmp, test) 96 | tmp.flush() 97 | cmd.append(tmp.name) 98 | elif hasattr(test, 'source_path'): 99 | cmd.append(test.source_path) 100 | else: 101 | cmd.append(test.getSourcePath()) 102 | 103 | out, err, exitCode = lit.util.executeCommand(cmd) 104 | 105 | diags = out + err 106 | if not exitCode and not diags.strip(): 107 | return lit.Test.PASS,'' 108 | 109 | # Try to include some useful information. 110 | report = """Command: %s\n""" % ' '.join(["'%s'" % a 111 | for a in cmd]) 112 | if self.useTempInput: 113 | report += """Temporary File: %s\n""" % tmp.name 114 | report += "--\n%s--\n""" % open(tmp.name).read() 115 | report += """Output:\n--\n%s--""" % diags 116 | 117 | return lit.Test.FAIL, report 118 | 119 | 120 | ### 121 | 122 | # Check exit code of a simple executable with no input 123 | class ExecutableTest(FileBasedTest): 124 | def execute(self, test, litConfig): 125 | if test.config.unsupported: 126 | return lit.Test.UNSUPPORTED 127 | 128 | out, err, exitCode = lit.util.executeCommand(test.getSourcePath()) 129 | 130 | if not exitCode: 131 | return lit.Test.PASS, '' 132 | 133 | return lit.Test.FAIL, out+err 134 | 135 | -------------------------------------------------------------------------------- /test/lit/formats/shtest.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import lit.TestRunner 4 | import lit.util 5 | 6 | from .base import FileBasedTest 7 | 8 | 9 | class ShTest(FileBasedTest): 10 | """ShTest is a format with one file per test. 11 | 12 | This is the primary format for regression tests as described in the LLVM 13 | testing guide: 14 | 15 | http://llvm.org/docs/TestingGuide.html 16 | 17 | The ShTest files contain some number of shell-like command pipelines, along 18 | with assertions about what should be in the output. 19 | """ 20 | def __init__(self, execute_external=False, extra_substitutions=[], 21 | preamble_commands=[]): 22 | self.execute_external = execute_external 23 | self.extra_substitutions = extra_substitutions 24 | self.preamble_commands = preamble_commands 25 | 26 | def execute(self, test, litConfig): 27 | return lit.TestRunner.executeShTest(test, litConfig, 28 | self.execute_external, 29 | self.extra_substitutions, 30 | self.preamble_commands) 31 | -------------------------------------------------------------------------------- /test/lit/llvm/__init__.py: -------------------------------------------------------------------------------- 1 | from lit.llvm import config 2 | 3 | llvm_config = None 4 | 5 | 6 | def initialize(lit_config, test_config): 7 | global llvm_config 8 | 9 | llvm_config = config.LLVMConfig(lit_config, test_config) 10 | -------------------------------------------------------------------------------- /test/lit/worker.py: -------------------------------------------------------------------------------- 1 | """ 2 | The functions in this module are meant to run on a separate worker process. 3 | Exception: in single process mode _execute is called directly. 4 | 5 | For efficiency, we copy all data needed to execute all tests into each worker 6 | and store it in global variables. This reduces the cost of each task. 7 | """ 8 | import contextlib 9 | import os 10 | import signal 11 | import time 12 | import traceback 13 | 14 | import lit.Test 15 | import lit.util 16 | 17 | 18 | _lit_config = None 19 | _parallelism_semaphores = None 20 | 21 | 22 | def initialize(lit_config, parallelism_semaphores): 23 | """Copy data shared by all test executions into worker processes""" 24 | global _lit_config 25 | global _parallelism_semaphores 26 | _lit_config = lit_config 27 | _parallelism_semaphores = parallelism_semaphores 28 | 29 | # We use the following strategy for dealing with Ctrl+C/KeyboardInterrupt in 30 | # subprocesses created by the multiprocessing.Pool. 31 | # https://noswap.com/blog/python-multiprocessing-keyboardinterrupt 32 | signal.signal(signal.SIGINT, signal.SIG_IGN) 33 | 34 | 35 | def execute(test): 36 | """Run one test in a multiprocessing.Pool 37 | 38 | Side effects in this function and functions it calls are not visible in the 39 | main lit process. 40 | 41 | Arguments and results of this function are pickled, so they should be cheap 42 | to copy. 43 | """ 44 | with _get_parallelism_semaphore(test): 45 | result = _execute(test, _lit_config) 46 | 47 | test.setResult(result) 48 | return test 49 | 50 | 51 | # TODO(python3): replace with contextlib.nullcontext 52 | @contextlib.contextmanager 53 | def NopSemaphore(): 54 | yield 55 | 56 | 57 | def _get_parallelism_semaphore(test): 58 | pg = test.config.parallelism_group 59 | if callable(pg): 60 | pg = pg(test) 61 | return _parallelism_semaphores.get(pg, NopSemaphore()) 62 | 63 | 64 | # Do not inline! Directly used by LitTestCase.py 65 | def _execute(test, lit_config): 66 | start = time.time() 67 | result = _execute_test_handle_errors(test, lit_config) 68 | result.elapsed = time.time() - start 69 | result.start = start 70 | result.pid = os.getpid() 71 | return result 72 | 73 | 74 | def _execute_test_handle_errors(test, lit_config): 75 | try: 76 | result = test.config.test_format.execute(test, lit_config) 77 | return _adapt_result(result) 78 | except: 79 | if lit_config.debug: 80 | raise 81 | output = 'Exception during script execution:\n' 82 | output += traceback.format_exc() 83 | output += '\n' 84 | return lit.Test.Result(lit.Test.UNRESOLVED, output) 85 | 86 | 87 | # Support deprecated result from execute() which returned the result 88 | # code and additional output as a tuple. 89 | def _adapt_result(result): 90 | if isinstance(result, lit.Test.Result): 91 | return result 92 | assert isinstance(result, tuple) 93 | code, output = result 94 | return lit.Test.Result(code, output) 95 | -------------------------------------------------------------------------------- /test/model.js: -------------------------------------------------------------------------------- 1 | var model = require('../core/model.js') 2 | 3 | var ModelMock = function() { 4 | this.updater = new model.ModelUpdate() 5 | this.count = 0 6 | } 7 | 8 | ModelMock.prototype.reset = function(n) { 9 | this.count = n 10 | this.updater.reset(this) 11 | } 12 | 13 | ModelMock.prototype.insert = function(begin, end) { 14 | this.count += end - begin 15 | this.updater.insert(this, begin, end) 16 | } 17 | 18 | ModelMock.prototype.remove = function(begin, end) { 19 | this.count -= end - begin 20 | this.updater.remove(this, begin, end) 21 | } 22 | 23 | ModelMock.prototype.update = function(begin, end) { 24 | this.updater.update(this, begin, end) 25 | } 26 | 27 | ModelMock.prototype.apply = function(view) { 28 | this.updater.apply(view) 29 | } 30 | 31 | module.exports = ModelMock 32 | -------------------------------------------------------------------------------- /test/qml/enum_and_ids_in_string_context.qml: -------------------------------------------------------------------------------- 1 | // RUN: %build 2 | // RUN: grep "\this.wrap = Text.NoWrap;" %out/qml.enum_and_ids_in_string_context.js 3 | // RUN: grep "Text = _globals.core.Text.prototype" %out/qml.enum_and_ids_in_string_context.js 4 | // RUN: grep "\$this._removeUpdater('textFormat'); \$this.textFormat = Text.Html;" %out/qml.enum_and_ids_in_string_context.js 5 | // RUN: ! grep "_globals.core.Device.prototype.Platform" %out/qml.enum_and_ids_in_string_context.js 6 | // RUN: grep "this.setValue('Device.Platform', \"text.wrap\")" %out/qml.enum_and_ids_in_string_context.js 7 | Text { 8 | id: text; 9 | textFormat: Text.Html; 10 | 11 | function setValue(name, value) { } 12 | 13 | onText: { 14 | this.wrap = Text.NoWrap; 15 | this.setValue('Device.Platform', "text.wrap") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/qml/expr.qml: -------------------------------------------------------------------------------- 1 | // RUN: %build 2 | // RUN: grep "this.a = (2 + (((2 \* 2) \% 3) / 4))" %out/qml.expr.js 3 | // RUN: grep "this.b = ((~ 1) - (~ 2))" %out/qml.expr.js 4 | // RUN: grep "this.c = ((2 \*\* 2) + 2)" %out/qml.expr.js 5 | // RUN: grep "this.d = ((+ 3) + (- 2))" %out/qml.expr.js 6 | // RUN: grep "this.e = (~ (~ 0))" %out/qml.expr.js 7 | // RUN: grep "this.f = ((1 + 1) << (2 + 1))" %out/qml.expr.js 8 | // RUN: grep "this.g = (1 + (2 \* (1 + 1)))" %out/qml.expr.js 9 | // RUN: grep "this.h = \[(1 \*\* 2),\$this.a,\$this.b\]" %out/qml.expr.js 10 | // RUN: grep "this.i = ((\$this.h\[0\]) + ((\$this.h\[1\]) \* (\$this.h\[2\])))" %out/qml.expr.js 11 | // RUN: grep "this.j = (123.456 . toFixed)(2)" %out/qml.expr.js 12 | // RUN: grep "this.k = ((typeof \$this.i) === 'number')" %out/qml.expr.js 13 | // RUN: grep "this.l = \$this.func((\$this.k !== undefined));" %out/qml.expr.js 14 | // RUN: grep "this.m = Date.now();" %out/qml.expr.js 15 | // RUN: grep "this.n = (new Date());" %out/qml.expr.js 16 | 17 | Object { 18 | property int a: 2 + 2 * 2 % 3 / 4; 19 | property int b: ~1 - ~2; 20 | property int c: 2 ** 2 + 2; 21 | property int d: + 3 + - 2; 22 | property int e: ~~0; 23 | property int f: 1 + 1 << 2 + 1; 24 | property int g: 1 + 2 * (1 + 1); 25 | property array h: [1 ** 2, a, b]; 26 | property int i: h[0] + h[1] * h[2]; 27 | property string j: (123.456).toFixed(2); 28 | property bool k: typeof i === 'number'; 29 | property bool l: this.func(this.k !== undefined); 30 | property int m: Date.now(); 31 | property Date n: new Date(); 32 | } 33 | -------------------------------------------------------------------------------- /test/qml/float_parse.qml: -------------------------------------------------------------------------------- 1 | // RUN: %build 2 | Text { 3 | opacity: .8; 4 | } 5 | -------------------------------------------------------------------------------- /test/qml/gh172-a.qml: -------------------------------------------------------------------------------- 1 | // RUN: !(%build) 2 | 3 | Item { 4 | id: myItem; 5 | width: x12906efiuh; 6 | } 7 | -------------------------------------------------------------------------------- /test/qml/gh172-b.qml: -------------------------------------------------------------------------------- 1 | // RUN: !(%build) 2 | 3 | Item { 4 | id: myItem; 5 | width: x12906efiuh.o9898dsf; 6 | } 7 | -------------------------------------------------------------------------------- /test/qml/gh205.qml: -------------------------------------------------------------------------------- 1 | // RUN: %build 2 | // RUN: grep "delegate.width = delegate.parent.parent.w0" %out/qml.gh205.js 3 | 4 | Item { 5 | anchors.fill: context; 6 | property color defaultColor: "blue"; 7 | Rectangle { 8 | anchors.fill: parent; 9 | color: parent.defaultColor; 10 | } 11 | 12 | property int w0: width/5; 13 | 14 | ListView { 15 | model: ListModel { 16 | ListElement { value: "foo"; } 17 | ListElement { value: "bar"; } 18 | ListElement { value: "baz"; } 19 | } 20 | anchors.fill: parent; 21 | delegate: Rectangle { 22 | color: defaultColor; 23 | width: w0; 24 | height: parent.parent.w0; 25 | Rectangle { 26 | anchors.margins: 10; 27 | anchors.fill: parent; 28 | color: "red"; 29 | Text { 30 | anchors.centerIn: parent; 31 | color: "white"; 32 | text: model.value + " " + w0; 33 | } 34 | width: 100; 35 | height: 100; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/qml/model_row.qml: -------------------------------------------------------------------------------- 1 | // RUN: %build 2 | // RUN: grep "delegate.row = delegate._get('model')" %out/qml.model_row.js 3 | 4 | Repeater { 5 | model: ListModel {} 6 | delegate: Item { 7 | property var row: model; 8 | } 9 | } -------------------------------------------------------------------------------- /test/qml/underscore_in_property_type.qml: -------------------------------------------------------------------------------- 1 | // RUN: %build 2 | Text { 3 | property my_special_type__ value; 4 | } 5 | -------------------------------------------------------------------------------- /test/view.js: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon') 2 | 3 | var View = function() { 4 | this._items = { length : 0 } 5 | } 6 | 7 | View.prototype._insertItems = function(begin, end) { 8 | if (begin < end) 9 | this._items.length += end - begin 10 | } 11 | 12 | View.prototype._updateItems = function(begin, end) { 13 | } 14 | 15 | View.prototype._removeItems = function(begin, end) 16 | { 17 | if (begin < end) 18 | this._items.length -= end - begin 19 | } 20 | View.prototype._updateDelegate = function(idx) { } 21 | View.prototype._updateDelegateIndex = function(idx) { } 22 | 23 | module.exports = View 24 | -------------------------------------------------------------------------------- /update-ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | from compiler.ts import Ts 5 | 6 | parser = argparse.ArgumentParser() 7 | parser.add_argument("directory", nargs='+') 8 | parser.add_argument("--ts", '-t', help='tr file to write to') 9 | args = parser.parse_args() 10 | 11 | ts = Ts(args.ts) 12 | ts.scan(args.directory) 13 | ts.save() 14 | --------------------------------------------------------------------------------