1st commit fabien's drumkeyboard

This commit is contained in:
Leon van Kammen 2025-07-29 10:59:32 +02:00
parent bc030e2669
commit 3b7ce6daab
11 changed files with 287 additions and 0 deletions

View file

@ -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
View 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
View 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
View file

@ -0,0 +1,4 @@
#!/bin/sh
set -e
name=drumkeyboard
zip -r package.$name.zip root .env .env.leave

Binary file not shown.

View 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

View file

@ -0,0 +1 @@
echo "" > /root/index.html # cleanup

View 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
}

View file

@ -0,0 +1,2 @@
#!/bin/html
<a-text value="drumkeyboard demo scene" position="-0.5 1.5 -2.5"></a-text>

View 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
})