window-buttons in XR
All checks were successful
/ mirror_to_github (push) Successful in 55s
/ test (push) Successful in 12s

This commit is contained in:
Leon van Kammen 2025-06-30 20:54:08 +02:00
parent 2b3c41fa6e
commit 55883df288
6 changed files with 57 additions and 24 deletions

View file

@ -50,9 +50,9 @@ if( !AFRAME.components.dom ){
this this
.ensureOverlay() .ensureOverlay()
.addCSS()
.createReactiveDOMElement() .createReactiveDOMElement()
.assignUniqueID() .assignUniqueID()
.addCSS()
.scaleDOMvsXR() .scaleDOMvsXR()
.triggerKeyboardForInputs() .triggerKeyboardForInputs()

View file

@ -41,7 +41,8 @@ AFRAME.registerComponent('helloworld-window', {
--> -->
</div>`, </div>`,
css: (me) => `.htmlform { padding:11px; }` css: (me) => `.htmlform { padding:11px; }
`
}, },
@ -67,11 +68,16 @@ AFRAME.registerComponent('helloworld-window', {
myvalue: function(e){ this.el.dom.querySelector('#myvalue').innerText = this.data.myvalue }, myvalue: function(e){ this.el.dom.querySelector('#myvalue').innerText = this.data.myvalue },
launcher: async function(){ launcher: async function(){
let s = await AFRAME.utils.require(this.requires) if( !this.el.getAttribute("dom") ){
let s = await AFRAME.utils.require(this.requires)
// instance this component // instance this component
this.el.setAttribute("dom", "") this.el.setAttribute("dom", "")
this.el.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera this.el.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
}else{
// toggle visibility
this.el.winbox[ this.el.winbox.min ? 'restore' : 'minimize' ]()
}
}, },
DOMready: function(){ DOMready: function(){

View file

@ -13,6 +13,7 @@
* *
* | property | type | default | info | * | property | type | default | info |
* |-------------------|-----------|------------------------|------| * |-------------------|-----------|------------------------|------|
* | `title` | `string` | 'xrsh.iso' | window title |
* | `iso` | `string` | https`//forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" | | * | `iso` | `string` | https`//forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" | |
* | `overlayfs` | `string` | '' | zip URL/file to autoextract on top of filesystem | * | `overlayfs` | `string` | '' | zip URL/file to autoextract on top of filesystem |
* | `width` | `number` | 800 || * | `width` | `number` | 800 ||
@ -72,6 +73,7 @@ if( typeof AFRAME != 'undefined '){
schema: { schema: {
iso: { type:"string", "default":"https://forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" }, iso: { type:"string", "default":"https://forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" },
overlayfs: { type:"string"}, overlayfs: { type:"string"},
title: { type:"string", "default":"xrsh.iso"},
width: { type: 'number',"default": 800 }, width: { type: 'number',"default": 800 },
height: { type: 'number',"default": 600 }, height: { type: 'number',"default": 600 },
depth: { type: 'number',"default": 0.03 }, depth: { type: 'number',"default": 0.03 },
@ -142,6 +144,7 @@ if( typeof AFRAME != 'undefined '){
.isoterminal{ .isoterminal{
padding: ${me.com.data.padding}px; padding: ${me.com.data.padding}px;
margin-top:-60px;
width:100%; width:100%;
height:99%; height:99%;
resize: both; resize: both;
@ -244,18 +247,19 @@ if( typeof AFRAME != 'undefined '){
-webkit-animation:none; -webkit-animation:none;
} }
.wb-body:has(> .isoterminal){ .winbox#${me.el.uid} .wb-header{
background: #000C; background: var(--xrsh-black) !important;
}
.wb-body:has(> .isoterminal){
background: var(--xrsh-black);
overflow:hidden; overflow:hidden;
} }
.XR .wb-body:has(> .isoterminal){ .XR .isoterminal{
background: transparent; background: transparent;
} }
.XR .isoterminal{
background: #000;
}
.isoterminal *{ .isoterminal *{
font-size: 14px; font-size: 14px;
font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace; font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
@ -342,7 +346,7 @@ if( typeof AFRAME != 'undefined '){
this.term.emit('term_init', {instance, aEntity:this}) this.term.emit('term_init', {instance, aEntity:this})
//instance.winbox.resize(720,380) //instance.winbox.resize(720,380)
let size = `width: ${this.data.width}; height: ${this.data.height}` let size = `width: ${this.data.width}; height: ${this.data.height}`
instance.setAttribute("window", `title: xrsh.iso; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}; min: ${this.data.minimized}; max: ${this.data.maximized}; class: no-full, no-max, no-resize; grabbable: components.html.el.object3D.children.${this.el.children.length}`) instance.setAttribute("window", `title: ${this.data.title}; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}; min: ${this.data.minimized}; max: ${this.data.maximized}; class: no-full, no-close, no-max, no-resize; grabbable: components.html.el.object3D.children.${this.el.children.length}`)
}) })
instance.addEventListener('window.oncreate', (e) => { instance.addEventListener('window.oncreate', (e) => {

View file

@ -17,14 +17,18 @@ function ISOTerminal(instance,opts){
ISOTerminal.prototype.emit = function(event,data,sender){ ISOTerminal.prototype.emit = function(event,data,sender){
data = data || false data = data || false
// *TODO* wrap certain events into this.preventFrameDrop( () => { .. }) to boost performance
const evObj = new CustomEvent(event, {detail: data} ) const evObj = new CustomEvent(event, {detail: data} )
// forward event to worker/instance/AFRAME element or component-function // forward event to worker/instance/AFRAME element or component-function
// this feels complex, but actually keeps event- and function-names more concise in codebase // this feels complex, but actually keeps event- and function-names more concise in codebase
this.dispatchEvent( evObj ) let fire = () => {
if( sender != "instance" && this.instance ) this.instance.dispatchEvent(evObj) this.dispatchEvent( evObj )
if( sender != "worker" && this.worker ) this.worker.postMessage({event,data}, PromiseWorker.prototype.getTransferable(data) ) if( sender != "instance" && this.instance ) this.instance.dispatchEvent(evObj)
if( sender !== undefined && typeof this[event] == 'function' ) this[event].apply(this, data && data.push ? data : [data] ) if( sender != "worker" && this.worker ) this.worker.postMessage({event,data}, PromiseWorker.prototype.getTransferable(data) )
if( sender !== undefined && typeof this[event] == 'function' ) this[event].apply(this, data && data.push ? data : [data] )
}
if( event.match(/^serial/) ){
this.preventFrameDrop( fire )
}else fire()
} }
ISOTerminal.addEventListener = (event,cb) => { ISOTerminal.addEventListener = (event,cb) => {

View file

@ -1,8 +1,8 @@
ISOTerminal.prototype.redirectConsole = function(handler){ ISOTerminal.prototype.redirectConsole = function(handler){
const log = console.log; const log = console._log = console.log;
const dir = console.dir; const dir = console._dir = console.dir;
const err = console.error; const err = console._error = console.error;
const warn = console.warn; const warn = console._warn = console.warn;
const addLineFeeds = (str) => typeof str == 'string' ? str.replace(/\n/g,"\r\n") : str const addLineFeeds = (str) => typeof str == 'string' ? str.replace(/\n/g,"\r\n") : str
console.log = (...args)=>{ console.log = (...args)=>{
@ -37,12 +37,13 @@ ISOTerminal.prototype.enableConsole = function(opts){
let _str = typeof str == 'string' ? str : JSON.stringify(str) let _str = typeof str == 'string' ? str : JSON.stringify(str)
let finalStr = ""; let finalStr = "";
prefix = prefix ? prefix+' ' : '' prefix = prefix ? prefix+' ' : ''
_str.trim().split("\n").map( (line) => { String(_str).trim().split("\n").map( (line) => {
finalStr += `${opts.stdout ? '' : "\x1b[38;5;165m/dev/browser: \x1b[0m"}`+prefix+line+'\n' finalStr += `${opts.stdout ? '' : "\x1b[38;5;165m/dev/browser: \x1b[0m"}`+prefix+line+'\n'
}) })
if( opts.stdout ){ if( opts.stdout ){
this.emit('serial-output-string', finalStr) this.emit('serial-output-string', finalStr, "worker")
}else this.emit('append_file', ["/dev/browser/console",finalStr]) }else this.emit('append_file', ["/dev/browser/console",finalStr])
this.lastStr = finalStr
}) })
window.addEventListener('error', function(event) { window.addEventListener('error', function(event) {

View file

@ -91,6 +91,8 @@ AFRAME.registerComponent('window', {
this.el.components['obb-collider'].data.trackedObject3D = this.data.grabbable this.el.components['obb-collider'].data.trackedObject3D = this.data.grabbable
this.el.components['obb-collider'].update() this.el.components['obb-collider'].update()
},1000) },1000)
this.patchButtons(e)
}, },
onclose: close onclose: close
@ -112,7 +114,23 @@ AFRAME.registerComponent('window', {
show: function(state){ show: function(state){
this.el.dom.closest('.winbox').style.display = state ? '' : 'none' this.el.dom.closest('.winbox').style.display = state ? '' : 'none'
},
// the buttons don't work in XR because HTMLMesh does not understand onclick on divs
patchButtons: function(e){
let wEl = e.mount;
let controls = [...wEl.closest(".winbox").querySelectorAll(".wb-control span")]
controls.map( (c) => {
if( c.className == "wb-close"){
let btn = document.createElement("button")
btn.className = "xr-close"
btn.innerText = "x"
btn.addEventListener("click", (e) => { } )// this will bubble up (to click ancestor in XR)
c.appendChild(btn)
}
})
} }
}) })
AFRAME.utils.positionObjectNextToNeighbor = function positionObjectNextToNeighbor(object, lastNeighbor = null, margin ){ AFRAME.utils.positionObjectNextToNeighbor = function positionObjectNextToNeighbor(object, lastNeighbor = null, margin ){