fix syntax

This commit is contained in:
2026-02-17 09:15:15 -06:00
parent f310c18b84
commit 4e1b63fd0e
52 changed files with 2169 additions and 1754 deletions

View File

@@ -22,13 +22,15 @@ function parse_mods(mod) {
// Translate SDL event to canonical format
// Returns canonical event or null if not translatable
function translate(evt) {
var control = null
// Keyboard
if (evt.type == 'key_down' || evt.type == 'key_up') {
if (evt.repeat) return null // Ignore key repeats
var control = keycode_to_control(evt.key)
control = keycode_to_control(evt.key)
if (!control) return null
return {
kind: 'button',
device_id: 'kbm',

View File

@@ -17,30 +17,30 @@ var controller_map = {
joyconpair: 'joyconpair'
}
function make(defaults, display_names) {
defaults = defaults || {}
display_names = display_names || {}
function make(defs, names) {
var d = defs || {}
var n = names || {}
var bindings = {
action_map: {},
_defaults: {},
display_names: display_names,
display_names: n,
is_rebinding: false,
rebind_target: null
}
// Copy defaults
arrfor(array(defaults), function(key){
var val = defaults[key]
arrfor(array(d), function(key){
var val = d[key]
bindings.action_map[key] = is_array(val) ? array(val) : [val]
bindings._defaults[key] = array(bindings.action_map[key])
})
// Get actions that match a control
bindings.get_actions = function(control) {
return filter(array(this.action_map), action => find(this.action_map[action], control) != null)
}
// Get bindings for a specific device kind
bindings.get_bindings_for_device = function(action, device_kind) {
var all = this.action_map[action] || []
@@ -57,69 +57,76 @@ function make(defaults, display_names) {
return true
})
}
// Get primary binding for display
bindings.get_primary_binding = function(action, device_kind) {
var device_bindings = this.get_bindings_for_device(action, device_kind)
if (length(device_bindings)) return device_bindings[0]
var all = this.action_map[action] || []
return length(all) > 0 ? all[0] : null
}
// Get icon for action
bindings.get_icon_for_action = function(action, device_kind, gamepad_type) {
var binding = this.get_primary_binding(action, device_kind)
var button = null
var key_map = null
var key = null
var gp_prefix = null
var is_ps = null
var gamepad_map = null
var icon = null
if (!binding) return null
if (device_kind == 'keyboard') {
if (starts_with(binding, 'mouse_button_')) {
var button = replace(binding, 'mouse_button_', '')
button = replace(binding, 'mouse_button_', '')
return 'ui/mouse/mouse_' + button + '.png'
} else {
var key_map = {
key_map = {
'escape': 'escape', 'return': 'return', 'space': 'space',
'up': 'arrow_up', 'down': 'arrow_down', 'left': 'arrow_left', 'right': 'arrow_right'
}
var key = key_map[binding] || binding
key = key_map[binding] || binding
return 'ui/keyboard/keyboard_' + key + '.png'
}
}
if (device_kind == 'gamepad' && gamepad_type) {
var prefix = controller_map[gamepad_type] || 'playstation'
var is_ps = find(['ps3', 'ps4', 'ps5'], gamepad_type) != null
var gamepad_map = {
gp_prefix = controller_map[gamepad_type] || 'playstation'
is_ps = find(['ps3', 'ps4', 'ps5'], gamepad_type) != null
gamepad_map = {
'gamepad_a': is_ps ? 'playstation_button_cross' : 'xbox_button_a',
'gamepad_b': is_ps ? 'playstation_button_circle' : 'xbox_button_b',
'gamepad_x': is_ps ? 'playstation_button_square' : 'xbox_button_x',
'gamepad_y': is_ps ? 'playstation_button_triangle' : 'xbox_button_y',
'gamepad_dpup': prefix + '_dpad_up',
'gamepad_dpdown': prefix + '_dpad_down',
'gamepad_dpleft': prefix + '_dpad_left',
'gamepad_dpright': prefix + '_dpad_right',
'gamepad_l1': prefix + '_trigger_l1',
'gamepad_r1': prefix + '_trigger_r1',
'gamepad_l2': prefix + '_trigger_l2',
'gamepad_r2': prefix + '_trigger_r2',
'gamepad_dpup': gp_prefix + '_dpad_up',
'gamepad_dpdown': gp_prefix + '_dpad_down',
'gamepad_dpleft': gp_prefix + '_dpad_left',
'gamepad_dpright': gp_prefix + '_dpad_right',
'gamepad_l1': gp_prefix + '_trigger_l1',
'gamepad_r1': gp_prefix + '_trigger_r1',
'gamepad_l2': gp_prefix + '_trigger_l2',
'gamepad_r2': gp_prefix + '_trigger_r2',
'gamepad_start': this._get_start_icon(gamepad_type)
}
var icon = gamepad_map[binding]
if (icon) return 'ui/' + prefix + '/' + icon + '.png'
icon = gamepad_map[binding]
if (icon) return 'ui/' + gp_prefix + '/' + icon + '.png'
}
return null
}
bindings._get_start_icon = function(gamepad_type) {
if (gamepad_type == 'ps3') return 'playstation3_button_start'
if (gamepad_type == 'ps4') return 'playstation4_button_options'
if (gamepad_type == 'ps5') return 'playstation5_button_options'
return 'xbox_button_start'
}
// Start rebinding
bindings.start_rebind = function(action) {
if (!this.action_map[action]) return false
@@ -127,73 +134,75 @@ function make(defaults, display_names) {
this.rebind_target = action
return true
}
// Complete rebinding with new control
bindings.rebind = function(action, new_control, device_kind) {
var target = null
var i = 0
var existing_kind = null
if (!this.action_map[action]) return false
// Remove from other actions
arrfor(array(this.action_map), act => {
var idx = search(this.action_map[act], new_control)
if (idx >= 0)
this.action_map[act] = array(array(this.action_map[act], 0, idx), array(this.action_map[act], idx+1))
})
// Clear existing bindings for this device kind
var target = this.action_map[action]
for (var i = length(target) - 1; i >= 0; i--) {
var existing_kind = starts_with(target[i], 'gamepad_') ? 'gamepad' :
starts_with(target[i], 'swipe_') ? 'touch' : 'keyboard'
target = this.action_map[action]
for (i = length(target) - 1; i >= 0; i--) {
existing_kind = starts_with(target[i], 'gamepad_') ? 'gamepad' :
starts_with(target[i], 'swipe_') ? 'touch' : 'keyboard'
if (existing_kind == device_kind)
this.action_map[action] = array(array(this.action_map[action], 0, i), array(this.action_map[action], i+1))
}
// Add new binding
this.action_map[action].unshift(new_control)
this.is_rebinding = false
this.rebind_target = null
return true
}
// Cancel rebinding
bindings.cancel_rebind = function() {
this.is_rebinding = false
this.rebind_target = null
}
// Save bindings
bindings.save = function(path) {
path = path || 'keybindings.json'
try {
io.slurpwrite(path, json.encode(this.action_map))
return true
} catch(e) {
return false
}
}
// Load bindings
bindings.load = function(path) {
path = path || 'keybindings.json'
try {
if (io.exists(path)) {
var data = json.decode(io.slurp(path))
arrfor(array(data), key => {
if (this.action_map[key]) this.action_map[key] = data[key]
})
return true
}
} catch(e) {}
bindings.save = function(p) {
var save_path = p || 'keybindings.json'
io.slurpwrite(save_path, json.encode(this.action_map))
return true
} disruption {
return false
}
// Load bindings
bindings.load = function(p) {
var load_path = p || 'keybindings.json'
var data = null
if (io.exists(load_path)) {
data = json.decode(io.slurp(load_path))
arrfor(array(data), key => {
if (this.action_map[key]) this.action_map[key] = data[key]
})
return true
}
return false
} disruption {
return false
}
// Reset to defaults
bindings.reset = function() {
arrfor(array(this._defaults), key => {
this.action_map[key] = array(this._defaults[key])
})
}
return bindings
}

View File

@@ -6,12 +6,13 @@ var _devices = {}
// Register a device from a canonical event
function register(canon) {
if (!canon || !canon.device_id) return
var kind = null
if (!_devices[canon.device_id]) {
var kind = 'keyboard'
kind = 'keyboard'
if (starts_with(canon.device_id, 'gp:')) kind = 'gamepad'
else if (starts_with(canon.device_id, 'touch:')) kind = 'touch'
_devices[canon.device_id] = {
id: canon.device_id,
kind: kind,

View File

@@ -15,62 +15,76 @@ var emacs_special = {
}
// 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
function gesture_stage(cfg) {
var o = cfg || {}
var min_swipe = o.swipe_min_dist || 30
var max_time = o.swipe_max_time || 500
var pinch_th = o.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]
var i = 0
var ev = null
var fingerId = null
var count = 0
var fingers = null
var currentDist = 0
var d = 0
var touch = null
var dt = 0
var dx = 0
var dy = 0
var absX = 0
var absY = 0
var dir = null
for (i = 0; i < length(events); i++) {
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)
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))
count = length(array(touches))
if (count == 1) gesture_state = 'single'
else if (count == 2) {
gesture_state = 'multi'
var fingers = Object.values(touches)
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))
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
fingers = Object.values(touches)
currentDist = dist(fingers[0].pos, fingers[1].pos)
d = currentDist - start_dist
if (Math.abs(d) >= pinch_th / 100) {
push(output, {
kind: 'gesture',
@@ -88,21 +102,22 @@ function gesture_stage(config) {
}
else if (ev.released) {
if (touches[fingerId]) {
var touch = touches[fingerId]
var count = length(array(touches))
touch = touches[fingerId]
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]
dt = (time.number() - touch.startTime) * 1000
dx = ev.pos[0] - touch.startPos[0]
dy = ev.pos[1] - touch.startPos[1]
if (dt < max_time) {
var absX = Math.abs(dx), absY = Math.abs(dy)
absX = Math.abs(dx)
absY = Math.abs(dy)
if (absX > min_swipe / 100 || absY > min_swipe / 100) {
var dir = absX > absY
dir = absX > absY
? (dx > 0 ? 'swipe_right' : 'swipe_left')
: (dy > 0 ? 'swipe_down' : 'swipe_up')
push(output, {
kind: 'gesture',
device_id: ev.device_id,
@@ -114,13 +129,13 @@ function gesture_stage(config) {
}
}
}
delete touches[fingerId]
if (length(array(touches)) == 0) gesture_state = null
}
}
}
return output
}
}
@@ -129,50 +144,54 @@ function gesture_stage(config) {
// 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]
var i = 0
var ev = null
var notation = null
var chord = null
for (i = 0; i < length(events); i++) {
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 = ""
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
chord = prefix + " " + notation
prefix = null
push(output, {
kind: 'chord',
@@ -193,7 +212,7 @@ function emacs_stage() {
})
}
}
return output
}
}
@@ -202,34 +221,39 @@ function emacs_stage() {
// 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]
var i = 0
var ev = null
var actions = null
var j = 0
var action = null
for (i = 0; i < length(events); i++) {
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)
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]
for (j = 0; j < length(actions); j++) {
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,
@@ -241,7 +265,7 @@ function action_stage(bindings) {
})
}
}
return output
}
}
@@ -251,55 +275,59 @@ function action_stage(bindings) {
function delivery_stage(user) {
return {
process: function(events) {
for (var i = 0; i < length(events); i++) {
var ev = events[i]
var i = 0
var ev = null
for (i = 0; i < length(events); i++) {
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 || {}
function make(user, cfg) {
var o = cfg || {}
var stages = []
var action = null
if (config.gestures != false) {
push(stages, gesture_stage(config))
if (o.gestures != false) {
push(stages, gesture_stage(o))
}
if (config.emacs != false) {
if (o.emacs != false) {
push(stages, emacs_stage())
}
action = action_stage(user.bindings)
push(stages, action)
push(stages, delivery_stage(user))
return {
stages: stages,
down() { return action.down },
handle: function(canon) {
var events = [canon]
for (var i = 0; i < length(this.stages); i++) {
var i = 0
for (i = 0; i < length(this.stages); i++) {
events = this.stages[i].process(events)
}
return events
}
}