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 |
|
||||
|-|-|
|
||||
| [drumkeyboard](drumkeyboard) | A customizable keyboard component |
|
||||
| [fzy](fzy) | lightweight + fast fuzzyfinder alternative |
|
||||
| [joe](joe) | Set of battleproof lightweight text/code editors |
|
||||
| [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