mirror of
https://github.com/holub/mame
synced 2025-07-07 19:03:29 +03:00
-plugins: Added an input macro plugin.
-plugins: Sort input selection menus for autofire plugin. -frontend: Fixed another case where the menus may not automatically scroll the first item into view.
This commit is contained in:
parent
d42a9fd87e
commit
b1c7644159
@ -234,13 +234,14 @@ end
|
||||
-- Button selection menu
|
||||
|
||||
local function populate_button_menu()
|
||||
local ioport = manager.machine.ioport
|
||||
menu = {}
|
||||
inputs = {}
|
||||
menu[#menu + 1] = {_('Select an input for autofire'), '', 'off'}
|
||||
menu[#menu + 1] = {'---', '', ''}
|
||||
header_height = #menu
|
||||
|
||||
for port_key, port in pairs(manager.machine.ioport.ports) do
|
||||
for port_key, port in pairs(ioport.ports) do
|
||||
for field_key, field in pairs(port.fields) do
|
||||
if is_supported_input(field) then
|
||||
inputs[#inputs + 1] = {
|
||||
@ -251,7 +252,29 @@ local function populate_button_menu()
|
||||
end
|
||||
end
|
||||
end
|
||||
-- TODO: group by device so we can sort table.sort(inputs, function(x, y) return x.ioport_field.name < y.ioport_field.name end)
|
||||
|
||||
local function compare(x, y)
|
||||
if x.ioport_field.device.tag < y.ioport_field.device.tag then
|
||||
return true
|
||||
elseif x.ioport_field.device.tag > y.ioport_field.device.tag then
|
||||
return false
|
||||
end
|
||||
groupx = ioport:type_group(x.ioport_field.type, x.ioport_field.player)
|
||||
groupy = ioport:type_group(y.ioport_field.type, y.ioport_field.player)
|
||||
if groupx < groupy then
|
||||
return true
|
||||
elseif groupx > groupy then
|
||||
return false
|
||||
elseif x.ioport_field.type < y.ioport_field.type then
|
||||
return true
|
||||
elseif x.ioport_field.type > y.ioport_field.type then
|
||||
return false
|
||||
else
|
||||
return x.ioport_field.name < y.ioport_field.name
|
||||
end
|
||||
end
|
||||
table.sort(inputs, compare)
|
||||
|
||||
for i, input in pairs(inputs) do
|
||||
menu[header_height + i] = { _p('input-name', input.ioport_field.name), '', '' }
|
||||
end
|
||||
|
133
plugins/inputmacro/init.lua
Normal file
133
plugins/inputmacro/init.lua
Normal file
@ -0,0 +1,133 @@
|
||||
-- license:BSD-3-Clause
|
||||
-- copyright-holders:Vas Crabb
|
||||
local exports = {
|
||||
name = 'inputmacro',
|
||||
version = '0.0.1',
|
||||
description = 'Input macro plugin',
|
||||
license = 'The BSD 3-Clause License',
|
||||
author = { name = 'Vas Crabb' } }
|
||||
|
||||
|
||||
local inputmacro = exports
|
||||
|
||||
function inputmacro.startplugin()
|
||||
--[[
|
||||
Configuration data:
|
||||
* name: display name (string)
|
||||
* binding: activation sequence (input sequence)
|
||||
* earlycancel: cancel or complete on release (Boolean)
|
||||
* loop: -1 = release, 0 = prolong, >0 = loop to step on hold (int)
|
||||
* steps:
|
||||
* inputs:
|
||||
* port: port (I/O port)
|
||||
* field: field (I/O port field)
|
||||
* delay: delay before activating inputs in frames (integer)
|
||||
* duration: duration to activate inputs for (integer)
|
||||
|
||||
Live state:
|
||||
* step: current step (integer or nil)
|
||||
* frame: frame of current step, starting at 1 (integer)
|
||||
]]
|
||||
local macros = { }
|
||||
local active_inputs = { }
|
||||
local shortname
|
||||
local menu
|
||||
local input
|
||||
|
||||
local function activate_inputs(inputs)
|
||||
for index, input in ipairs(inputs) do
|
||||
active_inputs[string.format('%s.%d.%d', input.port.tag, input.field.mask, input.field.type)] = input.field
|
||||
end
|
||||
end
|
||||
|
||||
local function process_frame()
|
||||
previous_inputs = active_inputs
|
||||
active_inputs = { }
|
||||
|
||||
for index, macro in ipairs(macros) do
|
||||
if macro.step then
|
||||
if macro.earlycancel and (not input:seq_pressed(macro.binding)) then
|
||||
-- stop immediately on release if early cancel set
|
||||
macro.step = nil
|
||||
else
|
||||
-- advance frame
|
||||
macro.frame = macro.frame + 1
|
||||
local step = macro.steps[macro.step]
|
||||
if macro.frame > (step.delay + step.duration) then
|
||||
if macro.step < #macro.steps then
|
||||
-- not the last step, advance step
|
||||
macro.step = macro.step + 1
|
||||
macro.frame = 1
|
||||
step = macro.steps[macro.step]
|
||||
elseif not input:seq_pressed(macro.binding) then
|
||||
-- input released and macro completed
|
||||
macro.step = nil
|
||||
step = nil
|
||||
elseif macro.loop > 0 then
|
||||
-- loop to step
|
||||
macro.step = macro.loop
|
||||
macro.frame = 1
|
||||
elseif macro.loop < 0 then
|
||||
-- release if held
|
||||
step = nil
|
||||
end
|
||||
end
|
||||
if step and (macro.frame > step.delay) then
|
||||
activate_inputs(step.inputs)
|
||||
end
|
||||
end
|
||||
elseif input:seq_pressed(macro.binding) then
|
||||
-- initial activation
|
||||
macro.step = 1
|
||||
macro.frame = 1
|
||||
local step = macro.steps[1]
|
||||
if step.delay == 0 then
|
||||
-- no delay on first step, activate inputs
|
||||
activate_inputs(step.inputs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for key, field in pairs(active_inputs) do
|
||||
field:set_value(1)
|
||||
end
|
||||
for key, field in pairs(previous_inputs) do
|
||||
if not active_inputs[key] then
|
||||
field:set_value(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function start()
|
||||
input = manager.machine.input
|
||||
if shortname ~= emu.romname() then
|
||||
local persister = require('inputmacro/inputmacro_persist')
|
||||
macros = persister.load_settings()
|
||||
shortname = emu.romname()
|
||||
end
|
||||
end
|
||||
|
||||
local function stop()
|
||||
local persister = require('inputmacro/inputmacro_persist')
|
||||
persister:save_settings(macros)
|
||||
end
|
||||
|
||||
local function menu_callback(index, event)
|
||||
return menu:handle_event(index, event)
|
||||
end
|
||||
|
||||
local function menu_populate()
|
||||
if not menu then
|
||||
menu = require('inputmacro/inputmacro_menu')
|
||||
menu:init(macros)
|
||||
end
|
||||
return menu:populate()
|
||||
end
|
||||
|
||||
emu.register_frame_done(process_frame)
|
||||
emu.register_start(start)
|
||||
emu.register_stop(stop)
|
||||
emu.register_menu(menu_callback, menu_populate, _('Input Macros'))
|
||||
end
|
||||
|
||||
return exports
|
586
plugins/inputmacro/inputmacro_menu.lua
Normal file
586
plugins/inputmacro/inputmacro_menu.lua
Normal file
@ -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
|
145
plugins/inputmacro/inputmacro_persist.lua
Normal file
145
plugins/inputmacro/inputmacro_persist.lua
Normal file
@ -0,0 +1,145 @@
|
||||
-- Helpers
|
||||
|
||||
local function settings_path()
|
||||
return emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/inputmacro/'
|
||||
end
|
||||
|
||||
local function settings_filename()
|
||||
return emu.romname() .. '.cfg'
|
||||
end
|
||||
|
||||
local function make_macro(setting)
|
||||
if (setting.name == nil) or (setting.binding == nil) or (setting.earlycancel == nil) or (setting.loop == nil) or (setting.steps == nil) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local result = {
|
||||
name = setting.name,
|
||||
binding = manager.machine.input:seq_from_tokens(setting.binding),
|
||||
earlycancel = setting.earlycancel,
|
||||
loop = setting.loop,
|
||||
steps = { } }
|
||||
|
||||
local ioport = manager.machine.ioport
|
||||
for i, step in ipairs(setting.steps) do
|
||||
if step.inputs and step.delay and step.duration then
|
||||
local s = {
|
||||
inputs = { },
|
||||
delay = step.delay,
|
||||
duration = step.duration }
|
||||
for j, input in ipairs(step.inputs) do
|
||||
if input.port and input.mask and input.type then
|
||||
local port = ioport.ports[input.port]
|
||||
if port then
|
||||
local field = port:field(input.mask)
|
||||
if field and (field.type == ioport:token_to_input_type(input.type)) then
|
||||
table.insert(s.inputs, { port = port, field = field })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if #s.inputs > 0 then
|
||||
table.insert(result.steps, s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if result.loop > #result.steps then
|
||||
result.loop = -1
|
||||
end
|
||||
|
||||
if #result.steps > 0 then
|
||||
return result
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function make_settings(macros)
|
||||
local input = manager.machine.input
|
||||
local ioport = manager.machine.ioport
|
||||
local result = { }
|
||||
for i, macro in ipairs(macros) do
|
||||
local m = {
|
||||
name = macro.name,
|
||||
binding = input:seq_to_tokens(macro.binding),
|
||||
earlycancel = macro.earlycancel,
|
||||
loop = macro.loop,
|
||||
steps = { } }
|
||||
table.insert(result, m)
|
||||
for j, step in ipairs(macro.steps) do
|
||||
local s = {
|
||||
inputs = { },
|
||||
delay = step.delay,
|
||||
duration = step.duration }
|
||||
table.insert(m.steps, s)
|
||||
for k, input in ipairs(step.inputs) do
|
||||
local b = {
|
||||
port = input.port.tag,
|
||||
mask = input.field.mask,
|
||||
type = ioport:input_type_to_token(input.field.type) }
|
||||
table.insert(s.inputs, b)
|
||||
end
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
-- Entry points
|
||||
|
||||
local lib = { }
|
||||
|
||||
function lib:load_settings()
|
||||
filename = settings_path() .. settings_filename()
|
||||
local file = io.open(filename, 'r')
|
||||
if not file then
|
||||
return { }
|
||||
end
|
||||
local json = require('json')
|
||||
local settings = json.parse(file:read('a'))
|
||||
file:close()
|
||||
if not settings then
|
||||
emu.print_error(string.format('Error loading input macros: error parsing file "%s" as JSON\n', filename))
|
||||
return { }
|
||||
end
|
||||
|
||||
result = { }
|
||||
for index, setting in ipairs(settings) do
|
||||
local macro = make_macro(setting)
|
||||
if macro then
|
||||
table.insert(result, macro)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function lib:save_settings(macros)
|
||||
local path = settings_path()
|
||||
local stat = lfs.attributes(path)
|
||||
if not stat then
|
||||
lfs.mkdir(path)
|
||||
elseif stat.mode ~= 'directory' then
|
||||
emu.print_error(string.format('Error saving input macros: "%s" is not a directory\n', path))
|
||||
return
|
||||
end
|
||||
filename = path .. settings_filename()
|
||||
|
||||
if #macros == 0 then
|
||||
os.remove(filename)
|
||||
return
|
||||
end
|
||||
|
||||
local json = require('json')
|
||||
local settings = make_settings(macros)
|
||||
local text = json.stringify(settings, { indent = true })
|
||||
local file = io.open(filename, 'w')
|
||||
if not file then
|
||||
emu.print_error(string.format('Error saving input macros: error opening file "%s" for writing\n', filename))
|
||||
return
|
||||
end
|
||||
file:write(text)
|
||||
file:close()
|
||||
end
|
||||
|
||||
return lib
|
10
plugins/inputmacro/plugin.json
Normal file
10
plugins/inputmacro/plugin.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"plugin": {
|
||||
"name": "inputmacro",
|
||||
"description": "Input macro plugin",
|
||||
"version": "0.0.1",
|
||||
"author": "Vas Crabb",
|
||||
"type": "plugin",
|
||||
"start": "false"
|
||||
}
|
||||
}
|
@ -518,11 +518,11 @@ void menu::draw(uint32_t flags)
|
||||
if (!customonly)
|
||||
ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color());
|
||||
|
||||
if ((m_selected >= (top_line + m_visible_lines)) || (m_selected < (top_line + 1)))
|
||||
top_line = m_selected - (m_visible_lines / 2);
|
||||
if (top_line < 0 || is_first_selected())
|
||||
top_line = 0;
|
||||
else if (m_selected >= (top_line + m_visible_lines))
|
||||
top_line = m_selected - (m_visible_lines / 2);
|
||||
if ((top_line > (m_items.size() - m_visible_lines)) || is_last_selected())
|
||||
else if ((top_line > (m_items.size() - m_visible_lines)) || is_last_selected())
|
||||
top_line = m_items.size() - m_visible_lines;
|
||||
else if (m_selected >= (top_line + m_visible_lines - 2))
|
||||
top_line = m_selected - m_visible_lines + ((m_selected == (m_items.size() - 1)) ? 1: 2);
|
||||
|
Loading…
Reference in New Issue
Block a user