├── .gitignore ├── LICENSE ├── README.md ├── assets ├── images │ ├── BackspaceIcon.png │ ├── ButtonShadow.png │ ├── CheckmarkIcon.png │ ├── DismissIcon.png │ ├── EnterIcon.png │ ├── GlobalIcon.png │ ├── KeyShadow.png │ ├── ShiftActiveIcon.png │ ├── ShiftIcon.png │ └── SwitchShadow.png └── sounds │ ├── ButtonClick.mp3 │ ├── ButtonClickDisabled.mp3 │ ├── InputClick.mp3 │ ├── KeyDown.mp3 │ ├── KeyIn.mp3 │ └── ToastShow.mp3 ├── boilerplate ├── .gitignore ├── README.md ├── assets │ ├── images │ │ ├── BackspaceIcon.png │ │ ├── ButtonShadow.png │ │ ├── CheckmarkIcon.png │ │ ├── DismissIcon.png │ │ ├── EnterIcon.png │ │ ├── GlobalIcon.png │ │ ├── KeyShadow.png │ │ ├── ShiftActiveIcon.png │ │ ├── ShiftIcon.png │ │ └── SwitchShadow.png │ └── sounds │ │ ├── ButtonClick.mp3 │ │ ├── ButtonClickDisabled.mp3 │ │ ├── InputClick.mp3 │ │ ├── KeyDown.mp3 │ │ ├── KeyIn.mp3 │ │ └── ToastShow.mp3 ├── index.html └── package.json ├── dist ├── aframe-material.js ├── aframe-material.js.map ├── aframe-material.min.js └── aframe-material.min.js.map ├── index.html ├── package.json ├── src ├── alert │ └── index.js ├── button │ ├── assets.js │ ├── index.js │ └── sfx.js ├── checkbox │ ├── assets.js │ ├── index.js │ └── sfx.js ├── core │ └── event.js ├── fade │ └── index.js ├── form │ └── index.js ├── index.js ├── input │ └── index.js ├── keyboard │ ├── assets.js │ ├── behaviors.js │ ├── config.js │ ├── draw.js │ ├── index.js │ ├── layouts.js │ └── sfx.js ├── radio │ ├── assets.js │ ├── index.js │ └── sfx.js ├── switch │ ├── assets.js │ ├── index.js │ └── sfx.js ├── toast │ ├── assets.js │ ├── index.js │ └── sfx.js └── utils.js ├── static └── screenshot.png ├── svg ├── BackspaceIcon.svg ├── CheckmarkIcon.svg ├── DismissIcon.svg ├── EnterIcon.svg ├── GlobalIcon.svg ├── ShiftActiveIcon.svg └── ShiftIcon.svg └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git 3 | *.sw[mnop] 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # node-waf configuration 29 | .lock-wscript 30 | 31 | # Compiled binary addons (http://nodejs.org/api/addons.html) 32 | build/Release 33 | 34 | # Dependency directories 35 | node_modules 36 | jspm_packages 37 | 38 | # Optional npm cache directory 39 | .npm 40 | 41 | # Optional REPL history 42 | .node_repl_history 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright © 2017 Etienne Pinchon. 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A-Frame Material Kit 👽 2 | 3 | This is real! WebVR is getting even better! 4 | Inputs, keyboard, buttons, checkboxes, radio buttons, switches, forms, toasts and more - following the Google Material design guideline for [A-Frame](https://aframe.io). 5 | It is perfect for room-scale webVR apps. 👌 6 | 7 | ![](static/screenshot.png) 8 | 9 | ## Demo 10 | 11 | #### [👉👉 Live demo 😎 👈👈](https://etiennepinchon.github.io/aframe-material/) 12 | 13 | Looks surreal if you have a headset! :) 14 | 15 | ## Getting Started 16 | 17 | Here is the code from the demo. That is it. Pure html! 18 | 19 | ```html 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | ``` 47 | 48 | ## 👉👉 Install 👈👈 49 | 50 | ## Option 1: 51 | Download the `boilerplate` and follow the readme (super easy). 52 | 53 | ## Option 2: 54 | Since this kit is using assets (icons and sounds) you need to download the git project and copy the `assets` folder to the location of your choice and specify the path in the head of the html page. 55 | 56 | ```html 57 | 58 | 61 | 62 | 63 | 66 | 67 | ``` 68 | 69 | ----------------------------------------------- 70 | 71 | # Documentation 🙌 72 | 73 | # 👉👉 a-keyboard ⌨️🤤 74 | 75 | Create a virtual keyboard that works with inputs out of the box. 76 | 77 | ```html 78 | 79 | ``` 80 | 81 | ### Attributes 82 | 83 | | Name | Description | Type | Default | 84 | | --- | --- | --- | --- | 85 | | is-open | Whether or not the keyboard should be open | boolean | false | 86 | | physical-keyboard | Whether or not the physical keyboard should be respond to key press. (Great for debugging) | boolean | false | 87 | 88 | ### Methods 89 | 90 | | Name | Description | 91 | | --- | --- | 92 | | show() | Display the keyboard without any animations. | 93 | | hide() | Hide the keyboard without any animations. | 94 | | open() | Open the keyboard smoothly. | 95 | | dismiss() | Close the keyboard smoothly. | 96 | | destroy() | Remove the keyboard from the scene. | 97 | 98 | ### Events 99 | 100 | | Name | Description | 101 | | --- | --- | 102 | | didopen | Triggered when the keyboard did open. | 103 | | diddismiss | Triggered when the keyboard did close. | 104 | | input | Triggered when a key is pressed. | 105 | | backspace | Triggered when the backspace key is pressed. | 106 | | enter | Triggered when the enter is pressed. | 107 | 108 | ### Custom example (just in case 😉) 109 | 110 | ```javascript 111 | let keyboard = document.querySelector("a-keyboard"); 112 | keyboard.open(); 113 | keyboard.addEventListener('input', (e)=>{ 114 | str += e.detail; 115 | console.log(str); 116 | }); 117 | keyboard.addEventListener('enter', (e)=>{ 118 | console.log("Enter key pressed!") 119 | }) 120 | keyboard.addEventListener('dismiss', (e)=>{ 121 | console.log("Dismiss: ", e); 122 | keyboard.dismiss(); 123 | }); 124 | keyboard.addEventListener('backspace', (e)=>{ 125 | str = str.slice(0, -1); 126 | console.log(str); 127 | }); 128 | ``` 129 | 130 | ----------------------------------------------- 131 | 132 | # 👉👉 a-input 133 | 134 | Create a single line text input that work with the ``. 135 | 136 | ```html 137 | 138 | ``` 139 | 140 | ### Attributes 141 | 142 | | Name | Description | Type | Default | 143 | | --- | --- | --- | --- | 144 | | value | Value of the field. | string | "" | 145 | | name | Name of the field. | string | "" | 146 | | disabled | Whether or not the input should be disabled. | boolean | false | 147 | | color | Text color. | color | "#000" | 148 | | font | Text font | string. | "default" | 149 | | opacity | Input opacity. | number | 1 | 150 | | placeholder | Value of the placeholder. | string | "" | 151 | | placeholder-color | Text color of the placeholder. | color | "#AAA" | 152 | | max-length | Limit the number of characters. | int | 0 (Infinite) | 153 | | type | Can be either "text" or "password". | string | "text" | 154 | | width | Width of the input. | number | 1 | 155 | | cursor-width | Width of the cursor. | number | 0.01 | 156 | | cursor-height | Height of the cursor. | number | 0.08 | 157 | | cursor-color | Color of the cursor. | color | "#007AFF" | 158 | | background-color | Color of the field. | color | "#FFF" | 159 | | background-opacity | Opacity of the field background only. | number | 1 | 160 | 161 | ### Methods 162 | 163 | | Name | Description | 164 | | --- | --- | 165 | | focus() | Focus the input. | 166 | | blur() | Blur the input. | 167 | 168 | ### Events 169 | 170 | | Name | Description | 171 | | --- | --- | 172 | | focus | Triggered when the input is focused. | 173 | | blur | Triggered when the input is blurred. | 174 | | change | Triggered when the value of the input changed. | 175 | 176 | ----------------------------------------------- 177 | 178 | # 👉👉 a-button 179 | 180 | Create a button (can be raised of flat). 181 | 182 | ```html 183 | 184 | ``` 185 | 186 | ### Attributes 187 | 188 | | Name | Description | Type | Default | 189 | | --- | --- | --- | --- | 190 | | value | Value of the button. | string | "Button" | 191 | | name | Name of the button. | string | "" | 192 | | disabled | Whether or not the button should be disabled. | boolean | false | 193 | | type | Can be either "raised" or "flat". | string | "raised" | 194 | | color | Text color. | color | "#FFF" | 195 | | button-color | Color of the button. | color | "#4076fd" | 196 | | font | Text font | string. | "default" | 197 | | opacity | Input opacity. | number | 1 | 198 | | width | Width of the input. | number | 1 | 199 | 200 | ### Events 201 | 202 | | Name | Description | 203 | | --- | --- | 204 | | change:width | Triggered when the width of the button changed. | 205 | 206 | ## a-form 207 | 208 | Create a form to get the same html `
` behaviors with ``, ``, ``, ``, ``. 209 | 210 | ```html 211 | 212 | ``` 213 | 214 | ----------------------------------------------- 215 | 216 | # 👉👉 a-radio 217 | 218 | Create a radio button. 219 | 220 | ```html 221 | 222 | ``` 223 | 224 | ### Attributes 225 | 226 | | Name | Description | Type | Default | 227 | | --- | --- | --- | --- | 228 | | checked | Whether or not the radio button should be checked. | boolean | false | 229 | | value | Value of the radio button. | string | "" | 230 | | name | Name of the radio button. | string | "" | 231 | | disabled | Whether or not the button should be disabled. | boolean | false | 232 | | label | Text following the radio button (totally optional). | string | "" | 233 | | color | Text color of the label. | color | "#757575" | 234 | | radio-button | Color of the radio button when unchecked. | color | "#757575" | 235 | | radio-color-checked | Color of the radio button when checked. | color | "#4076fd" | 236 | | font | Text font | string. | "default" | 237 | | opacity | Input opacity. | number | 1 | 238 | | width | Width of the input. | number | 1 | 239 | 240 | ### Events 241 | 242 | | Name | Description | 243 | | --- | --- | 244 | | change | Triggered when the value of the radio button changed. | 245 | 246 | ----------------------------------------------- 247 | 248 | # 👉👉 a-checkbox 249 | 250 | Create a checkbox. 251 | 252 | ```html 253 | 254 | ``` 255 | 256 | ### Attributes 257 | 258 | | Name | Description | Type | Default | 259 | | --- | --- | --- | --- | 260 | | checked | Whether or not the checkbox should be checked. | boolean | false | 261 | | value | Value of the checkbox. | string | "" | 262 | | name | Name of the checkbox. | string | "" | 263 | | disabled | Whether or not the checkbox should be disabled. | boolean | false | 264 | | label | Text following the checkbox (totally optional). | string | "" | 265 | | color | Text color of the label. | color | "#757575" | 266 | | radio-button | Color of the checkbox when unchecked. | color | "#757575" | 267 | | radio-color-checked | Color of the checkbox when checked. | color | "#4076fd" | 268 | | font | Text font | string. | "default" | 269 | | opacity | Input opacity. | number | 1 | 270 | | width | Width of the input. | number | 1 | 271 | 272 | ### Events 273 | 274 | | Name | Description | 275 | | --- | --- | 276 | | change | Triggered when the value of the checkbox changed. | 277 | 278 | ----------------------------------------------- 279 | 280 | # 👉👉 a-switch 281 | 282 | Create a switch button that can be toggle on/off. 283 | 284 | ```html 285 | 286 | ``` 287 | 288 | ### Attributes 289 | 290 | | Name | Description | Type | Default | 291 | | --- | --- | --- | --- | 292 | | enabled | Whether or not the switch should be enabled. | boolean | false | 293 | | name | Name of the checkbox. | string | "" | 294 | | disabled | Whether or not the switch should be disabled. | boolean | false | 295 | | fill-color | Color of the fill when off. | color | "#bababa" | 296 | | knob-color | Color of the knob when off. | color | "#f5f5f5" | 297 | | fill-color-enabled | Color of the fill when on. | color | "#80a8ff" | 298 | | knob-color-enabled | Color of the knob when on. | color | "#4076fd" | 299 | | fill-color-disabled | Color of the fill when disabled. | color | "#939393" | 300 | | knob-color-disabled | Color of the knob when disabled. | color | "#a2a2a2" | 301 | 302 | ### Events 303 | 304 | | Name | Description | 305 | | --- | --- | 306 | | change | Triggered when the switch is toggled. | 307 | 308 | ----------------------------------------------- 309 | 310 | # 👉👉 a-toast 311 | 312 | Create a toast to alert the user of something. 313 | 314 | ```html 315 | 316 | ``` 317 | 318 | ### Attributes 319 | 320 | | Name | Description | Type | Default | 321 | | --- | --- | --- | --- | 322 | | message | Text shown by the toast. | string | "You are cool" | 323 | | action | Text of the button (optional). | string | "" | 324 | | color | Text color. | color | "#FFF" | 325 | | background-color | Color of the toast. | color | "#4076fd" | 326 | | font | Text font | string. | "default" | 327 | | width | Width of the input. | number | 1 | 328 | | duration | Duration of the toast | number | 2000 (2sec) | 329 | | autoshow | Whether the toast should show right away. | boolean | true | 330 | 331 | ### Events 332 | 333 | | Name | Description | 334 | | --- | --- | 335 | | actionclick | Triggered when the action is clicked. | 336 | 337 | ### Custom example (just in case 😉) 338 | 339 | ```javascript 340 | let button = document.querySelector('a-button'); 341 | let toast = document.querySelector('a-toast'); 342 | toast.addEventListener('actionclick', ()=>{ 343 | toast.hide(); 344 | }) 345 | button.addEventListener('click', ()=> { 346 | toast.show(); 347 | }) 348 | ``` 349 | 350 | ----------------------------------------------- 351 | 352 | # 👉👉 a-rounded 353 | 354 | Create a rounded rectangle, useful to create beautiful interfaces 😁. 355 | 356 | [👉 github.com/etiennepinchon/aframe-rounded](https://github.com/etiennepinchon/aframe-rounded) 357 | 358 | ----------------------------------------------- 359 | 360 | ## Change log 361 | 362 | ### 0.1.1 363 | 364 | * Added `enter` keyboard event. 365 | * Added physical keyboard interaction using the `physical-keyboard` property. 366 | 367 | ----------------------------------------------- 368 | 369 | ## Want to make some changes to it? 370 | 371 | ### Installation 372 | 373 | First make sure you have Node installed. 374 | 375 | On Mac OS X, it's recommended to use [Homebrew](http://brew.sh/) to install Node + [npm](https://www.npmjs.com): 376 | 377 | brew install node 378 | 379 | To install the Node dependencies: 380 | 381 | npm install 382 | 383 | ### Local Development 384 | 385 | To serve the site from a simple Node development server: 386 | 387 | npm start 388 | 389 | Then launch the site from your favorite browser: 390 | 391 | [__http://localhost:3333/__](http://localhost:3333/) 392 | 393 | ## License 394 | 395 | Distributed under an [MIT License](LICENSE). 396 | 397 | Made by Etienne Pinchon (@etiennepinchon) - September 2017. 398 | -------------------------------------------------------------------------------- /assets/images/BackspaceIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/images/BackspaceIcon.png -------------------------------------------------------------------------------- /assets/images/ButtonShadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/images/ButtonShadow.png -------------------------------------------------------------------------------- /assets/images/CheckmarkIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/images/CheckmarkIcon.png -------------------------------------------------------------------------------- /assets/images/DismissIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/images/DismissIcon.png -------------------------------------------------------------------------------- /assets/images/EnterIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/images/EnterIcon.png -------------------------------------------------------------------------------- /assets/images/GlobalIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/images/GlobalIcon.png -------------------------------------------------------------------------------- /assets/images/KeyShadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/images/KeyShadow.png -------------------------------------------------------------------------------- /assets/images/ShiftActiveIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/images/ShiftActiveIcon.png -------------------------------------------------------------------------------- /assets/images/ShiftIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/images/ShiftIcon.png -------------------------------------------------------------------------------- /assets/images/SwitchShadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/images/SwitchShadow.png -------------------------------------------------------------------------------- /assets/sounds/ButtonClick.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/sounds/ButtonClick.mp3 -------------------------------------------------------------------------------- /assets/sounds/ButtonClickDisabled.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/sounds/ButtonClickDisabled.mp3 -------------------------------------------------------------------------------- /assets/sounds/InputClick.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/sounds/InputClick.mp3 -------------------------------------------------------------------------------- /assets/sounds/KeyDown.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/sounds/KeyDown.mp3 -------------------------------------------------------------------------------- /assets/sounds/KeyIn.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/sounds/KeyIn.mp3 -------------------------------------------------------------------------------- /assets/sounds/ToastShow.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/assets/sounds/ToastShow.mp3 -------------------------------------------------------------------------------- /boilerplate/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git 3 | *.sw[mnop] 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # node-waf configuration 29 | .lock-wscript 30 | 31 | # Compiled binary addons (http://nodejs.org/api/addons.html) 32 | build/Release 33 | 34 | # Dependency directories 35 | node_modules 36 | jspm_packages 37 | 38 | # Optional npm cache directory 39 | .npm 40 | 41 | # Optional REPL history 42 | .node_repl_history 43 | -------------------------------------------------------------------------------- /boilerplate/README.md: -------------------------------------------------------------------------------- 1 | # A-Frame Material Boilerplate 2 | 3 | ### Installation 4 | 5 | First make sure you have Node installed. 6 | 7 | On Mac OS X, it's recommended to use [Homebrew](http://brew.sh/) to install Node + [npm](https://www.npmjs.com): 8 | 9 | brew install node 10 | 11 | To install the Node dependencies: 12 | 13 | npm install 14 | 15 | 16 | ### Local Development 17 | 18 | To serve the site from a simple Node development server: 19 | 20 | npm start 21 | 22 | Then launch the site from your favorite browser: 23 | 24 | [__http://localhost:3000/__](http://localhost:3000/) 25 | 26 | If you wish to serve the site from a different port: 27 | 28 | PORT=8000 npm start 29 | 30 | ## License 31 | 32 | Distributed under an [MIT License](LICENSE). 33 | -------------------------------------------------------------------------------- /boilerplate/assets/images/BackspaceIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/images/BackspaceIcon.png -------------------------------------------------------------------------------- /boilerplate/assets/images/ButtonShadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/images/ButtonShadow.png -------------------------------------------------------------------------------- /boilerplate/assets/images/CheckmarkIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/images/CheckmarkIcon.png -------------------------------------------------------------------------------- /boilerplate/assets/images/DismissIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/images/DismissIcon.png -------------------------------------------------------------------------------- /boilerplate/assets/images/EnterIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/images/EnterIcon.png -------------------------------------------------------------------------------- /boilerplate/assets/images/GlobalIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/images/GlobalIcon.png -------------------------------------------------------------------------------- /boilerplate/assets/images/KeyShadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/images/KeyShadow.png -------------------------------------------------------------------------------- /boilerplate/assets/images/ShiftActiveIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/images/ShiftActiveIcon.png -------------------------------------------------------------------------------- /boilerplate/assets/images/ShiftIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/images/ShiftIcon.png -------------------------------------------------------------------------------- /boilerplate/assets/images/SwitchShadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/images/SwitchShadow.png -------------------------------------------------------------------------------- /boilerplate/assets/sounds/ButtonClick.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/sounds/ButtonClick.mp3 -------------------------------------------------------------------------------- /boilerplate/assets/sounds/ButtonClickDisabled.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/sounds/ButtonClickDisabled.mp3 -------------------------------------------------------------------------------- /boilerplate/assets/sounds/InputClick.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/sounds/InputClick.mp3 -------------------------------------------------------------------------------- /boilerplate/assets/sounds/KeyDown.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/sounds/KeyDown.mp3 -------------------------------------------------------------------------------- /boilerplate/assets/sounds/KeyIn.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/sounds/KeyIn.mp3 -------------------------------------------------------------------------------- /boilerplate/assets/sounds/ToastShow.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/boilerplate/assets/sounds/ToastShow.mp3 -------------------------------------------------------------------------------- /boilerplate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aframe-material😲😆😛 6 | 7 | 8 | 9 | 10 | 11 | 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 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /boilerplate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-material-boilerplate", 3 | "description": "Boilerplate for aframe-material", 4 | "version": "1.0.03", 5 | "license": "MIT", 6 | "repository": "etiennepinchon/aframe-rounded", 7 | "scripts": { 8 | "start": "budo --live --verbose --port 3000 --open" 9 | }, 10 | "devDependencies": { 11 | "browserify": "^12.0.1", 12 | "budo": "^7.1.0", 13 | "shelljs": "^0.6.0", 14 | "webpack": "^1.12.9", 15 | "ghpages": "0.0.3" 16 | }, 17 | "keywords": [ 18 | "aframe", 19 | "aframe-component", 20 | "webvr", 21 | "vr" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /dist/aframe-material.min.js: -------------------------------------------------------------------------------- 1 | !function(t){function e(a){if(i[a])return i[a].exports;var o=i[a]={exports:{},id:a,loaded:!1};return t[a].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var i={};return e.m=t,e.c=i,e.p="/dist/",e(0)}([function(t,e,i){t.exports=i(1)},function(t,e,i){"use strict";!function(){return AFRAME?(AFRAME.ASSETS_PATH||(AFRAME.ASSETS_PATH="./assets"),i(2),i(3),i(5),i(13),i(14),i(17),i(18),i(21),i(24),void i(27)):console.error("AFRAME is required!")}()},function(t,e){!function(t){function e(a){if(i[a])return i[a].exports;var o=i[a]={exports:{},id:a,loaded:!1};return t[a].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var i={};return e.m=t,e.c=i,e.p="",e(0)}([function(t,e){AFRAME.registerComponent("rounded",{schema:{enabled:{default:!0},width:{type:"number",default:1},height:{type:"number",default:1},radius:{type:"number",default:.3},topLeftRadius:{type:"number",default:-1},topRightRadius:{type:"number",default:-1},bottomLeftRadius:{type:"number",default:-1},bottomRightRadius:{type:"number",default:-1},color:{type:"color",default:"#F0F0F0"},opacity:{type:"number",default:1}},init:function(){this.rounded=new THREE.Mesh(this.draw(),new THREE.MeshPhongMaterial({color:new THREE.Color(this.data.color),side:THREE.DoubleSide})),this.updateOpacity(),this.el.setObject3D("mesh",this.rounded)},update:function(){this.data.enabled?this.rounded&&(this.rounded.visible=!0,this.rounded.geometry=this.draw(),this.rounded.material.color=new THREE.Color(this.data.color),this.updateOpacity()):this.rounded.visible=!1},updateOpacity:function(){this.data.opacity<0&&(this.data.opacity=0),this.data.opacity>1&&(this.data.opacity=1),this.data.opacity<1?this.rounded.material.transparent=!0:this.rounded.material.transparent=!1,this.rounded.material.opacity=this.data.opacity},tick:function(){},remove:function(){this.rounded&&(this.el.object3D.remove(this.rounded),this.rounded=null)},draw:function(){function t(t,e,i,a,o,n,r,s,l){n||(n=1e-5),r||(r=1e-5),s||(s=1e-5),l||(l=1e-5),t.moveTo(e,i+n),t.lineTo(e,i+o-n),t.quadraticCurveTo(e,i+o,e+n,i+o),t.lineTo(e+a-r,i+o),t.quadraticCurveTo(e+a,i+o,e+a,i+o-r),t.lineTo(e+a,i+l),t.quadraticCurveTo(e+a,i,e+a-l,i),t.lineTo(e+s,i),t.quadraticCurveTo(e,i,e,i+s)}var e=new THREE.Shape,i=[this.data.radius,this.data.radius,this.data.radius,this.data.radius];return this.data.topLeftRadius!=-1&&(i[0]=this.data.topLeftRadius),this.data.topRightRadius!=-1&&(i[1]=this.data.topRightRadius),this.data.bottomLeftRadius!=-1&&(i[2]=this.data.bottomLeftRadius),this.data.bottomRightRadius!=-1&&(i[3]=this.data.bottomRightRadius),t(e,0,0,this.data.width,this.data.height,i[0],i[1],i[2],i[3]),new THREE.ShapeBufferGeometry(e)},pause:function(){},play:function(){}}),AFRAME.registerPrimitive("a-rounded",{defaultComponents:{rounded:{}},mappings:{enabled:"rounded.enabled",width:"rounded.width",height:"rounded.height",radius:"rounded.radius","top-left-radius":"rounded.topLeftRadius","top-right-radius":"rounded.topRightRadius","bottom-left-radius":"rounded.bottomLeftRadius","bottom-right-radius":"rounded.bottomRightRadius",color:"rounded.color",opacity:"rounded.opacity"}})}])},function(t,e,i){"use strict";var a=i(4),o=function(t){this.el.object3D.traverse(function(e){e.material&&(e.material.transparent=!0,e.material.opacity=t)});var e=!0,i=!1,a=void 0;try{for(var o,n=this.textEntities[Symbol.iterator]();!(e=(o=n.next()).done);e=!0){var r=o.value;r.setAttribute("opacity",t)}}catch(t){i=!0,a=t}finally{try{!e&&n.return&&n.return()}finally{if(i)throw a}}};AFRAME.registerComponent("fadein",{schema:{duration:{type:"int",default:200}},init:function(){this.textEntities=this.el.querySelectorAll("a-text"),this.opacityUpdate(0),this.start=null},tick:function(t){this.start||(this.start=t);var e=Math.min((t-this.start)/this.data.duration,1);this.opacityUpdate(e),1===e&&(this.el.removeAttribute("fadein"),a.emit(this.el,"animationend"))},opacityUpdate:o}),AFRAME.registerComponent("fadeout",{schema:{duration:{type:"int",default:200}},init:function(){this.textEntities=this.el.querySelectorAll("a-text"),this.opacityUpdate(1),this.start=null},tick:function(t){this.start||(this.start=t);var e=1-Math.min((t-this.start)/this.data.duration,1);this.opacityUpdate(e),0===e&&(this.el.removeAttribute("fadeout"),a.emit(this.el,"animationend"))},opacityUpdate:o}),AFRAME.registerComponent("show",{init:function(){this.textEntities=this.el.querySelectorAll("a-text"),this.opacityUpdate(1),this.el.removeAttribute("show")},opacityUpdate:o}),AFRAME.registerComponent("hide",{init:function(){this.textEntities=this.el.querySelectorAll("a-text"),this.opacityUpdate(0),this.el.removeAttribute("hide")},opacityUpdate:o})},function(t,e){"use strict";t.exports={emit:function(t,e,i){t.dispatchEvent(new CustomEvent(e,{detail:i}))}}},function(t,e,i){"use strict";var a=i(6),o=i(7),n=i(8),r=i(11),s=i(12);AFRAME.registerComponent("keyboard",{schema:{isOpen:{type:"boolean",default:!1}},currentInput:null,init:function(){a.preloadAssets(o),s.init(this.el),n.init(this.el);var t=n.numericalUI(),e=n.mainUI(),i=n.actionsUI();this.el.alphabeticalLayout=n.alphabeticalLayout(),this.el.symbolsLayout=n.symbolsLayout(),t.appendChild(n.numericalLayout()),e.appendChild(this.el.alphabeticalLayout),i.appendChild(n.actionsLayout()),this.el.appendChild(t),this.el.appendChild(e),this.el.appendChild(i),this.el.show=function(){r.showKeyboard(l.el)},this.el.hide=function(){r.hideKeyboard(l.el)},this.el.open=function(){r.openKeyboard(l.el)},this.el.dismiss=function(){r.dismissKeyboard(l.el)},this.el.destroy=function(){r.destroyKeyboard(l.el)},this.el.setAttribute("scale","2 2 2"),this.el.setAttribute("rotation","-20 0 0"),this.el.setAttribute("position","-1.5 -0.3 -2");var l=this;this.el.addEventListener("input",function(t){l.currentInput&&l.currentInput.appendString(t.detail)}),this.el.addEventListener("backspace",function(t){l.currentInput&&l.currentInput.deleteLast()}),this.el.addEventListener("dismiss",function(t){l.currentInput&&l.currentInput.blur()}),document.body.addEventListener("didfocusinput",function(t){l.currentInput&&l.currentInput.blur(!0),l.currentInput=t.detail,l.el.isOpen||r.openKeyboard(l.el)}),document.body.addEventListener("didblurinput",function(t){l.currentInput=null,r.dismissKeyboard(l.el)})},update:function(){this.data.isOpen?r.showKeyboard(this.el):r.hideKeyboard(this.el)},tick:function(){},remove:function(){},pause:function(){},play:function(){}}),AFRAME.registerPrimitive("a-keyboard",{defaultComponents:{keyboard:{}},mappings:{"is-open":"keyboard.isOpen"}})},function(t,e){"use strict";var i={};i.preloadAssets=function(t){var e=document.querySelector("a-assets"),i=void 0;if(!e){var a=document.querySelector("a-scene");e=document.createElement("a-assets"),a.appendChild(e)}var o=!0,n=!1,r=void 0;try{for(var s,l=t[Symbol.iterator]();!(o=(s=l.next()).done);o=!0){var u=s.value;i=!1;var d=!0,c=!1,h=void 0;try{for(var p,b=e.children[Symbol.iterator]();!(d=(p=b.next()).done);d=!0){var f=p.value;u.id===f.id&&(i=!0)}}catch(t){c=!0,h=t}finally{try{!d&&b.return&&b.return()}finally{if(c)throw h}}if(!i){var y=document.createElement(u.type);y.setAttribute("id",u.id),y.setAttribute("src",u.src),e.appendChild(y)}}}catch(t){n=!0,r=t}finally{try{!o&&l.return&&l.return()}finally{if(n)throw r}}},i.extend=function(t,e){for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t},i.clone=function(t){if(Array.isArray(t))return t.slice(0);var e=Object.create(Object.getPrototypeOf(t)),i=void 0,a=Object.getOwnPropertyNames(t);for(i=0;i"},{type:"text",value:"^"},{type:"text",value:"|"},{type:"text",value:"!"},{type:"text",value:"?"},{type:"symbol",value:"ABC"},{type:"text",value:"@"},{type:"spacebar",value:""},{type:"text",value:","},{type:"text",value:"."}],actions:[{type:"backspace",value:"Del"},{type:"enter",value:"OK"},{type:"dismiss",value:"W"}]};t.exports=i},function(t,e){"use strict";var i={KEYBOARD_COLOR:"#263238",KEY_COLOR_HIGHLIGHT:"#29363c",KEY_COLOR_ACTIVE:"#404b50",SPACEBAR_COLOR_ACTIVE:"#3c464b",SPACEBAR_COLOR_HIGHLIGHT:"#445055",KEY_WIDTH:.08,SPACE_KEY_WIDTH:.368,SPACE_KEY_HEIGHT:.05,ACTION_WIDTH:.14};t.exports=i},function(t,e,i){"use strict";var a=i(10),o=i(6),n=i(4),r=i(12),s={};s.el=null,s.showKeyboard=function(t){t.o_position&&t.setAttribute("position",t.o_position),t.isOpen=!0;var e=!0,i=!1,a=void 0;try{for(var o,n=t.querySelectorAll("[data-ui]")[Symbol.iterator]();!(e=(o=n.next()).done);e=!0){var r=o.value,s=!0,l=!1,u=void 0;try{for(var d,c=r.children[Symbol.iterator]();!(s=(d=c.next()).done);s=!0){var h=d.value;h.setAttribute("show",!0)}}catch(t){l=!0,u=t}finally{try{!s&&c.return&&c.return()}finally{if(l)throw u}}}}catch(t){i=!0,a=t}finally{try{!e&&n.return&&n.return()}finally{if(i)throw a}}var p=t.parentNode;p||t.sceneEl.appendChild(t)},s.hideKeyboard=function(t){var e=t.getAttribute("position");e.x!==-1e4&&(t.o_position=e),t.isOpen=!1,t.setAttribute("position","-10000 -10000 -10000"),t.setAttribute("fadeout",{duration:1})},s.destroyKeyboard=function(t){var e=t.parentNode;e&&e.removeChild(t)},s.openKeyboard=function(t){t.o_position&&t.setAttribute("position",t.o_position),t.isOpen=!0,t._transitioning=!0;var e=t.parentNode;e||t.sceneEl.appendChild(t);var i=!0,a=!1,o=void 0;try{for(var r,l=function(){function e(){i.children[0].removeEventListener("animationend",e),setTimeout(function(){i.children[1].setAttribute("fadein",{duration:160}),n.emit(s.el,"didopen"),t._transitioning=!1},10)}var i=r.value,a=!0,o=!1,l=void 0;try{for(var u,d=i.children[Symbol.iterator]();!(a=(u=d.next()).done);a=!0){var c=u.value;c.setAttribute("hide",!0)}}catch(t){o=!0,l=t}finally{try{!a&&d.return&&d.return()}finally{if(o)throw l}}i.children[0].setAttribute("fadein",{duration:160}),i.children[0].addEventListener("animationend",e)},u=t.querySelectorAll("[data-ui]")[Symbol.iterator]();!(i=(r=u.next()).done);i=!0)l()}catch(t){a=!0,o=t}finally{try{!i&&u.return&&u.return()}finally{if(a)throw o}}},s.dismissKeyboard=function(t){t._transitioning=!0;var e=!0,i=!1,a=void 0;try{for(var o,r=function(){function e(){i.children[1].removeEventListener("animationend",e),setTimeout(function(){function e(){i.children[0].removeEventListener("animationend",e),s.hideKeyboard(t),n.emit(s.el,"diddismiss"),t._transitioning=!1}i.children[0].setAttribute("fadeout",{duration:160}),i.children[0].addEventListener("animationend",e)},10)}var i=o.value,a=!0,r=!1,l=void 0;try{for(var u,d=i.children[Symbol.iterator]();!(a=(u=d.next()).done);a=!0){var c=u.value;c.setAttribute("show",!0)}}catch(t){r=!0,l=t}finally{try{!a&&d.return&&d.return()}finally{if(r)throw l}}t.isOpen=!1,i.children[1].setAttribute("fadeout",{duration:160}),i.children[1].addEventListener("animationend",e)},l=t.querySelectorAll("[data-ui]")[Symbol.iterator]();!(e=(o=l.next()).done);e=!0)r()}catch(t){i=!0,a=t}finally{try{!e&&l.return&&l.return()}finally{if(i)throw a}}},s.addKeyEvents=function(t){t.addEventListener("click",s.keyClick),t.addEventListener("mousedown",s.keyDown),t.addEventListener("mouseup",s.keyOut),t.addEventListener("mouseleave",s.keyOut),t.addEventListener("mouseenter",s.keyIn)},s.keyClick=function(){r.keyDown(s.el);var t=this.getAttribute("key-type"),e=this.getAttribute("key-value");"text"===t||"spacebar"===t?("spacebar"===t&&(e=" "),s.isShiftEnabled?(e=e.toUpperCase(),s.shiftToggle()):s.isSymbols&&s.symbolsToggle(),n.emit(s.el,"input",e)):"shift"===t?s.shiftToggle():"symbol"===t?s.symbolsToggle():"backspace"===t?n.emit(s.el,"backspace"):"enter"===t?n.emit(s.el,"input","\n"):"dismiss"===t&&n.emit(s.el,"dismiss")},s.keyDown=function(){s.el._transitioning||(this.object3D.position.z=.003,"spacebar"===this.getAttribute("key-type")?this.setAttribute("color",a.SPACEBAR_COLOR_ACTIVE):this.setAttribute("color",a.KEY_COLOR_ACTIVE))},s.keyIn=function(){s.el._transitioning||this.object3D.children[2]&&this.object3D.children[2].material&&!this.object3D.children[2].material.opacity||(r.keyIn(s.el),"spacebar"===this.getAttribute("key-type")?this.setAttribute("color",a.SPACEBAR_COLOR_HIGHLIGHT):this.setAttribute("color",a.KEY_COLOR_HIGHLIGHT))},s.keyOut=function(){this.object3D.position.z=0,"spacebar"===this.getAttribute("key-type")?this.setAttribute("color",a.KEY_COLOR_ACTIVE):this.setAttribute("color",a.KEYBOARD_COLOR)},s.isShiftEnabled=!1,s.shiftToggle=function(){s.isShiftEnabled=!s.isShiftEnabled;var t=s.el.shiftKey.querySelector("[data-type]");s.isShiftEnabled?t.setAttribute("src","#aframeKeyboardShiftActive"):t.setAttribute("src","#aframeKeyboardShift");var e=!0,i=!1,a=void 0;try{for(var o,n=document.querySelectorAll("[key-id]")[Symbol.iterator]();!(e=(o=n.next()).done);e=!0){var r=o.value,l=r.getAttribute("key-id"),u=r.getAttribute("key-type");if(l.startsWith("main-")&&"text"===u){var d=r.querySelector("a-text");if(d){var c=d.getAttribute("value").toLowerCase();this.isShiftEnabled&&(c=c.toUpperCase()),d.setAttribute("value",c)}}}}catch(t){i=!0,a=t}finally{try{!e&&n.return&&n.return()}finally{if(i)throw a}}},s.isSymbols=!1,s.symbolsToggle=function(){if(s.isSymbols=!s.isSymbols,s.isSymbols){var t=s.el.alphabeticalLayout.parentNode;t.removeChild(s.el.alphabeticalLayout),t.appendChild(s.el.symbolsLayout)}else{var e=s.el.symbolsLayout.parentNode;e.removeChild(s.el.symbolsLayout),e.appendChild(s.el.alphabeticalLayout),setTimeout(function(){o.updateOpacity(s.el.alphabeticalLayout,1)},0)}},t.exports=s},function(t,e){"use strict";var i={init:function(t){var e=document.createElement("a-sound");e.setAttribute("key","aframeKeyboardKeyInSound"),e.setAttribute("sfx",!0),e.setAttribute("src","#aframeKeyboardKeyIn"),e.setAttribute("position","0 2 5"),t.appendChild(e),e=document.createElement("a-sound"),e.setAttribute("key","aframeKeyboardKeyDownSound"),e.setAttribute("sfx",!0),e.setAttribute("src","#aframeKeyboardKeyDown"),e.setAttribute("position","0 2 5"),t.appendChild(e)},keyIn:function(t){var e=t.querySelector("[key=aframeKeyboardKeyInSound]");e&&(e.components.sound.stopSound(),e.components.sound.playSound())},keyDown:function(t){var e=t.querySelector("[key=aframeKeyboardKeyDownSound]");e&&(e.components.sound.stopSound(),e.components.sound.playSound())}};t.exports=i},function(t,e,i){"use strict";var a=i(6),o=i(4);AFRAME.registerComponent("input",{schema:{value:{type:"string",default:""},name:{type:"string",default:""},disabled:{type:"boolean",default:!1},color:{type:"color",default:"#000"},align:{type:"string",default:"left"},font:{type:"string",default:""},letterSpacing:{type:"int",default:0},lineHeight:{type:"string",default:""},opacity:{type:"number",default:1},side:{type:"string",default:"front"},tabSize:{type:"int",default:4},placeholder:{type:"string",default:""},placeholderColor:{type:"color",default:"#AAA"},maxLength:{type:"int",default:0},type:{type:"string",default:"text"},width:{type:"number",default:1},cursorWidth:{type:"number",default:.01},cursorHeight:{type:"number",default:.08},cursorColor:{type:"color",default:"#007AFF"},backgroundColor:{type:"color",default:"#FFF"},backgroundOpacity:{type:"number",default:1}},init:function(){var t=this;this.background=document.createElement("a-rounded"),this.background.setAttribute("radius",.01),this.background.setAttribute("height",.18),this.background.setAttribute("side","double"),this.el.appendChild(this.background),this.cursor=document.createElement("a-plane"),this.cursor.setAttribute("position","0 0 0.003"),this.cursor.setAttribute("visible",!1),this.el.appendChild(this.cursor),this.text=document.createElement("a-entity"),this.el.appendChild(this.text),this.placeholder=document.createElement("a-entity"),this.placeholder.setAttribute("visible",!1),this.el.appendChild(this.placeholder),this.el.focus=this.focus.bind(this),this.el.blur=this.blur.bind(this),this.el.appendString=this.appendString.bind(this),this.el.deleteLast=this.deleteLast.bind(this),this.blink(),this.el.addEventListener("click",function(){this.components.input.data.disabled||t.focus()}),Object.defineProperty(this.el,"value",{get:function(){return this.getAttribute("value")},set:function(t){this.setAttribute("value",t)},enumerable:!0,configurable:!0})},blink:function(){var t=this;return this.isFocused?void(this.cursorInterval=setInterval(function(){t.cursor.setAttribute("visible",!t.cursor.getAttribute("visible"))},500)):(t.cursor.setAttribute("visible",!1),clearInterval(this.cursorInterval),void(this.cursorInterval=null))},isFocused:!1,focus:function(t){this.isFocused||(this.isFocused=!0,this.cursor.setAttribute("visible",!0),this.blink(),o.emit(this.el,"focus"),t||o.emit(document.body,"didfocusinput",this.el))},blur:function(t){this.isFocused&&(this.isFocused=!1,this.cursorInterval&&(clearInterval(this.cursorInterval),this.cursorInterval=null),this.cursor.setAttribute("visible",!1),o.emit(this.el,"blur"),t||o.emit(document.body,"didblurinput",this.el))},appendString:function(t){if("\n"===t)return this.blur();var e=this.el.getAttribute("value");e||(e=""),e+=t,this.el.setAttribute("value",e),o.emit(this.el,"change",e)},deleteLast:function(){var t=this.el.getAttribute("value");t||(t=""),t=t.slice(0,-1),this.el.setAttribute("value",t),o.emit(this.el,"change",t)},updateText:function(){function t(o,n,r,s){if(!o.object3D||!o.object3D.children||!o.object3D.children[0])return 0;var l=o.object3D.children[0].geometry.visibleGlyphs;if(!l)return 0;if(l=l[l.length-1],!l)return 0;if(l.line)return r?n.value=n.value.substr(1):n.value=n.value.slice(0,-1),o.setAttribute("text",n),t(o,n,r);s||(s=a.getWidthFactor(o,n.wrapCount)),l=(l.position[0]+l.data.width)/(s/e.data.width);var u=(l+i.left+i.right)/e.data.width;return u>1?(r?n.value=n.value.substr(1):n.value=n.value.slice(0,-1),o.setAttribute("text",n),t(o,n,r,s)):l}var e=this,i={left:.021,right:.021},o={color:this.data.color,align:this.data.align,side:this.data.side,tabSize:this.data.tabSize,wrapCount:24*this.data.width,width:this.data.width},n=this.text.getAttribute("text");n&&this.data.value!==n.value&&(this.cursorInterval&&(clearInterval(this.cursorInterval),this.cursorInterval=null),this.cursorTimer&&(clearTimeout(this.cursorTimer),this.cursorTimer=null),this.cursor.setAttribute("visible",!0),this.cursorTimer=setTimeout(function(){e.blink()},50)),this.data.maxLength?(o.value=this.data.value.substring(0,this.data.maxLength),this.el.setAttribute("value",o.value)):o.value=this.data.value,"password"===this.data.type&&(o.value="*".repeat(this.data.value.length)),this.data.font.length&&(o.font=this.data.font),this.data.letterSpacing&&(o.letterSpacing=this.data.letterSpacing),this.data.lineHeight.length&&(o.lineHeight=this.data.lineHeight),this.text.setAttribute("visible",!1),this.text.setAttribute("text",o),o.value.length?this.placeholder.setAttribute("visible",!1):this.placeholder.setAttribute("visible",!0);var r=a.clone(o);r.value=this.data.placeholder,r.color=this.data.placeholderColor,this.placeholder.setAttribute("text",r),setTimeout(function(){if(e.text.object3D){var a=e.text.object3D.children;if(a[0]&&a[0].geometry&&a[0].geometry.visibleGlyphs){var n=0;a[0].geometry.visibleGlyphs.length&&(n=t(e.text,o,!0),e.text.setAttribute("visible",!0)),e.cursor.setAttribute("position",n+i.left+" 0 0.003")}else e.cursor.setAttribute("position",i.left+" 0 0.003")}else e.cursor.setAttribute("position",i.left+" 0 0.003");t(e.placeholder,r)},0),this.background.setAttribute("color",this.data.backgroundColor),this.background.setAttribute("width",this.data.width),this.background.setAttribute("position","0 -0.09 0.001"),this.text.setAttribute("position",i.left-.001+this.data.width/2+" 0 0.002"),this.placeholder.setAttribute("position",i.left-.001+this.data.width/2+" 0 0.002")},updateCursor:function(){this.cursor.setAttribute("width",this.data.cursorWidth),this.cursor.setAttribute("height",this.data.cursorHeight),this.cursor.setAttribute("color",this.data.cursorColor)},update:function(){setTimeout(function(){},0),this.updateCursor(),this.updateText()},tick:function(){},remove:function(){},pause:function(){},play:function(){}}),AFRAME.registerPrimitive("a-input",{defaultComponents:{input:{}},mappings:{value:"input.value",name:"input.name",disabled:"input.disabled",color:"input.color",align:"input.align",font:"input.font","letter-spacing":"input.letterSpacing","line-height":"input.lineHeight",opacity:"input.opacity",side:"input.side","tab-size":"input.tabSize",placeholder:"input.placeholder","placeholder-color":"input.placeholderColor","max-length":"input.maxLength",type:"input.type",width:"input.width","cursor-width":"input.cursorWidth","cursor-height":"input.cursorHeight","cursor-color":"input.cursorColor","background-color":"input.backgroundColor","background-opacity":"input.backgroundOpacity"}})},function(t,e,i){"use strict";var a=i(6),o=i(4),n=i(15),r=i(16);AFRAME.registerComponent("switch",{schema:{name:{type:"string",default:""},enabled:{type:"boolean",default:!1},disabled:{type:"boolean",default:!1},fillColor:{type:"color",default:"#bababa"},knobColor:{type:"color",default:"#f5f5f5"},fillColorEnabled:{type:"color",default:"#80a8ff"},knobColorEnabled:{type:"color",default:"#4076fd"},fillColorDisabled:{type:"color",default:"#939393"},knobColorDisabled:{type:"color",default:"#a2a2a2"}},init:function(){a.preloadAssets(n),r.init(this.el),this.el.fill=document.createElement("a-rounded"),this.el.fill.setAttribute("width",.36),this.el.fill.setAttribute("height",.16),this.el.fill.setAttribute("radius",.08),this.el.fill.setAttribute("side","double"),this.el.fill.setAttribute("position","0 0 0.01"),this.el.appendChild(this.el.fill),this.el.knob=document.createElement("a-circle"),this.el.knob.setAttribute("position","0.06 0.08 0.02"),this.el.knob.setAttribute("radius",.12),this.el.knob.setAttribute("side","double"),this.el.appendChild(this.el.knob),this.el.shadow_el=document.createElement("a-image"),this.el.shadow_el.setAttribute("width",.3),this.el.shadow_el.setAttribute("height",.3),this.el.shadow_el.setAttribute("position","0 0 -0.001"),this.el.shadow_el.setAttribute("src","#aframeSwitchShadow"),this.el.knob.appendChild(this.el.shadow_el),this.el.addEventListener("click",function(){this.components.switch.data.disabled||(this.setAttribute("enabled",!this.components.switch.data.enabled),o.emit(this,"change",this.components.switch.data.enabled))}),this.el.addEventListener("mousedown",function(){return this.components.switch.data.disabled?r.clickDisabled(this):void r.click(this)}),Object.defineProperty(this.el,"enabled",{get:function(){return this.getAttribute("enabled")},set:function(t){this.setAttribute("enabled",t)},enumerable:!0,configurable:!0})},on:function(){this.el.fill.setAttribute("color",this.data.fillColorEnabled),this.el.knob.setAttribute("position","0.32 0.08 0.02"), 2 | this.el.knob.setAttribute("color",this.data.knobColorEnabled)},off:function(){this.el.fill.setAttribute("color",this.data.fillColor),this.el.knob.setAttribute("position","0.06 0.08 0.02"),this.el.knob.setAttribute("color",this.data.knobColor)},disable:function(){this.el.fill.setAttribute("color",this.data.fillColorDisabled),this.el.knob.setAttribute("color",this.data.knobColorDisabled)},update:function(){this.data.enabled?this.on():this.off(),this.data.disabled&&this.disable()},tick:function(){},remove:function(){},pause:function(){},play:function(){}}),AFRAME.registerPrimitive("a-switch",{defaultComponents:{switch:{}},mappings:{name:"switch.name",enabled:"switch.enabled",disabled:"switch.disabled","fill-color":"switch.fillColor","knob-color":"switch.knobColor","fill-color-enabled":"switch.fillColorEnabled","knob-color-enabled":"switch.knobColorEnabled","fill-color-disabled":"switch.fillColorDisabled","knob-color-disabled":"switch.knobColorDisabled"}})},function(t,e){"use strict";t.exports=[{type:"img",id:"aframeSwitchShadow",src:AFRAME.ASSETS_PATH+"/images/SwitchShadow.png"},{type:"audio",id:"aframeSwitchClick",src:AFRAME.ASSETS_PATH+"/sounds/InputClick.mp3"},{type:"audio",id:"aframeSwitchClickDisabled",src:AFRAME.ASSETS_PATH+"/sounds/ButtonClickDisabled.mp3"}]},function(t,e){"use strict";var i={init:function(t){var e=document.createElement("a-sound");e.setAttribute("key","aframeSwitchClickSound"),e.setAttribute("sfx",!0),e.setAttribute("src","#aframeSwitchClick"),e.setAttribute("position","0 2 5"),t.appendChild(e),e=document.createElement("a-sound"),e.setAttribute("key","aframeSwitchClickDisabledSound"),e.setAttribute("sfx",!0),e.setAttribute("src","#aframeSwitchClickDisabled"),e.setAttribute("position","0 2 5"),t.appendChild(e)},click:function(t){var e=t.querySelector("[key=aframeSwitchClickSound]");e&&(e.components.sound.stopSound(),e.components.sound.playSound())},clickDisabled:function(t){var e=t.querySelector("[key=aframeSwitchClickDisabledSound]");e&&(e.components.sound.stopSound(),e.components.sound.playSound())}};t.exports=i},function(t,e,i){"use strict";i(6),i(4);AFRAME.registerComponent("form",{schema:{},init:function(){},update:function(){},tick:function(){},remove:function(){},pause:function(){},play:function(){}}),AFRAME.registerPrimitive("a-form",{defaultComponents:{form:{}},mappings:{}})},function(t,e,i){"use strict";var a=i(6),o=i(4),n=i(19),r=i(20);AFRAME.registerComponent("radio",{schema:{checked:{type:"boolean",default:!1},disabled:{type:"boolean",default:!1},name:{type:"string",default:""},value:{type:"string",default:""},label:{type:"string",default:""},radioColor:{type:"color",default:"#757575"},radioColorChecked:{type:"color",default:"#4076fd"},color:{type:"color",default:"#757575"},font:{type:"string",default:""},letterSpacing:{type:"int",default:0},lineHeight:{type:"string",default:""},opacity:{type:"number",default:1},width:{type:"number",default:1}},init:function(){var t=this;a.preloadAssets(n),r.init(this.el),this.hitbox=document.createElement("a-plane"),this.hitbox.setAttribute("height",.2),this.hitbox.setAttribute("opacity",0),this.hitbox.setAttribute("position","0 0 0.001"),this.el.appendChild(this.hitbox),this.outline=document.createElement("a-ring"),this.outline.setAttribute("radius-outer",.1),this.outline.setAttribute("radius-inner",.078),this.outline.setAttribute("position","0.1 0 0.002"),this.el.appendChild(this.outline),this.circle=document.createElement("a-circle"),this.circle.setAttribute("radius",.05),this.circle.setAttribute("position","0.1 0 0.002"),this.el.appendChild(this.circle),this.label=document.createElement("a-entity"),this.el.appendChild(this.label),this.el.addEventListener("click",function(){this.components.radio.data.disabled||(this.setAttribute("checked",!0),t.onClick())}),this.el.addEventListener("mousedown",function(){return this.components.radio.data.disabled?r.clickDisabled(this):void r.click(this)}),Object.defineProperty(this.el,"value",{get:function(){return this.getAttribute("value")},set:function(t){this.setAttribute("value",t)},enumerable:!0,configurable:!0})},onClick:function(t){if(this.data.name){var e=this.el.closest("a-form");if(e){var i=!1,a=Array.from(e.querySelectorAll("[name="+this.data.name+"]"));a.reverse();var n=!0,r=!1,s=void 0;try{for(var l,u=a[Symbol.iterator]();!(n=(l=u.next()).done);n=!0){var d=l.value;d.components.radio&&(d===this.el&&d.hasAttribute("checked")?(i=!0,d.components.radio.check(),t||o.emit(d,"change",!0)):i||this.data.checked||!d.hasAttribute("checked")?d.components.radio.uncheck():(i=!0,d.components.radio.check()))}}catch(t){r=!0,s=t}finally{try{!n&&u.return&&u.return()}finally{if(r)throw s}}!i&&this.el.hasAttribute("checked")&&(this.check(),t||o.emit(this.el,"change",!0))}}},check:function(){this.outline.setAttribute("color",this.data.radioColorChecked),this.circle.setAttribute("color",this.data.radioColorChecked),this.circle.setAttribute("visible",!0),this.data.disabled&&this.disabled()},uncheck:function(){this.outline.setAttribute("color",this.data.radioColor),this.circle.setAttribute("visible",!1),this.data.disabled&&this.disabled()},disabled:function(){this.outline.setAttribute("color",this.data.radioColor),this.circle.setAttribute("color",this.data.radioColor)},update:function(){function t(o,n){if(!o.object3D||!o.object3D.children||!o.object3D.children[0])return 0;var r=o.object3D.children[0].geometry.visibleGlyphs;if(!r)return 0;if(r=r[r.length-1],!r)return 0;if(r.line)return i.value=i.value.slice(0,-1),o.setAttribute("text",i),t(o);n||(n=a.getWidthFactor(o,i.wrapCount)),r=(r.position[0]+r.data.width)/(n/e.data.width);var s=r/e.data.width;return s>1?(i.value=i.value.slice(0,-1),o.setAttribute("text",i),t(o,n)):r}var e=this;this.onClick(!0),this.hitbox.setAttribute("width",this.data.width),this.hitbox.setAttribute("position",this.data.width/2+" 0 0.001");var i={color:this.data.color,align:"left",wrapCount:10*(this.data.width+.2),width:this.data.width};this.data.font&&(i.font=this.data.font),i.value=this.data.label,i.color=this.data.color,this.label.setAttribute("text",i),this.label.setAttribute("position",this.data.width/2+.24+" 0 0.002"),setTimeout(function(){if(e.data.label.length&&t(e.label),e.data.disabled)var i=setInterval(function(){e.outline.object3D.children[0]&&(clearInterval(i),a.updateOpacity(e.outline,.4),a.updateOpacity(e.circle,.4),a.updateOpacity(e.label,.4))},10);else var o=setInterval(function(){e.outline.object3D.children[0]&&(clearInterval(o),a.updateOpacity(e.outline,1),a.updateOpacity(e.circle,1),a.updateOpacity(e.label,1))},10)},0)},tick:function(){},remove:function(){},pause:function(){},play:function(){}}),AFRAME.registerPrimitive("a-radio",{defaultComponents:{radio:{}},mappings:{checked:"radio.checked",disabled:"radio.disabled",name:"radio.name",value:"radio.value",label:"radio.label","radio-color":"radio.radioColor","radio-color-checked":"radio.radioColorChecked",color:"radio.color",align:"radio.align",font:"radio.font","letter-spacing":"radio.letterSpacing","line-height":"radio.lineHeight",opacity:"radio.opacity",width:"radio.width"}})},function(t,e){"use strict";t.exports=[{type:"audio",id:"aframeRadioClick",src:AFRAME.ASSETS_PATH+"/sounds/InputClick.mp3"},{type:"audio",id:"aframeRadioClickDisabled",src:AFRAME.ASSETS_PATH+"/sounds/ButtonClickDisabled.mp3"}]},function(t,e){"use strict";var i={init:function(t){var e=document.createElement("a-sound");e.setAttribute("key","aframeRadioClickSound"),e.setAttribute("sfx",!0),e.setAttribute("src","#aframeRadioClick"),e.setAttribute("position","0 2 5"),t.appendChild(e),e=document.createElement("a-sound"),e.setAttribute("key","aframeRadioClickDisabledSound"),e.setAttribute("sfx",!0),e.setAttribute("src","#aframeRadioClickDisabled"),e.setAttribute("position","0 2 5"),t.appendChild(e)},click:function(t){var e=t.querySelector("[key=aframeRadioClickSound]");e&&(e.components.sound.stopSound(),e.components.sound.playSound())},clickDisabled:function(t){var e=t.querySelector("[key=aframeRadioClickDisabledSound]");e&&(e.components.sound.stopSound(),e.components.sound.playSound())}};t.exports=i},function(t,e,i){"use strict";var a=i(6),o=i(4),n=i(22),r=i(23);AFRAME.registerComponent("checkbox",{schema:{checked:{type:"boolean",default:!1},disabled:{type:"boolean",default:!1},name:{type:"string",default:""},value:{type:"string",default:""},label:{type:"string",default:""},checkboxColor:{type:"color",default:"#757575"},checkboxColorChecked:{type:"color",default:"#4076fd"},color:{type:"color",default:"#757575"},font:{type:"string",default:""},letterSpacing:{type:"int",default:0},lineHeight:{type:"string",default:""},opacity:{type:"number",default:1},width:{type:"number",default:1}},init:function(){var t=this;a.preloadAssets(n),r.init(this.el),this.hitbox=document.createElement("a-plane"),this.hitbox.setAttribute("height",.2),this.hitbox.setAttribute("opacity",0),this.el.appendChild(this.hitbox),this.outline=document.createElement("a-rounded"),this.outline.setAttribute("width",.2),this.outline.setAttribute("height",.2),this.outline.setAttribute("radius",.02),this.outline.setAttribute("position","0 -0.1 0.01"),this.el.appendChild(this.outline),this.inside=document.createElement("a-rounded"),this.inside.setAttribute("width",.156),this.inside.setAttribute("height",.156),this.inside.setAttribute("radius",.01),this.inside.setAttribute("color","#EEE"),this.inside.setAttribute("position","0.0195 -0.078 0.02"),this.el.appendChild(this.inside),this.checkmark=document.createElement("a-image"),this.checkmark.setAttribute("width",.16),this.checkmark.setAttribute("height",.16),this.checkmark.setAttribute("src","#aframeCheckboxMark"),this.checkmark.setAttribute("position","0.1 0 0.03"),this.el.appendChild(this.checkmark),this.label=document.createElement("a-entity"),this.el.appendChild(this.label),this.el.addEventListener("click",function(){this.components.checkbox.data.disabled||(this.setAttribute("checked",!this.components.checkbox.data.checked),t.onClick())}),this.el.addEventListener("mousedown",function(){return this.components.checkbox.data.disabled?r.clickDisabled(this):void r.click(this)}),Object.defineProperty(this.el,"value",{get:function(){return this.getAttribute("value")},set:function(t){this.setAttribute("value",t)},enumerable:!0,configurable:!0})},onClick:function(t){this.data.checked?this.check():this.uncheck(),t||o.emit(this.el,"change",this.data.checked)},check:function(){this.outline.setAttribute("color",this.data.checkboxColorChecked),this.inside.setAttribute("color",this.data.checkboxColorChecked),this.checkmark.setAttribute("visible",!0),this.data.disabled&&this.disabled()},uncheck:function(){this.outline.setAttribute("color",this.data.checkboxColor),this.inside.setAttribute("color","#EEE"),this.checkmark.setAttribute("visible",!1),this.data.disabled&&this.disabled()},disabled:function(){this.outline.setAttribute("color",this.data.checkboxColor),this.inside.setAttribute("color",this.data.checkboxColor)},update:function(){function t(o,n){if(!o.object3D||!o.object3D.children||!o.object3D.children[0])return 0;var r=o.object3D.children[0].geometry.visibleGlyphs;if(!r)return 0;if(r=r[r.length-1],!r)return 0;if(r.line)return i.value=i.value.slice(0,-1),o.setAttribute("text",i),t(o);n||(n=a.getWidthFactor(o,i.wrapCount)),r=(r.position[0]+r.data.width)/(n/e.data.width);var s=r/e.data.width;return s>1?(i.value=i.value.slice(0,-1),o.setAttribute("text",i),t(o,n)):r}var e=this;this.onClick(!0),this.hitbox.setAttribute("width",this.data.width),this.hitbox.setAttribute("position",this.data.width/2+" 0 0.01");var i={color:this.data.color,align:"left",wrapCount:10*(this.data.width+.2),width:this.data.width};this.data.font&&(i.font=this.data.font),i.value=this.data.label,i.color=this.data.color,this.label.setAttribute("text",i),this.label.setAttribute("position",this.data.width/2+.24+" 0 0.01"),setTimeout(function(){if(e.data.label.length&&t(e.label),e.data.disabled)var i=setInterval(function(){e.checkmark.object3D.children[0]&&(clearInterval(i),a.updateOpacity(e.checkmark,.4),a.updateOpacity(e.label,.4))},10);else var o=setInterval(function(){e.checkmark.object3D.children[0]&&(clearInterval(o),a.updateOpacity(e.checkmark,1),a.updateOpacity(e.label,1))},10)},0)},tick:function(){},remove:function(){},pause:function(){},play:function(){}}),AFRAME.registerPrimitive("a-checkbox",{defaultComponents:{checkbox:{}},mappings:{checked:"checkbox.checked",disabled:"checkbox.disabled",name:"checkbox.name",value:"checkbox.value",label:"checkbox.label","checkbox-color":"checkbox.checkboxColor","checkbox-color-checked":"checkbox.checkboxColorChecked",color:"checkbox.color",align:"checkbox.align",font:"checkbox.font","letter-spacing":"checkbox.letterSpacing","line-height":"checkbox.lineHeight",opacity:"checkbox.opacity",width:"checkbox.width"}})},function(t,e){"use strict";t.exports=[{type:"img",id:"aframeCheckboxMark",src:AFRAME.ASSETS_PATH+"/images/CheckmarkIcon.png"},{type:"audio",id:"aframeCheckboxClick",src:AFRAME.ASSETS_PATH+"/sounds/InputClick.mp3"},{type:"audio",id:"aframeCheckboxClickDisabled",src:AFRAME.ASSETS_PATH+"/sounds/ButtonClickDisabled.mp3"}]},function(t,e){"use strict";var i={init:function(t){var e=document.createElement("a-sound");e.setAttribute("key","aframeCheckboxClickSound"),e.setAttribute("sfx",!0),e.setAttribute("src","#aframeCheckboxClick"),e.setAttribute("position","0 2 5"),t.appendChild(e),e=document.createElement("a-sound"),e.setAttribute("key","aframeButtonClickDisabledSound"),e.setAttribute("sfx",!0),e.setAttribute("src","#aframeButtonClickDisabled"),e.setAttribute("position","0 2 5"),t.appendChild(e)},click:function(t){var e=t.querySelector("[key=aframeCheckboxClickSound]");e&&(e.components.sound.stopSound(),e.components.sound.playSound())},clickDisabled:function(t){var e=t.querySelector("[key=aframeButtonClickDisabledSound]");e&&(e.components.sound.stopSound(),e.components.sound.playSound())}};t.exports=i},function(t,e,i){"use strict";var a=i(6),o=i(4),n=i(25),r=i(26);AFRAME.registerComponent("button",{schema:{disabled:{type:"boolean",default:!1},type:{type:"string",default:"raised"},name:{type:"string",default:""},value:{type:"string",default:"Button"},buttonColor:{type:"color",default:"#4076fd"},color:{type:"color",default:"#FFF"},font:{type:"string",default:""},letterSpacing:{type:"int",default:0},lineHeight:{type:"string",default:""},opacity:{type:"number",default:1},width:{type:"number",default:1}},init:function(){var t=this;a.preloadAssets(n),r.init(this.el),this.wrapper=document.createElement("a-entity"),this.wrapper.setAttribute("position","0 0 0.01"),this.el.appendChild(this.wrapper),this.shadow=document.createElement("a-image"),this.shadow.setAttribute("height",.36*1.25),this.shadow.setAttribute("src","#aframeButtonShadow"),this.wrapper.appendChild(this.shadow),this.outline=document.createElement("a-rounded"),this.outline.setAttribute("height",.36),this.outline.setAttribute("radius",.03),this.outline.setAttribute("position","0 -0.18 0.01"),this.wrapper.appendChild(this.outline),this.label=document.createElement("a-entity"),this.outline.appendChild(this.label),this.el.addEventListener("click",function(){this.components.button&&this.components.button.data.disabled||t.onClick()}),this.el.addEventListener("mousedown",function(){return this.components.button&&this.components.button.data.disabled?r.clickDisabled(this):(t.wrapper.setAttribute("position","0 0 0.036"),void r.click(this))}),this.el.addEventListener("mouseup",function(){this.components.button&&this.components.button.data.disabled||t.wrapper.setAttribute("position","0 0 0")}),this.el.getWidth=this.getWidth.bind(this),Object.defineProperty(this.el,"value",{get:function(){return this.getAttribute("value")},set:function(t){this.setAttribute("value",t)},enumerable:!0,configurable:!0})},onClick:function(){},getWidth:function(){return this.__width},update:function(){function t(o,n,r){if(!o.object3D||!o.object3D.children||!o.object3D.children[0])return setTimeout(function(){t(o,n)},10);var s=o.object3D.children[0].geometry.visibleGlyphs;if(!s)return setTimeout(function(){t(o,n)},10);if(s=s[s.length-1],!s)return n(0);if(s.line)return i.value=i.value.slice(0,-1),o.setAttribute("text",i),t(o,n);r||(r=a.getWidthFactor(o,i.wrapCount)),s=(s.position[0]+s.data.width)/(r/e.data.width);var l=s/e.data.width;return l>1?(i.value=i.value.slice(0,-1),o.setAttribute("text",i),t(o,n,r)):n(s)}var e=this;this.outline.setAttribute("color",this.data.buttonColor);var i={color:this.data.color,align:"center",wrapCount:10*this.data.width,width:this.data.width};this.data.font&&(i.font=this.data.font),"flat"===this.data.type&&(i.color=this.data.buttonColor),i.value=this.data.value.toUpperCase(),this.label.setAttribute("text",i),this.label.setAttribute("position",this.data.width/2+.24+" 0 0.01"),setTimeout(function(){if(e.data.value.length&&t(e.label,function(t){e.label.setAttribute("position",t/2+.14+" 0.18 0.02"),t+=.28,e.outline.setAttribute("width",t),e.__width=t,e.shadow.setAttribute("width",1.17*t),e.shadow.setAttribute("position",t/2+" 0 0"),o.emit(e.el,"change:width",t)}),e.data.disabled){e.shadow.setAttribute("visible",!1);var i=setInterval(function(){e.label.object3D.children[0]&&e.label.object3D.children[0].geometry.visibleGlyphs&&(clearInterval(i),a.updateOpacity(e.el,.4))},10)}else var n=setInterval(function(){e.label.object3D.children[0]&&e.label.object3D.children[0].geometry.visibleGlyphs&&(clearInterval(n),a.updateOpacity(e.el,1))},10);if("flat"===e.data.type){e.shadow.setAttribute("visible",!1);var r=setInterval(function(){e.label.object3D.children[0]&&e.label.object3D.children[0].geometry.visibleGlyphs&&(clearInterval(r),a.updateOpacity(e.outline,0),e.data.disabled&&a.updateOpacity(e.label,.4))},10)}},0)},tick:function(){},remove:function(){},pause:function(){},play:function(){}}),AFRAME.registerPrimitive("a-button",{defaultComponents:{button:{}},mappings:{disabled:"button.disabled",type:"button.type",name:"button.name",value:"button.value","button-color":"button.buttonColor",color:"button.color",font:"button.font","letter-spacing":"button.letterSpacing","line-height":"button.lineHeight",opacity:"button.opacity",width:"button.width"}})},function(t,e){"use strict";t.exports=[{type:"img",id:"aframeButtonShadow",src:AFRAME.ASSETS_PATH+"/images/ButtonShadow.png"},{type:"audio",id:"aframeButtonClick",src:AFRAME.ASSETS_PATH+"/sounds/ButtonClick.mp3"},{type:"audio",id:"aframeButtonClickDisabled",src:AFRAME.ASSETS_PATH+"/sounds/ButtonClickDisabled.mp3"}]},function(t,e){"use strict";var i={init:function(t){var e=document.createElement("a-sound");e.setAttribute("key","aframeButtonClickSound"),e.setAttribute("sfx",!0),e.setAttribute("src","#aframeButtonClick"),e.setAttribute("position","0 2 5"),t.appendChild(e),e=document.createElement("a-sound"),e.setAttribute("key","aframeButtonClickDisabledSound"),e.setAttribute("sfx",!0),e.setAttribute("src","#aframeButtonClickDisabled"),e.setAttribute("position","0 2 5"),t.appendChild(e)},click:function(t){var e=t.querySelector("[key=aframeButtonClickSound]");e&&(e.components.sound.stopSound(),e.components.sound.playSound())},clickDisabled:function(t){var e=t.querySelector("[key=aframeButtonClickDisabledSound]");e&&(e.components.sound.stopSound(),e.components.sound.playSound())}};t.exports=i},function(t,e,i){"use strict";var a=i(6),o=i(4),n=i(28),r=i(29);AFRAME.registerComponent("toast",{schema:{message:{type:"string",default:"You are cool"},action:{type:"string",default:""},backgroundColor:{type:"color",default:"#222"},actionColor:{type:"color",default:"#4076fd"},color:{type:"color",default:"#FFF"},font:{type:"string",default:""},letterSpacing:{type:"int",default:0},lineHeight:{type:"string",default:""},width:{type:"number",default:3},duration:{type:"number",default:2e3},autoshow:{type:"boolean",default:!0}},init:function(){function t(t){var i=e.label.getAttribute("text");i.width=e.data.width-t.detail,i.wrapCount=10*i.width,e.label.setAttribute("text",i),e.label.setAttribute("position",i.width/2+.14+" 0.04 0.001"),this.setAttribute("position",e.data.width-t.detail+" "+(.44-.36)/2+" 0.001")}var e=this;a.preloadAssets(n),r.init(this.el),this.el.setAttribute("position","10000 10000 10000"),this.el.setAttribute("rotation","-25 0 0"),this.el.setAttribute("scale","0.3 0.3 0.3"),this.background=document.createElement("a-rounded"),this.background.setAttribute("height",.44),this.background.setAttribute("radius",.03),this.background.setAttribute("position","0 -0.18 0.001"),this.el.appendChild(this.background),this.label=document.createElement("a-entity"),this.el.appendChild(this.label),this.action=document.createElement("a-button"),e.action.setAttribute("button-color","#222"),this.el.appendChild(this.action),this.action.addEventListener("change:width",t),this.action.addEventListener("click",function(){o.emit(e.el,"actionclick")});var i=setInterval(function(){e.action.object3D&&e.action.object3D.children[0]&&(clearInterval(i),a.updateOpacity(e.el,0),a.updateOpacity(e.label,0),a.updateOpacity(e.action,0),e.data.autoshow&&e.show())},10);this.el.show=this.show.bind(this),this.el.hide=this.hide.bind(this)},show:function(){this.hideTimer&&clearTimeout(this.hideTimer),this.el.setAttribute("position",-this.data.width/(2/this.el.object3D.scale.x)+" 0.25 -1.6");var t=this;setTimeout(function(){t.el.setAttribute("fadein",{duration:160}),setTimeout(function(){a.updateOpacity(t.label,1),t.action.components.button.shadow.setAttribute("visible",!1)},10)},0),this.hideTimer=setTimeout(function(){t.hide()},this.data.duration),r.show(this.el)},hide:function(){var t=this;setTimeout(function(){a.updateOpacity(t.label,0),t.action.components.button.shadow.setAttribute("visible",!1),setTimeout(function(){t.el.setAttribute("fadeout",{duration:160}),setTimeout(function(){t.el.setAttribute("position","10000 10000 10000")},200)},10)},0)},update:function(){this.background.setAttribute("color",this.data.backgroundColor),this.background.setAttribute("width",this.data.width);var t={color:this.data.color,align:"left",wrapCount:10*this.data.width,width:this.data.width,lineHeight:64};this.data.font&&(t.font=this.data.font),"flat"===this.data.type&&(t.color=this.data.buttonColor),t.value=this.data.message,this.label.setAttribute("text",t),this.label.setAttribute("position",this.data.width/2+.14+" 0 0.001"),this.action.setAttribute("value",this.data.action.toUpperCase()),this.action.setAttribute("color",this.data.actionColor)},tick:function(){},remove:function(){},pause:function(){},play:function(){}}),AFRAME.registerPrimitive("a-toast",{defaultComponents:{toast:{}},mappings:{message:"toast.message",action:"toast.action","action-color":"toast.actionColor","background-color":"toast.backgroundColor",color:"toast.color",font:"toast.font","letter-spacing":"toast.letterSpacing","line-height":"toast.lineHeight",width:"toast.width",duration:"toast.duration",autoshow:"toast.autoshow"}})},function(t,e){"use strict";t.exports=[{type:"audio",id:"aframeToastShow",src:AFRAME.ASSETS_PATH+"/sounds/ToastShow.mp3"}]},function(t,e){"use strict";var i={init:function(t){var e=document.createElement("a-sound");e.setAttribute("key","aframeToastShowSound"),e.setAttribute("sfx",!0),e.setAttribute("src","#aframeToastShow"),e.setAttribute("position","0 2 5"),t.appendChild(e)},show:function(t){var e=t.querySelector("[key=aframeToastShowSound]");e&&(e.components.sound.stopSound(),e.components.sound.playSound())}};t.exports=i}]); 3 | //# sourceMappingURL=aframe-material.min.js.map -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aframe-material😲😆😛 6 | 7 | 8 | 9 | 10 | 11 | 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 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Aframe-Material", 3 | "main": "dist/aframe-material.min.js", 4 | "description": "Material design (inputs, keyboards, checkbox, radio button) for A-Frame (https://aframe.io).", 5 | "version": "0.1.1", 6 | "license": "MIT", 7 | "scripts": { 8 | "watch": "cross-env webpack --progress --colors --watch", 9 | "build": "cross-env webpack --progress --colors", 10 | "start": "cross-env NODE_ENV=dev webpack-dev-server --progress --colors --hot -d --host 0.0.0.0", 11 | "dist": "npm run dist:max && npm run dist:min", 12 | "dist:max": "cross-env npm run build", 13 | "dist:min": "cross-env NODE_ENV=production npm run build" 14 | }, 15 | "devDependencies": { 16 | "babel-core": "^6.10.4", 17 | "babel-eslint": "^6.1.0", 18 | "babel-jest": "^16.0.0", 19 | "babel-loader": "^6.2.4", 20 | "babel-plugin-transform-class-properties": "^6.10.2", 21 | "babel-preset-es2015": "^6.16.0", 22 | "cross-env": "^2.0.0", 23 | "eslint": "^2.9.x", 24 | "eslint-config-standard": "^5.3.1", 25 | "eslint-plugin-promise": "^1.3.2", 26 | "eslint-plugin-react": "^5.2.2", 27 | "eslint-plugin-standard": "^1.3.2", 28 | "gh-pages": "^0.11.0", 29 | "gm": "^1.23.0", 30 | "jest": "^16.0.1", 31 | "object-assign": "^4.1.0", 32 | "open": "0.0.5", 33 | "shx": "^0.1.2", 34 | "style-loader": "^0.13.1", 35 | "stylelint": "^8.0.0", 36 | "stylelint-config-standard": "^17.0.0", 37 | "stylelint-order": "^0.6.0", 38 | "webpack": "^1.13.1", 39 | "webpack-dev-server": "^1.15.1" 40 | }, 41 | "keywords": [ 42 | "aframe", 43 | "aframe-component", 44 | "webvr", 45 | "vr" 46 | ], 47 | "dependencies": { 48 | "aframe-rounded": "^1.0.3" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/alert/index.js: -------------------------------------------------------------------------------- 1 | const Utils = require('../utils'); 2 | const Event = require('../core/event'); 3 | 4 | AFRAME.registerComponent('alert', { 5 | schema: { 6 | title: { type: 'string', default: "" }, 7 | message: { type: "string", default: "You are awesome!" }, 8 | affirmative: { type: "string", default: "Agree" }, 9 | dismissive: { type: "string", default: "" }, 10 | backgroundColor: { type: "color", default: "#000" }, 11 | fillColor: { type: "color", default: "#FFF" }, 12 | titleColor: { type: "color", default: "#111" }, 13 | messageColor: { type: "color", default: "#777" }, 14 | font: { type: "string", default: "" }, 15 | width: { type: "number", default: 1 } 16 | }, 17 | init: function () { 18 | var that = this; 19 | 20 | // BACKGROUND 21 | this.background = document.createElement('a-sky'); 22 | this.background.setAttribute('radius', 2.2) 23 | this.background.setAttribute('opacity', 0.2) 24 | this.background.setAttribute('position', '0 1 0') 25 | this.el.appendChild(this.background); 26 | 27 | // FILL 28 | this.fill = document.createElement('a-rounded'); 29 | this.fill.setAttribute('width', 1) 30 | this.fill.setAttribute('height', 1) 31 | this.fill.setAttribute('radius', 0.01) 32 | this.fill.setAttribute('side', 'double') 33 | this.fill.setAttribute('position', '-0.5 1 -1.8'); 34 | this.el.appendChild(this.fill); 35 | 36 | // TITLE 37 | this.title = document.createElement('a-entity'); 38 | this.fill.appendChild(this.title); 39 | 40 | // MESSAGE 41 | this.message = document.createElement('a-entity'); 42 | this.fill.appendChild(this.message); 43 | 44 | /* 45 | // Assets 46 | Utils.preloadAssets( Assets ); 47 | 48 | // FILL 49 | this.el.fill = document.createElement('a-rounded'); 50 | this.el.fill.setAttribute('width', 0.36) 51 | this.el.fill.setAttribute('height', 0.16) 52 | this.el.fill.setAttribute('radius', 0.08) 53 | this.el.fill.setAttribute('side', 'double') 54 | this.el.appendChild(this.el.fill); 55 | 56 | // KNOB 57 | this.el.knob = document.createElement('a-circle'); 58 | this.el.knob.setAttribute('position', '0.06 0.08 0.002') 59 | this.el.knob.setAttribute('radius', 0.12) 60 | this.el.knob.setAttribute('side', 'double') 61 | this.el.appendChild(this.el.knob); 62 | 63 | // SHADOW 64 | this.el.shadow_el = document.createElement('a-image'); 65 | this.el.shadow_el.setAttribute('width', 0.24*1.25); 66 | this.el.shadow_el.setAttribute('height', 0.24*1.25); 67 | this.el.shadow_el.setAttribute('position', '0 0 -0.001'); 68 | this.el.shadow_el.setAttribute('src', '#aframeSwitchShadow'); 69 | this.el.knob.appendChild(this.el.shadow_el); 70 | 71 | this.el.addEventListener('click', function() { 72 | that.el.setAttribute('enabled', !that.data.enabled ); 73 | });*/ 74 | }, 75 | update: function () { 76 | this.background.setAttribute('color', this.data.backgroundColor); 77 | this.fill.setAttribute('color', this.data.fillColor); 78 | 79 | let props = { 80 | align: 'left', 81 | wrapCount: 20*(this.data.width+0.04), 82 | width: this.data.width, 83 | baseline: 'top' 84 | } 85 | if (this.data.font) { props.font = this.data.font; } 86 | 87 | // TITLE 88 | props.value = this.data.title; 89 | props.color = this.data.titleColor; 90 | this.title.setAttribute('text', props); 91 | 92 | // MESSAGE 93 | props.value = this.data.message; 94 | props.color = this.data.messageColor; 95 | props.wrapCount = 24*(this.data.width+0.04), 96 | this.message.setAttribute('text', props); 97 | 98 | this.fill.setAttribute('width', this.data.width); 99 | this.fill.setAttribute('position', -this.data.width/2+' 1 -1.4'); 100 | this.title.setAttribute('position', this.data.width/2+0.06+' 0.92 0.001'); 101 | this.message.setAttribute('position', this.data.width/2+' 0 0.001'); 102 | 103 | 104 | /*if (this.data.enabled) { 105 | this.enable(); 106 | } else { 107 | this.disable(); 108 | }*/ 109 | }, 110 | tick: function () {}, 111 | remove: function () {}, 112 | pause: function () {}, 113 | play: function () {} 114 | }); 115 | 116 | AFRAME.registerPrimitive('a-alert', { 117 | defaultComponents: { 118 | alert: {} 119 | }, 120 | mappings: { 121 | title: 'alert.title', 122 | message: 'alert.message', 123 | affirmative: 'alert.affirmative', 124 | dismissive: 'alert.dismissive', 125 | 'background-color': 'alert.backgroundColor', 126 | 'fill-color': 'alert.fillColor', 127 | 'title-color': 'alert.titleColor', 128 | 'message-color': 'alert.messageColor', 129 | font: 'alert.font', 130 | width: 'alert.width' 131 | } 132 | }); 133 | -------------------------------------------------------------------------------- /src/button/assets.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { type: 'img', id: 'aframeButtonShadow', src: `${AFRAME.ASSETS_PATH}/images/ButtonShadow.png`}, 3 | { type: 'audio', id: 'aframeButtonClick', src: `${AFRAME.ASSETS_PATH}/sounds/ButtonClick.mp3`}, 4 | { type: 'audio', id: 'aframeButtonClickDisabled', src: `${AFRAME.ASSETS_PATH}/sounds/ButtonClickDisabled.mp3`} 5 | ] 6 | -------------------------------------------------------------------------------- /src/button/index.js: -------------------------------------------------------------------------------- 1 | const Utils = require('../utils'); 2 | const Event = require('../core/event'); 3 | const Assets = require('./assets'); 4 | const SFX = require('./sfx'); 5 | 6 | AFRAME.registerComponent('button', { 7 | schema: { 8 | disabled: { type: 'boolean', default: false }, 9 | type: { type: "string", default: "raised" }, 10 | name: { type: "string", default: "" }, 11 | value: { type: "string", default: "Button" }, 12 | buttonColor: { type: "color", default: "#4076fd"}, 13 | color: { type: "color", default: "#FFF" }, 14 | font: { type: "string", default: "" }, 15 | letterSpacing: { type: "int", default: 0 }, 16 | lineHeight: { type: "string", default: "" }, 17 | opacity: { type: "number", default: 1 }, 18 | width: { type: "number", default: 1 } 19 | }, 20 | init: function () { 21 | var that = this; 22 | 23 | // Assets 24 | Utils.preloadAssets( Assets ); 25 | 26 | // SFX 27 | SFX.init(this.el); 28 | 29 | this.wrapper = document.createElement('a-entity'); 30 | this.wrapper.setAttribute('position', '0 0 0.01') 31 | this.el.appendChild(this.wrapper); 32 | 33 | this.shadow = document.createElement('a-image'); 34 | this.shadow.setAttribute('height', 0.36*1.25); 35 | this.shadow.setAttribute('src', '#aframeButtonShadow'); 36 | this.wrapper.appendChild(this.shadow); 37 | 38 | // OUTLINE 39 | this.outline = document.createElement('a-rounded'); 40 | this.outline.setAttribute('height', 0.36); 41 | this.outline.setAttribute('radius', 0.03); 42 | this.outline.setAttribute('position', `0 -${0.36/2} 0.01`); 43 | this.wrapper.appendChild(this.outline); 44 | 45 | // LABEL 46 | this.label = document.createElement('a-entity'); 47 | this.outline.appendChild(this.label); 48 | 49 | // EVENTS 50 | this.el.addEventListener('click', function() { 51 | if (this.components.button && this.components.button.data.disabled) { return; } 52 | that.onClick(); 53 | }); 54 | this.el.addEventListener('mousedown', function() { 55 | if (this.components.button && this.components.button.data.disabled) { 56 | return SFX.clickDisabled(this); 57 | } 58 | that.wrapper.setAttribute('position', `0 0 0.036`); 59 | SFX.click(this); 60 | }); 61 | this.el.addEventListener('mouseup', function() { 62 | if (this.components.button && this.components.button.data.disabled) { return; } 63 | that.wrapper.setAttribute('position', `0 0 0`); 64 | }); 65 | 66 | this.el.getWidth = this.getWidth.bind(this); 67 | Object.defineProperty(this.el, 'value', { 68 | get: function() { return this.getAttribute('value'); }, 69 | set: function(value) { this.setAttribute('value', value); }, 70 | enumerable: true, 71 | configurable: true 72 | }); 73 | }, 74 | onClick: function() { 75 | //Event.emit(this.el, 'click'); 76 | }, 77 | getWidth: function() { 78 | return this.__width; 79 | }, 80 | update: function () { 81 | var that = this; 82 | this.outline.setAttribute('color', this.data.buttonColor); 83 | 84 | let props = { 85 | color: this.data.color, 86 | align: 'center', 87 | wrapCount: 10*this.data.width, 88 | width: this.data.width, 89 | } 90 | if (this.data.font) { props.font = this.data.font; } 91 | 92 | if (this.data.type === "flat") { 93 | props.color = this.data.buttonColor; 94 | } 95 | 96 | // TITLE 97 | props.value = this.data.value.toUpperCase(); 98 | this.label.setAttribute('text', props); 99 | this.label.setAttribute('position', this.data.width/2+0.24+' 0 0.01'); 100 | 101 | // TRIM TEXT IF NEEDED.. @TODO: optimize this mess.. 102 | function getTextWidth(el, callback, _widthFactor) { 103 | if (!el.object3D || !el.object3D.children || !el.object3D.children[0]) { 104 | return setTimeout(function() { 105 | getTextWidth(el, callback); 106 | }, 10); 107 | } 108 | let v = el.object3D.children[0].geometry.visibleGlyphs; 109 | if (!v) { 110 | return setTimeout(function() { 111 | getTextWidth(el, callback); 112 | }, 10); 113 | } 114 | v = v[v.length-1]; 115 | if (!v) { return callback(0); } 116 | if (v.line) { 117 | props.value = props.value.slice(0, -1); 118 | el.setAttribute("text", props); 119 | return getTextWidth(el, callback); 120 | } else { 121 | if (!_widthFactor) { _widthFactor = Utils.getWidthFactor(el, props.wrapCount); } 122 | v = (v.position[0] + v.data.width ) / (_widthFactor/that.data.width); 123 | let textRatio = v / that.data.width; 124 | if (textRatio > 1) { 125 | props.value = props.value.slice(0, -1); 126 | el.setAttribute("text", props); 127 | return getTextWidth(el, callback, _widthFactor); 128 | } 129 | } 130 | return callback(v); 131 | } 132 | setTimeout(function() { 133 | if (that.data.value.length) { 134 | getTextWidth(that.label, (width)=>{ 135 | that.label.setAttribute('position', `${width/2+0.28/2} ${0.36/2} 0.02`);// 136 | width = width+0.28; 137 | that.outline.setAttribute('width', width); 138 | that.__width = width; 139 | that.shadow.setAttribute('width', width*1.17); 140 | that.shadow.setAttribute('position', width/2+' 0 0'); 141 | Event.emit(that.el, 'change:width', width) 142 | }); 143 | } 144 | 145 | if (that.data.disabled) { 146 | that.shadow.setAttribute('visible', false); 147 | let timer = setInterval(function() { 148 | if (that.label.object3D.children[0] && that.label.object3D.children[0].geometry.visibleGlyphs) { 149 | clearInterval(timer); 150 | Utils.updateOpacity(that.el, 0.4); 151 | } 152 | }, 10) 153 | } else { 154 | let timer = setInterval(function() { 155 | if (that.label.object3D.children[0] && that.label.object3D.children[0].geometry.visibleGlyphs) { 156 | clearInterval(timer); 157 | Utils.updateOpacity(that.el, 1); 158 | } 159 | }, 10) 160 | } 161 | 162 | if (that.data.type === "flat") { 163 | that.shadow.setAttribute('visible', false); 164 | let timer = setInterval(function() { 165 | if (that.label.object3D.children[0] && that.label.object3D.children[0].geometry.visibleGlyphs) { 166 | clearInterval(timer); 167 | Utils.updateOpacity(that.outline, 0); 168 | if (that.data.disabled) { 169 | Utils.updateOpacity(that.label, 0.4); 170 | } 171 | } 172 | }, 10) 173 | } 174 | }, 0); 175 | }, 176 | tick: function () {}, 177 | remove: function () {}, 178 | pause: function () {}, 179 | play: function () {} 180 | }); 181 | 182 | AFRAME.registerPrimitive('a-button', { 183 | defaultComponents: { 184 | button: {} 185 | }, 186 | mappings: { 187 | disabled: 'button.disabled', 188 | type: 'button.type', 189 | name: 'button.name', 190 | value: 'button.value', 191 | 'button-color': 'button.buttonColor', 192 | color: 'button.color', 193 | font: 'button.font', 194 | 'letter-spacing': 'button.letterSpacing', 195 | 'line-height': 'button.lineHeight', 196 | 'opacity': 'button.opacity', 197 | 'width': 'button.width' 198 | } 199 | }); 200 | -------------------------------------------------------------------------------- /src/button/sfx.js: -------------------------------------------------------------------------------- 1 | const SFX = { 2 | 3 | init: function(parent) { 4 | let el = document.createElement('a-sound'); 5 | el.setAttribute('key', 'aframeButtonClickSound'); 6 | el.setAttribute('sfx', true); 7 | el.setAttribute('src', '#aframeButtonClick'); 8 | el.setAttribute('position', '0 2 5'); 9 | parent.appendChild(el); 10 | 11 | el = document.createElement('a-sound'); 12 | el.setAttribute('key', 'aframeButtonClickDisabledSound'); 13 | el.setAttribute('sfx', true); 14 | el.setAttribute('src', '#aframeButtonClickDisabled'); 15 | el.setAttribute('position', '0 2 5'); 16 | parent.appendChild(el); 17 | }, 18 | 19 | click: function(parent) { 20 | let el = parent.querySelector('[key=aframeButtonClickSound]'); 21 | if (!el) { return; } 22 | el.components.sound.stopSound(); 23 | el.components.sound.playSound(); 24 | }, 25 | 26 | clickDisabled: function(parent) { 27 | let el = parent.querySelector('[key=aframeButtonClickDisabledSound]'); 28 | if (!el) { return; } 29 | el.components.sound.stopSound(); 30 | el.components.sound.playSound(); 31 | } 32 | } 33 | 34 | module.exports = SFX; 35 | -------------------------------------------------------------------------------- /src/checkbox/assets.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { type: 'img', id: 'aframeCheckboxMark', src: `${AFRAME.ASSETS_PATH}/images/CheckmarkIcon.png`}, 3 | { type: 'audio', id: 'aframeCheckboxClick', src: `${AFRAME.ASSETS_PATH}/sounds/InputClick.mp3`}, 4 | { type: 'audio', id: 'aframeCheckboxClickDisabled', src: `${AFRAME.ASSETS_PATH}/sounds/ButtonClickDisabled.mp3`} 5 | ] 6 | -------------------------------------------------------------------------------- /src/checkbox/index.js: -------------------------------------------------------------------------------- 1 | const Utils = require('../utils'); 2 | const Event = require('../core/event'); 3 | const Assets = require('./assets'); 4 | const SFX = require('./sfx'); 5 | 6 | AFRAME.registerComponent('checkbox', { 7 | schema: { 8 | checked: { type: 'boolean', default: false }, 9 | disabled: { type: 'boolean', default: false }, 10 | name: { type: "string", default: "" }, 11 | value: { type: "string", default: "" }, 12 | label: { type: "string", default: "" }, 13 | checkboxColor: { type: "color", default: "#757575"}, 14 | checkboxColorChecked: { type: "color", default: "#4076fd"}, 15 | color: { type: "color", default: "#757575" }, 16 | font: { type: "string", default: "" }, 17 | letterSpacing: { type: "int", default: 0 }, 18 | lineHeight: { type: "string", default: "" }, 19 | opacity: { type: "number", default: 1 }, 20 | width: { type: "number", default: 1 } 21 | }, 22 | init: function () { 23 | var that = this; 24 | 25 | // Assets 26 | Utils.preloadAssets( Assets ); 27 | 28 | // SFX 29 | SFX.init(this.el); 30 | 31 | // HITBOX 32 | this.hitbox = document.createElement('a-plane'); 33 | this.hitbox.setAttribute('height', 0.2); 34 | this.hitbox.setAttribute('opacity', 0); 35 | this.el.appendChild(this.hitbox); 36 | 37 | // OUTLINE 38 | this.outline = document.createElement('a-rounded'); 39 | this.outline.setAttribute('width', 0.2); 40 | this.outline.setAttribute('height', 0.2); 41 | this.outline.setAttribute('radius', 0.02); 42 | this.outline.setAttribute('position', `0 -${0.2/2} 0.01`); 43 | this.el.appendChild(this.outline); 44 | 45 | // INSIDE 46 | this.inside = document.createElement('a-rounded'); 47 | this.inside.setAttribute('width', 0.156); 48 | this.inside.setAttribute('height', 0.156); 49 | this.inside.setAttribute('radius', 0.01); 50 | this.inside.setAttribute('color', "#EEE"); 51 | this.inside.setAttribute('position', `${0.156/8} -${0.156/2} 0.02`); 52 | this.el.appendChild(this.inside); 53 | 54 | // CHECKMARK 55 | this.checkmark = document.createElement('a-image'); 56 | this.checkmark.setAttribute('width', 0.16); 57 | this.checkmark.setAttribute('height', 0.16); 58 | this.checkmark.setAttribute('src', "#aframeCheckboxMark"); 59 | this.checkmark.setAttribute('position', '0.1 0 0.03'); 60 | this.el.appendChild(this.checkmark); 61 | 62 | // LABEL 63 | this.label = document.createElement('a-entity'); 64 | this.el.appendChild(this.label); 65 | 66 | // EVENTS 67 | this.el.addEventListener('click', function() { 68 | if (this.components.checkbox.data.disabled) { return; } 69 | this.components.checkbox.data.checked = !this.components.checkbox.data.checked; 70 | this.setAttribute('checked', this.components.checkbox.data.checked); 71 | that.onClick(); 72 | }); 73 | this.el.addEventListener('mousedown', function() { 74 | if (this.components.checkbox.data.disabled) { 75 | return SFX.clickDisabled(this); 76 | } 77 | SFX.click(this); 78 | }); 79 | 80 | Object.defineProperty(this.el, 'value', { 81 | get: function() { return this.getAttribute('value'); }, 82 | set: function(value) { this.setAttribute('value', value); }, 83 | enumerable: true, 84 | configurable: true 85 | }); 86 | }, 87 | onClick: function(noemit) { 88 | if (this.data.checked) { 89 | this.check(); 90 | } else { 91 | this.uncheck(); 92 | } 93 | if (!noemit) { Event.emit(this.el, 'change', this.data.checked); } 94 | }, 95 | check: function() { 96 | this.outline.setAttribute('color', this.data.checkboxColorChecked); 97 | this.inside.setAttribute('color', this.data.checkboxColorChecked); 98 | this.checkmark.setAttribute('visible', true); 99 | if (this.data.disabled) { this.disabled(); } 100 | }, 101 | uncheck: function() { 102 | this.outline.setAttribute('color', this.data.checkboxColor); 103 | this.inside.setAttribute('color', "#EEE"); 104 | this.checkmark.setAttribute('visible', false); 105 | if (this.data.disabled) { this.disabled(); } 106 | }, 107 | disabled: function() { 108 | this.outline.setAttribute('color', this.data.checkboxColor); 109 | this.inside.setAttribute('color', this.data.checkboxColor); 110 | }, 111 | update: function () { 112 | var that = this; 113 | this.onClick(true); 114 | 115 | // HITBOX 116 | this.hitbox.setAttribute('width', this.data.width) 117 | this.hitbox.setAttribute('position', this.data.width/2+' 0 0.01'); 118 | 119 | let props = { 120 | color: this.data.color, 121 | align: 'left', 122 | wrapCount: 10*(this.data.width+0.2), 123 | width: this.data.width, 124 | } 125 | if (this.data.font) { props.font = this.data.font; } 126 | 127 | // TITLE 128 | props.value = this.data.label; 129 | props.color = this.data.color; 130 | this.label.setAttribute('text', props); 131 | this.label.setAttribute('position', this.data.width/2+0.24+' 0 0.01'); 132 | 133 | // TRIM TEXT IF NEEDED.. @TODO: optimize this mess.. 134 | function getTextWidth(el, _widthFactor) { 135 | if (!el.object3D || !el.object3D.children || !el.object3D.children[0]) { return 0; } 136 | let v = el.object3D.children[0].geometry.visibleGlyphs; 137 | if (!v) { return 0; } 138 | v = v[v.length-1]; 139 | if (!v) { return 0; } 140 | if (v.line) { 141 | props.value = props.value.slice(0, -1); 142 | el.setAttribute("text", props); 143 | return getTextWidth(el); 144 | } else { 145 | if (!_widthFactor) { _widthFactor = Utils.getWidthFactor(el, props.wrapCount); } 146 | v = (v.position[0] + v.data.width) / (_widthFactor/that.data.width); 147 | let textRatio = v / that.data.width; 148 | if (textRatio > 1) { 149 | props.value = props.value.slice(0, -1); 150 | el.setAttribute("text", props); 151 | return getTextWidth(el, _widthFactor); 152 | } 153 | } 154 | return v; 155 | } 156 | setTimeout(function() { 157 | if (that.data.label.length) { 158 | getTextWidth(that.label); 159 | } 160 | if (that.data.disabled) { 161 | let timer = setInterval(function() { 162 | if (that.checkmark.object3D.children[0]) { 163 | clearInterval(timer); 164 | Utils.updateOpacity(that.checkmark, 0.4); 165 | Utils.updateOpacity(that.label, 0.4); 166 | } 167 | }, 10) 168 | } else { 169 | let timer = setInterval(function() { 170 | if (that.checkmark.object3D.children[0]) { 171 | clearInterval(timer); 172 | Utils.updateOpacity(that.checkmark, 1); 173 | Utils.updateOpacity(that.label, 1); 174 | } 175 | }, 10) 176 | } 177 | }, 0); 178 | }, 179 | tick: function () {}, 180 | remove: function () {}, 181 | pause: function () {}, 182 | play: function () {} 183 | }); 184 | 185 | AFRAME.registerPrimitive('a-checkbox', { 186 | defaultComponents: { 187 | checkbox: {} 188 | }, 189 | mappings: { 190 | checked: 'checkbox.checked', 191 | disabled: 'checkbox.disabled', 192 | name: 'checkbox.name', 193 | value: 'checkbox.value', 194 | label: 'checkbox.label', 195 | 'checkbox-color': 'checkbox.checkboxColor', 196 | 'checkbox-color-checked': 'checkbox.checkboxColorChecked', 197 | color: 'checkbox.color', 198 | align: 'checkbox.align', 199 | font: 'checkbox.font', 200 | 'letter-spacing': 'checkbox.letterSpacing', 201 | 'line-height': 'checkbox.lineHeight', 202 | 'opacity': 'checkbox.opacity', 203 | width: 'checkbox.width' 204 | } 205 | }); 206 | -------------------------------------------------------------------------------- /src/checkbox/sfx.js: -------------------------------------------------------------------------------- 1 | const SFX = { 2 | 3 | init: function(parent) { 4 | let el = document.createElement('a-sound'); 5 | el.setAttribute('key', 'aframeCheckboxClickSound'); 6 | el.setAttribute('sfx', true); 7 | el.setAttribute('src', '#aframeCheckboxClick'); 8 | el.setAttribute('position', '0 2 5'); 9 | parent.appendChild(el); 10 | 11 | el = document.createElement('a-sound'); 12 | el.setAttribute('key', 'aframeButtonClickDisabledSound'); 13 | el.setAttribute('sfx', true); 14 | el.setAttribute('src', '#aframeButtonClickDisabled'); 15 | el.setAttribute('position', '0 2 5'); 16 | parent.appendChild(el); 17 | }, 18 | 19 | click: function(parent) { 20 | let el = parent.querySelector('[key=aframeCheckboxClickSound]'); 21 | if (!el) { return; } 22 | el.components.sound.stopSound(); 23 | el.components.sound.playSound(); 24 | }, 25 | 26 | clickDisabled: function(parent) { 27 | let el = parent.querySelector('[key=aframeButtonClickDisabledSound]'); 28 | if (!el) { return; } 29 | el.components.sound.stopSound(); 30 | el.components.sound.playSound(); 31 | } 32 | } 33 | 34 | module.exports = SFX; 35 | -------------------------------------------------------------------------------- /src/core/event.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | emit: (el, name, data)=> { 3 | el.dispatchEvent( new CustomEvent(name, {detail: data}) ); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/fade/index.js: -------------------------------------------------------------------------------- 1 | const Event = require('../core/event'); 2 | 3 | var opacityUpdate = function(opacity) { 4 | this.el.object3D.traverse(function (o) { 5 | if (o.material) { 6 | o.material.transparent = true; 7 | o.material.opacity = opacity; 8 | } 9 | }); 10 | for (let text of this.textEntities) { 11 | text.setAttribute('opacity', opacity); 12 | } 13 | } 14 | 15 | // ----------------------------------------------------------------------------- 16 | // FADEIN 17 | 18 | AFRAME.registerComponent('fadein', { 19 | schema: { 20 | duration: { type: 'int', default: 200 }, 21 | }, 22 | init: function() { 23 | this.textEntities = this.el.querySelectorAll('a-text'); 24 | this.opacityUpdate(0); 25 | this.start = null; 26 | }, 27 | tick: function (t) { 28 | if (!this.start) { this.start = t; } 29 | var opacity = Math.min((t - this.start) / this.data.duration, 1); 30 | this.opacityUpdate(opacity); 31 | if (opacity === 1) { 32 | this.el.removeAttribute('fadein'); 33 | Event.emit(this.el, 'animationend'); 34 | } 35 | }, 36 | opacityUpdate: opacityUpdate 37 | }); 38 | 39 | // ----------------------------------------------------------------------------- 40 | // FADEOUT 41 | 42 | AFRAME.registerComponent('fadeout', { 43 | schema: { 44 | duration: { type: 'int', default: 200 } 45 | }, 46 | init: function() { 47 | this.textEntities = this.el.querySelectorAll('a-text'); 48 | this.opacityUpdate(1); 49 | this.start = null; 50 | }, 51 | tick: function (t) { 52 | if (!this.start) { this.start = t; } 53 | var opacity = 1-Math.min((t - this.start) / this.data.duration, 1); 54 | this.opacityUpdate(opacity); 55 | if (opacity === 0) { 56 | this.el.removeAttribute('fadeout'); 57 | Event.emit(this.el, 'animationend'); 58 | } 59 | }, 60 | opacityUpdate: opacityUpdate 61 | }); 62 | 63 | // ----------------------------------------------------------------------------- 64 | // SHOW 65 | 66 | AFRAME.registerComponent('show', { 67 | init: function() { 68 | this.textEntities = this.el.querySelectorAll('a-text'); 69 | this.opacityUpdate(1); 70 | this.el.removeAttribute('show'); 71 | }, 72 | opacityUpdate: opacityUpdate 73 | }); 74 | 75 | // ----------------------------------------------------------------------------- 76 | // HIDE 77 | 78 | AFRAME.registerComponent('hide', { 79 | init: function() { 80 | this.textEntities = this.el.querySelectorAll('a-text'); 81 | this.opacityUpdate(0); 82 | this.el.removeAttribute('hide'); 83 | }, 84 | opacityUpdate: opacityUpdate 85 | }); 86 | -------------------------------------------------------------------------------- /src/form/index.js: -------------------------------------------------------------------------------- 1 | const Utils = require('../utils'); 2 | const Event = require('../core/event'); 3 | 4 | AFRAME.registerComponent('form', { 5 | schema: { 6 | }, 7 | init: function () { 8 | }, 9 | update: function () { 10 | }, 11 | tick: function () {}, 12 | remove: function () {}, 13 | pause: function () {}, 14 | play: function () {} 15 | }); 16 | 17 | AFRAME.registerPrimitive('a-form', { 18 | defaultComponents: { 19 | form: {} 20 | }, 21 | mappings: { 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | (()=>{ 2 | if (!AFRAME) { return console.error('AFRAME is required!'); } 3 | if (!AFRAME.ASSETS_PATH) { AFRAME.ASSETS_PATH = "./assets"; } 4 | require('aframe-rounded'); 5 | require("./fade"); 6 | //require("./alert"); @TODO ;) 7 | require("./keyboard"); 8 | require("./input"); 9 | require("./switch"); 10 | require("./form"); 11 | require("./radio"); 12 | require("./checkbox"); 13 | require("./button"); 14 | require("./toast"); 15 | })(); 16 | -------------------------------------------------------------------------------- /src/input/index.js: -------------------------------------------------------------------------------- 1 | const Utils = require('../utils'); 2 | const Event = require('../core/event'); 3 | 4 | /* 5 | @BUG: Space has not effect when no letter comes after. 6 | @TODO: 70 % 7 | */ 8 | 9 | AFRAME.registerComponent('input', { 10 | schema: { 11 | value: { type: "string", default: "" }, 12 | name: { type: "string", default: "" }, 13 | disabled: { type: "boolean", default: false }, 14 | color: { type: "color", default: "#000" }, 15 | align: { type: "string", default: "left" }, 16 | font: { type: "string", default: "" }, 17 | letterSpacing: { type: "int", default: 0 }, 18 | lineHeight: { type: "string", default: "" }, 19 | opacity: { type: "number", default: 1 }, 20 | side: { type: "string", default: 'front' }, 21 | tabSize: { type: "int", default: 4 }, 22 | placeholder: { type: "string", default: "" }, 23 | placeholderColor: { type: "color", default: "#AAA" }, 24 | maxLength: { type: "int", default: 0 }, 25 | type: { type: "string", default: "text" }, 26 | width: { type: "number", default: 1 }, 27 | cursorWidth: { type: "number", default: 0.01 }, 28 | cursorHeight: { type: "number", default: 0.08 }, 29 | cursorColor: { type: "color", default: "#007AFF" }, 30 | backgroundColor: { type: "color", default: "#FFF" }, 31 | backgroundOpacity: { type: "number", default: 1 }, 32 | }, 33 | 34 | init: function () { 35 | let that = this; 36 | 37 | this.background = document.createElement('a-rounded'); 38 | this.background.setAttribute('radius', 0.01) 39 | this.background.setAttribute('height', 0.18) 40 | this.background.setAttribute('side', 'double') 41 | this.el.appendChild(this.background); 42 | 43 | this.cursor = document.createElement('a-plane'); 44 | this.cursor.setAttribute('position', '0 0 0.003'); 45 | this.cursor.setAttribute('visible', false); 46 | this.el.appendChild(this.cursor); 47 | 48 | this.text = document.createElement('a-entity'); 49 | this.el.appendChild(this.text); 50 | 51 | this.placeholder = document.createElement('a-entity'); 52 | this.placeholder.setAttribute('visible', false); 53 | this.el.appendChild(this.placeholder); 54 | 55 | this.el.focus = this.focus.bind(this); 56 | this.el.blur = this.blur.bind(this); 57 | this.el.appendString = this.appendString.bind(this); 58 | this.el.deleteLast = this.deleteLast.bind(this); 59 | 60 | //setTimeout(function() { that.updateText(); }, 0); 61 | this.blink(); 62 | 63 | this.el.addEventListener('click', function() { 64 | if (this.components.input.data.disabled) { return; } 65 | that.focus(); 66 | }); 67 | 68 | Object.defineProperty(this.el, 'value', { 69 | get: function() { return this.getAttribute('value'); }, 70 | set: function(value) { this.setAttribute('value', value); }, 71 | enumerable: true, 72 | configurable: true 73 | }); 74 | }, 75 | blink: function() { 76 | let that = this; 77 | if (!this.isFocused) { 78 | that.cursor.setAttribute('visible', false); 79 | clearInterval(this.cursorInterval); 80 | this.cursorInterval = null; 81 | return 82 | } 83 | this.cursorInterval = setInterval(function(){ 84 | that.cursor.setAttribute('visible', !that.cursor.getAttribute('visible')); 85 | }, 500); 86 | }, 87 | isFocused: false, 88 | focus: function(noemit) { 89 | if (this.isFocused) { return; } 90 | this.isFocused = true; 91 | this.cursor.setAttribute('visible', true); 92 | this.blink(); 93 | Event.emit(this.el, 'focus'); 94 | if (!noemit) { Event.emit(document.body, 'didfocusinput', this.el); } 95 | }, 96 | blur: function(noemit) { 97 | if (!this.isFocused) { return; } 98 | this.isFocused = false; 99 | if (this.cursorInterval) { 100 | clearInterval(this.cursorInterval); 101 | this.cursorInterval = null; 102 | } 103 | this.cursor.setAttribute('visible', false); 104 | Event.emit(this.el, 'blur'); 105 | if (!noemit) { Event.emit(document.body, 'didblurinput', this.el); } 106 | }, 107 | appendString: function(data) { 108 | if(data === '\n') { 109 | return this.blur(); 110 | } 111 | let str = this.el.getAttribute("value"); 112 | if (!str) { str = "" } 113 | str = str+data; 114 | this.el.setAttribute("value", str) 115 | Event.emit(this.el, 'change', str); 116 | }, 117 | deleteLast: function() { 118 | let str = this.el.getAttribute("value"); 119 | if (!str) { str = "" } 120 | str = str.slice(0, -1); 121 | this.el.setAttribute("value", str) 122 | Event.emit(this.el, 'change', str); 123 | }, 124 | updateText: function() { 125 | let that = this; 126 | let padding = { 127 | left: 0.021, 128 | right: 0.021 129 | }; 130 | 131 | let props = { 132 | color: this.data.color, 133 | align: this.data.align, 134 | side: this.data.side, 135 | tabSize: this.data.tabSize, 136 | wrapCount: 24*this.data.width, 137 | width: this.data.width 138 | } 139 | 140 | // Make cursor stop blinking when typing.. 141 | // (and blinking again after typing stop). 142 | let attr = this.text.getAttribute("text"); 143 | if (attr) { 144 | if (this.data.value !== attr.value) { 145 | if (this.cursorInterval) { 146 | clearInterval(this.cursorInterval); 147 | this.cursorInterval = null; 148 | } 149 | if (this.cursorTimer) { 150 | clearTimeout(this.cursorTimer); 151 | this.cursorTimer = null; 152 | } 153 | this.cursor.setAttribute('visible', true); 154 | this.cursorTimer = setTimeout(function(){ 155 | that.blink(); 156 | }, 50); 157 | } 158 | } 159 | 160 | // Max length 161 | if (this.data.maxLength) { 162 | props.value = this.data.value.substring(0, this.data.maxLength); 163 | this.el.setAttribute('value', props.value) 164 | } else { 165 | props.value = this.data.value; 166 | } 167 | 168 | if (this.data.type === "password") { 169 | props.value = "*".repeat(this.data.value.length); 170 | } 171 | 172 | if (this.data.font.length) { props.font = this.data.font } 173 | if (this.data.letterSpacing) { props.letterSpacing = this.data.letterSpacing; } 174 | if (this.data.lineHeight.length) { props.lineHeight = this.data.lineHeight; } 175 | this.text.setAttribute('visible', false); 176 | this.text.setAttribute("text", props); 177 | 178 | function getTextWidth(el, data, trimFirst, _widthFactor) { 179 | if (!el.object3D || !el.object3D.children || !el.object3D.children[0]) { return 0; } 180 | let v = el.object3D.children[0].geometry.visibleGlyphs; 181 | if (!v) { return 0; } 182 | v = v[v.length-1]; 183 | if (!v) { return 0; } 184 | if (v.line) { 185 | if (trimFirst) { 186 | data.value = data.value.substr(1); 187 | } else { 188 | data.value = data.value.slice(0, -1); 189 | } 190 | el.setAttribute("text", data); 191 | return getTextWidth(el, data, trimFirst); 192 | } else { 193 | if (!_widthFactor) { _widthFactor = Utils.getWidthFactor(el, data.wrapCount); } 194 | v = (v.position[0] + v.data.width) / (_widthFactor/that.data.width); 195 | let textRatio = (v+padding.left+padding.right) / that.data.width; 196 | 197 | if (textRatio > 1) { 198 | if (trimFirst) { 199 | data.value = data.value.substr(1); 200 | } else { 201 | data.value = data.value.slice(0, -1); 202 | } 203 | el.setAttribute("text", data); 204 | return getTextWidth(el, data, trimFirst, _widthFactor); 205 | } 206 | } 207 | return v; 208 | } 209 | 210 | 211 | if (props.value.length) { 212 | this.placeholder.setAttribute('visible', false); 213 | } else { 214 | this.placeholder.setAttribute('visible', true); 215 | } 216 | 217 | let placeholder_props = Utils.clone(props); 218 | placeholder_props.value = this.data.placeholder; 219 | placeholder_props.color = this.data.placeholderColor; 220 | this.placeholder.setAttribute("text", placeholder_props); 221 | 222 | setTimeout(function() { 223 | if (that.text.object3D) { 224 | let children = that.text.object3D.children; 225 | if (children[0] && children[0].geometry && children[0].geometry.visibleGlyphs) { 226 | let v = 0; 227 | if (children[0].geometry.visibleGlyphs.length) { 228 | v = getTextWidth(that.text, props, true); 229 | that.text.setAttribute('visible', true); 230 | } 231 | that.cursor.setAttribute('position', v+padding.left+' 0 0.003'); 232 | } else { 233 | that.cursor.setAttribute('position', padding.left+' 0 0.003'); 234 | } 235 | } else { that.cursor.setAttribute('position', padding.left+' 0 0.003'); } 236 | getTextWidth(that.placeholder, placeholder_props); 237 | }, 0) 238 | 239 | this.background.setAttribute('color', this.data.backgroundColor) 240 | /*if (this.data.backgroundOpacity) { 241 | setTimeout(function() { 242 | Utils.updateOpacity(that.background, that.data.backgroundOpacity); 243 | }, 0); 244 | }*/ 245 | this.background.setAttribute('width', this.data.width); 246 | //this.background.setAttribute('position', this.data.width/2+' 0 0'); 247 | this.background.setAttribute('position', '0 -0.09 0.001'); 248 | this.text.setAttribute('position', padding.left-0.001+this.data.width/2+' 0 0.002'); 249 | this.placeholder.setAttribute('position', padding.left-0.001+this.data.width/2+' 0 0.002'); 250 | }, 251 | updateCursor: function() { 252 | this.cursor.setAttribute('width', this.data.cursorWidth) 253 | this.cursor.setAttribute('height', this.data.cursorHeight) 254 | this.cursor.setAttribute('color', this.data.cursorColor); 255 | }, 256 | update: function () { 257 | let that = this; 258 | setTimeout(function() { 259 | // Utils.updateOpacity(that.el, that.data.opacity); 260 | }, 0) 261 | 262 | this.updateCursor(); 263 | this.updateText(); 264 | }, 265 | tick: function () {}, 266 | remove: function () {}, 267 | pause: function () {}, 268 | play: function () {} 269 | }); 270 | 271 | AFRAME.registerPrimitive('a-input', { 272 | defaultComponents: { 273 | input: {} 274 | }, 275 | mappings: { 276 | value: 'input.value', 277 | name: 'input.name', 278 | disabled: 'input.disabled', 279 | color: 'input.color', 280 | align: 'input.align', 281 | font: 'input.font', 282 | 'letter-spacing': 'input.letterSpacing', 283 | 'line-height': 'input.lineHeight', 284 | 'opacity': 'input.opacity', 285 | 'side': 'input.side', 286 | 'tab-size': 'input.tabSize', 287 | placeholder: 'input.placeholder', 288 | 'placeholder-color': 'input.placeholderColor', 289 | 'max-length': 'input.maxLength', 290 | type: 'input.type', 291 | width: 'input.width', 292 | 'cursor-width': "input.cursorWidth", 293 | 'cursor-height': "input.cursorHeight", 294 | 'cursor-color': "input.cursorColor", 295 | 'background-color': 'input.backgroundColor', 296 | 'background-opacity': 'input.backgroundOpacity' 297 | } 298 | }); 299 | -------------------------------------------------------------------------------- /src/keyboard/assets.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | aframeKeyboardShift: `${AFRAME.ASSETS_PATH}/images/ShiftIcon.png`, 3 | aframeKeyboardShiftActive: `${AFRAME.ASSETS_PATH}/images/ShiftActiveIcon.png`, 4 | aframeKeyboardGlobal: `${AFRAME.ASSETS_PATH}/images/GlobalIcon.png`, 5 | aframeKeyboardBackspace: `${AFRAME.ASSETS_PATH}/images/BackspaceIcon.png`, 6 | aframeKeyboardEnter: `${AFRAME.ASSETS_PATH}/images/EnterIcon.png`, 7 | aframeKeyboardDismiss: `${AFRAME.ASSETS_PATH}/images/DismissIcon.png`, 8 | aframeKeyboardShadow: `${AFRAME.ASSETS_PATH}/images/KeyShadow.png`, 9 | aframeKeyboardKeyIn: `${AFRAME.ASSETS_PATH}/sounds/KeyIn.mp3`, 10 | aframeKeyboardKeyDown: `${AFRAME.ASSETS_PATH}/sounds/KeyDown.mp3` 11 | } 12 | -------------------------------------------------------------------------------- /src/keyboard/behaviors.js: -------------------------------------------------------------------------------- 1 | const Assets = require('./assets'); 2 | const Config = require('./config'); 3 | const Utils = require('../utils'); 4 | const Event = require('../core/event'); 5 | const SFX = require('./sfx'); 6 | const Behaviors = {}; 7 | 8 | Behaviors.el = null; 9 | 10 | // ----------------------------------------------------------------------------- 11 | // KEYBOARD METHODS 12 | 13 | Behaviors.showKeyboard = function(el) { 14 | if (el.o_position) { 15 | el.object3D.position.copy(el.o_position); 16 | } 17 | el.isOpen = true; 18 | for(let item of el.querySelectorAll('[data-ui]') ) { 19 | for (let child of item.children) { 20 | child.setAttribute('show', true); 21 | } 22 | } 23 | let parent = el.parentNode; 24 | if (parent) { return; } 25 | el.sceneEl.appendChild(el); 26 | }; 27 | 28 | Behaviors.hideKeyboard = function(el) { 29 | let position = el.getAttribute("position"); 30 | if (position.x !== -10000) { 31 | if (!el.o_position) { 32 | el.o_position = new THREE.Vector3(); 33 | } 34 | el.o_position.copy(position); 35 | } 36 | el.isOpen = false; 37 | el.setAttribute("position", "-10000 -10000 -10000"); 38 | el.setAttribute('fadeout', {duration: 1}); 39 | } 40 | 41 | Behaviors.destroyKeyboard = function(el) { 42 | let parent = el.parentNode; 43 | if (!parent) { return; } 44 | parent.removeChild(el); 45 | }; 46 | 47 | Behaviors.openKeyboard = function(el) { 48 | if (el.o_position) { 49 | el.object3D.position.copy(el.o_position); 50 | } 51 | el.isOpen = true; 52 | el._transitioning = true; 53 | let parent = el.parentNode; 54 | if (!parent) { el.sceneEl.appendChild(el); } 55 | for(let item of el.querySelectorAll('[data-ui]') ) { 56 | for (let child of item.children) { 57 | child.setAttribute('hide', true); 58 | } 59 | function animationend() { 60 | item.children[0].removeEventListener('animationend', animationend) 61 | setTimeout(function() { 62 | item.children[1].setAttribute('fadein', {duration: 160}); 63 | Event.emit(Behaviors.el, 'didopen'); 64 | el._transitioning = false; 65 | }, 10) 66 | } 67 | item.children[0].setAttribute('fadein', {duration: 160}); 68 | item.children[0].addEventListener('animationend', animationend) 69 | } 70 | }; 71 | 72 | Behaviors.dismissKeyboard = function(el) { 73 | el._transitioning = true; 74 | for(let item of el.querySelectorAll('[data-ui]') ) { 75 | for (let child of item.children) { 76 | child.setAttribute('show', true); 77 | } 78 | el.isOpen = false; 79 | function animationend() { 80 | item.children[1].removeEventListener('animationend', animationend) 81 | setTimeout(function() { 82 | function animationend() { 83 | item.children[0].removeEventListener('animationend', animationend); 84 | Behaviors.hideKeyboard(el); 85 | Event.emit(Behaviors.el, 'diddismiss'); 86 | el._transitioning = false; 87 | } 88 | item.children[0].setAttribute('fadeout', {duration: 160}); 89 | item.children[0].addEventListener('animationend', animationend); 90 | }, 10) 91 | } 92 | item.children[1].setAttribute('fadeout', {duration: 160}); 93 | item.children[1].addEventListener('animationend', animationend) 94 | } 95 | }; 96 | 97 | // ----------------------------------------------------------------------------- 98 | // KEY EVENTS 99 | 100 | Behaviors.addKeyEvents = (el)=>{ 101 | el.addEventListener('click', Behaviors.keyClick); 102 | el.addEventListener('mousedown', Behaviors.keyDown); 103 | el.addEventListener('mouseup', Behaviors.keyOut); 104 | el.addEventListener('raycaster-intersected', Behaviors.keyIn ); 105 | el.addEventListener('raycaster-intersected-cleared', Behaviors.keyOut ); 106 | //triggerdown 107 | // https://aframe.io/docs/0.6.0/components/hand-controls.html 108 | }; 109 | 110 | // ----------------------------------------------------------------------------- 111 | // KEYCLICK 112 | 113 | Behaviors.keyClick = function() { 114 | SFX.keyDown(Behaviors.el); 115 | 116 | let type = this.getAttribute('key-type'); 117 | let value = this.getAttribute('key-value'); 118 | 119 | if (type === 'text' || type === 'spacebar') { 120 | if (type === 'spacebar') { value = ' '; } 121 | if (Behaviors.isShiftEnabled) { 122 | value = value.toUpperCase(); 123 | Behaviors.shiftToggle(); 124 | } 125 | else if (Behaviors.isSymbols) { 126 | Behaviors.symbolsToggle(); 127 | } 128 | Event.emit(Behaviors.el, 'input', value); 129 | } 130 | else if (type === 'shift') { 131 | Behaviors.shiftToggle(); 132 | } 133 | else if (type === 'symbol') { 134 | Behaviors.symbolsToggle(); 135 | } 136 | else if (type === 'backspace') { 137 | Event.emit(Behaviors.el, 'backspace'); 138 | } 139 | else if (type === 'enter') { 140 | Event.emit(Behaviors.el, 'input', '\n'); 141 | Event.emit(Behaviors.el, 'enter', '\n'); 142 | } 143 | else if (type === 'dismiss') { 144 | Event.emit(Behaviors.el, 'dismiss'); 145 | } 146 | } 147 | 148 | // ----------------------------------------------------------------------------- 149 | // KEYDOWN 150 | 151 | Behaviors.keyDown = function() { 152 | if (Behaviors.el._transitioning) { return; } 153 | this.object3D.position.z = 0.003; 154 | if (this.getAttribute('key-type') === 'spacebar') { 155 | this.setAttribute('color', Config.SPACEBAR_COLOR_ACTIVE); 156 | } else { 157 | this.setAttribute('color', Config.KEY_COLOR_ACTIVE); 158 | } 159 | }; 160 | 161 | // ----------------------------------------------------------------------------- 162 | // KEYIN 163 | 164 | Behaviors.keyIn = function() { 165 | if (Behaviors.el._transitioning) { return; } 166 | if (this.object3D.children[2] && this.object3D.children[2].material && !this.object3D.children[2].material.opacity) { 167 | return 168 | } 169 | SFX.keyIn(Behaviors.el); 170 | if (this.getAttribute('key-type') === 'spacebar') { 171 | this.setAttribute('color', Config.SPACEBAR_COLOR_HIGHLIGHT); 172 | } else { 173 | this.setAttribute('color', Config.KEY_COLOR_HIGHLIGHT); 174 | } 175 | }; 176 | 177 | // ----------------------------------------------------------------------------- 178 | // KEYOUT 179 | 180 | Behaviors.keyOut = function() { 181 | this.object3D.position.z = 0; 182 | if (this.getAttribute('key-type') === 'spacebar') { 183 | this.setAttribute('color', Config.KEY_COLOR_ACTIVE); 184 | } else { 185 | this.setAttribute('color', Config.KEYBOARD_COLOR); 186 | } 187 | } 188 | 189 | // ----------------------------------------------------------------------------- 190 | // SHIFT 191 | 192 | Behaviors.isShiftEnabled = false; 193 | Behaviors.shiftToggle = function() { 194 | Behaviors.isShiftEnabled = !Behaviors.isShiftEnabled; 195 | 196 | var icon_el = Behaviors.el.shiftKey.querySelector('[data-type]'); 197 | if (Behaviors.isShiftEnabled) { 198 | icon_el.setAttribute('src', Assets.aframeKeyboardShiftActive); 199 | } else { 200 | icon_el.setAttribute('src', Assets.aframeKeyboardShift); 201 | } 202 | 203 | for ( let keyEl of document.querySelectorAll("[key-id]") ) { 204 | let key_id = keyEl.getAttribute('key-id'), key_type = keyEl.getAttribute('key-type'); 205 | if (key_id.startsWith('main-') && key_type === "text") { 206 | let textEl = keyEl.querySelector('a-text'); 207 | if (textEl) { 208 | let value = textEl.getAttribute('value').toLowerCase(); 209 | if (this.isShiftEnabled) { value = value.toUpperCase(); } 210 | textEl.setAttribute('value', value); 211 | } 212 | } 213 | } 214 | } 215 | 216 | // ----------------------------------------------------------------------------- 217 | // SYMBOLS 218 | 219 | Behaviors.isSymbols = false; 220 | Behaviors.symbolsToggle = function() { 221 | Behaviors.isSymbols = !Behaviors.isSymbols; 222 | if (!Behaviors.isSymbols) { 223 | let parent = Behaviors.el.symbolsLayout.parentNode; 224 | parent.removeChild(Behaviors.el.symbolsLayout); 225 | parent.appendChild(Behaviors.el.alphabeticalLayout); 226 | setTimeout(function() { 227 | Utils.updateOpacity(Behaviors.el.alphabeticalLayout, 1); 228 | }, 0) 229 | } else { 230 | let parent = Behaviors.el.alphabeticalLayout.parentNode; 231 | parent.removeChild(Behaviors.el.alphabeticalLayout); 232 | parent.appendChild(Behaviors.el.symbolsLayout); 233 | } 234 | } 235 | 236 | 237 | 238 | module.exports = Behaviors; 239 | -------------------------------------------------------------------------------- /src/keyboard/config.js: -------------------------------------------------------------------------------- 1 | const Config = { 2 | KEYBOARD_COLOR: "#263238", 3 | KEY_COLOR_HIGHLIGHT: "#29363c", 4 | KEY_COLOR_ACTIVE: "#404b50", 5 | SPACEBAR_COLOR_ACTIVE: "#3c464b", 6 | SPACEBAR_COLOR_HIGHLIGHT: "#445055", 7 | KEY_WIDTH: 0.08, 8 | SPACE_KEY_WIDTH: 0.368, 9 | SPACE_KEY_HEIGHT: 0.05, 10 | ACTION_WIDTH: 0.140, 11 | } 12 | 13 | module.exports = Config; 14 | -------------------------------------------------------------------------------- /src/keyboard/draw.js: -------------------------------------------------------------------------------- 1 | const Assets = require('./assets'); 2 | const Layouts = require('./layouts'); 3 | const Config = require('./config'); 4 | const Behaviors = require('./behaviors'); 5 | const Draw = {}; 6 | 7 | Draw.el = null; 8 | 9 | Draw.init = (el)=>{ 10 | Draw.el = el; 11 | Behaviors.el = el; 12 | Behaviors.SFX = el.SFX; 13 | } 14 | 15 | // ----------------------------------------------------------------------------- 16 | // DRAW NUMERICAL UI 17 | 18 | Draw.numericalUI = ()=> { 19 | var wrapper = document.createElement('a-entity'); 20 | wrapper.setAttribute('position', '0.025 0 0.12'); 21 | wrapper.setAttribute('rotation', '0 25 0'); 22 | wrapper.setAttribute('data-ui', true); 23 | 24 | var el = document.createElement('a-rounded'); 25 | el.setAttribute('width', '0.280'); 26 | el.setAttribute('height', '0.360'); 27 | el.setAttribute('radius', '0.02'); 28 | el.setAttribute('color', Config.KEYBOARD_COLOR); 29 | wrapper.appendChild(el); 30 | 31 | return wrapper; 32 | } 33 | 34 | // ----------------------------------------------------------------------------- 35 | // DRAW MAIN UI 36 | 37 | Draw.mainUI = ()=>{ 38 | var wrapper = document.createElement('a-entity'); 39 | wrapper.setAttribute('position', '0.312 0 0'); 40 | wrapper.setAttribute('data-ui', true); 41 | 42 | var el = document.createElement('a-rounded'); 43 | el.setAttribute('width', '0.840'); 44 | el.setAttribute('height', '0.360'); 45 | el.setAttribute('radius', '0.02'); 46 | el.setAttribute('color', Config.KEYBOARD_COLOR); 47 | wrapper.appendChild(el); 48 | 49 | return wrapper; 50 | } 51 | 52 | // ----------------------------------------------------------------------------- 53 | // DRAW ACTION UI 54 | 55 | Draw.actionsUI = ()=> { 56 | var wrapper = document.createElement('a-entity'); 57 | wrapper.setAttribute('position', '1.180 0 0.01'); 58 | wrapper.setAttribute('rotation', '0 -25 0'); 59 | wrapper.setAttribute('data-ui', true); 60 | 61 | var el = document.createElement('a-rounded'); 62 | el.setAttribute('width', '0.180'); 63 | el.setAttribute('height', '0.360'); 64 | el.setAttribute('radius', '0.02'); 65 | el.setAttribute('color', Config.KEYBOARD_COLOR); 66 | wrapper.appendChild(el); 67 | 68 | return wrapper; 69 | } 70 | 71 | // ----------------------------------------------------------------------------- 72 | // DRAW NUMERICAL LAYOUT 73 | 74 | Draw.numericalLayout = ()=> { 75 | var data = Layouts.numerical; 76 | var wrapper = document.createElement('a-entity'); 77 | wrapper.setAttribute('position', '0.02 0.26 0.001'); 78 | 79 | let index_y = 0; 80 | for (var i in data) { 81 | let key_id = 'num-'+i; 82 | let key = Draw.key(key_id, data[i].type, data[i].value); 83 | let index_x = i%3; 84 | let x = Config.KEY_WIDTH*index_x; 85 | let y = Config.KEY_WIDTH*index_y; 86 | key.setAttribute('position', `${x} -${y} 0`); 87 | if (index_x === 2) { index_y++; } 88 | wrapper.appendChild(key); 89 | } 90 | 91 | return wrapper; 92 | } 93 | 94 | // ----------------------------------------------------------------------------- 95 | // DRAW ALPHABETICAL LAYOUT 96 | 97 | Draw.alphabeticalLayout = ()=> { 98 | var data = Layouts.alphabetical; 99 | var wrapper = document.createElement('a-entity'); 100 | wrapper.setAttribute('position', '0.02 0.26 0.001'); 101 | 102 | let index_y = 0, index_x = 0, prev_was_space = false; 103 | 104 | for (var i in data) { 105 | let key_id = 'main-'+i; 106 | let key = Draw.key(key_id, data[i].type, data[i].value); 107 | 108 | let x = Config.KEY_WIDTH*index_x; 109 | let y = Config.KEY_WIDTH*index_y; 110 | 111 | // Add left padding on the second line 112 | if (index_y === 1) { 113 | x = x + Config.KEY_WIDTH/2; 114 | } 115 | 116 | // Add margin on the key next to the spacebar key 117 | if (prev_was_space) { 118 | x = x + Config.SPACE_KEY_WIDTH - Config.KEY_WIDTH + (0.055*2); 119 | } 120 | 121 | // Add margin to the spacebar key 122 | if (data[i].type === 'spacebar') { 123 | prev_was_space = true; 124 | x = x+0.055; 125 | y = Config.KEY_WIDTH*index_y - 0.01; 126 | } 127 | 128 | key.setAttribute('position', `${x} -${y} 0`); 129 | 130 | if (index_y === 1 && index_x === 8) { 131 | index_x = -1; 132 | index_y++; 133 | } else if (index_x === 9) { 134 | index_x = -1; 135 | index_y++; 136 | } 137 | index_x++; 138 | 139 | wrapper.appendChild(key); 140 | } 141 | 142 | return wrapper; 143 | } 144 | 145 | // ----------------------------------------------------------------------------- 146 | // DRAW SYMBOLS LAYOUT 147 | 148 | Draw.symbolsLayout = ()=> { 149 | var data = Layouts.symbols; 150 | var wrapper = document.createElement('a-entity'); 151 | wrapper.setAttribute('position', '0.02 0.26 0.001'); 152 | 153 | let index_y = 0, index_x = 0, prev_was_space = false; 154 | 155 | for (var i in data) { 156 | 157 | let key_id = 'symbols-'+i; 158 | let key = Draw.key(key_id, data[i].type, data[i].value); 159 | let x = Config.KEY_WIDTH*index_x; 160 | let y = Config.KEY_WIDTH*index_y; 161 | 162 | // Add margin on the key next to the spacebar key 163 | if (prev_was_space) { 164 | x = x + Config.SPACE_KEY_WIDTH - Config.KEY_WIDTH + (0.055*2); 165 | } 166 | 167 | // Add margin to the spacebar key 168 | if (data[i].type === 'spacebar') { 169 | prev_was_space = true; 170 | x = x+0.055; 171 | y = Config.KEY_WIDTH*index_y - 0.01; 172 | } 173 | 174 | key.setAttribute('position', `${x} -${y} 0`); 175 | 176 | if (index_x === 9) { 177 | index_x = -1; 178 | index_y++; 179 | } 180 | index_x++; 181 | wrapper.appendChild(key); 182 | } 183 | 184 | return wrapper; 185 | } 186 | 187 | // ----------------------------------------------------------------------------- 188 | // DRAW ACTIONS LAYOUT 189 | 190 | Draw.actionsLayout = ()=> { 191 | var data = Layouts.actions; 192 | var wrapper = document.createElement('a-entity'); 193 | wrapper.setAttribute('position', '0.02 0.26 0.001'); 194 | 195 | let val_y = 0; 196 | for (var i in data) { 197 | let key_id = 'action-'+i; 198 | let key = Draw.key(key_id, data[i].type, data[i].value); 199 | 200 | key.setAttribute('position', `0 -${val_y} 0`); 201 | if (i == 0) { 202 | val_y += Config.ACTION_WIDTH+0.01; 203 | } 204 | else if (i == 1) { 205 | val_y += Config.KEY_WIDTH+0.01; 206 | } 207 | wrapper.appendChild(key); 208 | } 209 | 210 | return wrapper; 211 | } 212 | 213 | // ----------------------------------------------------------------------------- 214 | // DRAW KEY 215 | 216 | Draw.key = (id, type, value)=> { 217 | var that = this; 218 | 219 | var el = document.createElement('a-rounded'); 220 | el.setAttribute('key-id', id); 221 | el.setAttribute('width', Config.KEY_WIDTH); 222 | el.setAttribute('height', Config.KEY_WIDTH); 223 | el.setAttribute('radius', '0.008'); 224 | el.setAttribute('position', '0 0 0'); 225 | el.setAttribute('key-type', type); 226 | el.setAttribute('key-value', value); 227 | el.setAttribute('color', Config.KEYBOARD_COLOR); 228 | 229 | // --------------------------------------------------------------------------- 230 | // EVENTS 231 | 232 | Behaviors.addKeyEvents(el); 233 | 234 | // --------------------------------------------------------------------------- 235 | // SHADOW 236 | 237 | el.shadow_el = document.createElement('a-image'); 238 | el.shadow_el.setAttribute('width', Config.KEY_WIDTH*1.25); 239 | el.shadow_el.setAttribute('height', Config.KEY_WIDTH*1.25); 240 | el.shadow_el.setAttribute('position', Config.KEY_WIDTH/2+' '+Config.KEY_WIDTH/2+' -0.002'); 241 | el.shadow_el.setAttribute('src', Assets.aframeKeyboardShadow); 242 | el.appendChild(el.shadow_el); 243 | 244 | // --------------------------------------------------------------------------- 245 | // TEXT KEY 246 | 247 | if (type === 'text' || type === 'spacebar' || type === 'symbol') { 248 | var letter_el = document.createElement('a-text'); 249 | letter_el.setAttribute('value', value); 250 | letter_el.setAttribute('color', '#dbddde'); 251 | letter_el.setAttribute('position', Config.KEY_WIDTH/2+' '+Config.KEY_WIDTH/2+' 0.01'); 252 | letter_el.setAttribute('scale', '0.16 0.16 0.16'); 253 | letter_el.setAttribute('align', 'center'); 254 | letter_el.setAttribute('baseline', 'center'); 255 | el.appendChild(letter_el); 256 | } 257 | 258 | // --------------------------------------------------------------------------- 259 | // SPACEBAR KEY 260 | 261 | if (type === 'spacebar') { 262 | el.setAttribute('width', Config.SPACE_KEY_WIDTH); 263 | el.setAttribute('height', Config.SPACE_KEY_HEIGHT); 264 | el.setAttribute('color', '#404b50'); 265 | el.shadow_el.setAttribute('width', Config.SPACE_KEY_WIDTH*1.12); 266 | el.shadow_el.setAttribute('height', Config.SPACE_KEY_HEIGHT*1.2); 267 | el.shadow_el.setAttribute('position', Config.SPACE_KEY_WIDTH/2+' '+Config.SPACE_KEY_HEIGHT/2+' -0.02'); 268 | letter_el.setAttribute('color', '#adb1b3'); 269 | letter_el.setAttribute('scale', '0.12 0.12 0.12'); 270 | letter_el.setAttribute('position', Config.SPACE_KEY_WIDTH/2+' '+Config.SPACE_KEY_HEIGHT/2+' 0'); 271 | } 272 | 273 | // --------------------------------------------------------------------------- 274 | // SYMBOL KEY 275 | 276 | else if (type === 'symbol') { 277 | letter_el.setAttribute('scale', '0.12 0.12 0.12'); 278 | } 279 | 280 | // --------------------------------------------------------------------------- 281 | // ACTION KEY 282 | 283 | if (type === 'backspace' || type === 'enter' || type === 'dismiss') { 284 | el.setAttribute('width', Config.ACTION_WIDTH); 285 | el.shadow_el.setAttribute('width', Config.ACTION_WIDTH*1.25); 286 | el.shadow_el.setAttribute('position', Config.ACTION_WIDTH/2+' '+Config.KEY_WIDTH/2+' -0.02'); 287 | } 288 | 289 | // --------------------------------------------------------------------------- 290 | // SHIFT KEY 291 | 292 | if (type === 'shift') { 293 | var icon_el = document.createElement('a-image'); 294 | icon_el.setAttribute('data-type', 'icon'); 295 | icon_el.setAttribute('width', '0.032'); 296 | icon_el.setAttribute('height', '0.032'); 297 | icon_el.setAttribute('position', '0.04 0.04 0.01') 298 | icon_el.setAttribute('src', Assets.aframeKeyboardShift); 299 | el.appendChild(icon_el); 300 | Draw.el.shiftKey = el; 301 | } 302 | 303 | // --------------------------------------------------------------------------- 304 | // GLOBAL 305 | 306 | else if (type === 'global') { 307 | var icon_el = document.createElement('a-image'); 308 | icon_el.setAttribute('width', '0.032'); 309 | icon_el.setAttribute('height', '0.032'); 310 | icon_el.setAttribute('position', '0.04 0.04 0.01') 311 | icon_el.setAttribute('src', Assets.aframeKeyboardGlobal); 312 | el.appendChild(icon_el); 313 | } 314 | 315 | // --------------------------------------------------------------------------- 316 | // BACKSPACE 317 | 318 | else if (type === 'backspace') { 319 | var icon_el = document.createElement('a-image'); 320 | icon_el.setAttribute('width', '0.046'); 321 | icon_el.setAttribute('height', '0.046'); 322 | icon_el.setAttribute('position', '0.07 0.04 0.01') 323 | icon_el.setAttribute('src', Assets.aframeKeyboardBackspace); 324 | el.appendChild(icon_el); 325 | } 326 | 327 | // --------------------------------------------------------------------------- 328 | // ENTER 329 | 330 | else if (type === 'enter') { 331 | el.setAttribute('height', Config.ACTION_WIDTH); 332 | el.shadow_el.setAttribute('height', Config.ACTION_WIDTH*1.25); 333 | el.shadow_el.setAttribute('position', Config.ACTION_WIDTH/2+' '+Config.ACTION_WIDTH/2+' -0.02'); 334 | 335 | var circle_el = document.createElement('a-circle'); 336 | circle_el.setAttribute('color', '#4285f4'); 337 | circle_el.setAttribute('radius', 0.044); 338 | circle_el.setAttribute('position', '0.07 0.07 0.01') 339 | el.appendChild(circle_el); 340 | 341 | var icon_el = document.createElement('a-image'); 342 | icon_el.setAttribute('width', '0.034'); 343 | icon_el.setAttribute('height', '0.034'); 344 | icon_el.setAttribute('position', '0.07 0.07 0.011') 345 | icon_el.setAttribute('src', Assets.aframeKeyboardEnter); 346 | el.appendChild(icon_el); 347 | } 348 | 349 | // --------------------------------------------------------------------------- 350 | // DISMISS 351 | 352 | else if (type === 'dismiss') { 353 | var icon_el = document.createElement('a-image'); 354 | icon_el.setAttribute('width', '0.046'); 355 | icon_el.setAttribute('height', '0.046'); 356 | icon_el.setAttribute('position', '0.07 0.04 0.01') 357 | icon_el.setAttribute('src', Assets.aframeKeyboardDismiss); 358 | el.appendChild(icon_el); 359 | } 360 | 361 | return el; 362 | } 363 | 364 | module.exports = Draw; 365 | -------------------------------------------------------------------------------- /src/keyboard/index.js: -------------------------------------------------------------------------------- 1 | const Utils = require('../utils'); 2 | const Draw = require('./draw'); 3 | const Behaviors = require('./behaviors'); 4 | const SFX = require('./sfx'); 5 | const Event = require('../core/event'); 6 | 7 | AFRAME.registerComponent('keyboard', { 8 | schema: { 9 | isOpen: { type: "boolean", default: false }, 10 | physicalKeyboard: { type: "boolean", default: false } 11 | }, 12 | currentInput: null, 13 | init: function () { 14 | let that = this; 15 | 16 | // SFX 17 | SFX.init(this.el); 18 | 19 | // Draw 20 | Draw.init( this.el ); 21 | 22 | // Init keyboard UI 23 | let numericalUI = Draw.numericalUI(), 24 | mainUI = Draw.mainUI(), 25 | actionsUI = Draw.actionsUI(); 26 | 27 | // Create layout 28 | this.el.alphabeticalLayout = Draw.alphabeticalLayout(); 29 | this.el.symbolsLayout = Draw.symbolsLayout(); 30 | 31 | // Append layouts to UI 32 | numericalUI.appendChild( Draw.numericalLayout() ); 33 | mainUI.appendChild( this.el.alphabeticalLayout ); 34 | actionsUI.appendChild( Draw.actionsLayout() ); 35 | 36 | this.el.appendChild( numericalUI ); 37 | this.el.appendChild( mainUI ); 38 | this.el.appendChild( actionsUI ); 39 | 40 | // Inject methods in elements.. 41 | this.el.show = function() { Behaviors.showKeyboard(that.el); } 42 | this.el.hide = function() { Behaviors.hideKeyboard(that.el); } 43 | this.el.open = function() { Behaviors.openKeyboard(that.el); } 44 | this.el.dismiss = function() { Behaviors.dismissKeyboard(that.el); } 45 | this.el.destroy = function() { Behaviors.destroyKeyboard(that.el); } 46 | 47 | // Set default value 48 | this.el.setAttribute("scale", "2 2 2"); 49 | this.el.setAttribute("rotation", "-20 0 0"); 50 | this.el.setAttribute("position", "-1.5 -0.3 -2"); 51 | 52 | // Register keyboard events 53 | this.el.addEventListener('input', this.inputEvent.bind(this)); 54 | this.el.addEventListener('backspace', this.backspaceEvent.bind(this)); 55 | this.el.addEventListener('dismiss', this.dismissEvent.bind(this)); 56 | 57 | // Register global events 58 | document.addEventListener('keydown', this.keydownEvent.bind(this)); 59 | document.body.addEventListener('didfocusinput', this.didFocusInputEvent.bind(this)); 60 | document.body.addEventListener('didblurinput', this.didBlurInputEvent.bind(this)); 61 | }, 62 | update: function () { 63 | if (this.data.isOpen) { 64 | Behaviors.showKeyboard(this.el); 65 | } else { 66 | Behaviors.hideKeyboard(this.el); 67 | } 68 | }, 69 | tick: function () {}, 70 | remove: function () { 71 | this.el.removeEventListener('input', this.inputEvent.bind(this)); 72 | this.el.removeEventListener('backspace', this.backspaceEvent.bind(this)); 73 | this.el.removeEventListener('dismiss', this.dismissEvent.bind(this)); 74 | 75 | document.removeEventListener('keydown', this.keydownEvent.bind(this)); 76 | document.body.removeEventListener('didfocusinput', this.didFocusInputEvent.bind(this)); 77 | document.body.removeEventListener('didblurinput', this.didBlurInputEvent.bind(this)); 78 | }, 79 | pause: function () {}, 80 | play: function () {}, 81 | 82 | // Fired on keyboard key press 83 | inputEvent: function(e) { 84 | if (this.currentInput) { 85 | this.currentInput.appendString(e.detail); 86 | } 87 | }, 88 | 89 | // Fired on backspace key press 90 | backspaceEvent: function(e){ 91 | if (this.currentInput) { 92 | this.currentInput.deleteLast(); 93 | } 94 | }, 95 | 96 | dismissEvent: function(e){ 97 | if (this.currentInput) { 98 | this.currentInput.blur(); 99 | } 100 | }, 101 | 102 | // physical keyboard event 103 | keydownEvent: function(e) { 104 | if (this.currentInput && this.data.physicalKeyboard) { 105 | e.preventDefault(); 106 | e.stopPropagation(); 107 | 108 | if (e.key === 'Enter') { 109 | Event.emit(Behaviors.el, 'input', '\n'); 110 | Event.emit(Behaviors.el, 'enter', '\n'); 111 | } 112 | else if (e.key === 'Backspace') { 113 | Event.emit(Behaviors.el, 'backspace'); 114 | } 115 | else if (e.key === 'Escape') { 116 | Event.emit(Behaviors.el, 'dismiss'); 117 | } 118 | else if (e.key.length < 2) { 119 | Event.emit(Behaviors.el, 'input', e.key); 120 | } 121 | } 122 | }, 123 | 124 | // Fired when an input has been selected 125 | didFocusInputEvent: function(e) { 126 | if (this.currentInput) { 127 | this.currentInput.blur(true); 128 | } 129 | this.currentInput = e.detail; 130 | if (!this.el.isOpen) { 131 | Behaviors.openKeyboard(this.el); 132 | } 133 | }, 134 | 135 | // Fired when an input has been deselected 136 | didBlurInputEvent: function(e) { 137 | this.currentInput = null; 138 | Behaviors.dismissKeyboard(this.el); 139 | } 140 | }); 141 | 142 | AFRAME.registerPrimitive('a-keyboard', { 143 | defaultComponents: { 144 | keyboard: {} 145 | }, 146 | mappings: { 147 | 'is-open': 'keyboard.isOpen', 148 | 'physical-keyboard': 'keyboard.physicalKeyboard', 149 | } 150 | }); 151 | -------------------------------------------------------------------------------- /src/keyboard/layouts.js: -------------------------------------------------------------------------------- 1 | const Layouts = { 2 | numerical: [ 3 | { type: 'text', value: '1'}, 4 | { type: 'text', value: '2'}, 5 | { type: 'text', value: '3'}, 6 | { type: 'text', value: '4'}, 7 | { type: 'text', value: '5'}, 8 | { type: 'text', value: '6'}, 9 | { type: 'text', value: '7'}, 10 | { type: 'text', value: '8'}, 11 | { type: 'text', value: '9'}, 12 | { type: 'text', value: '.'}, 13 | { type: 'text', value: '0'}, 14 | { type: 'text', value: '-'} 15 | ], 16 | 17 | alphabetical: [ 18 | { type: 'text', value: 'q'}, 19 | { type: 'text', value: 'w'}, 20 | { type: 'text', value: 'e'}, 21 | { type: 'text', value: 'r'}, 22 | { type: 'text', value: 't'}, 23 | { type: 'text', value: 'y'}, 24 | { type: 'text', value: 'u'}, 25 | { type: 'text', value: 'i'}, 26 | { type: 'text', value: 'o'}, 27 | { type: 'text', value: 'p'}, 28 | 29 | { type: 'text', value: 'a'}, 30 | { type: 'text', value: 's'}, 31 | { type: 'text', value: 'd'}, 32 | { type: 'text', value: 'f'}, 33 | { type: 'text', value: 'g'}, 34 | { type: 'text', value: 'h'}, 35 | { type: 'text', value: 'j'}, 36 | { type: 'text', value: 'k'}, 37 | { type: 'text', value: 'l'}, 38 | 39 | { type: 'shift'}, 40 | { type: 'text', value: 'z'}, 41 | { type: 'text', value: 'x'}, 42 | { type: 'text', value: 'c'}, 43 | { type: 'text', value: 'v'}, 44 | { type: 'text', value: 'b'}, 45 | { type: 'text', value: 'n'}, 46 | { type: 'text', value: 'm'}, 47 | { type: 'text', value: '!'}, 48 | { type: 'text', value: '?'}, 49 | 50 | { type: 'symbol', value: '#+='}, 51 | { type: 'text', value: '@'}, 52 | { type: 'spacebar', value: ''}, 53 | { type: 'text', value: ','}, 54 | { type: 'text', value: '.'} 55 | ], 56 | 57 | symbols: [ 58 | { type: 'text', value: '@'}, 59 | { type: 'text', value: '#'}, 60 | { type: 'text', value: '$'}, 61 | { type: 'text', value: '%'}, 62 | { type: 'text', value: '&'}, 63 | { type: 'text', value: '*'}, 64 | { type: 'text', value: '-'}, 65 | { type: 'text', value: '+'}, 66 | { type: 'text', value: '('}, 67 | { type: 'text', value: ')'}, 68 | 69 | { type: 'text', value: '~'}, 70 | { type: 'text', value: '`'}, 71 | { type: 'text', value: '"'}, 72 | { type: 'text', value: '\''}, 73 | { type: 'text', value: ':'}, 74 | { type: 'text', value: ';'}, 75 | { type: 'text', value: '_'}, 76 | { type: 'text', value: '='}, 77 | { type: 'text', value: '\\'}, 78 | { type: 'text', value: '/'}, 79 | 80 | { type: 'text', value: '{'}, 81 | { type: 'text', value: '}'}, 82 | { type: 'text', value: '['}, 83 | { type: 'text', value: ']'}, 84 | { type: 'text', value: '<'}, 85 | { type: 'text', value: '>'}, 86 | { type: 'text', value: '^'}, 87 | { type: 'text', value: '|'}, 88 | { type: 'text', value: '!'}, 89 | { type: 'text', value: '?'}, 90 | 91 | { type: 'symbol', value: 'ABC'}, 92 | { type: 'text', value: '@'}, 93 | { type: 'spacebar', value: ''}, 94 | { type: 'text', value: ','}, 95 | { type: 'text', value: '.'} 96 | ], 97 | 98 | actions: [ 99 | { type: 'backspace', value: 'Del'}, 100 | { type: 'enter', value: 'OK'}, 101 | { type: 'dismiss', value: 'W'}, 102 | ] 103 | } 104 | 105 | module.exports = Layouts; 106 | -------------------------------------------------------------------------------- /src/keyboard/sfx.js: -------------------------------------------------------------------------------- 1 | var Assets = require('./Assets'); 2 | 3 | const SFX = { 4 | 5 | init: function(parent) { 6 | let el = document.createElement('a-sound'); 7 | el.setAttribute('key', 'aframeKeyboardKeyInSound'); 8 | el.setAttribute('sfx', true); 9 | el.setAttribute('src', Assets.aframeKeyboardKeyIn); 10 | el.setAttribute('position', '0 2 5'); 11 | parent.appendChild(el); 12 | 13 | el = document.createElement('a-sound'); 14 | el.setAttribute('key', 'aframeKeyboardKeyDownSound'); 15 | el.setAttribute('sfx', true); 16 | el.setAttribute('src', Assets.aframeKeyboardKeyDown); 17 | el.setAttribute('position', '0 2 5'); 18 | parent.appendChild(el); 19 | }, 20 | 21 | keyIn: function(parent) { 22 | let el = parent.querySelector('[key=aframeKeyboardKeyInSound]'); 23 | if (!el) { return; } 24 | el.components.sound.stopSound(); 25 | el.components.sound.playSound(); 26 | }, 27 | 28 | keyDown: function(parent) { 29 | let el = parent.querySelector('[key=aframeKeyboardKeyDownSound]'); 30 | if (!el) { return; } 31 | el.components.sound.stopSound(); 32 | el.components.sound.playSound(); 33 | } 34 | } 35 | 36 | module.exports = SFX; 37 | -------------------------------------------------------------------------------- /src/radio/assets.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { type: 'audio', id: 'aframeRadioClick', src: `${AFRAME.ASSETS_PATH}/sounds/InputClick.mp3`}, 3 | { type: 'audio', id: 'aframeRadioClickDisabled', src: `${AFRAME.ASSETS_PATH}/sounds/ButtonClickDisabled.mp3`} 4 | ] 5 | -------------------------------------------------------------------------------- /src/radio/index.js: -------------------------------------------------------------------------------- 1 | const Utils = require('../utils'); 2 | const Event = require('../core/event'); 3 | const Assets = require('./assets'); 4 | const SFX = require('./sfx'); 5 | 6 | AFRAME.registerComponent('radio', { 7 | schema: { 8 | checked: { type: 'boolean', default: false }, 9 | disabled: { type: 'boolean', default: false }, 10 | name: { type: "string", default: "" }, 11 | value: { type: "string", default: "" }, 12 | label: { type: "string", default: "" }, 13 | radioColor: { type: "color", default: "#757575"}, 14 | radioColorChecked: { type: "color", default: "#4076fd"}, 15 | color: { type: "color", default: "#757575" }, 16 | font: { type: "string", default: "" }, 17 | letterSpacing: { type: "int", default: 0 }, 18 | lineHeight: { type: "string", default: "" }, 19 | opacity: { type: "number", default: 1 }, 20 | width: { type: "number", default: 1 } 21 | }, 22 | init: function () { 23 | var that = this; 24 | 25 | // Assets 26 | Utils.preloadAssets( Assets ); 27 | 28 | // SFX 29 | SFX.init(this.el); 30 | 31 | // HITBOX 32 | this.hitbox = document.createElement('a-plane'); 33 | this.hitbox.setAttribute('height', 0.2); 34 | this.hitbox.setAttribute('opacity', 0); 35 | this.hitbox.setAttribute('position', '0 0 0.001') 36 | this.el.appendChild(this.hitbox); 37 | 38 | // OUTLINE 39 | this.outline = document.createElement('a-ring'); 40 | this.outline.setAttribute('radius-outer', 0.1) 41 | this.outline.setAttribute('radius-inner', 0.078); 42 | this.outline.setAttribute('position', '0.1 0 0.002'); 43 | this.el.appendChild(this.outline); 44 | 45 | // CIRCLE 46 | this.circle = document.createElement('a-circle'); 47 | this.circle.setAttribute('radius', 0.05) 48 | this.circle.setAttribute('position', '0.1 0 0.002'); 49 | this.el.appendChild(this.circle); 50 | 51 | // LABEL 52 | this.label = document.createElement('a-entity'); 53 | this.el.appendChild(this.label); 54 | 55 | // EVENTS 56 | this.el.addEventListener('click', function() { 57 | if (this.components.radio.data.disabled) { return; } 58 | this.setAttribute('checked', true); 59 | that.onClick(); 60 | }); 61 | this.el.addEventListener('mousedown', function() { 62 | if (this.components.radio.data.disabled) { 63 | return SFX.clickDisabled(this); 64 | } 65 | SFX.click(this); 66 | }); 67 | 68 | Object.defineProperty(this.el, 'value', { 69 | get: function() { return this.getAttribute('value'); }, 70 | set: function(value) { this.setAttribute('value', value); }, 71 | enumerable: true, 72 | configurable: true 73 | }); 74 | }, 75 | onClick: function(noemit) { 76 | if (this.data.name) { 77 | let nearestForm = this.el.closest("a-form"); 78 | if (nearestForm) { 79 | let didCheck = false; 80 | let children = Array.from(nearestForm.querySelectorAll(`[name=${this.data.name}]`)); 81 | children.reverse(); 82 | for (let child of children) { 83 | // Radio + not disabled 84 | if (child.components.radio ) { 85 | // Currently checked 86 | if (child === this.el && child.hasAttribute('checked')) { 87 | didCheck = true; 88 | child.components.radio.check(); 89 | if (!noemit) { Event.emit(child, 'change', true); } 90 | } else { 91 | if (!didCheck && !this.data.checked && child.hasAttribute('checked')) { 92 | didCheck = true; 93 | child.components.radio.check(); 94 | } else { 95 | child.components.radio.uncheck(); 96 | } 97 | } 98 | } 99 | } 100 | if (!didCheck && this.el.hasAttribute('checked')) { 101 | this.check(); 102 | if (!noemit) { Event.emit(this.el, 'change', true); } 103 | } 104 | } 105 | } 106 | }, 107 | check: function() { 108 | this.outline.setAttribute('color', this.data.radioColorChecked); 109 | this.circle.setAttribute('color', this.data.radioColorChecked); 110 | this.circle.setAttribute('visible', true); 111 | if (this.data.disabled) { this.disabled(); } 112 | }, 113 | uncheck: function() { 114 | this.outline.setAttribute('color', this.data.radioColor); 115 | this.circle.setAttribute('visible', false); 116 | if (this.data.disabled) { this.disabled(); } 117 | }, 118 | disabled: function() { 119 | this.outline.setAttribute('color', this.data.radioColor); 120 | this.circle.setAttribute('color', this.data.radioColor); 121 | }, 122 | update: function () { 123 | var that = this; 124 | this.onClick(true); 125 | 126 | // HITBOX 127 | this.hitbox.setAttribute('width', this.data.width) 128 | this.hitbox.setAttribute('position', this.data.width/2+' 0 0.001'); 129 | 130 | let props = { 131 | color: this.data.color, 132 | align: 'left', 133 | wrapCount: 10*(this.data.width+0.2), 134 | width: this.data.width, 135 | } 136 | if (this.data.font) { props.font = this.data.font; } 137 | 138 | // TITLE 139 | props.value = this.data.label; 140 | props.color = this.data.color; 141 | this.label.setAttribute('text', props); 142 | this.label.setAttribute('position', this.data.width/2+0.24+' 0 0.002'); 143 | 144 | // TRIM TEXT IF NEEDED.. @TODO: optimize this mess.. 145 | function getTextWidth(el, _widthFactor) { 146 | if (!el.object3D || !el.object3D.children || !el.object3D.children[0]) { return 0; } 147 | let v = el.object3D.children[0].geometry.visibleGlyphs; 148 | if (!v) { return 0; } 149 | v = v[v.length-1]; 150 | if (!v) { return 0; } 151 | if (v.line) { 152 | props.value = props.value.slice(0, -1); 153 | el.setAttribute("text", props); 154 | return getTextWidth(el); 155 | } else { 156 | if (!_widthFactor) { _widthFactor = Utils.getWidthFactor(el, props.wrapCount); } 157 | v = (v.position[0] + v.data.width) / (_widthFactor/that.data.width); 158 | let textRatio = v / that.data.width; 159 | if (textRatio > 1) { 160 | props.value = props.value.slice(0, -1); 161 | el.setAttribute("text", props); 162 | return getTextWidth(el, _widthFactor); 163 | } 164 | } 165 | return v; 166 | } 167 | setTimeout(function() { 168 | if (that.data.label.length) { 169 | getTextWidth(that.label); 170 | } 171 | if (that.data.disabled) { 172 | let timer = setInterval(function() { 173 | if (that.outline.object3D.children[0]) { 174 | clearInterval(timer); 175 | Utils.updateOpacity(that.outline, 0.4); 176 | Utils.updateOpacity(that.circle, 0.4); 177 | Utils.updateOpacity(that.label, 0.4); 178 | } 179 | }, 10) 180 | } else { 181 | let timer = setInterval(function() { 182 | if (that.outline.object3D.children[0]) { 183 | clearInterval(timer); 184 | Utils.updateOpacity(that.outline, 1); 185 | Utils.updateOpacity(that.circle, 1); 186 | Utils.updateOpacity(that.label, 1); 187 | } 188 | }, 10) 189 | } 190 | }, 0); 191 | }, 192 | tick: function () {}, 193 | remove: function () {}, 194 | pause: function () {}, 195 | play: function () {} 196 | }); 197 | 198 | AFRAME.registerPrimitive('a-radio', { 199 | defaultComponents: { 200 | radio: {} 201 | }, 202 | mappings: { 203 | checked: 'radio.checked', 204 | disabled: 'radio.disabled', 205 | name: 'radio.name', 206 | value: 'radio.value', 207 | label: 'radio.label', 208 | 'radio-color': 'radio.radioColor', 209 | 'radio-color-checked': 'radio.radioColorChecked', 210 | color: 'radio.color', 211 | align: 'radio.align', 212 | font: 'radio.font', 213 | 'letter-spacing': 'radio.letterSpacing', 214 | 'line-height': 'radio.lineHeight', 215 | 'opacity': 'radio.opacity', 216 | width: 'radio.width' 217 | } 218 | }); 219 | -------------------------------------------------------------------------------- /src/radio/sfx.js: -------------------------------------------------------------------------------- 1 | const SFX = { 2 | 3 | init: function(parent) { 4 | let el = document.createElement('a-sound'); 5 | el.setAttribute('key', 'aframeRadioClickSound'); 6 | el.setAttribute('sfx', true); 7 | el.setAttribute('src', '#aframeRadioClick'); 8 | el.setAttribute('position', '0 2 5'); 9 | parent.appendChild(el); 10 | 11 | el = document.createElement('a-sound'); 12 | el.setAttribute('key', 'aframeRadioClickDisabledSound'); 13 | el.setAttribute('sfx', true); 14 | el.setAttribute('src', '#aframeRadioClickDisabled'); 15 | el.setAttribute('position', '0 2 5'); 16 | parent.appendChild(el); 17 | }, 18 | 19 | click: function(parent) { 20 | let el = parent.querySelector('[key=aframeRadioClickSound]'); 21 | if (!el) { return; } 22 | el.components.sound.stopSound(); 23 | el.components.sound.playSound(); 24 | }, 25 | 26 | clickDisabled: function(parent) { 27 | let el = parent.querySelector('[key=aframeRadioClickDisabledSound]'); 28 | if (!el) { return; } 29 | el.components.sound.stopSound(); 30 | el.components.sound.playSound(); 31 | } 32 | } 33 | 34 | module.exports = SFX; 35 | -------------------------------------------------------------------------------- /src/switch/assets.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { type: 'img', id: 'aframeSwitchShadow', src: `${AFRAME.ASSETS_PATH}/images/SwitchShadow.png`}, 3 | { type: 'audio', id: 'aframeSwitchClick', src: `${AFRAME.ASSETS_PATH}/sounds/InputClick.mp3`}, 4 | { type: 'audio', id: 'aframeSwitchClickDisabled', src: `${AFRAME.ASSETS_PATH}/sounds/ButtonClickDisabled.mp3`} 5 | ] 6 | -------------------------------------------------------------------------------- /src/switch/index.js: -------------------------------------------------------------------------------- 1 | const Utils = require('../utils'); 2 | const Event = require('../core/event'); 3 | const Assets = require('./assets'); 4 | const SFX = require('./sfx'); 5 | 6 | AFRAME.registerComponent('switch', { 7 | schema: { 8 | name: { type: "string", default: "" }, 9 | enabled: { type: 'boolean', default: false }, 10 | disabled: { type: 'boolean', default: false }, 11 | fillColor: { type: "color", default: "#bababa" }, 12 | knobColor: { type: "color", default: "#f5f5f5" }, 13 | fillColorEnabled: { type: "color", default: "#80a8ff" }, 14 | knobColorEnabled: { type: "color", default: "#4076fd" }, 15 | fillColorDisabled: { type: "color", default: "#939393" }, 16 | knobColorDisabled: { type: "color", default: "#a2a2a2" } 17 | }, 18 | init: function () { 19 | var that = this; 20 | 21 | // Assets 22 | Utils.preloadAssets( Assets ); 23 | 24 | // SFX 25 | SFX.init(this.el); 26 | 27 | // FILL 28 | this.el.fill = document.createElement('a-rounded'); 29 | this.el.fill.setAttribute('width', 0.36) 30 | this.el.fill.setAttribute('height', 0.16) 31 | this.el.fill.setAttribute('radius', 0.08) 32 | this.el.fill.setAttribute('side', 'double') 33 | this.el.fill.setAttribute('position', '0 0 0.01') 34 | this.el.appendChild(this.el.fill); 35 | 36 | // KNOB 37 | this.el.knob = document.createElement('a-circle'); 38 | this.el.knob.setAttribute('position', '0.06 0.08 0.02') 39 | this.el.knob.setAttribute('radius', 0.12) 40 | this.el.knob.setAttribute('side', 'double') 41 | this.el.appendChild(this.el.knob); 42 | 43 | // SHADOW 44 | this.el.shadow_el = document.createElement('a-image'); 45 | this.el.shadow_el.setAttribute('width', 0.24*1.25); 46 | this.el.shadow_el.setAttribute('height', 0.24*1.25); 47 | this.el.shadow_el.setAttribute('position', '0 0 -0.001'); 48 | this.el.shadow_el.setAttribute('src', '#aframeSwitchShadow'); 49 | this.el.knob.appendChild(this.el.shadow_el); 50 | 51 | this.el.addEventListener('click', function() { 52 | if (this.components.switch.data.disabled) { return; } 53 | this.setAttribute('enabled', !this.components.switch.data.enabled ); 54 | Event.emit(this, 'change', this.components.switch.data.enabled); 55 | }); 56 | this.el.addEventListener('mousedown', function() { 57 | if (this.components.switch.data.disabled) { 58 | return SFX.clickDisabled(this); 59 | } 60 | SFX.click(this); 61 | }); 62 | 63 | Object.defineProperty(this.el, 'enabled', { 64 | get: function() { return this.getAttribute('enabled'); }, 65 | set: function(value) { this.setAttribute('enabled', value); }, 66 | enumerable: true, 67 | configurable: true 68 | }); 69 | }, 70 | on: function() { 71 | this.el.fill.setAttribute('color', this.data.fillColorEnabled) 72 | this.el.knob.setAttribute('position', '0.32 0.08 0.02'); 73 | this.el.knob.setAttribute('color', this.data.knobColorEnabled) 74 | }, 75 | off: function() { 76 | this.el.fill.setAttribute('color', this.data.fillColor) 77 | this.el.knob.setAttribute('position', '0.06 0.08 0.02'); 78 | this.el.knob.setAttribute('color', this.data.knobColor) 79 | }, 80 | disable: function() { 81 | this.el.fill.setAttribute('color', this.data.fillColorDisabled) 82 | this.el.knob.setAttribute('color', this.data.knobColorDisabled) 83 | }, 84 | update: function () { 85 | if (this.data.enabled) { 86 | this.on(); 87 | } else { 88 | this.off(); 89 | } 90 | if (this.data.disabled) { 91 | this.disable(); 92 | } 93 | }, 94 | tick: function () {}, 95 | remove: function () {}, 96 | pause: function () {}, 97 | play: function () {} 98 | }); 99 | 100 | AFRAME.registerPrimitive('a-switch', { 101 | defaultComponents: { 102 | switch: {} 103 | }, 104 | mappings: { 105 | name: 'switch.name', 106 | enabled: 'switch.enabled', 107 | disabled: 'switch.disabled', 108 | 'fill-color': 'switch.fillColor', 109 | 'knob-color': 'switch.knobColor', 110 | 'fill-color-enabled': 'switch.fillColorEnabled', 111 | 'knob-color-enabled': 'switch.knobColorEnabled', 112 | 'fill-color-disabled': 'switch.fillColorDisabled', 113 | 'knob-color-disabled': 'switch.knobColorDisabled' 114 | } 115 | }); 116 | -------------------------------------------------------------------------------- /src/switch/sfx.js: -------------------------------------------------------------------------------- 1 | const SFX = { 2 | 3 | init: function(parent) { 4 | let el = document.createElement('a-sound'); 5 | el.setAttribute('key', 'aframeSwitchClickSound'); 6 | el.setAttribute('sfx', true); 7 | el.setAttribute('src', '#aframeSwitchClick'); 8 | el.setAttribute('position', '0 2 5'); 9 | parent.appendChild(el); 10 | 11 | el = document.createElement('a-sound'); 12 | el.setAttribute('key', 'aframeSwitchClickDisabledSound'); 13 | el.setAttribute('sfx', true); 14 | el.setAttribute('src', '#aframeSwitchClickDisabled'); 15 | el.setAttribute('position', '0 2 5'); 16 | parent.appendChild(el); 17 | }, 18 | 19 | click: function(parent) { 20 | let el = parent.querySelector('[key=aframeSwitchClickSound]'); 21 | if (!el) { return; } 22 | el.components.sound.stopSound(); 23 | el.components.sound.playSound(); 24 | }, 25 | 26 | clickDisabled: function(parent) { 27 | let el = parent.querySelector('[key=aframeSwitchClickDisabledSound]'); 28 | if (!el) { return; } 29 | el.components.sound.stopSound(); 30 | el.components.sound.playSound(); 31 | } 32 | } 33 | 34 | module.exports = SFX; 35 | -------------------------------------------------------------------------------- /src/toast/assets.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { type: 'audio', id: 'aframeToastShow', src: `${AFRAME.ASSETS_PATH}/sounds/ToastShow.mp3`} 3 | ] 4 | -------------------------------------------------------------------------------- /src/toast/index.js: -------------------------------------------------------------------------------- 1 | const Utils = require('../utils'); 2 | const Event = require('../core/event'); 3 | const Assets = require('./assets'); 4 | const SFX = require('./sfx'); 5 | 6 | AFRAME.registerComponent('toast', { 7 | schema: { 8 | message: { type: 'string', default: "You are cool" }, 9 | action: { type: 'string', default: "" }, 10 | backgroundColor: { type: "color", default: "#222" },//242f35 11 | actionColor: { type: "color", default: "#4076fd"}, 12 | color: { type: "color", default: "#FFF" }, 13 | font: { type: "string", default: "" }, 14 | letterSpacing: { type: "int", default: 0 }, 15 | lineHeight: { type: "string", default: "" }, 16 | width: { type: "number", default: 3 }, 17 | duration: { type: 'number', default: 2000 }, 18 | autoshow: { type: 'boolean', default: true } 19 | }, 20 | init: function () { 21 | var that = this; 22 | 23 | // Assets 24 | Utils.preloadAssets( Assets ); 25 | 26 | // SFX 27 | SFX.init(this.el); 28 | 29 | // CONFIG 30 | this.el.setAttribute("position", `10000 10000 10000`); 31 | this.el.setAttribute("rotation", "-25 0 0"); 32 | this.el.setAttribute("scale", "0.3 0.3 0.3"); 33 | 34 | // OUTLINE 35 | this.background = document.createElement('a-rounded'); 36 | this.background.setAttribute('height', 0.44); 37 | this.background.setAttribute('radius', 0.03); 38 | this.background.setAttribute('position', `0 -${0.36/2} 0.001`); 39 | this.el.appendChild(this.background); 40 | 41 | // LABEL 42 | this.label = document.createElement('a-entity'); 43 | this.el.appendChild(this.label); 44 | 45 | // LABEL 46 | this.action = document.createElement('a-button'); 47 | that.action.setAttribute('button-color', '#222') 48 | this.el.appendChild(this.action); 49 | 50 | function changeWidth(e){ 51 | let attr = that.label.getAttribute('text'); 52 | attr.width = that.data.width-e.detail; 53 | attr.wrapCount = 10*attr.width; 54 | that.label.setAttribute('text', attr); 55 | that.label.setAttribute('position', attr.width/2+0.14+' 0.04 0.001'); 56 | 57 | this.setAttribute('position', `${that.data.width-e.detail} ${(0.44-0.36)/2} 0.001`) 58 | } 59 | this.action.addEventListener('change:width', changeWidth); 60 | this.action.addEventListener('click', function() { 61 | Event.emit(that.el, 'actionclick'); 62 | }); 63 | 64 | let timer = setInterval(function() { 65 | if (that.action.object3D && that.action.object3D.children[0]) { 66 | clearInterval(timer); 67 | Utils.updateOpacity(that.el, 0); 68 | Utils.updateOpacity(that.label, 0); 69 | Utils.updateOpacity(that.action, 0); 70 | if (that.data.autoshow) { that.show(); } 71 | } 72 | }, 10); 73 | 74 | // METHDOS 75 | this.el.show = this.show.bind(this); 76 | this.el.hide = this.hide.bind(this); 77 | }, 78 | show: function() { 79 | if (this.hideTimer) { 80 | clearTimeout(this.hideTimer); 81 | } 82 | this.el.setAttribute("position", `${-this.data.width/(2/this.el.object3D.scale.x)} 0.25 -1.6`); 83 | let that = this; 84 | /*if (!this.el.parentNode && this.el._parentNode) { 85 | this.el._parentNode.appendChild(this.el); 86 | }*/ 87 | setTimeout(function() { 88 | that.el.setAttribute('fadein', {duration: 160}); 89 | setTimeout(function() { 90 | Utils.updateOpacity(that.label, 1); 91 | that.action.components.button.shadow.setAttribute('visible', false); 92 | }, 10) 93 | }, 0) 94 | this.hideTimer = setTimeout(function() { 95 | that.hide(); 96 | }, this.data.duration); 97 | 98 | SFX.show(this.el); 99 | }, 100 | hide: function() { 101 | let that = this; 102 | setTimeout(function() { 103 | Utils.updateOpacity(that.label, 0); 104 | that.action.components.button.shadow.setAttribute('visible', false); 105 | setTimeout(function() { 106 | that.el.setAttribute('fadeout', {duration: 160}); 107 | setTimeout(function() { 108 | /*if (that.el.parentNode) { 109 | that.el._parentNode = that.el.parentNode; 110 | that.el.parentNode.removeChild(that.el); 111 | }*/ 112 | that.el.setAttribute("position", `10000 10000 10000`); 113 | }, 200); 114 | }, 10) 115 | }, 0); 116 | }, 117 | update: function () { 118 | var that = this; 119 | 120 | // BACKGROUND 121 | this.background.setAttribute('color', this.data.backgroundColor); 122 | this.background.setAttribute('width', this.data.width); 123 | 124 | let props = { 125 | color: this.data.color, 126 | align: 'left', 127 | wrapCount: 10*this.data.width, 128 | width: this.data.width, 129 | lineHeight: 64 130 | } 131 | if (this.data.font) { props.font = this.data.font; } 132 | 133 | if (this.data.type === "flat") { 134 | props.color = this.data.buttonColor; 135 | } 136 | 137 | // MESSAGE 138 | props.value = this.data.message 139 | this.label.setAttribute('text', props); 140 | this.label.setAttribute('position', this.data.width/2+0.14+' 0 0.001'); 141 | 142 | // ACTION 143 | this.action.setAttribute('value', this.data.action.toUpperCase()); 144 | this.action.setAttribute('color', this.data.actionColor); 145 | }, 146 | tick: function () {}, 147 | remove: function () {}, 148 | pause: function () {}, 149 | play: function () {} 150 | }); 151 | 152 | AFRAME.registerPrimitive('a-toast', { 153 | defaultComponents: { 154 | toast: {} 155 | }, 156 | mappings: { 157 | message: 'toast.message', 158 | action: 'toast.action', 159 | 'action-color': 'toast.actionColor', 160 | 'background-color': 'toast.backgroundColor', 161 | color: 'toast.color', 162 | font: 'toast.font', 163 | 'letter-spacing': 'toast.letterSpacing', 164 | 'line-height': 'toast.lineHeight', 165 | 'width': 'toast.width', 166 | 'duration': 'toast.duration', 167 | 'autoshow': 'toast.autoshow' 168 | } 169 | }); 170 | -------------------------------------------------------------------------------- /src/toast/sfx.js: -------------------------------------------------------------------------------- 1 | const SFX = { 2 | init: function(parent) { 3 | let el = document.createElement('a-sound'); 4 | el.setAttribute('key', 'aframeToastShowSound'); 5 | el.setAttribute('sfx', true); 6 | el.setAttribute('src', '#aframeToastShow'); 7 | el.setAttribute('position', '0 2 5'); 8 | parent.appendChild(el); 9 | }, 10 | 11 | show: function(parent) { 12 | let el = parent.querySelector('[key=aframeToastShowSound]'); 13 | if (!el) { return; } 14 | el.components.sound.stopSound(); 15 | el.components.sound.playSound(); 16 | } 17 | } 18 | 19 | module.exports = SFX; 20 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const Utils = {}; 2 | 3 | /** 4 | Utils.preloadAssets([]) 5 | Add assets to Assets managment system. 6 | */ 7 | Utils.preloadAssets = (assets_arr)=>{ 8 | let assets = document.querySelector('a-assets'), already_exists; 9 | 10 | if (!assets) { 11 | var scene = document.querySelector('a-scene'); 12 | assets = document.createElement('a-assets'); 13 | scene.appendChild(assets); 14 | } 15 | 16 | for (let item of assets_arr) { 17 | already_exists = false; 18 | 19 | /***** With Edge, assets.children is a HTMLCollection, not an Array! *****/ 20 | for (let stuff of Array.from(assets.children)) { 21 | if (item.id === stuff.id) { 22 | already_exists = true; 23 | } 24 | } 25 | 26 | if (!already_exists) { 27 | var asset_item = document.createElement(item.type); 28 | asset_item.setAttribute('id', item.id); 29 | asset_item.setAttribute('src', item.src); 30 | assets.appendChild(asset_item); 31 | } 32 | } 33 | } 34 | 35 | 36 | /** 37 | Utils.extend(a, b) 38 | Assign object to other object. 39 | */ 40 | Utils.extend = function(a, b) { 41 | for (let key in b) { 42 | if (b.hasOwnProperty(key)) { 43 | a[key] = b[key]; 44 | } 45 | } 46 | return a; 47 | }; 48 | 49 | 50 | Utils.clone = function(original) { 51 | if (Array.isArray(original)) { 52 | return original.slice(0); 53 | } 54 | 55 | // First create an empty object with 56 | // same prototype of our original source 57 | const clone = Object.create(Object.getPrototypeOf(original)); 58 | let i = undefined; 59 | const keys = Object.getOwnPropertyNames(original); 60 | i = 0; 61 | while (i < keys.length) { 62 | // copy each property into the clone 63 | Object.defineProperty(clone, keys[i], Object.getOwnPropertyDescriptor(original, keys[i])); 64 | i++; 65 | } 66 | return clone; 67 | }; 68 | 69 | 70 | Utils.updateOpacity = function(el, opacity) { 71 | if (el.hasAttribute('text')) { 72 | let props = el.getAttribute('text'); 73 | if (props) { 74 | props.opacity = opacity; 75 | el.setAttribute('text', props); 76 | } 77 | } 78 | el.object3D.traverse(function (o) { 79 | if (o.material) { 80 | o.material.transparent = true; 81 | o.material.opacity = opacity; 82 | } 83 | }); 84 | for (let text of el.querySelectorAll('a-text')) { 85 | text.setAttribute('opacity', opacity); 86 | } 87 | } 88 | 89 | 90 | // Calculate the width factor 91 | Utils.getWidthFactor = function(el, wrapCount) { 92 | let widthFactor = 0.00001; 93 | if (el.components.text && el.components.text.currentFont) { 94 | widthFactor = el.components.text.currentFont.widthFactor 95 | widthFactor = ((0.5 + wrapCount) * widthFactor); 96 | } 97 | return widthFactor; 98 | } 99 | 100 | module.exports = Utils; 101 | -------------------------------------------------------------------------------- /static/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennepinchon/aframe-material/94088c2cf7bfc8115345edfc0c749495af6334f4/static/screenshot.png -------------------------------------------------------------------------------- /svg/BackspaceIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /svg/CheckmarkIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /svg/DismissIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /svg/EnterIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /svg/GlobalIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /svg/ShiftActiveIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /svg/ShiftIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var childProcess = require('child_process'); 3 | var webpack = require('webpack'); 4 | 5 | // Add HMR for development environments only. 6 | var entry = ['./src/index.js']; 7 | if (process.env.NODE_ENV === 'dev') { 8 | entry = [ 9 | 'webpack-dev-server/client?http://localhost:3333', 10 | 'webpack/hot/only-dev-server' 11 | ].concat(entry); 12 | } 13 | 14 | function getBuildTimestamp () { 15 | function pad2 (value) { 16 | return ('0' + value).slice(-2); 17 | } 18 | var date = new Date(); 19 | var timestamp = [ 20 | pad2(date.getUTCDate()), 21 | pad2(date.getUTCMonth()+1), 22 | date.getUTCFullYear() 23 | ] 24 | return timestamp.join('-'); 25 | } 26 | 27 | var commitHash = childProcess.execSync('git rev-parse HEAD').toString(); 28 | 29 | // Minification. 30 | var plugins = [ 31 | new webpack.DefinePlugin({ 32 | 'process.env':{ 33 | 'NODE_ENV': JSON.stringify(process.env.NODE_ENV) 34 | }, 35 | VERSION: JSON.stringify(require('./package.json').version), 36 | BUILD_TIMESTAMP: JSON.stringify(getBuildTimestamp()), 37 | COMMIT_HASH: JSON.stringify(commitHash) 38 | }), 39 | ]; 40 | if (process.env.NODE_ENV === 'production') { 41 | plugins.push(new webpack.optimize.UglifyJsPlugin({ 42 | compress: {warnings: false} 43 | })); 44 | } 45 | 46 | // dist/ 47 | var filename = 'aframe-material.js'; 48 | var outPath = 'dist'; 49 | if (process.env.NODE_ENV === 'production') { 50 | filename = 'aframe-material.min.js'; 51 | } 52 | 53 | module.exports = { 54 | devServer: {port: 3333}, 55 | entry: entry, 56 | devtool : 'sourcemap', 57 | output: { 58 | path: path.join(__dirname, outPath), 59 | filename: filename, 60 | publicPath: '/dist/' 61 | }, 62 | module: { 63 | loaders: [ 64 | { 65 | test: /\.js?$/, 66 | exclude: /(node_modules|bower_components)/, 67 | loader: 'babel', 68 | query: { 69 | plugins: ['transform-class-properties'], 70 | presets: ['es2015'] 71 | } 72 | } 73 | ] 74 | }, 75 | plugins: plugins 76 | }; 77 | --------------------------------------------------------------------------------