diff --git a/index.html b/index.html index 0c978ee..ae39f8b 100644 --- a/index.html +++ b/index.html @@ -13,6 +13,10 @@ + + + + @@ -1221,6 +1225,7 @@ function parseKeys(status, key){ } } +var keyboardInputTarget = 'hud' AFRAME.registerComponent('hud', { init: function(){ var feedbackHUDel= document.createElement("a-troika-text") @@ -1237,9 +1242,11 @@ AFRAME.registerComponent('hud', { this.el.appendChild( typingHUDel ) hudTextEl = typingHUDel // should rely on the id based selector now document.addEventListener('keyup', function(event) { + if (keyboardInputTarget != 'hud') return parseKeys('keyup', event.key) }); document.addEventListener('keydown', function(event) { + if (keyboardInputTarget != 'hud') return parseKeys('keydown', event.key) }); } @@ -2169,13 +2176,403 @@ function addAllPrimitives(){ .map( (i,j) => addPrimitive( i, ""+ j/7 + " 1.4 -0.5" ) ) } +var highlighter +shiki.getHighlighter({ theme: 'monokai', langs: ['javascript' ] }).then(h => highlighter = h ) + // see https://github.com/shikijs/shiki/blob/main/docs/languages.md + +function highlight(code = `console.log("Here is your code."); var x = 5;`, language='javascript'){ + if (!highlighter) return {} // should also warn + const tokens = highlighter.codeToThemedTokens(code, language) + let pos=0 + let colorRange={} + tokens.map( line => { + line.map( (t,i) => { + colorRange[pos] = t.color/*.replace("#","0x")*/ + pos+=t.content.length + }) + pos++ + }) + return colorRange +} + + function startExperience(){ + //addCodeEditor(" \n\nqwe qwe qwe qwe qwe qwe qwe qweqweqwe\n\n\na", "") + //addCodeEditor() + fetch("https://fabien.benetou.fr/pub/home/future_of_text_demo/engine/colorme.js").then(r=>r.text()).then( page => { addCodeEditor( page ) }) + //fetch("https://fabien.benetou.fr/Tools/Docker?action=source").then(r=>r.text()).then( page => { addCodeEditor( page, "" ) }) if (AFRAME.utils.device.checkHeadsetConnected()) AFRAME.scenes[0].enterVR(); - document.querySelector("#snapping-sound").components.sound.playSound(); + //document.querySelector("#snapping-sound").components.sound.playSound(); document.querySelector("#mainbutton").style.display = "none" } +// should consider multiple instances instead +let codeEditor = { + element: null, + line: 0, + page: null, + startWindowRange: 0, + lengthWindowRange: 20, + scrollInterval: null, + currentlyDisplayedText: "", + caret: null +} + +function nextLineCodeEditor(lines=1){ // can be negative to scroll up + if (codeEditor.line+lines < 0) return + codeEditor.line+=lines + let content=codeEditor.page.split("\n").slice(codeEditor.line,codeEditor.line+codeEditor.lengthWindowRange).join("\n"); + codeEditor.currentlyDisplayedText=content + codeEditor.element.setAttribute("troika-text", {value: content}) + codeEditor.element.setAttribute("troika-text", {colorRanges: highlight(content, language='javascript')}) + // should respect language set, can be empty + let gutterEl = document.getElementById("leftgutter") + if (gutterEl){ + let lineNumbers = "\n" + for (let i=codeEditor.line+1;i<=codeEditor.line+codeEditor.lengthWindowRange;i++){ + for (let pad=0;pad nextLineCodeEditor(), 100) +} + +function highlightAllOccurences(keyword="function"){ + let indices = [] + let lastfound = codeEditor.currentlyDisplayedText.indexOf(keyword,0) + while (lastfound>-1) { + indices.push(lastfound) + lastfound = codeEditor.currentlyDisplayedText.indexOf(keyword,lastfound+keyword.length) + } + indices.map( pos => { + let offset = (codeEditor.currentlyDisplayedText.slice(0,pos).match(/[\n\t ]/g)||[]).length + pos-=offset + highlightString(pos, keyword.length) + }) +} + +function hightlightNextKeyword(keyword="function"){ + let pos = codeEditor.currentlyDisplayedText.indexOf(keyword) + // invisible characters... some still left + let offset = (codeEditor.currentlyDisplayedText.slice(0,pos).match(/[\n\t ]/g)||[]).length + pos-=offset + highlightString(pos, keyword.length) +} + +function highlightString(pos, length){ + for (let c=pos;c { + el.object3D.children[0].addEventListener("synccomplete", e => { + // this can be used for resizing but without add the element + if (document.getElementById("leftgutter")) return // already added, should unregister + + b = el.object3D.children[0]._textRenderInfo.blockBounds + w = b[2]-b[0] + h = b[3]-b[1] + + g = new THREE.BoxGeometry( w, h, .01 ); + m = new THREE.MeshBasicMaterial( {color: 0, opacity: 0.9, transparent: true} ); + c = new THREE.Mesh( g, m ); + el.object3D.add( c ); + c.name = "backdrop" + c.position.z=-.01 + c.position.x= w/2 + }) + }) +} + +function addGuttersToTroikaElement( el ){ + el.addEventListener("object3dset", e => { + el.object3D.children[0].addEventListener("synccomplete", e => { + if (document.getElementById("leftgutter")) return + // already added, should unregister, can be removed to allow dynamic resizing BUT should skip adding element + + b = el.object3D.children[0]._textRenderInfo.blockBounds + w = b[2]-b[0] + h = b[3]-b[1] + + gutterWidth = .2 * String(getNumberOfLinesFromCodeEditor()).length + //should adjust width based on number of lines in total first + g = new THREE.BoxGeometry( gutterWidth, h, .01 ); + m = new THREE.MeshBasicMaterial( {color: 0x333333, opacity: 0.9, transparent: true} ); + c = new THREE.Mesh( g, m ); + el.object3D.add( c ); + c.name = "leftgutter" + c.position.z=-.01 + c.position.x= -gutterWidth/2 + //c.rotation.y= .2 // looks nice but have to consider text on top first, could apply rotation to text too + var leftGutter = document.createElement("a-troika-text") + leftGutter.setAttribute("anchor", "left" ) + leftGutter.setAttribute("outline-width", "5%" ) + leftGutter.setAttribute("outline-color", "black" ) + let lineNumbers = "\n" + for (let i=codeEditor.line+1;i<=codeEditor.line+codeEditor.lengthWindowRange;i++){ + for (let pad=0;pad 0 && language != "none") codeEditor.element.setAttribute("troika-text", {colorRanges: highlight(content, language)}) + // shouldn't set colorRange if the result is {} which is the case when shiki highlighter isn't ready or available + + addBackdropToTroikaElement( codeEditor.element ) + addGuttersToTroikaElement( codeEditor.element ) + + let scrollbarPicked = false + let previousPosition + let p = document.querySelector('[pinchprimary]') + let target = new THREE.Vector3(); // create once an reuse it + p.addEventListener('pinchended', pinchPrimaryScrollbarEnded ); + function pinchPrimaryScrollbarEnded(event){ + //p.removeEventListener('pinchended', pinchPrimaryScrollbarEnded) + //p.removeEventListener('pinchmoved', pinchPrimaryScrollbarMoved) + //p.removeEventListener('pinchstarted', pinchPrimaryScrollbarStarted) + if (!scrollbarPicked) return + scrollbarPicked = false + } + p.addEventListener('pinchmoved', pinchPrimaryScrollbarMoved ); + function pinchPrimaryScrollbarMoved(event){ + if (!scrollbarPicked) return + if (previousPosition.y>event.detail.position.y) + nextLineCodeEditor(1) + else + nextLineCodeEditor(-1) + previousPosition = event.detail.position.clone() + } + p.addEventListener('pinchstarted', pinchPrimaryScrollbarStarted ); + function pinchPrimaryScrollbarStarted(event){ + let rightGutterEl = document.getElementById("rightgutter") + previousPosition = event.detail.position.clone() + rightGutterEl.object3D.getWorldPosition( target ); + if (previousPosition.distanceTo(target)<0.1) scrollbarPicked = true + } + + return codeEditor.element +} + // could change model opacity based on hand position, fading out when within a (very small here) safe space function removeOutlineFromEntity( el ){ @@ -2501,6 +2898,7 @@ function makeAnchorsVisibleOnTargets(){ } function startMesher(){ +// consider preview triangle from primary moved let meshPoints = [] let meshTriangles = [] let offset @@ -2512,7 +2910,8 @@ function startMesher(){ elSecondary.addEventListener('pinchended', endedSecondary ); function endedSecondary(){ targets.push(meshEl) - meshEl.setAttribute('dynamic-body', "") + meshEl.setAttribute('dynamic-body', "shape:hull") + // using 'dynamic-unless-picked' crashes the browser //makeAnchorsVisibleOnTargets() // too large here applyToClass("meshvertex", (e, val ) => e.setAttribute("visible", val), "false") el.removeEventListener('pinchended', end) @@ -2587,7 +2986,7 @@ AFRAME.registerComponent('startfunctions', { //doublePinchToScale() //emptyPinchToMove() //makeAnchorsVisibleOnTargets() - startMesher() + //startMesher() } }) @@ -2668,4 +3067,9 @@ AFRAME.registerComponent('startfunctions', { +