mirror of
https://github.com/holub/mame
synced 2025-04-27 18:53:05 +03:00

Made auto-boot script errors and plugin bootstrap errors fatal. Run auto-boot scripts in a sandbox. Globals can be accessed, but not set. The sandbox is cleared on hard reset, but not on soft reset. Added (hopefully) useful to string metafunctions to device_t and address space that show short names and tags. Fixed issues in plugins that surface when strict type checking is enabled, as this means numbers and nil are not automatically converted to strings. Plugins should be tested with debug builds to check for this. Made save item read_block raise an error on invalid arguments rather than returning an empty string, and made it use luaL_buffer directly rather than using the helper wrapper. Changed some more function bindings to use set_function to avoid issues related to ThePhD/sol2#608, and got rid of some unnecessary lambda captures.
369 lines
11 KiB
Lua
369 lines
11 KiB
Lua
local lib = {}
|
|
|
|
-- Common UI helper library
|
|
local commonui
|
|
|
|
-- Set of all menus
|
|
local MENU_TYPES = { MAIN = 0, EDIT = 1, ADD = 2, BUTTON = 3 }
|
|
|
|
-- Set of sections within a menu
|
|
local MENU_SECTIONS = { HEADER = 0, CONTENT = 1, FOOTER = 2 }
|
|
|
|
-- Last index of header items (above main content) in menu
|
|
local header_height = 0
|
|
|
|
-- Last index of content items (below header, above footer) in menu
|
|
local content_height = 0
|
|
|
|
-- Stack of menus (see MENU_TYPES)
|
|
local menu_stack = { MENU_TYPES.MAIN }
|
|
|
|
-- Button to select when showing the main menu (so newly added button can be selected)
|
|
local initial_button
|
|
|
|
-- Saved selection on main menu (to restore after configure menu is dismissed)
|
|
local main_selection_save
|
|
|
|
-- Whether configure menu is active (so first item can be selected initially)
|
|
local configure_menu_active = false
|
|
|
|
-- Saved selection on configure menu (to restore after button menu is dismissed)
|
|
local configure_selection_save
|
|
|
|
-- Helper for polling for hotkeys
|
|
local hotkey_poller
|
|
|
|
-- Button being created/edited
|
|
local current_button = {}
|
|
|
|
-- Initial button to select when opening buttons menu
|
|
local initial_input
|
|
|
|
-- Handler for BUTTON menu
|
|
local input_menu
|
|
|
|
-- Returns the section (from MENU_SECTIONS) and the index within that section
|
|
local function menu_section(index)
|
|
if index <= header_height then
|
|
return MENU_SECTIONS.HEADER, index
|
|
elseif index <= content_height then
|
|
return MENU_SECTIONS.CONTENT, index - header_height
|
|
else
|
|
return MENU_SECTIONS.FOOTER, index - content_height
|
|
end
|
|
end
|
|
|
|
local function create_new_button()
|
|
return {
|
|
on_frames = 1,
|
|
off_frames = 1,
|
|
counter = 0
|
|
}
|
|
end
|
|
|
|
local function is_button_complete(button)
|
|
return button.port and button.mask and button.type and button.key and button.on_frames and button.off_frames and button.button and button.counter
|
|
end
|
|
|
|
-- Main menu
|
|
|
|
local function populate_main_menu(buttons)
|
|
local ioport = manager.machine.ioport
|
|
local input = manager.machine.input
|
|
local menu = {}
|
|
table.insert(menu, {_p('plugin-autofire', 'Autofire buttons'), '', 'off'})
|
|
table.insert(menu, {string.format(_p('plugin-autofire', 'Press %s to delete'), manager.ui:get_general_input_setting(ioport:token_to_input_type('UI_CLEAR'))), '', 'off'})
|
|
table.insert(menu, {'---', '', ''})
|
|
header_height = #menu
|
|
|
|
-- Use frame rate of first screen or 60Hz if no screens
|
|
local freq = 60
|
|
local screen = manager.machine.screens:at(1)
|
|
if screen then
|
|
freq = 1 / screen.frame_period
|
|
end
|
|
|
|
if #buttons > 0 then
|
|
for index, button in ipairs(buttons) do
|
|
-- Round rate to two decimal places
|
|
local rate = freq / (button.on_frames + button.off_frames)
|
|
rate = math.floor(rate * 100) / 100
|
|
local text
|
|
if button.button then
|
|
text = string.format(_p('plugin-autofire', '%s [%g Hz]'), button.button.name, rate)
|
|
else
|
|
text = string.format(_p('plugin-autofire', 'n/a [%g Hz]'), rate)
|
|
end
|
|
table.insert(menu, {text, input:seq_name(button.key), ''})
|
|
if index == initial_button then
|
|
main_selection_save = #menu
|
|
end
|
|
end
|
|
else
|
|
table.insert(menu, {_p('plugin-autofire', '[no autofire buttons]'), '', 'off'})
|
|
end
|
|
initial_button = nil
|
|
content_height = #menu
|
|
|
|
table.insert(menu, {'---', '', ''})
|
|
table.insert(menu, {_p('plugin-autofire', 'Add autofire button'), '', ''})
|
|
|
|
local selection = main_selection_save
|
|
main_selection_save = nil
|
|
return menu, selection
|
|
end
|
|
|
|
local function handle_main_menu(index, event, buttons)
|
|
local section, adjusted_index = menu_section(index)
|
|
if section == MENU_SECTIONS.CONTENT then
|
|
if event == 'select' then
|
|
main_selection_save = index
|
|
current_button = buttons[adjusted_index]
|
|
table.insert(menu_stack, MENU_TYPES.EDIT)
|
|
return true
|
|
elseif event == 'clear' then
|
|
table.remove(buttons, adjusted_index)
|
|
main_selection_save = index
|
|
if adjusted_index > #buttons then
|
|
main_selection_save = main_selection_save - 1
|
|
end
|
|
return true
|
|
end
|
|
elseif section == MENU_SECTIONS.FOOTER then
|
|
if event == 'select' then
|
|
main_selection_save = index
|
|
current_button = create_new_button()
|
|
table.insert(menu_stack, MENU_TYPES.ADD)
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Add/edit menus (mostly identical)
|
|
|
|
local function populate_configure_menu(menu)
|
|
local button_name
|
|
if current_button.button then
|
|
button_name = current_button.button.name
|
|
elseif current_button.port then
|
|
button_name = _p('plugin-autofire', 'n/a')
|
|
else
|
|
button_name = _p('plugin-autofire', '[not set]')
|
|
end
|
|
local key_name = current_button.key and manager.machine.input:seq_name(current_button.key) or _p('plugin-autofire', '[not set]')
|
|
table.insert(menu, {_p('plugin-autofire', 'Input'), button_name, ''})
|
|
if not (configure_menu_active or configure_selection_save) then
|
|
configure_selection_save = #menu
|
|
end
|
|
table.insert(menu, {_p('plugin-autofire', 'Hotkey'), key_name, hotkey_poller and 'lr' or ''})
|
|
table.insert(menu, {_p('plugin-autofire', 'On frames'), tostring(current_button.on_frames), current_button.on_frames > 1 and 'lr' or 'r'})
|
|
table.insert(menu, {_p('plugin-autofire', 'Off frames'), tostring(current_button.off_frames), current_button.off_frames > 1 and 'lr' or 'r'})
|
|
configure_menu_active = true
|
|
end
|
|
|
|
local function handle_configure_menu(index, event)
|
|
if hotkey_poller then
|
|
-- special handling for polling for hotkey
|
|
if hotkey_poller:poll() then
|
|
if hotkey_poller.sequence then
|
|
current_button.key = hotkey_poller.sequence
|
|
current_button.key_cfg = manager.machine.input:seq_to_tokens(hotkey_poller.sequence)
|
|
end
|
|
hotkey_poller = nil
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
if index == 1 then
|
|
-- Input
|
|
if event == 'select' then
|
|
configure_selection_save = header_height + index
|
|
table.insert(menu_stack, MENU_TYPES.BUTTON)
|
|
if current_button.port and current_button.button then
|
|
initial_input = current_button.button
|
|
end
|
|
return true
|
|
end
|
|
elseif index == 2 then
|
|
-- Hotkey
|
|
if event == 'select' then
|
|
if not commonui then
|
|
commonui = require('commonui')
|
|
end
|
|
hotkey_poller = commonui.switch_polling_helper()
|
|
return true
|
|
end
|
|
elseif index == 3 then
|
|
-- On frames
|
|
manager.machine:popmessage(_p('plugin-autofire', 'Number of frames button will be pressed'))
|
|
if event == 'left' then
|
|
current_button.on_frames = current_button.on_frames - 1
|
|
return true
|
|
elseif event == 'right' then
|
|
current_button.on_frames = current_button.on_frames + 1
|
|
return true
|
|
elseif event == 'clear' then
|
|
current_button.on_frames = 1
|
|
return true
|
|
end
|
|
elseif index == 4 then
|
|
-- Off frames
|
|
manager.machine:popmessage(_p('plugin-autofire', 'Number of frames button will be released'))
|
|
if event == 'left' then
|
|
current_button.off_frames = current_button.off_frames - 1
|
|
return true
|
|
elseif event == 'right' then
|
|
current_button.off_frames = current_button.off_frames + 1
|
|
return true
|
|
elseif event == 'clear' then
|
|
current_button.off_frames = 1
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function populate_edit_menu()
|
|
local menu = {}
|
|
table.insert(menu, {_p('plugin-autofire', 'Edit autofire button'), '', 'off'})
|
|
table.insert(menu, {'---', '', ''})
|
|
header_height = #menu
|
|
|
|
populate_configure_menu(menu)
|
|
content_height = #menu
|
|
|
|
table.insert(menu, {'---', '', ''})
|
|
table.insert(menu, {_p('plugin-autofire', 'Done'), '', ''})
|
|
|
|
local selection = configure_selection_save
|
|
configure_selection_save = nil
|
|
if hotkey_poller then
|
|
return hotkey_poller:overlay(menu, selection, 'lrrepeat')
|
|
else
|
|
return menu, selection, 'lrrepeat'
|
|
end
|
|
end
|
|
|
|
local function handle_edit_menu(index, event, buttons)
|
|
local section, adjusted_index = menu_section(index)
|
|
if ((section == MENU_SECTIONS.FOOTER) and (event == 'select')) or (event == 'cancel') then
|
|
configure_menu_active = false
|
|
table.remove(menu_stack)
|
|
return true
|
|
elseif section == MENU_SECTIONS.CONTENT then
|
|
return handle_configure_menu(adjusted_index, event)
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function populate_add_menu()
|
|
local menu = {}
|
|
table.insert(menu, {_p('plugin-autofire', 'Add autofire button'), '', 'off'})
|
|
table.insert(menu, {'---', '', ''})
|
|
header_height = #menu
|
|
|
|
populate_configure_menu(menu)
|
|
content_height = #menu
|
|
|
|
table.insert(menu, {'---', '', ''})
|
|
if is_button_complete(current_button) then
|
|
table.insert(menu, {_p('plugin-autofire', 'Create'), '', ''})
|
|
else
|
|
table.insert(menu, {_p('plugin-autofire', 'Cancel'), '', ''})
|
|
end
|
|
|
|
local selection = configure_selection_save
|
|
configure_selection_save = nil
|
|
if hotkey_poller then
|
|
return hotkey_poller:overlay(menu, selection, 'lrrepeat')
|
|
else
|
|
return menu, selection, 'lrrepeat'
|
|
end
|
|
end
|
|
|
|
local function handle_add_menu(index, event, buttons)
|
|
local section, adjusted_index = menu_section(index)
|
|
if ((section == MENU_SECTIONS.FOOTER) and (event == 'select')) or (event == 'cancel') then
|
|
configure_menu_active = false
|
|
table.remove(menu_stack)
|
|
if is_button_complete(current_button) and (event == 'select') then
|
|
table.insert(buttons, current_button)
|
|
initial_button = #buttons
|
|
end
|
|
return true
|
|
elseif section == MENU_SECTIONS.CONTENT then
|
|
return handle_configure_menu(adjusted_index, event)
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Button selection menu
|
|
|
|
local function populate_button_menu()
|
|
local function is_supported_input(ioport_field)
|
|
-- IPT_BUTTON1 through IPT_BUTTON16 in ioport_type enum (ioport.h)
|
|
return ioport_field.type >= 64 and ioport_field.type <= 79
|
|
end
|
|
|
|
local function action(field)
|
|
if field then
|
|
current_button.port = field.port.tag
|
|
current_button.mask = field.mask
|
|
current_button.type = field.type
|
|
current_button.button = field
|
|
end
|
|
initial_input = nil
|
|
input_menu = nil
|
|
table.remove(menu_stack)
|
|
end
|
|
|
|
if not commonui then
|
|
commonui = require('commonui')
|
|
end
|
|
input_menu = commonui.input_selection_menu(action, _p('plugin-autofire', 'Select an input for autofire'), is_supported_input)
|
|
return input_menu:populate(initial_input)
|
|
end
|
|
|
|
local function handle_button_menu(index, event)
|
|
return input_menu:handle(index, event)
|
|
end
|
|
|
|
function lib:init_menu(buttons)
|
|
header_height = 0
|
|
content_height = 0
|
|
menu_stack = { MENU_TYPES.MAIN }
|
|
current_button = {}
|
|
input_menu = nil
|
|
end
|
|
|
|
function lib:populate_menu(buttons)
|
|
local current_menu = menu_stack[#menu_stack]
|
|
if current_menu == MENU_TYPES.MAIN then
|
|
return populate_main_menu(buttons)
|
|
elseif current_menu == MENU_TYPES.EDIT then
|
|
return populate_edit_menu()
|
|
elseif current_menu == MENU_TYPES.ADD then
|
|
return populate_add_menu()
|
|
elseif current_menu == MENU_TYPES.BUTTON then
|
|
return populate_button_menu()
|
|
end
|
|
end
|
|
|
|
function lib:handle_menu_event(index, event, buttons)
|
|
manager.machine:popmessage()
|
|
local current_menu = menu_stack[#menu_stack]
|
|
if current_menu == MENU_TYPES.MAIN then
|
|
return handle_main_menu(index, event, buttons)
|
|
elseif current_menu == MENU_TYPES.EDIT then
|
|
return handle_edit_menu(index, event, buttons)
|
|
elseif current_menu == MENU_TYPES.ADD then
|
|
return handle_add_menu(index, event, buttons)
|
|
elseif current_menu == MENU_TYPES.BUTTON then
|
|
return handle_button_menu(index, event)
|
|
end
|
|
end
|
|
|
|
return lib
|