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', {