315 lines
8.2 KiB
Plaintext
315 lines
8.2 KiB
Plaintext
// Router - Pipeline for processing input through stages
|
|
var time = use('time')
|
|
|
|
// Valid emacs keys
|
|
var valid_emacs_keys = [
|
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
|
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
|
'return', 'enter', 'space', 'escape', 'tab', 'backspace', 'delete',
|
|
'up', 'down', 'left', 'right', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
|
|
]
|
|
|
|
var emacs_special = {
|
|
'return': 'RET', 'enter': 'RET', 'space': 'SPC', 'escape': 'ESC',
|
|
'tab': 'TAB', 'backspace': 'DEL', 'delete': 'delete'
|
|
}
|
|
|
|
// Gesture stage - detects swipes and pinches from touchpad
|
|
function gesture_stage(config) {
|
|
config = config || {}
|
|
var min_swipe = config.swipe_min_dist || 30
|
|
var max_time = config.swipe_max_time || 500
|
|
var pinch_th = config.pinch_threshold || 10
|
|
|
|
var touches = {}
|
|
var gesture_state = null
|
|
var start_dist = 0
|
|
|
|
function dist(p1, p2) {
|
|
var dx = p2[0] - p1[0]
|
|
var dy = p2[1] - p1[1]
|
|
return Math.sqrt(dx * dx + dy * dy)
|
|
}
|
|
|
|
return {
|
|
process: function(events) {
|
|
var output = []
|
|
|
|
for (var i = 0; i < length(events); i++) {
|
|
var ev = events[i]
|
|
|
|
// Only process gamepad touchpad events
|
|
if (ev.control != 'gamepad_touchpad') {
|
|
push(output, ev)
|
|
continue
|
|
}
|
|
|
|
var fingerId = (ev.touchpad || 0) + '_' + (ev.finger || 0)
|
|
|
|
if (ev.pressed) {
|
|
touches[fingerId] = {
|
|
pos: ev.pos,
|
|
startPos: ev.pos,
|
|
startTime: time.number()
|
|
}
|
|
|
|
var count = length(array(touches))
|
|
if (count == 1) gesture_state = 'single'
|
|
else if (count == 2) {
|
|
gesture_state = 'multi'
|
|
var fingers = Object.values(touches)
|
|
start_dist = dist(fingers[0].pos, fingers[1].pos)
|
|
}
|
|
}
|
|
else if (ev.kind == 'axis') {
|
|
if (touches[fingerId]) {
|
|
touches[fingerId].pos = ev.pos
|
|
|
|
var count = length(array(touches))
|
|
if (count == 2 && gesture_state == 'multi') {
|
|
var fingers = Object.values(touches)
|
|
var currentDist = dist(fingers[0].pos, fingers[1].pos)
|
|
var d = currentDist - start_dist
|
|
|
|
if (Math.abs(d) >= pinch_th / 100) {
|
|
push(output, {
|
|
kind: 'gesture',
|
|
device_id: ev.device_id,
|
|
control: d > 0 ? 'pinch_out' : 'pinch_in',
|
|
pressed: true,
|
|
released: false,
|
|
delta: d,
|
|
time: ev.time
|
|
})
|
|
start_dist = currentDist
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (ev.released) {
|
|
if (touches[fingerId]) {
|
|
var touch = touches[fingerId]
|
|
var count = length(array(touches))
|
|
|
|
if (count == 1 && gesture_state == 'single') {
|
|
var dt = (time.number() - touch.startTime) * 1000
|
|
var dx = ev.pos[0] - touch.startPos[0]
|
|
var dy = ev.pos[1] - touch.startPos[1]
|
|
|
|
if (dt < max_time) {
|
|
var absX = Math.abs(dx), absY = Math.abs(dy)
|
|
if (absX > min_swipe / 100 || absY > min_swipe / 100) {
|
|
var dir = absX > absY
|
|
? (dx > 0 ? 'swipe_right' : 'swipe_left')
|
|
: (dy > 0 ? 'swipe_down' : 'swipe_up')
|
|
|
|
push(output, {
|
|
kind: 'gesture',
|
|
device_id: ev.device_id,
|
|
control: dir,
|
|
pressed: true,
|
|
released: false,
|
|
time: ev.time
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
delete touches[fingerId]
|
|
if (length(array(touches)) == 0) gesture_state = null
|
|
}
|
|
}
|
|
}
|
|
|
|
return output
|
|
}
|
|
}
|
|
}
|
|
|
|
// Emacs stage - converts keyboard input to emacs chords
|
|
function emacs_stage() {
|
|
var prefix = null
|
|
|
|
return {
|
|
process: function(events) {
|
|
var output = []
|
|
|
|
for (var i = 0; i < length(events); i++) {
|
|
var ev = events[i]
|
|
|
|
// Only process keyboard button events
|
|
if (ev.device_id != 'kbm' || ev.kind != 'button' || !ev.pressed) {
|
|
push(output, ev)
|
|
continue
|
|
}
|
|
|
|
if (find(valid_emacs_keys, ev.control) == null) {
|
|
push(output, ev)
|
|
continue
|
|
}
|
|
|
|
// Only process if we have modifiers OR waiting for chord
|
|
if (!ev.mods?.ctrl && !ev.mods?.alt && !prefix) {
|
|
push(output, ev)
|
|
continue
|
|
}
|
|
|
|
var notation = ""
|
|
if (ev.mods?.ctrl) notation += "C-"
|
|
if (ev.mods?.alt) notation += "M-"
|
|
|
|
if (length(ev.control) == 1) {
|
|
notation += lower(ev.control)
|
|
} else {
|
|
notation += emacs_special[ev.control] || ev.control
|
|
}
|
|
|
|
// Handle prefix keys
|
|
if (notation == "C-x" || notation == "C-c") {
|
|
prefix = notation
|
|
continue // Consume, don't output
|
|
}
|
|
|
|
// Complete chord if we have prefix
|
|
if (prefix) {
|
|
var chord = prefix + " " + notation
|
|
prefix = null
|
|
push(output, {
|
|
kind: 'chord',
|
|
device_id: ev.device_id,
|
|
control: chord,
|
|
pressed: true,
|
|
released: false,
|
|
time: ev.time
|
|
})
|
|
} else {
|
|
push(output, {
|
|
kind: 'chord',
|
|
device_id: ev.device_id,
|
|
control: notation,
|
|
pressed: true,
|
|
released: false,
|
|
time: ev.time
|
|
})
|
|
}
|
|
}
|
|
|
|
return output
|
|
}
|
|
}
|
|
}
|
|
|
|
// Action mapping stage - converts controls to named actions
|
|
function action_stage(bindings) {
|
|
var down = {}
|
|
|
|
return {
|
|
down: down,
|
|
process: function(events) {
|
|
var output = []
|
|
|
|
for (var i = 0; i < length(events); i++) {
|
|
var ev = events[i]
|
|
|
|
// Pass through non-button events
|
|
if (ev.kind != 'button' && ev.kind != 'chord' && ev.kind != 'gesture') {
|
|
push(output, ev)
|
|
continue
|
|
}
|
|
|
|
var actions = bindings.get_actions(ev.control)
|
|
|
|
if (length(actions) == 0) {
|
|
push(output, ev)
|
|
continue
|
|
}
|
|
|
|
for (var j = 0; j < length(actions); j++) {
|
|
var action = actions[j]
|
|
|
|
if (ev.pressed) down[action] = true
|
|
else if (ev.released) down[action] = false
|
|
|
|
push(output, {
|
|
kind: 'action',
|
|
device_id: ev.device_id,
|
|
control: action,
|
|
pressed: ev.pressed,
|
|
released: ev.released,
|
|
time: ev.time,
|
|
original: ev
|
|
})
|
|
}
|
|
}
|
|
|
|
return output
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delivery stage - dispatches to possessed entity
|
|
function delivery_stage(user) {
|
|
return {
|
|
process: function(events) {
|
|
for (var i = 0; i < length(events); i++) {
|
|
var ev = events[i]
|
|
|
|
// Only deliver actions
|
|
if (ev.kind != 'action') continue
|
|
|
|
user.dispatch(ev.control, {
|
|
pressed: ev.pressed,
|
|
released: ev.released,
|
|
time: ev.time
|
|
})
|
|
}
|
|
|
|
return events
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create router with pipeline
|
|
function make(user, config) {
|
|
config = config || {}
|
|
|
|
var stages = []
|
|
var action = null
|
|
|
|
if (config.gestures != false) {
|
|
push(stages, gesture_stage(config))
|
|
}
|
|
|
|
if (config.emacs != false) {
|
|
push(stages, emacs_stage())
|
|
}
|
|
|
|
action = action_stage(user.bindings)
|
|
push(stages, action)
|
|
push(stages, delivery_stage(user))
|
|
|
|
return {
|
|
stages: stages,
|
|
|
|
get down() { return action.down },
|
|
|
|
handle: function(canon) {
|
|
var events = [canon]
|
|
|
|
for (var i = 0; i < length(this.stages); i++) {
|
|
events = this.stages[i].process(events)
|
|
}
|
|
|
|
return events
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
make: make,
|
|
gesture_stage: gesture_stage,
|
|
emacs_stage: emacs_stage,
|
|
action_stage: action_stage,
|
|
delivery_stage: delivery_stage
|
|
}
|