2021-10-25 01:57:07 +11:00
-- license:BSD-3-Clause
-- copyright-holders:Vas Crabb
2021-10-21 04:11:43 +11:00
-- Constants
local MENU_TYPES = { MACROS = 0 , ADD = 1 , EDIT = 2 , INPUT = 3 }
-- Globals
2021-10-31 12:31:16 +11:00
local commonui
2021-10-21 04:11:43 +11:00
local macros
local menu_stack
2021-10-21 07:44:34 +11:00
local macros_start_macro -- really for the macros menu, but has to be declared local before edit menu functions
2021-10-21 04:11:43 +11:00
-- 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 ,
2021-11-05 02:46:04 +11:00
bindingcfg = ' ' ,
2021-10-21 04:11:43 +11:00
earlycancel = true ,
loop = - 1 ,
steps = {
{
inputs = {
{
port = nil ,
2021-11-05 02:46:04 +11:00
mask = nil ,
type = nil ,
2021-10-21 04:11:43 +11:00
field = nil } } ,
delay = 0 ,
duration = 1 } } }
end
-- Input menu
2021-10-31 12:31:16 +11:00
local input_menu
2021-10-21 07:44:34 +11:00
local input_start_field
2021-10-21 04:11:43 +11:00
2021-10-31 12:31:16 +11:00
function start_input_menu ( handler , start_field )
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
2021-10-21 04:11:43 +11:00
return true
end
end
2021-10-31 12:31:16 +11:00
local function action ( field )
if field then
handler ( field )
end
table.remove ( menu_stack )
input_menu = nil
input_start_field = nil
end
if not commonui then
commonui = require ( ' commonui ' )
end
input_menu = commonui.input_selection_menu ( action , _p ( ' plugin-inputmacro ' , ' Set Input ' ) , supported )
input_start_field = start_field
table.insert ( menu_stack , MENU_TYPES.INPUT )
end
2021-11-05 02:46:04 +11:00
local function handle_input ( index , action )
2021-10-31 12:31:16 +11:00
return input_menu : handle ( index , action )
2021-10-21 04:11:43 +11:00
end
2021-11-05 02:46:04 +11:00
local function populate_input ( )
2021-10-31 12:31:16 +11:00
return input_menu : populate ( input_start_field )
2021-10-21 04:11:43 +11:00
end
-- Add/edit menus
local edit_current_macro
2021-10-21 07:44:34 +11:00
local edit_start_selection
local edit_start_step
2021-10-22 01:14:05 +11:00
local edit_menu_active
2021-10-21 04:11:43 +11:00
local edit_insert_position
local edit_name_buffer
local edit_items
local edit_item_exit
2021-10-31 12:31:16 +11:00
local edit_switch_poller
2021-10-21 04:11:43 +11:00
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 handle_edit_items ( index , event )
2021-10-31 12:31:16 +11:00
if edit_switch_poller then
if edit_switch_poller : poll ( ) then
if edit_switch_poller.sequence then
edit_current_macro.binding = edit_switch_poller.sequence
2021-11-05 02:46:04 +11:00
edit_current_macro.bindingcfg = manager.machine . input : seq_to_tokens ( edit_switch_poller.sequence )
2021-10-31 12:31:16 +11:00
end
edit_switch_poller = nil
return true
end
return false
end
2021-10-21 04:11:43 +11:00
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
2021-10-31 12:31:16 +11:00
if not commonui then
commonui = require ( ' commonui ' )
end
edit_switch_poller = commonui.switch_polling_helper ( )
return true
2021-10-21 04:11:43 +11:00
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
2021-11-05 02:46:04 +11:00
local function hanlder ( field )
inputs [ command.input ] . port = field.port . tag
inputs [ command.input ] . mask = field.mask
inputs [ command.input ] . type = field.type
inputs [ command.input ] . field = field
end
2021-10-31 12:31:16 +11:00
start_input_menu ( hanlder , inputs [ command.input ] . field )
2021-10-21 07:44:34 +11:00
edit_start_selection = index
2021-10-21 04:11:43 +11:00
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
2021-11-05 02:46:04 +11:00
local function handler ( field )
local newinput = {
port = field.port . tag ,
mask = field.mask ,
type = field.type ,
field = field }
table.insert ( inputs , newinput )
end
2021-10-31 12:31:16 +11:00
start_input_menu ( handler )
2021-10-21 07:44:34 +11:00
edit_start_selection = index
2021-10-21 04:11:43 +11:00
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
2021-10-21 07:44:34 +11:00
edit_start_step = command.step
if edit_start_step > # edit_current_macro.steps then
edit_start_step = edit_start_step - 1
end
2021-10-21 04:11:43 +11:00
return true
end
elseif command.action == ' addstep ' then
if event == ' select ' then
local steps = edit_current_macro.steps
2021-11-05 02:46:04 +11:00
local function handler ( field )
local newstep = {
inputs = {
{
port = field.port . tag ,
mask = field.mask ,
type = field.type ,
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
2021-10-21 04:11:43 +11:00
end
2021-11-05 02:46:04 +11:00
edit_start_step = edit_insert_position
edit_insert_position = edit_insert_position + 1
end
2021-10-31 12:31:16 +11:00
start_input_menu ( handler )
2021-10-21 07:44:34 +11:00
edit_start_selection = index
2021-10-21 04:11:43 +11:00
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
2021-11-02 07:53:18 +11:00
local selection
if command.step then
if event == ' prevgroup ' then
if command.step > 1 then
local found_break = false
selection = index - 1
while ( not edit_items [ selection ] ) or ( edit_items [ selection ] . step == command.step ) do
selection = selection - 1
end
local step = edit_items [ selection ] . step
while edit_items [ selection - 1 ] and ( edit_items [ selection - 1 ] . step == step ) do
selection = selection - 1
end
end
elseif event == ' nextgroup ' then
if command.step < # edit_current_macro.steps then
selection = index + 1
while ( not edit_items [ selection ] ) or ( edit_items [ selection ] . step == command.step ) do
selection = selection + 1
end
end
end
end
return namecancel , selection
2021-10-21 04:11:43 +11:00
end
local function add_edit_items ( items )
edit_items = { }
local input = manager.machine . input
local arrows
2022-03-23 20:27:30 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Name ' ) , edit_name_buffer and ( edit_name_buffer .. ' _ ' ) or edit_current_macro.name , ' ' } )
2021-10-21 04:11:43 +11:00
edit_items [ # items ] = { action = ' name ' }
2021-10-22 01:14:05 +11:00
if not ( edit_start_selection or edit_start_step or edit_menu_active ) then
edit_start_selection = # items
end
edit_menu_active = true
2021-10-21 04:11:43 +11:00
local binding = edit_current_macro.binding
local activation = binding and input : seq_name ( binding ) or _p ( ' plugin-inputmacro ' , ' [not set] ' )
2022-03-23 20:27:30 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Activation sequence ' ) , activation , edit_switch_poller and ' lr ' or ' ' } )
2021-10-21 04:11:43 +11:00
edit_items [ # items ] = { action = ' binding ' }
local releaseaction = edit_current_macro.earlycancel and _p ( ' plugin-inputmacro ' , ' Stop immediately ' ) or _p ( ' plugin-inputmacro ' , ' Complete macro ' )
2022-03-23 20:27:30 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' On release ' ) , releaseaction , edit_current_macro.earlycancel and ' r ' or ' l ' } )
2021-10-21 04:11:43 +11:00
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
2022-03-23 20:27:30 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' When held ' ) , holdaction , arrows } )
2021-10-21 04:11:43 +11:00
edit_items [ # items ] = { action = ' holdaction ' }
for i , step in ipairs ( edit_current_macro.steps ) do
2022-03-23 20:27:30 +11:00
table.insert ( items , { string.format ( _p ( ' plugin-inputmacro ' , ' Step %d ' ) , i ) , ' ' , ' heading ' } )
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Delay (frames) ' ) , tostring ( step.delay ) , ( step.delay > 0 ) and ' lr ' or ' r ' } )
2021-10-21 04:11:43 +11:00
edit_items [ # items ] = { action = ' delay ' , step = i }
2021-10-21 07:44:34 +11:00
if edit_start_step == i then
edit_start_selection = # items
end
2021-10-21 04:11:43 +11:00
2022-03-23 20:27:30 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Duration (frames) ' ) , tostring ( step.duration ) , ( step.duration > 1 ) and ' lr ' or ' r ' } )
2021-10-21 04:11:43 +11:00
edit_items [ # items ] = { action = ' duration ' , step = i }
for j , input in ipairs ( step.inputs ) do
2021-11-05 02:46:04 +11:00
local inputname
if input.field then
2021-12-09 07:42:12 +11:00
inputname = input.field . name
2021-11-05 02:46:04 +11:00
elseif input.port then
inputname = _p ( ' plugin-inputmacro ' , ' n/a ' )
else
inputname = _p ( ' plugin-inputmacro ' , ' [not set] ' )
end
2022-03-23 20:27:30 +11:00
table.insert ( items , { string.format ( _p ( ' plugin-inputmacro ' , ' Input %d ' ) , j ) , inputname , ' ' } )
2021-10-21 04:11:43 +11:00
edit_items [ # items ] = { action = ' input ' , step = i , input = j }
end
if step.inputs [ # step.inputs ] . field then
2022-03-23 20:27:30 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Add input ' ) , ' ' , ' ' } )
2021-10-21 04:11:43 +11:00
edit_items [ # items ] = { action = ' addinput ' , step = i }
end
if # edit_current_macro.steps > 1 then
2022-03-23 20:27:30 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Delete step ' ) , ' ' , ' ' } )
2021-10-21 04:11:43 +11:00
edit_items [ # items ] = { action = ' deletestep ' , step = i }
end
end
2021-10-21 07:44:34 +11:00
edit_start_step = nil
2021-10-21 04:11:43 +11:00
local laststep = edit_current_macro.steps [ # edit_current_macro.steps ]
if laststep.inputs [ # laststep.inputs ] . field then
2022-03-23 20:27:30 +11:00
table.insert ( items , { ' --- ' , ' ' , ' ' } )
2021-10-21 04:11:43 +11:00
arrows = ' lr '
if edit_insert_position > # edit_current_macro.steps then
arrows = ' l '
elseif edit_insert_position < 2 then
arrows = ' r '
end
2022-03-23 20:27:30 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Add step at position ' ) , tostring ( edit_insert_position ) , arrows } )
2021-10-21 04:11:43 +11:00
edit_items [ # items ] = { action = ' addstep ' , step = i }
end
end
local function handle_add ( index , event )
2021-11-02 07:53:18 +11:00
local handled , selection = handle_edit_items ( index , event )
if handled then
return true , selection
2021-10-21 04:11:43 +11:00
elseif event == ' cancel ' then
edit_current_macro = nil
2021-10-22 01:14:05 +11:00
edit_menu_active = false
2021-10-21 04:11:43 +11:00
edit_items = nil
table.remove ( menu_stack )
2021-11-02 07:53:18 +11:00
return true , selection
2021-10-21 04:11:43 +11:00
elseif ( index == edit_item_exit ) and ( event == ' select ' ) then
if current_macro_complete ( ) then
table.insert ( macros , edit_current_macro )
2021-10-21 07:44:34 +11:00
macros_start_macro = # macros
2021-10-21 04:11:43 +11:00
end
2021-10-22 01:14:05 +11:00
edit_menu_active = false
2021-10-21 04:11:43 +11:00
edit_current_macro = nil
edit_items = nil
table.remove ( menu_stack )
2021-11-02 07:53:18 +11:00
return true , selection
2021-10-21 04:11:43 +11:00
end
2021-11-02 07:53:18 +11:00
return false , selection
2021-10-21 04:11:43 +11:00
end
local function handle_edit ( index , event )
2021-11-02 07:53:18 +11:00
local handled , selection = handle_edit_items ( index , event )
if handled then
return true , selection
2021-10-21 04:11:43 +11:00
elseif ( event == ' cancel ' ) or ( ( index == edit_item_exit ) and ( event == ' select ' ) ) then
edit_current_macro = nil
2021-10-22 01:14:05 +11:00
edit_menu_active = false
2021-10-21 04:11:43 +11:00
edit_items = nil
table.remove ( menu_stack )
2021-11-02 07:53:18 +11:00
return true , selection
2021-10-21 04:11:43 +11:00
end
2021-11-02 07:53:18 +11:00
return false , selection
2021-10-21 04:11:43 +11:00
end
local function populate_add ( )
local items = { }
2021-11-05 02:46:04 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Add Input Macro ' ) , ' ' , ' off ' } )
table.insert ( items , { ' --- ' , ' ' , ' ' } )
2021-10-21 04:11:43 +11:00
add_edit_items ( items )
2021-11-05 02:46:04 +11:00
table.insert ( items , { ' --- ' , ' ' , ' ' } )
2021-10-21 04:11:43 +11:00
if current_macro_complete ( ) then
2021-11-05 02:46:04 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Create ' ) , ' ' , ' ' } )
2021-10-21 04:11:43 +11:00
else
2021-11-05 02:46:04 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Cancel ' ) , ' ' , ' ' } )
2021-10-21 04:11:43 +11:00
end
edit_item_exit = # items
2021-10-21 07:44:34 +11:00
local selection = edit_start_selection
edit_start_selection = nil
2021-10-31 12:31:16 +11:00
if edit_switch_poller then
return edit_switch_poller : overlay ( items , selection , ' lrrepeat ' )
else
2021-11-02 07:53:18 +11:00
return items , selection , ' lrrepeat ' .. ( edit_name_buffer and ' ignorepause ' or ' ' )
2021-10-31 12:31:16 +11:00
end
2021-10-21 04:11:43 +11:00
end
local function populate_edit ( )
local items = { }
2021-11-05 02:46:04 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Edit Input Macro ' ) , ' ' , ' off ' } )
table.insert ( items , { ' --- ' , ' ' , ' ' } )
2021-10-21 04:11:43 +11:00
add_edit_items ( items )
2021-11-05 02:46:04 +11:00
table.insert ( items , { ' --- ' , ' ' , ' ' } )
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Done ' ) , ' ' , ' ' } )
2021-10-21 04:11:43 +11:00
edit_item_exit = # items
2021-10-21 07:44:34 +11:00
local selection = edit_start_selection
edit_start_selection = nil
2021-10-31 12:31:16 +11:00
if edit_switch_poller then
return edit_switch_poller : overlay ( items , selection , ' lrrepeat ' )
else
2021-11-02 07:53:18 +11:00
return items , selection , ' lrrepeat ' .. ( edit_name_buffer and ' ignorepause ' or ' ' )
2021-10-31 12:31:16 +11:00
end
2021-10-21 04:11:43 +11:00
end
-- Macros menu
local macros_item_first_macro
2021-10-21 07:44:34 +11:00
local macros_selection_save
2021-10-21 04:11:43 +11:00
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
2021-10-21 07:44:34 +11:00
macros_selection_save = index
2021-10-21 04:11:43 +11:00
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
2021-10-21 07:44:34 +11:00
macros_selection_save = index
2021-10-21 04:11:43 +11:00
table.insert ( menu_stack , MENU_TYPES.EDIT )
return true
elseif event == ' clear ' then
table.remove ( macros , macro )
2021-10-21 07:44:34 +11:00
if # macros > 0 then
macros_selection_save = index
if macro > # macros then
macros_selection_save = macros_selection_save - 1
end
end
2021-10-21 04:11:43 +11:00
return true
end
end
return false
end
function populate_macros ( )
local input = manager.machine . input
local ioport = manager.machine . ioport
local items = { }
2021-11-05 02:46:04 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Input Macros ' ) , ' ' , ' off ' } )
table.insert ( items , { string.format ( _p ( ' plugin-inputmacro ' , ' Press %s to delete ' ) , manager.ui : get_general_input_setting ( ioport : token_to_input_type ( ' UI_CLEAR ' ) ) ) , ' ' , ' off ' } )
table.insert ( items , { ' --- ' , ' ' , ' ' } )
2021-10-21 04:11:43 +11:00
macros_item_first_macro = # items + 1
if # macros > 0 then
for index , macro in ipairs ( macros ) do
2021-11-05 02:46:04 +11:00
table.insert ( items , { macro.name , input : seq_name ( macro.binding ) , ' ' } )
2021-10-21 07:44:34 +11:00
if macros_start_macro == index then
macros_selection_save = # items
end
2021-10-21 04:11:43 +11:00
end
else
2021-11-05 02:46:04 +11:00
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' [no macros] ' ) , ' ' , ' off ' } )
2021-10-21 04:11:43 +11:00
end
2021-10-21 07:44:34 +11:00
macros_start_macro = nil
2021-10-21 04:11:43 +11:00
2021-11-05 02:46:04 +11:00
table.insert ( items , { ' --- ' , ' ' , ' ' } )
table.insert ( items , { _p ( ' plugin-inputmacro ' , ' Add macro ' ) , ' ' , ' ' } )
2021-10-21 04:11:43 +11:00
macros_item_add = # items
2021-10-21 07:44:34 +11:00
local selection = macros_selection_save
macros_selection_save = nil
return items , selection
2021-10-21 04:11:43 +11:00
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