Rewrite keybinds module to function more like vanilla keybinds (#33)
* Rewrite keybinding module to work like vanilla keybinds * Treat left and right modifier keys separately * Fix minor crash bug * Don't close views if actively watching for keys
This commit is contained in:
parent
c617fc69b0
commit
124002bb1f
6 changed files with 350 additions and 374 deletions
|
@ -2,164 +2,16 @@ local dmf = get_mod("DMF")
|
|||
|
||||
local InputUtils = require("scripts/managers/input/input_utils")
|
||||
|
||||
local PRIMARY_BINDABLE_KEYS = {
|
||||
["keyboard"] = {
|
||||
[8] = {"Backspace", "backspace"},
|
||||
[9] = {"Tab", "tab"},
|
||||
[13] = {"Enter", "enter"},
|
||||
[20] = {"Caps Lock", "caps lock"},
|
||||
[32] = {"Space", "space"},
|
||||
[33] = {"Page Up", "page up"},
|
||||
[34] = {"Page Down", "page down"},
|
||||
[35] = {"End", "end"},
|
||||
[36] = {"Home", "home"},
|
||||
[37] = {"Left", "left"},
|
||||
[38] = {"Up", "up"},
|
||||
[39] = {"Right", "right"},
|
||||
[40] = {"Down", "down"},
|
||||
[45] = {"Insert", "insert"},
|
||||
[46] = {"Delete", "delete"},
|
||||
[48] = {"0", "0"},
|
||||
[49] = {"1", "1"},
|
||||
[50] = {"2", "2"},
|
||||
[51] = {"3", "3"},
|
||||
[52] = {"4", "4"},
|
||||
[53] = {"5", "5"},
|
||||
[54] = {"6", "6"},
|
||||
[55] = {"7", "7"},
|
||||
[56] = {"8", "8"},
|
||||
[57] = {"9", "9"},
|
||||
[65] = {"A", "a"},
|
||||
[66] = {"B", "b"},
|
||||
[67] = {"C", "c"},
|
||||
[68] = {"D", "d"},
|
||||
[69] = {"E", "e"},
|
||||
[70] = {"F", "f"},
|
||||
[71] = {"G", "g"},
|
||||
[72] = {"H", "h"},
|
||||
[73] = {"I", "i"},
|
||||
[74] = {"J", "j"},
|
||||
[75] = {"K", "k"},
|
||||
[76] = {"L", "l"},
|
||||
[77] = {"M", "m"},
|
||||
[78] = {"N", "n"},
|
||||
[79] = {"O", "o"},
|
||||
[80] = {"P", "p"},
|
||||
[81] = {"Q", "q"},
|
||||
[82] = {"R", "r"},
|
||||
[83] = {"S", "s"},
|
||||
[84] = {"T", "t"},
|
||||
[85] = {"U", "u"},
|
||||
[86] = {"V", "v"},
|
||||
[87] = {"W", "w"},
|
||||
[88] = {"X", "x"},
|
||||
[89] = {"Y", "y"},
|
||||
[90] = {"Z", "z"},
|
||||
[91] = {"Win", "win"},
|
||||
[92] = {"RWin", "right win"},
|
||||
[96] = {"Num 0", "numpad 0"},
|
||||
[97] = {"Num 1", "numpad 1"},
|
||||
[98] = {"Num 2", "numpad 2"},
|
||||
[99] = {"Num 3", "numpad 3"},
|
||||
[100] = {"Num 4", "numpad 4"},
|
||||
[101] = {"Num 5", "numpad 5"},
|
||||
[102] = {"Num 6", "numpad 6"},
|
||||
[103] = {"Num 7", "numpad 7"},
|
||||
[104] = {"Num 8", "numpad 8"},
|
||||
[105] = {"Num 9", "numpad 9"},
|
||||
[106] = {"Num *", "numpad *"},
|
||||
[107] = {"Num +", "numpad +"},
|
||||
[109] = {"Num -", "numpad -"},
|
||||
[110] = {"Num .", "numpad ."},
|
||||
[111] = {"Num /", "numpad /"},
|
||||
[112] = {"F1", "f1"},
|
||||
[113] = {"F2", "f2"},
|
||||
[114] = {"F3", "f3"},
|
||||
[115] = {"F4", "f4"},
|
||||
[116] = {"F5", "f5"},
|
||||
[117] = {"F6", "f6"},
|
||||
[118] = {"F7", "f7"},
|
||||
[119] = {"F8", "f8"},
|
||||
[120] = {"F9", "f9"},
|
||||
[121] = {"F10", "f10"},
|
||||
[122] = {"F11", "f11"},
|
||||
[123] = {"F12", "f12"},
|
||||
[144] = {"Num Lock", "num lock"},
|
||||
[145] = {"Scroll Lock", "scroll lock"},
|
||||
[166] = {"Browser Back", "browser back"},
|
||||
[167] = {"Browser Forward", "browser forward"},
|
||||
[168] = {"Browser Refresh", "browser refresh"},
|
||||
[169] = {"Browser Stop", "browser stop"},
|
||||
[170] = {"Browser Search", "browser search"},
|
||||
[171] = {"Browser Favorites", "browser favorites"},
|
||||
[172] = {"Browser Home", "browser home"},
|
||||
[173] = {"Volume Mute", "volume mute"},
|
||||
[174] = {"Volume Down", "volume down"},
|
||||
[175] = {"Volume Up", "volume up"},
|
||||
[176] = {"Next Track", "next track"},
|
||||
[177] = {"Previous Track", "previous track"},
|
||||
[178] = {"Stop", "stop"},
|
||||
[179] = {"Play/Pause", "play pause"},
|
||||
[180] = {"Mail", "mail"},
|
||||
[181] = {"Media", "media"},
|
||||
[182] = {"Start Application 1", "start app 1"},
|
||||
[183] = {"Start Application 2", "start app 2"},
|
||||
[186] = {";", ";"},
|
||||
[187] = {"=", "="},
|
||||
[188] = {",", ","},
|
||||
[189] = {"-", "-"},
|
||||
[190] = {".", "."},
|
||||
[191] = {"/", "/"},
|
||||
[192] = {"`", "`"},
|
||||
[219] = {"[", "["},
|
||||
[220] = {"\\", "\\"},
|
||||
[221] = {"]", "]"},
|
||||
[222] = {"'", "'"},
|
||||
--?[226] = {"\", "oem_102 (> <)"},
|
||||
[256] = {"Num Enter", "numpad enter"}
|
||||
},
|
||||
["mouse"] = {
|
||||
[0] = {"Mouse Left", "mouse left"},
|
||||
[1] = {"Mouse Right", "mouse right"},
|
||||
[2] = {"Mouse Middle", "mouse middle"},
|
||||
[3] = {"Mouse Extra 1", "mouse extra 1"},
|
||||
[4] = {"Mouse Extra 2", "mouse extra 2"},
|
||||
[10] = {"Mouse Wheel Up", "mouse wheel up"},
|
||||
[11] = {"Mouse Wheel Down", "mouse wheel down"},
|
||||
[12] = {"Mouse Wheel Left", "mouse wheel left"},
|
||||
[13] = {"Mouse Wheel Right", "mouse wheel right"}
|
||||
},
|
||||
--[[ -- will work on this if it will be needed
|
||||
["gamepad"] = {
|
||||
[0] = {"", "d_up"},
|
||||
[1] = {"", "d_down"},
|
||||
[2] = {"", "d_left"},
|
||||
[3] = {"", "d_right"},
|
||||
[4] = {"", "start"},
|
||||
[5] = {"", "back"},
|
||||
[6] = {"", "left_thumb"},
|
||||
[7] = {"", "right_thumb"},
|
||||
[8] = {"", "left_shoulder"},
|
||||
[9] = {"", "right_shoulder"},
|
||||
[10] = {"", "left_trigger"},
|
||||
[11] = {"", "right_trigger"},
|
||||
[12] = {"", "a"},
|
||||
[13] = {"", "b"},
|
||||
[14] = {"", "x"},
|
||||
[15] = {"", "y"},
|
||||
}]]
|
||||
}
|
||||
|
||||
local MODIFIER_KEYS = {
|
||||
["left shift"] = {160, "shift", "keyboard", 161},
|
||||
["right shift"] = {160, "shift", "keyboard", 161},
|
||||
["shift"] = {160, "shift", "keyboard", 161},
|
||||
["left ctrl"] = {162, "ctrl", "keyboard", 163},
|
||||
["right ctrl"] = {162, "ctrl", "keyboard", 163},
|
||||
["ctrl"] = {162, "ctrl", "keyboard", 163},
|
||||
["left alt"] = {164, "alt", "keyboard", 165},
|
||||
["right alt"] = {164, "alt", "keyboard", 165},
|
||||
["alt"] = {164, "alt", "keyboard", 165},
|
||||
["keyboard_left shift"] = {160, "shift", "keyboard", 161},
|
||||
["keyboard_right shift"] = {160, "shift", "keyboard", 161},
|
||||
["keyboard_shift"] = {160, "shift", "keyboard", 161},
|
||||
["keyboard_left ctrl"] = {162, "ctrl", "keyboard", 163},
|
||||
["keyboard_right ctrl"] = {162, "ctrl", "keyboard", 163},
|
||||
["keyboard_ctrl"] = {162, "ctrl", "keyboard", 163},
|
||||
["keyboard_left alt"] = {164, "alt", "keyboard", 165},
|
||||
["keyboard_right alt"] = {164, "alt", "keyboard", 165},
|
||||
["keyboard_alt"] = {164, "alt", "keyboard", 165},
|
||||
}
|
||||
|
||||
-- Both are treated equally in keybinds, but these global keys aren't localized
|
||||
|
@ -169,43 +21,8 @@ local MODIFIER_KEYS_LOC_ALIAS = {
|
|||
keyboard_shift = "keyboard_left shift",
|
||||
}
|
||||
|
||||
local KEYS_INFO = {}
|
||||
|
||||
-- Populate KEYS_INFO: Index, readable name, device name
|
||||
for input_device_name, input_device_keys in pairs(PRIMARY_BINDABLE_KEYS) do
|
||||
for key_index, key_info in pairs(input_device_keys) do
|
||||
KEYS_INFO[key_info[2]] = {key_index, key_info[1], input_device_name}
|
||||
end
|
||||
end
|
||||
|
||||
for key_id, key_data in pairs(MODIFIER_KEYS) do
|
||||
KEYS_INFO[key_id] = key_data
|
||||
end
|
||||
|
||||
-- Can't use 'Device.released' because it will break keybinds if button is released when game window is not active.
|
||||
local CHECK_INPUT_FUNCTIONS = {
|
||||
keyboard = {
|
||||
PRESSED = function(key_id) return Keyboard.pressed(KEYS_INFO[key_id][1]) end,
|
||||
RELEASED = function(key_id) return Keyboard.button(KEYS_INFO[key_id][1]) == 0 end
|
||||
},
|
||||
mouse = {
|
||||
PRESSED = function(key_id) return Mouse.pressed(KEYS_INFO[key_id][1]) end,
|
||||
RELEASED = function(key_id) return Mouse.button(KEYS_INFO[key_id][1]) == 0 end
|
||||
}
|
||||
}
|
||||
|
||||
local KEYS_INPUT_FUNCTIONS = {}
|
||||
|
||||
for key_id, key_data in pairs(KEYS_INFO) do
|
||||
KEYS_INPUT_FUNCTIONS[key_id] = {
|
||||
check_pressed = CHECK_INPUT_FUNCTIONS[key_data[3]].PRESSED,
|
||||
check_released = CHECK_INPUT_FUNCTIONS[key_data[3]].RELEASED
|
||||
}
|
||||
end
|
||||
|
||||
local _raw_keybinds_data = {}
|
||||
local _keybinds = {}
|
||||
local _pressed_key
|
||||
|
||||
local ERRORS = {
|
||||
PREFIX = {
|
||||
|
@ -216,12 +33,17 @@ local ERRORS = {
|
|||
}
|
||||
}
|
||||
|
||||
local SUPPORTED_DEVICES = {
|
||||
"keyboard",
|
||||
"mouse",
|
||||
}
|
||||
|
||||
-- #####################################################################################################################
|
||||
-- ##### Local functions ###############################################################################################
|
||||
-- #####################################################################################################################
|
||||
|
||||
-- @TODO: Link this input service to the player's input service and find some way to see if it's blocked
|
||||
local function is_dmf_input_service_active()
|
||||
-- @TODO: Implement check for active DMF input service
|
||||
return true
|
||||
end
|
||||
|
||||
|
@ -253,118 +75,260 @@ local function perform_keybind_action(data, is_pressed)
|
|||
end
|
||||
|
||||
|
||||
-- #####################################################################################################################
|
||||
-- ##### Input service functions #######################################################################################
|
||||
-- #####################################################################################################################
|
||||
|
||||
local function is_modifier_pressed(modifier)
|
||||
return (Keyboard.button(MODIFIER_KEYS[modifier][1]) + Keyboard.button(MODIFIER_KEYS[modifier][4])) > 0
|
||||
end
|
||||
|
||||
local function default_boolean()
|
||||
return false
|
||||
end
|
||||
|
||||
local function default_vector3()
|
||||
return Vector3(0, 0, 0)
|
||||
end
|
||||
|
||||
local function default_float()
|
||||
return 0
|
||||
end
|
||||
|
||||
local function boolean_combine(value_one, value_two)
|
||||
return value_one or value_two
|
||||
end
|
||||
|
||||
local function vector3_combine(value_one, value_two)
|
||||
if Vector3.length(value_two) < Vector3.length(value_one) then
|
||||
return value_one
|
||||
else
|
||||
return value_two
|
||||
end
|
||||
end
|
||||
|
||||
local function float_combine(value_one, value_two)
|
||||
return math.max(value_one, value_two)
|
||||
end
|
||||
|
||||
local ACTION_TYPES = {
|
||||
pressed = {
|
||||
device_func = "pressed",
|
||||
type = "boolean",
|
||||
default_device_func = default_boolean,
|
||||
combine_func = boolean_combine
|
||||
},
|
||||
held = {
|
||||
device_func = "held",
|
||||
type = "boolean",
|
||||
default_device_func = default_boolean,
|
||||
combine_func = boolean_combine
|
||||
},
|
||||
released = {
|
||||
device_func = "released",
|
||||
type = "boolean",
|
||||
default_device_func = default_boolean,
|
||||
combine_func = boolean_combine
|
||||
},
|
||||
axis = {
|
||||
device_func = "axis",
|
||||
type = "vector3",
|
||||
default_device_func = default_vector3,
|
||||
combine_func = vector3_combine
|
||||
},
|
||||
button = {
|
||||
device_func = "button",
|
||||
type = "float",
|
||||
default_device_func = default_float,
|
||||
combine_func = float_combine
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
local function _enabler_func(cb, action_type, enablers)
|
||||
for _, enabler in ipairs(enablers) do
|
||||
if not enabler.device:held(enabler.index) then
|
||||
return ACTION_TYPES[action_type].default_device_func()
|
||||
end
|
||||
end
|
||||
|
||||
return cb()
|
||||
end
|
||||
|
||||
|
||||
local function _disabler_func(cb, action_type, disablers)
|
||||
for _, disabler in ipairs(disablers) do
|
||||
if disabler.device:held(disabler.index) then
|
||||
return ACTION_TYPES[action_type].default_device_func()
|
||||
end
|
||||
end
|
||||
|
||||
return cb()
|
||||
end
|
||||
|
||||
|
||||
local function get_corresponding_device(key_id)
|
||||
for _, device_type in pairs(SUPPORTED_DEVICES) do
|
||||
|
||||
local device = Managers.input:_find_active_device(device_type)
|
||||
if device then
|
||||
|
||||
local index = device:button_index(key_id)
|
||||
if index then
|
||||
return {
|
||||
device = device,
|
||||
index = index,
|
||||
key_id = key_id,
|
||||
}
|
||||
end
|
||||
|
||||
index = device:axis_index(key_id)
|
||||
if index then
|
||||
return {
|
||||
device = device,
|
||||
index = index,
|
||||
key_id = key_id,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_key_info(keybind)
|
||||
local main = keybind.main
|
||||
local e = keybind.enablers
|
||||
local d = keybind.disablers
|
||||
|
||||
local info = get_corresponding_device(main)
|
||||
|
||||
if info then
|
||||
if #e > 0 then
|
||||
local enablers = {}
|
||||
|
||||
for _, key in ipairs(e) do
|
||||
local enabler = get_corresponding_device(key)
|
||||
|
||||
if enabler then
|
||||
enablers[#enablers + 1] = enabler
|
||||
end
|
||||
end
|
||||
|
||||
info.enablers = enablers
|
||||
end
|
||||
|
||||
if #d > 0 then
|
||||
local disablers = {}
|
||||
|
||||
for _, key in ipairs(d) do
|
||||
local disabler = get_corresponding_device(key)
|
||||
|
||||
if disabler then
|
||||
disablers[#disablers + 1] = disabler
|
||||
end
|
||||
end
|
||||
|
||||
info.disablers = disablers
|
||||
end
|
||||
end
|
||||
|
||||
return info
|
||||
end
|
||||
|
||||
|
||||
local function create_eval_func(keybind)
|
||||
local info = get_key_info(keybind)
|
||||
if info then
|
||||
|
||||
local eval_func
|
||||
if keybind.trigger == "held" then
|
||||
eval_func = callback(info.device, keybind.trigger, info.index)
|
||||
else
|
||||
local function func(device, trigger, index)
|
||||
return device[trigger](index)
|
||||
end
|
||||
|
||||
eval_func = callback(func, info.device:raw_device(), keybind.trigger, info.index)
|
||||
end
|
||||
|
||||
if info.enablers then
|
||||
eval_func = callback(_enabler_func, eval_func, keybind.trigger, info.enablers)
|
||||
end
|
||||
|
||||
if info.disablers then
|
||||
eval_func = callback(_disabler_func, eval_func, keybind.trigger, info.disablers)
|
||||
end
|
||||
|
||||
return eval_func
|
||||
end
|
||||
end
|
||||
|
||||
-- #####################################################################################################################
|
||||
-- ##### DMF internal functions and variables ##########################################################################
|
||||
-- #####################################################################################################################
|
||||
|
||||
-- Checks for pressed and released keybinds, performs keybind actions.
|
||||
-- * Checks for both right and left key modifiers (ctrl, alt, shift).
|
||||
-- * If some keybind is pressed, won't check for other keybinds until this keybind is released.
|
||||
-- * If several mods bound the same keys, keybind action will be performed for all of them, when keybind is pressed.
|
||||
-- * Keybind is considered released, when its primary key is released.
|
||||
-- * Right and left key modifiers (ctrl, alt, shift) are checked separately.
|
||||
-- * If several mods bound the same keys, keybind action will be performed for all of them when pressed.
|
||||
-- * Keybind is considered released when it was previously pressed and is no longer.
|
||||
function dmf.check_keybinds()
|
||||
local pressed_modifiers = {
|
||||
ctrl = is_modifier_pressed("ctrl"),
|
||||
alt = is_modifier_pressed("alt"),
|
||||
shift = is_modifier_pressed("shift")
|
||||
}
|
||||
|
||||
if not _pressed_key then
|
||||
for primary_key, keybinds_data in pairs(_keybinds) do
|
||||
if keybinds_data.check_pressed(primary_key) then
|
||||
for _, keybind_data in ipairs(keybinds_data) do
|
||||
-- For every keybind
|
||||
for _, keybind_data in ipairs(_keybinds) do
|
||||
|
||||
local all_pressed = true
|
||||
-- If the keybind is pressed
|
||||
if keybind_data.eval_func() then
|
||||
|
||||
-- Check that every enabler key is pressed
|
||||
for enabler, _ in pairs(keybind_data.enablers) do
|
||||
if MODIFIER_KEYS[enabler] then
|
||||
if not pressed_modifiers[MODIFIER_KEYS[enabler][2]] then
|
||||
all_pressed = false
|
||||
break
|
||||
end
|
||||
elseif KEYS_INPUT_FUNCTIONS[enabler].check_released(enabler) then
|
||||
all_pressed = false
|
||||
break
|
||||
end
|
||||
end
|
||||
-- Peform the keybind action once
|
||||
if (not keybind_data.pressed) and perform_keybind_action(keybind_data, true) then
|
||||
|
||||
-- Check that no modifier keys are pressed that shouldn't be
|
||||
if all_pressed and (
|
||||
(not keybind_data.ctrl and pressed_modifiers.ctrl) or
|
||||
(not keybind_data.alt and pressed_modifiers.alt) or
|
||||
(not keybind_data.shift and pressed_modifiers.shift)
|
||||
)
|
||||
then
|
||||
all_pressed = false
|
||||
end
|
||||
|
||||
-- Peform the keybind action if everything validates
|
||||
if all_pressed then
|
||||
if perform_keybind_action(keybind_data, true) then
|
||||
if keybind_data.trigger == "held" then
|
||||
keybind_data.release_action = true
|
||||
end
|
||||
_pressed_key = primary_key
|
||||
end
|
||||
end
|
||||
-- Queue the release action if applicable
|
||||
if keybind_data.trigger == "held" then
|
||||
keybind_data.release_action = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if _pressed_key then
|
||||
if _keybinds[_pressed_key].check_released(_pressed_key) then
|
||||
for _, keybind_data in ipairs(_keybinds[_pressed_key]) do
|
||||
if keybind_data.release_action then
|
||||
perform_keybind_action(keybind_data, false)
|
||||
keybind_data.release_action = nil
|
||||
end
|
||||
-- Prevent a repeat action
|
||||
keybind_data.pressed = true
|
||||
end
|
||||
|
||||
-- If the keybind was previously but is no longer pressed
|
||||
elseif keybind_data.pressed then
|
||||
|
||||
-- Reactivate the keybind
|
||||
keybind_data.pressed = nil
|
||||
|
||||
-- Play the release action if applicable
|
||||
if keybind_data.release_action then
|
||||
perform_keybind_action(keybind_data, false)
|
||||
keybind_data.release_action = nil
|
||||
end
|
||||
_pressed_key = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Converts managable (raw) table of keybinds data to the table designed for the function checking for pressed and
|
||||
-- released keybinds. After initial call requires to be called every time some keybind is added/removed.
|
||||
-- Converts manageable (raw) table of keybinds data to a table of callbacks for checking pressed and
|
||||
-- released keybinds. After initial call it must be called every time some keybind is added/removed.
|
||||
function dmf.generate_keybinds()
|
||||
_keybinds = {}
|
||||
|
||||
for mod, mod_keybinds in pairs(_raw_keybinds_data) do
|
||||
for _, raw_keybind_data in pairs(mod_keybinds) do
|
||||
|
||||
local keys = raw_keybind_data.keys
|
||||
local primary_key = keys[1]
|
||||
local enabler_keys = {}
|
||||
for i = 2, #keys do
|
||||
enabler_keys[keys[i]] = true
|
||||
end
|
||||
|
||||
local keybind_data = {
|
||||
mod = mod,
|
||||
global = raw_keybind_data.global,
|
||||
trigger = raw_keybind_data.trigger,
|
||||
type = raw_keybind_data.type,
|
||||
enablers = enabler_keys,
|
||||
ctrl = enabler_keys["ctrl"] or enabler_keys["left ctrl"] or enabler_keys["right ctrl"],
|
||||
alt = enabler_keys["alt"] or enabler_keys["left alt"] or enabler_keys["right alt"],
|
||||
shift = enabler_keys["shift"] or enabler_keys["left shift"] or enabler_keys["right shift"],
|
||||
mod = mod,
|
||||
global = raw_keybind_data.global,
|
||||
trigger = raw_keybind_data.trigger,
|
||||
type = raw_keybind_data.type,
|
||||
eval_func = create_eval_func(raw_keybind_data),
|
||||
|
||||
function_name = raw_keybind_data.function_name,
|
||||
view_name = raw_keybind_data.view_name
|
||||
}
|
||||
|
||||
_keybinds[primary_key] = _keybinds[primary_key] or {
|
||||
check_pressed = KEYS_INPUT_FUNCTIONS[primary_key].check_pressed,
|
||||
check_released = KEYS_INPUT_FUNCTIONS[primary_key].check_released
|
||||
}
|
||||
table.insert(_keybinds[primary_key], keybind_data)
|
||||
if keybind_data.eval_func then
|
||||
table.insert(_keybinds, keybind_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -372,7 +336,7 @@ end
|
|||
|
||||
-- Adds/removes keybinds.
|
||||
function dmf.add_mod_keybind(mod, setting_id, raw_keybind_data)
|
||||
if #raw_keybind_data.keys > 0 then
|
||||
if raw_keybind_data.main then
|
||||
_raw_keybinds_data[mod] = _raw_keybinds_data[mod] or {}
|
||||
_raw_keybinds_data[mod][setting_id] = raw_keybind_data
|
||||
elseif _raw_keybinds_data[mod] and _raw_keybinds_data[mod][setting_id] then
|
||||
|
@ -388,53 +352,32 @@ end
|
|||
|
||||
-- Creates DMF input service. It is required to know when non-global keybinds can be triggered.
|
||||
-- (Called every time a level is loaded, or on mods reload)
|
||||
-- @TODO: Link this input service to the player's input service and find some way to see if it's blocked
|
||||
function dmf.create_keybinds_input_service()
|
||||
-- @TODO: Link this input service to the player's input service and find some way to see if it's blocked
|
||||
--[[
|
||||
-- To create the DMF input service in Darktide
|
||||
local input_manager = Managers.input
|
||||
local service_type = "DMF"
|
||||
input_manager:add_setting(service_type, aliases, raw_key_table, filter_table, default_devices)
|
||||
input_manager:get_input_service(service_type)
|
||||
--]]
|
||||
-- -- To create the DMF input service in Darktide
|
||||
-- local input_manager = Managers.input
|
||||
-- local service_type = "DMF"
|
||||
-- input_manager:add_setting(service_type, aliases, raw_key_table, filter_table, default_devices)
|
||||
-- input_manager:get_input_service(service_type)
|
||||
end
|
||||
|
||||
|
||||
-- Converts key_index to readable key_id, which is used by DMF to identify keys.
|
||||
-- (Used for capturing keybinds)
|
||||
function dmf.get_key_id(device, key_index)
|
||||
local key_info = PRIMARY_BINDABLE_KEYS[device][key_index]
|
||||
return key_info and key_info[2]
|
||||
end
|
||||
|
||||
|
||||
-- Simply tells if key with key_id can be binded as primary key.
|
||||
-- (Used for verifying keybind widgets)
|
||||
-- @TODO: So far it seems like any key can be a primary keybind, but is this actually true?
|
||||
function dmf.can_bind_as_primary_key(key_id)
|
||||
return KEYS_INFO[key_id] and not MODIFIER_KEYS[key_id]
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
-- Builds string with readable keys' names to look like e.g. "Ctrl+Alt+Shift+<Primary Key>".
|
||||
-- (Used in keybind widget)
|
||||
function dmf.build_keybind_string(keys)
|
||||
local key_unassigned_string = Managers.localization:localize("loc_keybind_unassigned")
|
||||
local keybind_result = dmf.keys_to_keybind_result(keys)
|
||||
return keybind_result and InputUtils.localized_string_from_key_info(keybind_result) or key_unassigned_string
|
||||
end
|
||||
|
||||
|
||||
-- Translate key watch result to mod options keybind
|
||||
-- (Used in keybind widget)
|
||||
function dmf.keybind_result_to_keys(keybind_result)
|
||||
-- Translate keywatch result to array of local key names
|
||||
-- (Used in keybind widget for compatibility with legacy settings)
|
||||
function dmf.keywatch_result_to_local_keys(keywatch_result)
|
||||
local keys = {}
|
||||
|
||||
-- Get the local name of the main key
|
||||
if keybind_result.main then
|
||||
if keywatch_result.main then
|
||||
|
||||
local global_name = keybind_result.main
|
||||
local device_type = InputUtils.key_device_type(global_name)
|
||||
local local_name = InputUtils.local_key_name(global_name, device_type)
|
||||
local global_name = keywatch_result.main
|
||||
local local_name = InputUtils.local_key_name(global_name, InputUtils.key_device_type(global_name))
|
||||
|
||||
-- Check for a missing or unbindable primary key name
|
||||
if not local_name or not dmf.can_bind_as_primary_key(local_name) then
|
||||
|
@ -445,12 +388,9 @@ function dmf.keybind_result_to_keys(keybind_result)
|
|||
end
|
||||
|
||||
-- Add the enablers keys as additional keys
|
||||
if keybind_result.enablers then
|
||||
for _, global_name in ipairs(keybind_result.enablers) do
|
||||
|
||||
local device_type = InputUtils.key_device_type(global_name)
|
||||
local local_name = InputUtils.local_key_name(global_name, device_type)
|
||||
|
||||
if keywatch_result.enablers then
|
||||
for _, global_name in ipairs(keywatch_result.enablers) do
|
||||
local local_name = InputUtils.local_key_name(global_name, InputUtils.key_device_type(global_name))
|
||||
keys[#keys + 1] = local_name
|
||||
end
|
||||
end
|
||||
|
@ -459,21 +399,29 @@ function dmf.keybind_result_to_keys(keybind_result)
|
|||
end
|
||||
|
||||
|
||||
-- Translate mod options keybind to key watch result
|
||||
-- (Used in keybind widget)
|
||||
function dmf.keys_to_keybind_result(keys)
|
||||
local keybind_result = {
|
||||
-- Translate array of local key names to keywatch result
|
||||
-- (Used in keybind widget for compatibility with legacy settings)
|
||||
function dmf.local_keys_to_keywatch_result(keys)
|
||||
local keywatch_result = {
|
||||
enablers = {},
|
||||
disablers = {}
|
||||
}
|
||||
|
||||
if not keys or #keys == 0 then
|
||||
if type(keys) ~= "table" or #keys == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
if keys[1] then
|
||||
local local_name = keys[1]
|
||||
local global_name = KEYS_INFO[local_name] and InputUtils.local_to_global_name(local_name, KEYS_INFO[local_name][3])
|
||||
local global_name
|
||||
|
||||
-- Check all supported devices for the global name
|
||||
for _, device_type in ipairs(SUPPORTED_DEVICES) do
|
||||
global_name = InputUtils.local_to_global_name(local_name, device_type)
|
||||
if get_corresponding_device(global_name) then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- End early if our main key doesn't exist, and return an empty result
|
||||
if not global_name then
|
||||
|
@ -483,23 +431,31 @@ function dmf.keys_to_keybind_result(keys)
|
|||
if MODIFIER_KEYS_LOC_ALIAS[global_name] then
|
||||
global_name = MODIFIER_KEYS_LOC_ALIAS[global_name]
|
||||
end
|
||||
keybind_result.main = global_name
|
||||
keywatch_result.main = global_name
|
||||
end
|
||||
|
||||
-- Add all remaining keys to the enablers list
|
||||
for i = 2, #keys do
|
||||
local local_name = keys[i]
|
||||
local global_name = KEYS_INFO[local_name] and InputUtils.local_to_global_name(local_name, KEYS_INFO[local_name][3])
|
||||
local global_name
|
||||
|
||||
-- Check all supported devices for the global name
|
||||
for _, device_type in ipairs(SUPPORTED_DEVICES) do
|
||||
global_name = InputUtils.local_to_global_name(local_name,device_type)
|
||||
if get_corresponding_device(global_name) then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if global_name then
|
||||
if MODIFIER_KEYS_LOC_ALIAS[global_name] then
|
||||
global_name = MODIFIER_KEYS_LOC_ALIAS[global_name]
|
||||
end
|
||||
keybind_result.enablers[#keybind_result.enablers + 1] = global_name
|
||||
keywatch_result.enablers[#keywatch_result.enablers + 1] = global_name
|
||||
end
|
||||
end
|
||||
|
||||
return keybind_result
|
||||
return keywatch_result
|
||||
end
|
||||
|
||||
-- #####################################################################################################################
|
||||
|
|
|
@ -534,18 +534,23 @@ local function initialize_default_settings_and_keybinds(mod, initialized_widgets
|
|||
mod:set(data.setting_id, data.default_value)
|
||||
end
|
||||
if data.type == "keybind" then
|
||||
dmf.add_mod_keybind(
|
||||
mod,
|
||||
data.setting_id,
|
||||
{
|
||||
global = data.keybind_global,
|
||||
trigger = data.keybind_trigger,
|
||||
type = data.keybind_type,
|
||||
keys = mod:get(data.setting_id),
|
||||
function_name = data.function_name,
|
||||
view_name = data.view_name
|
||||
}
|
||||
)
|
||||
local keywatch_result = dmf.local_keys_to_keywatch_result(mod:get(data.setting_id))
|
||||
if keywatch_result and keywatch_result.main then
|
||||
dmf.add_mod_keybind(
|
||||
mod,
|
||||
data.setting_id,
|
||||
{
|
||||
global = data.keybind_global,
|
||||
trigger = data.keybind_trigger,
|
||||
type = data.keybind_type,
|
||||
main = keywatch_result.main,
|
||||
enablers = keywatch_result.enablers,
|
||||
disablers = keywatch_result.disablers,
|
||||
function_name = data.function_name,
|
||||
view_name = data.view_name
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -87,7 +87,7 @@ function dmf.safe_call_dofile(mod, error_prefix_data, file_path)
|
|||
show_error(mod, error_prefix_data, "file path should be a string.")
|
||||
return false
|
||||
end
|
||||
return dmf.safe_call(mod, error_prefix_data, dofile, mod, file_path)
|
||||
return dmf.safe_call(mod, error_prefix_data, dofile, file_path)
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ local _custom_views_data = {}
|
|||
local _ingame_ui
|
||||
local _loaded_views = {}
|
||||
|
||||
local _key_watch = false
|
||||
|
||||
local ERRORS = {
|
||||
THROWABLE = {
|
||||
-- inject_view:
|
||||
|
@ -312,13 +314,13 @@ end
|
|||
|
||||
|
||||
-- Track the creation of the view loader
|
||||
dmf:hook_safe(ViewLoader, "init", function()
|
||||
dmf:hook_safe(CLASS.ViewLoader, "init", function()
|
||||
_custom_view_persistent_data.loader_initialized = true
|
||||
end)
|
||||
|
||||
|
||||
-- Track the loading of views, set the loader flag if class selection is reached
|
||||
dmf:hook_safe(UIManager, "load_view", function(self, view_name)
|
||||
dmf:hook_safe(CLASS.UIManager, "load_view", function(self, view_name)
|
||||
if view_name == "class_selection_view" then
|
||||
_custom_view_persistent_data.loader_initialized = true
|
||||
end
|
||||
|
@ -326,13 +328,13 @@ dmf:hook_safe(UIManager, "load_view", function(self, view_name)
|
|||
end)
|
||||
|
||||
-- Track the unloading of views
|
||||
dmf:hook_safe(UIManager, "unload_view", function(self, view_name)
|
||||
dmf:hook_safe(CLASS.UIManager, "unload_view", function(self, view_name)
|
||||
_loaded_views[view_name] = nil
|
||||
end)
|
||||
|
||||
|
||||
-- Store the view handler for later use and inject views
|
||||
dmf:hook_safe(UIViewHandler, "init", function(self)
|
||||
dmf:hook_safe(CLASS.UIViewHandler, "init", function(self)
|
||||
_ingame_ui = self
|
||||
for view_name, _ in pairs(_custom_views_data) do
|
||||
if not dmf.safe_call_nrc(self, {ERRORS.PREFIX.ingameui_hook_injection, view_name}, inject_view, view_name) then
|
||||
|
@ -341,6 +343,16 @@ dmf:hook_safe(UIViewHandler, "init", function(self)
|
|||
end
|
||||
end)
|
||||
|
||||
-- Track the start of key watches
|
||||
dmf:hook_safe(CLASS.InputManager, "start_key_watch", function(self)
|
||||
_key_watch = true
|
||||
end)
|
||||
|
||||
-- Track the end of key watches
|
||||
dmf:hook_safe(CLASS.InputManager, "stop_key_watch", function(self)
|
||||
_key_watch = false
|
||||
end)
|
||||
|
||||
-- #####################################################################################################################
|
||||
-- ##### DMF internal functions and variables ##########################################################################
|
||||
-- #####################################################################################################################
|
||||
|
@ -368,8 +380,8 @@ function dmf.keybind_toggle_view(mod, view_name, keybind_transition_data, can_pe
|
|||
-- If the view is open, this is a toggle close
|
||||
if Managers.ui:view_active(view_name) then
|
||||
|
||||
-- Don't close the view if it's already closing
|
||||
if not Managers.ui:is_view_closing(view_name) then
|
||||
-- Don't close the view if it's already closing or we have an active key watch
|
||||
if not Managers.ui:is_view_closing(view_name) and not _key_watch then
|
||||
local force_close = true
|
||||
Managers.ui:close_view(view_name, force_close)
|
||||
end
|
||||
|
|
|
@ -228,7 +228,7 @@ DMFOptionsView._restart_popup_info = function (self)
|
|||
close_on_pressed = true,
|
||||
callback = callback(function ()
|
||||
self._popup_id = nil
|
||||
local view_name = "options_view"
|
||||
local view_name = "dmf_options_view"
|
||||
self._require_restart = false
|
||||
|
||||
Managers.ui:close_view(view_name)
|
||||
|
|
|
@ -6,11 +6,13 @@ local _type_template_map = {}
|
|||
|
||||
local _devices = {
|
||||
"keyboard",
|
||||
"mouse"
|
||||
"mouse",
|
||||
}
|
||||
|
||||
local _cancel_keys = {
|
||||
"keyboard_esc"
|
||||
}
|
||||
|
||||
local _reserved_keys = {}
|
||||
|
||||
local ERRORS = {
|
||||
|
@ -249,8 +251,8 @@ _type_template_map["dropdown"] = create_dropdown_template
|
|||
-- ######### Keybind #########
|
||||
-- ###########################
|
||||
|
||||
local set_keybind = function (self, keybind_data, keys)
|
||||
keybind_data.keys = keys
|
||||
local set_keybind = function (self, keybind_data, keywatch_result)
|
||||
keybind_data.keys = keywatch_result
|
||||
|
||||
local mod = get_mod(keybind_data.mod_name)
|
||||
dmf.add_mod_keybind(
|
||||
|
@ -260,18 +262,19 @@ local set_keybind = function (self, keybind_data, keys)
|
|||
global = keybind_data.keybind_global,
|
||||
trigger = keybind_data.keybind_trigger,
|
||||
type = keybind_data.keybind_type,
|
||||
keys = keybind_data.keys,
|
||||
main = keywatch_result.main,
|
||||
enablers = keywatch_result.enablers,
|
||||
disablers = keywatch_result.disablers,
|
||||
function_name = keybind_data.function_name,
|
||||
view_name = keybind_data.view_name,
|
||||
}
|
||||
)
|
||||
mod:set(keybind_data.setting_id, keybind_data.keys, true)
|
||||
mod:set(keybind_data.setting_id, dmf.keywatch_result_to_local_keys(keywatch_result), true)
|
||||
end
|
||||
|
||||
|
||||
-- Create keybind template
|
||||
local create_keybind_template = function (self, params)
|
||||
|
||||
local template = {
|
||||
widget_type = "keybind",
|
||||
service_type = "Ingame",
|
||||
|
@ -287,8 +290,8 @@ local create_keybind_template = function (self, params)
|
|||
indentation_level = params.depth,
|
||||
mod_name = params.mod_name,
|
||||
setting_id = params.setting_id,
|
||||
keys = dmf.keys_to_keybind_result(params.keys),
|
||||
default_value = dmf.keys_to_keybind_result(params.default_value) or {},
|
||||
keys = dmf.local_keys_to_keywatch_result(params.keys),
|
||||
default_value = dmf.local_keys_to_keywatch_result(params.default_value) or {},
|
||||
|
||||
on_activated = function (new_value, old_value)
|
||||
|
||||
|
@ -320,11 +323,11 @@ local create_keybind_template = function (self, params)
|
|||
end
|
||||
|
||||
-- Get the keys of the new value
|
||||
local keys = dmf.keybind_result_to_keys(new_value)
|
||||
local keys = dmf.keywatch_result_to_local_keys(new_value)
|
||||
|
||||
-- Set the new keybind unless it would unbind the mod options menu
|
||||
if keys and #keys > 0 or params.setting_id ~= "open_dmf_options" then
|
||||
set_keybind(self, params, keys)
|
||||
set_keybind(self, params, new_value)
|
||||
return true
|
||||
end
|
||||
|
||||
|
@ -332,10 +335,10 @@ local create_keybind_template = function (self, params)
|
|||
end,
|
||||
|
||||
get_function = function (template)
|
||||
local keys = get_mod(template.mod_name):get(template.setting_id)
|
||||
local keybind_result = dmf.keys_to_keybind_result(keys)
|
||||
local saved_keys = get_mod(template.mod_name):get(template.setting_id)
|
||||
local keywatch_result = dmf.local_keys_to_keywatch_result(saved_keys)
|
||||
|
||||
return keybind_result
|
||||
return keywatch_result
|
||||
end,
|
||||
}
|
||||
|
||||
|
@ -397,14 +400,14 @@ end
|
|||
|
||||
-- Insert a new item into a table before any items that pass the item_tester function
|
||||
local function insert_before(tbl, item_tester, new_item)
|
||||
local copy = {}
|
||||
for _, item in ipairs(tbl) do
|
||||
if item_tester(item) then
|
||||
table.insert(copy, new_item)
|
||||
end
|
||||
table.insert(copy, item)
|
||||
end
|
||||
return copy
|
||||
local copy = {}
|
||||
for _, item in ipairs(tbl) do
|
||||
if item_tester(item) then
|
||||
table.insert(copy, new_item)
|
||||
end
|
||||
table.insert(copy, item)
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
|
||||
|
@ -425,16 +428,16 @@ dmf:add_global_localize_strings({
|
|||
})
|
||||
|
||||
local dmf_option_definition = {
|
||||
text = "mods_options",
|
||||
type = "button",
|
||||
icon = "content/ui/materials/icons/system/escape/settings",
|
||||
trigger_function = function()
|
||||
local context = {
|
||||
can_exit = true,
|
||||
}
|
||||
local view_name = "dmf_options_view"
|
||||
Managers.ui:open_view(view_name, nil, nil, nil, nil, context)
|
||||
end,
|
||||
text = "mods_options",
|
||||
type = "button",
|
||||
icon = "content/ui/materials/icons/system/escape/settings",
|
||||
trigger_function = function()
|
||||
local context = {
|
||||
can_exit = true,
|
||||
}
|
||||
local view_name = "dmf_options_view"
|
||||
Managers.ui:open_view(view_name, nil, nil, nil, nil, context)
|
||||
end,
|
||||
}
|
||||
|
||||
local function is_options_button(item)
|
||||
|
|
Loading…
Add table
Reference in a new issue