diff options
Diffstat (limited to 'plugins/inputmacro/inputmacro_menu.lua')
-rw-r--r-- | plugins/inputmacro/inputmacro_menu.lua | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/plugins/inputmacro/inputmacro_menu.lua b/plugins/inputmacro/inputmacro_menu.lua new file mode 100644 index 00000000000..24b963a91e9 --- /dev/null +++ b/plugins/inputmacro/inputmacro_menu.lua @@ -0,0 +1,586 @@ +-- Constants + +local MENU_TYPES = { MACROS = 0, ADD = 1, EDIT = 2, INPUT = 3 } + + +-- Globals + +local macros +local menu_stack + + +-- Helpers + +local function new_macro() + local function check_name(n) + for index, macro in ipairs(macros) do + if macro.name == n then + return false + end + end + return true + end + + local name = _p('plugin-inputmacro', 'New macro') + local number = 1 + while not check_name(name) do + number = number + 1 + name = string.format(_p('plugin-inputmacro', 'New macro %d'), number) + end + return { + name = name, + binding = nil, + earlycancel = true, + loop = -1, + steps = { + { + inputs = { + { + port = nil, + field = nil } }, + delay = 0, + duration = 1 } } } +end + + +-- Input menu + +local input_action +local input_choices +local input_item_first_choice +local input_item_cancel + +function handle_input(index, action) + if (action == 'cancel') or ((index == input_item_cancel) and (action == 'select')) then + table.remove(menu_stack) + input_action = nil + return true + elseif action == 'select' then + field = input_choices[index - input_item_first_choice + 1] + if field then + table.remove(menu_stack) + input_action(field) + input_action = nil + return true + end + end + return false +end + +function populate_input() + local items = { } + + items[#items + 1] = { _p('plugin-inputmacro', 'Set Input'), '', 'off' } + items[#items + 1] = { '---', '', '' } + + if not input_choices then + local ioport = manager.machine.ioport + + local function supported(f) + if f.is_analog or f.is_toggle then + return false + elseif (f.type_class == 'config') or (f.type_class == 'dipswitch') then + return false + else + return true + end + end + + local function compare(a, b) + if a.device.tag < b.device.tag then + return true + elseif a.device.tag > b.device.tag then + return false + end + groupa = ioport:type_group(a.type, a.player) + groupb = ioport:type_group(b.type, b.player) + if groupa < groupb then + return true + elseif groupa > groupb then + return false + elseif a.type < b.type then + return true + elseif a.type > b.type then + return false + else + return a.name < b.name + end + end + + input_choices = { } + for tag, port in pairs(manager.machine.ioport.ports) do + for name, field in pairs(port.fields) do + if supported(field) then + input_choices[#input_choices + 1] = field + end + end + end + table.sort(input_choices, compare) + end + + input_item_first_choice = #items + 1 + for index, field in ipairs(input_choices) do + items[#items + 1] = { _p('input-name', field.name), '', '' } + end + + items[#items + 1] = { '---', '', '' } + items[#items + 1] = { _p('plugin-inputmacro', 'Cancel'), '', '' } + input_item_cancel = #items + + return items +end + + +-- Add/edit menus + +local edit_current_macro +local edit_insert_position +local edit_name_buffer +local edit_items +local edit_item_exit + +local function current_macro_complete() + if not edit_current_macro.binding then + return false + end + local laststep = edit_current_macro.steps[#edit_current_macro.steps] + if not laststep.inputs[#laststep.inputs].field then + return false + end + return true +end + +local function set_binding() + local input = manager.machine.input + local poller = input:switch_sequence_poller() + manager.machine:popmessage(_p('plugin-inputmacro', 'Enter activation sequence or wait to leave unchanged')) + manager.machine.video:frame_update() + poller:start() + local time = os.clock() + local clearmsg = true + while (not poller:poll()) and (poller.modified or (os.clock() < (time + 1))) do + if poller.modified then + if not poller.valid then + manager.machine:popmessage(_p('plugin-inputmacro', 'Invalid sequence entered')) + clearmsg = false + break + end + manager.machine:popmessage(input:seq_name(poller.sequence)) + manager.machine.video:frame_update() + end + end + if clearmsg then + manager.machine:popmessage() + end + if poller.valid then + edit_current_macro.binding = poller.sequence + return true + end + return false +end + +local function handle_edit_items(index, event) + local command = edit_items[index] + + local namecancel = false + if edit_name_buffer and ((not command) or (command.action ~= 'name')) then + edit_name_buffer = nil + namecancel = true + end + + if not command then + return namecancel + elseif command.action == 'name' then + local function namechar() + local ch = tonumber(event) + if not ch then + return nil + elseif (ch >= 0x100) or ((ch & 0x7f) >= 0x20) or (ch == 0x08) then + return utf8.char(ch) + else + return nil + end + end + + if edit_name_buffer then + if event == 'select' then + if #edit_name_buffer > 0 then + edit_current_macro.name = edit_name_buffer + end + edit_name_buffer = nil + return true + elseif event == 'cancel' then + edit_name_buffer = nil + return true + else + local char = namechar() + if char == '\b' then + edit_name_buffer = edit_name_buffer:gsub('[%z\1-\127\192-\255][\128-\191]*$', '') + return true + elseif char then + edit_name_buffer = edit_name_buffer .. char + return true + end + end + elseif event == 'select' then + edit_name_buffer = edit_current_macro.name + return true + else + local char = namechar() + if char == '\b' then + edit_name_buffer = '' + return true + elseif char then + edit_name_buffer = char + return true + end + end + elseif command.action == 'binding' then + if event == 'select' then + return set_binding() + end + elseif command.action == 'releaseaction' then + if (event == 'select') or (event == 'left') or (event == 'right') then + edit_current_macro.earlycancel = not edit_current_macro.earlycancel + return true + end + elseif command.action == 'holdaction' then + if event == 'left' then + edit_current_macro.loop = edit_current_macro.loop - 1 + return true + elseif event == 'right' then + edit_current_macro.loop = edit_current_macro.loop + 1 + return true + elseif event == 'clear' then + edit_current_macro.loop = -1 + return true + end + elseif command.action == 'delay' then + local step = edit_current_macro.steps[command.step] + if event == 'left' then + step.delay = step.delay - 1 + return true + elseif event == 'right' then + step.delay = step.delay + 1 + return true + elseif event == 'clear' then + step.delay = 0 + return true + end + elseif command.action == 'duration' then + local step = edit_current_macro.steps[command.step] + if event == 'left' then + step.duration = step.duration - 1 + return true + elseif event == 'right' then + step.duration = step.duration + 1 + return true + elseif event == 'clear' then + step.duration = 1 + return true + end + elseif command.action == 'input' then + local inputs = edit_current_macro.steps[command.step].inputs + if event == 'select' then + input_action = + function(field) + inputs[command.input].port = field.port + inputs[command.input].field = field + end + table.insert(menu_stack, MENU_TYPES.INPUT) + return true + elseif event == 'clear' then + if #inputs > 1 then + table.remove(inputs, command.input) + return true + end + end + elseif command.action == 'addinput' then + if event == 'select' then + local inputs = edit_current_macro.steps[command.step].inputs + input_action = + function(field) + inputs[#inputs + 1] = { + port = field.port, + field = field } + end + table.insert(menu_stack, MENU_TYPES.INPUT) + return true + end + elseif command.action == 'deletestep' then + if event == 'select' then + table.remove(edit_current_macro.steps, command.step) + if edit_current_macro.loop > #edit_current_macro.steps then + edit_current_macro.loop = -1 + elseif edit_current_macro.loop > command.step then + edit_current_macro.loop = edit_current_macro.loop - 1 + end + if edit_insert_position > command.step then + edit_insert_position = edit_insert_position - 1 + end + return true + end + elseif command.action == 'addstep' then + if event == 'select' then + local steps = edit_current_macro.steps + input_action = + function(field) + local newstep = { + inputs = { + { + port = field.port, + field = field } }, + delay = 0, + duration = 1 } + table.insert(steps, edit_insert_position, newstep) + if edit_current_macro.loop >= edit_insert_position then + edit_current_macro.loop = edit_current_macro.loop + 1 + end + edit_insert_position = edit_insert_position + 1 + end + table.insert(menu_stack, MENU_TYPES.INPUT) + return true + elseif event == 'left' then + edit_insert_position = edit_insert_position - 1 + return true + elseif event == 'right' then + edit_insert_position = edit_insert_position + 1 + return true + end + end + + return namecancel +end + +local function add_edit_items(items) + edit_items = { } + local input = manager.machine.input + local arrows + + items[#items + 1] = { _p('plugin-inputmacro', 'Name'), edit_name_buffer and (edit_name_buffer .. '_') or edit_current_macro.name, '' } + edit_items[#items] = { action = 'name' } + + local binding = edit_current_macro.binding + local activation = binding and input:seq_name(binding) or _p('plugin-inputmacro', '[not set]') + items[#items + 1] = { _p('plugin-inputmacro', 'Activation sequence'), activation, '' } + edit_items[#items] = { action = 'binding' } + + local releaseaction = edit_current_macro.earlycancel and _p('plugin-inputmacro', 'Stop immediately') or _p('plugin-inputmacro', 'Complete macro') + items[#items + 1] = { _p('plugin-inputmacro', 'On release'), releaseaction, edit_current_macro.earlycancel and 'r' or 'l' } + edit_items[#items] = { action = 'releaseaction' } + + local holdaction + arrows = 'lr' + if edit_current_macro.loop < 0 then + holdaction = _p('plugin-inputmacro', 'Release') + arrows = 'r' + elseif edit_current_macro.loop > 0 then + holdaction = string.format(_p('plugin-inputmacro', 'Loop to step %d'), edit_current_macro.loop) + if edit_current_macro.loop >= #edit_current_macro.steps then + arrows = 'l' + end + else + holdaction = string.format(_p('plugin-inputmacro', 'Prolong step %d'), #edit_current_macro.steps) + end + items[#items + 1] = { _p('plugin-inputmacro', 'When held'), holdaction, arrows } + edit_items[#items] = { action = 'holdaction' } + + for i, step in ipairs(edit_current_macro.steps) do + items[#items + 1] = { '---', '', '' } + + items[#items + 1] = { string.format(_p('plugin-inputmacro', 'Step %d'), i), '', 'off' } + items[#items + 1] = { _p('plugin-inputmacro', 'Delay (frames)'), step.delay, (step.delay > 0) and 'lr' or 'r' } + edit_items[#items] = { action = 'delay', step = i } + + items[#items + 1] = { _p('plugin-inputmacro', 'Duration (frames)'), step.duration, (step.duration > 1) and 'lr' or 'r' } + edit_items[#items] = { action = 'duration', step = i } + + for j, input in ipairs(step.inputs) do + local inputname = input.field and _p('input-name', input.field.name) or _p('plugin-inputmacro', '[not set]') + items[#items + 1] = { string.format(_p('plugin-inputmacro', 'Input %d'), j), inputname, '' } + edit_items[#items] = { action = 'input', step = i, input = j } + end + + if step.inputs[#step.inputs].field then + items[#items + 1] = { _p('plugin-inputmacro', 'Add input'), '', '' } + edit_items[#items] = { action = 'addinput', step = i } + end + + if #edit_current_macro.steps > 1 then + items[#items + 1] = { _p('plugin-inputmacro', 'Delete step'), '', '' } + edit_items[#items] = { action = 'deletestep', step = i } + end + end + + local laststep = edit_current_macro.steps[#edit_current_macro.steps] + if laststep.inputs[#laststep.inputs].field then + items[#items + 1] = { '---', '', '' } + + arrows = 'lr' + if edit_insert_position > #edit_current_macro.steps then + arrows = 'l' + elseif edit_insert_position < 2 then + arrows = 'r' + end + items[#items + 1] = { _p('plugin-inputmacro', 'Add step at position'), edit_insert_position, arrows } + edit_items[#items] = { action = 'addstep', step = i } + end +end + +local function handle_add(index, event) + if handle_edit_items(index, event) then + return true + elseif event == 'cancel' then + edit_current_macro = nil + edit_items = nil + table.remove(menu_stack) + return true + elseif (index == edit_item_exit) and (event == 'select') then + if current_macro_complete() then + table.insert(macros, edit_current_macro) + end + edit_current_macro = nil + edit_items = nil + table.remove(menu_stack) + return true + end + return false +end + +local function handle_edit(index, event) + if handle_edit_items(index, event) then + return true + elseif (event == 'cancel') or ((index == edit_item_exit) and (event == 'select')) then + edit_current_macro = nil + edit_items = nil + table.remove(menu_stack) + return true + end + return false +end + +local function populate_add() + local items = { } + + items[#items + 1] = { _p('plugin-inputmacro', 'Add Input Macro'), '', 'off' } + items[#items + 1] = { '---', '', '' } + + add_edit_items(items) + + items[#items + 1] = { '---', '', '' } + if current_macro_complete() then + items[#items + 1] = { _p('plugin-inputmacro', 'Create'), '', '' } + else + items[#items + 1] = { _p('plugin-inputmacro', 'Cancel'), '', '' } + end + edit_item_exit = #items + + return items +end + +local function populate_edit() + local items = { } + + items[#items + 1] = { _p('plugin-inputmacro', 'Edit Input Macro'), '', 'off' } + items[#items + 1] = { '---', '', '' } + + add_edit_items(items) + + items[#items + 1] = { '---', '', '' } + items[#items + 1] = { _p('plugin-inputmacro', 'Done'), '', '' } + edit_item_exit = #items + + return items +end + + +-- Macros menu + +local macros_item_first_macro +local macros_item_add + +function handle_macros(index, event) + if index == macros_item_add then + if event == 'select' then + edit_current_macro = new_macro() + edit_insert_position = #edit_current_macro.steps + 1 + table.insert(menu_stack, MENU_TYPES.ADD) + return true + end + elseif index >= macros_item_first_macro then + macro = index - macros_item_first_macro + 1 + if event == 'select' then + edit_current_macro = macros[macro] + edit_insert_position = #edit_current_macro.steps + 1 + table.insert(menu_stack, MENU_TYPES.EDIT) + return true + elseif event == 'clear' then + table.remove(macros, macro) + return true + end + end + return false +end + +function populate_macros() + local input = manager.machine.input + local ioport = manager.machine.ioport + local items = { } + + items[#items + 1] = { _p('plugin-inputmacro', 'Input Macros'), '', 'off' } + items[#items + 1] = { string.format(_p('plugin-inputmacro', 'Press %s to delete'), input:seq_name(ioport:type_seq(ioport:token_to_input_type('UI_CLEAR')))), '', 'off' } + items[#items + 1] = { '---', '', '' } + + macros_item_first_macro = #items + 1 + if #macros > 0 then + for index, macro in ipairs(macros) do + items[#items + 1] = { macro.name, input:seq_name(macro.binding), '' } + end + else + items[#items + 1] = { _p('plugin-inputmacro', 'No macros'), '', 'off' } + end + + items[#items + 1] = { '---', '', '' } + items[#items + 1] = { _p('plugin-inputmacro', 'Add macro'), '', '' } + macros_item_add = #items + + return items +end + + +-- Entry points + +local lib = { } + +function lib:init(m) + macros = m + menu_stack = { MENU_TYPES.MACROS } +end + +function lib:handle_event(index, event) + local current = menu_stack[#menu_stack] + if current == MENU_TYPES.MACROS then + return handle_macros(index, event) + elseif current == MENU_TYPES.ADD then + return handle_add(index, event) + elseif current == MENU_TYPES.EDIT then + return handle_edit(index, event) + elseif current == MENU_TYPES.INPUT then + return handle_input(index, event) + end +end + +function lib:populate() + local current = menu_stack[#menu_stack] + if current == MENU_TYPES.MACROS then + return populate_macros() + elseif current == MENU_TYPES.ADD then + return populate_add() + elseif current == MENU_TYPES.EDIT then + return populate_edit() + elseif current == MENU_TYPES.INPUT then + return populate_input() + end +end + +return lib |