parent
46bb9afe6d
commit
ae98cf9ca0
13 changed files with 277 additions and 184 deletions
|
|
@ -19,7 +19,7 @@ BEGIN{
|
||||||
print ""
|
print ""
|
||||||
}
|
}
|
||||||
|
|
||||||
/\$\(/ { cmd=$0;
|
/\$(?![\(|"])/ { cmd=$0;
|
||||||
gsub(/^.*\$\(/,"",cmd);
|
gsub(/^.*\$\(/,"",cmd);
|
||||||
gsub(/\).*/,"",cmd);
|
gsub(/\).*/,"",cmd);
|
||||||
cmd | getline stdout; close(cmd);
|
cmd | getline stdout; close(cmd);
|
||||||
|
|
|
||||||
80
README.md
80
README.md
|
|
@ -101,7 +101,7 @@ a Linux ISO image (via WASM).
|
||||||
| `height` | `number` | 600 ||
|
| `height` | `number` | 600 ||
|
||||||
| `depth` | `number` | 0.03 ||
|
| `depth` | `number` | 0.03 ||
|
||||||
| `lineHeight` | `number` | 18 ||
|
| `lineHeight` | `number` | 18 ||
|
||||||
| `prompt` | `boolean` | true | boot straight into ISO or give user choice |
|
| `bootmenu` | `boolean` | true | give user choice [or boot straight into ISO ] |
|
||||||
| `padding` | `number`` | 18 | |
|
| `padding` | `number`` | 18 | |
|
||||||
| `maximized` | `boolean` | false | |
|
| `maximized` | `boolean` | false | |
|
||||||
| `minimized` | `boolean` | false | |
|
| `minimized` | `boolean` | false | |
|
||||||
|
|
@ -147,6 +147,54 @@ NOTE: For convenience reasons, events are forwarded between com/isoterminal.js,
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## [launcher](com/launcher.js)
|
||||||
|
|
||||||
|
displays app (icons) for enduser to launch
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
<a-scene launcher/>
|
||||||
|
```
|
||||||
|
|
||||||
|
| property | type | example |
|
||||||
|
|--------------|--------------------|----------------------------------------------------------------------------------------|
|
||||||
|
| `registries` | `array` of strings | `<a-entity launcher="registers: https://foo.com/index.json, ./index.json"/>` |
|
||||||
|
|
||||||
|
| event | target | info |
|
||||||
|
|--------------|-------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `launcher` | an app | when pressing an app icon, `launcher` event will be send to the respective app |
|
||||||
|
|
||||||
|
There a multiple ways of letting the launcher know that an app can be launched:
|
||||||
|
|
||||||
|
1. any AFRAME component with an `launcher`-event + manifest is automatically added:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
AFRAME.registerComponent('foo',{
|
||||||
|
events:{
|
||||||
|
launcher: function(){ ...launch something... }
|
||||||
|
},
|
||||||
|
manifest:{ // HTML5 manifesto JSON object
|
||||||
|
// https://www.w3.org/TR/appmanifest/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. dynamically in javascript
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
window.launcher.register({
|
||||||
|
name:"foo",
|
||||||
|
icon: "https://.../optional_icon.png"
|
||||||
|
description: "lorem ipsum",
|
||||||
|
cb: () => alert("foo")
|
||||||
|
})
|
||||||
|
//window.launcher.unregister('foo')
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Restore the pose.
|
||||||
|
|
||||||
|
|
||||||
## [pastedrop](com/pastedrop.js)
|
## [pastedrop](com/pastedrop.js)
|
||||||
|
|
||||||
detects user copy/paste and file dragdrop action
|
detects user copy/paste and file dragdrop action
|
||||||
|
|
@ -161,9 +209,12 @@ and clipboard functions
|
||||||
| `pasteFile` | self | always translates input to a File object |
|
| `pasteFile` | self | always translates input to a File object |
|
||||||
|
|
||||||
|
|
||||||
|
Restore the pose.
|
||||||
|
|
||||||
|
|
||||||
## [require](com/require('').js)
|
## [require](com/require('').js)
|
||||||
|
|
||||||
automatically requires dependencies
|
automatically requires dependencies or missing components
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
await AFRAME.utils.require( this.dependencies ) (*) autoload missing components
|
await AFRAME.utils.require( this.dependencies ) (*) autoload missing components
|
||||||
|
|
@ -173,3 +224,28 @@ await AFRAME.utils.require(["./app/foo.js","foo.css"],this)
|
||||||
```
|
```
|
||||||
|
|
||||||
> (*) = prefixes baseURL AFRAME.utils.require.baseURL ('./com/' e.g.)
|
> (*) = prefixes baseURL AFRAME.utils.require.baseURL ('./com/' e.g.)
|
||||||
|
|
||||||
|
|
||||||
|
## [window](com/window.js)
|
||||||
|
|
||||||
|
wraps a draggable window around a dom id or [dom](com/dom.js) component.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<a-entity window="dom: #mydiv"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
> depends on [AFRAME.utils.require](com/require.js)
|
||||||
|
|
||||||
|
| property | type | default | info |
|
||||||
|
|------------------|-----------|------------------------|------|
|
||||||
|
| `title` |`string` | "" | |
|
||||||
|
| `width` |`string` | | |
|
||||||
|
| `height` |`string` | 260px | |
|
||||||
|
| `uid` |`string` | | |
|
||||||
|
| `attach` |`selector` | | |
|
||||||
|
| `dom` |`selector` | | |
|
||||||
|
| `max` |`boolean` | false | |
|
||||||
|
| `min` |`boolean` | false | |
|
||||||
|
| `x` |`string` | "center" | |
|
||||||
|
| `y` |`string` | "center" | |
|
||||||
|
| `class` |`array` | [] | |
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
window.AFRAME.registerComponent('xrf-wear', {
|
|
||||||
schema:{
|
|
||||||
el: {type:"selector"},
|
|
||||||
position: {type:"vec3"},
|
|
||||||
rotation: {type:"vec3"}
|
|
||||||
},
|
|
||||||
init: function(){
|
|
||||||
$('a-scene').addEventListener('enter-vr', (e) => this.wear(e) )
|
|
||||||
$('a-scene').addEventListener('exit-vr', (e) => this.unwear(e) )
|
|
||||||
},
|
|
||||||
wear: function(){
|
|
||||||
if( !this.wearable ){
|
|
||||||
let d = this.data
|
|
||||||
this.wearable = new THREE.Group()
|
|
||||||
this.el.object3D.children.map( (c) => this.wearable.add(c) )
|
|
||||||
this.wearable.position.set( d.position.x, d.position.y, d.position.z)
|
|
||||||
this.wearable.rotation.set( d.rotation.x, d.rotation.y, d.rotation.z)
|
|
||||||
}
|
|
||||||
this.data.el.object3D.add(this.wearable)
|
|
||||||
},
|
|
||||||
unwear: function(){
|
|
||||||
this.data.el.remove(this.wearable)
|
|
||||||
this.wearable.children.map( (c) => this.el.object3D.add(c) )
|
|
||||||
delete this.wearable
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
@ -47,7 +47,7 @@ if( !AFRAME.components['html-as-texture-in-xr'] ){
|
||||||
init: async function () {
|
init: async function () {
|
||||||
let el = document.querySelector(this.data.domid)
|
let el = document.querySelector(this.data.domid)
|
||||||
if( ! el ){
|
if( ! el ){
|
||||||
return console.error("html-as-texture-in-xr: cannot get dom element "+this.data.dom.id)
|
return console.error("html-as-texture-in-xr: cannot get dom element "+this.data.domid)
|
||||||
}
|
}
|
||||||
let s = await AFRAME.utils.require(this.dependencies)
|
let s = await AFRAME.utils.require(this.dependencies)
|
||||||
this.el.setAttribute("html",`html: ${this.data.domid}; cursor:#cursor; xrlayer: true`)
|
this.el.setAttribute("html",`html: ${this.data.domid}; cursor:#cursor; xrlayer: true`)
|
||||||
|
|
|
||||||
|
|
@ -444,7 +444,12 @@ if( typeof AFRAME != 'undefined '){
|
||||||
myvalue: function(e){ this.el.dom.querySelector('b').innerText = this.data.myvalue },
|
myvalue: function(e){ this.el.dom.querySelector('b').innerText = this.data.myvalue },
|
||||||
|
|
||||||
launcher: async function(){
|
launcher: async function(){
|
||||||
|
if( !this.term.instance ){
|
||||||
this.initTerminal()
|
this.initTerminal()
|
||||||
|
}else{
|
||||||
|
// toggle visibility
|
||||||
|
this.el.winbox[ this.el.winbox.min ? 'restore' : 'minimize' ]()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
|
||||||
277
com/launcher.js
277
com/launcher.js
|
|
@ -1,20 +1,55 @@
|
||||||
/*
|
/**
|
||||||
* ## launcher
|
* ## [launcher](com/launcher.js)
|
||||||
*
|
*
|
||||||
* displays app (icons) for enduser to launch
|
* displays app (icons) in 2D and 3D handmenu (enduser can launch desktop-like 'apps')
|
||||||
*
|
*
|
||||||
* ```javascript
|
* ```html
|
||||||
* <a-entity app="app/launcher.js"/>
|
* <a-entity launcher="attach: #left-hand"></a-entity>
|
||||||
|
*
|
||||||
|
* <a-assets>
|
||||||
|
* <a-mixin id="menuitem" geometry="primitive: plane; width: 0.15; height: 0.15; depth: 0.02" obb-collider="size: 0.03 0.03 0.03" ></a-mixin>
|
||||||
|
* <a-mixin id="menubutton" geometry="primitive: circle; radius: 0.025" material="side: double; color:#4C73FE"
|
||||||
|
* obb-collider="size: 0.03 0.03 0.03" position="-0.003 -0.027 -0.077" rotation="94.53 -6.35 -59.0"></a-mixin>
|
||||||
|
* </a-assets>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* | property | type | example |
|
* | property | type | example |
|
||||||
* |--------------|--------------------|----------------------------------------------------------------------------------------|
|
* |--------------|--------------------|----------------------------------------------------------------------------------------|
|
||||||
* | `registries` | `array` of strings | <a-entity app="app/launcher.js; registers: https://foo.com/index.json, ./index.json"/> |
|
* | `attach` | `selector` | hand or object to attach menu to |
|
||||||
|
* | `registries` | `array` of strings | `<a-entity launcher="registers: https://foo.com/index.json, ./index.json"/>` |
|
||||||
*
|
*
|
||||||
* | event | target | info |
|
* | event | target | info |
|
||||||
* |--------------|-------------------------------------------------------------------------------------------------------------|
|
* |--------------|-------------------------------------------------------------------------------------------------------------|
|
||||||
* | `launcher` | an app | when pressing an app icon, `launcher` event will be send to the respective app |
|
* | `launcher` | an app | when pressing an app icon, `launcher` event will be send to the respective app |
|
||||||
*/
|
*
|
||||||
|
* There a multiple ways of letting the launcher know that an app can be launched:
|
||||||
|
*
|
||||||
|
* 1. any AFRAME component with an `launcher`-event + manifest is automatically added:
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* AFRAME.registerComponent('foo',{
|
||||||
|
* events:{
|
||||||
|
* launcher: function(){ ...launch something... }
|
||||||
|
* },
|
||||||
|
* manifest:{ // HTML5 manifesto JSON object
|
||||||
|
* // https://www.w3.org/TR/appmanifest/
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 2. dynamically in javascript
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* window.launcher.register({
|
||||||
|
* name:"foo",
|
||||||
|
* icon: "https://.../optional_icon.png"
|
||||||
|
* description: "lorem ipsum",
|
||||||
|
* cb: () => alert("foo")
|
||||||
|
* })
|
||||||
|
* //window.launcher.unregister('foo')
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
AFRAME.registerComponent('launch', { // use this component to auto-launch component
|
AFRAME.registerComponent('launch', { // use this component to auto-launch component
|
||||||
init: function(){
|
init: function(){
|
||||||
|
|
@ -26,11 +61,10 @@ AFRAME.registerComponent('launch', { // use this component to auto-launch compon
|
||||||
|
|
||||||
AFRAME.registerComponent('launcher', {
|
AFRAME.registerComponent('launcher', {
|
||||||
schema: {
|
schema: {
|
||||||
attach: { type:"selector"},
|
attach: { type:"selector", default:"#left-hand"},
|
||||||
padding: { type:"number","default":0.15},
|
padding: { type:"number","default":0.15},
|
||||||
fingerTip: {type:"selector"},
|
fingerTip: {type:"selector", default:"#right-hand"},
|
||||||
fingerDistance: {type:"number", "default":0.25},
|
fingerDistance: {type:"number", "default":0.25},
|
||||||
rescale: {type:"number","default":0.4},
|
|
||||||
open: { type:"boolean", "default":true},
|
open: { type:"boolean", "default":true},
|
||||||
colors: { type:"array", "default": [
|
colors: { type:"array", "default": [
|
||||||
'#4C73FE',
|
'#4C73FE',
|
||||||
|
|
@ -45,38 +79,39 @@ AFRAME.registerComponent('launcher', {
|
||||||
cols: { type:"number", "default": 5 }
|
cols: { type:"number", "default": 5 }
|
||||||
},
|
},
|
||||||
|
|
||||||
dependencies:{
|
requires:{
|
||||||
dom: "com/dom.js"
|
dom: "com/dom.js",
|
||||||
|
htmlinxr: "com/html-as-texture-in-xr.js",
|
||||||
|
data2events: "com/data2event.js"
|
||||||
},
|
},
|
||||||
|
|
||||||
init: async function () {
|
init: async function () {
|
||||||
await AFRAME.utils.require(this.dependencies)
|
await AFRAME.utils.require(this.requires)
|
||||||
this.worldPosition = new THREE.Vector3()
|
this.worldPosition = new THREE.Vector3()
|
||||||
|
|
||||||
await AFRAME.utils.require({
|
|
||||||
html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
|
|
||||||
dom: "./com/dom.js",
|
|
||||||
data2events: "./com/data2event.js"
|
|
||||||
})
|
|
||||||
|
|
||||||
this.el.setAttribute("dom","")
|
this.el.setAttribute("dom","")
|
||||||
this.el.setAttribute("noxd","ignore") // hint to XD.js that we manage ourselve concerning 2D/3D switching
|
|
||||||
this.render()
|
this.render()
|
||||||
|
|
||||||
if( this.data.attach ){
|
// this.tick = AFRAME.utils.throttleTick( this.tick, 100, this );
|
||||||
this.el.object3D.visible = false
|
|
||||||
if( this.isHand(this.data.attach) ){
|
|
||||||
this.data.attach.addEventListener('model-loaded', () => {
|
|
||||||
this.ready = true
|
|
||||||
this.attachMenu()
|
|
||||||
})
|
|
||||||
// add button
|
|
||||||
this.menubutton = this.createMenuButton()
|
|
||||||
this.menubutton.object3D.visible = false
|
|
||||||
this.data.attach.appendChild( this.menubutton )
|
|
||||||
}else this.data.attach.appendChild(this.el)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// this.el.sceneEl.addEventListener('enter-vr', (e) => this.preventAccidentalButtonPresses() )
|
||||||
|
// this.el.sceneEl.addEventListener('enter-ar', (e) => this.preventAccidentalButtonPresses() )
|
||||||
|
// this.el.sceneEl.addEventListener('exit-vr', (e) => this.preventAccidentalButtonPresses() )
|
||||||
|
// this.el.sceneEl.addEventListener('exit-ar', (e) => this.preventAccidentalButtonPresses() )
|
||||||
|
|
||||||
|
//if( this.data.attach ){
|
||||||
|
// if( this.isHand(this.data.attach) ){
|
||||||
|
// this.data.attach.addEventListener('model-loaded', () => {
|
||||||
|
// this.ready = true
|
||||||
|
// //this.data.attach.appendChild(this.el)
|
||||||
|
// let armature = this.data.attach.object3D.getObjectByName('Armature')
|
||||||
|
// if( !armature ) return console.warn('cannot find armature')
|
||||||
|
// let object3D = this.el.object3D.children[0]
|
||||||
|
// this.el.remove()
|
||||||
|
// setTimeout( () => this.data.attach.object3D.add(object3D), 500)
|
||||||
|
// })
|
||||||
|
// }else this.data.attach.appendChild(this.el)
|
||||||
|
//}else console.warn("launcher.js: attach-option not given")
|
||||||
},
|
},
|
||||||
|
|
||||||
isHand: (el) => {
|
isHand: (el) => {
|
||||||
|
|
@ -84,7 +119,7 @@ AFRAME.registerComponent('launcher', {
|
||||||
},
|
},
|
||||||
|
|
||||||
dom: {
|
dom: {
|
||||||
scale: 3,
|
scale: 1,
|
||||||
events: ['click'],
|
events: ['click'],
|
||||||
html: (me) => `<div class="iconmenu">loading components..</div>`,
|
html: (me) => `<div class="iconmenu">loading components..</div>`,
|
||||||
css: (me) => `.iconmenu {
|
css: (me) => `.iconmenu {
|
||||||
|
|
@ -132,6 +167,10 @@ AFRAME.registerComponent('launcher', {
|
||||||
padding-top:13px;
|
padding-top:13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iconmenu > button:only-child{
|
||||||
|
border-radius:5px 5px 5px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.iconmenu > button > img {
|
.iconmenu > button > img {
|
||||||
transform: translate(0px,-14px);
|
transform: translate(0px,-14px);
|
||||||
opacity:0.5;
|
opacity:0.5;
|
||||||
|
|
@ -146,15 +185,9 @@ AFRAME.registerComponent('launcher', {
|
||||||
},
|
},
|
||||||
|
|
||||||
events:{
|
events:{
|
||||||
open: function(){
|
|
||||||
this.preventAccidentalButtonPresses()
|
DOMready: function(){
|
||||||
if( this.data.open ){
|
this.el.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}; faceuser: true`)
|
||||||
this.el.setAttribute("animation",`dur: 200; property: scale; from: 0 0 1; to: ${this.data.rescale} ${this.data.rescale} ${this.data.rescale}`)
|
|
||||||
this.menubutton.object3D.visible = false
|
|
||||||
}else{
|
|
||||||
this.el.setAttribute("animation",`dur: 200; property: scale; from: ${this.data.rescale} ${this.data.rescale} ${this.data.rescale}; to: 0 0 1`)
|
|
||||||
this.menubutton.object3D.visible = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -163,14 +196,6 @@ AFRAME.registerComponent('launcher', {
|
||||||
setTimeout( () => this.data.paused = false, 500 ) // prevent menubutton press collide with animated buttons
|
setTimeout( () => this.data.paused = false, 500 ) // prevent menubutton press collide with animated buttons
|
||||||
},
|
},
|
||||||
|
|
||||||
createMenuButton: function(colo){
|
|
||||||
let aentity = document.createElement('a-entity')
|
|
||||||
aentity.setAttribute("mixin","menubutton")
|
|
||||||
aentity.addEventListener('obbcollisionstarted', this.onpress )
|
|
||||||
aentity.addEventListener('obbcollisionended', this.onreleased )
|
|
||||||
return aentity
|
|
||||||
},
|
|
||||||
|
|
||||||
render: async function(els){
|
render: async function(els){
|
||||||
if( !this.el.dom ) return // too early (dom.js component not ready)
|
if( !this.el.dom ) return // too early (dom.js component not ready)
|
||||||
|
|
||||||
|
|
@ -180,118 +205,29 @@ AFRAME.registerComponent('launcher', {
|
||||||
let colors = this.data.colors
|
let colors = this.data.colors
|
||||||
const add2D = (launchCom,el,manifest) => {
|
const add2D = (launchCom,el,manifest) => {
|
||||||
let btn = document.createElement('button')
|
let btn = document.createElement('button')
|
||||||
|
let iconDefault = "data:image/svg+xml;base64,PHN2ZwogIHdpZHRoPSIyNCIKICBoZWlnaHQ9IjI0IgogIHZpZXdCb3g9IjAgMCAyNCAyNCIKICBmaWxsPSJub25lIgogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKPgogIDxwYXRoCiAgICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgICBjbGlwLXJ1bGU9ImV2ZW5vZGQiCiAgICBkPSJNMjAuMTcwMiAzTDIwLjE2NjMgMy4wMDQ1M0MyMS43NDU4IDMuMDkwODQgMjMgNC4zOTg5NiAyMyA2VjE4QzIzIDE5LjY1NjkgMjEuNjU2OSAyMSAyMCAyMUg0QzIuMzQzMTUgMjEgMSAxOS42NTY5IDEgMThWNkMxIDQuMzQzMTUgMi4zNDMxNSAzIDQgM0gyMC4xNzAyWk0xMC40NzY0IDVIMTYuNDc2NEwxMy4wODkgOUg3LjA4ODk5TDEwLjQ3NjQgNVpNNS4wODg5OSA5TDguNDc2NDQgNUg0QzMuNDQ3NzIgNSAzIDUuNDQ3NzIgMyA2VjlINS4wODg5OVpNMyAxMVYxOEMzIDE4LjU1MjMgMy40NDc3MiAxOSA0IDE5SDIwQzIwLjU1MjMgMTkgMjEgMTguNTUyMyAyMSAxOFYxMUgzWk0yMSA5VjZDMjEgNS40NDc3MSAyMC41NTIzIDUgMjAgNUgxOC40NzY0TDE1LjA4OSA5SDIxWiIKICAgIGZpbGw9ImN1cnJlbnRDb2xvciIKICAvPgo8L3N2Zz4="
|
||||||
btn.innerHTML = `${ manifest?.icons?.length > 0
|
btn.innerHTML = `${ manifest?.icons?.length > 0
|
||||||
? `<img src='${manifest.icons[0].src}' title='${manifest.name}: ${manifest.description}'/>`
|
? `<img src='${manifest.icons[0].src || iconDefault}' title='${manifest.name}: ${manifest.description}'/>`
|
||||||
: `${manifest.short_name}`
|
: `${manifest.short_name || manifest.name}`
|
||||||
}`
|
}`
|
||||||
btn.addEventListener('click', () => el.emit('launcher',{}) )
|
btn.addEventListener('click', () => el.emit('launcher',{}) )
|
||||||
this.el.dom.appendChild(btn)
|
this.el.dom.appendChild(btn)
|
||||||
}
|
}
|
||||||
|
|
||||||
const add3D = (launchCom,el,manifest) => {
|
|
||||||
let aentity = document.createElement('a-entity')
|
|
||||||
let atext = document.createElement('a-entity')
|
|
||||||
let padding = this.data.padding
|
|
||||||
if( (i % this.data.cols) == 0 ) j++
|
|
||||||
aentity.setAttribute("mixin","menuitem")
|
|
||||||
aentity.setAttribute("position",`${padding+(i++ % this.data.cols) * padding} ${j*padding} 0`)
|
|
||||||
if( !aentity.getAttribute("material")){
|
|
||||||
aentity.setAttribute('material',`side: double; color: ${colors[ i % colors.length]}`)
|
|
||||||
}
|
|
||||||
aentity.addEventListener('obbcollisionstarted', this.onpress )
|
|
||||||
aentity.addEventListener('obbcollisionended', this.onreleased )
|
|
||||||
atext.setAttribute("text",`value: ${manifest.short_name}; align: baseline; anchor: align; align:center; wrapCount:7`)
|
|
||||||
atext.setAttribute("scale","0.1 0.1 0.1")
|
|
||||||
aentity.appendChild(atext)
|
|
||||||
this.el.appendChild(aentity)
|
|
||||||
aentity.launchCom = launchCom
|
|
||||||
return aentity
|
|
||||||
}
|
|
||||||
|
|
||||||
// finally render them!
|
// finally render them!
|
||||||
this.el.dom.innerHTML = '' // clear
|
this.el.dom.innerHTML = '' // clear
|
||||||
els = els || this.system.components
|
els = els || this.system.launchables
|
||||||
els.map( (c) => {
|
els.map( (c) => {
|
||||||
const launchComponentKey = c.getAttributeNames().shift()
|
// console.warn(`could not find component '${launchComponentKey}' (forgot to include script-tag?)`)
|
||||||
const launchCom = c.components[ launchComponentKey ]
|
const manifest = c.manifest
|
||||||
if( !launchCom ) return console.warn(`could not find component '${launchComponentKey}' (forgot to include script-tag?)`)
|
|
||||||
const manifest = launchCom.manifest
|
|
||||||
if( manifest ){
|
if( manifest ){
|
||||||
add2D(launchCom,c,manifest)
|
add2D(c,c.el,manifest)
|
||||||
add3D(launchCom,c,manifest)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onpress: function(e){
|
|
||||||
const launcher = document.querySelector('[launcher]').components.launcher
|
|
||||||
if( launcher.data.paused ) return // prevent accidental pressed due to animation
|
|
||||||
if( e.detail.withEl.computedMixinStr == 'menuitem' ) return // dont react to menuitems touching eachother
|
|
||||||
|
|
||||||
// if user press menu button toggle menu
|
|
||||||
if( launcher && !launcher.data.open && e.srcElement.computedMixinStr == 'menubutton' ){
|
|
||||||
return (launcher.data.open = true)
|
|
||||||
}
|
|
||||||
if( launcher && !launcher.data.open ) return // dont process menuitems when menu is closed
|
|
||||||
let el = e.srcElement
|
|
||||||
if(!el) return
|
|
||||||
el.object3D.traverse( (o) => {
|
|
||||||
if( o.material && o.material.color ){
|
|
||||||
if( !o.material.colorOriginal ) o.material.colorOriginal = o.material.color.clone()
|
|
||||||
o.material.color.r *= 0.3
|
|
||||||
o.material.color.g *= 0.3
|
|
||||||
o.material.color.b *= 0.3
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if( el.launchCom ){
|
|
||||||
console.log("launcher.js: launching "+el.launchCom.el.getAttributeNames().shift())
|
|
||||||
launcher.preventAccidentalButtonPresses()
|
|
||||||
el.launchCom.el.emit('launcher') // launch component!
|
|
||||||
this.data.open = false // close to prevent infinite loop of clicks when leaving immersive mode
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onreleased: function(e){
|
|
||||||
if( e.detail.withEl.computedMixinStr == 'menuitem' ) return // dont react to menuitems touching eachother
|
|
||||||
let el = e.srcElement
|
|
||||||
el.object3D.traverse( (o) => {
|
|
||||||
if( o.material && o.material.color ){
|
|
||||||
if( o.material.colorOriginal ) o.material.color = o.material.colorOriginal.clone()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
attachMenu: function(){
|
|
||||||
if( this.el.parentNode != this.data.attach ){
|
|
||||||
this.el.object3D.visible = true
|
|
||||||
let armature = this.data.attach.object3D.getObjectByName('Armature')
|
|
||||||
if( !armature ) return console.warn('cannot find armature')
|
|
||||||
this.data.attach.object3D.children[0].add(this.el.object3D)
|
|
||||||
this.el.object3D.scale.x = this.data.rescale
|
|
||||||
this.el.object3D.scale.y = this.data.rescale
|
|
||||||
this.el.object3D.scale.z = this.data.rescale
|
|
||||||
|
|
||||||
// add obb-collider to index finger-tip
|
|
||||||
let aentity = document.createElement('a-entity')
|
|
||||||
trackedObject3DVariable = 'parentNode.components.hand-tracking-controls.bones.9';
|
|
||||||
this.data.fingerTip.appendChild(aentity)
|
|
||||||
aentity.setAttribute('obb-collider', {trackedObject3D: trackedObject3DVariable, size: 0.015});
|
|
||||||
|
|
||||||
if( this.isHand(this.data.attach) ){
|
|
||||||
// shortly show and hide menu into palm (hint user)
|
|
||||||
setTimeout( () => { this.data.open = false }, 1500 )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
tick: function(){
|
tick: function(){
|
||||||
if( this.ready && this.data.open ){
|
|
||||||
let indexTipPosition = document.querySelector('#right-hand[hand-tracking-controls]').components['hand-tracking-controls'].indexTipPosition
|
|
||||||
this.el.object3D.getWorldPosition(this.worldPosition)
|
|
||||||
const lookingAtPalm = this.data.attach.components['hand-tracking-controls'].wristObject3D.rotation.z > 2.0
|
|
||||||
if( !lookingAtPalm ){ this.data.open = false }
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
manifest: { // HTML5 manifest to identify app to xrsh
|
||||||
|
|
@ -352,35 +288,72 @@ in above's case "\nHelloworld application\n" will qualify as header.
|
||||||
AFRAME.registerSystem('launcher',{
|
AFRAME.registerSystem('launcher',{
|
||||||
|
|
||||||
init: function(){
|
init: function(){
|
||||||
this.components = []
|
this.launchables = []
|
||||||
|
this.dom = []
|
||||||
|
this.registered = []
|
||||||
// observe HTML changes in <a-scene>
|
// observe HTML changes in <a-scene>
|
||||||
observer = new MutationObserver( (a,b) => this.getLaunchables(a,b) )
|
observer = new MutationObserver( (a,b) => this.getLaunchables(a,b) )
|
||||||
observer.observe( this.sceneEl, {characterData: false, childList: true, attributes: false});
|
observer.observe( this.sceneEl, {characterData: false, childList: true, attributes: false});
|
||||||
|
|
||||||
|
window.launcher = this
|
||||||
|
|
||||||
|
for( let i = 0; i < 10; i++){
|
||||||
|
window.launcher.register({
|
||||||
|
name:"foo"+i,
|
||||||
|
// icon: "https://..."
|
||||||
|
description: "lorem ipsum",
|
||||||
|
cb: () => alert("foo")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getLaunchables()
|
||||||
|
},
|
||||||
|
|
||||||
|
register: function(launchable){
|
||||||
|
try{
|
||||||
|
let {name, description, cb} = launchable
|
||||||
|
this.registered.push({
|
||||||
|
manifest: {name, description, icons: [{src:launchable.icon}]},
|
||||||
|
launcher: cb
|
||||||
|
})
|
||||||
|
}catch(e){
|
||||||
|
console.error('AFRAME.systems.launcher.register({ name, description, icon, cb }) got invalid obj')
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
unregister: function(launchableName){
|
||||||
|
this.registered = this.registered.filter( (l) => l.name != launchableName )
|
||||||
},
|
},
|
||||||
|
|
||||||
getLaunchables: function(mutationsList,observer){
|
getLaunchables: function(mutationsList,observer){
|
||||||
let searchEvent = 'launcher'
|
let searchEvent = 'launcher'
|
||||||
let els = [...this.sceneEl.getElementsByTagName("*")]
|
let els = [...this.sceneEl.getElementsByTagName("*")]
|
||||||
let seen = {}
|
let seen = {}
|
||||||
|
this.launchables = [];
|
||||||
|
|
||||||
this.components = els.filter( (el) => {
|
// collect manually registered launchables
|
||||||
|
this.registered.map( (launchable) => this.launchables.push(launchable) )
|
||||||
|
|
||||||
|
// collect launchables in aframe dom elements
|
||||||
|
this.dom = els.filter( (el) => {
|
||||||
let hasEvent = false
|
let hasEvent = false
|
||||||
if( el.components ){
|
if( el.components ){
|
||||||
for( let i in el.components ){
|
for( let i in el.components ){
|
||||||
if( el.components[i].events && el.components[i].events[searchEvent] && !seen[i] ){
|
if( el.components[i].events && el.components[i].events[searchEvent] && !seen[i] ){
|
||||||
hasEvent = seen[i] = true
|
this.launchables.push(hasEvent = seen[i] = el.components[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hasEvent ? el : null
|
return hasEvent ? el : null
|
||||||
})
|
})
|
||||||
this.updateLauncher()
|
this.updateLauncher()
|
||||||
return seen
|
return this.launchables
|
||||||
},
|
},
|
||||||
|
|
||||||
updateLauncher: function(){
|
updateLauncher: function(){
|
||||||
let launcher = document.querySelector('[launcher]')
|
let launcher = document.querySelector('[launcher]')
|
||||||
if( launcher ) launcher.components.launcher.render()
|
if( launcher && launcher.components.launcher) launcher.components.launcher.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* ## [require](com/require('').js)
|
* ## [require](com/require('').js)
|
||||||
*
|
*
|
||||||
* automatically requires dependencies
|
* automatically requires dependencies or missing components
|
||||||
*
|
*
|
||||||
* ```javascript
|
* ```javascript
|
||||||
* await AFRAME.utils.require( this.dependencies ) (*) autoload missing components
|
* await AFRAME.utils.require( this.dependencies ) (*) autoload missing components
|
||||||
|
|
@ -74,3 +74,38 @@ AFRAME.utils.require = function(arr_or_obj,opts){
|
||||||
AFRAME.utils.require.required = {}
|
AFRAME.utils.require.required = {}
|
||||||
|
|
||||||
AFRAME.utils.require.baseURL = './com/'
|
AFRAME.utils.require.baseURL = './com/'
|
||||||
|
|
||||||
|
|
||||||
|
// this component will scan the DOM for missing components and lazy load them
|
||||||
|
AFRAME.registerSystem('require',{
|
||||||
|
|
||||||
|
init: function(){
|
||||||
|
this.components = []
|
||||||
|
// observe HTML changes in <a-scene>
|
||||||
|
observer = new MutationObserver( (a,b) => this.getMissingComponents(a,b) )
|
||||||
|
observer.observe( this.sceneEl, {characterData: false, childList: true, attributes: false});
|
||||||
|
},
|
||||||
|
|
||||||
|
getMissingComponents: function(mutationsList,observer){
|
||||||
|
let els = [...this.sceneEl.getElementsByTagName("*")]
|
||||||
|
let seen = []
|
||||||
|
|
||||||
|
els.map( async (el) => {
|
||||||
|
let attrs = el.getAttributeNames()
|
||||||
|
.filter( (a) => a.match(/(^aframe-injected|^data-aframe|^id$|^class$|^on)/) ? null : a )
|
||||||
|
for( let attr in attrs ){
|
||||||
|
let component = attrs[attr]
|
||||||
|
if( el.components && !el.components[component] ){
|
||||||
|
console.info(`require.js: lazy-loading missing <${el.tagName.toLowerCase()} ${component} ... > (TODO: fix selectors in schema)`)
|
||||||
|
// require && remount
|
||||||
|
try{
|
||||||
|
await AFRAME.utils.require([component])
|
||||||
|
el.removeAttribute(component)
|
||||||
|
el.setAttribute(component, el.getAttribute(component) )
|
||||||
|
}catch(e){ } // give up, normal AFRAME behaviour follows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
|
||||||
30
com/wearable.js
Normal file
30
com/wearable.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
AFRAME.registerComponent('wearable', {
|
||||||
|
schema:{
|
||||||
|
el: {type:"selector"},
|
||||||
|
position: {type:"vec3"},
|
||||||
|
rotation: {type:"vec3"}
|
||||||
|
},
|
||||||
|
init: function(){
|
||||||
|
this.position = this.el.object3D.position.clone()
|
||||||
|
this.rotation = this.el.object3D.rotation.clone()
|
||||||
|
if( !this.el ) return console.warn(`wear.js: cannot find ${this.data.el}`)
|
||||||
|
let ctl = this.data.el
|
||||||
|
|
||||||
|
// hand vs controller attach-heuristics
|
||||||
|
this.el.sceneEl.addEventListener('controllersupdated', (e) => {
|
||||||
|
if( !this.data.el.components['hand-tracking-controls'].controllerPresent ){
|
||||||
|
this.attach( ctl.components['hand-tracking-controls'].wristObject3D )
|
||||||
|
}else{
|
||||||
|
this.attach( ctl.object3D )
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
attach: function(target){
|
||||||
|
if( target.uuid == this.el.object3D.parent.uuid ) return; // already attached
|
||||||
|
target.add(this.el.object3D)
|
||||||
|
this.el.object3D.position.copy( this.data.position )
|
||||||
|
this.el.object3D.rotation.copy( this.data.rotation )
|
||||||
|
target.updateMatrixWorld();
|
||||||
|
}
|
||||||
|
})
|
||||||
Loading…
Add table
Reference in a new issue