1st commit fabien's drumkeyboard
This commit is contained in:
parent
bc030e2669
commit
3b7ce6daab
11 changed files with 287 additions and 0 deletions
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
| package | description |
|
| package | description |
|
||||||
|-|-|
|
|-|-|
|
||||||
|
| [drumkeyboard](drumkeyboard) | A customizable keyboard component |
|
||||||
| [fzy](fzy) | lightweight + fast fuzzyfinder alternative |
|
| [fzy](fzy) | lightweight + fast fuzzyfinder alternative |
|
||||||
| [joe](joe) | Set of battleproof lightweight text/code editors |
|
| [joe](joe) | Set of battleproof lightweight text/code editors |
|
||||||
| [nano](nano) | Lightweight code editor with syntax highlighting |
|
| [nano](nano) | Lightweight code editor with syntax highlighting |
|
||||||
|
|
|
||||||
18
drumkeyboard/.env
Executable file
18
drumkeyboard/.env
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/sh
|
||||||
|
dir=$(pwd)
|
||||||
|
|
||||||
|
# overlay our directories
|
||||||
|
run(){
|
||||||
|
test -f /.nano || {
|
||||||
|
find . -type d -mindepth 1 | while read dir; do
|
||||||
|
cp -d -r ./$dir/* /$dir/.
|
||||||
|
done
|
||||||
|
echo 1 > /.nano
|
||||||
|
cp ./root/.nanorc ~/.
|
||||||
|
echo "[i] imported overlay fs"
|
||||||
|
echo "[i] editor 'nano' installed" | logger
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test -n "$BROWSER" && run
|
||||||
|
|
||||||
5
drumkeyboard/README.md
Normal file
5
drumkeyboard/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Drumkeyboard
|
||||||
|
|
||||||
|
A customizable keyboard component
|
||||||
|
|
||||||
|
> NOTE: after importing package run `cd ~/scene-drumkeyboard` to load the demoscene
|
||||||
4
drumkeyboard/build.sh
Executable file
4
drumkeyboard/build.sh
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
name=drumkeyboard
|
||||||
|
zip -r package.$name.zip root .env .env.leave
|
||||||
BIN
drumkeyboard/package.drumkeyboard.zip
Normal file
BIN
drumkeyboard/package.drumkeyboard.zip
Normal file
Binary file not shown.
4
drumkeyboard/root/scene-drumkeyboard/.env
Executable file
4
drumkeyboard/root/scene-drumkeyboard/.env
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
|
echo "loading drumkeyboard demo scene"
|
||||||
|
cp index.html /root/index.html
|
||||||
|
cat drumkbd.js index.js > /root/index.js
|
||||||
1
drumkeyboard/root/scene-drumkeyboard/.env.leave
Normal file
1
drumkeyboard/root/scene-drumkeyboard/.env.leave
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
echo "" > /root/index.html # cleanup
|
||||||
229
drumkeyboard/root/scene-drumkeyboard/drumkbd.js
Normal file
229
drumkeyboard/root/scene-drumkeyboard/drumkbd.js
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
// let sequentialFilters = []
|
||||||
|
// <script src="filters/keymap.js"></script>
|
||||||
|
|
||||||
|
let currentFilter = null
|
||||||
|
|
||||||
|
function applyNextFilter( filename ){
|
||||||
|
if ( currentFilter == null ) currentFilter = -1
|
||||||
|
currentFilter++
|
||||||
|
if ( sequentialFilters[currentFilter] ){
|
||||||
|
sequentialFilters[ currentFilter ]( filename )
|
||||||
|
} else {
|
||||||
|
console.log( "done filtering for", filename )
|
||||||
|
currentFilter = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showFile( filename, openingOptions = {}){
|
||||||
|
console.log('showFile', filename)
|
||||||
|
fetch( '/mnt/root/scene-drumkeyboard/'+filename ).then( r => {
|
||||||
|
let idFromFilename = filename.replaceAll('.','') // has to remove from proper CSS ID
|
||||||
|
if (!filesWithMetadata[filename] ) filesWithMetadata[filename] = {}
|
||||||
|
filesWithMetadata[filename].contentType = r.headers.get('Content-Type')
|
||||||
|
filesWithMetadata[filename].idFromFilename = idFromFilename
|
||||||
|
filesWithMetadata[filename].openingOptions = openingOptions
|
||||||
|
console.log( 'ct metadata', filename, filesWithMetadata[filename].contentType )
|
||||||
|
|
||||||
|
applyNextFilter( filename )
|
||||||
|
// const showdefinitions = urlParams.get('showdefinitions');
|
||||||
|
// should be showFile() configuration parameter instead
|
||||||
|
// can be used via e.g. showFile("https://fabien.benetou.fr/?action=source",{ mereology:"whole"})
|
||||||
|
|
||||||
|
// should emit an event when done, for now just console.log which isn't programmatic
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDrumKeyboard(){
|
||||||
|
// get keymap
|
||||||
|
showFile('fabien_corneish_zen.keymap')
|
||||||
|
// showFile('fabien_corneish_zen.keymap')
|
||||||
|
|
||||||
|
const swiping = urlParams.get('swiping');
|
||||||
|
|
||||||
|
const keyclass = "keys_from_drumsticks"
|
||||||
|
// transform keymap to keys of keyboard
|
||||||
|
let keyboardEl = document.createElement("a-entity")
|
||||||
|
keyboardEl.id = 'keyboard'
|
||||||
|
AFRAME.scenes[0].appendChild( keyboardEl )
|
||||||
|
AFRAME.scenes[0].addEventListener("keymaploaded", e => {
|
||||||
|
applyToClass("keymap_layer", el => el.setAttribute("visible", "false") )
|
||||||
|
// alternatively other layers could be displayed but with lower opacity
|
||||||
|
// might be very busy but easier to recall
|
||||||
|
Array.from( document.querySelectorAll('.keymap_layer') ).map( (layerToAdd, layerNumber) => {
|
||||||
|
layerToAdd.getAttribute("value").split('\n').map( (l,y) => {
|
||||||
|
l.split("|").map( (k,x) => {
|
||||||
|
if (k.trim()) {
|
||||||
|
// zIndex could be once deep once shallow to potentially go faster between keys, kind of straggered vs ortho
|
||||||
|
let xOffset = -.2
|
||||||
|
let yOffset = 1.3
|
||||||
|
let zOffset = -.4
|
||||||
|
let keysPerRow = l.split("|").length -1
|
||||||
|
let ratio = 1/20
|
||||||
|
zOffset -= Math.abs( keysPerRow/2 - x ) * ratio/5 // arguably more ergonomic
|
||||||
|
if (swiping){ zOffset = zOffset + Math.abs( x - keysPerRow/2 ) * ratio } // opposite from swiping
|
||||||
|
// somehow the center isn't... the center
|
||||||
|
if (l.length < 70) xOffset += .15 // 80 was fine for layer 0 but not layer 1 and 2
|
||||||
|
let labelEl = document.createElement("a-troika-text")
|
||||||
|
labelEl.setAttribute("value",k.trim())
|
||||||
|
labelEl.setAttribute("position", "0 .51 0")
|
||||||
|
labelEl.setAttribute("font-size", "1")
|
||||||
|
labelEl.setAttribute("color", "black")
|
||||||
|
labelEl.setAttribute("rotation", "-90 0 0")
|
||||||
|
|
||||||
|
let keyEl = document.createElement("a-cylinder")
|
||||||
|
keyEl.setAttribute("segments-height", "2")
|
||||||
|
keyEl.setAttribute("segments-radial", "24")
|
||||||
|
keyEl.setAttribute("scale", ".01 .01 .01")
|
||||||
|
keyEl.setAttribute("rotation", "60 0 0")
|
||||||
|
keyEl.setAttribute("position", ""+(xOffset+x*ratio)+" " +(yOffset-y*ratio)+" "+(zOffset + y/50) )
|
||||||
|
keyEl.classList.add( keyclass )
|
||||||
|
keyEl.classList.add( "layer_"+ layerNumber )
|
||||||
|
keyEl.appendChild( labelEl )
|
||||||
|
keyboardEl.appendChild( keyEl )
|
||||||
|
// keyEl.id = keyclass+'_'+k.trim() // not correct anymore as multiple layers can have the same key
|
||||||
|
keyEl.id = keyclass+'_'+layerNumber+'_'+k.trim()
|
||||||
|
// setTimeout( _ => keyEl.object3D.lookAt( new THREE.Vector3(0, 1.5, 0)), 100 )
|
||||||
|
// not great as they have 90 deg rotation already, could find a better way
|
||||||
|
// ... but arguably should be the opposite based on resting hand positions
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
keys_from_drumsticks_0_SHFT.setAttribute("wireframe", shiftFromVirtualKeyboard) // arguable, could do so for all layers
|
||||||
|
// hide all layers but the current one
|
||||||
|
Array.from( document.querySelectorAll(".keys_from_drumsticks") ).map( k => k.setAttribute("visible", "false") )
|
||||||
|
Array.from( document.querySelectorAll(".keys_from_drumsticks"+".layer_"+layerFromVirtualKeyboard) ).map( k => k.setAttribute("visible", "true") )
|
||||||
|
keyBoardCheck()
|
||||||
|
})
|
||||||
|
|
||||||
|
const threshold = .02 // distance
|
||||||
|
const refractionPeriod = 500 // ms until next keypress
|
||||||
|
|
||||||
|
// add visible contact points
|
||||||
|
let jointTestEl1 = document.createElement("a-sphere")
|
||||||
|
jointTestEl1.setAttribute("radius", .01)
|
||||||
|
jointTestEl1.id = "jointtest1"
|
||||||
|
AFRAME.scenes[0].appendChild( jointTestEl1 )
|
||||||
|
let jointTestEl2 = document.createElement("a-sphere")
|
||||||
|
jointTestEl2.setAttribute("radius", .01)
|
||||||
|
jointTestEl2.id = "jointtest2"
|
||||||
|
AFRAME.scenes[0].appendChild( jointTestEl2 )
|
||||||
|
AFRAME.scenes[0].setAttribute("bind-element-to-finger__test1", { hand: 'r_handMeshNode', finger: 'index-finger-tip', target: '#jointtest1' } )
|
||||||
|
AFRAME.scenes[0].setAttribute("bind-element-to-finger__test2", { hand: 'l_handMeshNode', finger: 'index-finger-tip', target: '#jointtest2' } )
|
||||||
|
|
||||||
|
// thumb tip test
|
||||||
|
let jointTestEl3 = document.createElement("a-sphere")
|
||||||
|
jointTestEl3.setAttribute("radius", .01)
|
||||||
|
jointTestEl3.id = "jointtest3"
|
||||||
|
AFRAME.scenes[0].appendChild( jointTestEl3 )
|
||||||
|
let jointTestEl4 = document.createElement("a-sphere")
|
||||||
|
jointTestEl4.setAttribute("radius", .01)
|
||||||
|
jointTestEl4.id = "jointtest4"
|
||||||
|
AFRAME.scenes[0].appendChild( jointTestEl4 )
|
||||||
|
AFRAME.scenes[0].setAttribute("bind-element-to-finger__test3", { hand: 'r_handMeshNode', finger: 'thumb-tip', target: '#jointtest3' } )
|
||||||
|
AFRAME.scenes[0].setAttribute("bind-element-to-finger__test4", { hand: 'l_handMeshNode', finger: 'thumb-tip', target: '#jointtest4' } )
|
||||||
|
|
||||||
|
const forcecontrollers = urlParams.get('forcecontrollers');
|
||||||
|
if (forcecontrollers){
|
||||||
|
setTimeout( _ => {
|
||||||
|
jointtest1.object3D.parent = document.querySelector("[meta-touch-controls]").object3D
|
||||||
|
jointtest2.object3D.parent = document.querySelector("[oculus-touch-controls]").object3D
|
||||||
|
}, 2000 )
|
||||||
|
// TODO does not work with pen
|
||||||
|
// see <a-entity logiteck-mx-ink-controls="hand: right"></a-entity> that does not seem to show anything, even in AFrame 1.7.1 nor recent master build
|
||||||
|
}
|
||||||
|
|
||||||
|
// if done a la bind-element-to-finger consider
|
||||||
|
// const joint = AFRAME.scenes[0].object3D.getObjectByName(this.data.hand)?.parent.getObjectByName(this.data.finger)
|
||||||
|
// if ( joint && this.data.target.object3D.parent == AFRAME.scenes[0].object3D ) this.data.target.object3D.parent = joint
|
||||||
|
|
||||||
|
let pos1 = new THREE.Vector3()
|
||||||
|
let pos2 = new THREE.Vector3()
|
||||||
|
let pos3 = new THREE.Vector3()
|
||||||
|
let pos4 = new THREE.Vector3()
|
||||||
|
|
||||||
|
let shiftFromVirtualKeyboard = true
|
||||||
|
let layerFromVirtualKeyboard = 0
|
||||||
|
|
||||||
|
window.keyboardTarget = typinghud
|
||||||
|
|
||||||
|
// check for potential contact
|
||||||
|
let lastKeypress = Date.now()
|
||||||
|
function keyBoardCheck() {
|
||||||
|
return setInterval( _ => {
|
||||||
|
jointTestEl1.object3D.getWorldPosition( pos1 )
|
||||||
|
jointTestEl2.object3D.getWorldPosition( pos2 )
|
||||||
|
jointTestEl3.object3D.getWorldPosition( pos3 )
|
||||||
|
jointTestEl4.object3D.getWorldPosition( pos4 )
|
||||||
|
// could also check only when jointTestEl1 / jointTestEl2 are visible, ignore otherwise
|
||||||
|
|
||||||
|
// to do with all keys instead
|
||||||
|
Array.from( document.querySelectorAll(".keys_from_drumsticks"+".layer_"+layerFromVirtualKeyboard) )
|
||||||
|
.concat( [keys_from_drumsticks_0_LWR, keys_from_drumsticks_0_RSE] )
|
||||||
|
.filter( k => k.getAttribute("visible") )
|
||||||
|
.map( k => {
|
||||||
|
// should only look at visible keys, could limit via .filter( k => k.getAttribute("visible") == "true")
|
||||||
|
// this way one wouldn't type on an invisible keyboard
|
||||||
|
let d1 = k.object3D.position.distanceTo( pos1 )
|
||||||
|
let d2 = k.object3D.position.distanceTo( pos2 )
|
||||||
|
let d3 = k.object3D.position.distanceTo( pos3 )
|
||||||
|
let d4 = k.object3D.position.distanceTo( pos4 )
|
||||||
|
if ( d1 < threshold || d2 < threshold || d3 < threshold || d4 < threshold) {
|
||||||
|
//if ( d1 < threshold || d2 < threshold) {
|
||||||
|
k.setAttribute("color", "pink")
|
||||||
|
if (keyboardTarget == typinghud) typinghud.setAttribute("material","opacity", .5)
|
||||||
|
if ( Date.now() - lastKeypress < refractionPeriod ){
|
||||||
|
// console.warn('ignoring, executed during the last 500ms already')
|
||||||
|
let x = 42 // added just to ignore
|
||||||
|
} else {
|
||||||
|
lastKeypress = Date.now()
|
||||||
|
let value = k.firstChild.getAttribute("value")
|
||||||
|
if ( keyboardTarget.getAttribute("value") == "[]" ) keyboardTarget.setAttribute("value", "" ) // for typinghud starting value
|
||||||
|
if (value == "TAB") console.warn('does not complete yet') // TODO add completion
|
||||||
|
if (value == "SPC") value = " "
|
||||||
|
if (value == "ENT") {
|
||||||
|
if (keyboardTarget == typinghud) {
|
||||||
|
parseKeys("keydown", "Enter")
|
||||||
|
keyboardTarget.setAttribute("value", "" )
|
||||||
|
} else {
|
||||||
|
keyboardTarget.setAttribute("value", keyboardTarget.getAttribute("value") + '\n' )
|
||||||
|
}
|
||||||
|
} else if (value == "SHFT") {
|
||||||
|
shiftFromVirtualKeyboard = !shiftFromVirtualKeyboard
|
||||||
|
// visual highlight, also note that is closer to CAPSLOCK behavior
|
||||||
|
keys_from_drumsticks_0_SHFT.setAttribute("wireframe", shiftFromVirtualKeyboard) // arguable, could do so for all layers
|
||||||
|
} else if (value == "RSE") {
|
||||||
|
if (layerFromVirtualKeyboard<2) layerFromVirtualKeyboard++ // hardcoded max
|
||||||
|
console.log('should raise layer', layerFromVirtualKeyboard) // a la CAPSLOCK too
|
||||||
|
Array.from( document.querySelectorAll(".keys_from_drumsticks") ).map( k => k.setAttribute("visible", "false") )
|
||||||
|
Array.from( document.querySelectorAll(".keys_from_drumsticks"+".layer_"+layerFromVirtualKeyboard) ).map( k => k.setAttribute("visible", "true") )
|
||||||
|
// forcing visibility yet get ignored as on wrong layer
|
||||||
|
keys_from_drumsticks_0_LWR.setAttribute("visible", "true")
|
||||||
|
keys_from_drumsticks_0_RSE.setAttribute("visible", "true")
|
||||||
|
} else if (value == "LWR") {
|
||||||
|
if (layerFromVirtualKeyboard>0) layerFromVirtualKeyboard--
|
||||||
|
console.log('should lower layer', layerFromVirtualKeyboard) // a la CAPSLOCK too
|
||||||
|
Array.from( document.querySelectorAll(".keys_from_drumsticks") ).map( k => k.setAttribute("visible", "false") )
|
||||||
|
Array.from( document.querySelectorAll(".keys_from_drumsticks"+".layer_"+layerFromVirtualKeyboard) ).map( k => k.setAttribute("visible", "true") )
|
||||||
|
keys_from_drumsticks_0_LWR.setAttribute("visible", "true")
|
||||||
|
keys_from_drumsticks_0_RSE.setAttribute("visible", "true")
|
||||||
|
} else if (value == "BKSP") {
|
||||||
|
keyboardTarget.setAttribute("value", keyboardTarget.getAttribute("value").slice(0,-1) )
|
||||||
|
} else {
|
||||||
|
if (!shiftFromVirtualKeyboard) value = value.toLowerCase()
|
||||||
|
keyboardTarget.setAttribute("value", keyboardTarget.getAttribute("value") + value )
|
||||||
|
}
|
||||||
|
keyboardEl.emit( "keypressed", {key: value} )
|
||||||
|
}
|
||||||
|
} else if ( d1 < threshold*1.2 || d2 < threshold*1.2) { // arguably, not convinced it brings value more than confusion
|
||||||
|
k.setAttribute("color", "#ffe4e1")
|
||||||
|
} else {
|
||||||
|
k.setAttribute("color", "white")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyboardEl
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
2
drumkeyboard/root/scene-drumkeyboard/index.html
Executable file
2
drumkeyboard/root/scene-drumkeyboard/index.html
Executable file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/html
|
||||||
|
<a-text value="drumkeyboard demo scene" position="-0.5 1.5 -2.5"></a-text>
|
||||||
22
drumkeyboard/root/scene-drumkeyboard/index.js
Normal file
22
drumkeyboard/root/scene-drumkeyboard/index.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
AFRAME.registerComponent('bind-element-to-finger', {
|
||||||
|
multiple: true,
|
||||||
|
schema: {
|
||||||
|
hand: {type: 'string', default: 'r_handMeshNode'},
|
||||||
|
finger: {type: 'string', default: 'index-finger-tip'},
|
||||||
|
target : {type: 'selector'},
|
||||||
|
},
|
||||||
|
tick: function (time, timeDelta) {
|
||||||
|
const joint = AFRAME.scenes[0].object3D.getObjectByName(this.data.hand)?.parent.getObjectByName(this.data.finger)
|
||||||
|
if ( joint && this.data.target.object3D.parent == AFRAME.scenes[0].object3D ) this.data.target.object3D.parent = joint
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
window.keyboardTarget = typinghud
|
||||||
|
let kbd = addDrumKeyboard()
|
||||||
|
kbd.addEventListener( "keypressed", e => {
|
||||||
|
document.querySelector("[isoterminal]").components.isoterminal.term.send(e.detail.key)
|
||||||
|
// consider executing the content on ENTER
|
||||||
|
// document.querySelector("[isoterminal]").components.isoterminal.term.exec("ls")
|
||||||
|
// raw keypress, this getting SHFT, ENT, etc
|
||||||
|
})
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue